"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.skillRegistry = exports.SkillRegistry = void 0;
exports.ensureSkillRegistryInitialized = ensureSkillRegistryInitialized;
exports.getSkillsDir = getSkillsDir;
/**
 * Skill Loader
 *
 * 加载 skill 文件，包括普通 skills 和 module expert skills
 *
 * Module Expert Skills:
 * - 位于 skills/modules/ 目录下
 * - 包含 module 和 dialogue 字段
 * - 可以被跨领域专家调用
 */
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const js_yaml_1 = __importDefault(require("js-yaml"));
const renderingPipelineDetectionSkillGenerator_1 = require("../renderingPipelineDetectionSkillGenerator");
const logger_1 = __importDefault(require("../../utils/logger"));
const skillValidator_1 = require("./skillValidator");
const displayContractValidator_1 = require("./displayContractValidator");
// =============================================================================
// Skill Normalization (Backward Compatibility)
// =============================================================================
function firstNonEmptyLine(text) {
    const lines = String(text || '').split(/\r?\n/).map(l => l.trim()).filter(Boolean);
    return lines[0] || '';
}
/**
 * Normalize skill YAML variants into a stable runtime shape.
 *
 * Why:
 * - Some legacy skills use `description` + `tags` at top-level instead of `meta`.
 * - Some atomic skills define SQL via `steps` (single atomic step) instead of `sql`.
 * - Some legacy steps omit `type` but include `sql` (should be treated as atomic).
 *
 * The executor assumes `meta.display_name` / `meta.description` exist.
 */
function normalizeSkillDefinition(raw, filePath) {
    if (!raw || typeof raw !== 'object')
        return null;
    const skill = raw;
    // Normalize triggers variants (legacy YAML may use array form with pattern/confidence objects)
    if (skill.triggers) {
        const t = skill.triggers;
        // Legacy: triggers: [{ pattern: '...', confidence: 0.9 }, ...] OR triggers: ['foo', '(bar|baz)']
        if (Array.isArray(t)) {
            const keywords = [];
            const patterns = [];
            const looksLikeRegex = (s) => /[\\^$.*+?()[\]{}|]/.test(s);
            for (const item of t) {
                if (typeof item === 'string') {
                    const s = item.trim();
                    if (!s)
                        continue;
                    if (looksLikeRegex(s))
                        patterns.push(s);
                    else
                        keywords.push(s);
                    continue;
                }
                if (item && typeof item === 'object') {
                    if (typeof item.pattern === 'string' && String(item.pattern).trim()) {
                        patterns.push(String(item.pattern).trim());
                    }
                    if (typeof item.keyword === 'string' && String(item.keyword).trim()) {
                        keywords.push(String(item.keyword).trim());
                    }
                }
            }
            const normalized = {};
            if (keywords.length > 0)
                normalized.keywords = keywords;
            if (patterns.length > 0)
                normalized.patterns = patterns;
            if (Object.keys(normalized).length > 0) {
                skill.triggers = normalized;
            }
        }
        else if (t && typeof t === 'object') {
            // Legacy: triggers: { pattern: '...' }
            if (typeof t.pattern === 'string' && !t.patterns) {
                const p = String(t.pattern).trim();
                delete t.pattern;
                if (p)
                    t.patterns = [p];
            }
            // Ensure patterns/keywords are arrays when provided as a single string
            if (typeof t.patterns === 'string') {
                t.patterns = [String(t.patterns)];
            }
            if (typeof t.keywords === 'string') {
                t.keywords = [String(t.keywords)];
            }
        }
    }
    // Normalize legacy root-level display to output.display (executor reads output.display)
    if (skill.display && typeof skill.display === 'object') {
        if (!skill.output || typeof skill.output !== 'object') {
            skill.output = {};
        }
        if (!skill.output.display) {
            skill.output.display = skill.display;
        }
    }
    // Fill meta if missing (best-effort)
    if (!skill.meta || typeof skill.meta !== 'object') {
        const fallbackDisplayName = (typeof skill.display_name === 'string' && skill.display_name.trim()) ||
            (typeof skill.displayName === 'string' && skill.displayName.trim()) ||
            (typeof skill.name === 'string' && skill.name.trim()) ||
            path_1.default.basename(filePath).replace(/\.skill\.ya?ml$/i, '');
        const fallbackDescription = (typeof skill.description === 'string' && firstNonEmptyLine(skill.description)) ||
            `Skill: ${fallbackDisplayName}`;
        const tags = Array.isArray(skill.tags) ? skill.tags.map(String) : undefined;
        const icon = typeof skill.icon === 'string' && skill.icon.trim() ? String(skill.icon) : undefined;
        skill.meta = {
            display_name: fallbackDisplayName,
            description: fallbackDescription,
            ...(icon ? { icon } : {}),
            ...(tags && tags.length > 0 ? { tags } : {}),
        };
    }
    else {
        // Ensure required meta fields exist
        if (!skill.meta.display_name && typeof skill.name === 'string') {
            skill.meta.display_name = skill.name;
        }
        if (!skill.meta.description) {
            const fromTop = typeof skill.description === 'string' ? firstNonEmptyLine(skill.description) : '';
            skill.meta.description = fromTop || `Skill: ${skill.meta.display_name || skill.name || 'unknown'}`;
        }
        // Backfill tags/icon from legacy fields
        if (!Array.isArray(skill.meta.tags) && Array.isArray(skill.tags)) {
            skill.meta.tags = skill.tags.map(String);
        }
        if (!skill.meta.icon && typeof skill.icon === 'string') {
            skill.meta.icon = String(skill.icon);
        }
    }
    // Normalize step variants (type inference, iterator item_skill alias)
    if (Array.isArray(skill.steps)) {
        const toNumber = (v) => {
            if (typeof v === 'number' && Number.isFinite(v))
                return v;
            if (typeof v === 'string') {
                const s = v.trim();
                if (!s)
                    return null;
                const n = Number(s);
                return Number.isFinite(n) ? n : null;
            }
            return null;
        };
        const normalizeConditionToJsExpr = (expr) => {
            let e = String(expr || '').trim();
            if (!e)
                return '';
            e = e.replace(/\bAND\b/gi, '&&').replace(/\bOR\b/gi, '||');
            // Convert SQL-style '=' into JS '==' (but preserve >=, <=, !=, ==).
            e = e.replace(/([^<>=!])=([^=])/g, '$1==$2');
            return e;
        };
        const convertInterpretationToSynthesize = (step) => {
            const interp = step?.interpretation;
            if (!interp || typeof interp !== 'object')
                return null;
            const keyMetrics = Array.isArray(interp.key_metrics)
                ? interp.key_metrics
                : Array.isArray(interp.keyMetrics)
                    ? interp.keyMetrics
                    : [];
            const analysisHints = Array.isArray(interp.analysis_hints)
                ? interp.analysis_hints
                : Array.isArray(interp.analysisHints)
                    ? interp.analysisHints
                    : [];
            const fields = [];
            const insights = [];
            for (const km of keyMetrics) {
                if (!km || typeof km !== 'object')
                    continue;
                const key = typeof km.name === 'string' ? String(km.name).trim() : '';
                if (!key)
                    continue;
                const label = typeof km.description === 'string' && String(km.description).trim()
                    ? String(km.description).trim()
                    : key;
                fields.push({ key, label });
                const th = km.thresholds;
                if (th && typeof th === 'object') {
                    const warning = toNumber(th.warning);
                    const critical = toNumber(th.critical);
                    if (typeof critical === 'number') {
                        insights.push({
                            condition: `${key} >= ${critical}`,
                            template: `${label} 偏高：{{${key}}} (≥${critical})`,
                        });
                    }
                    if (typeof warning === 'number') {
                        const cond = typeof critical === 'number'
                            ? `${key} >= ${warning} && ${key} < ${critical}`
                            : `${key} >= ${warning}`;
                        insights.push({
                            condition: cond,
                            template: `${label} 略高：{{${key}}} (≥${warning})`,
                        });
                    }
                }
            }
            for (const hint of analysisHints) {
                if (!hint || typeof hint !== 'object')
                    continue;
                const condRaw = typeof hint.condition === 'string' ? String(hint.condition) : '';
                const template = typeof hint.insight === 'string' ? String(hint.insight).trim() : '';
                if (!template)
                    continue;
                const condition = condRaw ? normalizeConditionToJsExpr(condRaw) : undefined;
                insights.push({
                    ...(condition ? { condition } : {}),
                    template,
                });
            }
            if (fields.length === 0 && insights.length === 0)
                return null;
            const layer = step?.display && typeof step.display === 'object' ? step.display.layer : undefined;
            const role = layer === 'list' ? 'list' : 'overview';
            return {
                role,
                ...(fields.length > 0 ? { fields } : {}),
                ...(insights.length > 0 ? { insights } : {}),
            };
        };
        const normalizeStep = (step) => {
            if (!step || typeof step !== 'object')
                return;
            // Legacy: step without type but with sql => atomic
            if (!step.type && typeof step.sql === 'string') {
                step.type = 'atomic';
            }
            // Backward compatibility: iterator may use `skill:` instead of `item_skill:`
            if (step.type === 'iterator' && typeof step.skill === 'string' && typeof step.item_skill !== 'string') {
                step.item_skill = step.skill;
            }
            // Legacy: many skills carry an "interpretation" block which used to be ignored.
            // Convert it into synthesize config so SkillExecutor can generate deterministic summaries.
            if (!step.synthesize && step.interpretation) {
                const synth = convertInterpretationToSynthesize(step);
                if (synth) {
                    step.synthesize = synth;
                }
            }
            // Recurse into parallel steps
            if (step.type === 'parallel' && Array.isArray(step.steps)) {
                for (const nested of step.steps) {
                    normalizeStep(nested);
                }
            }
        };
        for (const step of skill.steps) {
            normalizeStep(step);
        }
    }
    return skill;
}
// =============================================================================
// Skill Registry
// =============================================================================
class SkillRegistry {
    constructor() {
        this.skills = new Map();
        this.moduleSkills = new Map(); // Skills with module metadata
        this.fragmentCache = new Map(); // SQL fragment path → content
        /** Vendor overrides keyed by base skill ID (from `extends` field) */
        this.vendorOverrides = new Map();
        this.displayContractIssues = [];
        this.displayContractIssueKeys = new Set();
        this.initialized = false;
    }
    /**
     * 加载所有 skills
     */
    async loadSkills(skillsDir) {
        if (this.initialized)
            return;
        logger_1.default.info('SkillLoader', `Loading skills from: ${skillsDir}`);
        // Load SQL fragments first (before skills, so fragment references can be validated)
        this.loadFragments(skillsDir);
        // 加载原子 skills
        const atomicDir = path_1.default.join(skillsDir, 'atomic');
        if (fs_1.default.existsSync(atomicDir)) {
            await this.loadSkillsFromDir(atomicDir);
        }
        // 加载组合 skills
        const compositeDir = path_1.default.join(skillsDir, 'composite');
        if (fs_1.default.existsSync(compositeDir)) {
            await this.loadSkillsFromDir(compositeDir);
        }
        // 加载本地 custom skills. Enterprise v1 disables write endpoints, but
        // non-enterprise admin writes must be readable after reload.
        const customDir = path_1.default.join(skillsDir, 'custom');
        if (fs_1.default.existsSync(customDir)) {
            await this.loadSkillsFromDir(customDir);
        }
        // 加载深度分析 skills (Phase 6)
        const deepDir = path_1.default.join(skillsDir, 'deep');
        if (fs_1.default.existsSync(deepDir)) {
            await this.loadSkillsFromDir(deepDir);
        }
        // 加载系统分析 skills (Phase 6)
        const systemDir = path_1.default.join(skillsDir, 'system');
        if (fs_1.default.existsSync(systemDir)) {
            await this.loadSkillsFromDir(systemDir);
        }
        // 加载结果对比 skills。它们描述 analysis_result_snapshot 的对比能力，
        // 由 comparison services 执行，不进入单 Trace SQL executor。
        const comparisonDir = path_1.default.join(skillsDir, 'comparison');
        if (fs_1.default.existsSync(comparisonDir)) {
            await this.loadSkillsFromDir(comparisonDir);
        }
        // 加载模块专家 skills (Cross-Domain Expert System)
        const modulesDir = path_1.default.join(skillsDir, 'modules');
        if (fs_1.default.existsSync(modulesDir)) {
            await this.loadModuleSkillsRecursively(modulesDir);
        }
        // 加载 pipeline skills (Pipeline Skill Architecture)
        // Note: Pipeline skills are loaded separately by PipelineSkillLoader
        // but we register them here for skill discovery
        const pipelinesDir = path_1.default.join(skillsDir, 'pipelines');
        if (fs_1.default.existsSync(pipelinesDir)) {
            await this.loadPipelineSkills(pipelinesDir);
        }
        // 加载 vendor overrides (厂商适配覆盖)
        const vendorsDir = path_1.default.join(skillsDir, 'vendors');
        if (fs_1.default.existsSync(vendorsDir)) {
            this.loadVendorOverrides(vendorsDir);
        }
        this.initialized = true;
        this.logDisplayContractSummary();
        logger_1.default.info('SkillLoader', `Loaded ${this.skills.size} skills (${this.moduleSkills.size} module experts, ${this.vendorOverrides.size} vendor-overridden skills)`);
    }
    /**
     * Load SQL fragments from skills/fragments/ directory.
     * Fragments are reusable CTE definitions that can be injected into step SQL.
     */
    loadFragments(skillsDir) {
        const fragmentsDir = path_1.default.join(skillsDir, 'fragments');
        if (!fs_1.default.existsSync(fragmentsDir))
            return;
        const files = fs_1.default.readdirSync(fragmentsDir);
        for (const file of files) {
            if (!file.endsWith('.sql'))
                continue;
            const filePath = path_1.default.join(fragmentsDir, file);
            try {
                const content = fs_1.default.readFileSync(filePath, 'utf-8').trim();
                const key = `fragments/${file}`;
                this.fragmentCache.set(key, content);
                logger_1.default.debug('SkillLoader', `Loaded SQL fragment: ${key}`);
            }
            catch (error) {
                logger_1.default.error('SkillLoader', `Failed to load fragment ${file}: ${error.message}`);
            }
        }
        logger_1.default.info('SkillLoader', `Loaded ${this.fragmentCache.size} SQL fragments`);
    }
    /** Get a loaded SQL fragment by its path key (e.g., 'fragments/target_threads.sql') */
    getFragment(fragmentPath) {
        return this.fragmentCache.get(fragmentPath);
    }
    /** Get all loaded fragment path keys */
    getFragmentPaths() {
        return new Set(this.fragmentCache.keys());
    }
    /** Get the full fragment cache (for passing to SkillExecutor) */
    getFragmentCache() {
        return this.fragmentCache;
    }
    /**
     * Run all load-time validations on a skill and log warnings.
     */
    validateAndLogWarnings(skill, filePath) {
        const displayWarnings = this.validateAndLogDisplayWarnings(skill, filePath);
        const condWarnings = (0, skillValidator_1.validateSkillConditions)(skill);
        for (const w of condWarnings) {
            logger_1.default.warn('SkillLoader', `[${skill.name}.${w.stepId}] ${w.message}`);
        }
        const fragWarnings = (0, skillValidator_1.validateFragmentReferences)(skill, this.getFragmentPaths());
        for (const w of fragWarnings) {
            logger_1.default.warn('SkillLoader', `[${skill.name}.${w.stepId}] ${w.message}`);
        }
        return displayWarnings;
    }
    /**
     * Run only the display contract validator. Vendor overrides are fragments of a
     * base skill, so condition/fragment validation would produce false positives.
     */
    validateAndLogDisplayWarnings(skill, filePath) {
        const displayWarnings = (0, displayContractValidator_1.validateSkillDisplayContract)(skill, { filePath });
        const newlyRecorded = this.recordDisplayContractIssues(displayWarnings);
        if (process.env.SMARTPERFETTO_VERBOSE_SKILL_WARNINGS === '1') {
            for (const warn of newlyRecorded) {
                logger_1.default.warn('SkillLoader', `Validation warning in ${(0, displayContractValidator_1.formatDisplayContractIssue)(warn)}`);
            }
        }
        return newlyRecorded;
    }
    recordDisplayContractIssues(issues) {
        const newlyRecorded = [];
        for (const issue of issues) {
            const key = [
                issue.filePath || '',
                issue.skillName,
                issue.stepId || '',
                issue.path,
                String(issue.value),
            ].join('\0');
            if (this.displayContractIssueKeys.has(key))
                continue;
            this.displayContractIssueKeys.add(key);
            this.displayContractIssues.push(issue);
            newlyRecorded.push(issue);
        }
        return newlyRecorded;
    }
    logDisplayContractSummary() {
        if (this.displayContractIssues.length === 0)
            return;
        const skills = new Set(this.displayContractIssues.map(issue => issue.skillName));
        const files = new Set(this.displayContractIssues.map(issue => issue.filePath).filter(Boolean));
        logger_1.default.warn('SkillLoader', `Display contract warnings: ${this.displayContractIssues.length} issues across ${skills.size} skills` +
            (files.size > 0 ? ` (${files.size} files)` : '') +
            '. Run `cd backend && npm run validate:skills` for details. ' +
            'Set SMARTPERFETTO_VERBOSE_SKILL_WARNINGS=1 to print every load-time issue.');
    }
    /**
     * 递归加载 modules 目录下的 skills
     * modules/
     *   ├── app/
     *   ├── framework/
     *   ├── kernel/
     *   └── hardware/
     */
    async loadModuleSkillsRecursively(dir) {
        const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
        for (const entry of entries) {
            const fullPath = path_1.default.join(dir, entry.name);
            if (entry.isDirectory()) {
                await this.loadModuleSkillsRecursively(fullPath);
            }
            else if (entry.name.endsWith('.skill.yaml') || entry.name.endsWith('.skill.yml')) {
                try {
                    const content = fs_1.default.readFileSync(fullPath, 'utf-8');
                    const loaded = js_yaml_1.default.load(content);
                    const skill = normalizeSkillDefinition(loaded, fullPath);
                    if (skill && skill.name) {
                        this.validateAndLogWarnings(skill, fullPath);
                        this.skills.set(skill.name, skill);
                        // Track module skills separately for efficient lookup
                        if (skill.module) {
                            this.moduleSkills.set(skill.name, skill);
                            logger_1.default.debug('SkillLoader', `Loaded module skill: ${skill.name} (${skill.module.layer}/${skill.module.component})`);
                        }
                        else {
                            logger_1.default.debug('SkillLoader', `Loaded skill: ${skill.name} (${skill.type})`);
                        }
                    }
                }
                catch (error) {
                    logger_1.default.error('SkillLoader', `Failed to load ${fullPath}:`, error.message);
                }
            }
        }
    }
    /**
     * 加载 pipeline skills
     * Pipeline skills are a special type that define rendering pipeline configurations
     */
    async loadPipelineSkills(dir) {
        const files = fs_1.default.readdirSync(dir);
        for (const file of files) {
            // Skip non-skill files and template files
            if (!file.endsWith('.skill.yaml') && !file.endsWith('.skill.yml'))
                continue;
            if (file.startsWith('_'))
                continue;
            const filePath = path_1.default.join(dir, file);
            try {
                const content = fs_1.default.readFileSync(filePath, 'utf-8');
                const skill = js_yaml_1.default.load(content);
                if (skill && skill.name && skill.type === 'pipeline_definition') {
                    this.validateAndLogDisplayWarnings(skill, filePath);
                    // Register pipeline skills with a special prefix for discoverability
                    this.skills.set(skill.name, skill);
                    logger_1.default.debug('SkillLoader', `Loaded pipeline skill: ${skill.name}`);
                }
            }
            catch (error) {
                logger_1.default.error('SkillLoader', `Failed to load pipeline ${file}:`, error.message);
            }
        }
    }
    /**
     * 加载 vendor overrides
     * vendors/ 下每个厂商子目录包含 *.override.yaml 文件
     * 每个 override 声明 `extends: <base_skill_id>` 和 `additional_steps`
     */
    loadVendorOverrides(dir) {
        const vendorDirs = fs_1.default.readdirSync(dir, { withFileTypes: true });
        for (const entry of vendorDirs) {
            if (!entry.isDirectory())
                continue;
            const vendorName = entry.name;
            const vendorDir = path_1.default.join(dir, vendorName);
            const files = fs_1.default.readdirSync(vendorDir);
            for (const file of files) {
                if (!file.endsWith('.override.yaml') && !file.endsWith('.override.yml'))
                    continue;
                const filePath = path_1.default.join(vendorDir, file);
                try {
                    const content = fs_1.default.readFileSync(filePath, 'utf-8');
                    const raw = js_yaml_1.default.load(content);
                    if (!raw || typeof raw !== 'object' || !raw.extends) {
                        logger_1.default.warn('SkillLoader', `Vendor override ${filePath} missing 'extends' field, skipping`);
                        continue;
                    }
                    // Normalize the base skill ID: "composite/startup_analysis" → "startup_analysis"
                    const baseSkillId = String(raw.extends).includes('/')
                        ? String(raw.extends).split('/').pop()
                        : String(raw.extends);
                    const override = {
                        vendor: raw.meta?.vendor || vendorName,
                        extends: baseSkillId,
                        displayName: raw.meta?.display_name,
                        description: raw.meta?.description,
                        detection: {
                            signatures: Array.isArray(raw.vendor_detection?.signatures)
                                ? raw.vendor_detection.signatures.map((s) => ({
                                    pattern: String(s.pattern || ''),
                                    confidence: s.confidence || 'medium',
                                }))
                                : [],
                        },
                        additionalSteps: Array.isArray(raw.additional_steps) ? raw.additional_steps : [],
                        overrideParams: raw.override_params,
                    };
                    if (override.additionalSteps.length > 0 || raw.output?.display) {
                        this.validateAndLogDisplayWarnings({
                            name: `${baseSkillId}@${override.vendor}:${path_1.default.basename(file, path_1.default.extname(file))}`,
                            version: String(raw.version || '1'),
                            meta: {
                                display_name: raw.meta?.display_name || `${baseSkillId} ${override.vendor} override`,
                                description: raw.meta?.description || `Vendor override for ${baseSkillId}`,
                            },
                            output: raw.output,
                            steps: override.additionalSteps,
                        }, filePath);
                    }
                    // Store keyed by base skill ID
                    const existing = this.vendorOverrides.get(baseSkillId) || [];
                    existing.push(override);
                    this.vendorOverrides.set(baseSkillId, existing);
                    logger_1.default.debug('SkillLoader', `Loaded vendor override: ${vendorName}/${file} → extends ${baseSkillId}`);
                }
                catch (error) {
                    logger_1.default.error('SkillLoader', `Failed to load vendor override ${filePath}: ${error.message}`);
                }
            }
        }
        // Log summary
        let totalOverrides = 0;
        for (const overrides of this.vendorOverrides.values()) {
            totalOverrides += overrides.length;
        }
        logger_1.default.info('SkillLoader', `Loaded ${totalOverrides} vendor overrides across ${this.vendorOverrides.size} base skills`);
    }
    /**
     * 获取指定 skill 的特定厂商覆盖
     * @param skillId 基础 skill ID (e.g. "startup_analysis")
     * @param vendor 厂商标识 (e.g. "xiaomi", "pixel")
     * @returns 匹配的 VendorOverride，不存在则返回 undefined
     */
    getVendorOverride(skillId, vendor) {
        const overrides = this.vendorOverrides.get(skillId);
        if (!overrides)
            return undefined;
        return overrides.find(o => o.vendor.toLowerCase() === vendor.toLowerCase());
    }
    /**
     * 获取指定 skill 的所有厂商覆盖
     * @param skillId 基础 skill ID
     * @returns 所有匹配的 VendorOverride 列表
     */
    getVendorOverridesForSkill(skillId) {
        return this.vendorOverrides.get(skillId) || [];
    }
    /**
     * 获取所有已加载的 vendor override 数量
     */
    getVendorOverrideCount() {
        let count = 0;
        for (const overrides of this.vendorOverrides.values()) {
            count += overrides.length;
        }
        return count;
    }
    /**
     * 从目录加载 skills
     */
    async loadSkillsFromDir(dir) {
        const files = fs_1.default.readdirSync(dir);
        for (const file of files) {
            if (!file.endsWith('.skill.yaml') && !file.endsWith('.skill.yml')) {
                continue;
            }
            const filePath = path_1.default.join(dir, file);
            try {
                const content = fs_1.default.readFileSync(filePath, 'utf-8');
                const loaded = js_yaml_1.default.load(content);
                const skill = normalizeSkillDefinition(loaded, filePath);
                if (skill && skill.name) {
                    this.validateAndLogWarnings(skill, filePath);
                    this.skills.set(skill.name, skill);
                    logger_1.default.debug('SkillLoader', `Loaded skill: ${skill.name} (${skill.type})`);
                }
            }
            catch (error) {
                logger_1.default.error('SkillLoader', `Failed to load ${file}:`, error.message);
            }
        }
    }
    /**
     * 获取 skill
     */
    getSkill(name) {
        return this.skills.get(name);
    }
    /**
     * 获取所有 skills
     */
    getAllSkills() {
        return Array.from(this.skills.values());
    }
    /**
     * Programmatically insert/override a skill definition.
     * Used for runtime-generated skills where YAML should be the single source of truth.
     */
    upsertSkill(skill) {
        const newDisplayIssues = this.validateAndLogWarnings(skill);
        this.skills.set(skill.name, skill);
        // Keep moduleSkills map consistent
        if (skill.module) {
            this.moduleSkills.set(skill.name, skill);
        }
        else {
            this.moduleSkills.delete(skill.name);
        }
        if (this.initialized && newDisplayIssues.length > 0) {
            this.logDisplayContractSummary();
        }
    }
    /**
     * 获取所有模块专家 skills
     */
    getAllModuleSkills() {
        return Array.from(this.moduleSkills.values());
    }
    /**
     * 根据模块层级查找 skills
     */
    findSkillsByLayer(layer) {
        return Array.from(this.moduleSkills.values()).filter((skill) => skill.module?.layer === layer);
    }
    /**
     * 根据组件名查找 skill
     */
    findSkillByComponent(component) {
        return Array.from(this.moduleSkills.values()).find((skill) => skill.module?.component.toLowerCase() === component.toLowerCase());
    }
    /**
     * 根据层级和组件查找 skill
     */
    findModuleSkill(layer, component) {
        return Array.from(this.moduleSkills.values()).find((skill) => skill.module?.layer === layer &&
            skill.module?.component.toLowerCase() === component.toLowerCase());
    }
    /**
     * 根据对话能力查找 skill
     * 查找能够回答特定问题类型的模块
     */
    findSkillByCapability(capabilityId) {
        return Array.from(this.moduleSkills.values()).find((skill) => skill.dialogue?.capabilities?.some((cap) => cap.id === capabilityId));
    }
    /**
     * 获取所有可用的对话能力
     */
    getAllCapabilities() {
        const capabilities = [];
        for (const skill of this.moduleSkills.values()) {
            if (skill.dialogue?.capabilities) {
                for (const cap of skill.dialogue.capabilities) {
                    capabilities.push({ skillName: skill.name, capability: cap });
                }
            }
        }
        return capabilities;
    }
    /**
     * 检查 skill 是否为模块专家
     */
    isModuleSkill(skillName) {
        return this.moduleSkills.has(skillName);
    }
    /**
     * 根据关键词匹配 skill
     */
    findMatchingSkill(question) {
        const lowerQuestion = question.toLowerCase();
        for (const skill of this.skills.values()) {
            if (!skill.triggers)
                continue;
            // 检查关键词
            const keywords = skill.triggers.keywords;
            if (keywords) {
                let keywordList = [];
                if (Array.isArray(keywords)) {
                    keywordList = keywords;
                }
                else {
                    keywordList = [
                        ...(keywords.zh || []),
                        ...(keywords.en || []),
                    ];
                }
                for (const keyword of keywordList) {
                    if (lowerQuestion.includes(keyword.toLowerCase())) {
                        return skill;
                    }
                }
            }
            // 检查模式
            if (skill.triggers.patterns) {
                for (const pattern of skill.triggers.patterns) {
                    try {
                        const regex = new RegExp(pattern, 'i');
                        if (regex.test(question)) {
                            return skill;
                        }
                    }
                    catch {
                        // 无效的正则表达式，跳过
                    }
                }
            }
        }
        return undefined;
    }
    /**
     * 检查是否已初始化
     */
    isInitialized() {
        return this.initialized;
    }
    /**
     * 重新加载所有 skills
     */
    async reload() {
        this.skills.clear();
        this.moduleSkills.clear();
        this.fragmentCache.clear();
        this.vendorOverrides.clear();
        this.displayContractIssues = [];
        this.displayContractIssueKeys.clear();
        this.initialized = false;
        const skillsDir = path_1.default.resolve(__dirname, '../../../skills');
        await this.loadSkills(skillsDir);
    }
    getDisplayContractIssues() {
        return [...this.displayContractIssues];
    }
}
exports.SkillRegistry = SkillRegistry;
// 单例
exports.skillRegistry = new SkillRegistry();
// =============================================================================
// 辅助函数
// =============================================================================
// Promise-based lock to prevent concurrent initialization
let initializationPromise = null;
/**
 * 确保 skill registry 已初始化
 * NOTE: This function is safe to call concurrently - it will only initialize once
 */
async function ensureSkillRegistryInitialized() {
    // Fast path: already initialized
    if (exports.skillRegistry.isInitialized())
        return;
    // If initialization is in progress, wait for it
    if (initializationPromise) {
        await initializationPromise;
        return;
    }
    // Start initialization (only one caller will reach here)
    const skillsDir = path_1.default.resolve(__dirname, '../../../skills');
    initializationPromise = (async () => {
        await exports.skillRegistry.loadSkills(skillsDir);
        // Runtime-generated skills (YAML-driven single source of truth)
        try {
            const generated = await (0, renderingPipelineDetectionSkillGenerator_1.generateRenderingPipelineDetectionSkill)();
            exports.skillRegistry.upsertSkill(generated);
            logger_1.default.debug('SkillLoader', `Overrode skill with YAML-driven generator: ${generated.name} (v${generated.version})`);
        }
        catch (error) {
            logger_1.default.warn('SkillLoader', `Failed to generate YAML-driven rendering pipeline detection skill: ${error?.message || error}`);
        }
    })();
    try {
        await initializationPromise;
    }
    finally {
        // Clear the promise after completion (success or failure)
        // This allows retry on failure
        if (!exports.skillRegistry.isInitialized()) {
            initializationPromise = null;
        }
    }
}
/**
 * 获取默认的 skills 目录
 */
function getSkillsDir() {
    return path_1.default.resolve(__dirname, '../../../skills');
}
//# sourceMappingURL=skillLoader.js.map