const ALLOWED_KEYS = new Set([
  "selected_model_id", "fallback_order", "decode_interval_ms", "silence_finalize_ms",
  "rolling_context_ms", "caption_max_chars", "minimum_display_ms", "auto_start_stream",
  "offline_authorization_severity", "diagnostic_caption_text", "tracks"
]);

class RevisionStore {
  constructor(db, options = {}) { this.db = db; this.now = options.now || Date.now; this.migrate(); }
  migrate() {
    this.db.exec(`CREATE TABLE IF NOT EXISTS transcription_settings (
      key TEXT PRIMARY KEY, value_json TEXT NOT NULL, revision INTEGER NOT NULL,
      actor_id TEXT NOT NULL, updated_at INTEGER NOT NULL
    );`);
  }
  list() {
    return Object.fromEntries(this.db.prepare("SELECT * FROM transcription_settings ORDER BY key").all().map((row) => [row.key, decode(row)]));
  }
  apply(changes, actorId) {
    if (!Array.isArray(changes) || !changes.length || changes.length > 50) throw new Error("One to fifty field changes are required.");
    const normalized = changes.map(validateChange);
    const applied = [];
    const conflicts = [];
    this.db.transaction(() => {
      for (const change of normalized) {
        const current = this.db.prepare("SELECT * FROM transcription_settings WHERE key = ?").get(change.key);
        const currentRevision = current?.revision || 0;
        if (change.base_revision !== currentRevision) {
          conflicts.push({ key: change.key, local_value: change.value, local_base_revision: change.base_revision, server: current ? decode(current) : { value: null, revision: 0, actor_id: null, updated_at: null } });
          continue;
        }
        const revision = currentRevision + 1;
        const updatedAt = this.now();
        this.db.prepare("INSERT INTO transcription_settings (key, value_json, revision, actor_id, updated_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(key) DO UPDATE SET value_json = excluded.value_json, revision = excluded.revision, actor_id = excluded.actor_id, updated_at = excluded.updated_at")
          .run(change.key, JSON.stringify(change.value), revision, String(actorId || "unknown"), updatedAt);
        applied.push({ key: change.key, value: change.value, revision, actor_id: String(actorId || "unknown"), updated_at: updatedAt });
      }
    })();
    return { applied, conflicts, current: this.list() };
  }
}

function validateChange(change) {
  if (!change || !ALLOWED_KEYS.has(change.key)) throw new Error(`Setting key ${change?.key || "(missing)"} is not allowed.`);
  const baseRevision = Number(change.base_revision);
  if (!Number.isInteger(baseRevision) || baseRevision < 0) throw new Error("A non-negative base revision is required for every changed field.");
  const encoded = JSON.stringify(change.value);
  if (encoded == null || Buffer.byteLength(encoded) > 64 * 1024) throw new Error("Setting value is too large.");
  return { key: change.key, value: change.value, base_revision: baseRevision };
}
function decode(row) { return { value: JSON.parse(row.value_json), revision: row.revision, actor_id: row.actor_id, updated_at: row.updated_at }; }

module.exports = { RevisionStore, ALLOWED_KEYS };
