// engine/steering-store.js — SQL-backed observable delivery state for
// inbox steering messages.
//
// One row per steering message in the steering_deliveries table.
// Mirrors the shape of engine/dispatch-store.js / engine/small-state-store.js:
//   - routes every read/write through getDb() (no JSON sidecar)
//   - emits emitStateEvent('steering', {agentId, id, status}) on every
//     status transition so the dashboard's MAX(events.id) cache check
//     fires and clients can refresh.
//
// Public API:
//   insert({ id, agentId, messageId, dispatchId?, status?, source?,
//            runtime?, payloadExcerpt?, createdAt? })
//   updateStatus(id, status, opts?)
//     opts: { lastError?, dispatchId?, runtime? }
//     Automatically stamps delivered_at on 'delivered',
//     acknowledged_at on 'acknowledged'.
//   listForAgent(agentId, { limit? = 50 })   // newest first
//   getById(id)
//
// Status enum: queued | live_kill | deferred | re_spawning |
//              delivered | acknowledged | stranded | dropped.

const VALID_STATUSES = new Set([
  'queued',
  'live_kill',
  'deferred',
  're_spawning',
  'delivered',
  'acknowledged',
  'stranded',
  'dropped',
]);

function _now() { return Date.now(); }

function _rowToRecord(row) {
  if (!row) return null;
  return {
    id: row.id,
    agentId: row.agent_id,
    messageId: row.message_id,
    dispatchId: row.dispatch_id || null,
    status: row.status,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
    deliveredAt: row.delivered_at,
    acknowledgedAt: row.acknowledged_at,
    lastError: row.last_error || null,
    payloadExcerpt: row.payload_excerpt || null,
    source: row.source || null,
    runtime: row.runtime || null,
  };
}

function _emitEvent(agentId, id, status) {
  try {
    const { emitStateEvent } = require('./db-events');
    emitStateEvent('steering', { agentId, id, status });
  } catch { /* best-effort */ }
}

/**
 * Insert a new delivery-state row. Idempotent on the (id, agentId,
 * status) tuple — re-inserting the same id is a no-op (returns the
 * existing record) so callers that race writeSteeringMessage from
 * different code paths don't double-emit events. New rows fire
 * emitStateEvent.
 */
function insert(rec) {
  if (!rec || typeof rec !== 'object') throw new Error('steering-store.insert: rec required');
  const id = String(rec.id || '').trim();
  const agentId = String(rec.agentId || '').trim();
  const messageId = String(rec.messageId || '').trim();
  if (!id) throw new Error('steering-store.insert: id required');
  if (!agentId) throw new Error('steering-store.insert: agentId required');
  if (!messageId) throw new Error('steering-store.insert: messageId required');
  const status = String(rec.status || 'queued');
  if (!VALID_STATUSES.has(status)) {
    throw new Error(`steering-store.insert: invalid status '${status}'`);
  }

  const { getDb } = require('./db');
  const db = getDb();
  const existing = db.prepare('SELECT * FROM steering_deliveries WHERE id = ?').get(id);
  if (existing) return _rowToRecord(existing);

  const now = _now();
  const createdAt = Number.isFinite(rec.createdAt) ? rec.createdAt : now;
  db.prepare(`
    INSERT INTO steering_deliveries
      (id, agent_id, message_id, dispatch_id, status, created_at, updated_at,
       delivered_at, acknowledged_at, last_error, payload_excerpt, source, runtime)
    VALUES (?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, ?, ?, ?)
  `).run(
    id,
    agentId,
    messageId,
    rec.dispatchId ? String(rec.dispatchId) : null,
    status,
    createdAt,
    now,
    rec.payloadExcerpt != null ? String(rec.payloadExcerpt).slice(0, 200) : null,
    rec.source ? String(rec.source) : null,
    rec.runtime ? String(rec.runtime) : null,
  );

  _emitEvent(agentId, id, status);
  return _rowToRecord(db.prepare('SELECT * FROM steering_deliveries WHERE id = ?').get(id));
}

/**
 * Transition a row to a new status. No-op (returns the current record
 * unchanged) when the status would not actually change — keeps the
 * event stream free of redundant rows. Always fires emitStateEvent on
 * a real transition. Optional opts: { lastError, dispatchId, runtime }.
 */
function updateStatus(id, status, opts = {}) {
  if (!id) throw new Error('steering-store.updateStatus: id required');
  if (!VALID_STATUSES.has(status)) {
    throw new Error(`steering-store.updateStatus: invalid status '${status}'`);
  }
  const { getDb } = require('./db');
  const db = getDb();
  const row = db.prepare('SELECT * FROM steering_deliveries WHERE id = ?').get(id);
  if (!row) return null;
  if (row.status === status && !opts.lastError && !opts.dispatchId && !opts.runtime) {
    return _rowToRecord(row);
  }

  const now = _now();
  const deliveredAt = status === 'delivered' && row.delivered_at == null ? now : row.delivered_at;
  const acknowledgedAt = status === 'acknowledged' && row.acknowledged_at == null ? now : row.acknowledged_at;
  const lastError = opts.lastError !== undefined ? (opts.lastError == null ? null : String(opts.lastError)) : row.last_error;
  const dispatchId = opts.dispatchId !== undefined ? (opts.dispatchId == null ? null : String(opts.dispatchId)) : row.dispatch_id;
  const runtime = opts.runtime !== undefined ? (opts.runtime == null ? null : String(opts.runtime)) : row.runtime;

  db.prepare(`
    UPDATE steering_deliveries SET
      status = ?,
      updated_at = ?,
      delivered_at = ?,
      acknowledged_at = ?,
      last_error = ?,
      dispatch_id = ?,
      runtime = ?
    WHERE id = ?
  `).run(status, now, deliveredAt, acknowledgedAt, lastError, dispatchId, runtime, id);

  _emitEvent(row.agent_id, id, status);
  return _rowToRecord(db.prepare('SELECT * FROM steering_deliveries WHERE id = ?').get(id));
}

function listForAgent(agentId, opts = {}) {
  if (!agentId) return [];
  const limit = Math.max(1, Math.min(500, Number(opts.limit) || 50));
  let db;
  try { const { getDb } = require('./db'); db = getDb(); }
  catch { return []; }
  const rows = db.prepare(`
    SELECT * FROM steering_deliveries
    WHERE agent_id = ?
    ORDER BY created_at DESC, rowid DESC
    LIMIT ?
  `).all(String(agentId), limit);
  return rows.map(_rowToRecord);
}

function getById(id) {
  if (!id) return null;
  let db;
  try { const { getDb } = require('./db'); db = getDb(); }
  catch { return null; }
  const row = db.prepare('SELECT * FROM steering_deliveries WHERE id = ?').get(String(id));
  return _rowToRecord(row);
}

module.exports = {
  VALID_STATUSES,
  insert,
  updateStatus,
  listForAgent,
  getById,
};
