import { join } from 'node:path';
export function resolveKnowledgeDbPathFromControlPlaneDir(controlPlaneDir) {
    return join(controlPlaneDir, 'knowledge.sqlite');
}
export function nowMs() {
    return Date.now();
}
export function stableText(value) {
    if (typeof value !== 'string')
        return null;
    const trimmed = value.trim();
    return trimmed.length > 0 ? trimmed : null;
}
export function uniq(values) {
    const seen = new Set();
    const result = [];
    for (const value of values ?? []) {
        const trimmed = value.trim();
        if (!trimmed)
            continue;
        if (seen.has(trimmed))
            continue;
        seen.add(trimmed);
        result.push(trimmed);
    }
    return result;
}
export function parseJsonValue(value, fallback) {
    if (typeof value !== 'string')
        return fallback;
    try {
        return JSON.parse(value);
    }
    catch {
        return fallback;
    }
}
export function createSchema(db) {
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_sources (
      id TEXT PRIMARY KEY,
      connector_id TEXT NOT NULL,
      source_type TEXT NOT NULL,
      title TEXT,
      source_uri TEXT,
      canonical_uri TEXT,
      summary TEXT,
      description TEXT,
      tags TEXT NOT NULL DEFAULT '[]',
      folder_path TEXT,
      status TEXT NOT NULL,
      artifact_id TEXT,
      content_hash TEXT,
      last_crawled_at INTEGER,
      crawl_error TEXT,
      session_id TEXT,
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_sources_canonical_uri ON knowledge_sources(canonical_uri)`);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_sources_updated_at ON knowledge_sources(updated_at)`);
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_nodes (
      id TEXT PRIMARY KEY,
      kind TEXT NOT NULL,
      slug TEXT NOT NULL,
      title TEXT NOT NULL,
      summary TEXT,
      aliases TEXT NOT NULL DEFAULT '[]',
      status TEXT NOT NULL,
      confidence INTEGER NOT NULL,
      source_id TEXT,
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_nodes_kind_slug ON knowledge_nodes(kind, slug)`);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_updated_at ON knowledge_nodes(updated_at)`);
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_edges (
      id TEXT PRIMARY KEY,
      from_kind TEXT NOT NULL,
      from_id TEXT NOT NULL,
      to_kind TEXT NOT NULL,
      to_id TEXT NOT NULL,
      relation TEXT NOT NULL,
      weight REAL NOT NULL,
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_edges_unique ON knowledge_edges(from_kind, from_id, to_kind, to_id, relation)`);
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_issues (
      id TEXT PRIMARY KEY,
      severity TEXT NOT NULL,
      code TEXT NOT NULL,
      message TEXT NOT NULL,
      status TEXT NOT NULL,
      source_id TEXT,
      node_id TEXT,
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_issues_code ON knowledge_issues(code)`);
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_extractions (
      id TEXT PRIMARY KEY,
      source_id TEXT NOT NULL,
      artifact_id TEXT,
      extractor_id TEXT NOT NULL,
      format TEXT NOT NULL,
      title TEXT,
      summary TEXT,
      excerpt TEXT,
      sections TEXT NOT NULL DEFAULT '[]',
      links TEXT NOT NULL DEFAULT '[]',
      estimated_tokens INTEGER NOT NULL DEFAULT 0,
      structure TEXT NOT NULL DEFAULT '{}',
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_extractions_source_id ON knowledge_extractions(source_id)`);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_extractions_format ON knowledge_extractions(format)`);
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_job_runs (
      id TEXT PRIMARY KEY,
      job_id TEXT NOT NULL,
      status TEXT NOT NULL,
      mode TEXT NOT NULL,
      requested_at INTEGER NOT NULL,
      started_at INTEGER,
      completed_at INTEGER,
      error TEXT,
      result TEXT NOT NULL DEFAULT '{}',
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_job_runs_job_id ON knowledge_job_runs(job_id)`);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_job_runs_requested_at ON knowledge_job_runs(requested_at)`);
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_usage_records (
      id TEXT PRIMARY KEY,
      target_kind TEXT NOT NULL,
      target_id TEXT NOT NULL,
      usage_kind TEXT NOT NULL,
      task TEXT,
      session_id TEXT,
      score REAL,
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_usage_target ON knowledge_usage_records(target_kind, target_id)`);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_usage_created_at ON knowledge_usage_records(created_at)`);
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_consolidation_candidates (
      id TEXT PRIMARY KEY,
      candidate_type TEXT NOT NULL,
      status TEXT NOT NULL,
      subject_kind TEXT NOT NULL,
      subject_id TEXT NOT NULL,
      title TEXT NOT NULL,
      summary TEXT,
      score REAL NOT NULL,
      evidence TEXT NOT NULL DEFAULT '[]',
      suggested_memory_class TEXT,
      suggested_scope TEXT,
      decided_at INTEGER,
      decided_by TEXT,
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_candidates_subject ON knowledge_consolidation_candidates(subject_kind, subject_id)`);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_candidates_status ON knowledge_consolidation_candidates(status)`);
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_consolidation_reports (
      id TEXT PRIMARY KEY,
      kind TEXT NOT NULL,
      title TEXT NOT NULL,
      summary TEXT NOT NULL,
      highlights TEXT NOT NULL DEFAULT '[]',
      metrics TEXT NOT NULL DEFAULT '{}',
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_reports_kind ON knowledge_consolidation_reports(kind)`);
    db.run(`
    CREATE TABLE IF NOT EXISTS knowledge_schedules (
      id TEXT PRIMARY KEY,
      job_id TEXT NOT NULL,
      label TEXT NOT NULL,
      enabled INTEGER NOT NULL,
      schedule TEXT NOT NULL,
      last_run_at INTEGER,
      next_run_at INTEGER,
      metadata TEXT NOT NULL DEFAULT '{}',
      created_at INTEGER NOT NULL,
      updated_at INTEGER NOT NULL
    )
  `);
    db.run(`CREATE INDEX IF NOT EXISTS idx_knowledge_schedules_job_id ON knowledge_schedules(job_id)`);
}
export function getKnowledgeSchemaStatements() {
    const statements = [];
    createSchema({
        run(sql) {
            const normalized = sql.trim();
            if (normalized.length > 0)
                statements.push(normalized);
        },
    });
    return statements;
}
export function renderKnowledgeSchemaSql() {
    return `${getKnowledgeSchemaStatements()
        .map((statement) => statement.endsWith(';') ? statement : `${statement};`)
        .join('\n\n')}\n`;
}
function rowObject(columns, values) {
    return Object.fromEntries(columns.map((column, index) => [column, values[index]]));
}
export function mapSourceRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        connectorId: String(row.connector_id),
        sourceType: String(row.source_type),
        ...(stableText(row.title) ? { title: String(row.title) } : {}),
        ...(stableText(row.source_uri) ? { sourceUri: String(row.source_uri) } : {}),
        ...(stableText(row.canonical_uri) ? { canonicalUri: String(row.canonical_uri) } : {}),
        ...(stableText(row.summary) ? { summary: String(row.summary) } : {}),
        ...(stableText(row.description) ? { description: String(row.description) } : {}),
        tags: parseJsonValue(row.tags, []),
        ...(stableText(row.folder_path) ? { folderPath: String(row.folder_path) } : {}),
        status: String(row.status),
        ...(stableText(row.artifact_id) ? { artifactId: String(row.artifact_id) } : {}),
        ...(stableText(row.content_hash) ? { contentHash: String(row.content_hash) } : {}),
        ...(typeof row.last_crawled_at === 'number' ? { lastCrawledAt: Number(row.last_crawled_at) } : {}),
        ...(stableText(row.crawl_error) ? { crawlError: String(row.crawl_error) } : {}),
        ...(stableText(row.session_id) ? { sessionId: String(row.session_id) } : {}),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
        updatedAt: Number(row.updated_at),
    };
}
export function mapNodeRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        kind: String(row.kind),
        slug: String(row.slug),
        title: String(row.title),
        ...(stableText(row.summary) ? { summary: String(row.summary) } : {}),
        aliases: parseJsonValue(row.aliases, []),
        status: String(row.status),
        confidence: Number(row.confidence),
        ...(stableText(row.source_id) ? { sourceId: String(row.source_id) } : {}),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
        updatedAt: Number(row.updated_at),
    };
}
export function mapEdgeRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        fromKind: String(row.from_kind),
        fromId: String(row.from_id),
        toKind: String(row.to_kind),
        toId: String(row.to_id),
        relation: String(row.relation),
        weight: Number(row.weight),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
        updatedAt: Number(row.updated_at),
    };
}
export function mapIssueRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        severity: String(row.severity),
        code: String(row.code),
        message: String(row.message),
        status: String(row.status),
        ...(stableText(row.source_id) ? { sourceId: String(row.source_id) } : {}),
        ...(stableText(row.node_id) ? { nodeId: String(row.node_id) } : {}),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
        updatedAt: Number(row.updated_at),
    };
}
export function mapExtractionRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        sourceId: String(row.source_id),
        ...(stableText(row.artifact_id) ? { artifactId: String(row.artifact_id) } : {}),
        extractorId: String(row.extractor_id),
        format: String(row.format),
        ...(stableText(row.title) ? { title: String(row.title) } : {}),
        ...(stableText(row.summary) ? { summary: String(row.summary) } : {}),
        ...(stableText(row.excerpt) ? { excerpt: String(row.excerpt) } : {}),
        sections: parseJsonValue(row.sections, []),
        links: parseJsonValue(row.links, []),
        estimatedTokens: Number(row.estimated_tokens),
        structure: parseJsonValue(row.structure, {}),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
        updatedAt: Number(row.updated_at),
    };
}
export function mapJobRunRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        jobId: String(row.job_id),
        status: String(row.status),
        mode: String(row.mode),
        requestedAt: Number(row.requested_at),
        ...(typeof row.started_at === 'number' ? { startedAt: Number(row.started_at) } : {}),
        ...(typeof row.completed_at === 'number' ? { completedAt: Number(row.completed_at) } : {}),
        ...(stableText(row.error) ? { error: String(row.error) } : {}),
        result: parseJsonValue(row.result, {}),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
        updatedAt: Number(row.updated_at),
    };
}
export function mapUsageRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        targetKind: String(row.target_kind),
        targetId: String(row.target_id),
        usageKind: String(row.usage_kind),
        ...(stableText(row.task) ? { task: String(row.task) } : {}),
        ...(stableText(row.session_id) ? { sessionId: String(row.session_id) } : {}),
        ...(typeof row.score === 'number' ? { score: Number(row.score) } : {}),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
    };
}
export function mapCandidateRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        candidateType: String(row.candidate_type),
        status: String(row.status),
        subjectKind: String(row.subject_kind),
        subjectId: String(row.subject_id),
        title: String(row.title),
        ...(stableText(row.summary) ? { summary: String(row.summary) } : {}),
        score: Number(row.score),
        evidence: parseJsonValue(row.evidence, []),
        ...(stableText(row.suggested_memory_class) ? { suggestedMemoryClass: String(row.suggested_memory_class) } : {}),
        ...(stableText(row.suggested_scope) ? { suggestedScope: String(row.suggested_scope) } : {}),
        ...(typeof row.decided_at === 'number' ? { decidedAt: Number(row.decided_at) } : {}),
        ...(stableText(row.decided_by) ? { decidedBy: String(row.decided_by) } : {}),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
        updatedAt: Number(row.updated_at),
    };
}
export function mapReportRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        kind: String(row.kind),
        title: String(row.title),
        summary: String(row.summary),
        highlights: parseJsonValue(row.highlights, []),
        metrics: parseJsonValue(row.metrics, {}),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
        updatedAt: Number(row.updated_at),
    };
}
export function mapScheduleRow(columns, values) {
    const row = rowObject(columns, values);
    return {
        id: String(row.id),
        jobId: String(row.job_id),
        label: String(row.label),
        enabled: Boolean(row.enabled),
        schedule: parseJsonValue(row.schedule, { kind: 'at', at: 0 }),
        ...(typeof row.last_run_at === 'number' ? { lastRunAt: Number(row.last_run_at) } : {}),
        ...(typeof row.next_run_at === 'number' ? { nextRunAt: Number(row.next_run_at) } : {}),
        metadata: parseJsonValue(row.metadata, {}),
        createdAt: Number(row.created_at),
        updatedAt: Number(row.updated_at),
    };
}
