import { test } from "@playwright/test";
import { journal, look, arriveAsDiner, roleContext, closeRole, table, PATRON } from "../lib-witness.mjs";

// The first W-054 walk got three things nobody had ever asserted - the companion badge, the
// per-person chips, and the no-double-counting count - and all three passed. It then stopped
// on two of MY mistakes and one real gap:
//   - I had diner 1 press "Place order" while scoped to their OWN items, so only their $3.49
//     went in and diner 2's $7.00 stayed unsent. Then I looked for a cancel control on diner
//     2's Ordered tab, which correctly said "Nothing sent yet". My design, not a defect.
//   - the late-joiner step timed out opening their basket. Mine.
//   - there is genuinely NO control to take over a companion's item. That one stands.
// So this walk does the co-order properly: diner 1 switches the basket to the WHOLE TABLE
// scope first and orders from there. That is the only way "P1 co-orders covering both
// patrons' items" can be true, and it is what the canary claims happens.
const RID  = "e01fff78-d0cc-50e3-937c-f79e7346c45f";   // Canary Mosaic V2 Multi - dev
const T    = table(10092);
const SLUG = "w054b-coorder-cancel-latejoin";

const clean = (s) => (s || "").replace(/\s+/g, " ").trim();
const MONEY = /\$\s?\d[\d,]*(?:\.\d{2})?/g;
const money = (s) => Array.from(new Set((s || "").match(MONEY) || []));
const chips = (p) => p.evaluate(() => Array.from(
  document.querySelectorAll('[data-testid^="cart-scope-chip"]'))
  .map(e => `${e.getAttribute("data-testid")}=${(e.textContent||"").trim().slice(0,24)}`));
const cartText = async (p) => {
  const s = p.getByTestId("cart-sheet");
  return (await s.count()) ? clean(await s.first().innerText()) : clean(await p.innerText("body"));
};
async function openCart(p) {
  for (const tid of ["m2-view-order", "m2-dock-cart"]) {
    const l = p.getByTestId(tid);
    if (await l.count()) { await l.first().evaluate(el => el.click()); await p.waitForTimeout(3500); }
    if (await p.getByTestId("cart-sheet").count()) return true;
  }
  return !!(await p.getByTestId("cart-sheet").count());
}
async function addOne(p, avoid) {
  const adds = p.locator('[data-testid^="m2-add-"]');
  const n = await adds.count();
  for (let i = 0; i < Math.min(n, 8); i++) {
    const tid = await adds.nth(i).getAttribute("data-testid");
    if (tid !== avoid) { await adds.nth(i).evaluate(el => el.click()); await p.waitForTimeout(3000); return tid; }
  }
  throw new Error("no distinct dish available to add");
}

test(SLUG, async ({ browser }) => {
  const jr = journal(SLUG);
  jr.target({ app: "patron mosaic_v2", baseUrl: PATRON, restaurantId: RID, table: T });
  jr.note("**The story:** two people at one table, two different dishes at two different prices. One of them pays the table's whole order in. Then the other cancels their dish, and a third person arrives late and should still see what is on the table.");

  const c1 = await roleContext(browser, jr, "diner-1");
  const p1 = await c1.newPage();
  const c2 = await roleContext(browser, jr, "diner-2");
  const p2 = await c2.newPage();
  let verdict = "INCOMPLETE", why = "", leftBehind = "nothing";
  const seen = {};
  try {
    await jr.soft("DINER 1", "we both sit down and each take something different", async id => {
      await arriveAsDiner(p1, RID, T);
      seen.d1 = await addOne(p1, null);
      await arriveAsDiner(p2, RID, T);
      seen.d2 = await addOne(p2, seen.d1);
      const t = await look(p1, jr, "diner-1", id, "our two menus, one dish each");
      seen.menuPrices = money(clean(t)).slice(0, 8);
      return `I took ${seen.d1}, they took ${seen.d2} | prices on the menu ${JSON.stringify(seen.menuPrices)}`;
    });

    await jr.soft("DINER 1", "my basket lets me switch to the whole table, and the price changes to match", async id => {
      await p1.reload({ waitUntil: "domcontentloaded" });
      await p1.waitForTimeout(9000);
      if (!(await openCart(p1))) throw new Error("my basket would not open");
      seen.chipsBefore = await chips(p1);
      seen.mineText = await cartText(p1);
      seen.mineCta = clean(await p1.getByTestId("order-now-btn").first().innerText().catch(() => ""));
      const all = p1.getByTestId("cart-scope-chip-all");
      if (!(await all.count())) throw new Error(`there is no whole-table chip. Chips: ${JSON.stringify(seen.chipsBefore)}`);
      await all.first().evaluate(el => el.click());
      await p1.waitForTimeout(4000);
      const t = await look(p1, jr, "diner-1", id, "my basket switched to the whole table");
      seen.allText = await cartText(p1);
      seen.allCta = clean(await p1.getByTestId("order-now-btn").first().innerText().catch(() => ""));
      seen.allMoney = money(seen.allText);
      if (seen.allCta === seen.mineCta)
        throw new Error(`switching to the whole table did not change what I am about to pay - it still says "${seen.allCta}". Chips: ${JSON.stringify(seen.chipsBefore)}`);
      return `just me: "${seen.mineCta}" -> whole table: "${seen.allCta}" | chips ${JSON.stringify(seen.chipsBefore)} | ${seen.allText.slice(0,260)}`;
    });

    await jr.soft("DINER 1", "I send the whole table's order in", async id => {
      const b = p1.getByTestId("order-now-btn");
      if (!(await b.count())) throw new Error("no place-order control on the whole-table view");
      seen.sentCta = clean(await b.first().innerText());
      await b.first().evaluate(el => el.click());
      await p1.waitForTimeout(9000);
      const t = await look(p1, jr, "diner-1", id, "after sending it for both of us");
      seen.p1After = clean(t).slice(0, 300);
      return `pressed "${seen.sentCta}" | ${seen.p1After.slice(0,220)}`;
    });

    await jr.soft("DINER 2", "their dish went in too — they are not left holding an unsent order", async id => {
      await p2.reload({ waitUntil: "domcontentloaded" });
      await p2.waitForTimeout(9000);
      if (!(await openCart(p2))) throw new Error("their basket would not open");
      const tab = p2.getByTestId("m2-cart-tab-table");
      if (await tab.count()) { await tab.first().evaluate(el => el.click()); await p2.waitForTimeout(3500); }
      const t = await look(p2, jr, "diner-2", id, "their Ordered tab");
      seen.p2Ordered = await cartText(p2);
      if (/nothing sent yet/i.test(seen.p2Ordered))
        throw new Error(`their Ordered tab says "Nothing sent yet" although I paid the whole table in with "${seen.sentCta}". It reads: ${seen.p2Ordered.slice(0,260)}`);
      return seen.p2Ordered.slice(0, 320);
    });

    await jr.soft("DINER 2", "they can cancel their own dish now it has gone in", async id => {
      seen.cancelIds = await p2.evaluate(() => Array.from(
        document.querySelectorAll('[data-testid^="cancel-item-"]')).map(e => e.getAttribute("data-testid")));
      if (!seen.cancelIds.length)
        throw new Error(`nothing on their Ordered tab cancels an item: ${seen.p2Ordered.slice(0,260)}`);
      await p2.locator(`[data-testid="${seen.cancelIds[0]}"]`).first().evaluate(el => el.click());
      await p2.waitForTimeout(9000);
      const t = await look(p2, jr, "diner-2", id, "after they cancelled their dish");
      seen.afterCancel = await cartText(p2);
      if (!/cancel/i.test(seen.afterCancel))
        throw new Error(`they pressed ${seen.cancelIds[0]} and the ledger never says cancelled: ${seen.afterCancel.slice(0,260)}`);
      return `cancelled ${seen.cancelIds[0]} | ${seen.afterCancel.slice(0,300)}`;
    });

    await jr.soft("DINER 3", "someone joins late and still sees what the table ordered before they arrived", async id => {
      const c3 = await roleContext(browser, jr, "diner-3");
      const p3 = await c3.newPage();
      await arriveAsDiner(p3, RID, T);
      await p3.waitForTimeout(4000);
      const opened = await openCart(p3);
      if (opened) {
        const tab = p3.getByTestId("m2-cart-tab-table");
        if (await tab.count()) { await tab.first().evaluate(el => el.click()); await p3.waitForTimeout(3500); }
      }
      const t = await look(p3, jr, "diner-3", id, "the late arrival's view of the table");
      seen.late = opened ? await cartText(p3) : clean(t);
      seen.lateChips = opened ? await chips(p3) : [];
      await closeRole(c3, jr, "diner-3");
      if (!opened) throw new Error(`the late arrival's basket would not open at all. Their screen: ${seen.late.slice(0,260)}`);
      if (!money(seen.late).length)
        throw new Error(`their screen shows nothing the table has ordered: ${seen.late.slice(0,300)}`);
      return `they see ${JSON.stringify(money(seen.late))} | chips ${JSON.stringify(seen.lateChips)} | ${seen.late.slice(0,260)}`;
    });

    verdict = "WALKED";
    why = "Co-ordered from the whole-table scope, cancelled from the other diner's ledger, and joined late.";
  } catch (e) {
    verdict = "INCOMPLETE"; why = `the walk stopped: ${e.message}`;
  } finally {
    leftBehind = `Table ${T} on Canary Mosaic V2 Multi: two guests, one table-wide order, one cancelled dish, one late arrival. Table left open.`;
    jr.note("### Everything I read off the screens\n\n```json\n" + JSON.stringify(seen, null, 1).slice(0, 6000) + "\n```");
    jr.note(`> **What I left behind:** ${leftBehind}`);
    await closeRole(c1, jr, "diner-1"); await closeRole(c2, jr, "diner-2");
    jr.finish(verdict, why);
  }
});
