import {ComponentRuntimeDataWithParentType, getDefaultRuntimeComponentData} from "./components";
import {PopupWindowStateContext, TreeContextData} from "./atoms";
import {convertTestableData, TestableCompositeData, TestableData, TestablePartial, useNodesStore} from "./store";
import {cloneDeep, update} from "lodash";
import {ComponentType} from "./ComponentsMeta";
import {QuantityMeta, QuantityOptions, QuantityType} from "./cards/Quantity";
import {
    generateDefaultNestedSelectActionsBranchData,
    wrapTestablesInDefaultGroup
} from "./NodeHelpers";
import {AnyProductsMeta} from "./cards/AnyProducts";
import {getLazyTestables} from "./hooks";
import {Testables} from "./testables";
import {TestableGroupTypeId} from "./testableGroupTypes";

export class TestablesSaver {
    private readonly context: PopupWindowStateContext<TreeContextData>;
    private testableType = 'filter'

    constructor(context: PopupWindowStateContext<TreeContextData>) {
        this.context = context;
    }

    add(filters: ComponentRuntimeDataWithParentType[], extraData?: {suggestedScope?: string}) {
        const {
            addTreeNode,
            addTestables,
            addTestable,
            wrapAllContexts,
            write,
            testables,
            testableRelations,
            testablePartials
        } = useNodesStore.getState()

        filters = convertTestableData(filters)
        const selectedFilters = filters.map(filter => ({
            ...filter,
            testableType: this.context.data.componentType,
        }));

        if (this.context.data.targetType === 'ghost') {
            // just add them directly to the memory, not to the tree
            addTestable(wrapTestablesInDefaultGroup(selectedFilters), id => {
            })
        } else if (this.context.data.targetType === 'root') {

            let selectedFiltersWithAnyProductsMaybeRemoved = cloneDeep(selectedFilters);
            // so here's the thing, If the user has selected AnyProducts and a Quantity or another filter, eg: they changed they mind
            // and added a Categories filter from the "Narrow down selection" menu, then
            // we should not add AnyProducts as its redundant
            // we ONLY add AnyProducts when the user has not selected a Quantity  or any other filter (meaning any products with any quantity)
            if (selectedFilters.length > 1) {
                if (selectedFilters.find(({type}) => type === 'AnyProducts')) {
                    // remove AnyProducts from the selected filters
                    selectedFiltersWithAnyProductsMaybeRemoved = selectedFilters.filter(({type}) => type !== 'AnyProducts');
                }
            }

            if (this.shouldRemoveDefaultQuantityFilter(this.context.data.componentType as ComponentType, selectedFilters)) {
                selectedFiltersWithAnyProductsMaybeRemoved = selectedFilters.filter(({type}) => type !== QuantityMeta.id)
            }

            addTreeNode({
                testable: wrapTestablesInDefaultGroup(
                    [
                        ...selectedFiltersWithAnyProductsMaybeRemoved,
                        .../*extraFiltersToAdd*/[].map(filter => ({
                            // @ts-ignore
                            ...filter,
                            testableType: this.context.data.componentType
                        }))
                    ]
                ),
                branch: generateDefaultNestedSelectActionsBranchData(),
                extraData
            })
        } else {
            const testablesManager = new Testables({testables, testableRelations, testablePartials})
            const targetId = this.context.data.targetId
            const groupType = this.context.data.groupType
            // is this a context or a testableComposite (group)?
            if (testablesManager.isContext(targetId as string)) {
                if (!groupType) {
                    addTestables([convertTestableData(filters), targetId as string])
                    return
                }

                // here we have to create a parent group if the context is not a default AND (is an AND and only has 1 context)
                const parentTestableGroup = testablesManager.getParent(targetId as string)!;
                let parentGroupToUse: TestableCompositeData

                if (testablesManager.isDefaultGroup(parentTestableGroup.id)) {
                    parentGroupToUse = parentTestableGroup as TestableCompositeData
                    addTestables([convertTestableData(filters), parentTestableGroup.id], (a, testables) => {
                        testables.changeTestableGroupOptions(parentGroupToUse.id, {
                            type: groupType,
                        })
                    })
                } else {
                    wrapAllContexts([parentTestableGroup.id], (a, testables) => {
                        // after the warp, the parent of the target context will be a new group (that has been placed inside the parent group)
                        const newParentGroup = testables.getParent(targetId as string);

                        testables.addTestables(...[convertTestableData(filters), newParentGroup!.id])
                        parentGroupToUse = newParentGroup as TestableCompositeData
                        testables.changeTestableGroupOptions(parentGroupToUse.id, {
                            type: groupType,
                        })
                    })

                }
            } else {
                if (!groupType || !testablesManager.isTestableCompositeGroup(targetId as string)) {
                    addTestables([convertTestableData(filters), targetId as string])
                    return
                }

                write(state => {
                    const writableTestables = new Testables(state)
                    const targetGroup = writableTestables.getNode(targetId as string) as TestableCompositeData | undefined

                    if (!targetGroup) {
                        throw new Error(`Expected "${targetId}" to be an existing testable group.`)
                    }

                    writableTestables.wrapChildrenInNewGroup(targetId as string)
                    writableTestables.changeTestableGroupOptions(targetId as string, {type: groupType})

                    const newGroup = writableTestables.createGroup(targetId as string, TestableGroupTypeId.AND, targetGroup.mode)
                    writableTestables.addTestables(convertTestableData(filters), newGroup.id)
                })
            }
        }
    }

    update<TestableDataType = ComponentRuntimeDataWithParentType | TestablePartial>(filters: TestableDataType[]) {
        const {removeTestable, updateTestables, updateTestablePartial, updateTestable} = useNodesStore.getState()
        const testables = getLazyTestables()

        if (filters.length === 0) {
            // remove all filters
            removeTestable(testables.getParent(this.context.data.targetId as string)!.id)
        } else {
            if (this.context.scope === 'tier-subchild') {
                updateTestablePartial([filters[0] as TestablePartial])
            } else if (this.context.scope === 'tier-base') {
                updateTestable([filters[0] as TestableData])
            } else {
                // commit the changes to the store
                updateTestables(testables => {
                    testables.updateTestables(
                        convertTestableData(
                            filters.map(filter => (testables.getWithParentType(filter, this.context.data.componentType)))
                        ),
                        testables.getParent(this.context.data.targetId as string)!.id,
                        true
                    )

                })
            }
        }

    }

    shouldRemoveDefaultQuantityFilter(testableType: ComponentType, selectedFilters: ComponentRuntimeDataWithParentType[]) {
        // if quantity is the only filter, keep it, regardless of what the user selected.
        if (selectedFilters.length === 1 && selectedFilters[0].type === QuantityMeta.id) {
            // matter of fact, add an "any products" if the quantity type is any
            if (selectedFilters[0].options.type === QuantityType.Any) {
                // add an "any products" filter
                selectedFilters.push({
                    parentType: testableType,
                    ...getDefaultRuntimeComponentData(AnyProductsMeta)
                });
                return true
            } else {
                return false;
            }
        }

        const quantityFilter = selectedFilters.find(({type}) => type === QuantityMeta.id)

        // if has a quantity filter and it's any quantity type
        if (quantityFilter && (quantityFilter.options as QuantityOptions).type === QuantityType.Any) {
            // remove the default quantity filter
            return true;
        }
    }

}
