/* Witness helpers.
   I am a person using the app, not an engineer testing it. Everything here is something
   a human could do: look, wait, tap what is visible. No evaluate-clicks, no API probes,
   no reading hidden DOM to rescue a step. If a person would be stuck, I am stuck, and
   that is the result.                                                                   */
import { test, expect } from "@playwright/test";
import fs from "node:fs";

export const DWELL = 5000;                       // 5s of stillness after every screen load
export const PATRON = "https://dcgmji73h6qel.cloudfront.net";
export const OWNER  = "https://owner.dev.qrate-ai.com";
export const WAITER = "https://waiter.dev.qrate-ai.com";

export function runDir() {
  const run = fs.readFileSync("/workspace/uat/.witness_run","utf8").trim();
  return `/workspace/uat/witness/${run}`;
}

/** A witness journal: what I expected, what I saw, and why I stopped. Written to NOTES.md
 *  AND attached to the trace, so opening the trace shows intent beside reality.          */
export function journal(slug) {
  const dir = `${runDir()}/${slug}`;
  fs.mkdirSync(dir, { recursive: true });
  const lines = [`# ${slug}`, "", `_Witnessed ${new Date().toISOString()} — QRATE-UAT-1_`, ""];
  let n = 0;
  return {
    dir, slug,
    /** Record a step in plain language, and mark it in the trace. */
    async step(role, expected, fn) {
      n += 1;
      const id = String(n).padStart(2, "0");
      let saw = "", ok = true;
      await test.step(`${id} · ${role} · ${expected}`, async () => {
        test.info().annotations.push({ type: "expected", description: expected });
        try {
          saw = (await fn(id)) || "did what was expected";
        } catch (e) {
          ok = false;
          saw = `STOPPED — ${String(e.message || e).split("\n")[0].slice(0, 220)}`;
          test.info().annotations.push({ type: "stopped-here", description: saw });
          throw e;
        } finally {
          test.info().annotations.push({ type: "what I saw", description: saw });
          lines.push(`### ${id} · ${role}`, "", `**I expected:** ${expected}`, "",
                     `**I saw:** ${saw}`, "");
        }
      });
      return saw;
    },
    /** Close the journal, explaining plainly why the walk ended where it did. */
    /** Like step(), but a failure is recorded as "I could not" and the walk continues. */
    async soft(role, expected, fn) {
      try { return await this.step(role, expected, fn); }
      catch (e) { return `COULD NOT — ${String(e.message || e).split("\n")[0].slice(0, 220)}`; }
    },
    finish(verdict, whyStopped) {
      lines.push("---", "", `**Verdict:** ${verdict}`, "",
                 `**Why it ended here:** ${whyStopped}`, "");
      fs.writeFileSync(`${dir}/NOTES.md`, lines.join("\n"));
    },
    note(text) { lines.push(text, ""); },
  };
}

/** Look at the screen for a human beat, then photograph it. */
export async function look(page, jr, role, id, label) {
  await page.waitForTimeout(DWELL);
  const d = `${jr.dir}/${role}`;
  fs.mkdirSync(d, { recursive: true });
  await page.screenshot({ path: `${d}/${id}-${label.replace(/\W+/g,"-").slice(0,40)}.png` });
  return (await page.locator("body").innerText()).replace(/\s+/g, " ").trim();
}

/** Arrive as a diner does: scan the code, wait, answer whatever the app asks. */
export async function arriveAsDiner(page, rid, table, layout = "mosaic_v2") {
  const url = `${PATRON}/menu?id=${rid}&table=${table}` + (layout ? `&layout=${layout}` : "");
  await page.goto(url, { waitUntil: "domcontentloaded" });
  await page.waitForTimeout(DWELL + 3000);
  const seen = [];
  // The welcome card asks for a swipe up. Do it as a finger would.
  if (await page.getByText(/Let's get started/i).count()) {
    seen.push("welcome card");
    const vp = page.viewportSize();
    await page.mouse.move(vp.width/2, vp.height*0.82);
    await page.mouse.down();
    for (const f of [0.75,0.62,0.5,0.38,0.28]) { await page.mouse.move(vp.width/2, vp.height*f); await page.waitForTimeout(70); }
    await page.mouse.up();
    await page.waitForTimeout(DWELL);
  }
  // Then it asks about dietary needs. Answer as someone with none.
  const skip = page.getByRole("button", { name: /show me everything|no,? show/i });
  if (await skip.count()) {
    seen.push("dietary question");
    await skip.first().click({ timeout: 15000 });
    await page.waitForTimeout(DWELL);
  }
  return seen;
}

/** Sign in on the waiter app the way staff would. */
export async function signInWaiter(page, email, password) {
  await page.goto(`${WAITER}/owner/staff-orders/`, { waitUntil: "domcontentloaded" });
  await page.waitForTimeout(DWELL);
  const em = page.locator('[data-testid="login-email"], input[type="email"]').first();
  if (await em.count()) {
    await em.fill(email);
    await page.locator('[data-testid="login-password"], input[type="password"]').first().fill(password);
    const sub = page.locator('[data-testid="login-submit"]').first();
    if (await sub.count()) await sub.click({ timeout: 25000 });
    else await page.getByRole("button", { name: /^sign in$/i }).first().click({ timeout: 25000 });
    await page.waitForTimeout(DWELL + 5000);
  }
  return page.url();
}

/* ─────────────────────────────────────────────────────────────────────────────
   Per-role browser contexts, each with its OWN video AND its own trace.

   The config's `trace: "on"` ALREADY auto-starts tracing on every context created
   from the `browser` fixture, including hand-built ones — calling tracing.start()
   here throws "Tracing has been already started". So we do not touch tracing; the
   run-level trace.zip lands in outputDir and `collectTraces()` files it next to the
   journey it belongs to.
   ───────────────────────────────────────────────────────────────────────────── */
import path from "node:path";

export async function roleContext(browser, jr, role) {
  const dir = `${jr.dir}/${role}`;
  fs.mkdirSync(dir, { recursive: true });
  const ctx = await browser.newContext({
    viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true,
    recordVideo: { dir, size: { width: 390, height: 844 } },
  });
  return ctx;
}

/** Close the context and give the video a name a person can read. */
export async function closeRole(ctx, jr, role) {
  const dir = `${jr.dir}/${role}`;
  await ctx.close();                                   // video is only finalised on close
  try {
    for (const f of fs.readdirSync(dir)) {
      if (f.endsWith(".webm") && f !== "video.webm") {
        fs.renameSync(path.join(dir, f), path.join(dir, "video.webm"));
        break;
      }
    }
  } catch { /* no video recorded */ }
}
