//
//  TableViewCell.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 10/29/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

import UIKit


@objcMembers open class TableViewCell: UITableViewCell, MoleculeViewProtocol, MoleculeListCellProtocol, MVMCoreViewProtocol, MFButtonProtocol {
    //--------------------------------------------------
    // MARK: - Properties
    //--------------------------------------------------
    
    open var molecule: MoleculeViewProtocol?
    open var listItemModel: ListItemModelProtocol?
    public let containerHelper = ContainerHelper()
    
    // For the accessory view convenience.
    private var caretView: CaretView?
    private var caretViewWidthSizeObject: MFSizeObject?
    private var caretViewHeightSizeObject: MFSizeObject?
    
    // For separation between cells.
    public var topSeparatorView: Line?
    public var bottomSeparatorView: Line?
    
    /// For subclasses that want to use a custom accessory view.
    open var customAccessoryView = false
    
    public var heroAccessoryCenter: CGPoint?
    
    private var initialSetupPerformed = false
    
    //--------------------------------------------------
    // MARK: - Styling
    //--------------------------------------------------
    
    open func styleLine(with style: ListItemStyle?) {
        switch style {
        case .standard?:
            topSeparatorView?.setStyle(.none)
            bottomSeparatorView?.setStyle(.standard)
        case .shortDivider?:
            topSeparatorView?.setStyle(.none)
            bottomSeparatorView?.setStyle(.thin)
        case .tallDivider?:
            topSeparatorView?.setStyle(.none)
            bottomSeparatorView?.setStyle(.thin)
        case .sectionFooter?:
            topSeparatorView?.setStyle(.none)
            bottomSeparatorView?.setStyle(.none)
        case ListItemStyle.none?:
            topSeparatorView?.setStyle(.none)
            bottomSeparatorView?.setStyle(.none)
        default: break
        }
    }
    
    /// Default state.
    open func styleStandard() {
        MFStyler.setMarginsFor(self, size: MVMCoreUIUtility.getWidth(), defaultHorizontal: true, top: Padding.Component.VerticalMarginSpacing, bottom: Padding.Component.VerticalMarginSpacing)
        styleLine(with: .standard)
    }

    /// Adds the molecule to the view.
    open func addMolecule(_ molecule: MoleculeViewProtocol) {
        contentView.addSubview(molecule)
        containerHelper.constrainView(molecule)
        self.molecule = molecule
    }
    
    open override func layoutSubviews() {
        super.layoutSubviews()
        
        // Ensures accessory view aligns to the center y derived from the
        if let _ = heroAccessoryCenter {
            _ = alignAccessoryToHero()
        }
    }
    
    // MARK: - Inits
    public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        initialSetup()
    }
    
    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        initialSetup()
    }
    
    public func initialSetup() {
        if !initialSetupPerformed {
            initialSetupPerformed = true
            setupView()
        }
    }
    
    // MARK: - MFViewProtocol
    open func updateView(_ size: CGFloat) {
        containerHelper.updateViewMargins(self, model: listItemModel, size: size)
        
        if accessoryView != nil {
            // Smaller left margin if accessory view.
            var margin = directionalLayoutMargins
            margin.trailing = 16
            contentView.directionalLayoutMargins = margin
            
            // Update caret automatically.
            if let caretView = caretView, let widthObject = caretViewWidthSizeObject, let heightObject = caretViewHeightSizeObject {
                caretView.frame = CGRect(x: 0, y: 0, width: widthObject.getValueBased(onSize: size), height: heightObject.getValueBased(onSize: size))
            }
        } else {
            contentView.directionalLayoutMargins = directionalLayoutMargins
        }
    
        topSeparatorView?.updateView(size)
        bottomSeparatorView?.updateView(size)
        (molecule as? MVMCoreViewProtocol)?.updateView(size)
    }
    
    open func setupView() {
        selectionStyle = .none
        insetsLayoutMarginsFromSafeArea = false
        preservesSuperviewLayoutMargins = false
        contentView.insetsLayoutMarginsFromSafeArea = false
        contentView.preservesSuperviewLayoutMargins = false
        styleStandard()
    }

    //--------------------------------------------------
    // MARK: - MoleculeViewProtocol
    //--------------------------------------------------
    
    open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        guard let model = model as? ListItemModelProtocol else { return }

        self.listItemModel = model
        styleLine(with: model.style)

        // Add the caret if there is an action and it's not declared hidden.
        if !customAccessoryView {
            if let _ = model.action, !(model.hideArrow ?? false) {
                addCaretViewAccessory()
            } else {
                accessoryView = nil
            }
        }

        // override the separator
        if let separator = model.line {
            addSeparatorsIfNeeded()
            bottomSeparatorView?.set(with: separator, nil, nil)
        }
        
        if let moleculeModel = model as? MoleculeModelProtocol,
            let backgroundColor = moleculeModel.backgroundColor {
            self.backgroundColor = backgroundColor.uiColor
        }
        
        // align if needed.
        containerHelper.set(with: model, for: molecule as? MVMCoreUIViewConstrainingProtocol)
    }
    
    open func reset() {
        molecule?.reset()
        styleStandard()
        backgroundColor = .white
    }

    // MARK: Overridables
    // Base classes need to implement these functions otherwise swift won't respect the subclass functions and use the ones in the protocol extension instead.
    
    open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { model.moleculeName }

    open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil }
    
    open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? { nil }
    
    // MARK: - Caret View
    /// Adds the standard mvm style caret to the accessory view
    @objc public func addCaretViewAccessory() {
        
        guard accessoryView == nil else { return }
        
        let caret = CaretView(lineWidth: 1)
        caret.translatesAutoresizingMaskIntoConstraints = true
        caret.isAccessibilityElement = true
        caret.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint")
        caret.accessibilityTraits = .button
        caret.size = .small(.vertical)
        if let size = caret.size?.dimensions() {
            caret.frame = CGRect(origin: .zero, size: size)
            caretViewWidthSizeObject = MFSizeObject(standardSize: size.width, standardiPadPortraitSize: 9)
            caretViewHeightSizeObject = MFSizeObject(standardSize: size.height, standardiPadPortraitSize: 16)
        }
        caretView = caret
        accessoryView = caret
    }
    
    /// NOTE: Should only be called when displayed or about to be displayed.
    public func alignAccessoryToHero() -> CGPoint? {
        
        // Layout call required to force draw in memory to get dimensions of subviews.
        layoutIfNeeded()
        guard let heroLabel = findHeroLabel(views: contentView.subviews), let hero = heroLabel.hero else { return nil }
        let rect = Label.boundingRect(forCharacterRange: NSRange(location: hero, length: 1), in: heroLabel)
        let y = convert(UIView(frame: rect).center, from: heroLabel).y
        accessoryView?.center.y = y
        heroAccessoryCenter = accessoryView?.center
        return heroAccessoryCenter ?? CGPoint(x: 0, y: y)
    }
    
    /// Traverses the view hierarchy for a 🦸‍♂️ heroic Label.
    private func findHeroLabel(views: [UIView]) -> Label? {
        
        if views.isEmpty {
            return nil
        }
        
        var queue = [UIView]()
        
        // Reversed the array to first check views at the front.
        for view in views.reversed() {
            // Only one Label will have a hero in a table cell.
            if let label = view as? Label, label.hero != nil {
                return label
            }
            
            queue.append(contentsOf: view.subviews)
        }
        
        return findHeroLabel(views: queue)
    }
    
    // MARK: - MoleculeListCellProtocol
    /// For when the separator between cells shows using json and frequency. Default is type: standard, frequency: allExceptTop.
    open func setLines(with model: LineModel?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, indexPath: IndexPath) {
        addSeparatorsIfNeeded()
        if let model = model {
            topSeparatorView?.set(with: model, delegateObject, additionalData)
            bottomSeparatorView?.set(with: model, delegateObject, additionalData)
        } else {
            topSeparatorView?.setStyle(.standard)
            bottomSeparatorView?.setStyle(.standard)
        }
        setSeparatorFrequency(model?.frequency ?? .allExceptTop, indexPath: indexPath)
    }
    
    open func willSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Bool { true }
    
    open func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
        guard let action = listItemModel?.action else { return }
        Button.performButtonAction(with: action, button: self, delegateObject: delegateObject, additionalData: additionalData)
    }
    
    open func didDeselectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { }
    
    open func willDisplay() {
        _ = alignAccessoryToHero()
    }
    
    // MARK: - Separator
    open func addSeparatorsIfNeeded() {
        if topSeparatorView == nil {
            let line = Line()
            line.setStyle(.none)
            addSubview(line)
            NSLayoutConstraint.pinViewTop(toSuperview: line, useMargins: false, constant: 0).isActive = true
            NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true
            NSLayoutConstraint.pinViewRight(toSuperview: line, useMargins: true, constant: 0).isActive = true
            topSeparatorView = line
        }
        if bottomSeparatorView == nil {
            let line = Line()
            line.setStyle(.none)
            addSubview(line)
            NSLayoutConstraint.pinViewBottom(toSuperview: line, useMargins: false, constant: 0).isActive = true
            NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true
            NSLayoutConstraint.pinViewRight(toSuperview: line, useMargins: true, constant: 0).isActive = true
            bottomSeparatorView = line
        }
    }
    
    /// For when the separator between cells shows.
    public func setSeparatorFrequency(_ separatorFrequency: LineModel.Frequency, indexPath: IndexPath) {
        switch separatorFrequency {
        case .all:
            if indexPath.row == 0 {
                topSeparatorView?.isHidden = false
            } else {
                topSeparatorView?.isHidden = true
            }
            bottomSeparatorView?.isHidden = false
        case .allExceptBottom:
            topSeparatorView?.isHidden = false
            bottomSeparatorView?.isHidden = true
        case .between:
            if indexPath.row == 0 {
                topSeparatorView?.isHidden = true
            } else {
                topSeparatorView?.isHidden = false
            }
            bottomSeparatorView?.isHidden = true
        case .allExceptTop:
            fallthrough
        default:
            topSeparatorView?.isHidden = true
            bottomSeparatorView?.isHidden = false
        }
    }
}
