/* WITNESS · W-003 boost-special — Canary Kitchen — dev   (second, thorough pass)
 *
 * WHAT THE JOURNEY PROMISES (its own 14-step list): an owner can BOOST a dish and PIN one as
 * a chef's pick, and that changes which dishes the app puts in front of a diner
 * ("Set CK-FAV-1 Boost via API" -> "Patron Sees boost_applied=true" -> clear -> false).
 *
 * Twelve of the fourteen steps are direct API calls. I cannot grade an API contract from a
 * browser and will not pretend to. The human half of the same promise is what I walk:
 *   1. can an owner FIND a boost control?          2. a chef's-pick control?
 *   3. does a diner ever SEE the result?
 *
 * My first pass read only the default tab. That is not good enough to say a control does not
 * exist, so this pass opens EVERY tab of the dish editor, the menu builder and Personalize,
 * and reports per surface. I still change nothing: setting/clearing a boost on a live canary
 * fixture mid-run hands the builder a false red, so steps 11-14 stay UNPROVEN.
 */
import { test } from "@playwright/test";
import { journal, look, deskContext, roleContext, closeRole, signInOwner, arriveAsDiner,
         openCourse, account, OWNER, PATRON, DWELL } from "../lib-witness.mjs";

const RID   = "99e9d621-6941-4acc-8d85-50f15f3a2f2b";   // Canary Kitchen — dev
const TABLE = 9941;
const WORDS = /boost|chef|special|featured|promote|top pick|pin(ned)?|priorit/i;

test("recs-boost-special", async ({ browser }) => {
  const jr = journal("recs-boost-special");
  jr.target({ journey: "W-003 boost-special", app: "owner + patron", ownerUrl: OWNER,
              patronUrl: PATRON, restaurant: "Canary Kitchen — dev", restaurantId: RID, table: TABLE });
  jr.note("**The board calls this PASSED on 14 steps.** Twelve are API calls with no screen. "
        + "This walk asks the human half: can an owner find the boost and chef's-pick controls, "
        + "and does a diner ever see the result?");
  jr.note("_I change nothing — so the set/clear half (steps 11-14) is UNPROVEN, not failed._");

  const octx = await deskContext(browser, jr, "owner");
  const op   = await octx.newPage();
  let verdict = "INCOMPLETE", why = "", tabReport = {};

  const scan = async () => op.evaluate(() => {
    const txt = document.body.innerText.replace(/\s+/g," ");
    return { hits: [...new Set((txt.match(/boost|chef|special|featured|promote|top pick|pinned|priority/gi)||[]))],
             controls: [...document.querySelectorAll("[data-testid]")].map(e=>e.getAttribute("data-testid"))
                        .filter(t=>/boost|chef|special|feature|promote|pin|priority/i.test(t)),
             switches: document.querySelectorAll('input[type=checkbox],input[type=range],[role=switch]').length };
  });

  try {
    await jr.step("OWNER", "I sign in and land on my restaurant", async id => {
      const a = account("canaryOwner");
      await signInOwner(op, a.email, a.password, RID);
      const t = await look(op, jr, "owner", id, "signed in");
      return `owner app at ${op.url()}. Screen: ${t.slice(0,170)}`;
    });

    await jr.step("OWNER", "I open one of my dishes to promote it", async id => {
      await op.goto(`${OWNER}/owner/food-items/?restaurantId=${RID}`, { waitUntil: "domcontentloaded" });
      await op.waitForTimeout(DWELL + 4000);
      const listScan = await scan();
      jr.note(`> **On the dish LIST, words that could mean "promote":** ${listScan.hits.join(", ")||"none"}`);
      await op.locator('tbody tr').first().click({ timeout: 20000 }).catch(()=>{});
      await op.waitForTimeout(DWELL);
      const t = await look(op, jr, "owner", id, "dish editor");
      const tabs = (await op.locator('[data-testid^="tab-"]').allInnerTexts()).map(x=>x.trim());
      return `dish editor open with tabs: ${tabs.join(" | ")}. Screen: ${t.slice(0,180)}`;
    });

    await jr.step("OWNER", "I look on EVERY tab of the dish for a way to boost it or mark it a chef's pick",
      async id => {
        const tabs = await op.locator('[data-testid^="tab-"]').all();
        for (let i = 0; i < tabs.length; i++) {
          const tid   = await tabs[i].getAttribute("data-testid");
          const label = (await tabs[i].innerText()).trim();
          await tabs[i].click({ timeout: 20000 }).catch(async()=>{ await tabs[i].evaluate(e=>e.click()); });
          await op.waitForTimeout(4500);
          const s = await scan();
          tabReport[label] = s;
          await look(op, jr, "owner", `${id}${i}`, `tab ${label}`);
        }
        jr.note("> **What each tab of the dish editor offers:**\n> ```\n> "
              + Object.entries(tabReport).map(([k,v]) =>
                  `${k}: words ${JSON.stringify(v.hits)} · named controls ${JSON.stringify(v.controls)} · ${v.switches} toggles/sliders`
                ).join("\n> ") + "\n> ```");
        const anyBoost = Object.values(tabReport).some(v => v.hits.some(h=>/boost|promote|priority/i.test(h))
                                                        || v.controls.some(c=>/boost|promote|priority/i.test(c)));
        const anyChef  = Object.values(tabReport).some(v => v.hits.some(h=>/chef|special|featured|pinned|top pick/i.test(h))
                                                        || v.controls.some(c=>/chef|special|feature|pin/i.test(c)));
        return `across ${Object.keys(tabReport).length} tabs — a boost control: ${anyBoost}; `
             + `a chef's-pick control: ${anyChef}`;
      });

    await jr.step("OWNER", "I open the Boost and Chef's Special controls and read what they tell me",
      async id => {
        const out = {};
        const menusTab = (await op.locator('[data-testid^="tab-"]').all())
          .find ? null : null;   // placeholder - resolved below
        const tabs = await op.locator('[data-testid^="tab-"]').all();
        for (const t of tabs) {
          if (/menus/i.test((await t.innerText()).trim())) {
            await t.click({ timeout: 20000 }).catch(async()=>{ await t.evaluate(e=>e.click()); });
            break;
          }
        }
        await op.waitForTimeout(4500);
        for (const kind of ["boost", "chefs-special"]) {
          const btn = op.locator(`[data-testid^="placements-tab-${kind}-"]`).first();
          if (!(await btn.count())) { out[kind] = "no such control"; continue; }
          const label = (await btn.innerText()).replace(/\s+/g," ").trim();
          await btn.click({ timeout: 20000 }).catch(async()=>{ await btn.evaluate(e=>e.click()); });
          await op.waitForTimeout(4000);
          const shot = await look(op, jr, "owner", `${id}${kind[0]}`, `${kind} control`);
          out[kind] = { label, screen: shot.replace(/\s+/g," ").slice(0, 420) };
        }
        jr.note("> **What the promote controls say:**\n> ```\n> "
              + Object.entries(out).map(([k,v]) => `${k}: ${typeof v === "string" ? v : v.label + " -> " + v.screen}`).join("\n> ")
              + "\n> ```");
        return Object.entries(out).map(([k,v]) => `${k}: ${typeof v === "string" ? v : v.label}`).join(" · ");
      });

    await jr.step("OWNER", "If it is not on the dish, it is on the menu builder or Personalize",
      async id => {
        const surfaces = {};
        for (const [name, url] of [["Menu builder", `${OWNER}/owner/menu/?restaurantId=${RID}`],
                                   ["Personalize",  `${OWNER}/owner/personalize/?restaurantId=${RID}`],
                                   ["Insights · recommendations", `${OWNER}/owner/insights/recommendations/?restaurantId=${RID}`]]) {
          await op.goto(url, { waitUntil: "domcontentloaded" });
          await op.waitForTimeout(DWELL + 3000);
          surfaces[name] = await scan();
          await look(op, jr, "owner", `${id}${name[0]}`, name);
        }
        jr.note("> **The other places an owner would look:**\n> ```\n> "
              + Object.entries(surfaces).map(([k,v]) =>
                  `${k}: words ${JSON.stringify(v.hits)} · named controls ${JSON.stringify(v.controls)}`).join("\n> ")
              + "\n> ```");
        const found = Object.entries(surfaces).filter(([,v]) => v.hits.length || v.controls.length);
        return found.length ? `found something on: ${found.map(([k])=>k).join(", ")}`
                            : "none of the three mentions boosting, chef's picks or featuring a dish";
      });

    await closeRole(octx, jr, "owner");

    const pctx = await roleContext(browser, jr, "patron");
    const pp   = await pctx.newPage();
    try {
      await jr.step("PATRON", "I sit down at a table in this restaurant", async id => {
        const g = await arriveAsDiner(pp, RID, TABLE, "mosaic_v2");
        const t = await look(pp, jr, "patron", id, "menu on arrival");
        return `passed: ${g.join(", ")||"no gates"}. Menu: ${t.slice(0,140)}`;
      });

      await jr.step("PATRON", "Somewhere on this menu the restaurant tells me what it recommends",
        async id => {
          const seen = {};
          for (const c of ["Beverages","Appetizers","Entrees","Desserts"]) {
            const r = await openCourse(pp, c);
            const txt = (await pp.locator('[data-testid="m2-course-viewport"], [data-testid="m2-masonry"]')
                            .first().innerText().catch(()=>"")).replace(/\s+/g," ");
            const badges = await pp.evaluate(() => [...document.querySelectorAll("[data-testid]")]
              .map(e=>e.getAttribute("data-testid"))
              .filter(t=>/pick|chef|special|boost|feature|hero|recommend/i.test(t)));
            seen[c] = { tiles: r.tiles ?? 0,
                        words: [...new Set((txt.match(/chef|special|top pick|recommended|featured|popular/gi)||[]))],
                        badgeIds: [...new Set(badges)] };
            await look(pp, jr, "patron", `${id}${c[0]}`, `course ${c}`);
          }
          jr.note("> **What each course shows me:**\n> ```\n> "
                + Object.entries(seen).map(([k,v])=>`${k}: ${v.tiles} dishes · words ${JSON.stringify(v.words)} · controls ${JSON.stringify(v.badgeIds)}`).join("\n> ")
                + "\n> ```");
          const any = Object.values(seen).some(v => v.words.length || v.badgeIds.length);
          if (!any) return "NOTHING. Across all four courses no dish is marked as a chef's pick, "
                         + "a top pick, boosted, featured or recommended. I am shown a flat list.";
          return JSON.stringify(seen);
        });

      verdict = "WALKED"; why = "walked the owner controls and the diner surface end to end";
    } finally { await closeRole(pctx, jr, "patron"); }
  } catch (e) {
    verdict = "STOPPED"; why = String(e.message||e).split("\n")[0].slice(0,300);
  } finally {
    jr.note("**Cleanup:** none — I changed nothing.");
    jr.finish(verdict, why);
    try { await closeRole(octx, jr, "owner"); } catch {}
  }
});
