/* WITNESS · W-025 modifier-link — "an owner links a modifier with two options, and the diner
 *                                  sees it; unlink one option and only that one disappears"
 *
 * WHAT THE JOURNEY PROMISES:
 *   2  OWNER  links a modifier with both options onto a dish
 *   3  PATRON sees the modifier and both options on that dish
 *   4  OWNER  unlinks one option
 *   5  PATRON only that option disappears
 *   6  OWNER  unlinks the whole modifier
 *   7  PATRON the modifier is gone from the dish
 *
 * Step 1 is the canary provisioning its own throwaway dish and add-ons. I create nothing.
 *
 * WHAT I CAN HONESTLY ANSWER FROM OUTSIDE: can an owner, today, get to the screen where a
 * modifier is linked - and can a diner see a modifier on any dish that is actually on these
 * menus? I do NOT link or unlink anything: steps 4 and 6 are mutations on a shared canary
 * fixture mid-run and doing them would hand the builder a false red. Those steps are
 * unproven and this walk says nothing about them.
 */
import { test } from "@playwright/test";
import fs from "node:fs";
import { journal, look, arriveAsDiner, signInOwner, roleContext, deskContext, closeRole,
         account, DWELL, OWNER, PATRON } from "../lib-witness.mjs";

const WORKS  = ["f469b191-ec79-585d-916a-e6c497617eb8", "Canary Mosaic V2 Dining"];
const BROKEN = [
  ["e881f420-175c-52e7-b828-1736de3d87f8", "Canary Spice Mod"],
  ["e8e9a568-066e-5bee-8d48-0b54e90eb5f7", "Canary Includes"],
  ["c2cb26f6-73ca-5306-ac22-07f4131b2753", "Canary Groupings"],
  ["1268ba64-18a8-504f-b1c2-b1f5528dd97a", "Canary Grouping Dietary"],
  ["cdaf8ce3-68ef-57ab-889e-592a5e685fc9", "Canary Owner Menu"],
];
const TABLE = 9945;

test("modifier-link", async ({ browser }) => {
  const jr = journal("modifier-link");
  jr.target({ app: "owner + patron", baseUrl: OWNER, table: TABLE });
  jr.note("**Who:** the canary owner on a laptop, then a diner on a phone.");
  jr.note("_I link and unlink nothing. Steps 4 and 6 are mutations on a live canary fixture "
        + "and running them mid-cycle would hand the builder a false red. What I can answer "
        + "is whether an owner can REACH the screen at all, and whether a diner sees a "
        + "modifier on anything that is actually on these menus._");

  const cO = await deskContext(browser, jr, "owner");
  const o  = await cO.newPage();
  const ev = {};
  let verdict = "INCOMPLETE", why = "";

  try {
    await jr.step("OWNER", "I sign in", async id => {
      const a = account("canaryOwner");
      await signInOwner(o, a.email, a.password);
      return (await look(o, jr, "owner", id, "signed in")).slice(0, 140);
    });

    await jr.step("OWNER", "I open my dish list, the screen a modifier is linked from", async id => {
      await o.goto(`${OWNER}/owner/food-items/?restaurantId=${WORKS[0]}`, { waitUntil: "domcontentloaded" });
      await o.waitForTimeout(DWELL + 3000);
      const rows = await o.locator('[data-testid^="food-library-table-row"]').count();
      const t = await look(o, jr, "owner", id, "dish list");
      ev.diningRows = rows;
      if (!rows) throw new Error(`no dishes listed on ${WORKS[1]}: ${t.slice(0, 220)}`);
      return `${rows} dishes listed at ${WORKS[1]}.`;
    });

    await jr.step("OWNER", "I open one of them to edit it", async id => {
      await o.locator('[data-testid^="food-library-table-row"]').first().click({ timeout: 20000 });
      await o.waitForTimeout(DWELL);
      const tabs = (await o.locator('[data-testid^="tab-"]').allInnerTexts()).map(x => x.trim());
      ev.editorTabs = tabs;
      const t = await look(o, jr, "owner", id, "dish editor");
      if (!tabs.length) throw new Error(`clicking a dish opened no editor: ${t.slice(0, 220)}`);
      return `The editor is open, with ${tabs.length} tabs: ${tabs.map(x => `"${x}"`).join(", ")}`;
    });

    await jr.step("OWNER", "the Modifiers tab gives me a way to link a modifier and its options",
      async id => {
        const tab = o.locator('[data-testid="tab-groupings"]').first();
        if (!(await tab.count())) throw new Error("there is no Modifiers tab on this editor");
        await tab.click({ timeout: 15000 });
        await o.waitForTimeout(DWELL);
        const t = await look(o, jr, "owner", id, "modifiers tab");
        const ids = [...new Set(await o.evaluate(() =>
          [...document.querySelectorAll("[data-testid]")]
            .filter(e => e.offsetParent !== null).map(e => e.getAttribute("data-testid"))))];
        ev.modifierTabControls = ids.filter(x => /group|modifier|option|member|pricing|link|add/i.test(x));
        jr.note(`> **What the Modifiers tab puts in front of an owner:** `
              + `${ev.modifierTabControls.join(", ") || "(nothing I could name)"}`);
        const canLink = ev.modifierTabControls.some(x => /add|new|create|link/i.test(x));
        if (!canLink)
          throw new Error(`I can read the tab but there is nothing on it to link a modifier `
                        + `with. The screen says: ${t.slice(0, 300)}`);
        return `Yes — the tab is there and carries ${ev.modifierTabControls.length} controls, `
             + `including something to add one. The screen reads: ${t.slice(0, 220)}`;
      });

    await jr.step("OWNER", "the same screen works on my OTHER restaurants — an owner should be "
                         + "able to link a modifier anywhere", async id => {
      const results = [];
      for (const [rid, name] of BROKEN) {
        await o.goto(`${OWNER}/owner/food-items/?restaurantId=${rid}`, { waitUntil: "domcontentloaded" });
        await o.waitForTimeout(DWELL + 3000);
        const rows = await o.locator('[data-testid^="food-library-table-row"]').count();
        const t = (await o.locator("body").innerText()).replace(/\s+/g, " ");
        const failed = /Failed to fetch menu-item summaries/i.test(t);
        results.push(`${name}: ${failed ? "RED ERROR, no dish list" : `${rows} dishes`}`);
        await look(o, jr, "owner", `${id}-${name.replace(/\W+/g, "")}`, `dish list ${name}`);
      }
      ev.otherRestaurants = results;
      const broken = results.filter(r => r.includes("RED ERROR"));
      if (broken.length)
        throw new Error(`On ${broken.length} of my ${BROKEN.length} other restaurants the dish `
                      + `list will not load at all — the screen shows a red "Failed to fetch `
                      + `menu-item summaries for <id>" and nothing else, so there is no dish to `
                      + `open and no modifier to link. ${results.join(" · ")}`);
      return results.join(" · ");
    });
  } catch (e) {
    verdict = "STOPPED"; why = String(e.message || e).split("\n")[0].slice(0, 400);
  } finally {
    await closeRole(cO, jr, "owner");
  }

  // ---- the diner half: is there a modifier to see? -------------------------------------
  const cP = await roleContext(browser, jr, "diner");
  const p  = await cP.newPage();
  try {
    await jr.soft("PATRON", "a diner at that restaurant is offered a modifier with options on "
                          + "some dish", async id => {
      await arriveAsDiner(p, WORKS[0], TABLE, "mosaic_v2");
      const found = [];
      for (const course of ["Beverages", "Appetizers", "Entrees", "Desserts"]) {
        const b = p.locator(`[data-testid="m2-section-${course}"]`).first();
        if (!(await b.count())) continue;
        await b.evaluate(el => el.click());
        await p.waitForTimeout(3000);
        const n = await p.locator('[data-testid^="m2-tile-"]').count();
        for (let i = 0; i < n; i++) {
          await arriveAsDiner(p, WORKS[0], TABLE, "mosaic_v2");
          await p.locator(`[data-testid="m2-section-${course}"]`).first().evaluate(el => el.click());
          await p.waitForTimeout(2500);
          await p.locator('[data-testid^="m2-tile-"]').nth(i).click({ timeout: 15000 })
            .catch(() => {});
          await p.waitForTimeout(3000);
          const s = p.locator('[data-testid="m2-composition"]');
          if (!(await s.count())) continue;
          const txt = (await s.innerText()).replace(/\s+/g, " ");
          const ids = [...new Set(await s.evaluate(el =>
            [...el.querySelectorAll("[data-testid]")].map(e => e.getAttribute("data-testid"))))];
          const optionish = ids.filter(x => /group|option|modifier|member|choice/i.test(x));
          found.push(`${course}/${txt.split(" $")[0].slice(0, 34)}: `
                   + (optionish.length ? optionish.join(",") : "no options at all"));
        }
      }
      ev.dinerDishes = found;
      await look(p, jr, "diner", id, "a dish sheet");
      jr.note(`> **Every dish a diner can open here, and what it offers:** ${found.join(" · ")}`);
      if (!found.some(f => !f.endsWith("no options at all")))
        throw new Error(`Not one dish on this menu offers a diner a modifier or an option. `
                      + `There is nothing here for step 3 to be true about.`);
      return found.join(" · ");
    });

    if (verdict === "INCOMPLETE") { verdict = "WALKED"; why = "reached every screen I could"; }
  } finally {
    fs.writeFileSync(`${jr.dir}/evidence.json`, JSON.stringify(ev, null, 1));
    jr.finish(verdict, why);
    await closeRole(cP, jr, "diner");
  }
});
