// Extracted from src/db/repository.ts (mechanical split — bodies unchanged).
// provenance repository domain. Facade: src/db/repository.ts (Repository class).
import Database from 'better-sqlite3';
import { escapeLike } from './shared.js';

/** DB row shape for workspace_file_provenance. */
export interface WorkspaceFileProvenanceRow {
  space_id: string | null;
  workspace_path: string;
  rel_path: string;
  created_by_task_id: number | null;
  created_by_job_id: string | null;
  created_by_piece: string | null;
  created_by_movement: string | null;
  source_kind: string;
  first_seen_at: string | null;
  last_modified_by_task_id: number | null;
  last_modified_by_job_id: string | null;
  last_modified_at: string | null;
  checksum: string | null;
  note: string | null;
}


export function rowToFileProvenance(
  row: WorkspaceFileProvenanceRow,
): import('../../engine/tools/core.js').FileProvenanceRecord {
  return {
    relPath: row.rel_path,
    sourceKind: (row.source_kind as import('../../engine/tools/core.js').FileProvenanceSourceKind) ?? 'unknown',
    createdByTaskId: row.created_by_task_id ?? null,
    createdByJobId: row.created_by_job_id ?? null,
    createdByPiece: row.created_by_piece ?? null,
    createdByMovement: row.created_by_movement ?? null,
    firstSeenAt: row.first_seen_at ?? null,
    lastModifiedByTaskId: row.last_modified_by_task_id ?? null,
    lastModifiedByJobId: row.last_modified_by_job_id ?? null,
    lastModifiedAt: row.last_modified_at ?? null,
    checksum: row.checksum ?? null,
    note: row.note ?? null,
  };
}


  // ── Workspace file provenance ────────────────────────────────────────────
  // Every read/write is scoped by workspace_path (the shared files-tree root),
  // which is the isolation boundary: a query for workspace A can never surface
  // workspace B's rows (and thus never another user's task lineage in a shared
  // space). schema.sql / migrate.ts / initSchema keep the table in triple sync.

  /**
   * Upsert a provenance row for one workspace file.
   *  - event 'create' with no existing row → inserts the creator identity.
   *  - existing row (any event) → updates only the last-modified fields, so the
   *    original creator + source_kind are preserved (Edit / overwrite semantics).
   *  - event 'modify' with no existing row → backfills a row for a file that
   *    pre-dates the ledger (source_kind=imported_existing, creator NULL).
   */
  export function recordProvenance(db: Database.Database, params: {
    workspacePath: string;
    relPath: string;
    event: 'create' | 'modify';
    sourceKind?: import('../../engine/tools/core.js').FileProvenanceSourceKind;
    spaceId?: string | null;
    taskId?: number | null;
    jobId?: string | null;
    piece?: string | null;
    movement?: string | null;
    checksum?: string | null;
    note?: string | null;
  }): void {
    const now = new Date().toISOString();
    const taskId = params.taskId ?? null;
    const jobId = params.jobId ?? null;
    const checksum = params.checksum ?? null;
    const existing = db
      .prepare('SELECT id FROM workspace_file_provenance WHERE workspace_path = ? AND rel_path = ?')
      .get(params.workspacePath, params.relPath) as { id: number } | undefined;

    if (existing) {
      // Preserve creator + source_kind; only advance the last-modified pointer.
      db
        .prepare(
          `UPDATE workspace_file_provenance
             SET last_modified_by_task_id = ?, last_modified_by_job_id = ?,
                 last_modified_at = ?, checksum = COALESCE(?, checksum)
           WHERE id = ?`,
        )
        .run(taskId, jobId, now, checksum, existing.id);
      return;
    }

    // No row yet. A create event knows the creator; a modify event means the
    // file pre-dates the ledger → backfill with an unknown creator.
    const sourceKind =
      params.event === 'create' ? params.sourceKind ?? 'unknown' : 'imported_existing';
    const createdTask = params.event === 'create' ? taskId : null;
    const createdJob = params.event === 'create' ? jobId : null;
    const createdPiece = params.event === 'create' ? params.piece ?? null : null;
    const createdMovement = params.event === 'create' ? params.movement ?? null : null;
    db
      .prepare(
        `INSERT INTO workspace_file_provenance
           (space_id, workspace_path, rel_path, created_by_task_id, created_by_job_id,
            created_by_piece, created_by_movement, source_kind, first_seen_at,
            last_modified_by_task_id, last_modified_by_job_id, last_modified_at, checksum, note)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      )
      .run(
        params.spaceId ?? null,
        params.workspacePath,
        params.relPath,
        createdTask,
        createdJob,
        createdPiece,
        createdMovement,
        sourceKind,
        now,
        taskId,
        jobId,
        now,
        checksum,
        params.note ?? null,
      );
  }


  export function getProvenance(db: Database.Database, workspacePath: string, relPath: string): import('../../engine/tools/core.js').FileProvenanceRecord | null {
    const row = db
      .prepare('SELECT * FROM workspace_file_provenance WHERE workspace_path = ? AND rel_path = ?')
      .get(workspacePath, relPath) as WorkspaceFileProvenanceRow | undefined;
    return row ? rowToFileProvenance(row) : null;
  }


  export function listProvenance(db: Database.Database, workspacePath: string, filters?: import('../../engine/tools/core.js').FileProvenanceListFilters): import('../../engine/tools/core.js').FileProvenanceRecord[] {
    const clauses = ['workspace_path = ?'];
    const args: unknown[] = [workspacePath];
    if (filters?.pathPrefix) {
      clauses.push('rel_path LIKE ? ESCAPE \'\\\'');
      args.push(escapeLike(filters.pathPrefix) + '%');
    }
    if (filters?.sourceKind) {
      clauses.push('source_kind = ?');
      args.push(filters.sourceKind);
    }
    if (typeof filters?.createdByTaskId === 'number') {
      clauses.push('created_by_task_id = ?');
      args.push(filters.createdByTaskId);
    }
    if (typeof filters?.lastModifiedByTaskId === 'number') {
      clauses.push('last_modified_by_task_id = ?');
      args.push(filters.lastModifiedByTaskId);
    }
    if (filters?.includeUnknown === false) {
      clauses.push("source_kind NOT IN ('unknown', 'imported_existing')");
    }
    // Cap hard at 500 rows regardless of caller input (bounded output).
    const limit = Math.min(Math.max(filters?.limit ?? 200, 1), 500);
    const rows = db
      .prepare(
        `SELECT * FROM workspace_file_provenance
           WHERE ${clauses.join(' AND ')}
           ORDER BY rel_path ASC
           LIMIT ?`,
      )
      .all(...args, limit) as WorkspaceFileProvenanceRow[];
    return rows.map(rowToFileProvenance);
  }


  /** Construct a workspace-bound read IO for GetFileProvenance / ListWorkspaceFiles. */
  export function makeFileProvenanceIO(db: Database.Database, workspacePath: string): import('../../engine/tools/core.js').FileProvenanceIO {
    return {
      get: (relPath) => getProvenance(db, workspacePath, relPath),
      list: (filters) => listProvenance(db, workspacePath, filters),
    };
  }
