/* WITNESS · menu-parity  (W-051 mosaic_v2-menu-parity)
 *
 * THE PROMISE has two halves. One is about a dish appearing under the sub-category pill its
 * owner filed it under rather than its canonical course. The other — step 4-6 — is that an
 * ADD-ON can never be put on a menu, and never renders as its own tile to a diner.
 *
 * The add-on half is checkable without provisioning anything: I read the add-on names off
 * the owner's own Food Items screen, then walk all four courses as a diner and confirm none
 * of them is on the menu as a dish. Nothing is created or changed.
 */
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 = 9951;
const COURSES = ["Beverages", "Appetizers", "Entrees", "Desserts"];

const tileNames = 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}"]`);
    return ((txt && txt.innerText) || "").replace(/\s+/g, " ").trim().slice(0, 60);
  }));

test("menu-parity", async ({ browser }) => {
  test.setTimeout(28 * 60_000);
  const jr = journal("menu-parity");
  jr.target({ ownerApp: OWNER, patronApp: PATRON, restaurantId: RID, fixture: "canary-mosaic",
              table: TABLE });
  jr.note("**Read only.**");

  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 list my add-ons — the things that are never a dish on their own",
      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 addons = owner.getByText(/^Add-ons\b/).first();
        if (await addons.count()) { await addons.click({ timeout: 15000 }).catch(()=>{}); await owner.waitForTimeout(4500); }
        const t = await look(owner, jr, "owner", id, "my add-ons");
        state.addonRows = await owner.evaluate(() =>
          [...document.querySelectorAll('[data-testid^="food-library-visibility-toggle-"]')]
            .map(x => { const r = x.closest("tr"); return (r?.innerText||"").replace(/\s+/g," ").trim().split(" Off ")[0].slice(0,50); })
            .filter(Boolean));
        fs.writeFileSync(`${jr.dir}/owner-addons.txt`, t);
        jr.note(`> **My add-ons:** ${state.addonRows.join(" · ") || "(none listed)"}`);
        if (!state.addonRows.length) throw new Error(`No add-on rows listed. Screen: ${t.slice(0,220)}`);
        return `${state.addonRows.length} add-ons: ${state.addonRows.join(", ")}`;
      });

    await jr.step("PATRON", "I walk all four courses and write down every dish on the menu",
      async id => {
        await arriveAsDiner(diner, RID, TABLE, "mosaic_v2");
        state.byCourse = {};
        for (const c of COURSES) {
          if (c !== "Beverages") await openCourse(diner, c);
          await look(diner, jr, "patron", `${id}-${c}`, `course ${c}`);
          state.byCourse[c] = await tileNames(diner);
        }
        const all = Object.entries(state.byCourse)
          .map(([c, n]) => `${c}(${n.length}): ${n.join(", ")}`).join("  ·  ");
        jr.note(`> **The whole menu as a diner sees it:** ${all}`);
        return all.slice(0, 400);
      });

    await jr.step("PATRON", "None of my add-ons is sitting on the menu as a dish", async id => {
      const menu = Object.values(state.byCourse).flat().map(s => s.toLowerCase());
      const leaked = state.addonRows.filter(a => {
        const k = a.toLowerCase().split(" ").slice(0, 3).join(" ");
        return k.length > 4 && menu.some(m => m.includes(k));
      });
      state.leaked = leaked;
      jr.note(`> **Add-ons that leaked onto the diner's menu as their own tile:** `
            + `${leaked.join(" · ") || "**none**"}`);
      if (leaked.length) throw new Error(
        `${leaked.length} add-on(s) are on the menu as if they were dishes: ${leaked.join(", ")}`);
      return `None of the ${state.addonRows.length} add-ons appears as a dish tile in any of the `
           + `four courses.`;
    });

    await jr.soft("PATRON", "I can tell which sub-category pill each dish is filed under",
      async id => {
        const jumps = await diner.evaluate(() =>
          [...document.querySelectorAll('[data-testid^="m2-subcat-jump-"]')]
            .map(e => `${e.getAttribute("data-testid")}="${(e.innerText||"").replace(/\s+/g," ").trim()}"`));
        state.pills = jumps;
        jr.note(`> **Sub-category pills on this course:** ${jumps.join(" · ") || "none"}`);
        if (!jumps.length) throw new Error("no sub-category pills on this course to check a dish against");
        return jumps.join(", ");
      });

    verdict = "WALKED"; why = "Checked the add-on half of the parity promise from both sides.";
  } 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");
  }
});
