import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { randomUUID } from 'crypto';
import { z } from 'zod';
import { SkillCardRegistry } from './skill-card.js';
import { GraphMemoryEngine } from './graph-memory.js';
import { resolveNexusStateDir } from './runtime-registry.js';
import { BUILTIN_SKILL_PACKS, dedupeArtifactsByPreference, deriveCatalogMetadata, detectDomains, readMarkdownArtifacts, slugify, } from './runtime-assets.js';
import { createEmbedder } from './embedder.js';
import { getSharedNgramIndex } from './ngram-index.js';
import { computeSemanticScore } from './semantic-ranking.js';
async function computeSemanticRelevance(goal, goalVector, artifact, embedder) {
    const artifactText = `${artifact.name} ${artifact.domain || ''} ${artifact.instructions || ''}`;
    if (!artifact.embedding) {
        artifact.embedding = await embedder.embed(artifactText);
    }
    return computeSemanticScore({
        query: goal,
        queryVector: goalVector,
        candidateText: artifactText,
        candidateVector: artifact.embedding,
        lexicalTexts: [artifact.name, artifact.domain ?? '', artifact.instructions ?? ''],
        embedder,
    }).final;
}
const sharedSkillCardRegistries = new Map();
function getSharedSkillCardRegistry() {
    const stateRoot = resolveNexusStateDir();
    const existing = sharedSkillCardRegistries.get(stateRoot);
    if (existing) {
        return existing;
    }
    const registry = new SkillCardRegistry(new GraphMemoryEngine());
    sharedSkillCardRegistries.set(stateRoot, registry);
    return registry;
}
export class SkillRuntime {
    rootDir;
    workspaceRoot;
    artifacts = new Map();
    bootstrapped = false;
    embedder;
    ngramIndex = null;
    registry;
    constructor(registry, rootDir, workspaceRoot) {
        try {
            this.registry = registry ?? getSharedSkillCardRegistry();
        }
        catch {
            this.registry = registry;
        }
        this.rootDir = rootDir ?? path.join(os.tmpdir(), 'nexus-prime-runtime-skills');
        this.workspaceRoot = workspaceRoot ?? process.cwd();
        fs.mkdirSync(this.rootDir, { recursive: true });
        try {
            this.ngramIndex = getSharedNgramIndex();
        }
        catch {
            this.ngramIndex = null;
        }
        this.ensureBootstrapped();
    }
    getEmbedder() {
        if (!this.embedder) {
            try {
                this.embedder = createEmbedder();
            }
            catch (err) {
                console.warn('Embedder initialization failed:', err.message);
                throw err;
            }
        }
        return this.embedder;
    }
    getArtifact(skillId) {
        this.ensureBootstrapped();
        return this.artifacts.get(skillId);
    }
    findByName(name) {
        this.ensureBootstrapped();
        const normalized = name.toLowerCase();
        return this.listArtifacts().find((artifact) => artifact.name.toLowerCase() === normalized || artifact.skillId.toLowerCase() === normalized);
    }
    importPeerSkill(sourcePeerId, payload) {
        this.ensureBootstrapped();
        const PeerSkillSchema = z.object({
            name: z.string().min(1).max(100),
            domain: z.string().optional(),
            instructions: z.string().min(10).max(10000),
            riskClass: z.enum(['read', 'mutate', 'orchestrate']).default('read')
        });
        const parsed = PeerSkillSchema.parse(payload);
        const artifact = {
            skillId: `skill_peer_${randomUUID().slice(0, 8)}`,
            version: 1,
            name: parsed.name,
            domain: parsed.domain,
            instructions: parsed.instructions,
            toolBindings: [], // Strictly text-bounded
            riskClass: parsed.riskClass,
            scope: 'session',
            provenance: `peer:${sourcePeerId}`,
            validationStatus: 'pending',
            rolloutStatus: 'staged',
            ...deriveCatalogMetadata(`peer:${sourcePeerId}`),
            effectiveness: {
                successes: 0, failures: 0, tokenDelta: 0, retriesAvoided: 0, verificationPasses: 0,
            },
            deploymentPoints: [],
        };
        this.stage(artifact);
        return artifact;
    }
    listArtifacts(options = {}) {
        this.ensureBootstrapped();
        const policy = options.selectionPolicy ?? 'registry-first';
        const dedupe = options.dedupe !== false;
        const artifacts = dedupe
            ? dedupeArtifactsByPreference([...this.artifacts.values()], policy)
            : [...this.artifacts.values()];
        return artifacts.sort((a, b) => {
            if ((a.scope === 'global' || a.scope === 'base') && b.scope !== 'global' && b.scope !== 'base')
                return -1;
            if ((b.scope === 'global' || b.scope === 'base') && a.scope !== 'global' && a.scope !== 'base')
                return 1;
            return a.name.localeCompare(b.name);
        });
    }
    createSkill(input) {
        const artifact = {
            skillId: `skill_${randomUUID().slice(0, 8)}`,
            version: 1,
            name: input.name,
            domain: input.domain,
            instructions: input.instructions,
            toolBindings: input.toolBindings,
            riskClass: input.riskClass,
            scope: input.scope,
            provenance: input.provenance,
            validationStatus: 'pending',
            rolloutStatus: 'staged',
            ...deriveCatalogMetadata(input.provenance),
            effectiveness: {
                successes: 0,
                failures: 0,
                tokenDelta: 0,
                retriesAvoided: 0,
                verificationPasses: 0,
            },
            deploymentPoints: [],
        };
        this.stage(artifact);
        return artifact;
    }
    async resolveSkillSelectors(names, goal) {
        this.ensureBootstrapped();
        const selectors = new Set(names.map((name) => name.toLowerCase()));
        const domains = detectDomains(goal, names);
        const RELEVANCE_THRESHOLD = 0.4;
        const availableArtifacts = this.listArtifacts({ dedupe: false });
        const exactMatches = availableArtifacts.filter((artifact) => selectors.has(artifact.name.toLowerCase()) ||
            selectors.has(artifact.skillId.toLowerCase()) ||
            (artifact.domain ? domains.includes(artifact.domain) : false));
        const allCandidates = availableArtifacts.filter((artifact) => !exactMatches.includes(artifact));
        // N-gram pre-filter: narrow candidates before expensive semantic scoring
        let candidates = allCandidates;
        try {
            const ngramHits = this.ngramIndex?.search(goal, 10);
            if (ngramHits && ngramHits.length > 0) {
                const hitIds = new Set(ngramHits.map((h) => h.docId));
                const filtered = allCandidates.filter((a) => hitIds.has(a.skillId));
                if (filtered.length > 0) {
                    candidates = filtered;
                }
            }
        }
        catch {
            // n-gram search failed; fall back to full scan
        }
        const embedder = this.getEmbedder();
        const goalVector = await embedder.embed(goal);
        const scoredCandidates = await Promise.all(candidates.map(async (artifact) => ({
            artifact,
            relevance: await computeSemanticRelevance(goal, goalVector, artifact, embedder),
        })));
        const fuzzyCandidates = scoredCandidates
            .filter((candidate) => candidate.relevance >= RELEVANCE_THRESHOLD)
            .sort((a, b) => b.relevance - a.relevance)
            .slice(0, 3)
            .map((candidate) => candidate.artifact);
        return dedupeSkills([...exactMatches, ...fuzzyCandidates], 'registry-first');
    }
    generateRuntimeSkills(goal, workerCount, signal = {}) {
        this.ensureBootstrapped();
        const runtimeSkills = [];
        runtimeSkills.push(this.createSkill({
            name: `focused-read-${slugify(goal).slice(0, 24) || 'run'}`,
            instructions: [
                'Prioritize the assigned reading plan before full-file exploration.',
                'Store concrete learnings before expanding scope.',
                'Do not mutate files during the read checkpoint.',
            ].join('\n'),
            toolBindings: [],
            riskClass: 'read',
            scope: 'runtime-hot',
            provenance: `runtime:auto-read:${workerCount}`,
        }));
        if (workerCount > 1) {
            runtimeSkills.push(this.createSkill({
                name: `parallel-orchestrate-${workerCount}-workers`,
                instructions: [
                    'Share findings through POD before duplicate work.',
                    'Keep worktree mutations scoped to assigned files.',
                    'Request verification after each non-trivial diff.',
                ].join('\n'),
                toolBindings: [],
                riskClass: 'orchestrate',
                scope: 'runtime-hot',
                provenance: `runtime:auto-orchestrate:${workerCount}`,
            }));
        }
        const matchedDomains = detectDomains(goal, signal.memoryMatches ?? []);
        const bundledMatches = this.listArtifacts()
            .filter((artifact) => artifact.scope === 'base' && artifact.domain && matchedDomains.includes(artifact.domain))
            .slice(0, 4);
        const derived = this.deriveFromSignals({
            goal,
            workerCount,
            memoryMatches: signal.memoryMatches,
            repeatedFailures: signal.repeatedFailures,
            sessionHints: signal.sessionHints,
        });
        return dedupeSkills([...runtimeSkills, ...bundledMatches, ...derived], 'registry-first');
    }
    deriveFromSignals(signal) {
        const derived = [];
        const domains = detectDomains(signal.goal, signal.memoryMatches ?? []);
        if ((signal.repeatedFailures ?? 0) > 0) {
            derived.push(this.createSkill({
                name: `derived-retry-skill-${slugify(signal.goal).slice(0, 18)}`,
                domain: domains[0] ?? 'workflows',
                instructions: [
                    'Analyze the failing verifier commands before retrying.',
                    'Reduce mutation scope to the smallest failing surface.',
                    'Document why the retry plan is different from the previous pass.',
                ].join('\n'),
                toolBindings: [],
                riskClass: 'orchestrate',
                scope: 'session',
                provenance: `derived:failure-cluster:${signal.repeatedFailures}`,
            }));
        }
        for (const domain of domains.slice(0, 2)) {
            derived.push(this.createSkill({
                name: `derived-${domain}-pattern-${slugify(signal.goal).slice(0, 18)}`,
                domain,
                instructions: [
                    `Leverage prior ${domain} learnings from memory before expanding scope.`,
                    'Extract the reusable checklist from successful runs and apply it to this task.',
                    'Only promote the pattern if verification improves.',
                ].join('\n'),
                toolBindings: [],
                riskClass: 'read',
                scope: 'session',
                provenance: `derived:memory:${domain}`,
            }));
        }
        return dedupeSkills(derived);
    }
    validate(artifact) {
        const errors = [];
        if (!artifact.name.trim())
            errors.push('Skill name is required.');
        if (!artifact.instructions.trim())
            errors.push('Skill instructions are required.');
        const mutateWithoutBinding = artifact.riskClass === 'mutate' && artifact.toolBindings.length === 0;
        if (mutateWithoutBinding)
            errors.push('Mutating skills must define at least one tool binding.');
        for (const binding of artifact.toolBindings) {
            if (binding.type === 'write_file' || binding.type === 'append_file') {
                if (!binding.path)
                    errors.push(`${binding.type} requires a path.`);
            }
            if (binding.type === 'replace_text') {
                if (!binding.path || binding.search === undefined || binding.replace === undefined) {
                    errors.push('replace_text requires path, search, and replace.');
                }
            }
            if (binding.type === 'run_command' && !binding.command) {
                errors.push('run_command requires a command.');
            }
        }
        const hotDeployAllowed = artifact.riskClass !== 'mutate';
        artifact.validationStatus = errors.length === 0 ? 'validated' : 'rejected';
        this.persistArtifact(artifact);
        return {
            valid: errors.length === 0,
            errors,
            hotDeployAllowed,
        };
    }
    listDistributableArtifacts() {
        this.ensureBootstrapped();
        return this.listArtifacts().filter((artifact) => this.isDistributableArtifact(artifact));
    }
    buildDistributionManifest(targetClientIds = []) {
        this.ensureBootstrapped();
        const normalizedTargets = [...new Set(targetClientIds
                .map((clientId) => String(clientId).trim().toLowerCase())
                .filter(Boolean))];
        return {
            generatedAt: Date.now(),
            serverGoverned: true,
            targetClientIds: normalizedTargets,
            skills: this.listDistributableArtifacts().map((artifact) => ({
                skillId: artifact.skillId,
                name: artifact.name,
                scope: artifact.scope,
                rolloutStatus: artifact.rolloutStatus,
                riskClass: artifact.riskClass,
                validationStatus: artifact.validationStatus,
                distributableTo: normalizedTargets,
            })),
        };
    }
    stage(artifact, options) {
        if (!artifact.embedding) {
            try {
                artifact.embedding = this.getEmbedder().embedSync(`${artifact.name} ${artifact.domain ?? ''} ${artifact.instructions ?? ''}`);
            }
            catch {
                // Semantic precomputation is best-effort.
            }
        }
        const hydrated = {
            ...artifact,
            ...deriveCatalogMetadata(artifact.provenance, artifact.catalogPath),
        };
        this.artifacts.set(hydrated.skillId, hydrated);
        if (!options?.bulk) {
            // Skip during bulk bootstrap: persistArtifact (2× file writes/skill) and
            // registerSkillCard (3× SQL writes/skill to graph.db) across 1000+ skills
            // block startup for 60+ s. Skills loaded from disk don't need re-persisting.
            // Explicit/user-triggered stage() calls are unaffected.
            this.persistArtifact(hydrated);
            this.registerSkillCard(hydrated);
        }
        if (!options?.bulk) {
            try {
                this.ngramIndex?.addDocument(hydrated.skillId, hydrated.name + ' ' + hydrated.instructions + ' ' + (hydrated.domain ?? ''));
            }
            catch {
                // n-gram indexing is best-effort; fall back silently
            }
        }
        return hydrated;
    }
    deploy(artifact, workerId, worktreeDir, checkpoint) {
        const validation = this.validate(artifact);
        if (!validation.valid) {
            throw new Error(`Skill ${artifact.name} failed validation: ${validation.errors.join('; ')}`);
        }
        const deployDir = path.join(worktreeDir, '.agent', 'skills', 'runtime');
        fs.mkdirSync(deployDir, { recursive: true });
        const fileName = `${slugify(artifact.name).toLowerCase() || artifact.skillId}.md`;
        fs.writeFileSync(path.join(deployDir, fileName), this.renderMarkdown(artifact), 'utf-8');
        artifact.rolloutStatus = checkpoint === 'before-verify' && artifact.riskClass === 'mutate' ? 'staged' : 'hot';
        artifact.deploymentPoints.push({
            workerId,
            checkpoint,
            deployedAt: Date.now(),
            worktreeDir,
        });
        this.persistArtifact(artifact);
        return artifact;
    }
    recordOutcome(skillId, metrics) {
        const artifact = this.artifacts.get(skillId);
        if (!artifact)
            return undefined;
        if (metrics.success)
            artifact.effectiveness.successes++;
        else
            artifact.effectiveness.failures++;
        artifact.effectiveness.tokenDelta += metrics.tokenDelta ?? 0;
        artifact.effectiveness.retriesAvoided += metrics.retriesAvoided ?? 0;
        artifact.effectiveness.verificationPasses += metrics.verificationPassed ? 1 : 0;
        if (artifact.effectiveness.failures > artifact.effectiveness.successes + 1) {
            artifact.rolloutStatus = 'revoked';
        }
        this.persistArtifact(artifact);
        return artifact;
    }
    promote(skillId, scope = 'global') {
        const artifact = this.artifacts.get(skillId);
        if (!artifact)
            return undefined;
        const validation = this.validate(artifact);
        if (!validation.valid)
            return undefined;
        artifact.scope = scope;
        artifact.rolloutStatus = 'promoted';
        this.persistArtifact(artifact);
        return artifact;
    }
    revoke(skillId) {
        const artifact = this.artifacts.get(skillId);
        if (!artifact)
            return undefined;
        artifact.rolloutStatus = 'revoked';
        this.persistArtifact(artifact);
        return artifact;
    }
    lastLocalRefresh = 0;
    ensureBootstrapped() {
        if (!this.bootstrapped) {
            this.loadBundledDefaults();
            this.loadLocalOverrides();
            this.bootstrapped = true;
            this.lastLocalRefresh = Date.now();
            return;
        }
        if (Date.now() - this.lastLocalRefresh > 2000) {
            this.loadLocalOverrides();
            this.lastLocalRefresh = Date.now();
        }
    }
    loadBundledDefaults() {
        for (const seed of BUILTIN_SKILL_PACKS) {
            this.stage({
                skillId: `skill_${slugify(seed.key)}`,
                version: 1,
                name: seed.name,
                domain: seed.domain,
                instructions: seed.instructions.join('\n'),
                toolBindings: seed.toolBindings,
                riskClass: seed.riskClass,
                scope: seed.scope,
                provenance: 'bundled',
                validationStatus: 'validated',
                rolloutStatus: 'promoted',
                ...deriveCatalogMetadata('bundled'),
                effectiveness: {
                    successes: 0,
                    failures: 0,
                    tokenDelta: 0,
                    retriesAvoided: 0,
                    verificationPasses: 0,
                },
                deploymentPoints: [],
            }, { bulk: true });
        }
    }
    loadLocalOverrides() {
        const skillDirs = [
            path.join(this.workspaceRoot, '.agent', 'skills'),
            path.join(this.workspaceRoot, '.agents', 'skills'),
        ];
        const entries = skillDirs.flatMap((skillDir) => readMarkdownArtifacts(skillDir));
        const shouldBulkLoad = entries.length > 200;
        const foundLocalIds = new Set();
        for (const entry of entries) {
            const frontmatter = entry.parsed.frontmatter;
            const name = String(frontmatter.name ?? path.basename(entry.path, '.md'));
            const skillId = `skill_local_${slugify(name)}`;
            foundLocalIds.add(skillId);
            this.stage({
                skillId,
                version: 1,
                name,
                domain: String(frontmatter.domain ?? detectDomains(name)[0] ?? ''),
                instructions: entry.parsed.body,
                toolBindings: [],
                riskClass: normalizeRiskClass(frontmatter.riskClass),
                scope: 'base',
                provenance: `local:${entry.path}`,
                validationStatus: 'validated',
                rolloutStatus: 'promoted',
                ...deriveCatalogMetadata(`local:${entry.path}`, entry.path),
                effectiveness: {
                    successes: 0,
                    failures: 0,
                    tokenDelta: 0,
                    retriesAvoided: 0,
                    verificationPasses: 0,
                },
                deploymentPoints: [],
            }, shouldBulkLoad ? { bulk: true } : undefined);
        }
        for (const [id, art] of this.artifacts.entries()) {
            if (art.provenance.startsWith('local:') && !foundLocalIds.has(id)) {
                this.artifacts.delete(id);
            }
        }
    }
    persistArtifact(artifact) {
        const dir = path.join(this.rootDir, artifact.skillId, `v${artifact.version}`);
        fs.mkdirSync(dir, { recursive: true });
        fs.writeFileSync(path.join(dir, 'artifact.json'), JSON.stringify(artifact, null, 2), 'utf-8');
        fs.writeFileSync(path.join(dir, 'skill.md'), this.renderMarkdown(artifact), 'utf-8');
        this.persistCanonicalArtifact(artifact);
    }
    renderMarkdown(artifact) {
        const isQuarantined = artifact.provenance.startsWith('peer:');
        const lines = [
            '---',
            `name: ${artifact.name}`,
            `description: Runtime-generated skill (${artifact.riskClass})`,
            `tags: [runtime, ${artifact.scope}, ${artifact.riskClass}${artifact.domain ? `, ${artifact.domain}` : ''}]`,
            ...(isQuarantined ? ['quarantine-tag: true'] : []),
            '---',
            '',
            `# ${artifact.name}`,
            '',
            '## Instructions',
            artifact.instructions,
        ];
        if (artifact.toolBindings.length > 0) {
            lines.push('', '## Tool Bindings');
            artifact.toolBindings.forEach(binding => {
                lines.push(`- ${binding.type}: ${JSON.stringify(binding)}`);
            });
        }
        return lines.join('\n');
    }
    registerSkillCard(artifact) {
        if (!this.registry)
            return;
        const card = {
            name: artifact.name,
            trigger: {
                logic: 'OR',
                conditions: [
                    {
                        field: 'task_keywords',
                        operator: 'contains',
                        value: artifact.name,
                    },
                ],
            },
            actions: artifact.toolBindings.map(binding => ({
                tool: binding.type,
                args: Object.fromEntries(Object.entries(binding).flatMap(([key, value]) => key === 'type' || value === undefined ? [] : [[key, String(value)]])),
            })),
            confidence: 0.7,
            adoptions: 0,
            origin: artifact.provenance,
        };
        this.registry.register(card);
    }
    isDistributableArtifact(artifact) {
        if (artifact.rolloutStatus !== 'promoted')
            return false;
        if (artifact.validationStatus !== 'validated')
            return false;
        return artifact.scope === 'global' || artifact.scope === 'base';
    }
    persistCanonicalArtifact(artifact) {
        if (artifact.rolloutStatus !== 'promoted')
            return;
        if (artifact.provenance === 'bundled' || artifact.provenance.startsWith('local:'))
            return;
        const canonicalDir = path.join(this.workspaceRoot, '.agents', 'skills', 'generated');
        fs.mkdirSync(canonicalDir, { recursive: true });
        const fileName = `${slugify(artifact.name).toLowerCase() || artifact.skillId}.md`;
        const targetPath = path.join(canonicalDir, fileName);
        fs.writeFileSync(targetPath, this.renderMarkdown(artifact), 'utf-8');
        artifact.catalogPath = targetPath;
        artifact.catalogOrigin = 'generated';
        artifact.catalogPriority = deriveCatalogMetadata(artifact.provenance, targetPath).catalogPriority;
    }
}
function normalizeRiskClass(value) {
    return value === 'mutate' || value === 'orchestrate' ? value : 'read';
}
function dedupeSkills(skills, policy = 'registry-first') {
    return dedupeArtifactsByPreference(skills, policy);
}
export const createSkillRuntime = (registry, rootDir, workspaceRoot) => new SkillRuntime(registry, rootDir, workspaceRoot);
