/**
 * Web Designer agent — a durable design queue (cja_tasks) drained one task at a
 * time. Each task runs the full-builder agent on any page (skills + brief), then
 * auto-publishes: build must pass, then a scoped git commit; a failing task
 * reverts itself. Owns: /tasks/run. Exposes host.drainTasks for QA autofix.
 */

let H;
let draining = false;

async function claimNextTask() {
  const { out } = await H.phpCli(["api/cli/tasks-next.php"]);
  try { const t = JSON.parse((out || "{}").trim() || "{}"); return t && t.task_id ? t : null; }
  catch { return null; }
}
async function finishTask(id, status, result) {
  await H.phpCli(["api/cli/tasks-finish.php", `--id=${id}`, `--status=${status}`, `--result=${JSON.stringify(result)}`]);
}

function liveUrl(target) {
  if (!target) return "/";
  if (target.startsWith("new:")) return "/" + target.slice(4).replace(/^\/+/, "");
  return target;
}
function nowStamp() {
  const d = new Date();
  const p = (n) => String(n).padStart(2, "0");
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
}

function buildDesignPrompt(task) {
  let assets = { images: [], videos: [] };
  try { assets = JSON.parse(task.assets || "{}") || {}; } catch {}
  const images = Array.isArray(assets.images) ? assets.images : [];
  const videos = Array.isArray(assets.videos) ? assets.videos : [];
  const target = task.target_page || "/";
  const isNew = target.startsWith("new:");
  const slug = isNew ? target.slice(4).replace(/^\/+/, "") : "";

  const p = [];
  p.push(
    "You are the Web Designer for the Carlos Arias website (carlosarias.co) — an Astro + Tailwind v4 static site.",
    "You are a real designer: make considered, on-brand design decisions, not just literal edits.",
    "",
    "Before you start, consult your design skills and APPLY them: read .claude/skills/brand/SKILL.md and .claude/skills/ui-ux/SKILL.md.",
    "The brand is sumi-e — ink on washi paper, a single vermillion seal, restraint, near-square 2px radii. Reuse the existing .ca-* classes and --ca-* tokens in app/src/styles.css and match nearby components; do not invent new one-off styles or add a second accent colour.",
    "Do NOT run builds or git — the console builds and publishes for you.",
    "",
  );
  if (isNew) {
    p.push(
      `TASK: create a NEW page at /${slug}.`,
      `- Create app/src/pages/${slug}.astro using BaseLayout and existing .ca-* section patterns (study app/src/pages/about.astro and services.astro for structure).`,
      "- Register it in the nav: add it to the nav array in app/src/components/Header.astro AND the footer links in app/src/components/Footer.astro.",
      "",
    );
  } else {
    p.push(
      `TASK: work on the existing page ${target}.`,
      "- Find its source under app/src/pages (Glob/Grep). Edit that file and any components it uses.",
      "",
    );
  }
  p.push("What to do:", task.prompt || "(no instructions given)", "");
  if (task.draft && String(task.draft).trim()) {
    p.push("Draft content to work from (polish it, don't paste it verbatim):", task.draft, "");
  }
  if (images.length) {
    p.push("Images you may use — Read each to see what it shows, then place it with its /media/... src and a real alt:");
    for (const im of images) p.push(`- ${im.url}  (on disk: app/public${im.url})${im.alt ? ` — hint: ${im.alt}` : ""}`);
    p.push("");
  }
  if (videos.length) {
    p.push("Short video links to embed where they fit (e.g. an Instagram reel — responsive 9:16, no autoplay sound):");
    for (const v of videos) p.push(`- ${v}`);
    p.push("");
  }
  p.push(
    "",
    "After you finish the change, log it to the public changelog using your `changelog` skill:",
    "- Add ONE entry to the $entries array in api/cli/seed-changelog.php, in the site's visitor-facing voice.",
    `- Use the timestamp '${nowStamp()}' and attribute it to 'Website Designer Agent' (the 5th array element).`,
    "- Choose the right type (added / updated / fixed / removed).",
    "- Do NOT run the reseed or the build — the console does that for you.",
    "- If you ended up making no change to the site, do not add a changelog entry.",
    "",
    "Keep the change scoped to what's asked and leave the working tree with only your intended edits.",
  );
  return p.join("\n");
}

async function runDesignTask(task) {
  const prompt = buildDesignPrompt(task);

  // Pre-flight: a dirty tree would be swept into this task's scoped commit.
  // Uploaded assets under app/public/media/ are this task's images and are fine.
  const pre = await H.git(["status", "--porcelain", "--", ...H.gitScope]);
  const dirty = pre.tail.split("\n").map((l) => l.slice(3).trim()).filter(Boolean).filter((p) => !p.startsWith("app/public/media/"));
  if (dirty.length) return { ok: false, error: "Working tree wasn't clean — commit or discard pending changes before running the queue." };

  const { result } = await H.runClaudeJson({ prompt, tools: "Read Write Edit Glob Grep WebSearch Skill" });
  const summary = typeof result === "string" ? result.trim().slice(0, 500) : "";

  const status = await H.git(["status", "--porcelain", "--", ...H.gitScope]);
  if (status.tail.trim() === "") {
    return { ok: true, commit: "", summary: summary || "No changes were needed.", live: liveUrl(task.target_page) };
  }
  // The agent logs to the changelog by editing seed-changelog.php; reseed so the
  // build picks it up (the changelog page reads the DB at build time).
  if (/seed-changelog\.php/.test(status.tail)) await H.phpCli(["api/cli/seed-changelog.php"]);

  const built = await H.build({});
  if (!built.ok) {
    await H.git(["checkout", "--", ...H.gitScope]);
    await H.git(["clean", "-fd", ...H.gitScope]);
    return { ok: false, error: "The change broke the build, so it was reverted." };
  }
  await H.git(["add", "--", ...H.gitScope]);
  const title = (task.title || "task").toString().slice(0, 120);
  await H.git(["commit", "-q", "-m", `web designer: ${title}\n\n[published via task queue]`]);
  const head = await H.git(["rev-parse", "--short", "HEAD"]);
  return { ok: true, commit: head.tail.trim(), summary, live: liveUrl(task.target_page) };
}

// Drain the queue sequentially. Shares host.state.busy; re-entrant-safe.
async function drainTasks() {
  if (draining) return;
  draining = true;
  try {
    for (;;) {
      if (H.state.busy) break; // in-page console mid-edit; a later kick resumes
      const task = await claimNextTask();
      if (!task) break;
      H.state.busy = true;
      let res;
      try { res = await runDesignTask(task); }
      catch (e) { res = { ok: false, error: String((e && e.message) || e) }; }
      H.state.busy = false;
      await finishTask(task.task_id, res.ok ? "published" : "failed",
        res.ok ? { commit: res.commit, summary: res.summary, live: res.live } : { error: res.error || "failed" });
    }
  } finally {
    draining = false;
  }
}

export function register(host) {
  H = host;
  host.drainTasks = drainTasks; // QA autofix kicks the queue through this

  host.route("POST", "/tasks/run", async (req, res) => {
    const env = H.loadEnv();
    if (!env.CLAUDE_CODE_OAUTH_TOKEN) return H.json(res, 401, { error: "No Claude token set." });
    drainTasks(); // fire-and-forget
    H.json(res, 200, { ok: true, draining, busy: H.state.busy });
  });
}
