import { getDb, nowIso, newId } from './db.js';

const MAX_NAME = 32;
const MAX_SPACES = 32;

function rowToSpace(row) {
  if (!row) return null;
  return {
    id: String(row.id),
    name: String(row.name || ''),
    position: Number(row.position) || 0,
    is_active: row.is_active === 1,
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
}

function cleanName(input, fallback) {
  const trimmed = String(input || '').trim().slice(0, MAX_NAME);
  return trimmed || fallback;
}

export function listSpaces() {
  const db = getDb();
  ensureSeed();
  const rows = db.prepare('SELECT * FROM dashboard_spaces ORDER BY position ASC, created_at ASC').all();
  const spaces = rows.map(rowToSpace);
  const active = spaces.find((s) => s.is_active) || spaces[0] || null;
  return { spaces, active_space_id: active ? active.id : null };
}

export function getSpace(id) {
  if (!id) return null;
  const row = getDb().prepare('SELECT * FROM dashboard_spaces WHERE id = ?').get(String(id));
  return rowToSpace(row);
}

// Single source of truth for "which dashboard space does this artifact land in".
// Callers pass candidate space ids in precedence order (most specific first,
// e.g. [explicit_space_id, project.default_space_id, agent.default_space_id]).
// The first candidate that names an existing space wins; otherwise `fallback`.
// fallback 'default' guarantees a real space (no orphan rows); fallback null
// lets a caller distinguish "no real target resolved" from "default".
export function resolveTargetSpace(candidates = [], { fallback = 'default' } = {}) {
  const list = Array.isArray(candidates) ? candidates : [candidates];
  for (const candidate of list) {
    if (candidate && getSpace(candidate)) return candidate;
  }
  return fallback;
}

export function ensureSeed() {
  const db = getDb();
  const count = db.prepare('SELECT COUNT(*) AS n FROM dashboard_spaces').get().n;
  if (count === 0) {
    const now = nowIso();
    db.prepare(
      'INSERT INTO dashboard_spaces (id, name, position, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)'
    ).run('default', 'main', 0, 1, now, now);
  }
  // Backfill: every artifact must belong to a space so the dashboard can route
  // it without a client-side reconciler. Pre-existing NULL-space rows land in
  // 'default'. Idempotent — only touches NULL rows.
  db.prepare("UPDATE artifacts SET space_id = 'default' WHERE space_id IS NULL").run();
}

export function createSpace({ name } = {}) {
  const db = getDb();
  ensureSeed();
  const total = db.prepare('SELECT COUNT(*) AS n FROM dashboard_spaces').get().n;
  if (total >= MAX_SPACES) {
    const err = new Error(`Maximum of ${MAX_SPACES} spaces reached.`);
    err.code = 'SPACE_LIMIT';
    throw err;
  }
  const maxPos = db.prepare('SELECT MAX(position) AS m FROM dashboard_spaces').get().m;
  const position = (Number(maxPos) || 0) + 1;
  const id = newId('space');
  const now = nowIso();
  const finalName = cleanName(name, `Space ${total + 1}`);
  db.prepare(
    'INSERT INTO dashboard_spaces (id, name, position, is_active, created_at, updated_at) VALUES (?, ?, ?, 0, ?, ?)'
  ).run(id, finalName, position, now, now);
  return getSpace(id);
}

export function renameSpace(id, name) {
  const space = getSpace(id);
  if (!space) {
    const err = new Error('Space not found.');
    err.code = 'SPACE_NOT_FOUND';
    throw err;
  }
  const finalName = cleanName(name, space.name);
  if (finalName === space.name) return space;
  getDb()
    .prepare('UPDATE dashboard_spaces SET name = ?, updated_at = ? WHERE id = ?')
    .run(finalName, nowIso(), id);
  return getSpace(id);
}

export function setActiveSpace(id) {
  const space = getSpace(id);
  if (!space) {
    const err = new Error('Space not found.');
    err.code = 'SPACE_NOT_FOUND';
    throw err;
  }
  const db = getDb();
  const now = nowIso();
  const tx = db.prepare('BEGIN IMMEDIATE');
  try {
    tx.run();
    db.prepare('UPDATE dashboard_spaces SET is_active = 0, updated_at = ? WHERE is_active = 1').run(now);
    db.prepare('UPDATE dashboard_spaces SET is_active = 1, updated_at = ? WHERE id = ?').run(now, id);
    db.prepare('COMMIT').run();
  } catch (err) {
    try { db.prepare('ROLLBACK').run(); } catch {}
    throw err;
  }
  return getSpace(id);
}

export function deleteSpace(id) {
  const db = getDb();
  const space = getSpace(id);
  if (!space) return null;
  const total = db.prepare('SELECT COUNT(*) AS n FROM dashboard_spaces').get().n;
  if (total <= 1) {
    const err = new Error('Cannot delete the last remaining space.');
    err.code = 'SPACE_LAST';
    throw err;
  }
  const fallback = db
    .prepare('SELECT * FROM dashboard_spaces WHERE id != ? ORDER BY position ASC, created_at ASC LIMIT 1')
    .get(id);
  if (!fallback) {
    const err = new Error('No fallback space available.');
    err.code = 'SPACE_NO_FALLBACK';
    throw err;
  }
  const now = nowIso();
  const tx = db.prepare('BEGIN IMMEDIATE');
  try {
    tx.run();
    db.prepare('UPDATE artifacts SET space_id = ? WHERE space_id = ?').run(fallback.id, id);
    // Revoke any public share links for the deleted space (hygiene — the viewer
    // also 404s once the dashboard_spaces row is gone).
    db.prepare('UPDATE space_share_links SET revoked = 1 WHERE space_id = ? AND revoked = 0').run(id);
    db.prepare('DELETE FROM dashboard_spaces WHERE id = ?').run(id);
    if (space.is_active) {
      db.prepare('UPDATE dashboard_spaces SET is_active = 1, updated_at = ? WHERE id = ?').run(now, fallback.id);
    }
    db.prepare('COMMIT').run();
  } catch (err) {
    try { db.prepare('ROLLBACK').run(); } catch {}
    throw err;
  }
  return { deleted_id: id, moved_to: fallback.id };
}

export function moveArtifactToSpace(artifactId, spaceId) {
  if (!artifactId) {
    const err = new Error('artifact_id is required.');
    err.code = 'ARTIFACT_REQUIRED';
    throw err;
  }
  const space = getSpace(spaceId);
  if (!space) {
    const err = new Error('Space not found.');
    err.code = 'SPACE_NOT_FOUND';
    throw err;
  }
  const db = getDb();
  const artifact = db.prepare('SELECT id FROM artifacts WHERE id = ?').get(String(artifactId));
  if (!artifact) {
    const err = new Error('Artifact not found.');
    err.code = 'ARTIFACT_NOT_FOUND';
    throw err;
  }
  db.prepare("UPDATE artifacts SET space_id = ?, pinned_to_space_at = datetime('now') WHERE id = ?").run(space.id, artifact.id);
  return { artifact_id: artifact.id, space_id: space.id };
}

export function listSpacesWithArtifactIds() {
  const db = getDb();
  ensureSeed();
  const spaces = db.prepare('SELECT * FROM dashboard_spaces ORDER BY position ASC, created_at ASC').all().map(rowToSpace);
  const rows = db.prepare('SELECT id, space_id FROM artifacts WHERE space_id IS NOT NULL').all();
  const bySpace = new Map(spaces.map((s) => [s.id, []]));
  for (const row of rows) {
    if (bySpace.has(row.space_id)) bySpace.get(row.space_id).push(String(row.id));
  }
  const enriched = spaces.map((s) => ({ ...s, artifact_ids: bySpace.get(s.id) || [] }));
  const active = enriched.find((s) => s.is_active) || enriched[0] || null;
  return { spaces: enriched, active_space_id: active ? active.id : null };
}

// One-shot import from a client-side localStorage payload. Only runs when the
// table is empty (aside from the auto-seeded default). Returns the imported
// snapshot or null if the import was skipped.
export function importFromClient({ spaces = [], active_space_id = null } = {}) {
  if (!Array.isArray(spaces) || spaces.length === 0) return null;
  const db = getDb();
  ensureSeed();
  const currentCount = db.prepare('SELECT COUNT(*) AS n FROM dashboard_spaces').get().n;
  const onlyDefault =
    currentCount === 1 &&
    db.prepare("SELECT id FROM dashboard_spaces WHERE id = 'default'").get();
  if (!onlyDefault) return null;
  const importable = spaces
    .filter((s) => s && typeof s.id === 'string' && s.id.length > 0)
    .slice(0, MAX_SPACES);
  if (importable.length === 0) return null;
  const now = nowIso();
  const tx = db.prepare('BEGIN IMMEDIATE');
  try {
    tx.run();
    db.prepare('DELETE FROM dashboard_spaces').run();
    importable.forEach((s, idx) => {
      const id = String(s.id);
      const name = cleanName(s.name, `Space ${idx + 1}`);
      const isActive = active_space_id ? id === String(active_space_id) : idx === 0;
      db.prepare(
        'INSERT INTO dashboard_spaces (id, name, position, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)'
      ).run(id, name, idx, isActive ? 1 : 0, now, now);
      const artifactIds = Array.isArray(s.artifactIds) ? s.artifactIds : [];
      const update = db.prepare(
        'UPDATE artifacts SET space_id = ? WHERE id = ? AND (space_id IS NULL OR space_id = ?)'
      );
      for (const aid of artifactIds) {
        if (typeof aid !== 'string') continue;
        update.run(id, aid, id);
      }
    });
    // If no row got is_active=1 (active_space_id missing), force first row.
    const anyActive = db.prepare('SELECT COUNT(*) AS n FROM dashboard_spaces WHERE is_active = 1').get().n;
    if (anyActive === 0) {
      const first = db.prepare('SELECT id FROM dashboard_spaces ORDER BY position ASC, created_at ASC LIMIT 1').get();
      if (first) db.prepare('UPDATE dashboard_spaces SET is_active = 1 WHERE id = ?').run(first.id);
    }
    db.prepare('COMMIT').run();
  } catch (err) {
    try { db.prepare('ROLLBACK').run(); } catch {}
    throw err;
  }
  return listSpacesWithArtifactIds();
}
