/* WITNESS · W-061 mosaic_v2-recommendations
 *
 * WHAT THE JOURNEY PROMISES (its own 11-step list): a restaurant can attach a recommendation
 * to a dish, and a diner who opens that dish is shown it ("Recommendations Render"), can add
 * it, and it becomes its OWN cart line rather than merging into the parent ("Add Pairing ->
 * Separate Cart Line"). Later steps add an ADD-ON pairing and promise its card renders too.
 *
 * WHICH RESTAURANT. The canary owner holds 17 restaurants and NONE is named for this journey;
 * six of the eleven steps are the canary creating its own throwaway parent + pairing dishes
 * over the API. So I cannot be told which fixture it drives, and I do not guess: I open every
 * dish this menu actually offers, in every course, and report what a diner is shown. If the
 * canary tears its dishes down after each run, then nothing it asserts is ever visible to a
 * person - and that is a finding about the evidence, not about me.
 *
 * I create nothing and change nothing.
 */
import { test } from "@playwright/test";
import { journal, look, arriveAsDiner, roleContext, deskContext, closeRole, openCourse, signInOwner,
         account, PATRON, OWNER, DWELL } from "../lib-witness.mjs";

const FIXTURES = [
  ["Canary Mosaic — dev",             "28ae37ab-706c-4e20-83d7-52f5d2dff747"],
  ["Canary Mosaic V2 Coverage — dev", "547c72a4-acab-57f3-a969-7fe62fbb0be6"],
];
const RID   = FIXTURES[0][1];
const TABLE = 9944;
const COURSES = ["Beverages","Appetizers","Entrees","Desserts"];

test("recs-recommendations", async ({ browser }) => {
  const jr = journal("recs-recommendations");
  jr.target({ journey: "W-061 mosaic_v2-recommendations", app: "patron (mosaic_v2)",
              baseUrl: PATRON, restaurant: "Canary Mosaic — dev", restaurantId: RID, table: TABLE });
  jr.note("**The board calls this PASSED on 11 steps.** Six of them are the canary creating its "
        + "own dishes over the API. This walk asks the only half a person can check: when I open "
        + "a dish on this menu, does the restaurant suggest anything to go with it, and does "
        + "adding that suggestion give me a second line rather than changing the first?");

  const ctx = await roleContext(browser, jr, "patron");
  const p = await ctx.newPage();
  let verdict = "INCOMPLETE", why = "", withRecs = [];

  const names = async () => (await p.locator('[data-testid^="m2-text-"]').allInnerTexts().catch(()=>[]))
                              .map(x=>x.replace(/\s+/g," ").trim().slice(0,58)).filter(Boolean);

  try {
    await jr.step("PATRON", "I sit down and see the whole menu", async id => {
      const g = await arriveAsDiner(p, RID, TABLE, "mosaic_v2");
      const menu = {};
      for (const c of COURSES) { const r = await openCourse(p, c); menu[c] = { tiles: r.tiles, names: await names() }; }
      await look(p, jr, "patron", id, "menu");
      jr.note("> **Every dish this restaurant offers me today:**\n> ```\n> "
            + Object.entries(menu).map(([k,v])=>`${k} (${v.tiles}): ${v.names.join(" · ")||"(no names read)"}`).join("\n> ")
            + "\n> ```");
      return `passed ${g.join(", ")||"no gates"}. `
           + Object.entries(menu).map(([k,v])=>`${k} ${v.tiles}`).join(", ");
    });

    await jr.step("PATRON", "I open every dish and look for something suggested to go with it",
      async id => {
        const report = [];
        for (const c of COURSES) {
          await openCourse(p, c);
          const n = await p.locator('[data-testid^="m2-tile-"]').count();
          for (let i = 0; i < n; i++) {
            const tile = p.locator('[data-testid^="m2-tile-"]').nth(i);
            const tid  = await tile.getAttribute("data-testid").catch(()=>null);
            await tile.click({ timeout: 12000 }).catch(()=>{});
            await p.waitForTimeout(3000);
            const sheet = p.locator('[data-testid="m2-composition"]').first();
            if (!(await sheet.count())) { report.push(`${c}#${i}: nothing opened`); continue; }
            // read ONLY inside the sheet - the menu shows through it
            const inSheet = (await sheet.innerText().catch(()=>"")).replace(/\s+/g," ");
            const ids = await sheet.evaluate(el => [...el.querySelectorAll("[data-testid]")]
              .map(e=>e.getAttribute("data-testid")));
            const recIds = ids.filter(t=>/recommend|pair|addon|add-on|suggest|goes-with/i.test(t));
            const recWord = /pairs? well|goes well|recommend|add-?on|suggest|complete your/i.test(inSheet);
            const name = (inSheet.match(/^[^$]{0,50}/)||[""])[0].trim();
            report.push(`${c}#${i} "${name.slice(0,34)}": recControls ${recIds.length}${recWord?" +wording":""}`);
            if (recIds.length || recWord) withRecs.push({ course: c, i, tid, name, recIds,
                                                          text: inSheet.slice(0,260) });
            await p.keyboard.press("Escape").catch(()=>{});
            await p.waitForTimeout(1500);
            const stillOpen = await p.locator('[data-testid="m2-composition"]').count();
            if (stillOpen) { await p.locator('[data-testid="m2-composition-backdrop"]').first()
                              .evaluate(e=>e.click()).catch(()=>{}); await p.waitForTimeout(1500); }
          }
        }
        await look(p, jr, "patron", id, "after opening every dish");
        jr.note("> **Dish by dish:**\n> ```\n> " + report.join("\n> ") + "\n> ```");
        if (!withRecs.length)
          return "NOTHING. I opened every dish in all four courses and not one of them suggests "
               + "anything to go with it — no pairing, no add-on, no 'pairs well with'. There is "
               + "no recommendation on this menu for a diner to be shown, so the render, the add "
               + "and the separate-cart-line promises have nothing to act on here.";
        return `${withRecs.length} dish(es) suggest something: `
             + withRecs.map(w=>`${w.name.slice(0,28)} [${w.recIds.join(",")}]`).join(" · ");
      });

    await jr.soft("OWNER", "Back of house: has this restaurant actually SET a pairing on any dish?",
      async id => {
        // A diner seeing no suggestion is only a defect if the restaurant configured one. So I
        // go and look at the owner's own Pairings tab. This distinguishes "nothing is set up"
        // from "it is set up and the diner never sees it".
        const octx = await deskContext(browser, jr, "owner");
        const op = await octx.newPage();
        const found = {};
        try {
          const a = account("canaryOwner");
          await signInOwner(op, a.email, a.password, RID);
          await op.goto(`${OWNER}/owner/food-items/?restaurantId=${RID}`, { waitUntil: "domcontentloaded" });
          await op.waitForTimeout(DWELL + 4000);
          const rows = await op.locator("tbody tr").count();
          for (let i = 0; i < Math.min(rows, 6); i++) {
            await op.locator("tbody tr").nth(i).click({ timeout: 20000 }).catch(()=>{});
            await op.waitForTimeout(4000);
            const name = (await op.locator("h1, h2").first().innerText().catch(()=>`row ${i}`)).trim().slice(0,40);
            const tabs = await op.locator('[data-testid^="tab-"]').all();
            for (const t of tabs) {
              if (/pairing/i.test((await t.innerText()).trim())) {
                await t.click({ timeout: 20000 }).catch(async()=>{ await t.evaluate(e=>e.click()); });
                break;
              }
            }
            await op.waitForTimeout(4000);
            const ids = await op.evaluate(() => [...document.querySelectorAll("[data-testid]")]
              .map(e=>e.getAttribute("data-testid")).filter(t=>/grouping-row|grouping-name|pairing/i.test(t)));
            const txt = (await op.locator("body").innerText()).replace(/\s+/g," ");
            found[name] = { pairingControls: ids.slice(0,8),
                            says: (txt.match(/Pairings?[^.]{0,180}/i)||[""])[0].slice(0,180) };
            await look(op, jr, "owner", `${id}${i}`, `pairings on ${name}`);
            await op.goBack().catch(()=>{});
            await op.waitForTimeout(3000);
          }
        } finally { await closeRole(octx, jr, "owner"); }
        jr.note("> **The owner's Pairings tab, dish by dish:**\n> ```\n> "
              + Object.entries(found).map(([k,v]) => `${k}: ${JSON.stringify(v.pairingControls)} — "${v.says}"`).join("\n> ")
              + "\n> ```");
        return Object.entries(found).map(([k,v]) => `${k}: ${v.pairingControls.length} pairing control(s)`).join(" · ");
      });

    if (withRecs.length) {
      await jr.step("PATRON", "I add the suggestion and it becomes its own line, not a change to the dish",
        async id => {
          const w = withRecs[0];
          await openCourse(p, w.course);
          await p.locator(`[data-testid="${w.tid}"]`).first().click({ timeout: 15000 });
          await p.waitForTimeout(3000);
          const sheet = p.locator('[data-testid="m2-composition"]').first();
          const before = await p.locator('[data-testid^="cart-item-"]').count();
          const recAdd = sheet.locator('[data-testid*="recommend"], [data-testid*="pair"]')
                              .locator('button, [role=button]').first();
          await recAdd.click({ timeout: 20000 }).catch(async()=>{ await recAdd.evaluate(e=>e.click()); });
          await p.waitForTimeout(DWELL);
          const t = await look(p, jr, "patron", id, "after adding the suggestion");
          return `cart lines before I added the suggestion: ${before}. Screen after: ${t.slice(0,220)}`;
        });
    }

    verdict = "WALKED"; why = withRecs.length
      ? "found and exercised a recommendation on this menu"
      : "opened every dish on this menu; none of them recommends anything, so there was nothing to exercise";
  } catch (e) {
    verdict = "STOPPED"; why = String(e.message||e).split("\n")[0].slice(0,300);
  } finally {
    jr.note("**Cleanup:** none — I created nothing and ordered nothing.");
    jr.finish(verdict, why);
    await closeRole(ctx, jr, "patron");
  }
});
