"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateParsedInterface = generateParsedInterface;
const typescript_1 = __importDefault(require("typescript"));
const ts_morph_1 = require("ts-morph");
// Allowed raw types.
const allowedRawTypes = new Set(["string", "number", "integer", "boolean", "array", "object"]);
/**
 * Returns a cleaned-up description for a node.
 * It first checks for JSDoc docs via the node’s structure.
 * If none are found, it falls back to reading leading comments (// or ///) using TypeScript's API.
 */
function getNodeDescription(node) {
    let description = "";
    const structure = node.getStructure();
    if (structure.docs && structure.docs.length > 0) {
        description = structure.docs.map(doc => doc.description || "").join("\n").trim();
    }
    if (!description) {
        // @ts-ignore
        const sourceFile = node.getSourceFile();
        const fullText = sourceFile.getFullText();
        // @ts-ignore
        const pos = node.getFullStart();
        const commentRanges = typescript_1.default.getLeadingCommentRanges(fullText, pos);
        if (commentRanges) {
            description = commentRanges
                .map(range => {
                let commentText = fullText.substring(range.pos, range.end);
                commentText = commentText
                    .replace(/^\/\/\/?/, "") // remove /// or //
                    .replace(/^\/\*+/, "") // remove starting /*
                    .replace(/\*+\/$/, "") // remove ending */
                    .trim();
                return commentText;
            })
                .join("\n")
                .trim();
        }
    }
    return description;
}
function extractValidFromRaw(propName, node, rawType) {
    // @ts-ignore
    const sourceFile = node.getSourceFile();
    const fullText = sourceFile.getFullText();
    // @ts-ignore
    const pos = node.getFullStart();
    const commentRanges = typescript_1.default.getLeadingCommentRanges(fullText, pos);
    let rawComments = "";
    if (commentRanges) {
        rawComments = commentRanges.map(range => fullText.substring(range.pos, range.end)).join("\n");
    }
    const validRegex = /@valid\s*([^\n]+)/g;
    const matches = [...rawComments.matchAll(validRegex)];
    if (matches.length > 0) {
        const validations = [];
        for (const match of matches) {
            if (match[1]) {
                let validStr = null;
                try {
                    validStr = match[1].trim();
                    let hasCombinators = false;
                    const isArray = validStr.endsWith("[]");
                    let innerValidStr = validStr;
                    let parsed = {};
                    if (isArray)
                        innerValidStr = validStr.slice(0, -2).trim();
                    const finishIt = (skipArray = false) => {
                        if (isArray) {
                            if (skipArray === false) {
                                parsed = {
                                    type: "Array",
                                    associatedValids: [parsed]
                                };
                            }
                        }
                        if (parsed.type == null) {
                            if (rawType === "string")
                                rawType = "Text";
                            if (parsed.values)
                                rawType = "Enum";
                            parsed.type = rawType;
                        }
                    };
                    if (innerValidStr.startsWith("JS:")) {
                        const jsStr = innerValidStr.substring(3);
                        parsed = {
                            type: "JS",
                            func: new Function(`return (async (value) => { ${jsStr} })()`)
                        };
                    }
                    else if (innerValidStr.startsWith("(")) {
                        const extractTypes = (input) => {
                            const regex = /^\s*\(\s*([^)]+?)\s*\)\s*$/;
                            const match = input.match(regex);
                            if (!match)
                                return [];
                            const inner = match[1];
                            return inner.split("|").map(type => type.trim()).filter(type => type.length > 0);
                        };
                        const extractedTypes = extractTypes(innerValidStr);
                        if (isArray === false) {
                            for (let iIdx = 0; iIdx < extractedTypes.length; iIdx++) {
                                const extractedType = extractedTypes[iIdx];
                                parsed = { type: extractedType };
                                finishIt();
                                validations.push(parsed);
                            }
                        }
                        else {
                            hasCombinators = true;
                            parsed = {
                                type: "Array",
                                associatedValids: []
                            };
                            for (let iIdx = 0; iIdx < extractedTypes.length; iIdx++) {
                                const extractedType = extractedTypes[iIdx];
                                const innerParsed = { type: extractedType };
                                parsed.associatedValids.push(innerParsed);
                            }
                            finishIt(true);
                            validations.push(parsed);
                        }
                        continue;
                    }
                    else if (!innerValidStr.startsWith("{")) {
                        const jsonStartStr = ",...{";
                        const jsonStart = innerValidStr.indexOf(jsonStartStr);
                        let rawType = innerValidStr;
                        let mergedJSON = null;
                        if (jsonStart > -1) {
                            const jsonPart = innerValidStr.substring(jsonStart + jsonStartStr.length - 1).trim();
                            mergedJSON = JSON.parse(jsonPart);
                            rawType = innerValidStr.substring(0, jsonStart).trim();
                        }
                        parsed = { type: rawType };
                        if (mergedJSON != null)
                            parsed = Object.assign(Object.assign({}, parsed), mergedJSON);
                    }
                    else {
                        parsed = JSON.parse(innerValidStr);
                    }
                    finishIt();
                    validations.push(parsed);
                }
                catch (e) {
                    throw new Error(`In property "${propName}", error parsing @valid ${validStr}: ${e}`);
                }
            }
        }
        return validations;
    }
    return undefined;
}
/**
 * Determines the raw type for a property.
 * It returns one of: string, number, integer, boolean, array, or object.
 * For inline type literals or named interfaces, it returns "object".
 * For arrays, it returns "array". If the type is Date, it returns "string".
 * Otherwise, if the type text (lowercased) matches one of the allowed types, that type is returned;
 * if not, it defaults to "string".
 */
function getRawType(prop) {
    const type = prop.getType();
    let typeSymbol = type.getAliasSymbol() || type.getSymbol();
    // Special-case: if the type is Date, return "string".
    if (typeSymbol && typeSymbol.getName() === "Date") {
        return "string";
    }
    // Inline type literal.
    if (prop.getTypeNode() && prop.getTypeNode().getKindName() === "TypeLiteral") {
        return "object";
    }
    // Array types.
    if (type.isArray()) {
        return "array";
    }
    // Named interface => "object".
    if (typeSymbol) {
        const decls = typeSymbol.getDeclarations();
        if (decls && decls.some(d => d.getKindName() === "InterfaceDeclaration")) {
            return "object";
        }
    }
    // Otherwise, check if the type text matches an allowed raw type.
    let text = type.getText().toLowerCase().trim();
    if (allowedRawTypes.has(text)) {
        return text;
    }
    return "string";
}
function getFirstTypeNameFromNode(node) {
    var _a, _b, _c, _d;
    let current = node;
    // Unwrap any parenthesis
    while (current.getKind() === ts_morph_1.SyntaxKind.ParenthesizedType) {
        current = current.getTypeNode();
    }
    // If it's an array type like (string | Foo)[] -> unwrap to string | Foo
    if (current.getKind() === ts_morph_1.SyntaxKind.ArrayType) {
        const elementType = (_b = (_a = current).getElementTypeNode) === null || _b === void 0 ? void 0 : _b.call(_a);
        if (elementType) {
            return getFirstTypeNameFromNode(elementType);
        }
    }
    // If it's a union or intersection, get the first constituent type
    if (current.getKind() === ts_morph_1.SyntaxKind.UnionType ||
        current.getKind() === ts_morph_1.SyntaxKind.IntersectionType) {
        const subTypes = (_d = (_c = current).getTypeNodes) === null || _d === void 0 ? void 0 : _d.call(_c);
        if ((subTypes === null || subTypes === void 0 ? void 0 : subTypes.length) > 0) {
            return getFirstTypeNameFromNode(subTypes[0]);
        }
    }
    // Base case: it's a simple named type, return its text
    return current.getText().trim();
}
/**
 * Recursively extracts metadata from an inline type literal node.
 * Returns an ParsedInterface object whose "parameters" property holds the extracted members.
 */
function extractTypeLiteralMetadata(typeLiteralNode) {
    let parameters = {};
    typeLiteralNode.getMembers().forEach(member => {
        if (member.getKindName() === "PropertySignature") {
            const prop = member;
            const propName = prop.getName();
            const fullType = prop.getType();
            const typeNode = prop.getTypeNode();
            const originalType = fullType.getText();
            const rawType = getRawType(prop);
            const required = !prop.hasQuestionToken();
            const isReadOnly = prop.isReadonly();
            const isArray = fullType.isArray();
            // @ts-ignore
            const propDescription = getNodeDescription(prop);
            // @ts-ignore
            const valids = extractValidFromRaw(propName, prop, rawType);
            // ✅ Use getFirstTypeNameFromNode to get the derivedType
            let derivedType = "unknown";
            if (typeNode) {
                derivedType = getFirstTypeNameFromNode(typeNode);
            }
            // Handle nested inline objects
            let subParameters = {};
            if ((typeNode === null || typeNode === void 0 ? void 0 : typeNode.getKindName()) === "TypeLiteral") {
                // @ts-ignore
                subParameters = extractTypeLiteralMetadata(typeNode).parameters;
            }
            parameters[propName] = {
                type: rawType,
                typeName: derivedType,
                description: propDescription,
                valids: valids,
                required: required,
                isReadOnly: isReadOnly,
                isArray: isArray,
                parameters: subParameters
            };
        }
    });
    return {
        type: "object",
        typeName: "", // Inline type literals don't have a name.
        description: "",
        required: true,
        isReadOnly: false,
        isArray: false,
        parameters: parameters
    };
}
/**
 * Recursively extracts metadata from an interface declaration.
 * For each property, if its type is an inline type literal, it uses extractTypeLiteralMetadata.
 * Otherwise, it computes the raw type and preserves the original type (or array element type) in typeName.
 */
function extractParsedInterface(iface) {
    var _a;
    // @ts-ignore
    const structure = iface.getStructure();
    const description = ((_a = structure.docs) === null || _a === void 0 ? void 0 : _a.map(doc => doc.description || "").join("\n").trim()) || "";
    let parameters = {};
    iface.getProperties().forEach(prop => {
        var _a;
        const propName = prop.getName();
        const fullType = prop.getType();
        const rawType = getRawType(prop);
        const isArray = fullType.isArray();
        let derivedType = "unknown";
        const typeNode = prop.getTypeNode();
        if (typeNode) {
            derivedType = getFirstTypeNameFromNode(typeNode);
        }
        const originalType = fullType.getText();
        // @ts-ignore
        const propDescription = getNodeDescription(prop);
        // @ts-ignore
        const valids = extractValidFromRaw(propName, prop, rawType);
        const required = !prop.hasQuestionToken();
        const isReadOnly = prop.isReadonly();
        let subParameters = {};
        if (((_a = prop.getTypeNode()) === null || _a === void 0 ? void 0 : _a.getKindName()) === "TypeLiteral") {
            // @ts-ignore
            subParameters = extractTypeLiteralMetadata(prop.getTypeNode()).parameters;
        }
        parameters[propName] = {
            type: rawType,
            typeName: derivedType,
            description: propDescription,
            valids: valids,
            required: required,
            isReadOnly: isReadOnly,
            isArray: isArray,
            parameters: subParameters
        };
    });
    const baseTypes = iface.getBaseTypes();
    baseTypes.forEach(baseType => {
        const baseSymbol = baseType.getSymbol();
        const baseDeclarations = baseSymbol === null || baseSymbol === void 0 ? void 0 : baseSymbol.getDeclarations();
        const parentDeclaration = baseDeclarations === null || baseDeclarations === void 0 ? void 0 : baseDeclarations.find(decl => decl.getKindName() === "InterfaceDeclaration");
        if (parentDeclaration) {
            const parentIface = parentDeclaration;
            const parentMetadata = extractParsedInterface(parentIface);
            parameters = Object.assign(Object.assign({}, parentMetadata.parameters), parameters);
        }
    });
    return {
        type: "object",
        typeName: iface.getName(),
        description: description,
        required: true,
        isReadOnly: false,
        isArray: false,
        parameters: parameters
    };
}
/**
 * Searches the project for an interface by name and generates metadata for it.
 */
function generateParsedInterface(interfaceName, tsConfigFilePath) {
    return __awaiter(this, void 0, void 0, function* () {
        const project = new ts_morph_1.Project({ tsConfigFilePath });
        const iface = project
            .getSourceFiles()
            .flatMap(sf => sf.getInterfaces())
            .find(iface => iface.getName() === interfaceName);
        if (!iface) {
            throw new Error(`Interface ${interfaceName} not found in the project.`);
        }
        return extractParsedInterface(iface);
    });
}
//# sourceMappingURL=HotConvertInterfaceToRouteParameters.js.map