/* global React, ReactDOM */
/* global ScreenAttract, ScreenHome, ScreenBrowse, ScreenDetail, ScreenSet, ScreenPay, KeraiCommerce */
const { useState: useStateApp, useEffect: useEffectApp, useRef: useRefApp } = React;
const KCA = KeraiCommerce;

/* Local recovery records for refunds that are still being arranged.
   Only the reference, amount, status and time are kept — never card data. */
const RECOVERY_KEY = "kerai.recovery";
const saveRecord = (record) => {
  try {
    const rs = JSON.parse(localStorage.getItem(RECOVERY_KEY)) || [];
    localStorage.setItem(RECOVERY_KEY, JSON.stringify([record, ...rs].slice(0, 20)));
  } catch {}
};

// Back to attract after this long without a touch, except while paying.
const IDLE_MS = 120000;
const THEME = "dark";

/* Plays a test outcome's card for the guest: presents it, inserts it and
   enters the PIN when the terminal asks, and takes it out when told to. */
const DRIVE_MS = { ready: 1400, insert: 1300, pin: 1600, remove: 1500 };
function driveOutcome(controller) {
  const done = new Set();
  const timers = [];
  const step = (key, ms, fn) => {
    if (done.has(key)) return;
    done.add(key);
    timers.push(setTimeout(fn, ms));
  };
  const onState = (s) => {
    const screen = KCA.screenFor(s);
    if (s.phase === "ready") step(`present:${s.attempt}`, DRIVE_MS.ready, () => controller.present());
    if (s.phase === "insert" && !s.card.inserted) step(`insert:${s.attempt}`, DRIVE_MS.insert, () => controller.insertCard());
    if (s.phase === "pin") step(`pin:${s.attempt}`, DRIVE_MS.pin, () => controller.enterPin());
    if (screen === "remove") step(`remove:${s.attempt}`, DRIVE_MS.remove, () => controller.removeCard());
  };
  onState(controller.getState());
  const unsubscribe = controller.subscribe(onState);
  return () => { unsubscribe(); timers.forEach(clearTimeout); };
}

function App() {
  const [screen, setScreen] = useStateApp("attract");
  const [history, setHistory] = useStateApp([]);
  const [browse, setBrowse] = useStateApp({ box: null, category: "all", metal: null });
  const [productId, setProductId] = useStateApp("ea-rndh");
  const [metal, setMetal] = useStateApp("white");
  const [caratId, setCaratId] = useStateApp(null);
  const [setId, setSetId] = useStateApp(null);
  const [stock, setStock] = useStateApp(KCA.baseStock);
  const [notice, setNotice] = useStateApp(null);
  const [checkout, setCheckout] = useStateApp(null);      // { controller, returnTo, outcomeId }

  const product = KCA.PRODUCT_BY_ID[productId];

  const go = (next, opts = {}) => {
    if (opts.morphFrom) window.__keraiMorph = opts.morphFrom;
    else if (next !== "detail") window.__keraiMorph = null;
    setHistory((h) => [...h, screen]);
    setScreen(next);
  };
  const back = () => {
    setHistory((h) => {
      if (h.length === 0) { setScreen("home"); return h; }
      setScreen(h[h.length - 1]);
      return h.slice(0, -1);
    });
  };
  const goHome = () => { setHistory([]); setScreen("home"); };

  const endCheckout = () => {
    setCheckout((c) => { if (c) c.controller.dispose(); return null; });
  };

  const resetToAttract = () => {
    endCheckout();
    setHistory([]);
    setNotice(null);
    setBrowse({ box: null, category: "all", metal: null });
    window.__keraiReach = false;
    // Demo machine: restock for each new guest so any piece can be bought again.
    setStock(KCA.baseStock());
    setScreen("attract");
  };

  // Keep the chosen diamond size when the gold colour changes, if it's in stock there.
  const pickCarat = (p, m, wanted, s = stock) =>
    wanted && KCA.isAvailable(KCA.skuFor(p.id, m, wanted), s) ? wanted : KCA.firstAvailableCarat(p, m, s);

  const openProduct = (p, { metal: m = metal, carat = null } = {}) => {
    const metalToUse = KCA.fromPrice(p, m, stock) != null ? m
      : (KCA.metalOptions(p, stock).find((x) => x.available) || { id: m }).id;
    setProductId(p.id);
    setMetal(metalToUse);
    setCaratId(pickCarat(p, metalToUse, carat));
    setNotice(null);
  };

  const chooseMetal = (m) => {
    setMetal(m);
    setCaratId((c) => pickCarat(product, m, c));
    setNotice(null);
  };

  const newController = (snapshot, outcome) => KCA.createSimulatedCheckout({
    snapshot,
    scenario: outcome ? KCA.outcomeScenario(outcome) : KCA.DEFAULT_SCENARIO,
    onRecord: saveRecord,
  });

  /* Continue to payment — re-check stock and price, then freeze the
     transaction snapshot and hand it to the checkout controller. */
  const startCheckout = async (request, { transition } = {}) => {
    const result = KCA.validatePurchase(request, { stock, displayedMinor: request.displayedMinor });
    if (!result.ok) {
      if (result.reason === "price_changed") setNotice(`The price is now ${KCA.formatMoney(result.amountMinor)}.`);
      else if (request.kind === "set") setNotice("This set is no longer available.");
      else setNotice("This piece is no longer available.");
      return;
    }
    setNotice(null);
    if (transition) await transition();
    setCheckout({ controller: newController(result.snapshot, null), returnTo: screen, outcomeId: null });
    go("pay");
  };

  // A test outcome restarts the same purchase under that outcome's script.
  const playOutcome = (outcome) => {
    setCheckout((c) => {
      if (!c) return c;
      const snapshot = c.controller.getState().snapshot;
      c.controller.dispose();
      return { ...c, controller: newController(snapshot, outcome), outcomeId: outcome.id };
    });
  };

  /* Follow the controller: take stock when the box is released, return
     to the product when payment is abandoned, reset once it's finished. */
  useEffectApp(() => {
    if (!checkout) return;
    const { controller, returnTo, outcomeId } = checkout;
    let vended = false;
    const onState = (s) => {
      if (s.vend === "ready" && !vended) {
        vended = true;
        setStock((st) => ({ ...st, [s.snapshot.vendSku]: Math.max(0, (st[s.snapshot.vendSku] || 0) - 1) }));
      }
      if (s.phase === "cancelled" || (s.phase === "closed" && s.payment !== "approved")) {
        endCheckout();
        setHistory((h) => h.slice(0, -1));
        setScreen(returnTo);
      } else if (s.phase === "closed") {
        resetToAttract();
      }
    };
    onState(controller.getState());
    const unsubscribe = controller.subscribe(onState);
    const stopDriving = outcomeId ? driveOutcome(controller) : () => {};
    return () => { unsubscribe(); stopDriving(); };
  }, [checkout]);

  /* Idle: a walk-away guest leaves the kiosk ready for the next one. */
  const idleRef = useRefApp(null);
  useEffectApp(() => {
    if (screen === "attract" || screen === "pay") return;
    const arm = () => {
      clearTimeout(idleRef.current);
      idleRef.current = setTimeout(resetToAttract, IDLE_MS);
    };
    arm();
    window.addEventListener("pointerdown", arm, true);
    return () => { clearTimeout(idleRef.current); window.removeEventListener("pointerdown", arm, true); };
  }, [screen]);

  /* Screens */
  const toAttract = () => resetToAttract();
  const openBrowse = (filters) => {
    setBrowse({ box: null, category: "all", metal: null, ...filters });
    window.__keraiReach = false;
    go("browse");
  };
  const openSet = (set) => {
    const v = KCA.VARIANT_BY_SKU[set.components[0]];
    openProduct(KCA.PRODUCT_BY_ID[v.productId], { metal: v.metal, carat: v.caratId });
    setSetId(set.id);
    go("set");
  };

  let content;
  switch (screen) {
    case "home":
      content = (
        <ScreenHome
          theme={THEME}
          onPickBox={(b) => openBrowse({ box: b })}
          onAllProducts={() => openBrowse({})}
          onSets={() => openBrowse({ category: "sets" })}
          onLogo={toAttract}
        />
      );
      break;
    case "browse":
      content = (
        <ScreenBrowse
          theme={THEME}
          box={browse.box}
          category={browse.category}
          metal={browse.metal}
          stock={stock}
          onChangeBox={(box) => setBrowse((b) => ({ ...b, box }))}
          onChangeCategory={(category) => setBrowse((b) => ({ ...b, category }))}
          onChangeMetal={(m) => setBrowse((b) => ({ ...b, metal: m }))}
          onBack={back}
          onHome={goHome}
          onPick={(p, m) => { openProduct(p, { metal: m }); go("detail"); }}
          onPickSet={openSet}
          onLogo={toAttract}
        />
      );
      break;
    case "detail":
      content = (
        <ScreenDetail
          key={product.id}
          theme={THEME}
          product={product}
          metal={metal}
          caratId={caratId}
          stock={stock}
          notice={notice}
          onChangeMetal={chooseMetal}
          onChangeCarat={(c) => { setCaratId(c); setNotice(null); }}
          onContinue={startCheckout}
          onBack={back}
          onHome={goHome}
          onLogo={toAttract}
        />
      );
      break;
    case "set":
      content = (
        <ScreenSet
          key={setId}
          theme={THEME}
          set={KCA.SET_BY_ID[setId]}
          fromSku={KCA.SET_BY_ID[setId].components[0]}
          stock={stock}
          notice={notice}
          onBack={() => { setNotice(null); back(); }}
          onContinue={startCheckout}
          onHome={goHome}
          onLogo={toAttract}
        />
      );
      break;
    case "pay":
      content = checkout
        ? (
          <ScreenPay
            key="pay"
            theme={THEME}
            controller={checkout.controller}
            outcomeId={checkout.outcomeId}
            onOutcome={playOutcome}
            onLeave={() => {
              endCheckout();
              setHistory([]);
              setNotice(null);
              setScreen("home");
            }}
          />
        )
        : <ScreenAttract theme={THEME} stock={stock} onTap={() => go("home")} />;
      break;
    default:
      content = <ScreenAttract theme={THEME} stock={stock} onTap={() => go("home")} />;
  }

  return <ProtoStage screen={screen}>{content}</ProtoStage>;
}

/* Stage — scales the 1080×1920 canvas to fit the viewport. */
function ProtoStage({ children, screen }) {
  const [scale, setScale] = useStateApp(1);
  useEffectApp(() => {
    const fit = () => setScale(Math.max(0.18, Math.min(window.innerWidth / 1080, window.innerHeight / 1920)));
    fit();
    window.addEventListener("resize", fit);
    return () => window.removeEventListener("resize", fit);
  }, []);
  return (
    <div style={{ position: "fixed", inset: 0, background: "#000", display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden" }}>
      <div style={{ width: 1080, height: 1920, flexShrink: 0, transform: `scale(${scale})`, transformOrigin: "center center", position: "relative", overflow: "hidden", background: "#000" }}
           data-screen-label={`screen-${screen}`}>
        {children}
      </div>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
