import {Grove, NestedTreeNodeData, NestedTreeNodeDataWithoutIds, TreeNodeData} from "./Grove";
import {cloneDeep} from "lodash";
import {addIdToComponent, generateIdForType, getIdTypeForTestable} from "./NodeHelpers";
import {TreeNode} from "./TreeNode";
import {InMemoryTestableNode} from "./InMemoryTestableNode";
import {Testables} from "./testables";
import {Offers} from "./Offers";
import {MemoryBranchNode} from "./MemoryBranchNode";
import {TestableNode, TestableNodeWithBranchAndFlow} from "./TestableNode";
import {BranchNode, StateBranchNode} from "./StateBranchNode";
import {TestableCompositeNode} from "./TestableCompositeNode";

/**
 *
 *
 *  IDs ARE OPTIONAL, they will be generated if not provided
 *
 * {
 *     id: 'tree-node-1',
 *     testable: {
 *             id: 'group-1',
 *             type: 'AND',
 *             mode: 'filter',
 *             children: [
 *                 {
 *                     id: 'context-1',
 *                     type: 'context',
 *                     mode: 'filter',
 *                     children: [
 *                         {
 *                             id: 'testable-1',
 *                             type: 'InCategories',
 *                             options: {
 *                                 expectedValues: ['123'],
 *                                 inclusionType: 'forbidden'
 *                             },
 *                             testableType: 'filter'
 *                         }
 *                     ]
 *                 }
 *             ]
 *     },
 *     branch: {
 *             id: 'branch-1-offers',
 *             type: 'offers',
 *             children: [
 *                 {
 *                     id: 'offer-1',
 *                     type: 'Discount',
 *                     options: {
 *                         discountValue: 10,
 *                     }
 *                 }
 *             ]
 *       }
 * }
 *
 * The result will be a TreeNode with in memory testable and branch objects
 */
export class TreeNodeFactory {
    // @ts-ignore
    private grove: Grove

    constructor(
        private readonly testables: Testables,
        private readonly offers: Offers,
    ) {
    }

    setGrove(grove: Grove): void {
        this.grove = grove;
    }

    createFromNestedData(nestedTreeNodeData: NestedTreeNodeData | NestedTreeNodeDataWithoutIds): TreeNode | undefined {
        const nestedTreeNodeDataWithIds = TreeNodeFactory.ensureIds(nestedTreeNodeData);

        return TreeNode.createTemporaryFromComponentsWithoutAncestors(
            new InMemoryTestableNode(nestedTreeNodeDataWithIds.testable, this.testables, nestedTreeNodeDataWithIds.parentBranchTestableId),
            new MemoryBranchNode(nestedTreeNodeDataWithIds.branch, this.offers, this.testables),
            this.grove,
            nestedTreeNodeDataWithIds.id,
            nestedTreeNodeData.extraData
        );
    }

    // same as above but passing a json string
    // todo: test ts
    createFromNestedDataJson(json: string): TreeNode[] | undefined {
        const nestedNodesData = JSON.parse(json) as NestedTreeNodeData[];

        return nestedNodesData.map(this.createFromNestedData.bind(this)).filter(v => v !== undefined)
    }

    /**
     * this should probably be in a different class
     */
    createComponentsFromStateTreeNodeData(treeNodeData: TreeNodeData): {
        testableNode: TestableNodeWithBranchAndFlow,
        branch: BranchNode
    } {
        /**
         * Right now we're juust hard-coding this but this will have to change when we introduce tier branches and testable partials
         */
        const testableNode = new TestableCompositeNode(this.testables, treeNodeData.testableId);
        const branch = new StateBranchNode(this.offers, treeNodeData.branch, this.testables);

        return {
            testableNode,
            branch
        }
    }

    public static ensureIds(nestedTreeNodeData: NestedTreeNodeData | NestedTreeNodeDataWithoutIds): NestedTreeNodeData {
        const newNestedTreeNodeData = cloneDeep(nestedTreeNodeData);

        // @ts-ignore
        newNestedTreeNodeData.id = newNestedTreeNodeData.id || generateIdForType('tree.node');
        newNestedTreeNodeData.testable.id = newNestedTreeNodeData.testable.id || generateIdForType(getIdTypeForTestable(newNestedTreeNodeData.testable.type));

        function addToTestableChild(child) {
            addIdToComponent(child)

            if (child.children) {
                child.children.forEach(addToTestableChild)
            }
        }

        // now the children
        newNestedTreeNodeData.testable?.children?.forEach?.(addToTestableChild)
        // now the branch
        newNestedTreeNodeData.branch.id = newNestedTreeNodeData.branch.id || generateIdForType('branch');
        // now the children
        newNestedTreeNodeData.branch.children = newNestedTreeNodeData.branch.children.map((child) => {
            // now here's the thing, the child could be an offer or a tier branch
            if (nestedTreeNodeData.branch.type === 'tiered') {
                // tier is just a nested tree node so this will be recursive
                return TreeNodeFactory.ensureIds(child);
            }

            return {
                ...child,
                id: child.id || generateIdForType('offers'),
            }
        })


        return newNestedTreeNodeData;
    }
}