import {BranchData, BranchNode, StateBranchNode, TieredData, TieredExtraData} from "./StateBranchNode";
import {Testable} from "./Testable";
import {TestableNode} from "./TestableNode";
import {TreeNode} from "./TreeNode";
import {BeforeAddingToGroveHook, NestedTreeNodeData, TreeNodeData} from "./Grove";
import {Offers} from "./Offers";
import {OfferData, TestableData} from "./store";
import {Testables} from "./testables";
import {addIdToComponent, generateIdForType} from "./NodeHelpers";
import {InMemoryTestableNode} from "./InMemoryTestableNode";
import {pickBy} from "lodash";
import {exporterImporters} from "./export/CustomImportersAndExporters";

export type NestedBranchData<ChildrenType extends object = any, ExtraData extends object = { [p: string]: any }> = {
    id: string,
    type: BranchData['type'],
    data?: ExtraData,
    /**
     * They could be an array of offer objects or an array of tree nodes, for example
     * NOT an array of strings
     */
    children: ChildrenType[]
}

export type NestedTieredData = {
    baseTestable: TestableData | Omit<TestableData, 'id'>,
} & Pick<TieredExtraData, 'tierType'>


/**
 * Because of time constraints and fatigue, I am not going to implement the full functionality of this class
 * I'm only going to implement the methods that are needed right now
 */
export class MemoryBranchNode<ChildrenType extends object> implements BranchNode<any, ChildrenType>, BeforeAddingToGroveHook {
    // @ts-ignore
    private treeNode: TreeNode;
    // @ts-ignore
    testableNode: TestableNode<Testable>;

    constructor(
        public nestedBranchData: NestedBranchData<ChildrenType>,
        private readonly offers: Offers,
        private readonly testables: Testables
    ) {
    }

    addChildren(childrenData: ChildrenType[]): void {
        throw new Error("Method not implemented.");
    }

    beforeAddingToGrove() {
        // this is where we'll add the offers to the state
        // and deal with tiered state
        // this should probably be done in a different class for each type of branch
        // but for now, let's do it here
        this.beforeAddingToTreeNode()
    }

    /**
     * Logic should be here because beforeAddingToGrove() only works when adding first level branches to groves
     * Here it works when changing a tree node branch directly from an existing tree node object, this includes dnested tree nodes
     */
    beforeAddingToTreeNode() {
        /**
         * This method is a mess and should be refactored
         */
        if (this.isOffers()) {
            // add the Discount testables to the state if any
            const offers = this.getChildren() as OfferData[];

            offers.forEach(offer => {
                const importer = exporterImporters[offer.type];

                importer?.importData?.(offer, {testables: this.testables})
            })
            this.offers.add(offers)
        }
        // let's add the testable base if this is a tiered branch
        if (this.isTiered()) {
            const baseTestable = (this.nestedBranchData.data as NestedTieredData).baseTestable;

            if (baseTestable && typeof baseTestable !== 'string') {
                const baseTestableWithId = addIdToComponent(baseTestable) as TestableData

                this.testables.addTestables([baseTestable], undefined, false);

                ;(this.nestedBranchData.data as TieredData) = {
                    baseTestableId: baseTestableWithId.id,
                    tierType: (this.nestedBranchData.data as NestedTieredData).tierType,
                }
            }

            // now we should ass the testables from all descendants
            //n so let's take each children tree node and create memory objects for them the
        }

        // the do the swap to the real branch node
        const mapBranchChild = (nestedTreeNodeData: NestedTreeNodeData, parentBranchData?: NestedBranchData): NestedTreeNodeData | TreeNodeData => {
            nestedTreeNodeData.id = nestedTreeNodeData.id || generateIdForType('tree.node')
            // only if it's a partial
            const optionalParentBaseTestableid = parentBranchData?.type === 'tiered' ? (parentBranchData.data as TieredData).baseTestableId : undefined
            const memoryTestableNode = new InMemoryTestableNode((nestedTreeNodeData as NestedTreeNodeData).testable, this.testables, optionalParentBaseTestableid)
            const memoryBranchNode = new MemoryBranchNode((nestedTreeNodeData as NestedTreeNodeData).branch, this.offers, this.testables)

            // let's call the events here manually
            memoryTestableNode.beforeAddingToGrove()
            memoryBranchNode.beforeAddingToTreeNode()

            console.log('adding', memoryTestableNode, memoryTestableNode.getId())
            return {
                id: (nestedTreeNodeData as NestedTreeNodeData).id,
                testableId: memoryTestableNode.getId(),
                // @ts-ignore
                branch: pickBy({
                    id: memoryBranchNode.getId(),
                    type: memoryBranchNode.getType(),
                    data: memoryBranchNode.isTiered() ? pickBy({
                        baseTestableId: nestedTreeNodeData.branch.data?.baseTestableId,
                        tierType: nestedTreeNodeData.branch.data?.tierType,
                    }, v => v !== undefined) as TieredExtraData<string> : undefined,
                    children: memoryBranchNode.isTiered() ? memoryBranchNode.getChildren().map(child => mapBranchChild(child)) : memoryBranchNode.getChildrenIds()
                }, v => v !== undefined),
            }
        };

        return (new StateBranchNode(
            this.offers,
            {
                ...this.nestedBranchData,
                // @ts-ignore
                children: this.isTiered() ? this.nestedBranchData.children.map(child => mapBranchChild(child, this.nestedBranchData)) : this.getChildrenIds()
            },
            this.testables,
        ))

    }

    getId(): string {
        return this.nestedBranchData.id;
    }

    getSourceRenderNodeId(): string {
        return this.testableNode.getId();
    }

    setTreeNode(treeNode: TreeNode): void {
        this.treeNode = treeNode;
    }

    getTreeNode(): TreeNode {
        return this.treeNode;
    }

    setTestable(testable: TestableNode<Testable>): void {
        this.testableNode = testable;
    }

    // @ts-ignore
    getChildren(): ChildrenType[] {
        return this.nestedBranchData.children as ChildrenType[]
    }

    getChildrenIds(): string[] {
        return this.nestedBranchData.children.map((child: any) => child.id)
    }

    getType(): BranchData["type"] {
        return this.nestedBranchData.type;
    }

    isOffers(): boolean {
        return this.nestedBranchData.type === 'offers';
    }

    isSelectAction(): boolean {
        return this.nestedBranchData.type === 'select-action';
    }

    isTiered(): boolean {
        return this.nestedBranchData.type === 'tiered';
    }

    isSimple(): boolean {
        return this.isOffers() || this.isSelectAction();
    }

    isInsideAnotherBranch(): boolean {
        throw new Error("Method not implemented.");
    }

}