/* WITNESS · mosaic_v2-coverage   (clean table, exactly two diners)
   The promise, as a person would put it: at a shared table one of us can say "I've got this"
   — cover just my own food, or cover the whole table — and everybody's phone should then
   agree about who is covering what, so two of us don't pay for the same dish and nobody's
   share is quietly left behind.

   Nobody has walked this. The board says PASSED.

   I PRESS NOTHING THAT PAYS. I open the cover flow, read what it offers, and stop at the
   payment control. These fixtures sit on a live Stripe merchant on dev.                     */
import { test, expect } from "@playwright/test";
import { journal, look, DWELL, roleContext, closeRole, PATRON } from "../lib-witness.mjs";
import { arrive, tap, whoIsHere, settleControls, openSheet } from "./split-lib.mjs";

const RID = "547c72a4-acab-57f3-a969-7fe62fbb0be6";   // canary mosaic_v2 coverage fixture
const TABLE = 9947;   // a fresh table again — NOT one the canary drives (it uses 9975 and 9982)

const sheetText = async page =>
  (await page.locator('[data-testid="cart-sheet"]').innerText().catch(() => "")).replace(/\s+/g, " ").trim();

async function openOrderedTab(page) {
  await openSheet(page);
  const tab = page.locator('[data-testid="m2-cart-tab-table"]').first();
  if (await tab.count()) await tap(page, tab);
  await page.waitForTimeout(DWELL);
}

/** Ask for the check the way a diner does, and report what settling offers. */
async function askForCheck(page) {
  for (const sel of ['[data-testid="layout-service-button"]', '[data-testid="cart-service-btn"]']) {
    const s = page.locator(sel).first();
    if (await s.count()) { await tap(page, s); break; }
  }
  await page.waitForTimeout(DWELL);
  const bill = page.locator('[data-testid="service-bubble-ready_for_bill"]').first();
  if (!(await bill.count())) return null;
  await tap(page, bill);
  await page.waitForTimeout(DWELL + 5000);
  return settleControls(page);
}

/** Everything readable inside whatever panel the cover choice opened. */
async function panelText(page) {
  return page.evaluate(() => {
    const p = document.querySelector('[data-testid*="cover"], [data-testid*="settle"], [data-testid="service-bubble"]');
    return p ? (p.textContent || "").replace(/\s+/g, " ").trim() : "";
  });
}

test("mosaic_v2-coverage", async ({ browser }) => {
  const jr = journal("mosaic_v2-coverage");
  jr.target({ app: "patron (mosaic_v2)", baseUrl: PATRON, restaurantId: RID, table: TABLE });
  jr.note(`**Where:** the coverage canary fixture, table ${TABLE} — not a table the canary drives.`);
  jr.note("**Who:** exactly two of us, A and B, on two separate phones.");
  jr.note("**I press nothing that pays.**");
  jr.note("_The waiter's bill view is where staff would read 'N of M covered'. The staff app lists "
        + "**no tables at all** for this restaurant, so that counter is out of reach here and the "
        + "diners' own phones are the only place coverage can be checked._");

  const cA = await roleContext(browser, jr, "patron-a");
  const cB = await roleContext(browser, jr, "patron-b");
  const a = await cA.newPage(), b = await cB.newPage();
  let verdict = "INCOMPLETE", why = "", leftBehind = "nothing";

  try {
    await jr.step("PATRON A", `I sit down at table ${TABLE} and the menu opens`, async id => {
      const g = await arrive(a, RID, TABLE);
      const t = await look(a, jr, "patron-a", id, "a arrives");
      const here = await whoIsHere(a);
      jr.note("> **Who was already at this table:** "
            + (here.length ? here.map(c => `\`${c.text.replace(/\n/g, " ")}\``).join(" · ") : "nobody"));
      expect(here.length, `table ${TABLE} should be empty before I start`).toBe(0);
      return `passed: ${g.join(", ") || "no gates"}. Table empty. ${t.slice(0, 110)}`;
    });

    await jr.step("PATRON B", "My friend sits down at the same table", async id => {
      const g = await arrive(b, RID, TABLE);
      const t = await look(b, jr, "patron-b", id, "b arrives");
      return `passed: ${g.join(", ") || "no gates"}. ${t.slice(0, 110)}`;
    });

    await jr.step("PATRON A", "I order one thing — exactly one", async id => {
      const add = a.locator('[data-testid^="m2-add-"]').first();
      await expect(add, "something should be orderable").toBeVisible({ timeout: 25000 });
      const name = (await add.getAttribute("aria-label")) || "(unnamed)";
      await tap(a, add);
      const readCount = async () => {
        const c = a.locator('[data-testid="m2-dock-cart-count"], [data-testid="m2-view-order-count"]').first();
        return (await c.count()) ? Number((await c.innerText()).replace(/\D/g, "")) : null;
      };
      await expect.poll(readCount, { timeout: 20000 }).toBe(1);
      await a.waitForTimeout(900);
      expect(await readCount(), "one tap must add exactly one thing").toBe(1);
      await look(a, jr, "patron-a", id, "a added one");
      return `added "${name}"; settled on exactly 1`;
    });

    await jr.step("PATRON B", "My friend orders one thing too", async id => {
      const add = b.locator('[data-testid^="m2-add-"]').first();
      const name = (await add.getAttribute("aria-label")) || "(unnamed)";
      await tap(b, add);
      await b.waitForTimeout(DWELL);
      await look(b, jr, "patron-b", id, "b added one");
      return `they added "${name}"`;
    });

    await jr.step("PATRON A", "My cart shows both of us before I send it", async id => {
      await openSheet(a);
      await look(a, jr, "patron-a", id, "a shared cart");
      const here = await whoIsHere(a);
      const all = here.find(c => c.testid === "cart-scope-chip-all");
      const guests = here.filter(c => c.testid !== "cart-scope-chip-all");
      const s = await sheetText(a);
      jr.note("> **My cart says the table holds:** " + here.map(c => `\`${c.text.replace(/\n/g, " ")}\``).join(" · "));
      jr.note(`> **The total on my cart reads** \`${s.match(/TOTAL \$[\d.]+/i)?.[0] || "(none)"}\``);
      if (guests.length !== 2)
        jr.note(`> ⚠️ **There are ${guests.length} guests on this table, not the two of us.** `
              + "Nobody else sat down with us.");
      expect(guests.length, "at least my friend and me should be on this table").toBeGreaterThanOrEqual(2);
      return `two guests; 'All' = "${all?.text.replace(/\n/g, " ")}"; ${s.slice(0, 150)}`;
    });

    await jr.step("PATRON A", "We send the order", async id => {
      const btn = a.locator('[data-testid="order-now-btn"]').first();
      await expect(btn, "there should be a way to send the order").toBeVisible({ timeout: 20000 });
      const label = (await btn.innerText()).replace(/\s+/g, " ").trim();
      jr.note(`> **The button says** \`${label}\` — note whether that price covers one of us or both.`);
      await tap(a, btn, 25000);
      await a.waitForTimeout(DWELL + 5000);
      leftBehind = `a table session and a placed order on table ${TABLE}`;
      await look(a, jr, "patron-a", id, "a ordered");
      return `pressed "${label}"`;
    });

    await jr.step("PATRON A", "The order really went in", async id => {
      await openOrderedTab(a);
      await look(a, jr, "patron-a", id, "a ordered tab");
      const s = await sheetText(a);
      jr.note(`> **MY Ordered list:** ${s.slice(0, 400) || "(no text)"}`);
      expect(s, "my Ordered list must not still say nothing was sent").not.toMatch(/nothing sent yet/i);
      return s.slice(0, 240);
    });

    await jr.step("PATRON A", "I ask to settle and look for a way to say who is covering what", async id => {
      const ctrls = await askForCheck(a);
      await look(a, jr, "patron-a", id, "a asked to settle");
      if (!ctrls) throw new Error("there was no 'Check Please' option in the service drawer");
      const bubble = (await a.locator('[data-testid="service-bubble"]').innerText().catch(() => "")).replace(/\s+/g, " ").trim();
      jr.note("> **What settling offers me:** " + (ctrls.length ? "`" + ctrls.join("`, `") + "`" : "**nothing**"));
      jr.note(`> **In the words on my screen:** ${bubble.slice(0, 320) || "(no text)"}`);
      expect(ctrls, "settling should offer me a way to cover").toContain("settlement-cover");
      return `offered: ${ctrls.join(", ")} — panel: ${bubble.slice(0, 200)}`;
    });

    await jr.step("PATRON A", "I choose to cover, and read exactly what choices that gives me", async id => {
      const cover = a.locator('[data-testid="settlement-cover"]').first();
      const label = (await cover.innerText()).replace(/\s+/g, " ").trim();
      await tap(a, cover);
      await a.waitForTimeout(DWELL);
      await look(a, jr, "patron-a", id, "a cover panel");
      const ids = await a.evaluate(() => [...new Set([...document.querySelectorAll("[data-testid]")]
        .map(e => e.getAttribute("data-testid"))
        .filter(x => /cover|settle|split|pay|guest|whom|who/i.test(x)))].sort());
      const words = await panelText(a);
      jr.note(`> **The button said** \`${label}\`**. The panel it opened offers:** `
            + (ids.length ? "`" + ids.join("`, `") + "`" : "no controls I can name"));
      jr.note(`> **In words:** ${words.slice(0, 420) || "(the panel had no readable text)"}`);
      return `"${label}" → ${ids.join(", ") || "nothing"} — ${words.slice(0, 200)}`;
    });

    await jr.step("PATRON A", "I say I'm covering someone else at the table — that is the whole point",
      async id => {
        const someone = a.locator('[data-testid="cover-scope-someone"]').first();
        await expect(someone, "there should be a way to cover someone else").toBeVisible({ timeout: 15000 });
        const label = (await someone.innerText()).replace(/\s+/g, " ").trim();
        await tap(a, someone);
        await a.waitForTimeout(DWELL);
        await look(a, jr, "patron-a", id, "a picking who to cover");
        const words = await panelText(a);
        const choices = await a.evaluate(() => [...document.querySelectorAll("button,[role=button]")]
          .filter(e => (e.textContent || "").trim().length < 60 && e.children.length < 4)
          .map(e => ({ testid: e.getAttribute("data-testid"), text: (e.textContent || "").replace(/\s+/g, " ").trim() }))
          .filter(o => o.text));
        jr.note(`> **I pressed \`${label}\`. What I am then offered:** `
              + (choices.length ? choices.map(o => `\`${o.text}\`${o.testid ? ` (\`${o.testid}\`)` : ""}`).join(" · ")
                                : "**no buttons at all**"));
        jr.note(`> **In words:** ${words.slice(0, 420) || "**the panel had no readable text**"}`);
        return `"${label}" -> ${choices.map(o => o.text).join(" | ").slice(0, 220) || "nothing"}`;
      });

    await jr.soft("PATRON A", "I pick my friend out of the list and cover them", async id => {
      const named = await a.evaluate(() => [...document.querySelectorAll("button,[role=button],[data-testid]")]
        .map(e => ({ testid: e.getAttribute("data-testid") || "", text: (e.textContent || "").replace(/\s+/g, " ").trim() }))
        .filter(o => /cover-guest|cover-patron|cover-someone-|cover-target/.test(o.testid)));
      if (!named.length)
        throw new Error("I was asked who I am covering, chose 'Someone on the table', and was given no "
                      + "list of people to choose from - I cannot name the person I want to cover");
      const who = named[0];
      await tap(a, a.locator(`[data-testid="${who.testid}"]`).first());
      await a.waitForTimeout(DWELL);
      await look(a, jr, "patron-a", id, "a covered friend");
      const words = await panelText(a);
      jr.note(`> **I chose to cover \`${who.text}\`. My screen then said:** ${words.slice(0, 400) || "**nothing at all**"}`);
      return `covered "${who.text}" - ${words.slice(0, 220) || "no confirmation shown"}`;
    });

    await jr.soft("PATRON B", "My friend's phone knows I am covering — and what is left for them", async id => {
      await b.reload({ waitUntil: "domcontentloaded" });
      await b.waitForTimeout(DWELL + 6000);
      await openOrderedTab(b);
      await look(b, jr, "patron-b", id, "b sees coverage");
      const s = await sheetText(b);
      const ctrls = await settleControls(b);
      const mentions = /cover|covered|paid|settl/i.test(s);
      jr.note(`> **My friend's order sheet:** ${s.slice(0, 360) || "(no sheet text)"}`);
      jr.note("> **Their settle controls:** " + (ctrls.length ? "`" + ctrls.join("`, `") + "`" : "none"));
      jr.note(mentions ? "> Their phone does mention coverage."
                       : "> ⚠️ **Nothing on their phone mentions coverage at all** — they cannot tell "
                       + "from their own screen whether anything of theirs is being covered.");
      if (!mentions) throw new Error("my friend's phone says nothing about anyone covering anything");
      return s.slice(0, 240);
    });

    await jr.soft("PATRON B", "My friend offers to cover the whole table instead", async id => {
      const ctrls = await askForCheck(b);
      await look(b, jr, "patron-b", id, "b asked to settle");
      if (!ctrls || !ctrls.includes("settlement-cover"))
        throw new Error(`my friend was not offered a cover control; they got: ${(ctrls || []).join(", ") || "nothing"}`);
      await tap(b, b.locator('[data-testid="settlement-cover"]').first());
      await b.waitForTimeout(DWELL);
      await look(b, jr, "patron-b", id + "b", "b cover panel");
      const words = await panelText(b);
      const opts = await b.evaluate(() => [...document.querySelectorAll("button,[role=button]")]
        .filter(e => (e.textContent || "").trim().length < 90 && e.children.length < 4)
        .map(e => (e.textContent || "").replace(/\s+/g, " ").trim()).filter(Boolean).slice(0, 25));
      jr.note(`> **My friend's cover panel says:** ${words.slice(0, 400) || "(no text)"}`);
      jr.note("> **Buttons they can press:** " + opts.map(o => `\`${o}\``).join(" · "));
      return words.slice(0, 240);
    });

    verdict = "WALKED";
    why = "drove two real diners through ordering and into the cover flow, and recorded exactly what "
        + "coverage choices exist — stopping short of anything that takes money";
  } catch (e) {
    verdict = "STOPPED"; why = String(e.message || e).split("\n")[0].slice(0, 300);
  } finally {
    jr.note(`> **What I left behind:** ${leftBehind}. The staff app lists no table cards for this `
          + "fixture, so I had no waiter's way to cancel the items or close the table.");
    jr.finish(verdict, why);
    await closeRole(cA, jr, "patron-a");
    await closeRole(cB, jr, "patron-b");
  }
});
