"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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.analyzeStdlibSkillCoverage = analyzeStdlibSkillCoverage;
exports.persistStdlibSnapshot = persistStdlibSnapshot;
/**
 * Stdlib Skill Coverage Analyzer (Spark Plan 01)
 *
 * Produces a `StdlibSkillCoverageContract` snapshot of:
 *  - Which Perfetto stdlib modules are referenced by Skill YAML prerequisites.
 *  - Which modules are detected via raw SQL inspection (`sqlIncludeInjector`).
 *  - Drift between declared and detected — both ways.
 *  - Modules that no Skill currently references (uncovered → Skill suggestion).
 *  - New modules since the last persisted snapshot (Spark #21 watcher hook).
 *
 * The analyzer is read-only and intentionally side-effect-light: it can run
 * inside `validate:skills` summary, the CLI `coverage` command, or be invoked
 * from any maintenance flow without spinning up a trace processor.
 */
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const perfettoStdlibScanner_1 = require("./perfettoStdlibScanner");
const sqlIncludeInjector_1 = require("../agentv3/sqlIncludeInjector");
const skillLoader_1 = require("./skillEngine/skillLoader");
const sparkContracts_1 = require("../types/sparkContracts");
/**
 * Match an explicit `INCLUDE PERFETTO MODULE <name>;` statement in raw SQL.
 * Mirrors the include syntax recognized by `sqlStdlibDependencyAnalyzer` so we count
 * modules the skill author already wrote into their SQL — without those
 * being treated as drift just because the auto-injector did not need to
 * add them.
 */
const EXPLICIT_INCLUDE_REGEX = /\bINCLUDE\s+PERFETTO\s+MODULE\s+([\w.]+)/gi;
/** Walk all SQL fragments inside a skill, returning lowercase module names. */
function detectStdlibModulesUsedBySkill(skill) {
    const detected = new Set();
    const visit = (sql) => {
        if (!sql || sql.trim().length === 0)
            return;
        // Count modules the author already wrote: `INCLUDE PERFETTO MODULE foo;`.
        // Codex review caught that injectStdlibIncludes() only reports modules
        // it newly added, so a correctly-included module would otherwise look
        // like "declared but unused" drift.
        for (const match of sql.matchAll(EXPLICIT_INCLUDE_REGEX)) {
            const mod = match[1]?.toLowerCase();
            if (mod)
                detected.add(mod);
        }
        try {
            const result = (0, sqlIncludeInjector_1.injectStdlibIncludes)(sql);
            for (const mod of result.injected)
                detected.add(mod);
        }
        catch {
            // Stay quiet — analyzer must never throw on malformed SQL; the
            // `validate:skills` path already surfaces those errors separately.
        }
    };
    // Atomic skills carry a single SQL block.
    if (skill.sql)
        visit(skill.sql);
    // Composite / iterator / parallel skills carry SQL inside steps.
    const walkStep = (step) => {
        if (!step)
            return;
        const anyStep = step;
        if (typeof anyStep.sql === 'string')
            visit(anyStep.sql);
        if (Array.isArray(anyStep.steps)) {
            for (const child of anyStep.steps)
                walkStep(child);
        }
        if (Array.isArray(anyStep.branches)) {
            for (const branch of anyStep.branches)
                walkStep(branch);
        }
    };
    if (Array.isArray(skill.steps)) {
        for (const step of skill.steps)
            walkStep(step);
    }
    return detected;
}
function diffSets(a, b) {
    const out = [];
    for (const item of a)
        if (!b.has(item))
            out.push(item);
    return out.sort();
}
/** Snapshot file for Spark #21 watcher diffing. */
function getDefaultSnapshotPath() {
    return path.resolve(__dirname, '../../data/stdlibSkillCoverageSnapshot.json');
}
function readPersistedSnapshot(snapshotPath) {
    try {
        if (!fs.existsSync(snapshotPath))
            return null;
        const raw = fs.readFileSync(snapshotPath, 'utf-8');
        const parsed = JSON.parse(raw);
        if (parsed.version !== 1 || !Array.isArray(parsed.modules))
            return null;
        return {
            version: 1,
            capturedAt: typeof parsed.capturedAt === 'number' ? parsed.capturedAt : 0,
            modules: parsed.modules,
        };
    }
    catch {
        return null;
    }
}
/**
 * Analyze Skill prerequisites + raw-SQL detected stdlib usage and produce a
 * `StdlibSkillCoverageContract` snapshot.
 *
 * Returns an unsupported contract (with empty arrays) when the stdlib catalog
 * is unavailable so the caller can detect missing data without inventing
 * coverage statistics.
 */
async function analyzeStdlibSkillCoverage(options = {}) {
    const stdlibModules = (0, perfettoStdlibScanner_1.getPerfettoStdlibModules)();
    if (stdlibModules.length === 0) {
        return {
            ...(0, sparkContracts_1.makeSparkProvenance)({
                source: 'stdlib-skill-coverage',
                unsupportedReason: 'Perfetto stdlib catalog is empty',
            }),
            totalModules: 0,
            modulesCovered: 0,
            skillsWithDrift: 0,
            uncoveredModules: [],
            skillUsage: [],
            coverage: [
                { sparkId: 1, planId: '01', status: 'unsupported' },
                { sparkId: 21, planId: '01', status: 'unsupported' },
            ],
        };
    }
    await (0, skillLoader_1.ensureSkillRegistryInitialized)();
    const skills = skillLoader_1.skillRegistry.getAllSkills();
    const stdlibSet = new Set(stdlibModules);
    const declaredCount = new Map();
    const usedCount = new Map();
    const skillUsage = [];
    let skillsWithDrift = 0;
    for (const skill of skills) {
        const declared = (skill.prerequisites?.modules ?? []).slice().sort();
        const detected = Array.from(detectStdlibModulesUsedBySkill(skill)).sort();
        const declaredSet = new Set(declared);
        const detectedSet = new Set(detected);
        const declaredButUnused = diffSets(declared, detectedSet);
        const detectedButUndeclared = diffSets(detected, declaredSet);
        if (declaredButUnused.length > 0 || detectedButUndeclared.length > 0) {
            skillsWithDrift += 1;
        }
        for (const m of declared) {
            if (stdlibSet.has(m))
                declaredCount.set(m, (declaredCount.get(m) ?? 0) + 1);
        }
        for (const m of detected) {
            if (stdlibSet.has(m))
                usedCount.set(m, (usedCount.get(m) ?? 0) + 1);
        }
        skillUsage.push({
            skillId: skill.name,
            declared,
            detected,
            declaredButUnused,
            detectedButUndeclared,
        });
    }
    // Coverage = modules referenced by any skill (declared OR detected).
    const referenced = new Set();
    for (const m of declaredCount.keys())
        referenced.add(m);
    for (const m of usedCount.keys())
        referenced.add(m);
    const uncoveredModules = [];
    for (const m of stdlibModules) {
        if (referenced.has(m))
            continue;
        uncoveredModules.push({
            module: m,
            declaredBySkills: declaredCount.get(m) ?? 0,
            usedBySkills: usedCount.get(m) ?? 0,
        });
    }
    // Watcher: detect modules added since the persisted snapshot.
    let newlyAddedModules;
    if (!options.ignoreSnapshot) {
        const snapshotPath = options.snapshotPath ?? getDefaultSnapshotPath();
        const previous = readPersistedSnapshot(snapshotPath);
        if (previous) {
            const previousSet = new Set(previous.modules);
            const added = stdlibModules.filter(m => !previousSet.has(m));
            if (added.length > 0) {
                newlyAddedModules = added.map(m => ({
                    module: m,
                    declaredBySkills: declaredCount.get(m) ?? 0,
                    usedBySkills: usedCount.get(m) ?? 0,
                    newSinceLastSnapshot: true,
                }));
            }
        }
    }
    return {
        ...(0, sparkContracts_1.makeSparkProvenance)({ source: 'stdlib-skill-coverage' }),
        totalModules: stdlibModules.length,
        modulesCovered: referenced.size,
        skillsWithDrift,
        uncoveredModules,
        skillUsage,
        ...(newlyAddedModules ? { newlyAddedModules } : {}),
        coverage: [
            {
                sparkId: 1,
                planId: '01',
                status: 'implemented',
                note: 'Skill prerequisites + raw SQL detection cross-checked.',
            },
            {
                sparkId: 21,
                planId: '01',
                status: newlyAddedModules ? 'implemented' : 'scaffolded',
                note: newlyAddedModules
                    ? 'Newly added stdlib modules surfaced via persisted snapshot diff.'
                    : 'Watcher uses persisted snapshot; no diff yet because no snapshot exists.',
            },
        ],
    };
}
/**
 * Persist the current stdlib catalog as a snapshot so future runs can detect
 * newly added modules. Safe to call from CLI; never throws on filesystem
 * issues — it logs a warning and continues.
 */
function persistStdlibSnapshot(snapshotPath) {
    const target = snapshotPath ?? getDefaultSnapshotPath();
    const modules = (0, perfettoStdlibScanner_1.getPerfettoStdlibModules)();
    const snapshot = {
        version: 1,
        capturedAt: Date.now(),
        modules,
    };
    try {
        fs.mkdirSync(path.dirname(target), { recursive: true });
        fs.writeFileSync(target, JSON.stringify(snapshot, null, 2), 'utf-8');
    }
    catch (err) {
        console.warn(`[StdlibSkillCoverage] Failed to write snapshot ${target}: ${err.message}`);
    }
}
//# sourceMappingURL=stdlibSkillCoverage.js.map