//
//  AddRemoveMoleculeBehavior.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 5/5/21.
//  Copyright © 2021 Verizon Wireless. All rights reserved.
//

import Foundation

public enum AddMoleculesPosition {
    case above
    case below
}

/// Protocol for adding items to a list
public protocol AddMolecules {
    typealias AddParameters = ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculesPosition)

    /// Get the molecules that need to be added, the position to add them to, and the animation to use
    func moleculesToAdd() -> AddParameters?
}

/// Protocol for removing items to a list
public protocol RemoveMolecules {
    /// Get the molecules that need to be removed, and the animation to use
    func moleculesToRemove() -> [ListItemModelProtocol & MoleculeModelProtocol]?
}

public extension AddMolecules {
    /// Convenience function that gets the molecules that need to be added, recursively checking for additional molecules to add.
    func getRecursiveMoleculesToAdd() -> AddMolecules.AddParameters? {
        guard let moleculesToAdd = moleculesToAdd() else { return nil }
        var newMolecules: [ListItemModelProtocol & MoleculeModelProtocol] = []
        for molecule in moleculesToAdd.0 {
            if let anotherAddMolecule = molecule as? AddMolecules,
               let moreMolecules = anotherAddMolecule.getRecursiveMoleculesToAdd() {
                guard moreMolecules.1 == .above else {
                    newMolecules.append(molecule)
                    newMolecules.append(contentsOf: moreMolecules.0)
                    continue
                }
                newMolecules.append(contentsOf: moreMolecules.0)
            }
            newMolecules.append(molecule)
        }
        return (newMolecules, moleculesToAdd.1)
    }
}

public extension RemoveMolecules {
    /// Convenience function that gets the molecules that need to be removed, recursively checking for additional molecules to remove.
    func getRecursiveMoleculesToRemove() -> [ListItemModelProtocol & MoleculeModelProtocol]? {
        guard let moleculesToRemove = moleculesToRemove() else { return nil }
        var newMolecules: [ListItemModelProtocol & MoleculeModelProtocol] = moleculesToRemove
        for case let molecule as (ListItemModelProtocol & MoleculeModelProtocol & RemoveMolecules) in moleculesToRemove {
            guard let moreMolecules = molecule.getRecursiveMoleculesToRemove() else { continue }
            newMolecules.append(contentsOf: moreMolecules)
        }
        return newMolecules
    }
}

public class AddRemoveMoleculesBehaviorModel: PageBehaviorModelProtocol {
    public class var identifier: String { "addRemoveListItemBehavior" }
    public var shouldAllowMultipleInstances: Bool { false }
    public init() {}
}

public class AddRemoveMoleculesBehavior: PageCustomActionHandlerBehavior, PageMoleculeTransformationBehavior {
    
    var delegate: MVMCoreUIDelegateObject?
    
    public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) {
        self.delegate = delegateObject
    }
    
    public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject) {
        guard let list = delegate?.moleculeListDelegate else { return }
        for case let model as (MoleculeModelProtocol & ListItemModelProtocol & AddMolecules) in rootMolecules {
            if let moleculesToAdd = model.getRecursiveMoleculesToAdd(),
               let indexPath = list.getAdjustedIndexPath(for: model, position: moleculesToAdd.1) {
                list.addMolecules(moleculesToAdd.0, indexPath: indexPath, animation: nil)
            }
        }
    }
    
    public func handleAction(type actionType: String?, information: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) -> Bool {
        if actionType == AddMoleculesActionModel.identifier {
            guard let list = delegate?.moleculeListDelegate,
                  let model = additionalData?[KeySourceModel] as? (ListItemModelProtocol & MoleculeModelProtocol & AddMolecules),
                  let moleculesToAdd = model.getRecursiveMoleculesToAdd(),
                  let indexPath = list.getAdjustedIndexPath(for: model, position: moleculesToAdd.1),
                  let animation = information?["animation"] as? Int else { return true }
            DispatchQueue.main.async {
                list.addMolecules(moleculesToAdd.0, indexPath: indexPath, animation: UITableView.RowAnimation(rawValue: animation))
            }
            return true
        } else if actionType == RemoveMoleculesActionModel.identifier {
            guard let list = delegate?.moleculeListDelegate,
                  let model = additionalData?[KeySourceModel] as? (ListItemModelProtocol & MoleculeModelProtocol & RemoveMolecules),
                  let moleculesToRemove = model.getRecursiveMoleculesToRemove(),
                  let animation = information?["animation"] as? Int else { return true }
            DispatchQueue.main.async {
                list.removeMolecules(moleculesToRemove, animation: UITableView.RowAnimation(rawValue: animation))
            }
            return true
        }
        return false
    }
}

private extension MoleculeListProtocol {
    /// Convenience function to get the index path adjusted for position.
    func getAdjustedIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol, position: AddMoleculesPosition) -> IndexPath? {
        guard let indexPath = getIndexPath(for: molecule) else { return nil }
        if position == .below {
            return IndexPath(row: indexPath.row + 1, section: indexPath.section)
        }
        return indexPath
    }
}

public class AddMoleculesActionModel: ActionModelProtocol {
    public static var identifier: String = "addMoleculesAction"
    public var actionType: String = AddMoleculesActionModel.identifier
    public var animation: UITableView.RowAnimation = .automatic
    public var extraParameters: JSONValueDictionary?
    public var analyticsData: JSONValueDictionary?
    
    //--------------------------------------------------
    // MARK: - Initializer
    //--------------------------------------------------
    
    public init(_ animation: UITableView.RowAnimation, extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
        self.animation = animation
        self.extraParameters = extraParameters
        self.analyticsData = analyticsData
    }
    
    public required init(from decoder: Decoder) throws {
        let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
        if let animation = try typeContainer.decodeIfPresent(UITableView.RowAnimation.self, forKey: .animation) {
            self.animation = animation
        }
        extraParameters = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .extraParameters)
        analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData)
    }
}

public class RemoveMoleculesActionModel: ActionModelProtocol {
    public static var identifier: String = "removeMoleculesAction"
    public var actionType: String = RemoveMoleculesActionModel.identifier
    public var animation: UITableView.RowAnimation = .automatic
    public var extraParameters: JSONValueDictionary?
    public var analyticsData: JSONValueDictionary?
    
    //--------------------------------------------------
    // MARK: - Initializer
    //--------------------------------------------------
    
    public init(_ animation: UITableView.RowAnimation, extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
        self.animation = animation
        self.extraParameters = extraParameters
        self.analyticsData = analyticsData
    }
    
    public required init(from decoder: Decoder) throws {
        let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
        if let animation = try typeContainer.decodeIfPresent(UITableView.RowAnimation.self, forKey: .animation) {
            self.animation = animation
        }
        extraParameters = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .extraParameters)
        analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData)
    }
}
