/* WITNESS · mosaic_v2-cover-someone (W-036) — QRATE-UAT-1
 *
 * WHY THIS EXISTS: this row was parked as blocked. It was not the product - my earlier run
 * only ever had the FIRST diner send an order, so when the second asked to cover someone
 * else's bill there was genuinely nobody to cover. The app was right; the harness was wrong.
 * `placeOwnOrder` now proves each diner's order actually landed before anything depends on it.
 *
 * THE QUESTION: two people are at one table. Can one of them pay for the other's food?
 */
import { test } from "@playwright/test";
import { journal, look, roleContext, closeRole, DWELL } from "../lib-witness.mjs";
import { arrive, tap, whoIsHere, settleControls, openSheet, placeOwnOrder } from "./split-lib.mjs";
import fs from "node:fs";

const F = JSON.parse(fs.readFileSync("/workspace/uat/runs/wave0/fixtures.json", "utf8"));
const RID = F["mosaic_v2-cover-someone"] || F["mosaic_v2-split"] || F["canary-mosaic"];
const TABLE = 9971;

test("mosaic_v2-cover-someone", async ({ browser }) => {
  const jr = journal("mosaic_v2-cover-someone");
  jr.target({ app: "patron x2", restaurantId: RID, table: TABLE,
              fixture: "Canary Mosaic V2 — dev" });
  jr.note("_Two diners at one table. The point is whether one can pick up the other's bill - "
        + "so BOTH have to have actually ordered first. My previous attempt skipped that and "
        + "then blamed the app for offering nothing to cover._");

  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 = "";
  const rec = {};

  try {
    await jr.step("PATRON", "Diner one sits down and orders something", async id => {
      await arrive(p1, RID, TABLE);
      await look(p1, jr, "diner-1", id, "diner one arrived");
      rec.o1 = await placeOwnOrder(p1, "Diner one");
      await look(p1, jr, "diner-1", id, "diner one has ordered");
      return `Diner one ordered: "${rec.o1.label}", ledger says "${rec.o1.badge}".`;
    });

    await jr.step("PATRON", "Diner two joins the same table and orders too", async id => {
      await arrive(p2, RID, TABLE);
      await look(p2, jr, "diner-2", id, "diner two arrived");
      rec.o2 = await placeOwnOrder(p2, "Diner two");
      await look(p2, jr, "diner-2", id, "diner two has ordered");
      const who = await whoIsHere(p2);
      jr.note(`> **Who diner two can see at the table:** `
            + `${who.map(w => w.text).join(" · ") || "nobody named"}`);
      rec.who = who;
      return `Diner two ordered: "${rec.o2.label}". At the table she can see: `
           + `${who.map(w => w.text).join(", ") || "no names"}.`;
    });

    await jr.step("PATRON", "Diner two asks to pay for someone else", async id => {
      const opened = await openSheet(p2);
      await p2.waitForTimeout(DWELL);
      // PROVE the sheet is actually open before reading anything off it. "No cover control"
      // and "no screen" produce the identical empty list, and only one of them is a finding
      // about the product. Reporting the first without excluding the second is how a harness
      // gap gets published as a defect.
      const sheetOpen = await p2.evaluate(() => {
        const s = document.querySelector('[data-testid="cart-sheet"]');
        return { present: !!s, text: (s?.innerText || "").replace(/\s+/g, " ").trim().slice(0, 200),
                 testids: [...document.querySelectorAll("[data-testid]")]
                   .map(e => e.getAttribute("data-testid")).filter(x => /cart|sheet|order|dock/i.test(x)).slice(0, 25) };
      });
      jr.note(`> **Is the order sheet actually open?** ${sheetOpen.present ? "yes" : "NO"} `
            + `· on-screen order/cart controls: \`${sheetOpen.testids.join("`, `")}\``);
      if (!sheetOpen.present)
        throw new Error(`I could not open the running-order sheet (openSheet=${opened}), so I cannot `
          + `say what diner two is or is not offered. This is my harness, not the product. `
          + `Controls present: ${sheetOpen.testids.join(", ") || "none"}.`);
      const before = await settleControls(p2);
      jr.note(`> **Settle controls diner two is offered:** \`${before.join("`, `")}\``);
      // "Check Please" is NOT in the cart sheet - it lives in the SERVICE BUBBLE, opened by a
      // `qrate:open-service-bubble` event. My previous read of "no settle controls" was taken
      // from the cart sheet alone, which is the wrong surface: absence there says nothing about
      // what the product offers. Open the right surface before judging what is missing.
      await p2.evaluate(() => window.dispatchEvent(new CustomEvent("qrate:open-service-bubble")));
      await p2.waitForTimeout(DWELL);
      const bubble = await p2.evaluate(() => {
        const ids = [...document.querySelectorAll("[data-testid]")]
          .map(e => e.getAttribute("data-testid"));
        return { open: ids.some(x => /service-bubble|service-drawer/i.test(x)),
                 actions: ids.filter(x => /service|check|bill|cover|split|pay|waiter/i.test(x)).slice(0, 30) };
      });
      jr.note(`> **Service bubble open?** ${bubble.open ? "yes" : "no"} · `
            + `what it offers: \`${bubble.actions.join("`, `")}\``);
      // The control is NOT called "check please" in the DOM - it is `service-bubble-ready_for_bill`.
      // My earlier selector matched neither, so the settlement sheet never opened and I read the
      // resulting emptiness as "the product offers no way to cover". The mosaic_v2-split walk had
      // already recorded the real path: Check Please -> settlement sheet -> settlement-cover.
      const check = p2.locator('[data-testid*="ready_for_bill"], [data-testid*="check-please"], '
                             + '[data-testid*="check_please"], [data-testid*="settlement"]').first();
      if (await check.count()) {
        await tap(p2, check);
        await p2.waitForTimeout(DWELL);
        const settle = await p2.evaluate(() => [...document.querySelectorAll("[data-testid]")]
          .map(e => e.getAttribute("data-testid"))
          .filter(x => /settlement|cover|split|bill|pay/i.test(x)));
        jr.note(`> **After pressing Check Please, the settlement sheet offers:** `
              + `\`${settle.join("\`, \`") || "nothing"}\``);
        await look(p2, jr, "diner-2", id, "settlement sheet after Check Please");
      }
      else if (!bubble.open)
        throw new Error(`I could not open the service bubble, so I cannot say whether a diner is `
          + `offered a way to ask for the bill. My harness. Controls seen: `
          + `${bubble.actions.join(", ") || "none"}.`);
      // The cart carries a SCOPE CHIP per person at the table (cart-scope-chip-<uuid>). Picking
      // another diner is the one state where "pay for them" would plausibly live, and I had never
      // driven it. Absence in the all-scope view says nothing about the per-person view - that is
      // the same wrong-surface mistake one level down. Drive it, then judge.
      await p2.keyboard.press("Escape").catch(() => {});
      await p2.waitForTimeout(800);
      await openSheet(p2);
      await p2.waitForTimeout(DWELL);
      const chips = await p2.evaluate(() => [...document.querySelectorAll('[data-testid^="cart-scope-chip-"]')]
        .map(e => ({ id: e.getAttribute("data-testid"),
                     text: (e.innerText || "").replace(/\s+/g, " ").trim().slice(0, 40) }))
        .filter(c => !/-all$/.test(c.id) && !/\(you\)/i.test(c.text)));
      jr.note(`> **Other people I can scope the cart to:** `
            + `${chips.map(c => c.text || c.id).join(" · ") || "NONE"}`);
      let scoped = { picked: null, controls: [] };
      if (chips.length) {
        const pick = chips[0];
        await tap(p2, p2.locator(`[data-testid="${pick.id}"]`).first());
        await p2.waitForTimeout(DWELL);
        scoped.picked = pick.text || pick.id;
        scoped.controls = await p2.evaluate(() => [...document.querySelectorAll("[data-testid]")]
          .map(e => e.getAttribute("data-testid"))
          .filter(x => /cover|settle|pay|bill|split|checkout|tab/i.test(x)));
        await look(p2, jr, "diner-2", id, `cart scoped to ${scoped.picked}`);
        jr.note(`> **Scoped the cart to ${scoped.picked} - what she is then offered:** `
              + `\`${scoped.controls.join("\`, \`") || "nothing to settle or cover"}\``);
      }
      const cover = p2.locator('[data-testid*="cover"]').first();
      if (!(await cover.count()))
        throw new Error(`Diner two is offered no way to cover anyone, even though both diners `
          + `have ordered. All-scope cart: ${before.join(", ") || "none"}. Service bubble offers: `
          + `${bubble.actions.join(", ") || "none"}. Cart scoped to ${scoped.picked || "nobody"}: `
          + `${scoped.controls.join(", ") || "nothing to settle or cover"}.`);
      await tap(p2, cover);
      await p2.waitForTimeout(DWELL);
      const t = await look(p2, jr, "diner-2", id, "cover someone");
      const options = await p2.evaluate(() => [...document.querySelectorAll('[data-testid*="cover"]')]
        .map(e => ({ id: e.getAttribute("data-testid"),
                     text: (e.innerText || "").replace(/\s+/g, " ").trim().slice(0, 60) })));
      rec.cover = options;
      jr.note(`> **Who she is offered to cover:** `
            + `${options.map(o => o.text || o.id).join(" · ") || "NOBODY"}`);
      const names = options.filter(o => o.text && !/cover/i.test(o.text));
      if (!names.length)
        throw new Error(`The cover screen opened but offers nobody to cover. Diner one HAS an `
          + `order on this table ("${rec.o1.badge}"), so there was someone to offer. Screen: ${t.slice(0, 200)}`);
      return `She is offered ${names.length} person(s) to cover: ${names.map(n => n.text).join(", ")}.`;
    });

    verdict = "PARTIAL_GREEN";
    why = "Both diners ordered for real this time, and the cover-someone screen offered a person to cover.";
  } catch (e) {
    why = e.message; verdict = "HONEST_RED";
  } finally {
    for (const [pg, ctx, name] of [[p1, c1, "diner-1"], [p2, c2, "diner-2"]]) {
      try {
        await openSheet(pg);
        const c = pg.locator('[data-testid^="cancel-item-"]').first();
        if (await c.count()) { await c.click({ timeout: 6000 }).catch(() => {}); await pg.waitForTimeout(2000); }
      } catch {}
      await closeRole(ctx, jr, name);
    }
    jr.note("> **Cleanup:** each diner cancelled their own dish where the control was still offered.");
    jr.finish(verdict, why);
  }
});
