"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsOrTsComponentInfoProvider = void 0;
const typescript_1 = __importDefault(require("typescript"));
const utils_1 = require("../../utils");
const utils_2 = require("./features/utils");
class JsOrTsComponentInfoProvider {
    constructor(typeChecker, classType) {
        this.typeChecker = typeChecker;
        this.classType = classType;
    }
    getEvents() {
        const eventType = this.getType('$$events_def');
        if (!eventType) {
            return [];
        }
        return this.mapPropertiesOfType(eventType);
    }
    getSlotLets(slot = 'default') {
        const slotType = this.getType('$$slot_def');
        if (!slotType) {
            return [];
        }
        const slotLets = slotType.getProperties().find((prop) => prop.name === slot);
        if (!slotLets?.valueDeclaration) {
            return [];
        }
        const slotLetsType = this.typeChecker.getTypeOfSymbolAtLocation(slotLets, slotLets.valueDeclaration);
        return this.mapPropertiesOfType(slotLetsType);
    }
    getProps() {
        const props = this.getType('$$prop_def');
        if (!props) {
            return [];
        }
        return this.mapPropertiesOfType(props);
    }
    getProp(propName) {
        const props = this.getType('$$prop_def');
        if (!props) {
            return [];
        }
        const prop = props.getProperties().find((prop) => prop.name === propName);
        if (!prop?.valueDeclaration) {
            return [];
        }
        const propDef = this.typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
        if (!propDef.isUnion()) {
            return [];
        }
        const types = (0, utils_1.flatten)(propDef.types.map((type) => this.getStringLiteralTypes(type)));
        // adopted from https://github.com/microsoft/TypeScript/blob/0921eac6dc9eba0be6319dff10b85d60c90155ea/src/services/stringCompletions.ts#L61
        return types.map((v) => ({
            name: v.value,
            kindModifiers: typescript_1.default.ScriptElementKindModifier.none,
            kind: typescript_1.default.ScriptElementKind.string,
            sortText: /**LocationPriority: */ '11'
        }));
    }
    /**
     * adopted from https://github.com/microsoft/TypeScript/blob/0921eac6dc9eba0be6319dff10b85d60c90155ea/src/services/stringCompletions.ts#L310
     */
    getStringLiteralTypes(type, uniques = new Set()) {
        if (!type) {
            return [];
        }
        type = type.isTypeParameter() ? type.getConstraint() || type : type;
        if (type.isUnion()) {
            return (0, utils_1.flatten)(type.types.map((t) => this.getStringLiteralTypes(t, uniques)));
        }
        if (type.isStringLiteral() &&
            !(type.flags & typescript_1.default.TypeFlags.EnumLiteral) &&
            !uniques.has(type.value)) {
            return [type];
        }
        return [];
    }
    getType(classProperty) {
        const symbol = this.classType.getProperty(classProperty);
        if (!symbol?.valueDeclaration) {
            return null;
        }
        return this.typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration);
    }
    mapPropertiesOfType(type) {
        return type
            .getProperties()
            .map((prop) => {
            // type would still be correct when there're multiple declarations
            const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
            if (!declaration) {
                return;
            }
            return {
                name: prop.name,
                type: this.typeChecker.typeToString(this.typeChecker.getTypeOfSymbolAtLocation(prop, declaration)),
                doc: typescript_1.default.displayPartsToString(prop.getDocumentationComment(this.typeChecker))
            };
        })
            .filter(utils_1.isNotNullOrUndefined);
    }
    /**
     * The result of this shouldn't be cached as it could lead to memory leaks. The type checker
     * could become old and then multiple versions of it could exist.
     */
    static create(lang, def) {
        const program = lang.getProgram();
        const sourceFile = program?.getSourceFile(def.fileName);
        if (!program || !sourceFile) {
            return null;
        }
        const defClass = (0, utils_2.findContainingNode)(sourceFile, def.textSpan, typescript_1.default.isClassDeclaration);
        if (!defClass) {
            return null;
        }
        const typeChecker = program.getTypeChecker();
        const classType = typeChecker.getTypeAtLocation(defClass);
        if (!classType) {
            return null;
        }
        return new JsOrTsComponentInfoProvider(typeChecker, classType);
    }
}
exports.JsOrTsComponentInfoProvider = JsOrTsComponentInfoProvider;
//# sourceMappingURL=ComponentInfoProvider.js.map