//
//  ToastableViewController.swift
//  ToastableViewController
//
//  Created by Shadowfacts on 8/14/21.
//  Copyright © 2021 Shadowfacts. All rights reserved.
//

import UIKit

@MainActor
protocol ToastableViewController: UIViewController {
    
    var toastParentView: UIView { get }
    var toastScrollView: UIScrollView? { get }
    
}

private let currentToastKey = UnsafeMutableRawPointer.allocate(byteCount: 0, alignment: 0)

extension ToastableViewController {
    
    private(set) var currentToast: ToastView? {
        get {
            let holder = objc_getAssociatedObject(self, currentToastKey) as? WeakHolder<ToastView>
            return holder?.object
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(self, currentToastKey, WeakHolder(newValue), .OBJC_ASSOCIATION_RETAIN)
            } else {
                objc_setAssociatedObject(self, currentToastKey, nil, .OBJC_ASSOCIATION_RETAIN)
            }
        }
    }
    
    var toastParentView: UIView { view }
    var toastScrollView: UIScrollView? {
        if let scrollView = view as? UIScrollView {
            return scrollView
        } else if let collectionVC = self as? CollectionViewController {
            return collectionVC.collectionView
        } else {
            return nil
        }
    }
    
    func showToast(configuration config: ToastConfiguration, animated: Bool) {
        currentToast?.dismissToast(animated: false)
        
        var config = config
        config.edge = effectiveEdge(edge: config.edge)
        
        let toast = ToastView(configuration: config)
        currentToast = toast
        
        let parentSafeArea = toastParentView.safeAreaLayoutGuide
        toast.translatesAutoresizingMaskIntoConstraints = false
        toastParentView.addSubview(toast)
        
        let yConstraint: NSLayoutConstraint
        switch config.edge {
        case .top:
            yConstraint = toast.topAnchor.constraint(equalTo: parentSafeArea.topAnchor, constant: config.edgeSpacing)
        case .bottom:
            yConstraint = parentSafeArea.bottomAnchor.constraint(equalTo: toast.bottomAnchor, constant: config.edgeSpacing)
        case .automatic:
            fatalError("unreachable")
        }
        
        NSLayoutConstraint.activate([
            toast.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: parentSafeArea.leadingAnchor, multiplier: 1),
            parentSafeArea.trailingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: toast.trailingAnchor, multiplier: 1),
            parentSafeArea.centerXAnchor.constraint(equalTo: toast.centerXAnchor),
            yConstraint,
        ])
        
        if animated {
            toast.animateAppearance()
        } else {
            config.onAppear?(nil)
        }
        
        if config.dismissOnScroll,
           let scrollView = toastScrollView {
            toast.setupDismissOnScroll(connectedTo: scrollView)
        }
        
        if let time = config.dismissAutomaticallyAfter {
            DispatchQueue.main.asyncAfter(deadline: .now() + time) { [weak toast] in
                guard let toast = toast, toast.shouldDismissAutomatically else { return }
                toast.dismissToast(animated: true)
            }
        }
    }
    
    private func effectiveEdge(edge: ToastConfiguration.Edge) -> ToastConfiguration.Edge {
        guard case .automatic = edge else {
            return edge
        }
        if UIDevice.current.userInterfaceIdiom == .phone {
            return .bottom
        } else {
            return .top
        }
    }
    
}
