/* global React, ReactDOM */
// Bromont 2026 — On-Air Scorebug overlay.
//
// Consumed by OBS as a Browser Source at 1920×1080 with a transparent
// background. State is broadcast from the timer app on the same origin
// via BroadcastChannel("bromont.sj-timer.v1"). Two layouts are selectable
// via query string:
//   ?layout=l3   lower-third bar (default)
//   ?layout=bug  corner scorebug, top-left
//
// Optional URL params:
//   ?demo=1      — render a fake state without listening, for design QA
//   ?safe=0|1    — show 10% title-safe guides (1 = on; default off)

const { useState, useEffect } = React;

// Console-driven channel: the operator's console at /console/sj-timer/ is the
// publisher (same machine / same origin). Nothing renders until the operator
// puts the round on air (state.onAir === true).
const BROADCAST_CHANNEL = "bromont.sj-timer.console.v1";

// --------------------------------------------------------------
// Helpers — identical math to app.jsx so the overlay looks correct
// even before a state message arrives (we render zeros at idle).
// --------------------------------------------------------------
function formatClock(ms) {
  const total = Math.max(0, ms || 0);
  const m = Math.floor(total / 60000);
  const s = Math.floor((total % 60000) / 1000);
  const t = Math.floor((total % 1000) / 100);
  return { m, s: String(s).padStart(2, "0"), t };
}
function fmtFaults(n) {
  if (n == null) return "0";
  if (Number.isInteger(n)) return String(n).padStart(2, "0");
  return n.toFixed(1);
}

// --------------------------------------------------------------
// Default / idle state — also doubles as the demo payload.
// --------------------------------------------------------------
const IDLE = {
  phase: "pre",
  rider: "Karl Slezak",
  horse: "Hot Bobo",
  country: "CAN",
  order: 14,
  totalOrder: 32,
  classLabel: "CCI4*-L",
  timeAllowed: 64,
  timeLimit: 128,
  elapsedMs: 0,
  adjustedElapsedMs: 0,
  rails: 0,
  refusals: 0,
  refusalLimit: 3,
  timeCorrection: 0,
  tFaults: 0,
  totalFaults: 0,
  overTime: false,
  nearLimit: false,
  eliminated: false,
  elimReason: null,
};

// --------------------------------------------------------------
// Subscriber hook — listens on the BroadcastChannel and exposes
// the latest state. Sends a "hello" message on mount so the timer
// app replays its current snapshot (otherwise the overlay would
// stay idle until the next state change in the timer).
// --------------------------------------------------------------
function useScorebugState(demo) {
  const [state, setState] = useState(IDLE);
  useEffect(() => {
    if (demo) return; // freeze on the IDLE payload for design QA
    if (typeof BroadcastChannel === "undefined") return;
    const ch = new BroadcastChannel(BROADCAST_CHANNEL);
    ch.onmessage = (ev) => {
      if (ev?.data?.type === "state" && ev.data.payload) {
        setState(ev.data.payload);
      }
    };
    ch.postMessage({ type: "hello" });
    return () => ch.close();
  }, [demo]);
  return state;
}

// --------------------------------------------------------------
// Status pill — LIVE / HELD / OVER / ELIM
// --------------------------------------------------------------
function StatusPill({ state }) {
  let label = "STAND BY";
  let cls = "neutral";
  if (state.eliminated) { label = "ELIMINATED"; cls = "elim"; }
  else if (state.phase === "live" && state.overTime) { label = "OVER TIME"; cls = "warn"; }
  else if (state.phase === "live") { label = "ON COURSE"; cls = "live"; }
  else if (state.phase === "paused") { label = "HELD"; cls = "held"; }
  else if (state.phase === "done") { label = "FINISHED"; cls = "done"; }
  return (
    <div className={`pill pill-${cls}`}>
      {cls === "live" && <span className="pill-dot" />}
      <span className="pill-label">{label}</span>
    </div>
  );
}

// --------------------------------------------------------------
// Lower-third — full-width bar pinned to bottom safe area.
// --------------------------------------------------------------
function LowerThird({ state }) {
  const { m, s, t } = formatClock(state.elapsedMs);
  const faults = state.eliminated ? "EL" : fmtFaults(state.totalFaults);
  return (
    <div
      className="l3"
      data-phase={state.phase}
      data-over={state.overTime ? "true" : "false"}
      data-elim={state.eliminated ? "true" : "false"}
    >
      <div className="l3-rail" />

      <div className="l3-order">
        <div className="l3-order-num">{String(state.order).padStart(2, "0")}</div>
        <div className="l3-order-of">OF {state.totalOrder}</div>
      </div>

      <div className="l3-who">
        <div className="l3-rider">{state.rider}</div>
        <div className="l3-horse">
          <span>{state.horse}</span>
          <span className="l3-dot">·</span>
          <span className="l3-class">{state.classLabel}</span>
        </div>
      </div>

      <div className="l3-flag">{state.country}</div>

      <div className="l3-divider" />

      <div className="l3-clock">
        <div className="l3-clock-meta">
          <StatusPill state={state} />
          <span className="l3-allowed">
            ALLOWED <b>{state.timeAllowed}s</b>
          </span>
        </div>
        <div className="l3-clock-num">
          <span className="min">{m}</span>
          <span className="sep">:</span>
          <span className="sec">{s}</span>
          <span className="ms">.{t}</span>
        </div>
      </div>

      <div className="l3-faults">
        <div className="l3-faults-label">FAULTS</div>
        <div className="l3-faults-num">{faults}</div>
        <div className="l3-faults-sub">
          R{state.rails} · F{state.refusals}/{state.refusalLimit}
          {state.timeCorrection > 0 && <> · +{state.timeCorrection}s</>}
        </div>
      </div>
    </div>
  );
}

// --------------------------------------------------------------
// Corner bug — compact stacked block, top-left.
// --------------------------------------------------------------
function CornerBug({ state }) {
  const { m, s, t } = formatClock(state.elapsedMs);
  const faults = state.eliminated ? "EL" : fmtFaults(state.totalFaults);
  return (
    <div
      className="bug"
      data-phase={state.phase}
      data-over={state.overTime ? "true" : "false"}
      data-elim={state.eliminated ? "true" : "false"}
    >
      <div className="bug-head">
        <StatusPill state={state} />
        <div className="bug-order">
          <span className="lbl">#</span>{String(state.order).padStart(2, "0")}
          <span className="of">/ {state.totalOrder}</span>
        </div>
      </div>

      <div className="bug-who">
        <div className="bug-rider">{state.rider}</div>
        <div className="bug-line">
          <span className="bug-horse">{state.horse}</span>
          <span className="bug-country">{state.country}</span>
        </div>
      </div>

      <div className="bug-stats">
        <div className="bug-clock">
          <span className="min">{m}</span>
          <span className="sep">:</span>
          <span className="sec">{s}</span>
          <span className="ms">.{t}</span>
        </div>
        <div className="bug-faults">
          <div className="bug-faults-num">{faults}</div>
          <div className="bug-faults-label">FAULTS</div>
        </div>
      </div>

      <div className="bug-foot">
        <span>ALLOWED <b>{state.timeAllowed}s</b></span>
        <span className="dot">·</span>
        <span>R{state.rails}</span>
        <span className="dot">·</span>
        <span>F{state.refusals}/{state.refusalLimit}</span>
        {state.timeCorrection > 0 && (
          <>
            <span className="dot">·</span>
            <span className="tc">+{state.timeCorrection}s</span>
          </>
        )}
        <span className="bug-class">{state.classLabel}</span>
      </div>
    </div>
  );
}

// --------------------------------------------------------------
// Mount
// --------------------------------------------------------------
function App() {
  const params = new URLSearchParams(location.search);
  const layout = (params.get("layout") || "l3").toLowerCase();
  const demo = params.get("demo") === "1";
  const safe = params.get("safe") === "1";
  const state = useScorebugState(demo);

  // The console gates what reaches air: render nothing until onAir (demo overrides).
  const visible = demo || state.onAir;

  return (
    <div className="overlay-root" data-layout={layout}>
      {visible && (layout === "bug" ? <CornerBug state={state} /> : <LowerThird state={state} />)}
      {safe && <div className="title-safe" aria-hidden="true" />}
    </div>
  );
}

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