//
//  SegmentedPageViewController.swift
//  Tusker
//
//  Created by Shadowfacts on 9/13/19.
//  Copyright © 2019 Shadowfacts. All rights reserved.
//

import UIKit

protocol SegmentedPageViewControllerPage: Hashable {
    var segmentedControlTitle: String { get }
}

class SegmentedPageViewController<Page: SegmentedPageViewControllerPage>: UIViewController, UIPageViewControllerDelegate, TabbedPageViewController {
    
    private(set) var pages: [Page]!
    private let pageProvider: (Page) -> UIViewController
    private var pageControllers = [Page: UIViewController]()
    
    private var initialPage: Page
    private(set) var currentPage: Page
    var currentIndex: Int! {
        pages.firstIndex(of: currentPage)
    }
    var currentViewController: UIViewController!
    
    let segmentedControl = ScrollingSegmentedControl<Page>()
    
    init(pages: [Page], pageProvider: @escaping (Page) -> UIViewController) {
        precondition(!pages.isEmpty)
        
        self.pageProvider = pageProvider
        
        initialPage = pages.first!
        currentPage = pages.first!
        
        super.init(nibName: nil, bundle: nil)
        
        setPages(pages, animated: false)
        
        segmentedControl.didSelectOption = { [unowned self] option in
            if let option {
                self.selectPage(option, animated: true)
            }
        }
        // TODO: the custom segmented control isn't treated as a group and I have no idea how to change that
        // the segemented control itself is only focusable when VoiceOver is in Group navigation mode,
        // so make it clear that to switch tabs the user needs to enter the group
        segmentedControl.accessibilityHint = "Enter group to select timeline"
        segmentedControl.setSelectedOption(segmentedControl.options.first!.value, animated: false)
        navigationItem.titleView = segmentedControl
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setPages(_ pages: [Page], animated: Bool) {
        precondition(!pages.isEmpty)
        
        self.pages = pages
        
        if !pages.contains(currentPage) {
            selectPage(pages.first!, animated: animated)
        }
        
        for key in pageControllers.keys where !pages.contains(key) {
            pageControllers.removeValue(forKey: key)
        }
        
        // this needs to happen in init because EnhancedNavigationViewController expects to be able to look at the titleView
        // before the view has necessarily loaded
        segmentedControl.options = pages.map {
            .init(value: $0, name: $0.segmentedControlTitle)
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .appBackground
        
        selectPage(initialPage, animated: false)
        
        addKeyCommand(MenuController.prevSubTabCommand)
        addKeyCommand(MenuController.nextSubTabCommand)
        
        // disable the transparent nav bar because it gets messy with multiple pages at different scroll positions
        if let nav = navigationController {
            let appearance = UINavigationBarAppearance()
            appearance.configureWithDefaultBackground()
            nav.navigationBar.scrollEdgeAppearance = appearance
        }
    }
    
    func configureViewController(_ viewController: UIViewController) {
    }
    
    func selectPage(_ page: Page, animated: Bool) {
        guard pages.contains(page) else {
            fatalError("invalid page \(page) that is not in SegmentedPageViewController.pages")
        }
        guard isViewLoaded else {
            initialPage = page
            return
        }
        let direction: AnimationMode
        if let prevIndex = currentIndex {
            let index = pages.firstIndex(of: page)!
            direction = index - prevIndex > 0 ? .forward : .reverse
        } else {
            direction = .none
        }
        
        currentPage = page
        let newController: UIViewController
        if let existing = pageControllers[page] {
            newController = existing
        } else {
            newController = pageProvider(page)
            configureViewController(newController)
            pageControllers[page] = newController
        }
        
        setViewController(newController, animated: animated ? direction : .none)
        navigationItem.title = newController.title
        
        segmentedControl.setSelectedOption(page, animated: animated)
    }
    
    private func setViewController(_ newViewController: UIViewController, animated: AnimationMode) {
        guard let currentViewController,
              animated != .none else {
            currentViewController?.removeViewAndController()
            newViewController.view.translatesAutoresizingMaskIntoConstraints = false
            // don't use embedChild here because it triggers an appearance transition, even though this vc hasn't appeared yet
            addChild(newViewController)
            view.addSubview(newViewController.view)
            NSLayoutConstraint.activate([
                newViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                newViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                newViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
                newViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            ])
            newViewController.didMove(toParent: self)
            self.currentViewController = newViewController
            return
        }
        guard currentViewController !== newViewController else {
            return
        }
        self.currentViewController = newViewController
        newViewController.view.translatesAutoresizingMaskIntoConstraints = false
        embedChild(newViewController)
        let direction: CGFloat = animated == .forward ? 1 : -1
        newViewController.view.transform = CGAffineTransform(translationX: direction * view.bounds.width, y: 0)
        let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UISpringTimingParameters(dampingRatio: 1, initialVelocity: .zero))
        animator.addAnimations {
            newViewController.view.transform = .identity
            currentViewController.view.transform = CGAffineTransform(translationX: -1 * direction * self.view.bounds.width, y: 0)
        }
        animator.addCompletion { _ in
            currentViewController.removeViewAndController()
        }
        animator.startAnimation()
    }
    
    // MARK: TabbedPageViewController
    
    func selectNextPage() {
        guard currentIndex < pages.count - 1 else { return }
        selectPage(pages[currentIndex + 1], animated: true)
    }
    
    func selectPrevPage() {
        guard currentIndex > 0 else { return }
        selectPage(pages[currentIndex - 1], animated: true)
    }
}
    
extension SegmentedPageViewController {
    enum AnimationMode: Equatable {
        case none
        case forward
        case reverse
    }
}

extension SegmentedPageViewController: TabBarScrollableViewController {
    func tabBarScrollToTop() {
        if let scrollableVC = currentViewController as? TabBarScrollableViewController {
            scrollableVC.tabBarScrollToTop()
        }
    }
}

extension SegmentedPageViewController: BackgroundableViewController {
    func sceneDidEnterBackground() {
        if let current = currentViewController as? BackgroundableViewController {
            current.sceneDidEnterBackground()
        }
    }
}

extension SegmentedPageViewController: StatusBarTappableViewController {
    func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult {
        if let current = currentViewController as? StatusBarTappableViewController {
            return current.handleStatusBarTapped(xPosition: xPosition)
        }
        return .continue
    }
}

extension SegmentedPageViewController: NestedResponderProvider {
    var innerResponder: UIResponder? {
        currentViewController
    }
}
