/* WITNESS · dietary-filters-mosaic
 *   W-039 mosaic_v2-dietary-carousel  (positive: the filters really filter)
 *   W-038 mosaic_v2-custom-dietary / W-050 mosaic_v2-lifestyle-filter  (negative: no Jain chip)
 *
 * WHY THIS WALK EXISTS
 * --------------------
 * All three journeys run against ONE shared restaurant, Canary Mosaic. Two of them provision
 * their own dishes and tear them down within 3-24 seconds, so the thing they assert is not
 * visible to a person. This walk does the part that IS honest to walk:
 *   - the filter path end to end on DURABLE dishes, with exact before/after counts
 *   - and the plain fact that no Jain chip renders on any course
 *
 * GUARDED AGAINST A CONTAMINATED WINDOW.
 * The fixture's item count swings 15 -> 142 -> 15 as other suites provision into it. A count
 * assertion taken inside that swing is meaningless, so step 2 STOPS the walk if the baseline
 * is not what a stable fixture shows. Better no reading than a reading I cannot trust.
 *
 * NO MUTATION. Nothing carted, ordered, tagged or saved.
 */
import { test } from "@playwright/test";
import { journal, look, arriveAsDiner, closeRole, roleContext, PATRON, DWELL }
  from "../lib-witness.mjs";
import fs from "node:fs";

const RID = JSON.parse(fs.readFileSync("/workspace/uat/runs/wave0/fixtures.json","utf8"))["canary-mosaic"];
const TABLE = 9968;

async function dietaryGate(page) {
  for (let i = 0; i < 3; i++) {
    const g = page.locator('[data-testid="m2-dietary-gate"]').first();
    try { await g.waitFor({ state: "visible", timeout: 6000 }); } catch { return; }
    await page.locator('[data-testid="m2-dietary-gate-no"]').first()
              .evaluate(el => el.click()).catch(() => {});
    await g.waitFor({ state: "hidden", timeout: 8000 }).catch(() => {});
    await page.waitForTimeout(2000);
  }
}
const tap = (page, t) => page.locator(`[data-testid="${t}"]`).first()
                             .evaluate(el => el.click()).catch(() => {});

/** "Show N dishes" off the apply button. In the ZERO state the button is REMOVED from the DOM
 *  and replaced by m2-filter-blocker, so innerText() on it hangs until timeout - count first. */
async function showN(page) {
  const btn = page.locator('[data-testid="m2-filter-apply"]').first();
  if (!(await btn.count())) {
    const blocked = await page.locator('[data-testid="m2-filter-blocker"]').count();
    return { n: 0, label: blocked ? "(no apply button — the zero-dish blocker is showing instead)"
                                  : "(no apply button and no blocker)" };
  }
  const label = (await btn.innerText()).replace(/\s+/g, " ").trim();
  return { n: Number((label.match(/(\d+)/) || [])[1] ?? NaN), label };
}
async function openDrawer(page) { await tap(page, "m2-dock-filter"); await page.waitForTimeout(2500); }
async function course(page, c) { await tap(page, `m2-section-${c}`); await page.waitForTimeout(3000); }

test("dietary-filters-mosaic", async ({ browser }) => {
  const jr = journal("dietary-filters-mosaic");
  jr.target({ app: "patron (mosaic_v2)", url: PATRON, restaurantId: RID, table: TABLE,
              fixture: "canary-mosaic (shared by m2dc, m2cd and m2lf)" });
  jr.note("_Three journeys share this one restaurant. This walk checks the filters on the "
        + "dishes that are always there, and asks whether the Jain chip a diner is promised "
        + "ever appears._");
  jr.note("**No mutation:** nothing carted, ordered, tagged or saved.");

  const ctx = await roleContext(browser, jr, "patron");
  const page = await ctx.newPage();
  let verdict = "INCOMPLETE", why = "";

  try {
    await jr.step("PATRON", "I sit down and open the menu", async id => {
      await arriveAsDiner(page, RID, TABLE, "mosaic_v2").catch(() => {});
      await page.waitForTimeout(9000);
      await dietaryGate(page);
      const t = await look(page, jr, "patron", id, "arrival");
      const tiles = await page.locator('[data-testid^="m2-tile-"]').count();
      if (!tiles) throw new Error(`Nothing on the menu. Screen: ${t.slice(0,180)}`);
      return `${tiles} dish(es) on the course it opens on (drinks).`;
    });

    await jr.step("PATRON", "I go to the mains and count what is there", async id => {
      await course(page, "Entrees");
      await openDrawer(page);
      const { n, label } = await showN(page);
      await look(page, jr, "patron", id, "mains, nothing filtered");
      if (n !== 3)
        throw new Error(`Mains shows "${label}" — not the 3 a stable fixture shows. Other `
                      + `canary suites provision into this same restaurant and its item count `
                      + `swings between 15 and 142, so any count I take now is meaningless. `
                      + `STOPPING rather than recording a number I cannot trust.`);
      return `The mains offer me ${n} dishes ("${label}").`;
    });

    await jr.step("PATRON", "I ask for vegan and it narrows to one", async id => {
      await tap(page, "m2-dietary-vegan");
      await page.waitForTimeout(2500);
      const { n, label } = await showN(page);
      const t = await look(page, jr, "patron", id, "mains, vegan only");
      jr.note(`> **Mains · vegan:** the button now reads "${label}".`);
      if (n !== 1)
        throw new Error(`Asking for vegan gives me "${label}", not one dish. Screen: ${t.slice(0,180)}`);
      return `It narrows from 3 to ${n}. That is the filter doing its job.`;
    });

    await jr.step("PATRON", "and the one it keeps is the right one", async id => {
      await tap(page, "m2-filter-apply");
      await page.waitForTimeout(3500);
      await look(page, jr, "patron", id, "the vegan dish");
      const names = await page.evaluate(() =>
        [...document.querySelectorAll('[data-testid^="m2-text-"]')]
          .map(e => e.innerText.replace(/\s+/g," ").trim().split(" Canary baseline")[0])
          .filter(Boolean));
      if (!names.length) throw new Error("The menu shows me no dish names after applying.");
      jr.note(`> **What is left on the mains:** ${names.map(n => "`"+n.slice(0,40)+"`").join(", ")}`);
      return `I am left with: ${names.join(" / ").slice(0,200)}`;
    });

    await jr.soft("PATRON", "I ask to avoid dairy and it keeps the dish that never said",
      async id => {
        await openDrawer(page);
        await tap(page, "m2-dietary-vegan");            // release vegan
        await page.waitForTimeout(1500);
        await tap(page, "m2-allergen-dairy");
        await page.waitForTimeout(2500);
        const { n, label } = await showN(page);
        await look(page, jr, "patron", id, "mains, avoiding dairy");
        jr.note(`> **Mains · avoiding dairy:** "${label}".`);
        return `${n} dish(es) survive avoiding dairy. Worth knowing: this is a BROWSE filter, `
             + `not a safety one — a dish with no allergen information recorded is KEPT, not `
             + `hidden. A diner with a real allergy should not read this as "safe".`;
      });

    await jr.step("PATRON", "Now: is there a Jain option anywhere, on any course?",
      async id => {
        const found = [];
        for (const c of ["Beverages", "Appetizers", "Entrees", "Desserts"]) {
          await page.locator('[data-testid="m2-filter-close"], [data-testid="m2-filter-scrim"]')
                    .first().evaluate(el => el.click()).catch(() => {});
          await page.waitForTimeout(1200);
          await course(page, c);
          await openDrawer(page);
          const chips = await page.evaluate(() =>
            [...document.querySelectorAll('[data-testid^="m2-dietary-"],[data-testid^="m2-lifestyle-"],[data-testid^="m2-allergen-"],[data-testid^="m2-served-"]')]
              .map(e => `${e.getAttribute("data-testid")}=${(e.innerText||"").replace(/\s+/g," ").trim()}`));
          await look(page, jr, "patron", id, `${c} — what I can filter by`);
          jr.note(`> **${c}:** ${chips.length ? "`"+chips.join("`, `")+"`" : "_no chips at all_"}`);
          if (chips.some(c2 => /jain/i.test(c2))) found.push(c);
        }
        if (found.length) return `Yes — a Jain option appears on: ${found.join(", ")}.`;
        return `No. I opened the filter drawer on all four courses and there is no Jain option `
             + `on any of them — not by testid and not by label.`;
      });

    verdict = "WALKED";
    why = "Checked the filters on durable dishes and looked for Jain on every course.";
  } catch (e) {
    verdict = "STOPPED"; why = String(e.message||e).split("\n")[0].slice(0,300); throw e;
  } finally { jr.finish(verdict, why); await closeRole(ctx, jr, "patron"); }
});
