import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { randomUUID } from 'crypto';
import { BUILTIN_WORKFLOW_PACKS, dedupeArtifactsByPreference, deriveCatalogMetadata, detectDomains, readMarkdownArtifacts, slugify, } from './runtime-assets.js';
export class WorkflowRuntime {
    rootDir;
    workspaceRoot;
    artifacts = new Map();
    bootstrapped = false;
    constructor(rootDir, workspaceRoot) {
        this.rootDir = rootDir ?? path.join(os.tmpdir(), 'nexus-prime-runtime-workflows');
        this.workspaceRoot = workspaceRoot ?? process.cwd();
        fs.mkdirSync(this.rootDir, { recursive: true });
        this.ensureBootstrapped();
    }
    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) => a.name.localeCompare(b.name));
    }
    getArtifact(workflowId) {
        this.ensureBootstrapped();
        return this.artifacts.get(workflowId);
    }
    findByName(name) {
        this.ensureBootstrapped();
        const normalized = name.toLowerCase();
        return this.listArtifacts().find((artifact) => artifact.name.toLowerCase() === normalized || artifact.workflowId.toLowerCase() === normalized);
    }
    createWorkflow(input) {
        const artifact = {
            ...input,
            workflowId: `workflow_${randomUUID().slice(0, 8)}`,
            version: 1,
            validationStatus: 'pending',
            rolloutStatus: 'staged',
            ...deriveCatalogMetadata(input.provenance),
            effectiveness: {
                successes: 0,
                failures: 0,
                retriesAvoided: 0,
                verificationPasses: 0,
            },
            deploymentPoints: [],
        };
        return this.stage(artifact);
    }
    stage(artifact) {
        artifact.validationStatus = this.validate(artifact).valid ? 'validated' : 'rejected';
        const hydrated = {
            ...artifact,
            ...deriveCatalogMetadata(artifact.provenance, artifact.catalogPath),
        };
        this.artifacts.set(hydrated.workflowId, hydrated);
        this.persistArtifact(hydrated);
        return hydrated;
    }
    deploy(workflowId, runId, scope = 'session') {
        const artifact = this.artifacts.get(workflowId);
        if (!artifact)
            return undefined;
        artifact.scope = scope;
        artifact.rolloutStatus = scope === 'global' ? 'promoted' : 'hot';
        artifact.deploymentPoints.push({ runId, deployedAt: Date.now(), scope });
        this.persistArtifact(artifact);
        return artifact;
    }
    recordOutcome(workflowId, metrics) {
        const artifact = this.artifacts.get(workflowId);
        if (!artifact)
            return undefined;
        if (metrics.success)
            artifact.effectiveness.successes++;
        else
            artifact.effectiveness.failures++;
        artifact.effectiveness.retriesAvoided += metrics.retriesAvoided ?? 0;
        artifact.effectiveness.verificationPasses += metrics.verificationPassed ? 1 : 0;
        if (artifact.effectiveness.failures > artifact.effectiveness.successes + 1) {
            artifact.rolloutStatus = 'revoked';
        }
        else if (artifact.effectiveness.successes >= 2 && artifact.rolloutStatus !== 'promoted') {
            artifact.rolloutStatus = 'promoted';
            artifact.scope = 'global';
        }
        this.persistArtifact(artifact);
        return artifact;
    }
    revoke(workflowId) {
        const artifact = this.artifacts.get(workflowId);
        if (!artifact)
            return undefined;
        artifact.rolloutStatus = 'revoked';
        this.persistArtifact(artifact);
        return artifact;
    }
    resolveWorkflowSelectors(names, goal) {
        this.ensureBootstrapped();
        const selectors = new Set(names.map((name) => name.toLowerCase()));
        const domains = detectDomains(goal, names);
        const matches = this.listArtifacts({ dedupe: false }).filter((artifact) => selectors.has(artifact.name.toLowerCase()) ||
            selectors.has(artifact.workflowId.toLowerCase()) ||
            domains.includes(artifact.domain));
        return dedupeWorkflows(matches, 'registry-first');
    }
    deriveFromSignals(signal) {
        const domains = detectDomains(signal.goal, signal.memoryMatches ?? []);
        const derived = [];
        if ((signal.repeatedFailures ?? 0) > 0) {
            derived.push(this.createWorkflow({
                name: `derived-retry-loop-${slugify(signal.goal).slice(0, 20)}`,
                domain: domains[0] ?? 'workflows',
                description: 'Derived retry workflow synthesized from repeated failures.',
                triggerConditions: ['run failure cluster', 'retry requested'],
                expectedOutputs: ['retry plan', 'focused verification'],
                guardrails: ['Restrict retry scope to the failing surface.', 'Attach verifier evidence before re-apply.'],
                verifierHooks: ['Re-run the smallest failing verifier first.'],
                roleAffinity: ['planner', 'verifier', 'skill-maker'],
                steps: [
                    { title: 'Identify failing commands and affected files', checkpoint: 'retry', role: 'planner', bindings: [] },
                    { title: 'Apply a narrowed retry plan', checkpoint: 'retry', role: 'coder', bindings: [] },
                    { title: 'Verify retry diff before promotion', checkpoint: 'before-verify', role: 'verifier', bindings: [] },
                ],
                scope: 'session',
                provenance: `derived:failure-cluster:${Math.max(signal.repeatedFailures ?? 1, 1)}`,
            }));
        }
        for (const domain of domains.slice(0, 2)) {
            derived.push(this.createWorkflow({
                name: `derived-${domain}-workflow-${slugify(signal.goal).slice(0, 18)}`,
                domain,
                description: `Derived ${domain} workflow from goal and memory overlap.`,
                triggerConditions: [`goal matched ${domain}`, 'memory overlap detected'],
                expectedOutputs: ['derived checklist', 'verifier evidence'],
                guardrails: ['Derived workflows must stay additive until verified.'],
                verifierHooks: ['Log the workflow evidence into the run ledger.'],
                roleAffinity: ['planner', 'coder', 'verifier'],
                steps: [
                    { title: `Plan ${domain} sub-problem`, checkpoint: 'before-read', role: 'planner', bindings: [] },
                    { title: `Execute ${domain} pass`, checkpoint: 'before-mutate', role: 'coder', bindings: [] },
                    { title: `Verify ${domain} outcome`, checkpoint: 'before-verify', role: 'verifier', bindings: [] },
                ],
                scope: 'session',
                provenance: `derived:domain:${domain}`,
            }));
        }
        return dedupeWorkflows(derived, 'registry-first');
    }
    applyToTask(workflows, currentVerifyCommands, currentActions) {
        const verify = new Set(currentVerifyCommands);
        const actions = [...currentActions];
        const events = workflows.map((workflow) => ({
            type: 'workflow.selected',
            workflowId: workflow.workflowId,
            name: workflow.name,
            domain: workflow.domain,
            scope: workflow.scope,
        }));
        for (const workflow of workflows) {
            workflow.verifierHooks.forEach((hook) => {
                if (hook.includes('npm ') || hook.includes('pnpm ') || hook.includes('node ')) {
                    verify.add(hook);
                }
            });
            workflow.steps.forEach((step) => {
                if (step.command)
                    verify.add(step.command);
                const verifierStep = step.role === 'verifier' || step.checkpoint === 'before-verify';
                if (verifierStep) {
                    step.bindings
                        .filter((binding) => binding.type === 'run_command' && binding.command)
                        .forEach((binding) => verify.add(binding.command || ''));
                }
                else {
                    actions.push(...step.bindings);
                }
            });
        }
        return {
            verifyCommands: [...verify],
            actions,
            events,
        };
    }
    validate(artifact) {
        const errors = [];
        if (!artifact.name.trim())
            errors.push('Workflow name is required.');
        if (artifact.steps.length === 0)
            errors.push('Workflow must contain at least one step.');
        return { valid: errors.length === 0, errors };
    }
    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_WORKFLOW_PACKS) {
            this.ingestSeed(seed, 'bundled');
        }
    }
    loadLocalOverrides() {
        const workflowDirs = [
            path.join(this.workspaceRoot, '.agent', 'workflows'),
            path.join(this.workspaceRoot, '.agents', 'workflows'),
        ];
        const foundLocalIds = new Set();
        for (const workflowDir of workflowDirs) {
            for (const entry of readMarkdownArtifacts(workflowDir)) {
                const frontmatter = entry.parsed.frontmatter;
                const lines = entry.parsed.body.split('\n');
                const steps = lines
                    .map((line) => line.match(/^(\d+)\.\s+(.+)$/))
                    .filter(Boolean)
                    .map((match) => ({
                    title: String(match?.[2] ?? '').trim(),
                    command: extractCommand(String(match?.[2] ?? '')),
                    checkpoint: 'before-mutate',
                    bindings: [],
                }));
                const name = String(frontmatter.name ?? path.basename(entry.path, '.md'));
                const workflowId = `workflow_local_${slugify(name)}`;
                foundLocalIds.add(workflowId);
                this.stage({
                    workflowId,
                    version: 1,
                    name,
                    domain: String(frontmatter.domain ?? detectDomains(name)[0] ?? 'workflows'),
                    description: String(frontmatter.description ?? ''),
                    triggerConditions: toStringArray(frontmatter.triggers),
                    expectedOutputs: toStringArray(frontmatter.outputs),
                    guardrails: toStringArray(frontmatter.guardrails),
                    verifierHooks: toStringArray(frontmatter.verify),
                    roleAffinity: toStringArray(frontmatter.roles),
                    steps,
                    scope: 'base',
                    provenance: `local:${entry.path}`,
                    validationStatus: 'validated',
                    rolloutStatus: 'promoted',
                    ...deriveCatalogMetadata(`local:${entry.path}`, entry.path),
                    effectiveness: {
                        successes: 0,
                        failures: 0,
                        retriesAvoided: 0,
                        verificationPasses: 0,
                    },
                    deploymentPoints: [],
                });
            }
        }
        for (const [id, art] of this.artifacts.entries()) {
            if (art.provenance.startsWith('local:') && !foundLocalIds.has(id)) {
                this.artifacts.delete(id);
            }
        }
    }
    ingestSeed(seed, provenance) {
        this.stage({
            workflowId: `workflow_${slugify(seed.key)}`,
            version: 1,
            name: seed.name,
            domain: seed.domain,
            description: seed.description,
            triggerConditions: seed.triggerConditions,
            expectedOutputs: seed.expectedOutputs,
            guardrails: seed.guardrails,
            verifierHooks: seed.verifierHooks,
            roleAffinity: seed.roleAffinity,
            steps: seed.steps.map((step) => ({
                title: step.title,
                command: step.command,
                checkpoint: step.checkpoint,
                role: step.role,
                bindings: step.bindings ?? [],
            })),
            scope: 'base',
            provenance,
            validationStatus: 'validated',
            rolloutStatus: 'promoted',
            ...deriveCatalogMetadata(provenance),
            effectiveness: {
                successes: 0,
                failures: 0,
                retriesAvoided: 0,
                verificationPasses: 0,
            },
            deploymentPoints: [],
        });
    }
    persistArtifact(artifact) {
        const dir = path.join(this.rootDir, artifact.workflowId, `v${artifact.version}`);
        fs.mkdirSync(dir, { recursive: true });
        fs.writeFileSync(path.join(dir, 'artifact.json'), JSON.stringify(artifact, null, 2), 'utf-8');
        this.persistCanonicalArtifact(artifact);
    }
    persistCanonicalArtifact(artifact) {
        if (artifact.rolloutStatus !== 'promoted')
            return;
        if (artifact.provenance === 'bundled' || artifact.provenance.startsWith('local:'))
            return;
        const canonicalDir = path.join(this.workspaceRoot, '.agents', 'workflows', 'generated');
        fs.mkdirSync(canonicalDir, { recursive: true });
        const fileName = `${slugify(artifact.name).toLowerCase() || artifact.workflowId}.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;
    }
    renderMarkdown(artifact) {
        const frontmatter = [
            '---',
            `name: ${artifact.name}`,
            `domain: ${artifact.domain}`,
            `description: ${artifact.description}`,
            `triggers: [${artifact.triggerConditions.map((value) => JSON.stringify(value)).join(', ')}]`,
            `outputs: [${artifact.expectedOutputs.map((value) => JSON.stringify(value)).join(', ')}]`,
            `guardrails: [${artifact.guardrails.map((value) => JSON.stringify(value)).join(', ')}]`,
            `verify: [${artifact.verifierHooks.map((value) => JSON.stringify(value)).join(', ')}]`,
            `roles: [${artifact.roleAffinity.map((value) => JSON.stringify(value)).join(', ')}]`,
            '---',
            '',
        ];
        const steps = artifact.steps.map((step, index) => {
            const command = step.command ? ` \`${step.command}\`` : '';
            return `${index + 1}. ${step.title}${command}`;
        });
        return [...frontmatter, ...steps].join('\n');
    }
}
function extractCommand(value) {
    const match = value.match(/`([^`]+)`/);
    return match?.[1];
}
function toStringArray(value) {
    return Array.isArray(value) ? value.map(String).filter(Boolean) : [];
}
function dedupeWorkflows(artifacts, policy = 'registry-first') {
    return dedupeArtifactsByPreference(artifacts, policy);
}
export const createWorkflowRuntime = (rootDir, workspaceRoot) => new WorkflowRuntime(rootDir, workspaceRoot);
