//
//  TabBarView.swift
//  App
//
//  Created by James Magahern on 10/28/20.
//

import UIKit

class TabView: UIControl
{
    var active: Bool = false { didSet { layoutSubviews() } }
    var collapsed: Bool = false
    var identifier: UUID?
    
    let label = UILabel(frame: .zero)
    let closeButton = UIButton(frame: .zero)
    let imageView = UIImageView(image: nil)
    
    private let leftSeparator = UIView(frame: .zero)
    private let rightSeparator = UIView(frame: .zero)
    
    convenience init() {
        self.init(frame: .zero)
        
        addSubview(label)
        label.text = "Tab View"
        label.font = .systemFont(ofSize: 11.0)
        
        addSubview(closeButton)
        closeButton.setImage(UIImage(systemName: "xmark.square.fill"), for: .normal)
        closeButton.isPointerInteractionEnabled = true
        closeButton.imageView?.contentMode = .center
        closeButton.tintColor = .label
        
        addSubview(leftSeparator)
        addSubview(rightSeparator)
        leftSeparator.backgroundColor = .secondarySystemFill
        rightSeparator.backgroundColor = .secondarySystemFill
        
        imageView.contentMode = .scaleAspectFit
        addSubview(imageView)
        
        // Try just one for now
        leftSeparator.isHidden = true
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let insetBounds = bounds.inset(by: layoutMargins)
        
        let closeButtonPadding = CGFloat(5.0)
        let closeButtonSize = CGSize(width: bounds.height, height: bounds.height)
        closeButton.frame = CGRect(
            x: (insetBounds.width - closeButton.imageView!.image!.size.height).rounded(), y: 0.0,
            width: closeButtonSize.width, height: closeButtonSize.height
        )
        
        var xOffset = layoutMargins.left
        imageView.frame = CGRect(
            x: xOffset, y: insetBounds.minY,
            width: insetBounds.height, height: insetBounds.height
        )
        xOffset += imageView.frame.width + 8.0
        
        label.frame = CGRect(
            x: xOffset, y: insetBounds.minY,
            width: closeButton.frame.minX - closeButtonPadding - xOffset, height: insetBounds.height
        )
        
        let separatorWidth = CGFloat(1.0)
        leftSeparator.frame = CGRect(
            x: 0.0, y: 0.0,
            width: separatorWidth, height: bounds.height
        )
        
        rightSeparator.frame = CGRect(
            x: bounds.width - separatorWidth, y: 0.0,
            width: separatorWidth, height: bounds.height
        )
        
        let alphaMod = !active ? 0 : 1.0
        label.alpha = CGFloat.maximum(alphaMod, 0.4)
        imageView.alpha = CGFloat.maximum(alphaMod, 0.4)
        closeButton.alpha = CGFloat.maximum(alphaMod, 0.5)
        
        if isTracking {
            backgroundColor = .systemFill
        } else if active {
            backgroundColor = .init(dynamicProvider: { (traitCollection) -> UIColor in
                if traitCollection.userInterfaceStyle == .light {
                    return .secondarySystemGroupedBackground
                } else {
                    return .secondarySystemGroupedBackground
                }
            })
        } else {
            backgroundColor = .init(dynamicProvider: { (traitCollection) -> UIColor in
                if traitCollection.userInterfaceStyle == .light {
                    return .secondarySystemFill
                } else {
                    return UIColor(white: 0.1, alpha: 1.0)
                }
            })
        }
    }
    
    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        super.beginTracking(touch, with: event)
        setNeedsLayout()
        return true
    }
    
    override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
        super.endTracking(touch, with: event)
        setNeedsLayout()
    }
}

protocol TabBarViewDataSource: AnyObject {
    func numberOfTabs(forTabBarView: TabBarView) -> Int
    func tabBarView(_ tabBarView: TabBarView, titleForTabAtIndex: Int) -> String
    func tabBarView(_ tabBarView: TabBarView, imageForTabAtIndex: Int) -> UIImage?
    func tabBarView(_ tabBarView: TabBarView, uniqueIdentifierForTabAtIndex: Int) -> UUID
}

protocol TabBarViewDelegate: AnyObject {
    func tabBarView(_ tabBarView: TabBarView, didClickToActivateTabAtIndex: Int)
    func tabBarView(_ tabBarView: TabBarView, didClickToCloseTabAtIndex: Int)
}

class TabBarView: UIView
{
    static let preferredHeight: CGFloat = 30.0
    
    public weak var delegate: TabBarViewDelegate?
    public weak var dataSource: TabBarViewDataSource?
    
    private var tabViews: [TabView] = []
    private var activeTabIndex: Int = 0
    private var tabContainerView = UIScrollView(frame: .zero)
    private var needsScrollAdjustment = false
    private let bottomSeparatorView = UIView(frame: .zero)
    
    private let placeholderTabImage = UIImage(systemName: "network")
    
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        CGSize(width: size.width, height: Self.preferredHeight)
    }
    
    convenience init() {
        self.init(frame: .zero)
        
        addSubview(tabContainerView)
        backgroundColor = .secondarySystemGroupedBackground
        tabContainerView.showsHorizontalScrollIndicator = false
        tabContainerView.showsVerticalScrollIndicator = false
        
        addSubview(bottomSeparatorView)
        bottomSeparatorView.backgroundColor = .systemFill
    }
    
    public func reloadTabs(animated: Bool = true) {
        guard let dataSource = self.dataSource else { return }
        
        var tabViewsRemoved = Set<TabView>(tabViews)
        
        let numberOfTabs = dataSource.numberOfTabs(forTabBarView: self)
        for i in 0..<numberOfTabs {
            let identifier = dataSource.tabBarView(self, uniqueIdentifierForTabAtIndex: i)
            if let tabView = tabViewsRemoved.first(where: { $0.identifier == identifier }) {
                tabViewsRemoved.remove(tabView)
            } else {
                let newTabView = makeTabView(withIdentifier: identifier)
                if animated { newTabView.collapsed = true }
                if i > 0 && i < tabViews.count {
                    tabViews.insert(newTabView, at: i - 1)
                } else {
                    tabViews.append(newTabView)
                }
            }
        }
        
        layoutSubviews()
        
        if animated {
            // Incoming tabs uncollapse
            tabViews.forEach { $0.collapsed = false }
            
            // Outgoing tabs collapse
            tabViewsRemoved.forEach { $0.collapsed = true }
            
            // Reload tabs that are still here
            for i in 0..<numberOfTabs {
                reloadTab(atIndex: i)
            }
            
            UIView.animate(withDuration: 0.22, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: [], animations: { [unowned self] in
                layoutSubviews()
            }, completion: { [weak self] finished in
                guard let self = self else { return }
                tabViewsRemoved.forEach { $0.removeFromSuperview() }
                self.tabViews.removeAll { tabViewsRemoved.contains($0) }
            })
        }
        
        needsScrollAdjustment = true
    }
    
    public func reloadTab(atIndex index: Int) {
        guard let dataSource = self.dataSource else { return }
        if index > tabViews.count - 1 { return }
        
        let title = dataSource.tabBarView(self, titleForTabAtIndex: index)
        let image = dataSource.tabBarView(self, imageForTabAtIndex: index)
        let identifier = dataSource.tabBarView(self, uniqueIdentifierForTabAtIndex: index)
        
        if let tabView = visibleTab(atIndex: index) {
            tabView.label.text = title
            tabView.identifier = identifier
            tabView.imageView.image = image ?? placeholderTabImage
        }
    }
    
    public func activateTab(atIndex index: Int) {
        self.activeTabIndex = index
        setNeedsLayout()
    }
    
    private func visibleTab(atIndex index: Int) -> TabView? {
        let visibleTabs = tabViews.filter { $0.collapsed == false }
        if index >= visibleTabs.count { return nil }
        
        return visibleTabs[index]
    }
    
    private func makeTabView(withIdentifier identifier: UUID) -> TabView {
        let tabView = TabView()
        tabView.identifier = identifier
        
        tabView.addAction(UIAction(handler: { [unowned self, unowned tabView] _ in
            guard let delegate = self.delegate else { return }
            guard let tabIndex = self.tabViews.firstIndex(of: tabView) else { return }
            
            delegate.tabBarView(self, didClickToActivateTabAtIndex: tabIndex)
        }), for: .touchUpInside)
        
        tabView.closeButton.addAction(UIAction(handler: { [unowned self, unowned tabView] _ in
            guard let delegate = self.delegate else { return }
            guard let tabIndex = self.tabViews.firstIndex(of: tabView) else { return }
            
            delegate.tabBarView(self, didClickToCloseTabAtIndex: tabIndex)
        }), for: .touchUpInside)
        
        return tabView
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let tabContainerBounds = bounds
        tabContainerView.frame = tabContainerBounds
        
        let separatorHeight = CGFloat(1.0)
        bottomSeparatorView.frame = CGRect(
            x: 0.0, y: bounds.height - separatorHeight,
            width: bounds.width, height: separatorHeight
        )
        
        let minimumTabWidth = { (traitCollection: UITraitCollection) -> CGFloat in
            if traitCollection.horizontalSizeClass == .compact {
                return (tabContainerBounds.width / 3.0) + 15.0
            } else {
                return 140.0
            }
        }(traitCollection)
        
        let maximumTabWidth = tabContainerBounds.width
        
        let visibleTabCount = tabViews.filter({ $0.collapsed == false }).count
        
        var xOffset = CGFloat(0.0)
        var tabWidth: CGFloat = (tabContainerBounds.width / CGFloat(visibleTabCount))
        if tabWidth < minimumTabWidth {
            tabWidth = minimumTabWidth
        } else if tabWidth > maximumTabWidth {
            tabWidth = maximumTabWidth
        }
        
        var visibleTabIndex = 0
        for tabView in tabViews {
            tabContainerView.addSubview(tabView)
            
            tabView.alpha = tabView.collapsed ? 0.0 : 1.0
            
            tabView.frame = CGRect(
                x: xOffset,
                y: tabContainerBounds.minY,
                width: tabView.collapsed ? 0.0 : tabWidth,
                height: tabContainerBounds.height
            )
            
            xOffset += tabView.frame.width
            
            if visibleTabIndex == activeTabIndex {
                tabView.active = true
            } else {
                tabView.active = false
            }
            
            tabView.layoutIfNeeded()
            
            if !tabView.collapsed { visibleTabIndex += 1 }
        }
        
        tabContainerView.contentSize = CGSize(
            width: xOffset, height: tabContainerBounds.height
        )
        
        // Adjust content offset, if needed
        if needsScrollAdjustment {
            if let activeTabView = tabViews.first(where: { $0.active }) {
                if activeTabView.frame.maxX > (tabContainerView.contentOffset.x + tabContainerBounds.width) {
                    let contentOffset = min(tabContainerView.contentSize.width - tabContainerView.bounds.width, activeTabView.frame.maxX - tabContainerBounds.width)
                    tabContainerView.setContentOffset(CGPoint(x: contentOffset, y: 0), animated: true)
                }
            }
        }
    }
}
