// Template + Record CRUD. A "record" = multiple entries composed via a template's slots.
// Base, D1 only. (Ported clean from KBDB; no vectorize/triplet imports.)
import type { Template } from '../types';
import { createEntry } from './entry-crud';

function uid(prefix: string): string {
  return `${prefix}_${crypto.randomUUID()}`;
}

// ---- Templates ----

export interface CreateTemplateInput {
  name: string;
  description?: string | null;
  slots: string[];
  created_by?: string | null;
  id?: string;
}

export async function createTemplate(db: D1Database, input: CreateTemplateInput): Promise<Template> {
  const id = input.id ?? uid('tpl');
  await db
    .prepare(`INSERT INTO templates (id, name, description, slots_json, created_by) VALUES (?, ?, ?, ?, ?)`)
    .bind(id, input.name, input.description ?? null, JSON.stringify(input.slots), input.created_by ?? null)
    .run();
  const row = await getTemplate(db, id);
  if (!row) throw new Error('createTemplate: row not found after insert');
  return row;
}

export async function getTemplate(db: D1Database, idOrName: string): Promise<Template | null> {
  const row = await db
    .prepare('SELECT * FROM templates WHERE id = ? OR name = ? LIMIT 1')
    .bind(idOrName, idOrName)
    .first<Template>();
  return row ?? null;
}

export async function listTemplates(db: D1Database): Promise<Template[]> {
  const res = await db.prepare('SELECT * FROM templates ORDER BY created_at DESC').all<Template>();
  return res.results ?? [];
}

export async function updateTemplate(db: D1Database, id: string, patch: { description?: string | null; slots?: string[] }): Promise<Template | null> {
  const cols: string[] = [];
  const params: unknown[] = [];
  if (patch.description !== undefined) { cols.push('description = ?'); params.push(patch.description); }
  if (patch.slots !== undefined) { cols.push('slots_json = ?'); params.push(JSON.stringify(patch.slots)); }
  if (cols.length === 0) return getTemplate(db, id);
  cols.push('updated_at = unixepoch()');
  await db.prepare(`UPDATE templates SET ${cols.join(', ')} WHERE id = ?`).bind(...params, id).run();
  return getTemplate(db, id);
}

// ---- Records (entry_values composed by template) ----

export interface CreateRecordInput {
  template: string;          // template id or name
  values: Record<string, string>; // slot_name -> content
  owner_id?: string | null;
  record_id?: string;
}

export interface RecordResult {
  record_id: string;
  template_id: string;
  values: Record<string, string>;
}

export async function createRecord(db: D1Database, input: CreateRecordInput): Promise<RecordResult> {
  const tpl = await getTemplate(db, input.template);
  if (!tpl) throw new Error(`template not found: ${input.template}`);
  const slots: string[] = JSON.parse(tpl.slots_json);
  const recordId = input.record_id ?? uid('rec');

  for (const slot of slots) {
    if (!(slot in input.values)) continue;
    const entry = await createEntry(db, {
      content: input.values[slot],
      entry_type: 'value',
      owner_id: input.owner_id ?? null,
    });
    await db
      .prepare(`INSERT INTO entry_values (id, record_id, template_id, slot_name, entry_id) VALUES (?, ?, ?, ?, ?)`)
      .bind(uid('ev'), recordId, tpl.id, slot, entry.id)
      .run();
  }
  return { record_id: recordId, template_id: tpl.id, values: input.values };
}

// Update an existing record's slot values (mira-dissolve T2.1, issue #6).
// "Deprecate by flipping a slot value" — base append-only is NOT broken: we change the
// underlying entries.content of the slot's entry, we do not alter table structure / add columns / delete rows.
//   - slot already on the record → UPDATE the linked entries.content.
//   - slot valid for the record's template but not yet present → create entry + entry_value (idempotent grow).
//   - slot not in the template's slots_json → reject (records must stay template-shaped).
// Returns null if the record does not exist.
export async function updateRecord(
  db: D1Database,
  recordId: string,
  values: Record<string, string>,
): Promise<RecordResult | null> {
  // Existing slot → entry_id + template_id for this record.
  // JOIN entries 帶回 owner_id：grow 路徑建新 entry 時要沿用 record 既有 owner_id
  //（portal-auth design §2.2 附帶修復——原本漏帶 → 孤兒 entry（owner_id=NULL），
  //  owner-scoped 查詢（searchByTemplate / searchEntries）看不到該 slot 值）。
  const evRes = await db
    .prepare(
      `SELECT ev.slot_name AS slot_name, ev.entry_id AS entry_id, ev.template_id AS template_id, e.owner_id AS owner_id
       FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
       WHERE ev.record_id = ?`,
    )
    .bind(recordId)
    .all<{ slot_name: string; entry_id: string; template_id: string; owner_id: string | null }>();
  const evRows = evRes.results ?? [];
  if (evRows.length === 0) return null; // record does not exist

  const templateId = evRows[0].template_id;
  // record 的歸屬＝其既有 slot entries 的 owner_id（createRecord 寫入時同一值）。
  const recordOwnerId = evRows.find((r) => r.owner_id != null)?.owner_id ?? null;
  const slotToEntry = new Map(evRows.map((r) => [r.slot_name, r.entry_id]));

  const tpl = await getTemplate(db, templateId);
  const allowed: string[] = tpl ? JSON.parse(tpl.slots_json) : [...slotToEntry.keys()];

  for (const [slot, content] of Object.entries(values)) {
    if (!allowed.includes(slot)) {
      throw new Error(`slot not in template: ${slot}`);
    }
    const entryId = slotToEntry.get(slot);
    if (entryId) {
      // flip the slot value: update the linked entry's content (table structure untouched)
      await db.prepare(`UPDATE entries SET content = ?, updated_at = unixepoch() WHERE id = ?`).bind(content, entryId).run();
    } else {
      // valid template slot not yet on this record → grow it (create entry + link)
      // owner_id 帶 record 既有歸屬（design §2.2 附帶修復，防孤兒 entry）
      const entry = await createEntry(db, { content, entry_type: 'value', owner_id: recordOwnerId });
      await db
        .prepare(`INSERT INTO entry_values (id, record_id, template_id, slot_name, entry_id) VALUES (?, ?, ?, ?, ?)`)
        .bind(uid('ev'), recordId, templateId, slot, entry.id)
        .run();
    }
  }
  return getRecord(db, recordId);
}

export async function getRecord(db: D1Database, recordId: string): Promise<RecordResult | null> {
  const res = await db
    .prepare(
      `SELECT ev.slot_name as slot, e.content as content, ev.template_id as template_id
       FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
       WHERE ev.record_id = ?`,
    )
    .bind(recordId)
    .all<{ slot: string; content: string; template_id: string }>();
  const rows = res.results ?? [];
  if (rows.length === 0) return null;
  const values: Record<string, string> = {};
  for (const r of rows) values[r.slot] = r.content;
  return { record_id: recordId, template_id: rows[0].template_id, values };
}

export async function searchByTemplate(db: D1Database, template: string, owner_id?: string, limit = 100): Promise<RecordResult[]> {
  const tpl = await getTemplate(db, template);
  if (!tpl) return [];
  // owner_id 過濾在 SQL 做：record 的歸屬存在底層 entries.owner_id（createRecord 寫入時帶）。
  // 給了 owner_id → JOIN entries 限定該 owner（租戶隔離，cypher proxy 強制注入）；
  // 沒給 → 不限（內部/全域查詢）。先前 `|| true` 是 stub，會洩漏跨租戶資料（2026-06-14 修）。
  const cap = Math.min(limit, 500);
  const res = owner_id
    ? await db
        .prepare(
          `SELECT DISTINCT ev.record_id as record_id FROM entry_values ev
           JOIN entries e ON ev.entry_id = e.id
           WHERE ev.template_id = ? AND e.owner_id = ?
           ORDER BY ev.created_at DESC LIMIT ?`,
        )
        .bind(tpl.id, owner_id, cap)
        .all<{ record_id: string }>()
    : await db
        .prepare(`SELECT DISTINCT record_id FROM entry_values WHERE template_id = ? ORDER BY created_at DESC LIMIT ?`)
        .bind(tpl.id, cap)
        .all<{ record_id: string }>();
  const out: RecordResult[] = [];
  for (const { record_id } of res.results ?? []) {
    const rec = await getRecord(db, record_id);
    if (rec) out.push(rec);
  }
  return out;
}
