/* WITNESS · menu-food-tags2  (W-010 food-tags-invariant — the invariant itself)
 *
 * The first pass proved the vegetarian filter DOES something: 3 dishes became 2 and the
 * chicken went. It did NOT prove the rule this journey is actually named after:
 *
 *     a dish with NO dietary tags means "we don't know", so a vegetarian diner
 *     must NOT be shown it  (the journey's own steps: empty dietary -> na=true,
 *     then "N/A item filtered for vegetarian patron")
 *
 * So here I read, off the owner's own dish editor, which dietary pills are actually SET on
 * each of the three dishes on the Mains course — and then sit down as a vegetarian diner and
 * see which of those three survive. If a dish with no tags survives, the invariant is not
 * what a person experiences. Nothing is changed: I open dish editors and close them.
 */
import { test } from "@playwright/test";
import { journal, look, arriveAsDiner, openCourse, roleContext, deskContext, closeRole,
         signInOwner, account, OWNER, PATRON, DWELL } from "../lib-witness.mjs";
import fs from "node:fs";

const RID = "28ae37ab-706c-4e20-83d7-52f5d2dff747";   // Canary Mosaic — dev
const TABLE = 9911;
const DISHES = ["Canary Fries", "Canary Grilled Chicken", "Canary Margherita"];

const pills = page => page.evaluate(() =>
  [...document.querySelectorAll('[data-testid^="dietary-pill-"]')].map(e => ({
    tid: e.getAttribute("data-testid"),
    label: (e.innerText || "").replace(/\s+/g, " ").trim(),
    ariaPressed: e.getAttribute("aria-pressed"),
    dataSelected: e.getAttribute("data-selected") || e.getAttribute("data-state") || null,
    cls: String(e.className || "").slice(0, 160),
  })));

test("menu-food-tags2", async ({ browser }) => {
  test.setTimeout(28 * 60_000);
  const jr = journal("menu-food-tags2");
  jr.target({ ownerApp: OWNER, patronApp: PATRON, restaurantId: RID, fixture: "canary-mosaic",
              table: TABLE });
  jr.note("**Read only.** Dish editors are opened and closed; no tag is added or removed.");

  const oc = await deskContext(browser, jr, "owner");
  const pc = await roleContext(browser, jr, "patron");
  const owner = await oc.newPage(), diner = await pc.newPage();
  const state = { tags: {} };
  let verdict = "INCOMPLETE", why = "";

  try {
    await jr.step("OWNER", "I open my food library", async id => {
      const a = account("canaryOwner");
      await signInOwner(owner, a.email, a.password, RID);
      await owner.goto(`${OWNER}/owner/food-items/?restaurantId=${RID}`, { waitUntil: "domcontentloaded" });
      await owner.waitForTimeout(DWELL * 2);
      const t = await look(owner, jr, "owner", id, "my food library");
      return t.slice(0, 200);
    });

    for (const name of DISHES) {
      await jr.soft("OWNER", `I open "${name}" and read the dietary tags that are set on it`,
        async id => {
          await owner.goto(`${OWNER}/owner/food-items/?restaurantId=${RID}`, { waitUntil: "domcontentloaded" });
          await owner.waitForTimeout(DWELL * 2);
          const row = owner.locator('[data-testid^="food-library-table-row-"]', { hasText: name }).first();
          if (!(await row.count())) throw new Error(`"${name}" is not listed in my food library.`);
          await row.click({ timeout: 20000 });
          await owner.waitForTimeout(5000);
          const t = await look(owner, jr, "owner", `${id}-${name.replace(/\W+/g,"-")}`, `tags on ${name}`);
          const p = await pills(owner);
          if (!p.length) throw new Error(`The editor for "${name}" shows no dietary pills at all.`);
          // Which look chosen? Report every signal rather than guessing one.
          const chosen = p.filter(x => x.ariaPressed === "true" || /selected|active|checked|on\b/i.test(x.dataSelected || "")
                                    || /selected|active|is-on|bg-(amber|green|primary)/i.test(x.cls));
          state.tags[name] = { all: p.map(x => x.tid), chosen: chosen.map(x => x.label || x.tid),
                              sample: p.slice(0, 3) };
          jr.note(`> **${name}** — dietary pills that read as CHOSEN: `
                + `${chosen.map(x => x.label).join(" · ") || "**none**"} `
                + `(out of ${p.length} pills offered)`);
          return chosen.length
            ? `chosen: ${chosen.map(x => x.label).join(", ")}`
            : `NOTHING is selected — this dish carries no dietary tag at all.`;
        });
    }

    await jr.step("PATRON", "I sit down, say I am vegetarian, and see which of the three survive",
      async id => {
        await arriveAsDiner(diner, RID, TABLE, "mosaic_v2");
        await openCourse(diner, "Entrees");
        const names = () => diner.evaluate(() =>
          [...document.querySelectorAll('[data-testid^="m2-tile-"]')].map(e => {
            const i = e.getAttribute("data-testid").replace("m2-tile-", "");
            const x = document.querySelector(`[data-testid="m2-text-${i}"]`);
            return ((x && x.innerText) || "").replace(/\s+/g, " ").trim().split(" ").slice(0,3).join(" ");
          }));
        state.before = await names();
        await look(diner, jr, "patron", id, "Mains before");
        await diner.locator('[data-testid="m2-dock-filter"]').click({ timeout: 25000 });
        await diner.waitForTimeout(2500);
        await diner.locator('[data-testid="m2-dietary-vegetarian"]').first().click({ timeout: 20000 });
        await diner.waitForTimeout(1200);
        await diner.locator('[data-testid="m2-filter-apply"]').click({ timeout: 20000 });
        await diner.waitForTimeout(2000);
        state.after = await names();
        await look(diner, jr, "patron", id + "b", "Mains after saying vegetarian");
        jr.note(`> **Before:** ${state.before.join(" · ")} → **after "vegetarian":** `
              + `${state.after.join(" · ") || "(nothing)"}`);
        return `${state.before.length} -> ${state.after.length}`;
      });

    await jr.step("BOTH", "What the owner's screen actually shows against what the filter left me",
      async id => {
        /* NOTE — the pill "is this chosen?" reading below is NOT the oracle and must never be
           reported as one: every pill on this screen has className "" and aria-pressed null, so
           an attribute heuristic reads nothing. The OWNER SCREENSHOTS taken in step 02-04 are
           the oracle, and they are what this step's finding rests on. */
        jr.note("> **Oracle:** the owner screenshots `owner/02..04-*.png`, not the attribute scrape. "
              + "A pill's selected state on this design system is carried by styling that "
              + "`className`/`aria-pressed` do not expose.");
        jr.note(`> **Attribute scrape (unreliable, recorded only so nobody re-derives it):** `
              + Object.entries(state.tags).map(([k,v]) => `${k}: ${v.chosen.join("/") || "-"}`).join(" · "));
        return `Before: ${state.before.join(", ")} · after vegetarian: ${state.after.join(", ")}`;
      });

    await jr.soft("PATRON", "Now I say I am VEGAN — a vegetarian-but-not-vegan dish must go too",
      async id => {
        const names = () => diner.evaluate(() =>
          [...document.querySelectorAll('[data-testid^="m2-tile-"]')].map(e => {
            const i = e.getAttribute("data-testid").replace("m2-tile-", "");
            const x = document.querySelector(`[data-testid="m2-text-${i}"]`);
            return ((x && x.innerText) || "").replace(/\s+/g, " ").trim().split(" ").slice(0,3).join(" ");
          }));
        await diner.locator('[data-testid="m2-dock-filter"]').click({ timeout: 25000 });
        await diner.waitForTimeout(2500);
        const veg = diner.locator('[data-testid="m2-dietary-vegetarian"]').first();
        if (await veg.count()) { await veg.click({ timeout: 20000 }).catch(() => {}); await diner.waitForTimeout(900); }
        const vegan = diner.locator('[data-testid="m2-dietary-vegan"]').first();
        if (!(await vegan.count())) throw new Error("the filter drawer offers no Vegan choice on this course");
        await vegan.click({ timeout: 20000 });
        await diner.waitForTimeout(1200);
        await diner.locator('[data-testid="m2-filter-apply"]').click({ timeout: 20000 });
        await diner.waitForTimeout(2000);
        state.vegan = await names();
        await look(diner, jr, "patron", id, "Mains after saying vegan");
        jr.note(`> **After "vegan":** ${state.vegan.join(" · ") || "(nothing)"} — `
              + `Canary Fries is tagged Vegetarian+Vegan+Gluten-Free, Canary Margherita is `
              + `Vegetarian only (see owner/02 and owner/04). A vegan diner should keep the `
              + `first and lose the second.`);
        return `vegetarian left ${state.after.join(", ")}; vegan leaves ${state.vegan.join(", ") || "nothing"}`;
      });

    verdict = "WALKED"; why = "Compared the owner's real tags against what the vegetarian filter left me.";
  } catch (e) {
    verdict = "STOPPED"; why = String(e.message||e).split("\n")[0].slice(0,300);
  } finally {
    fs.writeFileSync(`${jr.dir}/state.json`, JSON.stringify(state, null, 1));
    jr.finish(verdict, why);
    await closeRole(pc, jr, "patron"); await closeRole(oc, jr, "owner");
  }
});
