//
//  DuckableContainerViewController.swift
//  Duckable
//
//  Created by Shadowfacts on 11/7/22.
//

import UIKit

let duckedCornerRadius: CGFloat = 10
let detentHeight: CGFloat = 44

@available(iOS 16.0, *)
public class DuckableContainerViewController: UIViewController {
    
    public let child: UIViewController
    private var bottomConstraint: NSLayoutConstraint!
    private(set) var state = State.idle
    
    public var duckedViewController: DuckableViewController? {
        if case .ducked(let vc, placeholder: _) = state {
            return vc
        } else {
            return nil
        }
    }
    
    public init(child: UIViewController) {
        self.child = child
        
        super.init(nibName: nil, bundle: nil)
        
        swizzleSheetController()
    }
    
    required init?(coder: NSCoder) {
        fatalError()
    }

    public override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .black

        child.beginAppearanceTransition(true, animated: false)
        addChild(child)
        child.didMove(toParent: self)
        child.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(child.view)
        child.endAppearanceTransition()
        
        bottomConstraint = child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        NSLayoutConstraint.activate([
            child.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            child.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            child.view.topAnchor.constraint(equalTo: view.topAnchor),
            bottomConstraint,
        ])
    }
    
    func presentDuckable(_ viewController: DuckableViewController, animated: Bool, isDucked: Bool, completion: (() -> Void)?) {
        guard case .idle = state else {
            if animated,
               case .ducked(_, placeholder: let placeholder) = state {
                UIImpactFeedbackGenerator(style: .light).impactOccurred()
                let origConstant = placeholder.topConstraint.constant
                UIView.animateKeyframes(withDuration: 0.4, delay: 0) {
                    UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
                        placeholder.topConstraint.constant = origConstant - 20
                        self.view.layoutIfNeeded()
                    }
                    UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
                        placeholder.topConstraint.constant = origConstant
                        self.view.layoutIfNeeded()
                    }
                }
            }
            return
        }
        if isDucked {
            state = .ducked(viewController, placeholder: createPlaceholderForDuckedViewController(viewController))
            configureChildForDuckedPlaceholder()
        } else {
            state = .presentingDucked(viewController, isFirstPresentation: true)
            doPresentDuckable(viewController, animated: animated, completion: completion)
        }
    }
    
    private func doPresentDuckable(_ viewController: DuckableViewController, animated: Bool, completion: (() -> Void)?) {
        viewController.modalPresentationStyle = .custom
        viewController.transitioningDelegate = self
        present(viewController, animated: animated) {
            self.configureChildForDuckedPlaceholder()
            completion?()
        }
    }
    
    func dismissalTransitionWillBegin() {
        guard case .presentingDucked(_, _) = state else {
            return
        }
        state = .idle
        bottomConstraint.isActive = false
        bottomConstraint = child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        bottomConstraint.isActive = true
        child.view.layer.cornerRadius = 0
        setOverrideTraitCollection(nil, forChild: child)
    }
    
    func createPlaceholderForDuckedViewController(_ viewController: DuckableViewController) -> DuckedPlaceholderViewController {
        let placeholder = DuckedPlaceholderViewController(for: viewController, owner: self)
        placeholder.view.translatesAutoresizingMaskIntoConstraints = false
        
        placeholder.beginAppearanceTransition(true, animated: false)
        self.addChild(placeholder)
        placeholder.didMove(toParent: self)
        self.view.addSubview(placeholder.view)
        placeholder.endAppearanceTransition()
        
        let placeholderTopConstraint = placeholder.view.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -detentHeight)
        placeholder.topConstraint = placeholderTopConstraint
        NSLayoutConstraint.activate([
            placeholder.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            placeholder.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            placeholder.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            placeholderTopConstraint
        ])
            
        // otherwise the layout changes get lumped in with the system animation
        UIView.performWithoutAnimation {
            self.view.layoutIfNeeded()
        }
        return placeholder
    }
    
    func duckViewController() {
        guard case .presentingDucked(let viewController, isFirstPresentation: _) = state else {
            return
        }
        switch viewController.duckableViewControllerShouldDuck() {
        case .duck:
            let placeholder = createPlaceholderForDuckedViewController(viewController)
            state = .ducked(viewController, placeholder: placeholder)
            configureChildForDuckedPlaceholder()
            dismiss(animated: true)
        case .block:
            viewController.sheetPresentationController!.selectedDetentIdentifier = .large
        case .dismiss:
//            duckableViewControllerWillDismiss()
            dismiss(animated: true)
        }
    }
    
    private func configureChildForDuckedPlaceholder() {
        bottomConstraint.isActive = false
        bottomConstraint = child.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -detentHeight - 4)
        bottomConstraint.isActive = true
        
        child.view.layer.cornerRadius = duckedCornerRadius
        child.view.layer.cornerCurve = .continuous
        child.view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
        child.view.layer.masksToBounds = true
    }
    
    @objc func unduckViewController() {
        guard case .ducked(let viewController, placeholder: let placeholder) = state else {
            return
        }
        state = .presentingDucked(viewController, isFirstPresentation: false)
        doPresentDuckable(viewController, animated: true) {
            placeholder.view.removeFromSuperview()
            placeholder.willMove(toParent: nil)
            placeholder.removeFromParent()
        }
    }
    
    func sheetOffsetDidChange() {
        if case .presentingDucked(let duckable, isFirstPresentation: _) = state {
            duckable.duckableViewControllerMayAttemptToDuck()
        }
    }
    
    enum State {
        case idle
        case presentingDucked(DuckableViewController, isFirstPresentation: Bool)
        case ducked(DuckableViewController, placeholder: DuckedPlaceholderViewController)
    }

}

@available(iOS 16.0, *)
extension DuckableContainerViewController: UIViewControllerTransitioningDelegate {
    public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        let controller = DuckableSheetPresentationController(presentedViewController: presented, presenting: presenting)
        controller.delegate = self
        controller.prefersGrabberVisible = true
        controller.selectedDetentIdentifier = .large
        controller.largestUndimmedDetentIdentifier = .bottom
        controller.detents = [
            .custom(identifier: .bottom, resolver: { context in
                return detentHeight
            }),
            .large(),
        ]
        return controller
    }
    
    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        if case .ducked(_, placeholder: _) = state {
            return DuckAnimationController(
                owner: self,
                needsShrinkAnimation: isDetentChangingDueToGrabberAction
            )
        } else {
            return nil
        }
    }
}

@available(iOS 16.0, *)
class DuckableSheetPresentationController: UISheetPresentationController {
    override func dismissalTransitionWillBegin() {
        super.dismissalTransitionWillBegin()
        (self.delegate as! DuckableContainerViewController).dismissalTransitionWillBegin()
    }
}

@available(iOS 16.0, *)
extension DuckableContainerViewController: UISheetPresentationControllerDelegate {
    public func presentationController(_ presentationController: UIPresentationController, willPresentWithAdaptiveStyle style: UIModalPresentationStyle, transitionCoordinator: UIViewControllerTransitionCoordinator?) {
        guard let snapshot = child.view.snapshotView(afterScreenUpdates: false) else {
            setOverrideTraitCollection(UITraitCollection(userInterfaceLevel: .elevated), forChild: child)
            return
        }
        snapshot.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(snapshot)
        NSLayoutConstraint.activate([
            snapshot.leadingAnchor.constraint(equalTo: child.view.leadingAnchor),
            snapshot.trailingAnchor.constraint(equalTo: child.view.trailingAnchor),
            snapshot.topAnchor.constraint(equalTo: child.view.topAnchor),
            snapshot.bottomAnchor.constraint(equalTo: child.view.bottomAnchor),
        ])
        setOverrideTraitCollection(UITraitCollection(userInterfaceLevel: .elevated), forChild: child)
        transitionCoordinator!.animate { context in
            snapshot.layer.opacity = 0
        } completion: { _ in
            snapshot.removeFromSuperview()
        }
    }
    
    public func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
        if sheetPresentationController.selectedDetentIdentifier == .bottom {
            duckViewController()
        }
    }
}
