//
//  GeneralSettingsViewController.swift
//  GeneralSettingsViewController
//
//  Created by James Magahern on 6/25/21.
//

import UIKit

struct LabelContentConfiguration : UIContentConfiguration
{
    var text: String = ""
    var insets: UIEdgeInsets = .zero
    var textAlignment: NSTextAlignment = .natural
        
    func makeContentView() -> UIView & UIContentView {
        let label = UILabel(frame: .zero)
        let contentView = GenericContentView<UILabel, LabelContentConfiguration>(configuration: self, view: label) { config, label in
            label.text = config.text
            label.textAlignment = config.textAlignment
        }
        contentView.insets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
        return contentView
    }
    
    func updated(for state: UIConfigurationState) -> Self {
        self
    }
}

struct TextFieldContentConfiguration : UIContentConfiguration
{
    var text: String = ""
    var placeholderText: String? = nil
    var textChanged: ((String) -> Void)? = nil
    var pressedReturn: ((UITextField) -> Void)? = nil
    var keyboardType: UIKeyboardType = .default
    var returnKeyType: UIReturnKeyType = .default
    
    func makeContentView() -> UIView & UIContentView {
        let textField = UITextField(frame: .zero)
        textField.borderStyle = .roundedRect
        textField.autocorrectionType = .no
        textField.autocapitalizationType = .none
        textField.keyboardType = keyboardType
        textField.returnKeyType = returnKeyType
        
        return GenericContentView<UITextField, TextFieldContentConfiguration>(configuration: self, view: textField) { config, textField in
            textField.text = config.text
            textField.placeholder = config.placeholderText
            
            if let textChanged = config.textChanged {
                textField.addAction(UIAction { _ in textChanged(textField.text ?? "") }, for: .editingChanged)
            }
            
            if let pressedReturn = config.pressedReturn {
                class ReturnDelegate : NSObject, UITextFieldDelegate {
                    var pressedReturn: ((UITextField) -> Void)
                    
                    public init(pressedReturn: @escaping ((UITextField) -> Void)) {
                        self.pressedReturn = pressedReturn
                    }
                    
                    public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
                        pressedReturn(textField)
                        return false
                    }
                }
                
                let delegate = ReturnDelegate(pressedReturn: pressedReturn)
                objc_setAssociatedObject(textField, "returnDelegate", delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                textField.delegate = delegate
            }
        }
    }
    
    func updated(for state: UIConfigurationState) -> TextFieldContentConfiguration {
        self
    }
}

struct ButtonContentConfiguration : UIContentConfiguration
{
    var menu: UIMenu
    
    func makeContentView() -> UIView & UIContentView {
        let button = UIButton(primaryAction: nil)
        return GenericContentView<UIButton, ButtonContentConfiguration>(configuration: self, view: button) { config, button in
            button.menu = menu
            button.showsMenuAsPrimaryAction = true
            if #available(macCatalyst 15.0, iOS 15.0, *) {
                var buttonConfiguration = UIButton.Configuration.plain()
                buttonConfiguration.titleAlignment = .trailing
                
                button.configuration = buttonConfiguration
                button.changesSelectionAsPrimaryAction = true
            }
        }
    }
    
    func updated(for state: UIConfigurationState) -> ButtonContentConfiguration {
        self
    }
}

class GeneralSettingsViewController: UIViewController
{
    enum Section: String, CaseIterable {
        case searchEngine = "Search Engine"
        case syncServer = "Sync Server"
    }

    typealias Item = String

    static let SearchProviderPopupItem = "searchProvider.popup"
    static let SyncServerItem = "syncServer.field"

    let dataSource: UICollectionViewDiffableDataSource<Section, Item>
    let collectionView: UICollectionView
    let viewModel: ViewModel

    static func createLayout(forIdiom idiom: UIUserInterfaceIdiom) -> UICollectionViewLayout {
#if targetEnvironment(macCatalyst)
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                              heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.60),
                                               heightDimension: .absolute(44))

        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
        group.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .flexible(1.0), top: nil, trailing: nil, bottom: nil)

        let insets = NSDirectionalEdgeInsets(top: 24.0, leading: 64.0, bottom: 24.0, trailing: 64.0)

        let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.40),
                                                      heightDimension: .estimated(44.0))
        let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
            layoutSize: headerFooterSize,
            elementKind: UICollectionView.elementKindSectionHeader,
            alignment: .topLeading,
            absoluteOffset: CGPoint(x: insets.leading, y: insets.top + 5.0)
        )
        sectionHeader.extendsBoundary = false
        sectionHeader.contentInsets = insets

        let section = NSCollectionLayoutSection(group: group)
        // section.interGroupSpacing = spacing
        section.contentInsets = insets
        section.supplementariesFollowContentInsets = true
        section.boundarySupplementaryItems = [ sectionHeader ]

        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
#else
        var listConfiguration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        listConfiguration.headerMode = .supplementary
        return UICollectionViewCompositionalLayout.list(using: listConfiguration)
#endif
    }

    static func sectionHeaderConfiguration(forIdiom idiom: UIUserInterfaceIdiom, sectionName: String) -> UIContentConfiguration {
        if idiom == .mac {
            return LabelContentConfiguration(
                text: sectionName + ": ",
                insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0, right: 10.0),
                textAlignment: .right
            )
        } else {
            var config = UIListContentConfiguration.header()
            config.text = sectionName
            return config
        }
    }

#if targetEnvironment(macCatalyst)
    static let staticIdiom = UIUserInterfaceIdiom.mac
#else
    static let staticIdiom = UIUserInterfaceIdiom.pad
#endif

    init() {
        let actionHandler = { (action: UIAction) in
            let engineName = action.title
            Settings.shared.defaultSearchEngineName = engineName
        }

        let viewModel = ViewModel()
        let itemCellRegistry = UICollectionView.CellRegistration<UICollectionViewCell, Item> { cell, indexPath, identifier in
            if identifier == Self.SearchProviderPopupItem {
                let names = Settings.shared.searchEngines.keys.sorted()
                var engineMenuItems: [UIMenuElement] = names.map { name in
                    let action = UIAction(title: name, handler: actionHandler)
                    action.state = (Settings.shared.defaultSearchEngineName == name) ? .on : .off
                    return action
                }

                engineMenuItems.append(UIMenu(options: .displayInline, children: [
                    UIAction(title: "Manage Search Engines…", handler: { _ in
                        viewModel.onManageSearchEngines()
                    })
                ]))

                let menu = UIMenu(children: engineMenuItems)
                cell.contentConfiguration = ButtonContentConfiguration(menu: menu)
            } else if identifier == Self.SyncServerItem {
                cell.contentConfiguration = TextFieldContentConfiguration(
                    text: Settings.shared.syncServer ?? "",
                    placeholderText: "https://sync.server.com",
                    textChanged: { newString in
                        Settings.shared.syncServer = newString
                    },
                    pressedReturn: { $0.resignFirstResponder() },
                    keyboardType: .URL,
                    returnKeyType: .done
                )
            }

#if !targetEnvironment(macCatalyst)
            cell.backgroundConfiguration = UIBackgroundConfiguration.listCell()
#endif
        }

        let sectionHeaderRegistry = UICollectionView.SupplementaryRegistration<UICollectionViewCell>(elementKind: UICollectionView.elementKindSectionHeader, handler: {
            (cell, string, indexPath) in
            let sectionName = Section.allCases[indexPath.section].rawValue
            cell.contentConfiguration = Self.sectionHeaderConfiguration(forIdiom: Self.staticIdiom, sectionName: sectionName)
        })

        let layout = Self.createLayout(forIdiom: Self.staticIdiom)
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.backgroundColor = .systemGroupedBackground

        dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
            (collectionView, indexPath, identifier) in
            return collectionView.dequeueConfiguredReusableCell(using: itemCellRegistry, for: indexPath, item: identifier)
        }

        dataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
            return collectionView.dequeueConfiguredReusableSupplementary(using: sectionHeaderRegistry, for: indexPath)
        }

        self.viewModel = viewModel

        super.init(nibName: nil, bundle: nil)

        collectionView.delegate = self
        tabBarItem.title = "General"
        tabBarItem.image = UIImage(systemName: "gear")

        let resetPopup = { [weak self] in
            guard let self else { return }
            
            var snapshot = dataSource.snapshot()
            snapshot.reloadItems([ Self.SearchProviderPopupItem ])
            dataSource.apply(snapshot, animatingDifferences: false)
        }

        viewModel.onManageSearchEngines = { [weak self] in
            guard let self else { return }

            // Reset menu item (to show currently selected search engine)
            resetPopup()

            let viewController = ManageSearchEnginesViewController()
            viewController.title = "Manage Search Engines"

            viewController.model.onAddSearchEngine = { [weak self] in
                guard let self else { return }

                let addSearchEngineViewController = AddSearchEngineViewController()
                let navController = UINavigationController(rootViewController: addSearchEngineViewController)

                // Cancel
                addSearchEngineViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction { _ in
                    navController.dismiss(animated: true)
                })

                // Done
                addSearchEngineViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction { [weak self] _ in
                    self?.saveCustomSearchEngine(model: addSearchEngineViewController.viewModel)
                    navController.dismiss(animated: true)

                    resetPopup()
                    viewController.model.reloadConfiguredEngines()
                })

                self.present(navController, animated: true)
            }

            viewController.model.onChangeEngines = {
                resetPopup()
            }

            #if targetEnvironment(macCatalyst)
                let alertController = UIAlertController(title: "Not yet implemented for macOS.", message: nil, preferredStyle: .alert)
                alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in alertController.dismiss(animated: true) }))
                present(alertController, animated: true)
            #else
                navigationController?.pushViewController(viewController, animated: true)
            #endif
        }
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
        self.view = collectionView
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        applySnapshot(animatingDifferences: false)
    }

    // MARK: - Types

    @MainActor
    @Observable
    class ViewModel
    {
        var onManageSearchEngines: () -> Void = {}
    }
}

extension GeneralSettingsViewController : UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
        false
    }
}

private extension GeneralSettingsViewController {
    func applySnapshot(animatingDifferences: Bool) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections(Section.allCases)
        snapshot.appendItems([ Self.SearchProviderPopupItem ], toSection: .searchEngine)
        snapshot.appendItems([ Self.SyncServerItem ], toSection: .syncServer)
        dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
    }
    
    func saveCustomSearchEngine(model: AddSearchEngineView.ViewModel) {
        let name = model.name.trimmingCharacters(in: .whitespacesAndNewlines)
        let url = model.url.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !name.isEmpty, !url.isEmpty else { return }

        // Require placeholder
        guard url.contains("%q") || url.contains("%s") else { return }
        
        // Save
        var engines = Settings.shared.searchEngines
        engines[name] = url
        Settings.shared.searchEngines = engines

        if model.makeDefault {
            Settings.shared.defaultSearchEngineName = name
        }

        // Reset inputs and refresh UI
        var snapshot = dataSource.snapshot()
        snapshot.reloadItems([ Self.SearchProviderPopupItem ])
        dataSource.apply(snapshot, animatingDifferences: false)
    }
}
