import { FrontendParameters } from "./model";

export class DFCParamsBranch {
    name: string = 'root';
    children: DFCParamsBranch[] = [];

    private static frontendParameters = undefined as unknown as FrontendParameters;
    private static getProductHierarchy() {
        return DFCParamsBranch.frontendParameters.hierarchy_data.product_hierarchy;
    }

    constructor(name?: string, parent?: DFCParamsBranch | null, parentDepth = -3, parentGranularity = "") {
        if (!DFCParamsBranch.frontendParameters) {
            console.error("frontendparams are undefined");
        }

        if (name) this.name = name;
        let children: string[] | undefined = undefined;
        const depth = parentDepth + 1;
        let granularity = "";
        if (parent) {  // this.depth is -2
            const isGranularityLayer = depth === -1;
            granularity = isGranularityLayer ? this.name : parentGranularity;

            if (depth < DFCParamsBranch.getProductHierarchy().length) { // this.depth is in range (0, Branch.productHierarchy.length)
                const childLayer = this.getLayer(depth + 1);
                if (childLayer) {
                    const granularityAllowedChildren = this.getGranularityHierarchyConstraints(childLayer, granularity);
                    if (isGranularityLayer) {
                        children = granularityAllowedChildren;
                    } else {
                        children = this.getKeyConstraints(this.name, depth, granularity).filter(
                            child => granularityAllowedChildren.includes(child)
                        );
                    }
                    this.addChildren(children, depth, granularity);
                }
            }
        } else {
            children = DFCParamsBranch.frontendParameters.all_granularities;
            this.addChildren(children, depth, granularity);
        }
    }


    private getLayer(depth: number) {
        if (!DFCParamsBranch.frontendParameters || !DFCParamsBranch.frontendParameters.hierarchy_data.product_hierarchy) {
            // Handle the case where the product hierarchy is not defined
            return undefined;
        }
        if (
            (depth > -1) &&
            (depth < DFCParamsBranch.getProductHierarchy().length)
        ) return DFCParamsBranch.getProductHierarchy()[depth];
    }

    private getKeyConstraints(key: string, depth: number, granularity: string) {
        if (!granularity) return [];
        const currentLayer = this.getLayer(depth);
        if (!currentLayer) return [];

        return DFCParamsBranch.frontendParameters.hierarchy_data.hierarchy_dependencies[currentLayer].child_constraints[key];
    }

    private getGranularityHierarchyConstraints(layer: string, granularity: string) {
        if (!granularity) return [];
        return DFCParamsBranch.frontendParameters.granularity_x_hierarchy_constraints[granularity][layer];
    }

    private addChild(name: string, depth: number, granularity: string) {
        const child = new DFCParamsBranch(name, this, depth, granularity);
        let currentParent: DFCParamsBranch | undefined = this;
        while (currentParent) {
            if (currentParent.name === name) {
                // Found a circular dependency, handle appropriately, possibly throw an error
                throw new Error('Circular dependency detected');
            }
            currentParent = currentParent.parent;
        }

        this.children.push(child);
        return child;
    }
    static setFrontendParams(frontendParameters: FrontendParameters) {
        if (!frontendParameters) {
            throw new Error('frontendParameters must not be null or undefined');
        }
        DFCParamsBranch.frontendParameters = frontendParameters;
    }
    private addChildren(names: string[], depth: number, granularity: string) {
        names.forEach(name => this.addChild(name, depth, granularity));
        return this.children;
    }

    private _getAdjacentBranches(subset: string[][], depth: number, adjecentBranches: Set<string>[]) {
        const currentLayerDepth = depth + 2;
        const childLayerSubset = subset[currentLayerDepth];
        // Ensure a base case is hit
        if (depth >= subset.length - 1) {
            return adjecentBranches;
        }
        if (currentLayerDepth >= subset.length) {
            // We've gone beyond the depth of the provided subset
            return adjecentBranches;
        }
        const matchingChildren = this.children.filter(child => childLayerSubset.includes(child.name));
        matchingChildren.forEach(matchedChild => {
            matchedChild._getAdjacentBranches(subset, depth + 1, adjecentBranches);
        });
        this.children.forEach(child => {
            (adjecentBranches as Set<string>[])[currentLayerDepth].add(child.name);
        });
        return adjecentBranches;
    }

    getAdjacentBranches(subset: string[][]) {
        if (!Array.isArray(subset)) {
            throw new Error('Input subset must be an array of strings.');
        }

        const productHierarchyAbsoluteLength = DFCParamsBranch.frontendParameters.hierarchy_data.product_hierarchy.length + 1;
        if (subset.length === productHierarchyAbsoluteLength) {
            const adjecentBranches = new Array(subset.length).fill(undefined).map(_ => new Set<string>());
            const updatedBranches = this._getAdjacentBranches(subset, -2, adjecentBranches);
            return updatedBranches.map(layerValuesSet => Array.from(layerValuesSet));
        } else {
            throw new Error(`provided subset lenght doesnt match the productHierarchy length,\n subset.length: ${subset.length
                };\n product hierarchy length (with granularity): ${productHierarchyAbsoluteLength
                };`);
        }
    }
}