import Foundation
import UIKit
import SwiftSignalKit
import Display
import Postbox
import TelegramCore
import TelegramPresentationData
import ComponentFlow
import ViewControllerComponent
import AccountContext
import ContextUI
import ShareController
import UndoUI
import BundleIconComponent
import TelegramUIPreferences
import OpenInExternalAppUI
import MultilineTextComponent
import MinimizedContainer
import InstantPageUI
import NavigationStackComponent
import LottieComponent
import WebKit

private let settingsTag = GenericComponentViewTag()

private final class BrowserScreenComponent: CombinedComponent {
    typealias EnvironmentType = ViewControllerComponentContainer.Environment
    
    let context: AccountContext
    let contentState: BrowserContentState?
    let presentationState: BrowserPresentationState
    let canShare: Bool
    let performAction: ActionSlot<BrowserScreen.Action>
    let performHoldAction: (UIView, ContextGesture?, BrowserScreen.Action) -> Void
    let panelCollapseFraction: CGFloat
    
    init(
        context: AccountContext,
        contentState: BrowserContentState?,
        presentationState: BrowserPresentationState,
        canShare: Bool,
        performAction: ActionSlot<BrowserScreen.Action>,
        performHoldAction: @escaping (UIView, ContextGesture?, BrowserScreen.Action) -> Void,
        panelCollapseFraction: CGFloat
    ) {
        self.context = context
        self.contentState = contentState
        self.presentationState = presentationState
        self.canShare = canShare
        self.performAction = performAction
        self.performHoldAction = performHoldAction
        self.panelCollapseFraction = panelCollapseFraction
    }
    
    static func ==(lhs: BrowserScreenComponent, rhs: BrowserScreenComponent) -> Bool {
        if lhs.context !== rhs.context {
            return false
        }
        if lhs.contentState != rhs.contentState {
            return false
        }
        if lhs.presentationState != rhs.presentationState {
            return false
        }
        if lhs.canShare != rhs.canShare {
            return false
        }
        if lhs.panelCollapseFraction != rhs.panelCollapseFraction {
            return false
        }
        return true
    }
    
    final class State: ComponentState {
    }
    
    func makeState() -> State {
        return State()
    }
    
    static var body: Body {
        let navigationBar = Child(BrowserNavigationBarComponent.self)
        let toolbar = Child(BrowserToolbarComponent.self)
        let addressList = Child(BrowserAddressListComponent.self)
        
        let navigationBarExternalState = BrowserNavigationBarComponent.ExternalState()
        
        return { context in
            let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
            let performAction = context.component.performAction
            let performHoldAction = context.component.performHoldAction
            
            let isTablet = environment.metrics.isTablet
            let canOpenIn = !(context.component.contentState?.url.hasPrefix("tonsite") ?? false)
            
            let navigationContent: AnyComponentWithIdentity<BrowserNavigationBarEnvironment>?
            var navigationLeftItems: [AnyComponentWithIdentity<Empty>]
            var navigationRightItems: [AnyComponentWithIdentity<Empty>]
            if context.component.presentationState.isSearching {
                navigationContent = AnyComponentWithIdentity(
                    id: "search",
                    component: AnyComponent(
                        SearchBarContentComponent(
                            theme: environment.theme,
                            strings: environment.strings,
                            performAction: performAction
                        )
                    )
                )
                navigationLeftItems = []
                navigationRightItems = []
            } else {
                let contentType = context.component.contentState?.contentType ?? .instantPage
                switch contentType {
                case .webPage:
                    navigationContent = AnyComponentWithIdentity(
                        id: "addressBar",
                        component: AnyComponent(
                            AddressBarContentComponent(
                                theme: environment.theme,
                                strings: environment.strings,
                                metrics: environment.metrics,
                                url: context.component.contentState?.url ?? "",
                                isSecure: context.component.contentState?.isSecure ?? false,
                                isExpanded: context.component.presentationState.addressFocused,
                                performAction: performAction
                            )
                        )
                    )
                case .instantPage, .document:
                    let title = context.component.contentState?.title ?? ""
                    navigationContent = AnyComponentWithIdentity(
                        id: "titleBar_\(title)",
                        component: AnyComponent(
                            TitleBarContentComponent(
                                theme: environment.theme,
                                title: title
                            )
                        )
                    )
                }
               
                if context.component.presentationState.addressFocused && !isTablet {
                    navigationLeftItems = []
                    navigationRightItems = []
                } else {
                    navigationLeftItems = [
                        AnyComponentWithIdentity(
                            id: "close",
                            component: AnyComponent(
                                Button(
                                    content: AnyComponent(
                                        MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.WebBrowser_Done, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.accentTextColor, paragraphAlignment: .center)), horizontalAlignment: .left, maximumNumberOfLines: 1)
                                    ),
                                    action: {
                                        performAction.invoke(.close)
                                    }
                                )
                            )
                        )
                    ]
                                        
                    if isTablet {
                        #if DEBUG
                        navigationLeftItems.append(
                            AnyComponentWithIdentity(
                                id: "minimize",
                                component: AnyComponent(
                                    Button(
                                        content: AnyComponent(
                                            BundleIconComponent(
                                                name: "Media Gallery/PictureInPictureButton",
                                                tintColor: environment.theme.rootController.navigationBar.accentTextColor
                                            )
                                        ),
                                        action: {
                                            performAction.invoke(.close)
                                        }
                                    )
                                )
                            )
                        )
                        #endif
                        
                        let canGoBack = context.component.contentState?.canGoBack ?? false
                        let canGoForward = context.component.contentState?.canGoForward ?? false
                        
                        navigationLeftItems.append(
                            AnyComponentWithIdentity(
                                id: "back",
                                component: AnyComponent(
                                    Button(
                                        content: AnyComponent(
                                            BundleIconComponent(
                                                name: "Instant View/Back",
                                                tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoBack ? 1.0 : 0.4)
                                            )
                                        ),
                                        action: {
                                            performAction.invoke(.navigateBack)
                                        }
                                    )
                                )
                            )
                        )
                        
                        navigationLeftItems.append(
                            AnyComponentWithIdentity(
                                id: "forward",
                                component: AnyComponent(
                                    Button(
                                        content: AnyComponent(
                                            BundleIconComponent(
                                                name: "Instant View/Forward",
                                                tintColor: environment.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(canGoForward ? 1.0 : 0.4)
                                            )
                                        ),
                                        action: {
                                            performAction.invoke(.navigateForward)
                                        }
                                    )
                                )
                            )
                        )
                    }
                    
                    navigationRightItems = [
                        AnyComponentWithIdentity(
                            id: "settings",
                            component: AnyComponent(
                                ReferenceButtonComponent(
                                    content: AnyComponent(
                                        LottieComponent(
                                            content: LottieComponent.AppBundleContent(
                                                name: "anim_moredots"
                                            ),
                                            color: environment.theme.rootController.navigationBar.accentTextColor,
                                            size: CGSize(width: 30.0, height: 30.0)
                                        )
                                    ),
                                    tag: settingsTag,
                                    action: {
                                        performAction.invoke(.openSettings)
                                    }
                                )
                            )
                        )
                    ]
                    
                    if isTablet {
                        navigationRightItems.insert(
                            AnyComponentWithIdentity(
                                id: "bookmarks",
                                component: AnyComponent(
                                    Button(
                                        content: AnyComponent(
                                            BundleIconComponent(
                                                name: "Instant View/Bookmark",
                                                tintColor: environment.theme.rootController.navigationBar.accentTextColor
                                            )
                                        ),
                                        action: {
                                            performAction.invoke(.openBookmarks)
                                        }
                                    )
                                )
                            ),
                            at: 0
                        )
                        if context.component.canShare {
                            navigationRightItems.insert(
                                AnyComponentWithIdentity(
                                    id: "share",
                                    component: AnyComponent(
                                        Button(
                                            content: AnyComponent(
                                                BundleIconComponent(
                                                    name: "Chat List/NavigationShare",
                                                    tintColor: environment.theme.rootController.navigationBar.accentTextColor
                                                )
                                            ),
                                            action: {
                                                performAction.invoke(.share)
                                            }
                                        )
                                    )
                                ),
                                at: 0
                            )
                        }
                        if canOpenIn {
                            navigationRightItems.append(
                                AnyComponentWithIdentity(
                                    id: "openIn",
                                    component: AnyComponent(
                                        Button(
                                            content: AnyComponent(
                                                BundleIconComponent(
                                                    name: "Instant View/Browser",
                                                    tintColor: environment.theme.rootController.navigationBar.accentTextColor
                                                )
                                            ),
                                            action: {
                                                performAction.invoke(.openIn)
                                            }
                                        )
                                    )
                                )
                            )
                        }
                    }
                }
            }
            
            let collapseFraction = context.component.presentationState.isSearching ? 0.0 : context.component.panelCollapseFraction
            
            let navigationBar = navigationBar.update(
                component: BrowserNavigationBarComponent(
                    backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor,
                    separatorColor: environment.theme.rootController.navigationBar.separatorColor,
                    textColor: environment.theme.rootController.navigationBar.primaryTextColor,
                    progressColor: environment.theme.rootController.navigationBar.segmentedBackgroundColor,
                    accentColor: environment.theme.rootController.navigationBar.accentTextColor,
                    topInset: environment.statusBarHeight,
                    height: environment.navigationHeight - environment.statusBarHeight,
                    sideInset: environment.safeInsets.left,
                    metrics: environment.metrics,
                    externalState: navigationBarExternalState,
                    leftItems: navigationLeftItems,
                    rightItems: navigationRightItems,
                    centerItem: navigationContent,
                    readingProgress: context.component.contentState?.readingProgress ?? 0.0,
                    loadingProgress: context.component.contentState?.estimatedProgress,
                    collapseFraction: collapseFraction,
                    activate: {
                        performAction.invoke(.expand)
                    }
                ),
                availableSize: context.availableSize,
                transition: context.transition
            )
            context.add(navigationBar
                .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height / 2.0))
            )
            
            let toolbarContent: AnyComponentWithIdentity<Empty>?
            if context.component.presentationState.isSearching {
                toolbarContent = AnyComponentWithIdentity(
                    id: "search",
                    component: AnyComponent(
                        SearchToolbarContentComponent(
                            strings: environment.strings,
                            textColor: environment.theme.rootController.navigationBar.primaryTextColor,
                            index: context.component.presentationState.searchResultIndex,
                            count: context.component.presentationState.searchResultCount,
                            isEmpty: context.component.presentationState.searchQueryIsEmpty,
                            performAction: performAction
                        )
                    )
                )
            } else {
                toolbarContent = AnyComponentWithIdentity(
                    id: "navigation",
                    component: AnyComponent(
                        NavigationToolbarContentComponent(
                            accentColor: environment.theme.rootController.navigationBar.accentTextColor,
                            textColor: environment.theme.rootController.navigationBar.primaryTextColor,
                            canGoBack: context.component.contentState?.canGoBack ?? false,
                            canGoForward: context.component.contentState?.canGoForward ?? false,
                            canOpenIn: canOpenIn,
                            canShare: context.component.canShare,
                            isDocument: context.component.contentState?.contentType == .document,
                            performAction: performAction,
                            performHoldAction: performHoldAction
                        )
                    )
                )
            }
            
            let toolbarBottomInset: CGFloat
            if context.component.presentationState.isSearching && environment.inputHeight > 0.0 {
                toolbarBottomInset = environment.inputHeight
            } else {
                toolbarBottomInset = environment.safeInsets.bottom
            }
            
            var toolbarSize: CGFloat = 0.0
            if isTablet && !context.component.presentationState.isSearching {
                
            } else {
                let toolbar = toolbar.update(
                    component: BrowserToolbarComponent(
                        backgroundColor: environment.theme.rootController.navigationBar.blurredBackgroundColor,
                        separatorColor: environment.theme.rootController.navigationBar.separatorColor,
                        textColor: environment.theme.rootController.navigationBar.primaryTextColor,
                        bottomInset: toolbarBottomInset,
                        sideInset: environment.safeInsets.left,
                        item: toolbarContent,
                        collapseFraction: 0.0
                    ),
                    availableSize: context.availableSize,
                    transition: context.transition
                )
                context.add(toolbar
                    .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0 + toolbar.size.height * collapseFraction))
                    .appear(ComponentTransition.Appear { _, view, transition in
                        transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: view.frame.height), to: CGPoint(), additive: true)
                    })
                    .disappear(ComponentTransition.Disappear { view, transition, completion in
                        transition.animatePosition(view: view, from: CGPoint(), to: CGPoint(x: 0.0, y: view.frame.height), additive: true, completion: { _ in
                            completion()
                        })
                    })
                )
                toolbarSize = toolbar.size.height
            }
            
            if context.component.presentationState.addressFocused {
                let addressListSize: CGSize
                if isTablet {
                    addressListSize = context.availableSize
                } else {
                    addressListSize = CGSize(width: context.availableSize.width, height: context.availableSize.height - navigationBar.size.height - toolbarSize)
                }
                let controller = environment.controller
                let addressList = addressList.update(
                    component: BrowserAddressListComponent(
                        context: context.component.context,
                        theme: environment.theme,
                        strings: environment.strings,
                        insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right),
                        metrics: environment.metrics,
                        addressBarFrame: navigationBarExternalState.centerItemFrame,
                        performAction: performAction,
                        presentInGlobalOverlay: { c in
                            controller()?.presentInGlobalOverlay(c)
                        }
                    ),
                    availableSize: addressListSize,
                    transition: context.transition
                )
                
                if isTablet {
                    context.add(addressList
                        .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
                        .appear(.default(alpha: true))
                        .disappear(.default(alpha: true))
                    )
                } else {
                    context.add(addressList
                        .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height + addressList.size.height / 2.0))
                        .clipsToBounds(true)
                        .appear(.default(alpha: true))
                        .disappear(.default(alpha: true))
                    )
                }
            }
            
            return context.availableSize
        }
    }
}

struct BrowserPresentationState: Equatable {
    struct FontState: Equatable {
        var size: Int32
        var isSerif: Bool
    }
    var fontState: FontState
    var isSearching: Bool
    var searchResultIndex: Int
    var searchResultCount: Int
    var searchQueryIsEmpty: Bool
    var addressFocused: Bool
}

public class BrowserScreen: ViewController, MinimizableController {
    enum Action {
        case close
        case reload
        case stop
        case navigateBack
        case navigateForward
        case share
        case minimize
        case openIn
        case openSettings
        case updateSearchActive(Bool)
        case updateSearchQuery(String)
        case scrollToPreviousSearchResult
        case scrollToNextSearchResult
        case decreaseFontSize
        case increaseFontSize
        case resetFontSize
        case updateFontIsSerif(Bool)
        case toggleInstantView(Bool)
        case addBookmark
        case openBookmarks
        case openAddressBar
        case closeAddressBar
        case navigateTo(String, Bool)
        case expand
    }

    final class Node: ViewControllerTracingNode {
        private weak var controller: BrowserScreen?
        private let context: AccountContext
        
        private let contentContainerView = UIView()
        fileprivate let contentNavigationContainer = ComponentView<Empty>()
        private(set) var content: [BrowserContent] = []
        fileprivate var contentState: BrowserContentState?
        private var contentStateDisposable = MetaDisposable()
        
        private var presentationState: BrowserPresentationState
        
        private let performAction = ActionSlot<BrowserScreen.Action>()
        
        fileprivate let componentHost = ComponentView<ViewControllerComponentContainer.Environment>()
        
        private var presentationData: PresentationData
        private var presentationDataDisposable: Disposable?
        private var validLayout: (ContainerViewLayout, CGFloat)?
                
        init(controller: BrowserScreen) {
            self.context = controller.context
            self.controller = controller
            self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
            
            self.presentationState = BrowserPresentationState(
                fontState: BrowserPresentationState.FontState(size: 100, isSerif: false),
                isSearching: false, 
                searchResultIndex: 0,
                searchResultCount: 0,
                searchQueryIsEmpty: true,
                addressFocused: false
            )
                                                
            super.init()
            
            self.pushContent(controller.subject, transition: .immediate)
            if let content = self.content.last {
                content.addToRecentlyVisited()
            }
            
            self.performAction.connect { [weak self] action in
                guard let self, let content = self.content.last, let url = self.contentState?.url else {
                    return
                }
                switch action {
                case .close:
                    self.controller?.dismiss()
                case .reload:
                    content.reload()
                case .stop:
                    content.stop()
                case .navigateBack:
                    if content.currentState.canGoBack {
                        content.navigateBack()
                    } else {
                        self.popContent(transition: .spring(duration: 0.4))
                    }
                case .navigateForward:
                    content.navigateForward()
                case .share:
                    let presentationData = self.presentationData
                    let subject: ShareControllerSubject
                    var isDocument = false
                    if let content = self.content.last {
                        if let documentContent = content as? BrowserDocumentContent {
                            subject = .media(.standalone(media: documentContent.file))
                            isDocument = true
                        } else if let documentContent = content as? BrowserPdfContent {
                            subject = .media(.standalone(media: documentContent.file))
                            isDocument = true
                        } else {
                            subject = .url(url)
                        }
                    } else {
                        subject = .url(url)
                    }
                    let shareController = ShareController(context: self.context, subject: subject)
                    shareController.completed = { [weak self] peerIds in
                        guard let strongSelf = self else {
                            return
                        }
                        let _ = (strongSelf.context.engine.data.get(
                            EngineDataList(
                                peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
                            )
                        )
                        |> deliverOnMainQueue).startStandalone(next: { [weak self] peerList in
                            guard let strongSelf = self else {
                                return
                            }
                            
                            let peers = peerList.compactMap { $0 }
                            let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
                            
                            let text: String
                            var savedMessages = false
                            if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId && !isDocument {
                                text = presentationData.strings.WebBrowser_LinkAddedToBookmarks
                                savedMessages = true
                            } else {
                                if peers.count == 1, let peer = peers.first {
                                    let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
                                    text = isDocument ? presentationData.strings.WebBrowser_FileForwardTooltip_Chat_One(peerName).string : presentationData.strings.WebBrowser_LinkForwardTooltip_Chat_One(peerName).string
                                    savedMessages = peer.id == strongSelf.context.account.peerId
                                } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
                                    let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
                                    let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
                                    text = isDocument ? presentationData.strings.WebBrowser_FileForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string : presentationData.strings.WebBrowser_LinkForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string
                                } else if let peer = peers.first {
                                    let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
                                    text = isDocument ? presentationData.strings.WebBrowser_FileForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string : presentationData.strings.WebBrowser_LinkForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
                                } else {
                                    text = ""
                                }
                            }
                            
                            strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
                                if savedMessages, let self, action == .info {
                                    let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
                                             |> deliverOnMainQueue).start(next: { [weak self] peer in
                                        guard let self, let peer else {
                                            return
                                        }
                                        guard let navigationController = self.controller?.navigationController as? NavigationController else {
                                            return
                                        }
                                        self.minimize()
                                        self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true))
                                    })
                                }
                                return false
                            }), in: .current)
                        })
                    }
                    shareController.actionCompleted = { [weak self] in
                        self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
                    }
                    self.controller?.present(shareController, in: .window(.root))
                case .minimize:
                    self.minimize()
                case .openIn:
                    var processed = false
                    if let controller = self.controller {
                        switch controller.subject {
                        case let .document(file, canShare), let .pdfDocument(file, canShare):
                            processed = true
                            controller.openDocument(file, canShare)
                        default:
                            break
                        }
                    }
                    if !processed {
                        self.context.sharedContext.applicationBindings.openUrl(url)
                    }
                case .openSettings:
                    self.openSettings()
                case let .updateSearchActive(active):
                    self.updatePresentationState(transition: .easeInOut(duration: 0.2), { state in
                        var updatedState = state
                        updatedState.isSearching = active
                        updatedState.searchQueryIsEmpty = true
                        return updatedState
                    })
                    if !active {
                        content.setSearch(nil, completion: nil)
                    }
                case let .updateSearchQuery(query):
                    content.setSearch(query, completion: { [weak self] count in
                        self?.updatePresentationState({ state in
                            var updatedState = state
                            updatedState.searchResultIndex = 0
                            updatedState.searchResultCount = count
                            updatedState.searchQueryIsEmpty = query.isEmpty
                            return updatedState
                        })
                    })
                case .scrollToPreviousSearchResult:
                    self.view.window?.endEditing(true)
                    content.scrollToPreviousSearchResult(completion: { [weak self] index, count in
                        self?.updatePresentationState({ state in
                            var updatedState = state
                            updatedState.searchResultIndex = index
                            updatedState.searchResultCount = count
                            return updatedState
                        })
                    })
                case .scrollToNextSearchResult:
                    self.view.window?.endEditing(true)
                    content.scrollToNextSearchResult(completion: { [weak self] index, count in
                        self?.updatePresentationState({ state in
                            var updatedState = state
                            updatedState.searchResultIndex = index
                            updatedState.searchResultCount = count
                            return updatedState
                        })
                    })
                case .decreaseFontSize:
                    self.updatePresentationState({ state in
                        var updatedState = state
                        switch state.fontState.size {
                        case 150:
                            updatedState.fontState.size = 125
                        case 125:
                            updatedState.fontState.size = 115
                        case 115:
                            updatedState.fontState.size = 100
                        case 100:
                            updatedState.fontState.size = 85
                        case 85:
                            updatedState.fontState.size = 75
                        case 75:
                            updatedState.fontState.size = 50
                        default:
                            updatedState.fontState.size = 50
                        }
                        return updatedState
                    })
                    content.updateFontState(self.presentationState.fontState)
                case .increaseFontSize:
                    self.updatePresentationState({ state in
                        var updatedState = state
                        switch state.fontState.size {
                        case 125:
                            updatedState.fontState.size = 150
                        case 115:
                            updatedState.fontState.size = 125
                        case 100:
                            updatedState.fontState.size = 115
                        case 85:
                            updatedState.fontState.size = 100
                        case 75:
                            updatedState.fontState.size = 85
                        case 50:
                            updatedState.fontState.size = 75
                        default:
                            updatedState.fontState.size = 150
                        }
                        return updatedState
                    })
                    content.updateFontState(self.presentationState.fontState)
                case .resetFontSize:
                    self.updatePresentationState({ state in
                        var updatedState = state
                        updatedState.fontState.size = 100
                        return updatedState
                    })
                    content.updateFontState(self.presentationState.fontState)
                case let .updateFontIsSerif(value):
                    self.updatePresentationState({ state in
                        var updatedState = state
                        updatedState.fontState.isSerif = value
                        return updatedState
                    })
                    content.updateFontState(self.presentationState.fontState)
                case let .toggleInstantView(enabled):
                    content.toggleInstantView(enabled)
                case .addBookmark:
                    if let content = self.content.last {
                        self.addBookmark(content.currentState.url, showArrow: true)
                    }
                case .openBookmarks:
                    self.openBookmarks()
                case .openAddressBar:
                    self.updatePresentationState(transition: .spring(duration: 0.4), { state in
                        var updatedState = state
                        updatedState.addressFocused = true
                        return updatedState
                    })
                case .closeAddressBar:
                    self.updatePresentationState(transition: .spring(duration: 0.4), { state in
                        var updatedState = state
                        updatedState.addressFocused = false
                        return updatedState
                    })
                case let .navigateTo(address, addToRecent):
                    if let content = self.content.last as? BrowserWebContent {
                        content.navigateTo(address: address)
                        if addToRecent {
                            content.addToRecentlyVisited()
                        }
                    }
                    self.updatePresentationState(transition: .spring(duration: 0.4), { state in
                        var updatedState = state
                        updatedState.addressFocused = false
                        return updatedState
                    })
                case .expand:
                    if let content = self.content.last {
                        content.resetScrolling()
                    }
                }
            }
            
            self.presentationDataDisposable = (controller.context.sharedContext.presentationData
            |> deliverOnMainQueue).start(next: { [weak self] presentationData in
                guard let self else {
                    return
                }
                self.presentationData = presentationData
                for content in self.content {
                    content.updatePresentationData(presentationData)
                }
                self.requestLayout(transition: .immediate)
            })
        }
        
        deinit {
            self.presentationDataDisposable?.dispose()
            self.contentStateDisposable.dispose()
        }
        
        override func didLoad() {
            super.didLoad()
            
            self.contentContainerView.clipsToBounds = true
            self.view.addSubview(self.contentContainerView)
        }
        
        func updatePresentationState(transition: ComponentTransition = .immediate, _ f: (BrowserPresentationState) -> BrowserPresentationState) {
            self.presentationState = f(self.presentationState)
            self.requestLayout(transition: transition)
        }
        
        func pushContent(_ content: BrowserScreen.Subject, additionalContent: BrowserContent? = nil, transition: ComponentTransition) {
            let browserContent: BrowserContent
            switch content {
            case let .webPage(url):
                let webContent = BrowserWebContent(context: self.context, presentationData: self.presentationData, url: url, preferredConfiguration: self.controller?.preferredConfiguration)
                webContent.cancelInteractiveTransitionGestures = { [weak self] in
                    if let self, let view = self.controller?.view {
                        cancelInteractiveTransitionGestures(view: view)
                    }
                }
                browserContent = webContent
                self.controller?.preferredConfiguration = nil
            case let .instantPage(webPage, anchor, sourceLocation, preloadedResouces):
                let instantPageContent = BrowserInstantPageContent(context: self.context, presentationData: self.presentationData, webPage: webPage, anchor: anchor, url: webPage.content.url ?? "", sourceLocation: sourceLocation, preloadedResouces: preloadedResouces, originalContent: additionalContent)
                instantPageContent.openPeer = { [weak self] peer in
                    guard let self else {
                        return
                    }
                    self.openPeer(peer)
                }
                instantPageContent.restoreContent = { [weak self, weak instantPageContent] content in
                    guard let self, let instantPageContent else {
                        return
                    }
                    self.pushBrowserContent(content, additionalContent: instantPageContent, transition: .easeInOut(duration: 0.3).withUserData(NavigationStackComponent<Empty>.CurlTransition.hide))
                }
                browserContent = instantPageContent
            case let .document(file, _):
                browserContent = BrowserDocumentContent(context: self.context, presentationData: self.presentationData, file: file)
            case let .pdfDocument(file, _):
                browserContent = BrowserPdfContent(context: self.context, presentationData: self.presentationData, file: file)
            }
            browserContent.pushContent = { [weak self] content, additionalContent in
                guard let self else {
                    return
                }
                var transition: ComponentTransition
                if let _ = additionalContent {
                    transition = .easeInOut(duration: 0.3).withUserData(NavigationStackComponent<Empty>.CurlTransition.show)
                } else {
                    transition = .spring(duration: 0.4)
                }
                self.pushContent(content, additionalContent: additionalContent, transition: transition)
            }
            browserContent.openAppUrl = { [weak self] url in
                guard let self else {
                    return
                }
                self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: false, presentationData: self.presentationData, navigationController: self.controller?.navigationController as? NavigationController, dismissInput: { [weak self] in
                    self?.view.window?.endEditing(true)
                })
            }
            browserContent.present = { [weak self] c, a in
                guard let self, let controller = self.controller else {
                    return
                }
                controller.present(c, in: .window(.root), with: a)
            }
            browserContent.presentInGlobalOverlay = { [weak self] c in
                guard let self, let controller = self.controller else {
                    return
                }
                controller.presentInGlobalOverlay(c)
            }
            browserContent.getNavigationController = { [weak self] in
                return self?.controller?.navigationController as? NavigationController
            }
            browserContent.minimize = { [weak self] in
                guard let self else {
                    return
                }
                self.minimize()
            }
            browserContent.close = { [weak self] in
                guard let self, let controller = self.controller else {
                    return
                }
                if controller.isMinimized {
                    if let navigationController = controller.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
                        minimizedContainer.removeController(controller)
                    }
                } else {
                    controller.dismiss()
                }
            }
            
            self.pushBrowserContent(browserContent, additionalContent: additionalContent, transition: transition)
        }
        
        func pushBrowserContent(_ browserContent: BrowserContent, additionalContent: BrowserContent? = nil, transition: ComponentTransition) {
            if let additionalContent, let index = self.content.firstIndex(where: { $0 === additionalContent }) {
                self.content[index] = browserContent
            } else {
                self.content.append(browserContent)
            }
            self.requestLayout(transition: transition)
            
            self.setupContentStateUpdates()
        }
        
        func popContent(transition: ComponentTransition) {
            self.content.removeLast()
            self.requestLayout(transition: transition)
            
            self.setupContentStateUpdates()
        }
        
        func openPeer(_ peer: EnginePeer) {
            guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else {
                return
            }
            self.minimize()
            self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), animated: true))
        }
        
        func addBookmark(_ url: String, showArrow: Bool) {
            let _ = enqueueMessages(
                account: self.context.account,
                peerId: self.context.account.peerId,
                messages: [.message(
                    text: url,
                    attributes: [],
                    inlineStickers: [:],
                    mediaReference: nil,
                    threadId: nil,
                    replyToMessageId: nil,
                    replyToStoryId: nil,
                    localGroupingKey: nil,
                    correlationId: nil,
                    bubbleUpEmojiOrStickersets: []
                )]
            ).start()
            
            let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
            
            let lastController = self.controller?.navigationController?.viewControllers.last as? ViewController
            lastController?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: presentationData.strings.WebBrowser_LinkAddedToBookmarks), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in
                if let self, action == .info {
                    let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
                        |> deliverOnMainQueue).start(next: { [weak self] peer in
                        guard let self, let peer else {
                            return
                        }
                        guard let navigationController = self.controller?.navigationController as? NavigationController else {
                            return
                        }
                        self.minimize()
                        self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), forceOpenChat: true))
                    })
                }
                return false
            }), in: .current)
        }
        
        private func setupContentStateUpdates() {
            for content in self.content {
                content.onScrollingUpdate = { _ in }
            }
            
            guard let content = self.content.last else {
                self.controller?.title = ""
                self.contentState = nil
                self.contentStateDisposable.set(nil)
                self.requestLayout(transition: .easeInOut(duration: 0.25))
                return
            }
            
            var previousState = BrowserContentState(title: "", url: "", estimatedProgress: 1.0, readingProgress: 0.0, contentType: .webPage, canGoBack: false, canGoForward: false, backList: [], forwardList: [])
            if self.content.count > 1 {
                for content in self.content.prefix(upTo: self.content.count - 1) {
                    var backList = previousState.backList
                    backList.append(BrowserContentState.HistoryItem(url: content.currentState.url, title: content.currentState.title, uuid: content.uuid))
                    previousState = previousState.withUpdatedBackList(backList)
                }
            }
            
            self.contentStateDisposable.set((content.state
            |> deliverOnMainQueue).startStrict(next: { [weak self] state in
                guard let self else {
                    return
                }
                var backList = state.backList
                backList.insert(contentsOf: previousState.backList, at: 0)
                
                var canGoBack = state.canGoBack
                if !backList.isEmpty {
                    canGoBack = true
                }
                
                let previousState = self.contentState
                let state = state.withUpdatedCanGoBack(canGoBack).withUpdatedBackList(backList)
                self.controller?.title = state.title
                self.contentState = state
                
                if !self.isUpdating {
                    let transition: ComponentTransition
                    if let previousState, previousState.withUpdatedReadingProgress(state.readingProgress) == state {
                        transition = .immediate
                    } else {
                        transition = .easeInOut(duration: 0.25)
                    }
                    self.requestLayout(transition: transition)
                }
            }))
                        
            content.onScrollingUpdate = { [weak self] update in
                self?.onContentScrollingUpdate(update)
            }
        }
        
        func minimize(topEdgeOffset: CGFloat? = nil, damping: CGFloat? = nil, initialVelocity: CGFloat? = nil) {
            guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else {
                return
            }
            navigationController.minimizeViewController(controller, topEdgeOffset: topEdgeOffset, damping: damping, velocity: initialVelocity, beforeMaximize: { _, completion in
                completion()
            }, setupContainer: { [weak self] current in
                let minimizedContainer: MinimizedContainerImpl?
                if let current = current as? MinimizedContainerImpl {
                    minimizedContainer = current
                } else if let context = self?.controller?.context {
                    minimizedContainer = MinimizedContainerImpl(sharedContext: context.sharedContext)
                } else {
                    minimizedContainer = nil
                }
                return minimizedContainer
            }, animated: true)
        }
        
        func openBookmarks() {
            guard let url = self.contentState?.url else {
                return
            }
            let controller = BrowserBookmarksScreen(context: self.context, url: url, openUrl: { [weak self] url in
                if let self {
                    self.performAction.invoke(.navigateTo(url, true))
                }
            }, addBookmark: { [weak self] in
                self?.addBookmark(url, showArrow: false)
            })
            self.controller?.push(controller)
        }
        
        func openSettings() {
            guard let referenceView = self.componentHost.findTaggedView(tag: settingsTag) as? ReferenceButtonComponent.View else {
                return
            }
            
            guard let controller = self.controller, let content = self.content.last else {
                return
            }
            
            if let animationComponentView = referenceView.componentView.view as? LottieComponent.View {
                animationComponentView.playOnce()
            }
            
            if let webContent = content as? BrowserWebContent {
                webContent.requestInstantView()
            }

            self.view.endEditing(true)
            
            let settings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings])
            |> take(1)
            |> map { sharedData -> WebBrowserSettings in
                if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) {
                    return current
                } else {
                    return WebBrowserSettings.defaultSettings
                }
            }
            
            let source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: referenceView.referenceNode.view))
            
            let items: Signal<ContextController.Items, NoError> = combineLatest(
                queue: Queue.mainQueue(),
                settings,
                content.state
            )
            |> map { [weak self] settings, contentState -> ContextController.Items in
                guard let self, let layout = self.validLayout?.0 else {
                    return ContextController.Items(content: .list([]))
                }
                
                let performAction = self.performAction
                let fontItem = BrowserFontSizeContextMenuItem(
                    value: self.presentationState.fontState.size,
                    decrease: { [weak self] in
                        performAction.invoke(.decreaseFontSize)
                        if let self {
                            return self.presentationState.fontState.size
                        } else {
                            return 100
                        }
                    }, increase: { [weak self] in
                        performAction.invoke(.increaseFontSize)
                        if let self {
                            return self.presentationState.fontState.size
                        } else {
                            return 100
                        }
                    }, reset: {
                        performAction.invoke(.resetFontSize)
                    }
                )
                
                var defaultWebBrowser: String? = settings.defaultWebBrowser
                if defaultWebBrowser == nil || defaultWebBrowser == "inAppSafari" {
                    defaultWebBrowser = "safari"
                }
                
                let url = contentState.url
                let openInOptions = availableOpenInOptions(context: self.context, item: .url(url: url))
                let openInTitle: String
                let openInUrl: String
                if let option = openInOptions.first(where: { $0.identifier == defaultWebBrowser }) {
                    openInTitle = option.title
                    if case let .openUrl(url) = option.action() {
                        openInUrl = url
                    } else {
                        openInUrl = url
                    }
                } else {
                    openInTitle = "Safari"
                    openInUrl = url
                }
                
                let canOpenIn = !(self.contentState?.url.hasPrefix("tonsite") ?? false)
                var canShare = true
                if let controller = self.controller {
                    switch controller.subject {
                    case let .document(_, canShareValue), let .pdfDocument(_, canShareValue):
                        canShare = canShareValue
                    default:
                        break
                    }
                }
                
                var items: [ContextMenuItem] = []
                if contentState.contentType == .document, contentState.title.lowercased().hasSuffix(".pdf") {
                    
                } else {
                    items.append(.custom(fontItem, false))
                    //TODO:localize
                    
                    if case .webPage = contentState.contentType {
                        let isAvailable = contentState.hasInstantView
                        items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_ShowInstantView, textColor: isAvailable ? .primary : .disabled, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Boost"), color: isAvailable ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withAlphaComponent(0.3)) }, action: isAvailable ? { (controller, action) in
                            performAction.invoke(.toggleInstantView(true))
                            action(.default)
                        } : nil)))
                    } else if case .instantPage = contentState.contentType, contentState.isInnerInstantViewEnabled {
                        items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_HideInstantView, textColor: .primary, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/InstantViewOff"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
                            performAction.invoke(.toggleInstantView(false))
                            action(.default)
                        })))
                    }
                }
                
                if !items.isEmpty {
                    items.append(.separator)
                }
                
                if case .webPage = contentState.contentType {
                    items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Reload, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Reload"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
                        performAction.invoke(.reload)
                        action(.default)
                    })))
                }
                if [.webPage, .document].contains(contentState.contentType) {
                    items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Search"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
                        performAction.invoke(.updateSearchActive(true))
                        action(.default)
                    })))
                }
                
                if canShare && !layout.metrics.isTablet {
                    items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
                        performAction.invoke(.share)
                        action(.default)
                    })))
                }
                
                if [.webPage, .instantPage].contains(contentState.contentType) {
                    items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_AddBookmark, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
                        performAction.invoke(.addBookmark)
                        action(.default)
                    })))
                    if !layout.metrics.isTablet && canOpenIn {
                        items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in
                            if let self {
                                self.context.sharedContext.applicationBindings.openUrl(openInUrl)
                            }
                            action(.default)
                        })))
                    }
                }
                return ContextController.Items(content: .list(items))
            }
            
            let contextController = ContextController(presentationData: self.presentationData, source: source, items: items)
            contextController.dismissed = { [weak content] in
                if let webContent = content as? BrowserWebContent {
                    webContent.releaseInstantView()
                }
            }
            self.controller?.present(contextController, in: .window(.root))
        }
        
        override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            let result = super.hitTest(point, with: event)
            if result == self.componentHost.view, let content = self.content.last {
                return content.hitTest(self.view.convert(point, to: content), with: event)
            }
            return result
        }
        
        private var scrollingPanelOffsetFraction: CGFloat = 0.0
        private var scrollingPanelOffsetToTopEdge: CGFloat = 0.0
        private var scrollingPanelOffsetToBottomEdge: CGFloat = .greatestFiniteMagnitude

        private var navigationBarHeight: CGFloat?
        private var toolbarHeight: CGFloat?
        func onContentScrollingUpdate(_ update: ContentScrollingUpdate) {
            var offsetDelta: CGFloat?
            offsetDelta = (update.absoluteOffsetToTopEdge ?? 0.0) - self.scrollingPanelOffsetToTopEdge
            if update.isReset {
                offsetDelta = 0.0
            }
            
            self.scrollingPanelOffsetToTopEdge = update.absoluteOffsetToTopEdge ?? 0.0
            self.scrollingPanelOffsetToBottomEdge = update.absoluteOffsetToBottomEdge ?? .greatestFiniteMagnitude
            
            if let topPanelHeight = self.navigationBarHeight, let bottomPanelHeight = self.toolbarHeight {
                var scrollingPanelOffsetFraction = self.scrollingPanelOffsetFraction
                
                if topPanelHeight > 0.0, let offsetDelta = offsetDelta {
                    let fractionDelta = -offsetDelta / topPanelHeight
                    scrollingPanelOffsetFraction = max(0.0, min(1.0, self.scrollingPanelOffsetFraction - fractionDelta))
                }
                
                if bottomPanelHeight > 0.0 && self.scrollingPanelOffsetToBottomEdge < bottomPanelHeight {
                    scrollingPanelOffsetFraction = min(scrollingPanelOffsetFraction, self.scrollingPanelOffsetToBottomEdge / bottomPanelHeight)
                } else if topPanelHeight > 0.0 && self.scrollingPanelOffsetToTopEdge < topPanelHeight {
                    scrollingPanelOffsetFraction = min(scrollingPanelOffsetFraction, self.scrollingPanelOffsetToTopEdge / topPanelHeight)
                }
                
                var transition = update.transition
                if !update.isInteracting {
                    if scrollingPanelOffsetFraction < 0.5 {
                        scrollingPanelOffsetFraction = 0.0
                    } else {
                        scrollingPanelOffsetFraction = 1.0
                    }
                    if case .none = transition.animation {
                    } else {
                        transition = transition.withAnimation(.curve(duration: 0.25, curve: .easeInOut))
                    }
                }
                
                if update.isReset {
                    scrollingPanelOffsetFraction = 0.0
                }
                
                if scrollingPanelOffsetFraction != self.scrollingPanelOffsetFraction {
                    self.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction
                    self.requestLayout(transition: transition)
                }
            }
        }
        
        func navigateTo(_ item: BrowserContentState.HistoryItem) {
            if let _ = item.webItem {
                if let last = self.content.last {
                    last.navigateTo(historyItem: item)
                }
            } else if let uuid = item.uuid {
                var newContent = self.content
                while newContent.last?.uuid != uuid {
                    newContent.removeLast()
                }
                self.content = newContent
                self.requestLayout(transition: .spring(duration: 0.4))
            }
        }
        
        func performHoldAction(view: UIView, gesture: ContextGesture?, action: BrowserScreen.Action) {
            guard let controller = self.controller, let contentState = self.contentState else {
                return
            }
            
            let source: ContextContentSource = .reference(BrowserReferenceContentSource(controller: controller, sourceView: view))
            var items: [ContextMenuItem] = []
            switch action {
            case .navigateBack:
                for item in contentState.backList {
                    items.append(.action(ContextMenuActionItem(text: item.title, textLayout: .secondLineWithValue(item.url), icon: { _ in return nil }, action: { [weak self] (_, action) in
                        self?.navigateTo(item)
                        action(.default)
                    })))
                }
            case .navigateForward:
                for item in contentState.forwardList {
                    items.append(.action(ContextMenuActionItem(text: item.title, textLayout: .secondLineWithValue(item.url), icon: { _ in return nil }, action: { [weak self] (_, action) in
                        self?.navigateTo(item)
                        action(.default)
                    })))
                }
            default:
                return
            }
            
            let contextController = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))))
            self.controller?.present(contextController, in: .window(.root))
        }
        
        private var isUpdating = false
        func requestLayout(transition: ComponentTransition) {
            if !self.isUpdating, let (layout, navigationBarHeight) = self.validLayout {
                self.containerLayoutUpdated(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition)
            }
        }
        
        func containerLayoutUpdated(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ComponentTransition) {
            self.isUpdating = true
            defer {
                self.isUpdating = false
            }
            
            self.validLayout = (layout, navigationBarHeight)
            
            let environment = ViewControllerComponentContainer.Environment(
                statusBarHeight: layout.statusBarHeight ?? 0.0,
                navigationHeight: navigationBarHeight,
                safeInsets: UIEdgeInsets(
                    top: layout.intrinsicInsets.top + layout.safeInsets.top,
                    left: layout.safeInsets.left,
                    bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom,
                    right: layout.safeInsets.right
                ),
                additionalInsets: layout.additionalInsets,
                inputHeight: layout.inputHeight ?? 0.0,
                metrics: layout.metrics,
                deviceMetrics: layout.deviceMetrics,
                orientation: layout.metrics.orientation,
                isVisible: true,
                theme: self.presentationData.theme,
                strings: self.presentationData.strings,
                dateTimeFormat: self.presentationData.dateTimeFormat,
                controller: { [weak self] in
                    return self?.controller
                }
            )
            
            var canShare = true
            if let controller = self.controller {
                switch controller.subject {
                case let .document(_, canShareValue), let .pdfDocument(_, canShareValue):
                    canShare = canShareValue
                default:
                    break
                }
            }

            let componentSize = self.componentHost.update(
                transition: transition,
                component: AnyComponent(
                    BrowserScreenComponent(
                        context: self.context,
                        contentState: self.contentState,
                        presentationState: self.presentationState,
                        canShare: canShare,
                        performAction: self.performAction,
                        performHoldAction: { [weak self] view, gesture, action in
                            if let self {
                                self.performHoldAction(view: view, gesture: gesture, action: action)
                            }
                        },
                        panelCollapseFraction: self.scrollingPanelOffsetFraction
                    )
                ),
                environment: {
                    environment
                },
                forceUpdate: false,
                containerSize: layout.size
            )
            if let componentView = self.componentHost.view {
                if componentView.superview == nil {
                    self.view.addSubview(componentView)
                    componentView.clipsToBounds = true
                }
                transition.setFrame(view: componentView, frame: CGRect(origin: .zero, size: componentSize))
            }
            transition.setFrame(view: self.contentContainerView, frame: CGRect(origin: .zero, size: layout.size))
                
            var items: [AnyComponentWithIdentity<Empty>] = []
            for content in self.content {
                items.append(
                    AnyComponentWithIdentity(id: content.uuid, component: AnyComponent(
                        BrowserContentComponent(
                            content: content,
                            insets: UIEdgeInsets(
                                top: layout.statusBarHeight ?? 0.0,
                                left: layout.safeInsets.left,
                                bottom: layout.intrinsicInsets.bottom,
                                right: layout.safeInsets.right
                            ),
                            navigationBarHeight: navigationBarHeight,
                            scrollingPanelOffsetFraction: self.scrollingPanelOffsetFraction,
                            hasBottomPanel: !layout.metrics.isTablet || self.presentationState.isSearching
                        )
                    ))
                )
            }
            
            let _ = self.contentNavigationContainer.update(
                transition: transition,
                component: AnyComponent(
                    NavigationStackComponent(
                        items: items,
                        requestPop: { [weak self] in
                            guard let self else {
                                return
                            }
                            self.popContent(transition: .spring(duration: 0.4))
                        }
                    )
                ),
                environment: {},
                containerSize: layout.size
            )
            let navigationFrame = CGRect(origin: .zero, size: layout.size)
            if let view = self.contentNavigationContainer.view {
                if view.superview == nil {
                    self.contentContainerView.addSubview(view)
                }
                transition.setFrame(view: view, frame: navigationFrame)
            }
            
            self.navigationBarHeight = environment.navigationHeight
            self.toolbarHeight = 49.0
        }
    }
    
    public enum Subject {
        case webPage(url: String)
        case instantPage(webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation, preloadedResources: [Any]?)
        case document(file: TelegramMediaFile, canShare: Bool)
        case pdfDocument(file: TelegramMediaFile, canShare: Bool)
        
        public var fileId: MediaId? {
            switch self {
            case let .document(file, _), let .pdfDocument(file, _):
                return file.fileId
            default:
                return nil
            }
        }
    }
    
    private let context: AccountContext
    public let subject: Subject
    private var preferredConfiguration: WKWebViewConfiguration?
    private var openPreviousOnClose = false
    
    public var openDocument: (TelegramMediaFile, Bool) -> Void = { _, _ in }
    
    private var validLayout: ContainerViewLayout?
    
    public static let supportedDocumentMimeTypes: [String] = [
        "text/plain",
        "text/rtf",
        "application/pdf",
        "application/msword",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "application/vnd.ms-excel",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
        "application/vnd.openxmlformats-officedocument.presentationml.presentation"
    ]
    
    public static let supportedDocumentExtensions: [String] = [
        "txt",
        "rtf",
        "pdf",
        "doc",
        "docx",
        "xls",
        "xlsx",
        "pptx"
    ]
    
    public init(context: AccountContext, subject: Subject, preferredConfiguration: WKWebViewConfiguration? = nil, openPreviousOnClose: Bool = false) {
        var subject = subject
        if case let .webPage(url) = subject, let parsedUrl = URL(string: url) {
            if parsedUrl.host?.hasSuffix(".ton") == true {
                var urlComponents = URLComponents(string: url)
                urlComponents?.scheme = "tonsite"
                if let updatedUrl = urlComponents?.url?.absoluteString {
                    subject = .webPage(url: updatedUrl)
                }
            }
        }
        self.context = context
        self.subject = subject
        self.preferredConfiguration = preferredConfiguration
        self.openPreviousOnClose = openPreviousOnClose
        
        super.init(navigationBarPresentationData: nil)
        
        self.navigationPresentation = .modalInCompactLayout
        
        self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown)
        
        self.scrollToTop = { [weak self] in
            self?.node.content.last?.scrollToTop()
        }
    }
    
    required public init(coder: NSCoder) {
        preconditionFailure()
    }
    
    var node: Node {
        return self.displayNode as! Node
    }
    
    override public func loadDisplayNode() {
        self.displayNode = Node(controller: self)

        super.displayNodeDidLoad()
    }
    
    override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
        self.validLayout = layout
        
        super.containerLayoutUpdated(layout, transition: transition)
        
        var navigationHeight = self.navigationLayout(layout: layout).navigationFrame.height
        if layout.metrics.isTablet, layout.size.width > layout.size.height {
            navigationHeight += 6.0
        }
        self.node.containerLayoutUpdated(layout: layout, navigationBarHeight: navigationHeight, transition: ComponentTransition(transition))
    }
    
    public func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?) {
        self.openPreviousOnClose = false
        self.node.minimize(topEdgeOffset: topEdgeOffset, damping: 180.0, initialVelocity: initialVelocity)
    }
    
    private var didPlayAppearanceAnimation = false
    public override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        if !self.didPlayAppearanceAnimation, let layout = self.validLayout, layout.metrics.isTablet {
            self.node.layer.animatePosition(from: CGPoint(x: 0.0, y: layout.size.height), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
        }
    }
    
    public override func dismiss(completion: (() -> Void)? = nil) {
        if let layout = self.validLayout, layout.metrics.isTablet {
            self.node.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: layout.size.height), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
                super.dismiss(completion: completion)
            })
        } else {
            super.dismiss(completion: completion)
        }
    }
    
    public override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        if self.openPreviousOnClose, let navigationController = self.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer, let controller = minimizedContainer.controllers.last {
            navigationController.maximizeViewController(controller, animated: true)
        }
    }
    
    public var isMinimized = false {
        didSet {
            if let webContent = self.node.content.last as? BrowserWebContent {
                if !self.isMinimized {
                    webContent.webView.setNeedsLayout()
                }
            }
        }
    }
    public var isMinimizable = true
    
    public var minimizedIcon: UIImage? {
        if let contentState = self.node.contentState {
            switch contentState.contentType {
            case .webPage:
                return contentState.favicon
            case .instantPage:
                return UIImage(bundleImageName: "Chat/Message/AttachedContentInstantIcon")?.withRenderingMode(.alwaysTemplate)
            case .document:
                return nil
            }
        }
        return nil
    }
        
    public var minimizedProgress: Float? {
        if let contentState = self.node.contentState {
            return Float(contentState.readingProgress)
        }
        return nil
    }
    
    public func makeContentSnapshotView() -> UIView? {
        if let contentSnapshot = self.node.content.last?.makeContentSnapshotView(), let layout = self.validLayout {
            if let wrapperView = self.view.snapshotView(afterScreenUpdates: false) {
                contentSnapshot.frame = contentSnapshot.frame.offsetBy(dx: 0.0, dy: self.navigationLayout(layout: layout).navigationFrame.height)
                wrapperView.addSubview(contentSnapshot)
                return wrapperView
            } else {
                return contentSnapshot
            }
        } else {
            return self.view.snapshotView(afterScreenUpdates: false)
        }
    }
}

private final class BrowserReferenceContentSource: ContextReferenceContentSource {
    private let controller: ViewController
    private let sourceView: UIView

    init(controller: ViewController, sourceView: UIView) {
        self.controller = controller
        self.sourceView = sourceView
    }

    func transitionInfo() -> ContextControllerReferenceViewInfo? {
        return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
    }
}

private final class BrowserContentComponent: Component {
    let content: BrowserContent
    let insets: UIEdgeInsets
    let navigationBarHeight: CGFloat
    let scrollingPanelOffsetFraction: CGFloat
    let hasBottomPanel: Bool
    
    init(
        content: BrowserContent,
        insets: UIEdgeInsets,
        navigationBarHeight: CGFloat,
        scrollingPanelOffsetFraction: CGFloat,
        hasBottomPanel: Bool
    ) {
        self.content = content
        self.insets = insets
        self.navigationBarHeight = navigationBarHeight
        self.scrollingPanelOffsetFraction = scrollingPanelOffsetFraction
        self.hasBottomPanel = hasBottomPanel
    }
    
    static func ==(lhs: BrowserContentComponent, rhs: BrowserContentComponent) -> Bool {
        if lhs.content.uuid != rhs.content.uuid {
            return false
        }
        if lhs.insets != rhs.insets {
            return false
        }
        if lhs.navigationBarHeight != rhs.navigationBarHeight {
            return false
        }
        if lhs.scrollingPanelOffsetFraction != rhs.scrollingPanelOffsetFraction {
            return false
        }
        if lhs.hasBottomPanel != rhs.hasBottomPanel {
            return false
        }
        return true
    }

    final class View: UIView {
        init() {
            super.init(frame: CGRect())
        }

        required init?(coder aDecoder: NSCoder) {
            preconditionFailure()
        }

        func update(component: BrowserContentComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
            if component.content.superview !== self {
                self.addSubview(component.content)
            }
            
            let collapsedHeight: CGFloat = 24.0
            let topInset: CGFloat = component.navigationBarHeight * (1.0 - component.scrollingPanelOffsetFraction) + (component.insets.top + collapsedHeight) * component.scrollingPanelOffsetFraction
            let bottomInset = component.hasBottomPanel ? (49.0 + component.insets.bottom) * (1.0 - component.scrollingPanelOffsetFraction) : 0.0
            let insets = UIEdgeInsets(top: topInset, left: component.insets.left, bottom: bottomInset, right: component.insets.right)
            let fullInsets = UIEdgeInsets(top: component.insets.top + component.navigationBarHeight, left: component.insets.left, bottom: component.hasBottomPanel ? 49.0 + component.insets.bottom : 0.0, right: component.insets.right)
                        
            component.content.updateLayout(size: availableSize, insets: insets, fullInsets: fullInsets, safeInsets: component.insets, transition: transition)
            transition.setFrame(view: component.content, frame: CGRect(origin: .zero, size: availableSize))
            
            return availableSize
        }
    }

    func makeView() -> View {
        return View()
    }

    func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, transition: transition)
    }
}

private func cancelInteractiveTransitionGestures(view: UIView) {
    if let gestureRecognizers = view.gestureRecognizers {
        for gesture in gestureRecognizers {
            if let gesture = gesture as? InteractiveTransitionGestureRecognizer {
                gesture.cancel()
            } else if let scrollView = gesture.view as? UIScrollView, gesture.isEnabled, scrollView.tag == 0x5C4011 {
                gesture.isEnabled = false
                gesture.isEnabled = true
            }
        }
    }
    if let superview = view.superview {
        cancelInteractiveTransitionGestures(view: superview)
    }
}