import { LayerConfigJson } from "./Json/LayerConfigJson"

export interface LevelInfo {
    ids: string[]
    loop?: boolean
}

export class LayerConfigDependencyGraph {
    /**
     * Calculates the dependencies for the given layer
     * @param layerconfig
     */
    public static getLayerImports(layerconfig: LayerConfigJson): string[] {
        const defaultImports: ReadonlyArray<string> = ["questions", "filters", "icons"]
        if (defaultImports.indexOf(layerconfig.id) >= 0) {
            return []
        }
        const importedTrs: string[] = []
        for (const tr of layerconfig.tagRenderings ?? []) {
            if (typeof tr === "string") {
                importedTrs.push(tr)
            } else if (tr["builtin"] !== undefined) {
                const builtin = <string | string[]>tr["builtin"]
                if (typeof builtin === "string") {
                    importedTrs.push(builtin)
                } else {
                    importedTrs.push(...builtin)
                }
            }
        }

        const imports = new Set<string>(defaultImports)
        for (const importValue of importedTrs) {
            if (importValue.indexOf(".") < 0) {
                continue
            }
            const [layer, _] = importValue.split(".")
            imports.add(layer)
        }
        return Array.from(imports)
    }

    public static buildDirectDependencies(layers: LayerConfigJson[]) {
        const dependsOn = new Map<string, string[]>()
        for (const layer of layers) {
            const layerDependsOn = LayerConfigDependencyGraph.getLayerImports(layer)
            dependsOn.set(layer.id, layerDependsOn)
        }
        return dependsOn
    }
    public static buildLevels(dependsOn: Map<string, string[]>): LevelInfo[] {
        const levels: LevelInfo[] = []
        const seenIds = new Set<string>()
        while (Array.from(dependsOn.keys()).length > 0) {
            const currentLevel: LevelInfo = {
                ids: <string[]>[],
            }
            levels.push(currentLevel)
            for (const layerId of dependsOn.keys()) {
                const dependencies = dependsOn.get(layerId)
                if (dependencies.length === 0) {
                    currentLevel.ids.push(layerId)
                    seenIds.add(layerId)
                }
            }

            const newDependsOn = new Map<string, string[]>()
            for (const layerId of dependsOn.keys()) {
                if (seenIds.has(layerId)) {
                    continue
                }
                const dependencies = dependsOn.get(layerId)
                newDependsOn.set(
                    layerId,
                    dependencies.filter((d) => !seenIds.has(d))
                )
            }
            const oldSize = dependsOn.size
            if (oldSize === newDependsOn.size) {
                // We detected a loop.
                currentLevel.loop = true
                const allLayers = Array.from(newDependsOn.keys())
                currentLevel.ids.push(...allLayers)
                allLayers.forEach((l) => seenIds.add(l))
            }
            dependsOn = newDependsOn
        }
        return levels
    }
}
