/*
 * Copyright (c) 2024 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 { IDLFile, IDLNode, IDLNamedNode, IDLImport, isReferenceType, hasExtAttribute, IDLExtendedAttributes, getFileFor } from "./idl/index.js"
import { isFile, isNamedNode, isNamespace, isEnum, isInterface, isImport } from "./idl/index.js"

export function getPov(node: IDLNode): IDLNode | undefined {
    let result: IDLNode | undefined = node
    while (result && !(isFile(result) || isNamespace(result))) {
        result = result.parent
    }
    return result
}

export function resolveNamedNode(target: string[], pov: IDLNode|undefined, corpus: IDLFile[]): IDLNamedNode | undefined {
    let result: IDLNamedNode | undefined
    let povScope: string[] = []
    while (pov) {
        if (isFile(pov)) {
            if (result = resolveDownFromFile(target, pov, corpus))
                return result
            const importUsings = pov.entries.filter(it => isImport(it) && !it.name).map(it => it as IDLImport)
            for (const importUsing of importUsings)
                if (result = resolveDownFromRoot([...importUsing.clause, ...target], corpus))
                    return result
            povScope = pov.packageClause.slice()
            break
        } else {
            if (result = resolveDownFromNode(target, pov, false, corpus))
                return result
        }
        pov = pov.parent
    }

    for(;;) {
        if (result = resolveDownFromRoot([...povScope, ...target], corpus))
            return result
        if (povScope.length)
            povScope.pop()
        else
            break;
    }

    return undefined
}

function resolveDownFromNode(target: string[], pov: IDLNode, withSelf: boolean, corpus: IDLFile[]): IDLNamedNode | undefined {
    if (withSelf && isNamedNode(pov)) {
        if (isReferenceType(pov) || !pov.name.length)
            return undefined

        let nameMatched = target[0] === pov.name
        if (!nameMatched)
            nameMatched = target[0] === "default" && hasExtAttribute(pov, IDLExtendedAttributes.DefaultExport)
        if (!nameMatched)
            return undefined

        target = target.slice(1)
        if (isImport(pov)) {
            return resolveDownFromRoot([...pov.clause, ...target], corpus)
        }
        if (!target.length)
            return pov
    }

    let candidates: IDLNamedNode[]
    if (isNamespace(pov))
        candidates = pov.members
    else if (isEnum(pov))
        candidates = pov.elements
    else if (isInterface(pov))
        candidates = pov.constants
    else
        return undefined

    let result: IDLNamedNode | undefined
    for (const candidate of candidates) {
        if (result = resolveDownFromNode(target, candidate, true, corpus))
            return result
    }

    return undefined
}

function resolveDownFromFile(target: string[], pov: IDLFile, corpus: IDLFile[]): IDLNamedNode | undefined {
    let result: IDLNamedNode | undefined
    for (const candidate of pov.entries) {
        if (result = resolveDownFromNode(target, candidate, true, corpus))
            return result
    }

    return undefined
}

function resolveDownFromRoot(target: string[], corpus: IDLFile[]): IDLNamedNode | undefined {
    let result: IDLNamedNode | undefined
    for (const file of corpus) {
        if (file.packageClause.length >= target.length)
            continue

        let match = true
        for(let index = 0; index < file.packageClause.length; ++index)
            if (file.packageClause[index] !== target[index]) {
                match = false
                break
            }
        if (!match)
            continue

        if (result = resolveDownFromFile(target.slice(file.packageClause.length), file, corpus))
            return result
    }

    return undefined
}