/* WITNESS · menu-food-tags  (W-010 food-tags-invariant)
 *
 * THE PROMISE: a dish with NO dietary tags is treated as "we don't know", so a diner who
 * has told us they are vegetarian must not be shown it; a dish tagged vegetarian must be.
 *
 * I check the invariant WITHOUT changing anything: I read the real dietary tags off the
 * owner's own Food Items screen, then sit down as a vegetarian diner and see which of those
 * exact dishes survive the filter. The expected answer comes from the journey's own
 * statement of the rule, not from the code.
 */
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 = 9910;

/* The tile itself renders an image; the dish NAME lives in the sibling `m2-text-<id>` layer.
   Reading innerText off the tile gives you the single letter of the photo placeholder — which
   is exactly the mistake that stopped this walk the first time. */
const tiles = page => page.evaluate(() =>
  [...document.querySelectorAll('[data-testid^="m2-tile-"]')].map(e => {
    const id = e.getAttribute("data-testid").replace("m2-tile-", "");
    const txt = document.querySelector(`[data-testid="m2-text-${id}"]`);
    const name = ((txt && txt.innerText) || e.getAttribute("aria-label") || e.innerText || "")
      .replace(/\s+/g, " ").trim();
    return { tid: `m2-tile-${id}`, id, text: name.slice(0, 80) };
  }));

test("menu-food-tags", async ({ browser }) => {
  test.setTimeout(28 * 60_000);
  const jr = journal("menu-food-tags");
  jr.target({ patronApp: PATRON, ownerApp: OWNER, restaurantId: RID, fixture: "canary-mosaic",
              table: TABLE });
  jr.note("**No mutation.** I read the tags that are already there and watch the filter act on them.");

  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 = {};
  let verdict = "INCOMPLETE", why = "";

  try {
    await jr.step("OWNER", "I read which of my dishes are tagged vegetarian and which have no tag at all",
      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");
        // The Dietary filter chips on this screen are the owner's own view of the tags.
        const chips = await owner.evaluate(() =>
          [...document.querySelectorAll("[data-testid]")]
            .map(e => `${e.getAttribute("data-testid")}="${(e.innerText||"").replace(/\s+/g," ").trim().slice(0,40)}"`)
            .filter(s => /dietary|allergen/i.test(s)));
        state.ownerDietaryControls = chips;
        jr.note(`> **What my food library tells me about dietary tags:** `
              + (chips.length ? chips.join(" · ") : "**no per-dish dietary column at all — the "
                + "list shows photo, name, spice, menus, groupings and visibility only**"));
        fs.writeFileSync(`${jr.dir}/owner-food-library.txt`, t);
        return chips.length
          ? `${chips.length} dietary controls: ${chips.slice(0,8).join(", ")}`
          : `The food library has no dietary column. Columns are: `
          + `${(t.match(/PHOTO[\s\S]{0,120}/) || [""])[0].replace(/\s+/g," ")}`;
      });

    await jr.soft("OWNER", "I open one dish and read its dietary tags", async id => {
      const row = owner.locator('[data-testid^="food-library-visibility-toggle-"]').first();
      const name = await owner.evaluate(() => {
        const t = document.querySelector('[data-testid^="food-library-visibility-toggle-"]');
        const r = t?.closest("tr");
        return (r?.innerText || "").replace(/\s+/g, " ").trim().slice(0, 60);
      });
      await owner.locator("tr", { hasText: name.split(" ").slice(0,3).join(" ") }).first()
                 .click({ timeout: 20000 }).catch(() => {});
      await owner.waitForTimeout(4000);
      const t = await look(owner, jr, "owner", id, "one dish, open");
      const diet = await owner.evaluate(() =>
        [...document.querySelectorAll("[data-testid]")]
          .map(e => `${e.getAttribute("data-testid")}="${(e.innerText||"").replace(/\s+/g," ").trim().slice(0,30)}"`)
          .filter(s => /dietary|vegetarian|vegan|allergen/i.test(s)).slice(0, 25));
      state.dishDietary = diet;
      jr.note(`> **Dietary controls on the dish editor:** ${diet.join(" · ") || "none found"}`);
      if (!diet.length) throw new Error(`I opened a dish and found no dietary tagging UI. Screen: ${t.slice(0,220)}`);
      return `${diet.length} dietary controls on the dish editor.`;
    });

    await jr.step("PATRON", "I sit down and tell the menu I am vegetarian", async id => {
      await arriveAsDiner(diner, RID, TABLE, "mosaic_v2");
      const r = await openCourse(diner, "Entrees");
      state.before = await tiles(diner);
      await look(diner, jr, "patron", id, "food before I say anything");
      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())) throw new Error("There is no vegetarian choice in the filter drawer.");
      await veg.click({ timeout: 20000 });
      await diner.waitForTimeout(1200);
      await diner.locator('[data-testid="m2-filter-apply"]').click({ timeout: 20000 });
      const t = await look(diner, jr, "patron", id + "b", "food after I say I am vegetarian");
      state.after = await tiles(diner);
      jr.note(`> **${r.course}: ${state.before.length} dishes before, ${state.after.length} after "vegetarian".**`);
      jr.note(`> **Before:** ${state.before.map(x=>x.text.slice(0,24)).join(" · ")}`);
      jr.note(`> **After:** ${state.after.map(x=>x.text.slice(0,24)).join(" · ") || "(nothing)"}`);
      return `${state.before.length} -> ${state.after.length} dishes.`;
    });

    await jr.soft("PATRON", "The dishes that survive are the ones actually tagged vegetarian",
      async id => {
        const survived = state.after.map(x => x.text.split(" ").slice(0,3).join(" "));
        const removed = state.before.filter(b => !state.after.some(a => a.tid === b.tid))
                                    .map(x => x.text.split(" ").slice(0,3).join(" "));
        jr.note(`> **Survived the vegetarian filter:** ${survived.join(" · ") || "none"}`);
        jr.note(`> **Filtered out:** ${removed.join(" · ") || "none"}`);
        if (state.after.length === state.before.length)
          throw new Error(
            `Saying I am vegetarian removed nothing — all ${state.before.length} dishes are still `
          + `on my screen. Either every one of them is tagged vegetarian, or the "unknown means `
          + `hide it" rule this journey is about is not being applied to what I can see.`);
        return `${removed.length} removed, ${survived.length} kept.`;
      });

    verdict = "WALKED"; why = "Read the tags, then watched the vegetarian filter act on them.";
  } 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");
  }
});
