import { Hono } from 'hono';
import { randomUUID } from 'node:crypto';
import { db, type CardRow, type CommentRow, type ReferenceRow } from '../db.js';
import { isColumn, type Board, type Card, type Column, type Comment, type Reference } from '../types.js';
import { DOC_PAGES } from '../data/docPages.js';

export const kanban = new Hono();

// --- hydration helpers -----------------------------------------------------

const refsForCard = db.prepare(
  'SELECT label, type, href, canonical FROM card_references WHERE card_id = ?',
);
const tagsForCard = db.prepare('SELECT tag FROM tags WHERE card_id = ?');
const commentsForCard = db.prepare(
  'SELECT id, card_id, text, author, created_at FROM comments WHERE card_id = ? ORDER BY created_at ASC',
);

export function hydrateCard(row: CardRow): Card {
  const references: Reference[] = (refsForCard.all(row.id) as ReferenceRow[]).map((r) => ({
    label: r.label,
    type: r.type as Reference['type'],
    href: r.href,
    removable: r.canonical === 0,
  }));
  const tags: string[] = (tagsForCard.all(row.id) as { tag: string }[]).map((t) => t.tag);
  const comments: Comment[] = (commentsForCard.all(row.id) as CommentRow[]).map((c) => ({
    id: c.id,
    cardId: c.card_id,
    text: c.text,
    author: c.author,
    createdAt: c.created_at,
  }));
  return {
    id: row.id,
    title: row.title,
    description: row.description,
    details: row.details,
    category: row.category,
    files: row.files,
    notes: row.notes,
    status: row.status as Column,
    sortOrder: row.sort_order,
    comments,
    tags,
    references,
  };
}

// --- routes ----------------------------------------------------------------

/** Full board, cards ordered by sort_order. */
kanban.get('/board', (c) => {
  const rows = db.prepare('SELECT * FROM cards ORDER BY sort_order ASC').all() as CardRow[];
  const cards = rows.map(hydrateCard);
  const lastRow = db.prepare('SELECT MAX(updated_at) AS u FROM cards').get() as { u: string | null } | undefined;
  const body: Board = { cards, lastUpdated: lastRow?.u ?? new Date().toISOString() };
  return c.json(body);
});

/** Doc-page registry — drives the "add reference" picker on the frontend. */
kanban.get('/pages', (c) => c.json(DOC_PAGES));

/** Move a card to a different column. */
kanban.patch('/cards/:id', async (c) => {
  const id = c.req.param('id');
  const body = await c.req.json().catch(() => ({}));
  if (!isColumn(body.status)) {
    return c.json({ error: "body must include a valid 'status'" }, 400);
  }
  const result = db
    .prepare('UPDATE cards SET status = ?, updated_at = ? WHERE id = ?')
    .run(body.status, new Date().toISOString(), id);
  if (result.changes === 0) return c.json({ error: 'card not found' }, 404);
  const row = db.prepare('SELECT * FROM cards WHERE id = ?').get(id) as CardRow | undefined;
  return c.json(hydrateCard(row!));
});

/** Add a comment. */
kanban.post('/cards/:id/comments', async (c) => {
  const cardId = c.req.param('id');
  const body = await c.req.json().catch(() => ({}));
  const text = typeof body.text === 'string' ? body.text.trim() : '';
  const author = typeof body.author === 'string' && body.author.trim() ? body.author.trim() : 'Anonymous';
  if (!text) return c.json({ error: "'text' is required" }, 400);

  if (!(db.prepare('SELECT 1 FROM cards WHERE id = ?').get(cardId))) {
    return c.json({ error: 'card not found' }, 404);
  }
  const comment: Comment = {
    id: randomUUID(),
    cardId,
    text,
    author,
    createdAt: Date.now(),
  };
  db.prepare(
    'INSERT INTO comments (id, card_id, text, author, created_at) VALUES (?, ?, ?, ?, ?)',
  ).run(comment.id, comment.cardId, comment.text, comment.author, comment.createdAt);
  return c.json(comment, 201);
});

/** Delete a comment. */
kanban.delete('/comments/:commentId', (c) => {
  const result = db.prepare('DELETE FROM comments WHERE id = ?').run(c.req.param('commentId'));
  if (result.changes === 0) return c.json({ error: 'comment not found' }, 404);
  return c.body(null, 204);
});

/** Add a tag. */
kanban.post('/cards/:id/tags', async (c) => {
  const cardId = c.req.param('id');
  const body = await c.req.json().catch(() => ({}));
  const tag = typeof body.tag === 'string' ? body.tag.trim() : '';
  if (!tag) return c.json({ error: "'tag' is required" }, 400);
  if (!(db.prepare('SELECT 1 FROM cards WHERE id = ?').get(cardId))) {
    return c.json({ error: 'card not found' }, 404);
  }
  db.prepare('INSERT OR IGNORE INTO tags (card_id, tag) VALUES (?, ?)').run(cardId, tag);
  return c.json({ cardId, tag }, 201);
});

/** Remove a tag. */
kanban.delete('/cards/:id/tags/:tag', (c) => {
  const result = db
    .prepare('DELETE FROM tags WHERE card_id = ? AND tag = ?')
    .run(c.req.param('id'), decodeURIComponent(c.req.param('tag')));
  if (result.changes === 0) return c.json({ error: 'tag not found' }, 404);
  return c.body(null, 204);
});

/** Add a user reference (canonical references are protected, not added here). */
kanban.post('/cards/:id/references', async (c) => {
  const cardId = c.req.param('id');
  const body = await c.req.json().catch(() => ({}));
  const label = typeof body.label === 'string' ? body.label.trim() : '';
  const href = typeof body.href === 'string' ? body.href.trim() : '';
  const type = typeof body.type === 'string' ? body.type : 'doc';
  if (!label || !href) return c.json({ error: "'label' and 'href' are required" }, 400);
  if (!(db.prepare('SELECT 1 FROM cards WHERE id = ?').get(cardId))) {
    return c.json({ error: 'card not found' }, 404);
  }
  db.prepare(
    'INSERT OR REPLACE INTO card_references (card_id, label, type, href, canonical) VALUES (?, ?, ?, ?, 0)',
  ).run(cardId, label, type, href);
  return c.json({ cardId, label, type, href, removable: true }, 201);
});

/** Remove a user reference (canonical references cannot be removed). */
kanban.delete('/cards/:id/references', (c) => {
  const href = c.req.query('href');
  if (!href) return c.json({ error: "'href' query param is required" }, 400);
  const result = db
    .prepare('DELETE FROM card_references WHERE card_id = ? AND href = ? AND canonical = 0')
    .run(c.req.param('id'), href);
  if (result.changes === 0) {
    return c.json({ error: 'reference not found or is canonical' }, 404);
  }
  return c.body(null, 204);
});
