/**
 * DecisionGraph — Unified Data Model (ADR-033)
 *
 * Canonical shared data model for every pipeline execution.
 * A directed acyclic graph (DAG) where nodes are data artifacts
 * and edges represent derivation relationships.
 *
 * Features:
 *   - Typed node/edge CRUD with schema validation
 *   - Append-only version history (originals preserved)
 *   - Automatic edge creation from derivedFrom references
 *   - Graph lifecycle management (created → accumulated → sealed)
 *   - Compound querying with pagination
 *   - Semantic search on summary fields
 *   - JSON file persistence + AgentDB memory sync
 *   - Concurrent-safe writes via monotonic timestamps
 *   - Statistics and metrics aggregation
 */
import * as crypto from 'node:crypto';
import * as fs from 'node:fs';
import * as path from 'node:path';
// ============================================================================
// Runtime Node Type Enum (ADR-033 specifies enum, not just type union)
// ============================================================================
/** All valid DecisionNodeType values as a runtime-accessible object. */
export const DECISION_NODE_TYPES = {
    // Pass 1 — Problem Intelligence
    PROBLEM_DEFINITION: 'problem_definition',
    BUSINESS_CONTEXT: 'business_context',
    TECHNICAL_SCOPE: 'technical_scope',
    ARCHITECTURE_HYPOTHESIS: 'architecture_hypothesis',
    EXECUTION_PLAN: 'execution_plan',
    DECISION_LINEAGE: 'decision_lineage',
    // Pass 2 — Simulation
    SCENARIO: 'scenario',
    RISK_SIGNAL: 'risk_signal',
    RELIABILITY_REPORT: 'reliability_report',
    // Pass 3 — Architecture
    SYSTEM_ARCHITECTURE: 'system_architecture',
    INTEGRATION_MAP: 'integration_map',
    DATA_FLOW: 'data_flow',
    SERVICE_BOUNDARY: 'service_boundary',
    PROTOTYPE: 'prototype',
    API_CONTRACT: 'api_contract',
    SDK_INTERFACE: 'sdk_interface',
    // Pass 4 — Governance
    SECURITY_ANALYSIS: 'security_analysis',
    COMPLIANCE_REPORT: 'compliance_report',
    COST_MODEL: 'cost_model',
    GOVERNANCE_REPORT: 'governance_report',
    POLICY_ANALYSIS: 'policy_analysis',
    // Pass 5 — Decision Artifacts
    EXECUTIVE_MEMO: 'executive_memo',
    EXECUTIVE_SUMMARY: 'executive_summary',
    RISK_ASSESSMENT: 'risk_assessment',
    IMPLEMENTATION_ROADMAP: 'implementation_roadmap',
    ORG_IMPACT: 'org_impact',
    STAKEHOLDER_MAP: 'stakeholder_map',
    KPI_DEFINITION: 'kpi_definition',
    OKR_ALIGNMENT: 'okr_alignment',
    RAID_LOG: 'raid_log',
    CONFIDENCE_SCORE: 'confidence_score',
    // Pass 6 — Deployment
    DEPLOYMENT_ENDPOINT: 'deployment_endpoint',
    INTEGRATION_RESULT: 'integration_result',
    // Pass 7 — Observability
    TELEMETRY_CONFIG: 'telemetry_config',
    HEALTH_CHECK: 'health_check',
    LEARNED_PATTERN: 'learned_pattern',
    // Cross-cutting
    PROVENANCE_MANIFEST: 'provenance_manifest',
};
/** Set of all valid node type values for runtime validation. */
export const VALID_NODE_TYPES = new Set(Object.values(DECISION_NODE_TYPES));
/** All valid EdgeRelation values as a runtime-accessible object. */
export const EDGE_RELATIONS = {
    DERIVED_FROM: 'derived_from',
    INFORMS: 'informs',
    VALIDATES: 'validates',
    SUPERSEDES: 'supersedes',
    AGGREGATES: 'aggregates',
};
/** Set of all valid edge relation values for runtime validation. */
export const VALID_EDGE_RELATIONS = new Set(Object.values(EDGE_RELATIONS));
// ============================================================================
// DecisionGraph
// ============================================================================
export class DecisionGraph {
    nodes = new Map();
    edges = [];
    versionHistory = new Map();
    executionId;
    outputDir;
    lifecycle;
    writeSequence = 0;
    constructor(executionId, outputDir) {
        this.executionId = executionId;
        this.outputDir = outputDir;
        this.lifecycle = 'created';
    }
    // --------------------------------------------------------------------------
    // Identity & Lifecycle
    // --------------------------------------------------------------------------
    /** Get the execution ID. */
    getExecutionId() {
        return this.executionId;
    }
    /** Get the current lifecycle state. */
    getLifecycle() {
        return this.lifecycle;
    }
    /** Transition to accumulated state (called after first node beyond root). */
    markAccumulated() {
        if (this.lifecycle === 'created') {
            this.lifecycle = 'accumulated';
        }
    }
    /** Seal the graph — no further writes allowed. */
    seal() {
        this.lifecycle = 'sealed';
    }
    /** Mark as queryable — sealed + available for external queries. */
    markQueryable() {
        if (this.lifecycle === 'sealed') {
            this.lifecycle = 'queryable';
        }
    }
    /** Check if the graph accepts writes. */
    isWritable() {
        return this.lifecycle === 'created' || this.lifecycle === 'accumulated';
    }
    // --------------------------------------------------------------------------
    // Node ID Generation
    // --------------------------------------------------------------------------
    /** Generate a new unique node ID. */
    generateNodeId() {
        return crypto.randomUUID();
    }
    /**
     * Generate the canonical AgentDB memory key for a node.
     * Format: decisiongraph:{execution_id}:{node_id}
     */
    memoryKey(nodeId) {
        return `decisiongraph:${this.executionId}:${nodeId}`;
    }
    // --------------------------------------------------------------------------
    // Node Validation
    // --------------------------------------------------------------------------
    /** Validate a node against the schema. Returns errors (empty = valid). */
    validateNode(node) {
        const errors = [];
        if (!node.id || typeof node.id !== 'string') {
            errors.push({ field: 'id', message: 'Node ID is required and must be a non-empty string' });
        }
        if (!VALID_NODE_TYPES.has(node.type)) {
            errors.push({ field: 'type', message: `Invalid node type "${node.type}". Must be one of: ${Array.from(VALID_NODE_TYPES).join(', ')}` });
        }
        if (!node.name || typeof node.name !== 'string') {
            errors.push({ field: 'name', message: 'Node name is required and must be a non-empty string' });
        }
        if (node.content === null || node.content === undefined || typeof node.content !== 'object') {
            errors.push({ field: 'content', message: 'Content is required and must be an object' });
        }
        if (typeof node.summary !== 'string') {
            errors.push({ field: 'summary', message: 'Summary is required and must be a string' });
        }
        // producedBy is MANDATORY per ADR-033
        if (!node.producedBy || !node.producedBy.domain || !node.producedBy.agent) {
            errors.push({ field: 'producedBy', message: 'producedBy is required with domain and agent fields' });
        }
        if (typeof node.pass !== 'number' || node.pass < 1 || node.pass > 7) {
            errors.push({ field: 'pass', message: 'Pass must be a number between 1 and 7' });
        }
        // derivedFrom is MANDATORY (can be empty array for root nodes)
        if (!Array.isArray(node.derivedFrom)) {
            errors.push({ field: 'derivedFrom', message: 'derivedFrom is required and must be an array of node IDs' });
        }
        if (typeof node.confidence !== 'number' || node.confidence < 0 || node.confidence > 1) {
            errors.push({ field: 'confidence', message: 'Confidence must be a number between 0.0 and 1.0' });
        }
        const validStatuses = ['complete', 'partial', 'degraded', 'failed'];
        if (!validStatuses.includes(node.status)) {
            errors.push({ field: 'status', message: `Status must be one of: ${validStatuses.join(', ')}` });
        }
        if (!Array.isArray(node.tags)) {
            errors.push({ field: 'tags', message: 'Tags must be an array of strings' });
        }
        if (typeof node.version !== 'number' || node.version < 1) {
            errors.push({ field: 'version', message: 'Version must be a positive integer' });
        }
        if (!node.timestamp || typeof node.timestamp !== 'string') {
            errors.push({ field: 'timestamp', message: 'Timestamp is required and must be an ISO 8601 string' });
        }
        return errors;
    }
    // --------------------------------------------------------------------------
    // Node Operations — Write
    // --------------------------------------------------------------------------
    /** Add a validated node to the graph. Throws on validation failure. */
    addNode(node) {
        if (!this.isWritable()) {
            throw new Error(`DecisionGraph: cannot write to graph in "${this.lifecycle}" state`);
        }
        if (this.nodes.has(node.id)) {
            throw new Error(`DecisionGraph: duplicate node ID "${node.id}"`);
        }
        // Validate
        const errors = this.validateNode(node);
        if (errors.length > 0) {
            throw new Error(`DecisionGraph: node validation failed:\n${errors.map(e => `  ${e.field}: ${e.message}`).join('\n')}`);
        }
        this.nodes.set(node.id, node);
        this.writeSequence++;
        // Store initial version in history
        this.versionHistory.set(node.id, [{
                version: node.version,
                timestamp: node.timestamp,
                content: node.content,
                summary: node.summary,
                status: node.status,
                confidence: node.confidence,
            }]);
        // Auto-create derived_from edges from derivedFrom references
        for (const parentId of node.derivedFrom) {
            this.edges.push({ from: parentId, to: node.id, relation: 'derived_from' });
        }
        // Transition lifecycle
        if (this.lifecycle === 'created' && this.nodes.size > 1) {
            this.lifecycle = 'accumulated';
        }
    }
    /**
     * Update an existing node (ADR-033: append-only history).
     * Original content is preserved in version history.
     * Increments version and updates timestamp.
     */
    updateNode(nodeId, updates) {
        if (!this.isWritable()) {
            throw new Error(`DecisionGraph: cannot write to graph in "${this.lifecycle}" state`);
        }
        const existing = this.nodes.get(nodeId);
        if (!existing) {
            throw new Error(`DecisionGraph: node "${nodeId}" not found`);
        }
        const newVersion = existing.version + 1;
        const newTimestamp = new Date().toISOString();
        const updated = {
            ...existing,
            ...updates,
            version: newVersion,
            timestamp: newTimestamp,
        };
        // Validate the updated node
        const errors = this.validateNode(updated);
        if (errors.length > 0) {
            throw new Error(`DecisionGraph: update validation failed:\n${errors.map(e => `  ${e.field}: ${e.message}`).join('\n')}`);
        }
        this.nodes.set(nodeId, updated);
        this.writeSequence++;
        // Append to version history (original preserved)
        const history = this.versionHistory.get(nodeId);
        if (history) {
            history.push({
                version: newVersion,
                timestamp: newTimestamp,
                content: updated.content,
                summary: updated.summary,
                status: updated.status,
                confidence: updated.confidence,
            });
        }
        return updated;
    }
    // --------------------------------------------------------------------------
    // Node Operations — Read
    // --------------------------------------------------------------------------
    /** Get a node by ID. Returns undefined if not found. */
    getNode(nodeId) {
        return this.nodes.get(nodeId);
    }
    /** Get all nodes of a specific type. */
    getNodesByType(type) {
        const result = [];
        for (const node of this.nodes.values()) {
            if (node.type === type) {
                result.push(node);
            }
        }
        return result;
    }
    /** Get all nodes from a specific pass. */
    getNodesByPass(pass) {
        const result = [];
        for (const node of this.nodes.values()) {
            if (node.pass === pass) {
                result.push(node);
            }
        }
        return result;
    }
    /** Get all nodes produced by a specific agent. */
    getNodesByAgent(domain, agent) {
        const result = [];
        for (const node of this.nodes.values()) {
            if (node.producedBy.domain === domain && node.producedBy.agent === agent) {
                result.push(node);
            }
        }
        return result;
    }
    /** Get all nodes matching a tag. */
    getNodesByTag(tag) {
        const result = [];
        for (const node of this.nodes.values()) {
            if (node.tags.includes(tag)) {
                result.push(node);
            }
        }
        return result;
    }
    /** Get total node count. */
    get nodeCount() {
        return this.nodes.size;
    }
    /** Get total edge count. */
    get edgeCount() {
        return this.edges.length;
    }
    /** Get all nodes as an array. */
    getAllNodes() {
        return Array.from(this.nodes.values());
    }
    /** Get all edges as an array. */
    getAllEdges() {
        return [...this.edges];
    }
    // --------------------------------------------------------------------------
    // Compound Querying with Pagination (ADR-033)
    // --------------------------------------------------------------------------
    /**
     * Query nodes with compound filters and optional pagination.
     * All filter fields are ANDed together.
     */
    queryNodes(filter, pagination) {
        let results = [];
        for (const node of this.nodes.values()) {
            if (filter.type !== undefined && node.type !== filter.type)
                continue;
            if (filter.pass !== undefined && node.pass !== filter.pass)
                continue;
            if (filter.tag !== undefined && !node.tags.includes(filter.tag))
                continue;
            if (filter.status !== undefined && node.status !== filter.status)
                continue;
            if (filter.domain !== undefined && node.producedBy.domain !== filter.domain)
                continue;
            if (filter.agent !== undefined && node.producedBy.agent !== filter.agent)
                continue;
            if (filter.minConfidence !== undefined && node.confidence < filter.minConfidence)
                continue;
            results.push(node);
        }
        const total = results.length;
        const offset = pagination?.offset ?? 0;
        const limit = pagination?.limit ?? total;
        results = results.slice(offset, offset + limit);
        return {
            items: results,
            total,
            offset,
            limit,
            hasMore: offset + limit < total,
        };
    }
    // --------------------------------------------------------------------------
    // Semantic Search (ADR-033 — text similarity on summary fields)
    // --------------------------------------------------------------------------
    /**
     * Search nodes by text similarity on the summary field.
     * Uses simple token overlap scoring (production systems would use
     * vector embeddings via AgentDB HNSW — this provides the interface).
     */
    searchBySummary(query, topK = 10) {
        if (!query.trim())
            return [];
        const queryTokens = tokenize(query);
        if (queryTokens.size === 0)
            return [];
        const scored = [];
        for (const node of this.nodes.values()) {
            if (!node.summary)
                continue;
            const nodeTokens = tokenize(node.summary);
            if (nodeTokens.size === 0)
                continue;
            // Jaccard similarity
            let intersection = 0;
            for (const t of queryTokens) {
                if (nodeTokens.has(t))
                    intersection++;
            }
            const union = queryTokens.size + nodeTokens.size - intersection;
            const score = union > 0 ? intersection / union : 0;
            if (score > 0) {
                scored.push({ node, score });
            }
        }
        scored.sort((a, b) => b.score - a.score);
        return scored.slice(0, topK).map(s => s.node);
    }
    // --------------------------------------------------------------------------
    // Edge Operations
    // --------------------------------------------------------------------------
    /** Add an edge to the graph with validation. */
    addEdge(from, to, relation) {
        if (!VALID_EDGE_RELATIONS.has(relation)) {
            throw new Error(`DecisionGraph: invalid edge relation "${relation}"`);
        }
        // Check for duplicate edges
        const exists = this.edges.some(e => e.from === from && e.to === to && e.relation === relation);
        if (!exists) {
            this.edges.push({ from, to, relation });
        }
    }
    /** Get all edges from a specific node. */
    getEdgesFrom(nodeId) {
        return this.edges.filter(e => e.from === nodeId);
    }
    /** Get all edges to a specific node. */
    getEdgesTo(nodeId) {
        return this.edges.filter(e => e.to === nodeId);
    }
    /** Get all edges with a specific relation. */
    getEdgesByRelation(relation) {
        return this.edges.filter(e => e.relation === relation);
    }
    /** Get the full derivation chain for a node (all ancestors, depth-first). */
    getDerivationChain(nodeId) {
        const visited = new Set();
        const chain = [];
        const walk = (id) => {
            if (visited.has(id))
                return;
            visited.add(id);
            const node = this.nodes.get(id);
            if (!node)
                return;
            for (const parentId of node.derivedFrom) {
                walk(parentId);
            }
            chain.push(node);
        };
        walk(nodeId);
        return chain;
    }
    /** Get the immediate children of a node (nodes that derive from it). */
    getChildren(nodeId) {
        const children = [];
        for (const node of this.nodes.values()) {
            if (node.derivedFrom.includes(nodeId)) {
                children.push(node);
            }
        }
        return children;
    }
    // --------------------------------------------------------------------------
    // Version History (ADR-033 — append-only, originals preserved)
    // --------------------------------------------------------------------------
    /** Get the full version history for a node. */
    getVersionHistory(nodeId) {
        return this.versionHistory.get(nodeId) ?? [];
    }
    /** Get a specific version of a node's content. */
    getNodeAtVersion(nodeId, version) {
        const history = this.versionHistory.get(nodeId);
        if (!history)
            return undefined;
        return history.find(h => h.version === version);
    }
    // --------------------------------------------------------------------------
    // Convenience Node Factory
    // --------------------------------------------------------------------------
    /** Create and add a node with minimal boilerplate. Auto-tags with pass/type/agent. */
    createNode(params) {
        // Auto-generate tags from pass, type, agent, and status
        const status = params.status ?? 'complete';
        const autoTags = [
            `pass:${params.pass}`,
            `type:${params.type}`,
            `domain:${params.producedBy.domain}`,
            `agent:${params.producedBy.agent}`,
            `execution:${this.executionId}`,
            `status:${status}`,
        ];
        const userTags = params.tags ? [...params.tags] : [];
        // Merge without duplicates
        const allTags = [...new Set([...autoTags, ...userTags])];
        const node = {
            id: this.generateNodeId(),
            type: params.type,
            name: params.name,
            content: params.content,
            summary: params.summary,
            producedBy: params.producedBy,
            pass: params.pass,
            derivedFrom: params.derivedFrom ?? [],
            timestamp: new Date().toISOString(),
            confidence: params.confidence ?? 1.0,
            status: params.status ?? 'complete',
            tags: allTags,
            version: 1,
        };
        this.addNode(node);
        return node;
    }
    // --------------------------------------------------------------------------
    // Statistics (ADR-033 — graph metrics)
    // --------------------------------------------------------------------------
    /** Compute aggregate statistics for the graph. */
    getStatistics() {
        const nodesByPass = {};
        const nodesByType = {};
        const nodesByStatus = {};
        let totalConfidence = 0;
        let completeCount = 0;
        for (const node of this.nodes.values()) {
            nodesByPass[node.pass] = (nodesByPass[node.pass] ?? 0) + 1;
            nodesByType[node.type] = (nodesByType[node.type] ?? 0) + 1;
            nodesByStatus[node.status] = (nodesByStatus[node.status] ?? 0) + 1;
            totalConfidence += node.confidence;
            if (node.status === 'complete')
                completeCount++;
        }
        const nodeCount = this.nodes.size;
        return {
            executionId: this.executionId,
            lifecycle: this.lifecycle,
            nodeCount,
            edgeCount: this.edges.length,
            nodesByPass,
            nodesByType,
            nodesByStatus,
            averageConfidence: nodeCount > 0 ? totalConfidence / nodeCount : 0,
            completenessRatio: nodeCount > 0 ? completeCount / nodeCount : 0,
        };
    }
    // --------------------------------------------------------------------------
    // Serialization
    // --------------------------------------------------------------------------
    /** Serialize the graph to JSON (includes version history). */
    toJSON() {
        const historyObj = {};
        for (const [nodeId, entries] of this.versionHistory.entries()) {
            historyObj[nodeId] = entries;
        }
        return {
            executionId: this.executionId,
            lifecycle: this.lifecycle,
            nodes: Array.from(this.nodes.values()),
            edges: [...this.edges],
            versionHistory: historyObj,
            writeSequence: this.writeSequence,
        };
    }
    /** Persist the graph to the output directory. */
    persist() {
        const graphDir = path.join(this.outputDir, 'decision-graph');
        fs.mkdirSync(graphDir, { recursive: true });
        const filePath = path.join(graphDir, 'decision-graph.json');
        fs.writeFileSync(filePath, JSON.stringify(this.toJSON(), null, 2), 'utf-8');
        return filePath;
    }
    /** Load a previously persisted graph (rehydrates lifecycle, history, sequence). */
    static load(executionId, outputDir) {
        const filePath = path.join(outputDir, 'decision-graph', 'decision-graph.json');
        if (!fs.existsSync(filePath)) {
            return new DecisionGraph(executionId, outputDir);
        }
        const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
        const graph = new DecisionGraph(raw.executionId, outputDir);
        graph.lifecycle = raw.lifecycle ?? (raw.nodes.length > 0 ? 'accumulated' : 'created');
        graph.writeSequence = raw.writeSequence ?? raw.nodes.length;
        // Load nodes directly (skip validation for persisted data)
        for (const node of raw.nodes) {
            graph.nodes.set(node.id, node);
        }
        for (const edge of raw.edges) {
            graph.edges.push(edge);
        }
        // Load version history
        if (raw.versionHistory) {
            for (const [nodeId, entries] of Object.entries(raw.versionHistory)) {
                graph.versionHistory.set(nodeId, entries);
            }
        }
        else {
            // Reconstruct minimal history from current nodes
            for (const node of raw.nodes) {
                graph.versionHistory.set(node.id, [{
                        version: node.version,
                        timestamp: node.timestamp,
                        content: node.content,
                        summary: node.summary,
                        status: node.status,
                        confidence: node.confidence,
                    }]);
            }
        }
        return graph;
    }
    // --------------------------------------------------------------------------
    // Summary
    // --------------------------------------------------------------------------
    /** Generate a human-readable summary of the graph. */
    summarize() {
        const stats = this.getStatistics();
        const lines = [
            `DecisionGraph: ${this.executionId}`,
            `  Lifecycle: ${stats.lifecycle}`,
            `  Nodes: ${stats.nodeCount}`,
            `  Edges: ${stats.edgeCount}`,
            `  Avg Confidence: ${(stats.averageConfidence * 100).toFixed(0)}%`,
            `  Completeness: ${(stats.completenessRatio * 100).toFixed(0)}%`,
            '',
        ];
        lines.push('  By Pass:');
        for (const [pass, count] of Object.entries(stats.nodesByPass).sort(([a], [b]) => Number(a) - Number(b))) {
            lines.push(`    Pass ${pass}: ${count} nodes`);
        }
        lines.push('  By Type:');
        for (const [type, count] of Object.entries(stats.nodesByType).sort()) {
            lines.push(`    ${type}: ${count}`);
        }
        if (Object.keys(stats.nodesByStatus).length > 0) {
            lines.push('  By Status:');
            for (const [status, count] of Object.entries(stats.nodesByStatus).sort()) {
                lines.push(`    ${status}: ${count}`);
            }
        }
        return lines.join('\n');
    }
}
// ============================================================================
// Helpers
// ============================================================================
/** Tokenize a string for simple text similarity. */
function tokenize(text) {
    return new Set(text
        .toLowerCase()
        .replace(/[^a-z0-9\s]/g, ' ')
        .split(/\s+/)
        .filter(t => t.length > 2));
}
//# sourceMappingURL=decision-graph.js.map