//
//  TabPickerViewController.swift
//  SBrowser
//
//  Created by James Magahern on 7/30/20.
//

import UIKit
import Combine

protocol TabPickerViewControllerDelegate: AnyObject
{
    func tabPicker(_ picker: TabPickerViewController, createNewTabWithURL: URL?)
    func tabPicker(_ picker: TabPickerViewController, closeTabWithIdentifier tab: UUID)
    func tabPicker(_ picker: TabPickerViewController, tabInfoForIdentifier: UUID) -> TabInfo
    func tabPicker(_ picker: TabPickerViewController, didSelectTabInfo: TabInfo, fromHost: String)
}

class TabPickerViewController: UIViewController, UICollectionViewDelegate
{
    typealias TabID = UUID
    
    public static var localHostIdentifier = "__localhost__";
    
    public var selectedTabIdentifier: TabID?
    public var selectedTabHost: String? { didSet { didChangeSelectedTabHost(selectedTabHost!) } }
    
    weak var delegate: TabPickerViewControllerDelegate?
    public var tabObserver: AnyCancellable?
    public var displayedError: Error? = nil { didSet { didSetDisplayedError(displayedError) } }
    
    private var displayedErrorView: UITextView?
    private var selectedTabIdentifiersForEditing: Set<TabInfo> = []
    private var tabIdentifiersByHost: [String: [TabInfo]] = [:]
    
    private var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
    private lazy var listLayout = UICollectionViewCompositionalLayout.list(using: listConfiguration)
    private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: listLayout).conf { collectionView in
        collectionView.allowsMultipleSelectionDuringEditing = true
        collectionView.backgroundColor = .systemGroupedBackground
        collectionView.delegate = self
    }
    
    private lazy var cellRegistry = UICollectionView.CellRegistration<UICollectionViewListCell, TabInfo> { [unowned self] (listCell, indexPath, tab) in
        var config = listCell.defaultContentConfiguration()
        
        if let title = tab.title, title.count > 0 {
            config.text = title
            config.secondaryText = tab.urlString
        } else if let url = tab.urlString {
            config.text = url
            config.secondaryText = url
        } else {
            config.text = "New Tab"
        }
        
        config.textProperties.numberOfLines = 1
        config.secondaryTextProperties.numberOfLines = 1
        
        if let faviconData = tab.faviconData, let image = UIImage(data: faviconData) {
            config.image = image
        } else {
            config.image = UIImage(systemName: "safari")
        }
        
        config.imageProperties.maximumSize = CGSize(width: 21.0, height: 21.0)
        config.imageProperties.cornerRadius = 3.0
        
        if let selectedTabIdentifier, selectedTabIdentifier == tab.identifier {
            listCell.accessories = [ .checkmark() ]
        } else {
            listCell.accessories = []
        }
        
        listCell.contentConfiguration = config
    }

    private lazy var dataSource = UICollectionViewDiffableDataSource<Int, TabInfo>(collectionView: collectionView)
    { [unowned self] (collectionView, indexPath, item) -> UICollectionViewCell? in
        return collectionView.dequeueConfiguredReusableCell(using: cellRegistry, for: indexPath, item: item)
    }
    
    override var traitCollection: UITraitCollection {
        get { return super.traitCollection.alwaysPadLike() }
    }
    
    public lazy var newTabButton: UIBarButtonItem = {
        UIBarButtonItem(systemItem: .add, primaryAction: UIAction(handler: { [unowned self] _ in
            self.delegate?.tabPicker(self, createNewTabWithURL: nil)
        }), menu: nil)
    }()
    
    private lazy var deleteTabButton: UIBarButtonItem = {
        UIBarButtonItem(systemItem: .trash, primaryAction: UIAction(handler: { [unowned self] _ in
            deleteSelectedTabs()
        }), menu: nil)
    }()
    
    private lazy var hostPickerButton: UIButton = {
        var buttonConfiguration = UIButton.Configuration.bordered()
        buttonConfiguration.title = "Host"
        
        let button = UIButton(configuration: buttonConfiguration)
        button.changesSelectionAsPrimaryAction = true
        button.showsMenuAsPrimaryAction = true
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setContentCompressionResistancePriority(.required, for: .horizontal)
        
        return button
    }()
    
    init() {
        super.init(nibName: nil, bundle: nil)
        self.title = "Tabs"
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        // Load this lazy var now.
        _ = cellRegistry.self
        
        listConfiguration.trailingSwipeActionsConfigurationProvider = { [unowned self] indexPath in
            return UISwipeActionsConfiguration(actions: [ UIContextualAction(style: .destructive, title: "Close", handler: { [unowned self] (action, view, completionHandler) in
                if let item = dataSource.itemIdentifier(for: indexPath) {
                    var snapshot = dataSource.snapshot()
                    delegate?.tabPicker(self, closeTabWithIdentifier: item.identifier)
                    
                    snapshot.deleteItems([ item ])
                    dataSource.apply(snapshot, animatingDifferences: true)
                }
            })])
        }

        self.view = self.collectionView
        
        configureNavigationButtons(forEditing: isEditing)
    }
    
    public func setTabInfos(_ infos: [TabInfo], forHost host: String) {
        tabIdentifiersByHost[host] = infos
        
        if host == selectedTabHost {
            var snapshot = dataSource.snapshot()
            snapshot.deleteAllItems()
            snapshot.appendSections([ 0 ])
            snapshot.appendItems(infos)
            dataSource.apply(snapshot) // crashing here...
        }
        
        reloadHostPickerButtonMenu()
    }
    
    private func reloadHostPickerButtonMenu() {
        var menuChildren: [UIAction] = []
        for host in tabIdentifiersByHost.keys {
            menuChildren.append(UIAction(title: host, handler: { [unowned self] _ in
                selectedTabHost = host
            }))
        }
        
        hostPickerButton.menu = UIMenu(children: menuChildren)
        
        // Update list for whatever the selected item is 
        selectedTabHost = hostPickerButton.menu?.selectedElements.first?.title
        
        if tabIdentifiersByHost.keys.count > 0 && tabIdentifiersByHost.keys.first != Self.localHostIdentifier {
            navigationItem.titleView = hostPickerButton
        } else {
            navigationItem.titleView = nil
        }
    }
    
    private func configureNavigationButtons(forEditing: Bool) {
        if !forEditing {
            navigationItem.rightBarButtonItem = newTabButton
        } else {
            deleteTabButton.isEnabled = collectionView.indexPathsForSelectedItems?.count ?? 0 > 0
            navigationItem.rightBarButtonItem = deleteTabButton
        }
        
        navigationItem.leftBarButtonItem = editButtonItem
    }
    
    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        collectionView.isEditing = editing
        
        configureNavigationButtons(forEditing: editing)
    }
    
    private func deleteSelectedTabs() {
        var snapshot = dataSource.snapshot()
        for tab in selectedTabIdentifiersForEditing {
            snapshot.deleteItems([ tab ])
            delegate?.tabPicker(self, closeTabWithIdentifier: tab.identifier)
        }
        
        dataSource.apply(snapshot, animatingDifferences: true)
    }
    
    private func didChangeSelectedTabHost(_ tabHost: String) {
        guard let tabIdentifiers = tabIdentifiersByHost[tabHost] else { fatalError() }
        
        var snapshot = dataSource.snapshot()
        snapshot.deleteAllItems()
        snapshot.appendSections([ 0 ])
        snapshot.appendItems(tabIdentifiers)
        dataSource.applySnapshotUsingReloadData(snapshot)
    }
    
    private func didSetDisplayedError(_ displayedError: Error?) {
        if let displayedError {
            // Clear items first
            var snapshot = dataSource.snapshot()
            snapshot.deleteAllItems()
            dataSource.applySnapshotUsingReloadData(snapshot)
            
            if displayedErrorView == nil {
                let errorView = UITextView(frame: .zero)
                errorView.isUserInteractionEnabled = false
                errorView.translatesAutoresizingMaskIntoConstraints = false
                errorView.isScrollEnabled = false
                collectionView.addSubview(errorView)
                
                let guide = collectionView.layoutMarginsGuide
                NSLayoutConstraint.activate([
                    errorView.leadingAnchor     .constraint(equalTo: guide.leadingAnchor),
                    errorView.trailingAnchor    .constraint(equalTo: guide.trailingAnchor),
                    errorView.centerYAnchor     .constraint(equalTo: guide.centerYAnchor),
                ])
                
                self.displayedErrorView = errorView
            }
            
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.alignment = .center
            
            var attributedString = try! AttributedString(markdown: "**Error loading tabs**: \(displayedError.localizedDescription)")
            attributedString.foregroundColor = UIColor.secondaryLabel
            attributedString.paragraphStyle = paragraphStyle
            attributedString.font = UIFont.preferredFont(forTextStyle: .body)
            
            displayedErrorView?.attributedText = NSAttributedString(attributedString)
        } else {
            displayedErrorView?.removeFromSuperview()
            displayedErrorView = nil
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard let tab = dataSource.itemIdentifier(for: indexPath) else { return }
        
        if !isEditing {
            delegate?.tabPicker(self, didSelectTabInfo: tab, fromHost: selectedTabHost!)
        } else {
            deleteTabButton.isEnabled = collectionView.indexPathsForSelectedItems?.count ?? 0 > 0
            selectedTabIdentifiersForEditing.update(with: tab)
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
        guard let tabIdentifier = dataSource.itemIdentifier(for: indexPath) else { return }
        
        if isEditing {
            selectedTabIdentifiersForEditing.remove(tabIdentifier)
            deleteTabButton.isEnabled = collectionView.indexPathsForSelectedItems?.count ?? 0 > 0
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool {
        true
    }
    
    func collectionView(_ collectionView: UICollectionView, didBeginMultipleSelectionInteractionAt indexPath: IndexPath) {
        isEditing = true
    }
}
