// Prepared SQL for the memories table. Every parameter binds positionally; no template
// concatenation with caller data so SQL injection is structurally impossible at this layer.
// The validator gate sits in vault.ts, BEFORE these functions are called, so this module assumes
// inputs are already shape-valid and only owes the SQL.
import { memoryRecordToRow, rowToMemoryRecord } from "./serialize.js";
import { scopeCoordinateOf, scopeKindOf } from "./scope-key.js";
import { MemoryStorageError } from "./errors.js";
const INSERT_SQL = `
INSERT INTO memories (
  id, schema_version, type, scope_kind, scope_coordinate, body, payload_json,
  status, sensitivity, pinned, confidence, valid_from, valid_until, stale_reason,
  tags_json, source_kind, source_conversation_id, source_workflow_run_id,
  source_evidence_manifest_id, captured_at, capture_rationale, model_provider,
  model_id, model_revision, retention_policy_key, retention_retain_until,
  retention_notes, created_at, updated_at
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`;
const SELECT_BY_ID_SQL = "SELECT * FROM memories WHERE id = ?";
const DELETE_SQL = "DELETE FROM memories WHERE id = ?";
// UPDATE rewrites every column from the resolved record so a partial patch can land without
// touching SQL per-field. The vault composes the merge in TypeScript (validator-safe) and hands
// us a final record; we just write it.
const UPDATE_SQL = `
UPDATE memories SET
  type = ?,
  body = ?,
  payload_json = ?,
  status = ?,
  sensitivity = ?,
  pinned = ?,
  confidence = ?,
  valid_from = ?,
  valid_until = ?,
  stale_reason = ?,
  tags_json = ?,
  source_kind = ?,
  source_conversation_id = ?,
  source_workflow_run_id = ?,
  source_evidence_manifest_id = ?,
  captured_at = ?,
  capture_rationale = ?,
  model_provider = ?,
  model_id = ?,
  model_revision = ?,
  retention_policy_key = ?,
  retention_retain_until = ?,
  retention_notes = ?,
  updated_at = ?
WHERE id = ?
`;
function bindValues(record, cipher) {
    const r = memoryRecordToRow(record, cipher);
    return [
        r.id,
        r.schema_version,
        r.type,
        r.scope_kind,
        r.scope_coordinate,
        r.body,
        r.payload_json,
        r.status,
        r.sensitivity,
        r.pinned,
        r.confidence,
        r.valid_from,
        r.valid_until,
        r.stale_reason,
        r.tags_json,
        r.source_kind,
        r.source_conversation_id,
        r.source_workflow_run_id,
        r.source_evidence_manifest_id,
        r.captured_at,
        r.capture_rationale,
        r.model_provider,
        r.model_id,
        r.model_revision,
        r.retention_policy_key,
        r.retention_retain_until,
        r.retention_notes,
        r.created_at,
        r.updated_at,
    ];
}
export function insertMemoryRow(db, record, cipher) {
    db.prepare(INSERT_SQL).run(...bindValues(record, cipher));
}
export function getMemoryRow(db, id, cipher) {
    const row = db.prepare(SELECT_BY_ID_SQL).get(id);
    return row === undefined ? undefined : rowToMemoryRecord(row, cipher);
}
export function updateMemoryRow(db, record, cipher) {
    const r = memoryRecordToRow(record, cipher);
    const info = db
        .prepare(UPDATE_SQL)
        .run(r.type, r.body, r.payload_json, r.status, r.sensitivity, r.pinned, r.confidence, r.valid_from, r.valid_until, r.stale_reason, r.tags_json, r.source_kind, r.source_conversation_id, r.source_workflow_run_id, r.source_evidence_manifest_id, r.captured_at, r.capture_rationale, r.model_provider, r.model_id, r.model_revision, r.retention_policy_key, r.retention_retain_until, r.retention_notes, r.updated_at, r.id);
    if (info.changes === 0) {
        throw new MemoryStorageError("not-found", "Memory not found.");
    }
}
export function deleteMemoryRow(db, id) {
    const info = db.prepare(DELETE_SQL).run(id);
    return info.changes > 0;
}
function buildEnumClause(column, values) {
    if (values === undefined || values.length === 0)
        return undefined;
    const placeholders = values.map(() => "?").join(",");
    return { clauses: [`${column} IN (${placeholders})`], params: values };
}
function buildExpiryClause(includeExpired, nowMs) {
    if (includeExpired === true)
        return undefined;
    return { clauses: ["(valid_until IS NULL OR valid_until > ?)"], params: [nowMs] };
}
function buildPinnedClause(pinned) {
    if (pinned === undefined)
        return undefined;
    return { clauses: ["pinned = ?"], params: [pinned ? 1 : 0] };
}
const ORDER_COLUMN_MAP = {
    createdAt: "created_at",
    updatedAt: "updated_at",
    validFrom: "valid_from",
};
function resolveOrderBy(options) {
    const column = ORDER_COLUMN_MAP[options.orderBy ?? "createdAt"];
    const dir = options.orderDir === "asc" ? "ASC" : "DESC";
    return { column, dir };
}
function buildListSql(where, options) {
    const { column, dir } = resolveOrderBy(options);
    let sql = "SELECT * FROM memories";
    if (where.length > 0) {
        sql += ` WHERE ${where.join(" AND ")}`;
    }
    sql += ` ORDER BY ${column} ${dir}`;
    if (typeof options.limit === "number") {
        sql += " LIMIT ?";
        if (typeof options.offset === "number") {
            sql += " OFFSET ?";
        }
    }
    return sql;
}
function appendPagingParams(params, options) {
    if (typeof options.limit !== "number")
        return;
    params.push(options.limit);
    if (typeof options.offset === "number") {
        params.push(options.offset);
    }
}
export function listMemoriesRows(db, options, nowMs, cipher) {
    const params = [];
    const where = [];
    for (const built of [
        buildEnumClause("type", options.type),
        buildEnumClause("status", options.status),
        buildPinnedClause(options.pinned),
        buildExpiryClause(options.includeExpired, nowMs),
    ]) {
        if (built === undefined)
            continue;
        where.push(...built.clauses);
        params.push(...built.params);
    }
    appendPagingParams(params, options);
    const rows = db
        .prepare(buildListSql(where, options))
        .all(...params);
    return rows.map((row) => rowToMemoryRecord(row, cipher));
}
export function listMemoriesByScopeRows(db, scope, options, nowMs, cipher) {
    const kind = scopeKindOf(scope);
    const coordinate = scopeCoordinateOf(scope);
    const params = [kind, coordinate];
    const where = ["scope_kind = ?", "scope_coordinate = ?"];
    for (const built of [
        buildEnumClause("type", options.type),
        buildEnumClause("status", options.status),
        buildPinnedClause(options.pinned),
        buildExpiryClause(options.includeExpired, nowMs),
    ]) {
        if (built === undefined)
            continue;
        where.push(...built.clauses);
        params.push(...built.params);
    }
    appendPagingParams(params, options);
    const rows = db
        .prepare(buildListSql(where, options))
        .all(...params);
    return rows.map((row) => rowToMemoryRecord(row, cipher));
}
