//
//  BrowserViewController.swift
//  SBrowser
//
//  Created by James Magahern on 7/21/20.
//

import Combine
import MessageUI
import UIKit
import UniformTypeIdentifiers

class BrowserViewController: UIViewController
{
    let browserView = BrowserView()
    var currentTab: Tab { didSet { didChangeTab(currentTab) } }
    var webView: WKWebView { currentTab.webView }
    
    internal let tabController = TabController()
    internal let tabBarViewController: TabBarViewController
    internal let toolbarController = ToolbarViewController()
    internal let findOnPageController = FindOnPageViewController()
    
    internal let autocompleteViewController = AutocompleteViewController()
    internal var policyManager: ResourcePolicyManager { tabController.policyManager }
    
    override var canBecomeFirstResponder: Bool { true }
    
    private var loadingObservation: NSKeyValueObservation?
    private var backButtonObservation: NSKeyValueObservation?
    private var forwardButtonObservation: NSKeyValueObservation?
    private var hasSecureContentObservation: NSKeyValueObservation?
    private var activeTabObservation: AnyCancellable?
    
    internal var shiftKeyHeld: Bool = false
    internal var commandKeyHeld: Bool = false
    internal var windowButtonHeld: Bool {
        get { toolbarController.newTabButton.isTracking }
        set { toolbarController.newTabButton.cancelTracking(with: nil) }
    }
    
    static let longPressWindowButtonToMakeNewTab: Bool = false
    
    internal var darkModeEnabled: Bool {
        get { currentTab.bridge.darkModeEnabled }
        set {
            currentTab.bridge.darkModeEnabled = newValue
            toolbarController.darkModeEnabled = newValue
        }
    }
    
    internal enum PreferredEmailSharingRecipient: String, CaseIterable {
        case readLater = "Read Later"
        case bookmark  = "Bookmark"
        case other     = "Other…"
    }
    
    override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent }
    
    internal var changingFocusToAutocompleteController = false
    
    init() {
        self.currentTab = tabController.tabs.first!
        self.tabBarViewController = TabBarViewController(tabController: tabController)
        super.init(nibName: nil, bundle: nil)
        
        self.tabController.controllerDelegate = self
        
        addChild(toolbarController)
        addChild(findOnPageController)
        addChild(tabBarViewController)
        
        didChangeTab(currentTab)
    }
    
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
    override func loadView() {
        browserView.toolbarView = toolbarController.toolbarView
        browserView.tabBarView = tabBarViewController.tabBarView
        
        if FindOnPageViewController.isEnabled() {
            browserView.findOnPageView = findOnPageController.findOnPageView
            
            // Find on page dismiss
            findOnPageController.findOnPageView.doneButton.addAction(UIAction(handler: { [unowned self] _ in
                browserView.setFindOnPageVisible(false, animated: true)
            }), for: .touchUpInside)
        }
        
        // Refresh button
        toolbarController.urlBar.refreshButton.addAction(UIAction(handler: { [unowned self] action in
            if self.webView.isLoading {
                self.webView.stopLoading()
            } else {
                self.webView.reload()
            }
        }), for: .touchUpInside)
        
        // Back button
        toolbarController.backButton.addAction(UIAction(handler: { [unowned self] _ in
            self.webView.goBack()
        }), for: .touchUpInside)
        
        // Forward button
        toolbarController.forwardButton.addAction(UIAction(handler: { [unowned self] _ in
            self.webView.goForward()
        }), for: .touchUpInside)
        
        // Share button
        toolbarController.shareButton.addAction(UIAction(handler: { [unowned self] _ in
            showShareSheetForCurrentURL(fromViewController: nil)
        }), for: .touchUpInside)
        
        // Script button
        toolbarController.scriptControllerIconView.addAction(UIAction(handler: { [unowned self] action in
            let hostOrigin = self.webView.url?.securityOrigin ?? ""
            let loadedScripts = currentTab.allowedScriptOrigins.union(currentTab.blockedScriptOrigins)
            let scriptViewController = ScriptPolicyViewController(policyManager: self.policyManager,
                                                                  hostOrigin: hostOrigin,
                                                                  loadedScripts: loadedScripts,
                                                                  scriptsAllowedForTab: currentTab.javaScriptEnabled)
            scriptViewController.delegate = self
            
            let navController = UINavigationController(rootViewController: scriptViewController)
            navController.modalPresentationStyle = .popover
            navController.popoverPresentationController?.sourceView = self.toolbarController.scriptControllerIconView
            navController.popoverPresentationController?.delegate = self
            
            self.present(navController, animated: true, completion: nil)
        }), for: .touchUpInside)
        
        // Dark mode button
        toolbarController.darkModeButton.addAction(UIAction(handler: { [unowned self] _ in
            self.darkModeEnabled = !self.darkModeEnabled
        }), for: .touchUpInside)
        
        // Tabs button
        toolbarController.windowButton.addAction(UIAction(handler: { [unowned self] _ in
            let tabPickerController = TabPickerViewController()
            tabPickerController.delegate = self
            tabPickerController.title = "Tabs"
            tabPickerController.tabBarItem.image = UIImage(systemName: "rectangle.on.rectangle")
            tabPickerController.selectedTabIdentifier = currentTab.identifier
            tabPickerController.tabObserver = tabController.$tabs
                .receive(on: RunLoop.main)
                .sink(receiveValue: { (newTabs: [Tab]) in
                    tabPickerController.setTabInfos(newTabs.map { $0.tabInfo }, forHost: TabPickerViewController.localHostIdentifier)
                })
            
            // Set localhost tabs
            let tabInfos = tabController.tabs.map { $0.tabInfo }
            tabPickerController.setTabInfos(tabInfos, forHost: TabPickerViewController.localHostIdentifier)
            tabPickerController.selectedTabHost = TabPickerViewController.localHostIdentifier
            
            let remoteTabPickerController = TabPickerViewController()
            remoteTabPickerController.delegate = self
            remoteTabPickerController.title = "Remote Tabs"
            remoteTabPickerController.tabBarItem.image = UIImage(systemName: "icloud")
            remoteTabPickerController.newTabButton.isEnabled = false
            remoteTabPickerController.editButtonItem.isEnabled = false
            
            // Fetch tabs now
            AttractorServer.shared.getTabInfos { [weak remoteTabPickerController] result in
                guard let picker = remoteTabPickerController else { return }
                
                switch result {
                case .success(let tabInfos):
                    tabInfos.forEach { (key: String, value: [TabInfo]) in
                        picker.setTabInfos(value, forHost: key)
                    }
                case .failure(let error):
                    picker.displayedError = error
                }
            }
            
            let tabBarController = UITabBarController(nibName: nil, bundle: nil)
            tabBarController.viewControllers = [
                UINavigationController(rootViewController: tabPickerController),
                UINavigationController(rootViewController: remoteTabPickerController)
            ]
            
            tabBarController.modalPresentationStyle = .popover
            tabBarController.popoverPresentationController?.sourceView = self.toolbarController.windowButton
            tabBarController.popoverPresentationController?.delegate = self

            self.present(tabBarController, animated: true, completion: nil)
        }), for: .touchUpInside)
        
        let newTabAction = UIAction { [unowned self] action in
            if let gestureRecognizer = action.sender as? UILongPressGestureRecognizer {
                if gestureRecognizer.state != .began { return }
            }
            
            self.createNewTab(withURL: nil)
        }
        
        if Self.longPressWindowButtonToMakeNewTab {
            // Long press window button to make new tab?
            
            let gestureRecognizer = UILongPressGestureRecognizer(action: newTabAction)
            toolbarController.windowButton.addGestureRecognizer(gestureRecognizer)
        }
        
        // New tab button
        toolbarController.newTabButton.addAction(newTabAction, for: .touchUpInside)
        
        // Error button
        toolbarController.urlBar.errorButton.addAction(UIAction(handler: { [unowned self] _ in
            let alert = UIAlertController(title: "Error", message: currentTab.loadError?.localizedDescription, preferredStyle: .actionSheet)
            alert.popoverPresentationController?.sourceView = self.toolbarController.urlBar.errorButton
            
            alert.addAction(UIAlertAction(title: "Reload", style: .destructive, handler: { _ in
                self.webView.reload()
                alert.dismiss(animated: true, completion: nil)
            }))
            
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [unowned self] _ in
                alert.dismiss(animated: true, completion: nil)
                
                // Clears out the error state
                self.toolbarController.urlBar.loadProgress = .complete
            }))
            
            self.present(alert, animated: true, completion: nil)
        }), for: .touchUpInside)
        
        // Cancel button: hide autocomplete if applicable
        toolbarController.toolbarView.cancelButton.addAction(UIAction(handler: { [unowned self] _ in
            self.autocompleteViewController.view.isHidden = true
        }), for: .touchUpInside)
        
        // TextField delegate
        toolbarController.urlBar.delegate = self
        toolbarController.urlBar.textField.delegate = self
        
        // Autocomplete controller
        autocompleteViewController.delegate = self
        autocompleteViewController.view.isHidden = true
        
        // Font size adjust
        toolbarController.urlBar.documentButton.addAction(UIAction(handler: { [unowned self] _ in
            let documentControls = DocumentControlViewController(darkModeEnabled: currentTab.bridge.darkModeEnabled)
            documentControls.modalPresentationStyle = .popover
            documentControls.popoverPresentationController?.permittedArrowDirections = [ .down, .up ]
            documentControls.popoverPresentationController?.sourceView = toolbarController.urlBar.documentButton
            documentControls.popoverPresentationController?.delegate = self
            
            let numberFormatter = NumberFormatter()
            numberFormatter.numberStyle = .percent
            
            let label = documentControls.fontSizeAdjustView.labelView
            label.text = numberFormatter.string(for: currentTab.webView._viewScale)
            
            // Font size adjust
            documentControls.fontSizeAdjustView.decreaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in
                self.decreaseSize(sender)
                label.text = numberFormatter.string(for: currentTab.webView._viewScale)
            }), for: .touchUpInside)
            
            documentControls.fontSizeAdjustView.increaseSizeButton.addAction(UIAction(handler: { [unowned self] sender in
                self.increaseSize(sender)
                label.text = numberFormatter.string(for: currentTab.webView._viewScale)
            }), for: .touchUpInside)
            
            // Find on page
            documentControls.findOnPageControlView.addAction(UIAction(handler: { [unowned self] _ in
                documentControls.dismiss(animated: true, completion: { [unowned self] in
                    // Needs to happen after dismissal, otherwise the find navigator will pick up the document
                    // controls as the divergent responder/deepest action responder (my bad...).
                    if FindOnPageViewController.isEnabled() {
                        browserView.setFindOnPageVisible(true, animated: true)
                    } else if #available(iOS 16.0, *) {
                        browserView.webView?.findInteraction?.presentFindNavigator(showingReplace: false)
                    }
                })
            }), for: .touchUpInside)
            
            // Navigation controls
            documentControls.navigationControlView.backButton.isEnabled = webView.canGoBack
            documentControls.navigationControlView.backButton.addAction(UIAction() { [unowned self] _ in
                webView.goBack()
            }, for: .touchUpInside)
            
            documentControls.observations.append(webView.observe(\.canGoBack, changeHandler: { (webView, _) in
                documentControls.navigationControlView.backButton.isEnabled = webView.canGoBack
            }))
            
            documentControls.navigationControlView.forwardButton.isEnabled = webView.canGoForward
            documentControls.navigationControlView.forwardButton.addAction(UIAction() { [unowned self] _ in
                webView.goForward()
            }, for: .touchUpInside)
            
            documentControls.observations.append(webView.observe(\.canGoForward, changeHandler: { (webView, _) in
                documentControls.navigationControlView.forwardButton.isEnabled = webView.canGoForward
            }))
            
            // Reader mode
            documentControls.readabilityView.addAction(UIAction { [unowned self] _ in
                documentControls.dismiss(animated: true, completion: nil)
                showReaderWindow()
            }, for: .touchUpInside)
            
            // Archive
            documentControls.archiveView.addAction(UIAction { [unowned self] _ in
                guard let currentURL = webView.url else { return }
                guard let archiveURL = URL(string: "https://archive.today/\(currentURL.absoluteString)") else { return }
                
                // Open in new tab:
                createNewTab(withURL: archiveURL)
                documentControls.dismiss(animated: true, completion: nil)
            }, for: .touchUpInside)
            
            // Dark mode
            documentControls.darkModeView.addAction(UIAction { [unowned self] _ in
                self.darkModeEnabled = !self.darkModeEnabled
                documentControls.dismiss(animated: true, completion: nil)
            }, for: .touchUpInside)
            
            // Email
            documentControls.emailView.addAction(UIAction { [unowned self] _ in
                queryPreferredEmailSharingRecipientForCurrentURL(fromViewController: documentControls)
            }, for: .touchUpInside)
            
            // Settings
            documentControls.settingsView.addAction(UIAction { [unowned self] _ in
                documentControls.dismiss(animated: false, completion: nil)
                showSettingsWindow()
            }, for: .touchUpInside)
            
            // Share
            documentControls.sharingView.addAction(UIAction { [unowned self] _ in
                showShareSheetForCurrentURL(fromViewController: documentControls)
            }, for: .touchUpInside)
            
            // History
            documentControls.historyView.addAction(UIAction { [unowned self] action in
                documentControls.dismiss(animated: false, completion: nil)
                showHistory(action)
            }, for: .touchUpInside)
            
            present(documentControls, animated: true, completion: nil)
        }), for: .touchUpInside)
        
        // Tab controller
        activeTabObservation = tabController.$activeTabIndex
            .receive(on: RunLoop.main)
            .sink(receiveValue: { [unowned self] (activeTab: Int) in
                if activeTab < tabController.tabs.count {
                    let tab = tabController.tabs[activeTab]
                    if currentTab != tab {
                        currentTab = tab
                    }
                }
                
                // Show tab bar view?
                self.updateTabBarVisibility()
        })
        
        registerForTraitChanges([UITraitHorizontalSizeClass.self]) { [weak self] (traitEnvironment: Self, previousTraitCollection) in
            guard let self else { return }
            toolbarController.reconfigureButtons(for: traitCollection.horizontalSizeClass)
        }
        
        self.view = browserView
    }
    
    internal func updateTabBarVisibility() {
        browserView.tabBarViewVisible = tabController.tabs.count > 1
    }
    
    internal func showShareSheetForCurrentURL(fromViewController: UIViewController?) {
        guard let url = self.webView.url else { return }
        
        let shareableURL = ShareableURL(
            url: url,
            title: webView.title ?? url.absoluteString,
            favicon: currentTab.favicon
        )
        
        let activityController = UIActivityViewController(activityItems: [ shareableURL ], applicationActivities: nil)
        activityController.popoverPresentationController?.sourceView = toolbarController.shareButton
        
        if let fromViewController = fromViewController {
            fromViewController.dismiss(animated: false, completion: nil)
        }
        
        self.present(activityController, animated: true, completion: nil)
    }
    
    internal func queryPreferredEmailSharingRecipientForCurrentURL(fromViewController: UIViewController?) {
        if let fromViewController = fromViewController {
            fromViewController.dismiss(animated: false, completion: nil)
        }
        
        let actionSheet = UIAlertController(title: "Select Preferred Recipient", message: nil, preferredStyle: .actionSheet)
        for preferredRecipient in PreferredEmailSharingRecipient.allCases {
            actionSheet.addAction(UIAlertAction(title: preferredRecipient.rawValue, style: .default, handler: { [unowned self] action in
                actionSheet.dismiss(animated: true, completion: { [unowned self] in
                    composeEmailForCurrentURL(preferredRecipientSelection: preferredRecipient)
                })
            }))
        }
        
        actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
            actionSheet.dismiss(animated: true, completion: nil)
        }))
        
        actionSheet.popoverPresentationController?.sourceView = toolbarController.urlBar.documentButton
        
        present(actionSheet, animated: true, completion: nil)
    }
    
    internal func composeEmailForCurrentURL(preferredRecipientSelection: PreferredEmailSharingRecipient) {
        guard let url = self.webView.url else { return }
        
        let composeController = MFMailComposeViewController()
        composeController.setSubject(webView.title ?? url.absoluteString)
        composeController.setMessageBody(url.absoluteString, isHTML: false)
        composeController.mailComposeDelegate = self
        
        let preferredRecipient: String? = { preferredRecipientSelection in
            switch preferredRecipientSelection {
                case .readLater:
                    return "read@buzzert.net"
                case .bookmark:
                    return "bookmarks@buzzert.net"
                case .other:
                    return nil
            }
        }(preferredRecipientSelection)
        
        if let preferredRecipient = preferredRecipient {
            composeController.setToRecipients([ preferredRecipient ])
        }
        
        present(composeController, animated: true, completion: nil)
    }
    
    internal func showSettingsWindow() {
        #if targetEnvironment(macCatalyst)
        let userActivity = NSUserActivity(activityType: SessionActivityType.SettingsWindow.rawValue)
        UIApplication.shared.requestSceneSessionActivation(nil, userActivity: userActivity, options: .none, errorHandler: nil)
        #else
        let settingsVC = SettingsViewController(windowScene: view.window!.windowScene!)
        let wrapperNC = UINavigationController(rootViewController: settingsVC)
        present(wrapperNC, animated: true, completion: nil)
        #endif
    }
    
    internal func showReaderWindow() {
        currentTab.bridge.parseDocumentForReaderMode { [currentTab] string in
            DispatchQueue.main.async {
                if self.traitCollection.userInterfaceIdiom == .phone {
                    let readableViewController = ReaderViewController(readableHTMLString: string, baseURL: currentTab.bridge.webView.url)
                    readableViewController.title = currentTab.bridge.webView.title
                    readableViewController.darkModeEnabled = currentTab.bridge.darkModeEnabled
                    readableViewController.delegate = self
                    
                    let navigationController = UINavigationController(rootViewController: readableViewController)
                    self.present(navigationController, animated: true, completion: nil)
                } else {
                    let userActivity = NSUserActivity(activityType: SessionActivityType.ReaderWindow.rawValue)
                    userActivity.title = currentTab.title
                    userActivity.userInfo = [
                        ReaderUserActivityKeys.baseURL.rawValue    : currentTab.bridge.webView.url ?? NSNull(),
                        ReaderUserActivityKeys.htmlString.rawValue : string,
                    ]
                    let requestOptions = UIWindowScene.ActivationRequestOptions()
                    requestOptions.preferredPresentationStyle = .prominent
                    UIApplication.shared.requestSceneSessionActivation(nil, userActivity: userActivity, options: requestOptions)
                }
            }
        }
    }
    
    internal func showHistoryWindow() {
        let historyViewController = HistoryBrowserViewController { [unowned self] url in
            if currentTab.url == nil {
                currentTab.beginLoadingURL(url)
            } else {
                createNewTab(withURL: url)
            }
            
            presentedViewController?.dismiss(animated: true)
        }
        
        historyViewController.title = "History"
        historyViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction { _ in
            // xxx: This is not the SwiftUI-y way to do this.
            historyViewController.dismiss(animated: true)
        })
        
        historyViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Clear", primaryAction: UIAction { [unowned self] action in
            let alertController = UIAlertController(title: "Clear History", message: "Are you sure you want to clear all history?", preferredStyle: .actionSheet)
            alertController.addAction(UIAlertAction(title: "Clear", style: .destructive, handler: { [unowned self] _ in
                BrowserHistory.shared.clearAllHistory()
                presentedViewController?.dismiss(animated: true)
            }))
            alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel))
            alertController.popoverPresentationController?.sourceItem = action.presentationSourceItem
            
            presentedViewController?.present(alertController, animated: true)
        })
        
        let navigationController = UINavigationController(rootViewController: historyViewController)
        present(navigationController, animated: true)
    }
    
    internal func updateLoadProgress(forWebView webView: WKWebView) {
        if let loadError = currentTab.loadError {
            toolbarController.urlBar.loadProgress = .error(error: loadError)
        } else if webView.estimatedProgress == 1.0 {
            toolbarController.urlBar.loadProgress = .complete
        } else if webView.isLoading {
            toolbarController.urlBar.loadProgress = .loading(progress: webView.estimatedProgress)
        } else {
            toolbarController.urlBar.loadProgress = .idle
        }
    }
    
    internal func updateTitleAndURL(forWebView webView: WKWebView) {
        guard let tab = tabController.tab(forWebView: webView) else { return }
        
        if webView == browserView.webView {
            browserView.titlebarView.setTitle(webView.title ?? "")
            
            if let urlString = webView.url?.absoluteString {
                toolbarController.urlBar.textField.text = urlString
            } else {
                toolbarController.urlBar.textField.text = ""
            }
        }
        
        // Figure out which tab this corresponds to
        if let tabIndex = tabController.tabs.firstIndex(of: tab) {
            tabBarViewController.tabBarView.reloadTab(atIndex: tabIndex)
        }
    }
    
    private func didChangeTab(_ tab: Tab) {
        if let activeIndex = tabController.tabs.firstIndex(of: tab) {
            tabController.activeTabIndex = activeIndex
        }
        
        currentTab.delegate = self
        
        let webView = currentTab.webView
        webView.allowsBackForwardNavigationGestures = true
        webView.navigationDelegate = self
        webView.uiDelegate = self
        
        // Change webView
        browserView.webView = webView
        findOnPageController.webView = webView
        
        // Autocomplete view
        browserView.autocompleteView = autocompleteViewController.collectionView
        
        // Color theme
        browserView.titlebarView.setColorTheme(tab.colorTheme)
        
        // Load progress
        updateLoadProgress(forWebView: webView)
        loadingObservation = webView.observe(\.estimatedProgress) { [unowned self] (webView, observedChange) in
            self.updateLoadProgress(forWebView: webView)
        }
        
        // Title observer
        updateTitleAndURL(forWebView: webView)
        
        // Back/forward observer
        toolbarController.backButton.isEnabled = webView.canGoBack
        backButtonObservation = webView.observe(\.canGoBack, changeHandler: { [unowned self] (webView, observedChange) in
            toolbarController.backButton.isEnabled = webView.canGoBack
        })
        
        toolbarController.forwardButton.isEnabled = webView.canGoForward
        forwardButtonObservation = webView.observe(\.canGoForward, changeHandler: { [unowned self] (webView, observedChange) in
            toolbarController.forwardButton.isEnabled = webView.canGoForward
        })
        
        // Secure content
        browserView.titlebarView.showsSecurityIndicator = webView.hasOnlySecureContent
        hasSecureContentObservation = webView.observe(\.hasOnlySecureContent, changeHandler: { [unowned self] (webView, observedChange) in
            browserView.titlebarView.showsSecurityIndicator = webView.hasOnlySecureContent
        })
        
        // Script blocker button
        updateScriptBlockerButton()
        
        // Enforce dark mode setting
        currentTab.bridge.darkModeEnabled = toolbarController.darkModeEnabled
        
        // Blur url bar, if applicable
        toolbarController.urlBar.textField.resignFirstResponder()

        // If this tab's web content process was jettisoned while in background,
        // its current page may appear blank and url be nil. Proactively reload
        // when the tab becomes active again to restore state.
        if tab.contentProcessTerminated {
            if webView.url != nil || tab.url != nil {
                webView.reload()
            } else if let home = tab.homeURL {
                tab.beginLoadingURL(home)
            } else {
                webView.reload()
            }
        }
    }
    
    override func target(forAction action: Selector, withSender sender: Any?) -> Any? {
        var findActions: [Selector] = []
        if #available(macCatalyst 16.0, iOS 16.0, *) {
            findActions = [
                #selector(UIResponder.find(_:)),
                #selector(UIResponder.findNext(_:)),
                #selector(UIResponder.findPrevious(_:)),
                #selector(UIResponder.findAndReplace(_:)),
                #selector(UIResponder.useSelectionForFind(_:)),
            ]
        }
        
        if findActions.contains(where: { $0 == action }) {
            return webView.target(forAction: action, withSender: sender)
        }
        
        return super.target(forAction: action, withSender: sender)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        becomeFirstResponder()

        // If the app resumed and the current tab's web content process was
        // terminated while in background, ensure we restore it now.
        if currentTab.contentProcessTerminated {
            if webView.url != nil || currentTab.url != nil {
                webView.reload()
            } else if let home = currentTab.homeURL {
                currentTab.beginLoadingURL(home)
            } else {
                webView.reload()
            }
        }
    }
        
    internal func updateScriptBlockerButton() {
        var numBlockedScripts: Int = currentTab.blockedScriptOrigins.count
        if currentTab.url != nil, currentTab.javaScriptEnabled == false {
            // Because the page is blocked too, notify.
            numBlockedScripts += 1
        }
        
        var policy: ScriptPolicy? = nil
        if let url = webView.url, let host = url.host {
            policy = policyManager.scriptPolicy(forOrigin: host)
        }
        
        let iconView = toolbarController.scriptControllerIconView
        iconView.shieldsDown = currentTab.javaScriptEnabled
        // iconView.setBlockedScriptsNumber(numBlockedScripts)
        
        if let policy = policy {
            iconView.currentPolicy = policy
        }
        
        iconView.isEnabled = (webView.url != nil)
    }
    
    @discardableResult
    public func createNewTab(withURL url: URL?, loadInBackground: Bool = false) -> Tab {
        let newTab = tabController.createNewTab(url: url)
        
        if !loadInBackground {
            currentTab = newTab
            
            if url == nil && traitCollection.userInterfaceIdiom == .mac {
                self.toolbarController.urlBar.textField.becomeFirstResponder()
            }
        } else {
            // Send this message to get it to load NOW, instead of waiting for it to show up
            // in the view hierarchy.
            currentTab.webView.didMoveToWindow()
            
            // Update tab bar now
            updateTabBarVisibility()
        }
        
        return newTab
    }
}

extension BrowserViewController: UIPopoverPresentationControllerDelegate
{
    func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
        // Forces popovers to present on iPhone
        return .none
    }
}

extension BrowserViewController: ScriptPolicyViewControllerDelegate
{
    func didChangeScriptPolicy() {
        currentTab.bridge.policyDataSourceDidChange()
        webView.reload()
    }
    
    func setScriptsEnabledForTab(_ enabled: Bool) {
        currentTab.javaScriptEnabled = enabled
        toolbarController.scriptControllerIconView.shieldsDown = enabled
    }
}

extension BrowserViewController: AutocompleteViewControllerDelegate
{
    func autocompleteController(_: AutocompleteViewController, didSelectHistoryItem item: HistoryItem) {
        currentTab.beginLoadingURL(item.url)
        autocompleteViewController.view.isHidden = true
    }
}

extension BrowserViewController: TabDelegate
{
    func didBlockScriptOrigin(_ origin: String, forTab: Tab) {
        updateScriptBlockerButton()
    }
}

extension BrowserViewController: TabPickerViewControllerDelegate
{
    func tabPicker(_ picker: TabPickerViewController, createNewTabWithURL url: URL?) {
        currentTab = tabController.createNewTab(url: url)
        picker.dismiss(animated: true)
    }
    
    func tabPicker(_ picker: TabPickerViewController, tabInfoForIdentifier identifier: UUID) -> TabInfo {
        guard let tab = tabController.tab(forIdentifier: identifier) else { fatalError() }
        return tab.tabInfo
    }
    
    func tabPicker(_ picker: TabPickerViewController, didSelectTabInfo info: TabInfo, fromHost host: String) {
        var tab: Tab?
        if host == TabPickerViewController.localHostIdentifier {
            tab = tabController.tab(forIdentifier: info.identifier)
        } else if let urlString = info.urlString {
            tab = tabController.createNewTab(url: URL(string: urlString))
        }
        
        guard let tab else { return }
        
        currentTab = tab
        picker.dismiss(animated: true, completion: nil)
    }
    
    func tabPicker(_ picker: TabPickerViewController, closeTabWithIdentifier tabIdentifier: UUID) {
        guard let tab = tabController.tab(forIdentifier: tabIdentifier) else { return }
        
        tabController.closeTab(tab)
        
        // Dismiss picker if current tab is closed using the picker
        if tab == currentTab {
            picker.dismiss(animated: true, completion: nil)
        }
    }
}

extension BrowserViewController: UITextFieldDelegate
{
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let text = textField.text {
            let matches = BrowserHistory.shared.visitedToplevelHistoryItems(matching: text)
            autocompleteViewController.historyItems = matches
            
            autocompleteViewController.view.isHidden = matches.count == 0
        }
        
        return true
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if let text = textField.text?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) {
                
            // Dumb rules for stuff that "looks like" a URL
            if !text.contains(" "),
               text.components(separatedBy: ".").count > 1,
               var url = URL(string: text)
           {
                if url.scheme == nil {
                    let urlString = "http://\(text)"
                    if let fixedURL = URL(string: urlString) {
                        url = fixedURL
                    }
                }
                
                currentTab.beginLoadingURL(url)
            } else {
                let searchURL = Settings.shared.currentSearchProvider().searchURLWithQuery(text)
                currentTab.beginLoadingURL(searchURL)
            }
            
            textField.resignFirstResponder()
        }
        
        return false
    }
        
    func textFieldDidEndEditing(_ textField: UITextField) {
        if !changingFocusToAutocompleteController {
            autocompleteViewController.view.isHidden = true
        }
    }
}

extension BrowserViewController: ReaderViewControllerDelegate
{
    func readerViewController(_ reader: ReaderViewController, didRequestNavigationToURL navigationURL: URL) {
        currentTab.beginLoadingURL(navigationURL)
    }
}

extension BrowserViewController: URLBarDelegate
{
    func urlBarRequestedFocusEscape(_ urlBar: URLBar) {
        changingFocusToAutocompleteController = true
            _ = self.autocompleteViewController.becomeFirstResponder()
        changingFocusToAutocompleteController = false
    }
}

extension BrowserViewController: MFMailComposeViewControllerDelegate
{
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        controller.dismiss(animated: true, completion: nil)
    }
}

extension BrowserViewController: TabControllerDelegate
{
    func tabController(_ controller: TabController, didUpdateTitle: String, forTab tab: Tab) {
        updateTitleAndURL(forWebView: tab.webView)
        
        // Fetch favicon in background, if applicable
        if tab.favicon == nil, let url = tab.webView.url {
            tab.updateFaviconForURL(url)
        }
    }
    
    func tabController(_ controller: TabController, didUpdateFavicon: UIImage?, forTab tab: Tab) {
        updateTitleAndURL(forWebView: tab.webView)
    }
}
