/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as fs from "node:fs"
import * as path from "node:path"

import { ConfigSchema, D, ConfigTypeInfer } from "./configDescriber.js"
import { mergeJSONs } from "./configMerge.js";
import { IDLEntry } from "./idl/node.js";

const T = {
    stringArray: () => D.array(D.string())
}

export const ModuleConfigurationSchema = D.object({
    name: D.string(),
    external: D.maybe(D.boolean()),
    packages: T.stringArray(),
    useFoldersLayout: D.maybe(D.boolean()),
    tsLikePackage: D.maybe(D.string()),
})

export type ModuleConfiguration = ConfigTypeInfer<typeof ModuleConfigurationSchema>

export const CoreConfigurationSchema = D.object({
    ApiKind: D.number(),
    TypePrefix: D.string(),
    LibraryPrefix: D.string(),
    OptionalPrefix: D.string(),

    rootComponents: T.stringArray(),
    parameterized: T.stringArray(),
    ignoreMaterialized: T.stringArray(),
    builderClasses: T.stringArray(),
    forceMaterialized: T.stringArray(),
    forceCallback: D.map(D.string(), T.stringArray()).onMerge('replace'),
    forceResource: T.stringArray(),
    moduleName: D.string(),
    modules: D.map(D.string(), ModuleConfigurationSchema).onMerge('replace'),

    globalPackages: T.stringArray(),

    extendableComponents: T.stringArray()
})

export type CoreConfiguration = ConfigTypeInfer<typeof CoreConfigurationSchema>

export const defaultCoreConfiguration: CoreConfiguration = {
    ApiKind: 0,
    TypePrefix: "",
    LibraryPrefix: "",
    OptionalPrefix: "",

    rootComponents: [],
    parameterized: [],
    ignoreMaterialized: [],
    builderClasses: [],
    forceMaterialized: [],
    forceCallback: new Map<string, []>(),
    forceResource: [],
    moduleName: "",
    modules: new Map<string, ModuleConfiguration>(),

    globalPackages: [],

    extendableComponents: []
}

let currentConfig: CoreConfiguration = defaultCoreConfiguration

export function setDefaultConfiguration<T extends CoreConfiguration>(config: T): void {
    currentConfig = config
}

export function patchDefaultConfiguration<T extends CoreConfiguration>(config: Partial<T>): void {
    currentConfig = Object.assign({}, currentConfig, config)
}

export function generatorConfiguration<T extends CoreConfiguration>(): T {
    return currentConfig as T
}

export function generatorTypePrefix() {
    const conf = generatorConfiguration()
    return `${conf.TypePrefix}${conf.LibraryPrefix}`
}

export function getExtendableClassNames(): Set<string> {
    const config = generatorConfiguration()
    const names = new Set<string>()
    for (const fqn of config.extendableComponents) {
        const lastDot = fqn.lastIndexOf('.')
        const declName = lastDot >= 0 ? fqn.substring(lastDot + 1) : fqn
        if (declName.startsWith('Extendable')) {
            names.add(declName)
        }
    }
    return names
}

export function isExtendableComponentEntry(entry: IDLEntry): boolean {
    return getExtendableClassNames().has(entry.name)
}

function parseConfigFile(configurationFile: string): any {
    if (!fs.existsSync(configurationFile)) throw new Error(`Configuration file ${configurationFile} does not exist!`)

    const data = fs.readFileSync(path.resolve(configurationFile)).toString()
    return JSON.parse(data)
}

export function parseConfigFiles<T>(schema: ConfigSchema<T>, configurationFiles: string[]): T {
    const json = mergeJSONs(
        configurationFiles.map(parseConfigFile),
        schema
    )
    const result = schema.validate(json)
    if (!result.success()) {
        throw new Error("Configuration is not valid!\n" + result.error() + '\n')
    }
    return result.unwrap()
}