import { getTypeName, isNullType, isTemplateDeclaration, isTemplateInstance, } from "@typespec/compiler";
import { SchemaContext } from "@autorest/codemodel";
import { getNamespace } from "./utils.js";
/** Acts as a cache for processing inputs.
 *
 * If the input is undefined, the output is always undefined.
 * for a given input, the process is only ever called once.
 *
 *
 */
export class ProcessingCache {
    constructor(transform) {
        this.transform = transform;
        this.results = new Map();
    }
    has(original) {
        return !!original && !!this.results.get(original);
    }
    set(original, result) {
        this.results.set(original, result);
        return result;
    }
    process(original, ...args) {
        if (original) {
            const result = this.results.get(original) || this.transform(original, ...args);
            this.results.set(original, result);
            return result;
        }
        return undefined;
    }
}
/** adds only if the item is not in the collection already
 *
 * @note  While this isn't very efficient, it doesn't disturb the original
 * collection, so you won't get inadvertent side effects from using Set, etc.
 */
export function pushDistinct(targetArray, ...items) {
    for (const i of items) {
        if (!targetArray.includes(i)) {
            targetArray.push(i);
        }
    }
    return targetArray;
}
export function modelContainsDerivedModel(model) {
    return !isTemplateDeclaration(model) && !(isTemplateInstance(model) && model.derivedModels.length === 0);
}
export function isModelReferredInTemplate(template, target) {
    var _a, _b, _c;
    return (template === target ||
        ((_c = (_b = (_a = template === null || template === void 0 ? void 0 : template.templateMapper) === null || _a === void 0 ? void 0 : _a.args) === null || _b === void 0 ? void 0 : _b.some((it) => it.kind === "Model" || it.kind === "Union" ? isModelReferredInTemplate(it, target) : false)) !== null && _c !== void 0 ? _c : false));
}
export function getNameForTemplate(target) {
    switch (target.kind) {
        case "Model": {
            let name = target.name;
            if (target.templateMapper && target.templateMapper.args) {
                name = name + target.templateMapper.args.map((it) => getNameForTemplate(it)).join("");
            }
            return name;
        }
        case "String":
            return target.value;
        default:
            return "";
    }
}
export function isNullableType(type) {
    if (type.kind === "Union") {
        const nullVariants = Array.from(type.variants.values()).filter((it) => isNullType(it.type));
        return nullVariants.length >= 1;
    }
    else {
        return false;
    }
}
export function isSameLiteralTypes(variants) {
    const kindSet = new Set(variants.map((it) => it.type.kind));
    if (kindSet.size === 1) {
        // Union of same literals
        const kind = kindSet.values().next().value;
        return kind === "String" || kind === "Number" || kind === "Boolean";
    }
    else {
        if (kindSet.size === 2 && kindSet.has("String") && kindSet.has("Scalar")) {
            // Union of string liberals and string scalar, treat as extensible enum
            return variants.filter((it) => it.type.kind === "Scalar").every((it) => it.type.name === "string");
        }
    }
    return false;
}
export function getDurationFormat(encode) {
    let format = "duration-rfc3339";
    // duration encoded as seconds
    if (encode.encoding === "seconds") {
        const scalarName = encode.type.name;
        if (scalarName.startsWith("int") || scalarName.startsWith("uint") || scalarName === "safeint") {
            format = "seconds-integer";
        }
        else if (scalarName.startsWith("float")) {
            format = "seconds-number";
        }
        else {
            throw new Error(`Unrecognized scalar type used by duration encoded as seconds: '${scalarName}'.`);
        }
    }
    return format;
}
export function hasScalarAsBase(type, scalarName) {
    let scalarType = type;
    while (scalarType) {
        if (scalarType.name === scalarName) {
            return true;
        }
        scalarType = scalarType.baseScalar;
    }
    return false;
}
export function unionReferredByType(program, type, cache) {
    if (cache.has(type)) {
        const ret = cache.get(type);
        if (ret) {
            return ret;
        }
        else {
            return null;
        }
    }
    cache.set(type, undefined);
    if (type.kind === "Union") {
        // ref CodeModelBuilder.processUnionSchema
        const nonNullVariants = Array.from(type.variants.values()).filter((it) => !isNullType(it.type));
        if (nonNullVariants.length === 1) {
            // Type | null, follow that Type
            const ret = unionReferredByType(program, nonNullVariants[0], cache);
            if (ret) {
                cache.set(type, ret);
                return ret;
            }
        }
        else if (isSameLiteralTypes(nonNullVariants)) {
            // "literal1" | "literal2" -> Enum
            cache.set(type, null);
            return null;
        }
        else {
            // found Union
            cache.set(type, type);
            return type;
        }
    }
    else if (type.kind === "Model") {
        if (type.indexer) {
            // follow indexer (for Array/Record)
            const ret = unionReferredByType(program, type.indexer.value, cache);
            if (ret) {
                cache.set(type, ret);
                return ret;
            }
        }
        // follow properties
        for (const property of type.properties.values()) {
            const ret = unionReferredByType(program, property.type, cache);
            if (ret) {
                cache.set(type, ret);
                return ret;
            }
        }
        cache.set(type, null);
        return null;
    }
    cache.set(type, null);
    return null;
}
export function getUnionDescription(union, typeNameOptions) {
    let name = union.name;
    if (!name) {
        const names = [];
        union.variants.forEach((it) => {
            names.push(getTypeName(it.type, typeNameOptions));
        });
        name = names.join(" | ");
    }
    return name;
}
export function getModelNameForProperty(property) {
    var _a;
    if (property.model) {
        if (property.model.name) {
            return property.model.name;
        }
        else if (property.model.namespace) {
            for (const model of property.model.namespace.models.values()) {
                for (const prop of model.properties.values()) {
                    const candidateModel = prop.type;
                    // find the property that refers to the unnamed property.model
                    if (candidateModel.kind === "Model" &&
                        (candidateModel === property.model || ((_a = candidateModel.indexer) === null || _a === void 0 ? void 0 : _a.value) === property.model)) {
                        return getModelNameForProperty(prop);
                    }
                }
            }
        }
    }
    return "";
}
export function modelIs(model, name, namespace) {
    let currentModel = model;
    while (currentModel) {
        if (currentModel.name === name && getNamespace(currentModel) === namespace) {
            return true;
        }
        currentModel = currentModel.sourceModel;
    }
    return false;
}
export function getAccess(type) {
    return getDecoratorScopedValue(type, "$access", (it) => {
        const value = it.args[0].value;
        if (value.kind === "EnumMember") {
            return value.name;
        }
        else {
            return undefined;
        }
    });
}
export function isAllValueInteger(values) {
    return values.every((it) => Number.isInteger(it));
}
export function getUsage(type) {
    return getDecoratorScopedValue(type, "$usage", (it) => {
        const value = it.args[0].value;
        const values = [];
        const ret = [];
        if (value.kind === "EnumMember") {
            values.push(value);
        }
        else if (value.kind === "Union") {
            for (const v of value.variants.values()) {
                values.push(v.type);
            }
        }
        else {
            return undefined;
        }
        for (const v of values) {
            switch (v.name) {
                case "input":
                    ret.push(SchemaContext.Input);
                    break;
                case "output":
                    ret.push(SchemaContext.Output);
                    break;
            }
        }
        if (ret.length === 0) {
            return undefined;
        }
        return ret;
    });
}
function getDecoratorScopedValue(type, decorator, mapFunc) {
    let value = type.decorators
        .filter((it) => it.decorator.name === decorator && it.args.length == 2 && it.args[1].value.value === "java")
        .map((it) => mapFunc(it))
        .find(() => true);
    if (value) {
        return value;
    }
    value = type.decorators
        .filter((it) => it.decorator.name === decorator &&
        it.args.length == 2 &&
        it.args[1].value.value === "client")
        .map((it) => mapFunc(it))
        .find(() => true);
    if (value) {
        return value;
    }
    value = type.decorators
        .filter((it) => it.decorator.name === decorator && it.args.length == 1)
        .map((it) => mapFunc(it))
        .find(() => true);
    if (value) {
        return value;
    }
    return undefined;
}
//# sourceMappingURL=type-utils.js.map