/*
 * 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 { existsSync, readFileSync } from "node:fs"
import { D, ConfigTypeInfer, ConfigSchema } from "./configDescriber"
import { mergeJSONs } from "./configMerge"
import { capitalize } from "./util"
import { resolve } from "node:path"

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 const HookMethodSchema = D.object({
    hookName: D.string(),
    replaceImplementation: D.boolean()
})

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(),
    standaloneComponents: T.stringArray(),
    parameterized: T.stringArray(),
    ignoreMaterialized: T.stringArray(),
    ignoreGenerics: T.stringArray(),
    builderClasses: T.stringArray(),
    forceMaterialized: T.stringArray(),
    forceCallback: D.map(D.string(), T.stringArray()).onMerge('replace'),
    forceResource: T.stringArray(),
    forceContext: T.stringArray(),
    hooks: D.map(D.string(), D.map(D.string(), HookMethodSchema)).onMerge('replace'),
    moduleName: D.string(),
    modules: D.map(D.string(), ModuleConfigurationSchema).onMerge('replace'),
    libraryNameMapping: D.maybe(D.map(D.string(), D.map(D.string(), D.string())).onMerge('replace')),

    globalPackages: T.stringArray(),

    extendableComponents: T.stringArray()
})

export type CoreConfiguration = ConfigTypeInfer<typeof CoreConfigurationSchema>

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

    rootComponents: [],
    standaloneComponents: [],
    parameterized: [],
    ignoreMaterialized: [],
    ignoreGenerics: [],
    builderClasses: [],
    forceMaterialized: [],
    forceCallback: new Map<string, []>(),
    forceResource: [],
    forceContext: [],
    hooks: new Map<string, Map<string, HookMethod>>(),
    moduleName: "",
    modules: new Map<string, ModuleConfiguration>(),
    libraryNameMapping: new Map<string, Map<string, string>>(),

    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 getExtendableComponentNames(): 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.substring('Extendable'.length))
        }
    }
    return names
}

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 isExtendableComponent(componentName: string): boolean {
    return getExtendableComponentNames().has(componentName)
}

interface HookMethod {
    hookName: string,
    replaceImplementation: boolean
}

export function getHookMethod(className: string, methodName: string): HookMethod | undefined {
    const hookMethods = generatorConfiguration().hooks.get(className)
    if (!hookMethods) return undefined
    const hook = hookMethods.get(methodName)
    if (!hook) return undefined
    const method: HookMethod = {
        hookName: hook.hookName ?? `hook${className}${capitalize(methodName)}`,
        replaceImplementation: hook.replaceImplementation ?? true
    }
    return method
}

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

    const data = readFileSync(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()
}