// PitstopCanvas - animates the moving IndyCar approaching, stopping, getting
// serviced, and exiting. Driven by node_times produced by the Monte Carlo
// simulator. Coordinate space: viewBox 0..W × 0..H (1200 × 500). Car image is
// the transparent IndyCar PNG bundled at assets/indycar.png.

const W = 1200, H = 500;
const CAR_W = 330, CAR_H = 220;
const CAR_X = (W - CAR_W) / 2;
const CAR_Y = 170;
const WHEEL_R = 13;
const FRONT_X_FRAC = 0.23, REAR_X_FRAC = 0.82;
const TOP_Y_FRAC = 0.24, BOTTOM_Y_FRAC = 0.76;
const JACK_PLUG_X_FRAC = 0.92, JACK_PLUG_Y_FRAC = 0.50;
const FUEL_X_FRAC = 0.55, FUEL_Y_FRAC = 0.58;
const PIT_LANE_CAR_Y = 10;
const APPROACH_DUR = 1.6;
const EXIT_DUR = 1.6;
const PIT_LANE_MARKER_Y = 110;
const STOP_X = CAR_X;

const POSITION_COLORS_PC = {
  "RF Changer": "#3b82f6",
  "LF Changer": "#22c55e",
  "RR Changer": "#f97316",
  "LR Changer": "#a855f7",
  "Jackman":    "#ef4444",
  "Fueler":     "#fbbf24",
};

function pointOnCar(xFrac, yFrac) {
  return { x: CAR_X + CAR_W * xFrac, y: CAR_Y + CAR_H * yFrac };
}
function getWheels(carX, carY) {
  const fX = carX + CAR_W * FRONT_X_FRAC;
  const rX = carX + CAR_W * REAR_X_FRAC;
  const tY = carY + CAR_H * TOP_Y_FRAC;
  const bY = carY + CAR_H * BOTTOM_Y_FRAC;
  return {
    rf: { x: fX, y: tY, label: "RF" },
    lf: { x: fX, y: bY, label: "LF" },
    rr: { x: rX, y: tY, label: "RR" },
    lr: { x: rX, y: bY, label: "LR" },
  };
}
function getWheelState(prefix, t, n) {
  const offS = n[`${prefix}_nut_off_start`] ?? 999;
  const offF = n[`${prefix}_nut_off_finish`] ?? 999;
  const pull = n[`${prefix}_pull`] ?? 999;
  const mnt = n[`${prefix}_mount`] ?? 999;
  const onS = n[`${prefix}_nut_on_start`] ?? 999;
  const onF = n[`${prefix}_nut_on_finish`] ?? 999;
  if (t < offS) return { state: "waiting", color: "#374151" };
  if (t < offF) return { state: "off", color: "#ef4444" };
  if (t < pull) return { state: "loose", color: "#f97316" };
  if (t < mnt)  return { state: "empty", color: "#1f2937" };
  if (t < onS)  return { state: "mount", color: "#3b82f6" };
  if (t < onF)  return { state: "on", color: "#22c55e" };
  return { state: "done", color: "#10b981" };
}
function getJackState(t, n) {
  const jp = n.jack_plug ?? 999, cu = n.car_up ?? 999, cd = n.car_drop ?? 999;
  if (t < jp) return { color: "#374151", state: "wait" };
  if (t < cu) return { color: "#ef4444", state: "plug" };
  if (t < cd) return { color: "#3b82f6", state: "lift" };
  return { color: "#10b981", state: "done" };
}
function getFuelState(t, n) {
  const fi = n.fuel_plug_in ?? 999, fo = n.fuel_unplug ?? 999;
  if (t < fi) return { color: "#374151", state: "wait" };
  if (t < fo) return { color: "#fbbf24", state: "fuel" };
  return { color: "#10b981", state: "done" };
}
function getCrewPos(role, t, n, carX, carY) {
  const wheels = getWheels(carX, carY);
  if (role === "Jackman") {
    const jp = n.jack_plug ?? 999, cu = n.car_up ?? 999, cd = n.car_drop ?? 999;
    const plug = pointOnCar(JACK_PLUG_X_FRAC, JACK_PLUG_Y_FRAC);
    const startX = plug.x, startY = H - 40;
    if (t < jp) {
      const f = Math.min(1, Math.max(0, t) / Math.max(jp, 0.01));
      return { x: startX + (plug.x - startX) * f, y: startY + (plug.y - startY) * f };
    }
    if (t < cu) return { x: plug.x, y: plug.y };
    if (t < cd) return { x: plug.x, y: plug.y };
    return { x: plug.x, y: plug.y, done: true };
  }
  const wheelMap = {
    "RF Changer": { wheel: wheels.rf, prefix: "rf" },
    "LF Changer": { wheel: wheels.lf, prefix: "lf" },
    "RR Changer": { wheel: wheels.rr, prefix: "rr" },
    "LR Changer": { wheel: wheels.lr, prefix: "lr" },
  };
  const info = wheelMap[role];
  if (info) {
    const offS = n[`${info.prefix}_nut_off_start`] ?? 0;
    const onF = n[`${info.prefix}_nut_on_finish`] ?? 999;
    const isTop = info.wheel.y < CAR_Y + CAR_H / 2;
    const readyY = isTop ? CAR_Y - 20 : CAR_Y + CAR_H + 20;
    if (t < offS) return { x: info.wheel.x, y: readyY };
    if (t < onF) return { x: info.wheel.x, y: info.wheel.y };
    return { x: info.wheel.x, y: readyY, done: true };
  }
  if (role === "Fueler") {
    const fi = n.fuel_plug_in ?? 0, fo = n.fuel_unplug ?? 999;
    const plug = pointOnCar(FUEL_X_FRAC, FUEL_Y_FRAC);
    const startX = CAR_X + CAR_W * 0.85, startY = H - 40;
    const walkDur = 0.6, walkStart = fi - walkDur;
    if (t < walkStart) return { x: startX, y: startY };
    if (t < fi) {
      const f = (t - walkStart) / walkDur;
      return { x: startX + (plug.x - startX) * f, y: startY + (plug.y - startY) * f };
    }
    if (t < fo) return { x: plug.x, y: plug.y };
    const f = Math.min(1, (t - fo) / 0.4);
    return { x: plug.x + (startX - plug.x) * f, y: plug.y + (startY - plug.y) * f, done: f >= 1 };
  }
  return { x: 0, y: 0 };
}

window.PitstopCanvas = function PitstopCanvas({ animationData, accentColor = "#ff2e3e" }) {
  const [t, setT] = React.useState(-APPROACH_DUR);
  const [playing, setPlaying] = React.useState(false);
  // Playback speed is locked to real-time (1x) - speed selector removed by request.
  const speed = 1;
  const lastFrame = React.useRef(null);
  const rafRef = React.useRef(0);

  const nodes = animationData?.node_times ?? {};
  const totalTime = animationData?.total_time ?? 5.0;
  const exitStart = nodes.car_goes ?? totalTime;
  const startTime = -APPROACH_DUR;
  const endTime = exitStart + EXIT_DUR;

  React.useEffect(() => {
    if (!animationData) return;
    lastFrame.current = null;
    setT(startTime);
    setPlaying(true);
  }, [animationData]);

  React.useEffect(() => {
    if (!playing || !animationData) return;
    const animate = (ts) => {
      if (lastFrame.current !== null) {
        const dt = ((ts - lastFrame.current) / 1000) * speed;
        setT((tt) => {
          const next = tt + dt;
          if (next >= endTime) { setPlaying(false); return endTime; }
          return next;
        });
      }
      lastFrame.current = ts;
      rafRef.current = requestAnimationFrame(animate);
    };
    rafRef.current = requestAnimationFrame(animate);
    return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); lastFrame.current = null; };
  }, [playing, speed, animationData, endTime]);

  const rawPos = (tt) => {
    const entryFrom = W + 400;
    const exitTo = -CAR_W - 400;
    if (tt < 0) {
      const u = Math.min(1, Math.max(0, (tt - startTime) / APPROACH_DUR));
      const cruiseFrac = 0.38, cruiseDxRatio = 0.55;
      const totalDx = STOP_X - entryFrom;
      const cruiseDx = totalDx * cruiseDxRatio;
      if (u < cruiseFrac) {
        const v = u / cruiseFrac;
        return { x: entryFrom + cruiseDx * v, y: PIT_LANE_CAR_Y };
      }
      const v = (u - cruiseFrac) / (1 - cruiseFrac);
      const ex = 1 - Math.pow(1 - v, 1.8);
      const ey = v * v * (3 - 2 * v);
      const cruiseEndX = entryFrom + cruiseDx;
      return { x: cruiseEndX + (STOP_X - cruiseEndX) * ex, y: PIT_LANE_CAR_Y + (CAR_Y - PIT_LANE_CAR_Y) * ey };
    }
    if (tt > exitStart) {
      const u = Math.min(1, (tt - exitStart) / EXIT_DUR);
      const sFrac = 0.22, cFrac = 0.42;
      const sDxR = 0.18, cDxR = 0.32;
      const totalDx = exitTo - STOP_X;
      const sDx = totalDx * sDxR, cDx = totalDx * cDxR;
      if (u < sFrac) {
        const v = u / sFrac;
        return { x: STOP_X + sDx * Math.pow(v, 1.6), y: CAR_Y };
      }
      if (u < sFrac + cFrac) {
        const v = (u - sFrac) / cFrac;
        const ey = v * v * (3 - 2 * v);
        const cStart = STOP_X + sDx;
        return { x: cStart + cDx * v, y: CAR_Y + (PIT_LANE_CAR_Y - CAR_Y) * ey };
      }
      const v = (u - sFrac - cFrac) / (1 - sFrac - cFrac);
      const cEnd = STOP_X + sDx + cDx;
      return { x: cEnd + (exitTo - cEnd) * v, y: PIT_LANE_CAR_Y };
    }
    return { x: STOP_X, y: CAR_Y };
  };

  const pos = rawPos(t);
  const carX = pos.x, carY = pos.y;
  const inMotion = t < 0 || t > exitStart;
  let rot = 0;
  if (inMotion) {
    const ahead = rawPos(t + 0.03);
    const dx = ahead.x - carX, dy = ahead.y - carY;
    const m = Math.hypot(dx, dy);
    rot = m < 0.01 ? 0 : (Math.atan2(dy, dx) * 180) / Math.PI - 180;
  }

  const carUp = nodes.car_up ?? 999, carDrop = nodes.car_drop ?? 999;
  const isLifted = t >= carUp && t < carDrop;
  const carYOffset = isLifted ? -10 : 0;
  const flashUp = t >= carUp && t < carUp + 0.55;
  const flashDrop = t >= carDrop && t < carDrop + 0.55;

  const wheels = getWheels(carX, carY);

  // Stationary phase only - crew, wheel state circles, and jack/fuel circles
  // are visible only when the car is parked in the box (t in [0, exitStart]).
  // During approach (t < 0) and exit (t > exitStart) we hide all of them so
  // they don't appear glued to the moving car body.
  const stationary = t >= 0 && t < exitStart;
  const crewRoles = ["Jackman", "RF Changer", "LF Changer", "RR Changer", "LR Changer", "Fueler"];

  return (
    <div className="pc-root">
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet" className="pc-svg">
        <defs>
          <linearGradient id="pcLane" x1="0" x2="0" y1="0" y2="1">
            <stop offset="0%" stopColor="#0a0e14" />
            <stop offset="100%" stopColor="#101822" />
          </linearGradient>
          <pattern id="pcGrid" width="60" height="60" patternUnits="userSpaceOnUse">
            <path d="M60 0 H0 V60" fill="none" stroke="#1f2937" strokeWidth="0.5" />
          </pattern>
          <filter id="pcGlow"><feGaussianBlur stdDeviation="3" /></filter>
        </defs>
        <rect x={0} y={0} width={W} height={H} fill="url(#pcLane)" />
        <rect x={0} y={0} width={W} height={H} fill="url(#pcGrid)" opacity="0.45" />
        {/* Pit wall */}
        <rect x={0} y={H - 22} width={W} height={22} fill="#1a212c" />
        <line x1={0} y1={H - 22} x2={W} y2={H - 22} stroke={accentColor} strokeWidth="1" opacity="0.7" />
        <text x={20} y={H - 7} fill="#6b7280" fontSize={10} fontFamily="ui-monospace,monospace" letterSpacing="2">PIT WALL</text>

        {/* Pit lane */}
        <line x1={0} y1={PIT_LANE_MARKER_Y} x2={W} y2={PIT_LANE_MARKER_Y} stroke="#374151" strokeDasharray="8 6" />
        <text x={20} y={PIT_LANE_MARKER_Y - 6} fill="#4b5563" fontSize={10} fontFamily="ui-monospace,monospace" letterSpacing="2">PIT LANE</text>

        {/* Pit box */}
        {(() => {
          const ti = CAR_H * 0.14, bi = CAR_H * 0.12;
          const bx = CAR_X, by = CAR_Y + ti, bw = CAR_W, bh = CAR_H - ti - bi;
          return (
            <g stroke={accentColor} strokeWidth={3} strokeLinecap="square" opacity="0.85">
              <line x1={bx} y1={by} x2={bx + bw} y2={by} />
              <line x1={bx} y1={by + bh} x2={bx + bw} y2={by + bh} />
              <line x1={bx} y1={by} x2={bx} y2={by + bh} />
            </g>
          );
        })()}

        {/* Car */}
        <g transform={`translate(0, ${carYOffset})`}>
          <image
            href="assets/indycar_sim.png"
            x={carX} y={carY}
            width={CAR_W} height={CAR_H}
            preserveAspectRatio="xMidYMid meet"
            transform={`rotate(${rot} ${carX + CAR_W / 2} ${carY + CAR_H / 2})`}
          />
          {stationary && Object.entries(wheels).map(([k, w]) => {
            const ws = getWheelState(k, t, nodes);
            return (
              <g key={k}>
                <circle cx={w.x} cy={w.y} r={WHEEL_R} fill={ws.color} fillOpacity={0.85} stroke="#fff" strokeWidth={1.4} />
                {ws.state === "done" && <text x={w.x} y={w.y + 4} textAnchor="middle" fill="#fff" fontSize={13} fontWeight="bold">✓</text>}
              </g>
            );
          })}
        </g>

        {/* Jack + fuel state circles */}
        {stationary && (() => {
          const js = getJackState(t, nodes), fs = getFuelState(t, nodes);
          const jp = pointOnCar(JACK_PLUG_X_FRAC, JACK_PLUG_Y_FRAC);
          const fp = pointOnCar(FUEL_X_FRAC, FUEL_Y_FRAC);
          return (
            <g transform={`translate(0, ${carYOffset})`}>
              <circle cx={jp.x} cy={jp.y} r={WHEEL_R} fill={js.color} fillOpacity={0.85} stroke="#fff" strokeWidth={1.4} />
              {js.state === "done" && <text x={jp.x} y={jp.y + 4} textAnchor="middle" fill="#fff" fontSize={13} fontWeight="bold">✓</text>}
              <circle cx={fp.x} cy={fp.y} r={WHEEL_R} fill={fs.color} fillOpacity={0.85} stroke="#fff" strokeWidth={1.4} />
              {fs.state === "done" && <text x={fp.x} y={fp.y + 4} textAnchor="middle" fill="#fff" fontSize={13} fontWeight="bold">✓</text>}
            </g>
          );
        })()}

        {/* Crew dots */}
        {stationary && crewRoles.map((role) => {
          const p = getCrewPos(role, t, nodes, carX, carY);
          const c = POSITION_COLORS_PC[role] || "#888";
          return (
            <g key={role}>
              <circle cx={p.x} cy={p.y} r={9} fill={c} stroke="#0b0f15" strokeWidth={1.5} opacity={p.done ? 0.7 : 1} />
              <text x={p.x} y={p.y - 13} textAnchor="middle" fill={c} fontSize={9} fontFamily="ui-monospace,monospace" fontWeight="700">
                {role.replace(" Changer", "")}
              </text>
            </g>
          );
        })}

        {/* Timer */}
        <g transform="translate(0,0)">
          <text x={W - 20} y={48} textAnchor="end" fill="#fff" fontSize={26} fontFamily="ui-monospace,monospace" fontWeight="bold">
            {t >= 0 ? `${Math.min(t, totalTime).toFixed(2)}s` : ""}
          </text>
          <text x={W - 20} y={66} textAnchor="end" fill={accentColor} fontSize={10} fontFamily="ui-monospace,monospace" letterSpacing="2">STOP TIME</text>
        </g>

        {/* Flashes - glow drawn first as backdrop, sharp text on top so the
            label reads clearly instead of looking blurred-out. */}
        {flashUp && (
          <g>
            <text x={W / 2} y={CAR_Y - 20} textAnchor="middle" fill="#22c55e" fontSize={28} fontFamily="ui-monospace,monospace" fontWeight="bold" opacity="0.55" filter="url(#pcGlow)">CAR UP</text>
            <text x={W / 2} y={CAR_Y - 20} textAnchor="middle" fill="#22c55e" fontSize={28} fontFamily="ui-monospace,monospace" fontWeight="bold">CAR UP</text>
          </g>
        )}
        {flashDrop && (
          <g>
            <text x={W / 2} y={CAR_Y - 20} textAnchor="middle" fill={accentColor} fontSize={28} fontFamily="ui-monospace,monospace" fontWeight="bold" opacity="0.55" filter="url(#pcGlow)">CAR DROP</text>
            <text x={W / 2} y={CAR_Y - 20} textAnchor="middle" fill={accentColor} fontSize={28} fontFamily="ui-monospace,monospace" fontWeight="bold">CAR DROP</text>
          </g>
        )}

      </svg>

      <div className="pc-controls">
        <button
          onClick={() => { if (t >= endTime) setT(startTime); setPlaying((p) => !p); }}
          disabled={!animationData}
          className="pc-btn pc-btn-prim"
          style={{ background: accentColor }}
        >
          {playing ? "PAUSE" : "PLAY"}
        </button>
        <button
          onClick={() => { setPlaying(false); setT(startTime); lastFrame.current = null; }}
          className="pc-btn"
        >RESET</button>
        <input
          type="range"
          min={Math.round(startTime * 1000)}
          max={Math.round(endTime * 1000)}
          value={Math.round(t * 1000)}
          onChange={(e) => { setPlaying(false); setT(parseInt(e.target.value, 10) / 1000); }}
          className="pc-scrub"
          style={{ accentColor }}
        />
        <span className="pc-time">{Math.max(0, Math.min(t, totalTime)).toFixed(2)}s</span>
      </div>
    </div>
  );
};
