//
//  EntryField.swift
//  MVMCoreUI
//
//  Created by Kevin Christiano on 10/21/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

import UIKit

/**
 * The EntryForm provides the base logic for the description label, placeholder/error label and field container.
 *
 * When subclassing, be sure to override setupFieldContainerContent(). In this method you will setup all the content bound to the field container.
 */
@objcMembers open class EntryField: View {
    //--------------------------------------------------
    // MARK: - Outlets
    //--------------------------------------------------
    public private(set) var titleLabel: FormLabel = {
        let label = FormLabel()
        label.setContentCompressionResistancePriority(.required, for: .vertical)
        return label
    }()
    
    public private(set) var entryFieldContainer = EntryFieldContainer()
    
    /// Provides contextual information on the TextField.
    public private(set) var feedbackLabel: FormLabel = {
        let label = FormLabel()
        label.setContentCompressionResistancePriority(.required, for: .vertical)
        return label
    }()

    public private(set) var errorLabel: Label = {
        let label = Label()
        label.setFontStyle(.RegularMicro)
        label.textColor = .mvmBlack
        label.setContentCompressionResistancePriority(.required, for: .vertical)
        return label
    }()

    public lazy var stack: UIStackView = {
        errorLabel.isHidden = true
        let stack = UIStackView(arrangedSubviews: [titleLabel, entryFieldContainer, errorLabel, feedbackLabel])
        stack.axis = .vertical
        stack.alignment = .fill
        stack.distribution = .fill
        stack.setCustomSpacing(Padding.One, after: titleLabel)
        stack.setCustomSpacing(Padding.Two, after: entryFieldContainer)
        stack.setCustomSpacing(Padding.One, after: errorLabel)
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()

    //--------------------------------------------------
    // MARK: - Delegate
    //--------------------------------------------------
    
    var delegateObject: MVMCoreUIDelegateObject?
    
    //--------------------------------------------------
    // MARK: - Stored Properties
    //--------------------------------------------------
    public var isValid: Bool = false
    
    /// Validate on each entry in the textField. Default: true
    public var validateEachCharacter: Bool = true
    
    //--------------------------------------------------
    // MARK: - Computed Properties
    //--------------------------------------------------
    
    /// Toggles enabled (original) or disabled UI.
    public var isEnabled: Bool {
        get { entryFieldContainer.isEnabled }
        set (enabled) {
            if(entryFieldContainer.isEnabled != enabled){
                titleLabel.isEnabled = enabled
                feedbackLabel.isEnabled = enabled
                entryFieldContainer.isEnabled = enabled
                entryFieldModel?.enabled = enabled
            }
        }
    }
    
    /// Toggles enabled (original) or disabled UI.
    public var isRequired: Bool = true {
        didSet{
            titleLabel.isRequired = isRequired
        }
    }
        
    /// Toggles error or original UI.
    public var showError: Bool {
        get { entryFieldContainer.showError }
        set (error) {
            if error {
                errorLabel.text = self.errorMessage ?? ""
                errorLabel.isHidden = false
            } else {
                errorLabel.isHidden = true
            }
            entryFieldContainer.showError = error
            entryFieldModel?.showError = error
        }
    }
    
    var errorMessage: String? {
        entryFieldModel?.dynamicErrorMessage ?? entryFieldModel?.errorMessage
    }
    
    /// Toggles original or locked UI.
    public var isLocked: Bool {
        get { entryFieldContainer.isLocked }
        set (locked) {
            entryFieldContainer.isLocked = locked
            entryFieldModel?.locked = locked
        }
    }
    
    /// Toggles selected or original (unselected) UI.
    public var isSelected: Bool {
        get { entryFieldContainer.isSelected }
        set (selected) {
            entryFieldContainer.isSelected = selected
            entryFieldModel?.selected = selected
        }
    }
    
    /// Sets the text of titleLabel
    public var title: String? {
        get { titleLabel.text }
        set {
            titleLabel.set(text: newValue ?? "")
            setAccessibilityString(newValue)
        }
    }
        
    /// Override this to conveniently get/set the textfield(s).
    public var text: String? {
        get { nil }
        set { fatalError("You MUST override EntryField's 'text' variable in your subclass.") }
    }
    
    /// Sets feedback text in the textField.
    public var feedback: String? {
        get { feedbackLabel.text }
        set {
            feedbackLabel.set(text: newValue ?? "")
            feedbackLabel.accessibilityElementsHidden = feedbackLabel.text?.isEmpty ?? true
        }
    }
    
    public var entryFieldModel: EntryFieldModel? {
        model as? EntryFieldModel
    }
    
    //--------------------------------------------------
    // MARK: - Initializers
    //--------------------------------------------------
    
    /// This must be overriden by a subclass.
    @objc public override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    @objc public convenience init() {
        self.init(frame: .zero)
    }
    
    @objc public init(title: String) {
        super.init(frame: .zero)
        
        titleLabel.text = title
    }
    
    @objc required public init?(coder: NSCoder) {
        super.init(coder: coder)
        fatalError("EntryField does not support xib.")
    }
    
    required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        super.init(frame: .zero)
        set(with: model, delegateObject, additionalData)
    }
    
    //--------------------------------------------------
    // MARK: - Lifecycle
    //--------------------------------------------------
    
    /// Initial configuration of class and view.
    @objc final public override func setupView() {
        super.setupView()
        
        isAccessibilityElement = false
        setContentCompressionResistancePriority(.required, for: .vertical)
        accessibilityElements = [titleLabel, errorLabel, feedbackLabel]
        backgroundColor = .mvmWhite

        addSubview(stack)

        entryFieldContainer.setContentCompressionResistancePriority(.required, for: .vertical)
        setupFieldContainerContent(entryFieldContainer)

        stack.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
        stack.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
        layoutMarginsGuide.trailingAnchor.constraint(equalTo: stack.trailingAnchor).isActive = true
        layoutMarginsGuide.bottomAnchor.constraint(equalTo: stack.bottomAnchor).isActive = true
    }
    
    @objc open override func layoutSubviews() {
        super.layoutSubviews()
        
        entryFieldContainer.refreshUI()
    }
    
    /// Intended to add the interactive content (i.e. textField) to the entryFieldContainer.
    @objc open func setupFieldContainerContent(_ container: UIView) {
        // To Be Overriden
    }
    
    @objc open override func updateView(_ size: CGFloat) {
        super.updateView(size)
        stack.updateView(size)
    }
    
    //--------------------------------------------------
    // MARK: - Validation
    //--------------------------------------------------
    
    /// Validates the text of the entry field.
    @objc public func validateText() {
        if let isValid = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) {
            self.isValid = isValid
        }
    }
    
    /// Executes on .textDidBeginEditingNotification
    @objc func startEditing() {
        isSelected = true
    }
    
    /// Executes on .textDidChangeNotification (each character entry)
    @objc func valueChanged() {
        guard validateEachCharacter else { return }
    }
    
    /// Executes on .textDidEndEditingNotification
    @objc func endInputing() {
        isSelected = false
        resignFirstResponder()
    }
    
    @objc public func updateValidation(_ isValid: Bool) {
        let previousValidity = self.isValid
        self.isValid = isValid
        
        if previousValidity && !isValid {
            shouldShowError(true)
        } else if (!previousValidity && isValid) {
            shouldShowError(false)
        }
    }
    
    func shouldShowError(_ showError: Bool) {
        self.showError = showError
    }
    
    //--------------------------------------------------
    // MARK: - MoleculeViewProtocol
    //--------------------------------------------------
    
    @objc open override func reset() {
        super.reset()
        
        backgroundColor = .clear
        isAccessibilityElement = false
        titleLabel.font = Styler.Font.RegularMicro.getFont()
        titleLabel.textColor = .mvmBlack
        feedbackLabel.font = Styler.Font.RegularMicro.getFont()
        feedbackLabel.textColor = .mvmBlack
        errorLabel.font = Styler.Font.RegularMicro.getFont()
        errorLabel.textColor = .mvmBlack
        errorLabel.text = nil
        entryFieldContainer.disableAllBorders = false
        feedbackLabel.text = nil
        entryFieldContainer.reset()
    }
    
    open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        super.set(with: model, delegateObject, additionalData)
        self.delegateObject = delegateObject
        
        guard let model = entryFieldModel else { return }
        
        entryFieldContainer.set(with: model, delegateObject, additionalData)
        
        //setup the properties for setting models
        //later with enabled/showError
        titleLabel.setup(model: model.titleStateLabel, delegateObject, additionalData)
        feedbackLabel.setup(model: model.feedbackStateLabel, delegateObject, additionalData)

        isEnabled = model.enabled && !model.readOnly
        isRequired = model.required
        model.updateUI = { [weak self] in
            MVMCoreDispatchUtility.performBlock(onMainThread: {
                guard let self = self else { return }
                
                if self.isSelected {
                    self.updateValidation(model.isValid ?? true)
                    
                } else if model.isValid ?? true && self.showError {
                    self.showError = false
                }
                self.isEnabled = model.enabled
                self.text = model.text
            })
        }
        
        model.updateUIDynamicError = { [weak self] in
            MVMCoreDispatchUtility.performBlock(onMainThread: {
                guard let self = self else { return }
                
                let validState = model.isValid ?? false
                if !validState && model.shouldClearText {
                    self.text = ""
                    model.shouldClearText = false
                }
                self.updateValidation(validState)
            })
        }
                
        entryFieldContainer.disableAllBorders = model.hideBorders
        accessibilityIdentifier = model.accessibilityIdentifier ?? model.fieldKey
                
        if let isLocked = model.locked {
            self.isLocked = isLocked
            
        } else if (model.selected ?? false) && !model.wasInitiallySelected {
            
            model.wasInitiallySelected = true
            self.isSelected = true
        }
    }
    
    open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 115 }
}

// MARK: - Accessibility
extension EntryField {
    
    @objc open func pushAccessibilityNotification() {
        // To Be Overriden
    }
    
    /**
     Adding missing accessibilityLabel value
     if we have some value in accessibilityLabel,
     then only can append regular and picker item
     */
    @objc open func setAccessibilityString(_ accessibilityString: String?) {
        // To Be Overriden
    }
}

extension LabelModel {
    public convenience init(text: String = "", fontStyle: Styler.Font, textColor: Color)  {
        self.init(text: text)
        self.fontStyle = fontStyle
        self.textColor = textColor
    }
}
