"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSchemaResolvers = createSchemaResolvers;
const scriptSetup_1 = require("./scriptSetup");
const publicPropsInterfaces = new Set([
    'PublicProps',
    'VNodeProps',
    'AllowedComponentProps',
    'ComponentCustomProps',
]);
function createSchemaResolvers(ts, typeChecker, printer, language, getSourceScript, options, deprecatedOptions) {
    const visited = new Set();
    function shouldIgnore(subtype) {
        const name = getFullyQualifiedName(subtype);
        if (name === 'any') {
            return true;
        }
        if (visited.has(subtype)) {
            return true;
        }
        if (options === false) {
            return true;
        }
        if (typeof options === 'object') {
            for (const item of options.ignore ?? []) {
                if (typeof item === 'function') {
                    const result = item(name, subtype, typeChecker);
                    if (typeof result === 'boolean') {
                        return result;
                    }
                }
                else if (name === item) {
                    return true;
                }
            }
        }
        return false;
    }
    function reducer(acc, cur) {
        acc[cur.name] = cur;
        return acc;
    }
    function getJsDocTags(target) {
        return target.getJsDocTags(typeChecker).map(tag => ({
            name: tag.name,
            text: tag.text !== undefined ? ts.displayPartsToString(tag.text) : undefined,
        }));
    }
    function resolveNestedProperties(propSymbol) {
        const subtype = typeChecker.getTypeOfSymbol(propSymbol);
        let schema;
        let declarations;
        let global = false;
        let _default;
        let required = !(propSymbol.flags & ts.SymbolFlags.Optional);
        for (const decl of propSymbol.declarations ?? []) {
            if (isPublicProp(decl)) {
                global = true;
            }
            if (ts.isPropertyAssignment(decl) && ts.isObjectLiteralExpression(decl.initializer)) {
                for (const option of decl.initializer.properties) {
                    if (ts.isPropertyAssignment(option)) {
                        const key = option.name.getText();
                        if (key === 'default') {
                            const defaultExp = (0, scriptSetup_1.resolveDefaultOptionExpression)(ts, option.initializer);
                            _default = printer.printNode(ts.EmitHint.Expression, defaultExp, decl.getSourceFile());
                        }
                        else if (key === 'required') {
                            if (option.initializer.getText() === 'true') {
                                required = true;
                            }
                        }
                    }
                }
            }
        }
        return {
            name: propSymbol.getEscapedName().toString(),
            global,
            default: _default,
            description: ts.displayPartsToString(propSymbol.getDocumentationComment(typeChecker)),
            tags: getJsDocTags(propSymbol),
            required,
            type: getFullyQualifiedName(subtype),
            get schema() {
                return schema ??= resolveSchema(subtype);
            },
            get declarations() {
                if (deprecatedOptions.noDeclarations) {
                    return [];
                }
                return this.getDeclarations();
            },
            get rawType() {
                if (deprecatedOptions.rawType) {
                    return this.getTypeObject();
                }
            },
            getDeclarations() {
                return declarations ??= getDeclarations(propSymbol.declarations ?? []);
            },
            getTypeObject() {
                return subtype;
            },
        };
    }
    function isPublicProp(declaration) {
        let parent = declaration.parent;
        while (parent) {
            if (ts.isInterfaceDeclaration(parent) || ts.isTypeAliasDeclaration(parent)) {
                if (publicPropsInterfaces.has(parent.name.text)) {
                    return true;
                }
                return false;
            }
            parent = parent.parent;
        }
        return false;
    }
    function resolveSlotProperties(prop) {
        const propType = typeChecker.getNonNullableType(typeChecker.getTypeOfSymbol(prop));
        const signatures = propType.getCallSignatures();
        const paramType = signatures[0]?.parameters[0];
        const subtype = paramType
            ? typeChecker.getTypeOfSymbol(paramType)
            : typeChecker.getAnyType();
        let schema;
        let declarations;
        return {
            name: prop.getName(),
            type: getFullyQualifiedName(subtype),
            description: ts.displayPartsToString(prop.getDocumentationComment(typeChecker)),
            tags: getJsDocTags(prop),
            get schema() {
                return schema ??= resolveSchema(subtype);
            },
            get declarations() {
                if (deprecatedOptions.noDeclarations) {
                    return [];
                }
                return this.getDeclarations();
            },
            get rawType() {
                if (deprecatedOptions.rawType) {
                    return this.getTypeObject();
                }
            },
            getDeclarations() {
                return declarations ??= getDeclarations(prop.declarations ?? []);
            },
            getTypeObject() {
                return subtype;
            },
        };
    }
    function resolveExposedProperties(expose) {
        const subtype = typeChecker.getTypeOfSymbol(expose);
        let schema;
        let declarations;
        return {
            name: expose.getName(),
            type: getFullyQualifiedName(subtype),
            description: ts.displayPartsToString(expose.getDocumentationComment(typeChecker)),
            tags: getJsDocTags(expose),
            get schema() {
                return schema ??= resolveSchema(subtype);
            },
            get declarations() {
                if (deprecatedOptions.noDeclarations) {
                    return [];
                }
                return this.getDeclarations();
            },
            get rawType() {
                if (deprecatedOptions.rawType) {
                    return this.getTypeObject();
                }
            },
            getDeclarations() {
                return declarations ??= getDeclarations(expose.declarations ?? []);
            },
            getTypeObject() {
                return subtype;
            },
        };
    }
    function resolveEventSignature(call) {
        let schema;
        let declarations;
        let subtype;
        let symbol;
        let subtypeStr = '[]';
        let getSchema = () => [];
        if (call.parameters.length >= 2) {
            symbol = call.parameters[1];
            subtype = typeChecker.getTypeOfSymbol(symbol);
            if (call.parameters[1].valueDeclaration?.dotDotDotToken) {
                subtypeStr = getFullyQualifiedName(subtype);
                getSchema = () => typeChecker.getTypeArguments(subtype).map(resolveSchema);
            }
            else {
                subtypeStr = '[';
                for (let i = 1; i < call.parameters.length; i++) {
                    subtypeStr += getFullyQualifiedName(typeChecker.getTypeOfSymbol(call.parameters[i]))
                        + ', ';
                }
                subtypeStr = subtypeStr.slice(0, -2) + ']';
                getSchema = () => {
                    const result = [];
                    for (let i = 1; i < call.parameters.length; i++) {
                        result.push(resolveSchema(typeChecker.getTypeOfSymbol(call.parameters[i])));
                    }
                    return result;
                };
            }
        }
        return {
            name: typeChecker.getTypeOfSymbol(call.parameters[0]).value,
            description: ts.displayPartsToString(call.getDocumentationComment(typeChecker)),
            tags: getJsDocTags(call),
            type: subtypeStr,
            signature: typeChecker.signatureToString(call),
            get schema() {
                return schema ??= getSchema();
            },
            get declarations() {
                if (deprecatedOptions.noDeclarations) {
                    return [];
                }
                return this.getDeclarations();
            },
            get rawType() {
                if (deprecatedOptions.rawType) {
                    return this.getTypeObject();
                }
            },
            getDeclarations() {
                return declarations ??= call.declaration ? getDeclarations([call.declaration]) : [];
            },
            getTypeObject() {
                return subtype;
            },
        };
    }
    function resolveCallbackSchema(signature) {
        let schema;
        return {
            kind: 'event',
            type: typeChecker.signatureToString(signature),
            get schema() {
                return schema ??= signature.parameters.length
                    ? typeChecker
                        .getTypeArguments(typeChecker.getTypeOfSymbol(signature.parameters[0]))
                        .map(resolveSchema)
                    : undefined;
            },
        };
    }
    function resolveSchema(subtype) {
        const type = getFullyQualifiedName(subtype);
        if (shouldIgnore(subtype)) {
            return type;
        }
        visited.add(subtype);
        if (subtype.isUnion()) {
            let schema;
            return {
                kind: 'enum',
                type,
                get schema() {
                    return schema ??= subtype.types.map(resolveSchema);
                },
            };
        }
        else if (typeChecker.isArrayLikeType(subtype)) {
            let schema;
            return {
                kind: 'array',
                type,
                get schema() {
                    return schema ??= typeChecker.getTypeArguments(subtype).map(resolveSchema);
                },
            };
        }
        else if (subtype.getCallSignatures().length === 0
            && (subtype.isClassOrInterface() || subtype.isIntersection()
                || subtype.objectFlags & ts.ObjectFlags.Anonymous)) {
            let schema;
            return {
                kind: 'object',
                type,
                get schema() {
                    return schema ??= subtype.getProperties().map(resolveNestedProperties).reduce(reducer, {});
                },
            };
        }
        else if (subtype.getCallSignatures().length === 1) {
            return resolveCallbackSchema(subtype.getCallSignatures()[0]);
        }
        else if (subtype.flags & ts.TypeFlags.EnumLiteral && subtype.isLiteral()) {
            // enum members stringify to their qualified name (e.g. "MyEnum.Small"), which
            // hides the runtime value from consumers such as docs generators; expose the
            // value alongside, printed like any other literal
            const { value } = subtype;
            if (typeof value === 'string' || typeof value === 'number') {
                return {
                    kind: 'literal',
                    type,
                    value: JSON.stringify(value),
                };
            }
        }
        return type;
    }
    function getFullyQualifiedName(type) {
        const str = typeChecker.typeToString(type, undefined, ts.TypeFormatFlags.UseFullyQualifiedType | ts.TypeFormatFlags.NoTruncation);
        if (str.includes('import(')) {
            return str.replace(/import\(.*?\)\./g, '');
        }
        return str;
    }
    function getDeclarations(declaration) {
        return declaration.map(getDeclaration).filter(d => !!d);
    }
    function getDeclaration(declaration) {
        const fileName = declaration.getSourceFile().fileName;
        const sourceScript = getSourceScript(fileName);
        if (sourceScript?.generated) {
            const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
            if (serviceScript) {
                const map = language.maps.get(serviceScript.code, sourceScript);
                for (const [start] of map.toSourceLocation(declaration.getStart())) {
                    for (const [end] of map.toSourceLocation(declaration.getEnd())) {
                        return {
                            file: String(sourceScript.id),
                            range: [start, end],
                        };
                    }
                }
            }
            return;
        }
        return {
            file: declaration.getSourceFile().fileName,
            range: [declaration.getStart(), declaration.getEnd()],
        };
    }
    return {
        resolveNestedProperties,
        resolveSlotProperties,
        resolveEventSignature,
        resolveExposedProperties,
        resolveSchema,
    };
}
//# sourceMappingURL=schemaResolvers.js.map