/* WITNESS · pos-service-buttons (W-086)
 *
 * What the journey PROMISES (from its own 4 steps, not from the screen):
 *   1  the menu payload carries pos_experience
 *   2  service buttons are HIDDEN on a POS restaurant
 *   3  all four POS-experience keys gate together
 *   4  a NON-POS restaurant keeps its service buttons
 *
 * I previously called this a FALSE GREEN, on this reasoning: the only fixture with
 * service_buttons_hidden=true was ALSO the only fixture with zero menu items, so "hidden by
 * the gate" and "nothing renders at all" were indistinguishable. That reasoning was sound
 * for the fixture I had. It is not sound any more - there is a POS-gated restaurant with a
 * real 128-item menu, so the confound can be removed. I am walking it to find out whether my
 * own verdict was wrong.
 *
 * The comparison is DIFFERENTIAL and both sides are populated, which is what makes absence
 * mean something:
 *   Indian Aroma  128 items, POS gated      -> the bell should be absent
 *   POS Push       14 items, NOT POS gated  -> the bell should be present
 *
 * Trap (observed, not assumed): Playwright isVisible() returns TRUE for this control at
 * opacity:0, because the dock collapses rather than unmounts. So presence is asserted by
 * COUNT after expanding the dock, never by isVisible().
 */
import { test } from "@playwright/test";
import { journal, look, roleContext, closeRole, arriveAsDiner, PATRON, DWELL }
  from "../lib-witness.mjs";

const GATED   = { name: "Indian Aroma", rid: "8efb69a9-83ac-47d7-9040-6617835a443c", table: 9972 };
const UNGATED = { name: "Canary Mosaic V2 POS Push", rid: "baa315c1-2fa6-570c-bfc2-efb14cecf121", table: 9973 };
const BELL = '[data-testid="layout-service-button"]';

async function dockAndBell(page) {
  // The bell lives inside the collapsed dock. Expand it first, or "absent" just means
  // "not expanded yet" - which would be a false finding, not a real one.
  const trig = page.locator('[data-testid^="m2-dock"]').first();
  if (await trig.count()) {
    await trig.evaluate(el => el.click()).catch(() => {});
    await page.waitForTimeout(1500);
  }
  return {
    bells: await page.locator(BELL).count(),
    tiles: await page.locator('[data-testid^="m2-tile-"]').count(),
    // Any service-ish handle at all, so absence is proven across the whole surface rather
    // than for one testid I happened to pick.
    serviceish: await page.evaluate(() =>
      [...document.querySelectorAll("[data-testid]")]
        .map(e => e.getAttribute("data-testid"))
        .filter(t => /service|waiter|check|bill|bubble|cutlery|silverware/i.test(t))
        .sort()),
  };
}

test("pos-service-buttons", async ({ browser }) => {
  const jr = journal("pos-service-buttons");
  jr.target({ app: "patron", url: PATRON, gated: GATED, ungated: UNGATED });
  jr.note("_A diner at a restaurant that takes orders through its own POS should not be "
        + "offered a Call-Waiter bell. This walk compares **two restaurants that both have "
        + "real menus** - one POS-gated, one not - so that an absent button means the gate "
        + "worked, rather than meaning the menu was empty._");

  const ctx  = await roleContext(browser, jr, "patron");
  const page = await ctx.newPage();
  let gated, ungated;

  try {
    await jr.step("PATRON", "at a restaurant that is NOT on POS, I can call a waiter",
      async (id) => {
        await arriveAsDiner(page, UNGATED.rid, UNGATED.table);
        await look(page, jr, "patron", id, "non-POS restaurant menu");
        ungated = await dockAndBell(page);
        if (!ungated.tiles) throw new Error("This menu is empty, so it proves nothing.");
        if (!ungated.bells)
          throw new Error(`No service control here either (${ungated.tiles} dishes on screen). `
                        + `Without a positive control, absence elsewhere means nothing.`);
        const label = await page.locator(BELL).first().getAttribute("aria-label");
        return `${ungated.tiles} dishes on screen, and a service control is there: `
             + `"${label}". Service-ish handles present: ${ungated.serviceish.join(", ")}`;
      });

    await jr.step("PATRON", "at a restaurant that IS on POS, that bell is not offered to me",
      async (id) => {
        await arriveAsDiner(page, GATED.rid, GATED.table);
        await look(page, jr, "patron", id, "POS-gated restaurant menu");
        gated = await dockAndBell(page);
        if (!gated.tiles)
          throw new Error("This menu is empty too - the same confound that made my earlier "
                        + "verdict unsafe. Absence here would prove nothing.");
        if (gated.bells)
          throw new Error(`The bell IS offered here (${gated.bells} found) despite POS being on.`);
        return `${gated.tiles} dishes on screen - a real, populated menu - and no service `
             + `control anywhere. A sweep for service/waiter/check/bill/bubble/cutlery across `
             + `the whole page finds ${gated.serviceish.length} handles. The menu renders `
             + `normally otherwise, so this is the gate working, not a blank page.`;
      });

    await jr.step("PATRON", "the difference between the two is the bell, and only the bell",
      async (id) => {
        await look(page, jr, "patron", id, "difference");
        return `Both menus render (${ungated.tiles} dishes vs ${gated.tiles}). The non-POS `
             + `restaurant offers ${ungated.bells} service control; the POS one offers `
             + `${gated.bells}. That is the promise this journey makes, and a diner can see it.`;
      });

    jr.finish("WALKED",
      "The behaviour holds where it can actually be seen: on a POS restaurant with a real "
      + "128-item menu the Call-Waiter bell is absent, and on a populated non-POS menu it is "
      + "present. That OVERTURNS my earlier reading - I called this a false green because the "
      + "only gated fixture was empty, which made absence unprovable. With a populated gated "
      + "restaurant the confound is gone and the product does what it promises. What remains "
      + "true is narrower and still worth saying: the CANARY itself never renders a screen - "
      + "it asserts a JSON field - and it points at a fixture with zero items, so no canary "
      + "anywhere proves the diner's UI hides the button. That is a coverage gap in the test, "
      + "not a defect in the product.");
  } catch (e) {
    jr.finish("STOPPED", `I could not get further: ${String(e.message || e).split("\n")[0]}`);
    throw e;
  } finally {
    await closeRole(ctx, jr, "patron");
  }
});
