import { loadIndex } from "./loader.js";
export class SearchEngine {
    dataDir;
    classByName = new Map();
    classNames = [];
    methodIndex = new Map();
    enumIndex = new Map();
    propertyIndex = new Map();
    wikiPages = [];
    groups = [];
    loaded = false;
    constructor(dataDir) {
        this.dataDir = dataDir;
        this.load();
    }
    load() {
        const data = loadIndex(this.dataDir);
        this.wikiPages = data.wikiPages;
        this.groups = data.groups;
        const allClasses = [...data.enfusionClasses, ...data.armaClasses];
        for (const cls of allClasses) {
            const key = cls.name.toLowerCase();
            this.classByName.set(key, cls);
            this.classNames.push(cls.name);
            // Index methods (public + protected + static)
            const allMethods = [
                ...(cls.methods || []),
                ...(cls.protectedMethods || []),
                ...(cls.staticMethods || []),
            ];
            for (const method of allMethods) {
                const methodKey = method.name.toLowerCase();
                let entries = this.methodIndex.get(methodKey);
                if (!entries) {
                    entries = [];
                    this.methodIndex.set(methodKey, entries);
                }
                entries.push({
                    className: cls.name,
                    classSource: cls.source,
                    classGroup: cls.group,
                    method,
                });
            }
            // Index enums
            for (const enumInfo of cls.enums || []) {
                const enumKey = enumInfo.name.toLowerCase();
                let entries = this.enumIndex.get(enumKey);
                if (!entries) {
                    entries = [];
                    this.enumIndex.set(enumKey, entries);
                }
                entries.push({
                    className: cls.name,
                    classSource: cls.source,
                    classGroup: cls.group,
                    enumInfo,
                });
                // Also index by enum value names for searching
                for (const val of enumInfo.values) {
                    const valKey = val.name.toLowerCase();
                    let valEntries = this.enumIndex.get(valKey);
                    if (!valEntries) {
                        valEntries = [];
                        this.enumIndex.set(valKey, valEntries);
                    }
                    // Only add if not already referencing same enum
                    if (!valEntries.some((e) => e.enumInfo.name === enumInfo.name && e.className === cls.name)) {
                        valEntries.push({
                            className: cls.name,
                            classSource: cls.source,
                            classGroup: cls.group,
                            enumInfo,
                        });
                    }
                }
            }
            // Index properties (public + protected)
            for (const prop of [...(cls.properties || []), ...(cls.protectedProperties || [])]) {
                const propKey = prop.name.toLowerCase();
                let entries = this.propertyIndex.get(propKey);
                if (!entries) {
                    entries = [];
                    this.propertyIndex.set(propKey, entries);
                }
                entries.push({
                    className: cls.name,
                    classSource: cls.source,
                    classGroup: cls.group,
                    property: prop,
                });
            }
        }
        this.loaded = true;
    }
    getClass(name) {
        return this.classByName.get(name.toLowerCase());
    }
    searchClasses(query, source = "all", limit = 10) {
        const q = query.toLowerCase();
        const results = [];
        for (const cls of this.classByName.values()) {
            if (source !== "all" && cls.source !== source)
                continue;
            const nameLower = cls.name.toLowerCase();
            let score = 0;
            // Exact match
            if (nameLower === q) {
                score = 100;
            }
            // Prefix match
            else if (nameLower.startsWith(q)) {
                score = 80;
            }
            // Substring in name
            else if (nameLower.includes(q)) {
                score = 60;
            }
            // Match in brief description
            else if (cls.brief.toLowerCase().includes(q)) {
                score = 30;
            }
            // Match in full description
            else if (cls.description.toLowerCase().includes(q)) {
                score = 20;
            }
            if (score > 0) {
                results.push({ cls, score });
            }
        }
        results.sort((a, b) => b.score - a.score);
        return results.slice(0, limit).map((r) => r.cls);
    }
    searchMethods(query, source = "all", limit = 10) {
        const q = query.toLowerCase();
        const results = [];
        for (const [methodName, entries] of this.methodIndex) {
            let score = 0;
            if (methodName === q) {
                score = 100;
            }
            else if (methodName.startsWith(q)) {
                score = 80;
            }
            else if (methodName.includes(q)) {
                score = 60;
            }
            if (score > 0) {
                for (const entry of entries) {
                    if (source !== "all" && entry.classSource !== source)
                        continue;
                    results.push({ result: entry, score });
                }
            }
        }
        results.sort((a, b) => b.score - a.score);
        return results.slice(0, limit).map((r) => r.result);
    }
    searchEnums(query, source = "all", limit = 10) {
        const q = query.toLowerCase();
        const results = [];
        const seen = new Set();
        for (const [enumKey, entries] of this.enumIndex) {
            let score = 0;
            if (enumKey === q) {
                score = 100;
            }
            else if (enumKey.startsWith(q)) {
                score = 80;
            }
            else if (enumKey.includes(q)) {
                score = 60;
            }
            if (score > 0) {
                for (const entry of entries) {
                    if (source !== "all" && entry.classSource !== source)
                        continue;
                    // Deduplicate by className+enumName
                    const dedup = `${entry.className}::${entry.enumInfo.name}`;
                    if (seen.has(dedup))
                        continue;
                    seen.add(dedup);
                    results.push({ result: entry, score });
                }
            }
        }
        results.sort((a, b) => b.score - a.score);
        return results.slice(0, limit).map((r) => r.result);
    }
    searchProperties(query, source = "all", limit = 10) {
        const q = query.toLowerCase();
        const results = [];
        for (const [propName, entries] of this.propertyIndex) {
            let score = 0;
            if (propName === q) {
                score = 100;
            }
            else if (propName.startsWith(q)) {
                score = 80;
            }
            else if (propName.includes(q)) {
                score = 60;
            }
            if (score > 0) {
                for (const entry of entries) {
                    if (source !== "all" && entry.classSource !== source)
                        continue;
                    results.push({ result: entry, score });
                }
            }
        }
        results.sort((a, b) => b.score - a.score);
        return results.slice(0, limit).map((r) => r.result);
    }
    searchAny(query, source = "all", limit = 10) {
        const classResults = this.searchClasses(query, source, limit).map((cls) => ({
            type: "class",
            score: cls.name.toLowerCase() === query.toLowerCase() ? 100 : 50,
            classInfo: cls,
        }));
        const methodResults = this.searchMethods(query, source, limit).map((mr) => ({
            type: "method",
            score: mr.method.name.toLowerCase() === query.toLowerCase() ? 90 : 40,
            methodResult: mr,
        }));
        const enumResults = this.searchEnums(query, source, limit).map((er) => ({
            type: "enum",
            score: er.enumInfo.name.toLowerCase() === query.toLowerCase() ? 95 : 45,
            enumResult: er,
        }));
        const propertyResults = this.searchProperties(query, source, limit).map((pr) => ({
            type: "property",
            score: pr.property.name.toLowerCase() === query.toLowerCase() ? 85 : 35,
            propertyResult: pr,
        }));
        const combined = [...classResults, ...methodResults, ...enumResults, ...propertyResults];
        combined.sort((a, b) => b.score - a.score);
        return combined.slice(0, limit);
    }
    searchWiki(query, limit = 5) {
        const tokens = query.toLowerCase().split(/\s+/);
        const results = [];
        for (const page of this.wikiPages) {
            const titleLower = page.title.toLowerCase();
            const contentLower = page.content.toLowerCase();
            let score = 0;
            for (const token of tokens) {
                if (titleLower.includes(token))
                    score += 10;
                if (contentLower.includes(token))
                    score += 1;
            }
            if (score > 0) {
                results.push({ page, score });
            }
        }
        results.sort((a, b) => b.score - a.score);
        return results.slice(0, limit).map((r) => r.page);
    }
    getGroups() {
        return this.groups;
    }
    getGroup(name) {
        return this.groups.find((g) => g.name.toLowerCase() === name.toLowerCase());
    }
    /** Get all class names (for resource listing) */
    getAllClassNames() {
        return this.classNames;
    }
    /**
     * Get the full inheritance tree for a class.
     * Walks up through parents[] and down through children[].
     */
    getClassTree(name) {
        const ancestors = [];
        const descendants = [];
        // Walk up to ancestors
        const visited = new Set();
        const walkUp = (className) => {
            const cls = this.getClass(className);
            if (!cls)
                return;
            for (const parent of cls.parents) {
                if (visited.has(parent.toLowerCase()))
                    continue;
                visited.add(parent.toLowerCase());
                ancestors.push(parent);
                walkUp(parent);
            }
        };
        walkUp(name);
        // Walk down to descendants
        visited.clear();
        const walkDown = (className) => {
            const cls = this.getClass(className);
            if (!cls)
                return;
            for (const child of cls.children) {
                if (visited.has(child.toLowerCase()))
                    continue;
                visited.add(child.toLowerCase());
                descendants.push(child);
                walkDown(child);
            }
        };
        walkDown(name);
        return { ancestors, descendants };
    }
    /**
     * Get the ordered inheritance chain from root to the given class.
     * Returns [root, ..., parent, className].
     */
    getInheritanceChain(name) {
        const chain = [name];
        const visited = new Set([name.toLowerCase()]);
        let current = name;
        while (true) {
            const cls = this.getClass(current);
            if (!cls || cls.parents.length === 0)
                break;
            const parent = cls.parents[0];
            if (visited.has(parent.toLowerCase()))
                break; // cycle protection
            visited.add(parent.toLowerCase());
            chain.unshift(parent);
            current = parent;
        }
        return chain;
    }
    /**
     * Get all inherited members by walking the inheritance chain.
     * Returns methods, properties, and enums from all ancestor classes.
     */
    getInheritedMembers(name) {
        const methods = [];
        const properties = [];
        const enums = [];
        const chain = this.getInheritanceChain(name);
        // Skip the class itself — only include ancestors
        for (const ancestorName of chain.slice(0, -1)) {
            const cls = this.getClass(ancestorName);
            if (!cls)
                continue;
            for (const method of [...(cls.methods || []), ...(cls.protectedMethods || []), ...(cls.staticMethods || [])]) {
                methods.push({
                    className: cls.name,
                    classSource: cls.source,
                    classGroup: cls.group,
                    method,
                });
            }
            for (const prop of [...(cls.properties || []), ...(cls.protectedProperties || [])]) {
                properties.push({
                    className: cls.name,
                    classSource: cls.source,
                    classGroup: cls.group,
                    property: prop,
                });
            }
            for (const enumInfo of cls.enums || []) {
                enums.push({
                    className: cls.name,
                    classSource: cls.source,
                    classGroup: cls.group,
                    enumInfo,
                });
            }
        }
        return { methods, properties, enums };
    }
    /**
     * Get all classes that inherit from ScriptComponent (directly or indirectly).
     * Useful for finding available components to attach to entities.
     */
    getComponents() {
        const tree = this.getClassTree("ScriptComponent");
        const results = [];
        for (const name of tree.descendants) {
            const cls = this.getClass(name);
            if (cls)
                results.push(cls);
        }
        return results;
    }
    /**
     * Check if a class name exists in the index (case-insensitive).
     */
    hasClass(name) {
        return this.classByName.has(name.toLowerCase());
    }
    isLoaded() {
        return this.loaded;
    }
    getStats() {
        return {
            totalClasses: this.classByName.size,
            totalMethods: this.methodIndex.size,
            totalEnums: this.enumIndex.size,
            totalProperties: this.propertyIndex.size,
            totalWikiPages: this.wikiPages.length,
        };
    }
}
//# sourceMappingURL=search-engine.js.map