import {generate} from "shortid";
import {TestableCompositeData, TestableCompositeDataWithChildren, TestableData} from "./store";
import {NestedTestableNodeData} from "./InMemoryTestableNode";
import {cloneDeep} from "lodash";
import {NestedBranchData} from "./MemoryBranchNode";
import {isNonContextTestableGroupType, TestableGroupTypeId} from "./testableGroupTypes";

type TestableIdTypes = 'testable.group' | 'testable.context' | 'testable' | 'testable.partial'
export const getIdTypeForTestable = (type: TestableCompositeData['type'] | undefined | TestableData['type']): TestableIdTypes => {
    if (isNonContextTestableGroupType(type)) {
        return 'testable.group';
    } else if (type === TestableGroupTypeId.CONTEXT) {
        return 'testable.context';
    } else if (typeof type === 'undefined') {
        return 'testable.partial';
    } else {
        return 'testable';
    }
}
export const generateIdForType = (type: 'tree.node' | 'branch' | 'offers' | TestableIdTypes, id?: string): string => {
    // giving a fixed id is useful for when you already have a fixed if and you only need the correct prefix for the type
    id = id || generate()

    if (type === 'testable.group') {
        return `group-${id}`;
    } else if (type === 'testable.context') {
        return `context-${id}`
    } else if (type === 'testable') {
        return `testable-${id}`;
    } else if (type === 'testable.partial') {
        return `testable-partial-${id}`;
    } else if (type === 'tree.node') {
        return `tree-node-${id}`;
    } else if (type === 'branch') {
        return `branch-${id}`;
    } else if (type === 'offers') {
        return `offer-${id}`;
    } else {
        // a generic id
        return id as string
    }
}

type IdToTestable =
    (TestableData | Omit<TestableData, 'id'>)
    | (TestableCompositeData | Omit<TestableCompositeData, 'id'>)
    | (TestableCompositeDataWithChildren | Omit<TestableCompositeDataWithChildren, 'id'>);

export function addIdToComponent(testable: IdToTestable, override = false, isOffer: boolean = false): IdToTestable & {
    id: string
}/*TestableData | TestableCompositeData*/ {
    // @ts-ignore
    if (!testable.id || override) {
        // @ts-ignore
        testable.id = generateIdForType(isOffer ? 'offers' : getIdTypeForTestable(testable.type))
    }

    if ('children' in testable) {
        testable.children.forEach(c => addIdToComponent(c, override))
    }

    return testable as TestableData | TestableCompositeData
}

type IdWithChildren = { id?: string, children?: IdWithChildren[], [otherProps: string]: any }
export const removeIds = <Data extends Record<any, any> = Record<any, any>>(nestedData: IdWithChildren): Omit<Data, 'id'> => {
    const { id, ...dataWithoutId } = nestedData;

    if (dataWithoutId.children) {
        dataWithoutId.children = dataWithoutId.children.map(child => removeIds(child))
    }

    return dataWithoutId as Omit<Data, 'id'>
}
/**
 * This only works for TestableData, not supported for TestableCompositeData
 *
 * Testables can have ids or not, if they don't have ids, they will be generated
 *
 * New objects are created, the references in the returned object are not the same as the input object
 */
export const wrapTestablesInDefaultGroup = (testables: (TestableData | Omit<TestableData, 'id'>)[], mode: TestableCompositeData['mode'] = 'filter', type: TestableCompositeData['type'] = TestableGroupTypeId.AND): NestedTestableNodeData => {
    // we need a way to figure out the mode based on the testables. For now though, let's just ask the caller
    return {
        id: generateIdForType('testable.group'),
        type,
        mode,
        children: [
            wrapTestablesInContext(testables)
        ]
    }
}
export const wrapTestablesInContext = (testables: (TestableData | Omit<TestableData, 'id'>)[], mode: TestableCompositeData['mode'] = 'filter') => ({
    id: generateIdForType('testable.context'),
    type: TestableGroupTypeId.CONTEXT,
    mode,
    children: cloneDeep(testables.map(addIdToComponent)) as TestableData[]
})
export const throwError = (message: string) => {
    throw new Error(message);
}

export const generateDefaultNestedSelectActionsBranchData = (extraData?: object): NestedBranchData => {
    return {
        id: generateIdForType('branch'),
        type: 'select-action',
        data: extraData || {},
        children: []
    }
}
