/* global React, Icon, Button, Badge, StatusDot, Avatar, Card, IOSScreen, ProgressBar, DATA */
// ============================================================
// Vue device unique — écran live + télécommande + tâches
// ============================================================
function DeviceView({ device, user, onBack, onLogout, admin, embedded }) {
  const { useState, useRef, useEffect } = React;
  if (window.useLang) useLang();
  const [actions, setActions] = useState([
    { t: "ready", label: "Stream connecté", at: now() },
  ]);
  const [busyBtn, setBusyBtn] = useState(null);
  // FPS : persiste le choix de l'utilisateur TANT QUE l'onglet est ouvert
  // (sessionStorage). Onglet fermé -> retombe à 5 au prochain lancement
  // (économie : et de toute façon, page fermée = plus aucune capture côté backend).
  const [fps, _setFps] = useState(() => {
    if (device.live) { try { const v = parseInt(sessionStorage.getItem("tampro.fps"), 10); if (v >= 1 && v <= 30) return v; } catch (e) {} return 15; }
    return offlineFps(device);
  });
  const setFps = (v) => { _setFps(v); try { sessionStorage.setItem("tampro.fps", String(v)); } catch (e) {} };
  const swipeRef = useRef(null);
  const locked = device.status === "locked", offline = device.status === "offline";
  const dead = locked || offline;

  // --- LIVE : FLUX CONTINU (MJPEG) pour la FLUIDITÉ + image HD "au repos" pour la NETTETÉ ---
  // Le flux pousse les images sur une connexion longue (le tunnel ne plafonne plus le fps).
  // Dès qu'on arrête de bouger ~1,2 s, on superpose UNE image haute résolution (net au repos),
  // masquée pendant l'interaction (où le flux fluide prend le dessus). = le meilleur des deux.
  const liveDev = !!device.live && !!device.udid;
  const [streamSrc, setStreamSrc] = useState(null);
  const [hqSrc, setHqSrc] = useState(null);      // image HD superposée quand on ne bouge pas
  const [feedLost, setFeedLost] = useState(false);
  const reconnRef = useRef(0);
  const lastTouchRef = useRef(0);
  const buildSrc = () => window.Backend.streamUrl(device.udid, fps) + "&r=" + reconnRef.current;
  useEffect(() => {
    if (!liveDev || dead) { setStreamSrc(null); setFeedLost(false); return; }
    let on = true;
    (async () => {
      if (!window.Backend.devbase()) { try { await window.Backend.resolveBackend(); } catch (e) {} }
      if (on) { setFeedLost(false); reconnRef.current++; setStreamSrc(buildSrc()); }
    })();
    return () => { on = false; };
  }, [liveDev, dead, fps, device.udid]);
  // "Net au repos" : sans interaction depuis 1,2 s -> on récupère UNE image HD (hq).
  const IDLE_MS = 1200;
  useEffect(() => {
    if (!liveDev || dead) { setHqSrc(h => { if (h) URL.revokeObjectURL(h); return null; }); return; }
    let on = true, fetching = false, doneForThisIdle = false;
    const tick = setInterval(async () => {
      if ((Date.now() - lastTouchRef.current) <= IDLE_MS) { doneForThisIdle = false; return; }
      if (doneForThisIdle || fetching) return;       // une seule HD par période de repos
      fetching = true;
      try {
        if (!window.Backend.devbase()) await window.Backend.resolveBackend();
        const r = await fetch(window.Backend.screenUrl(device.udid, fps, true), { cache: "no-store" });
        if (on && r.ok) { const b = await r.blob(); if (on && b.size > 0) {
          const u = URL.createObjectURL(b); setHqSrc(prev => { if (prev) URL.revokeObjectURL(prev); return u; }); doneForThisIdle = true; } }
      } catch (e) {} finally { fetching = false; }
    }, 500);
    return () => { on = false; clearInterval(tick); };
  }, [liveDev, dead, fps, device.udid]);
  // reconnexion auto si le flux casse (tunnel qui hoquette, iPhone re-verrouillé, URL changée…)
  const reconnect = async () => {
    if (!liveDev || dead) return;
    reconnRef.current++;
    try { await window.Backend.resolveBackend(); } catch (e) {}
    setStreamSrc(buildSrc());
  };
  const onFeedError = () => { setFeedLost(true); setTimeout(() => reconnect(), 1000); };
  const onFeedOk = () => setFeedLost(false);
  const liveSrc = (liveDev && !dead) ? streamSrc : null;
  const connecting = liveDev && !dead && !streamSrc;

  // une interaction -> on masque la HD (le flux fluide reprend) et on réarme le "repos"
  const touch = () => { lastTouchRef.current = Date.now(); setHqSrc(h => { if (h) URL.revokeObjectURL(h); return null; }); };
  const push = (label, meta) => setActions(a => [{ t: "act", label, meta, at: now() }, ...a].slice(0, 30));

  // mapping d'un bouton de télécommande -> vraie commande HC BOX (via backend)
  const SWIPES = { up: [.5, .7, .5, .3], down: [.5, .3, .5, .7], left: [.8, .5, .2, .5], right: [.2, .5, .8, .5], scrollUp: [.5, .3, .5, .7], scrollDown: [.5, .7, .5, .3], back: [.02, .5, .6, .5] };
  const DIR2 = { "→": [.2, .5, .8, .5], "←": [.8, .5, .2, .5], "↓": [.5, .3, .5, .7], "↑": [.5, .7, .5, .3] };
  const backendCtrl = (btnId, meta) => {
    if (!liveDev) return;
    const u = device.udid;
    if (btnId === "home") window.Backend.action(u, "home");
    else if (btnId === "refresh") { reconnect(); }
    else if (SWIPES[btnId]) { const [x, y, ex, ey] = SWIPES[btnId]; window.Backend.action(u, "swipe", { x, y, endX: ex, endY: ey, duration: 0.3 }); }
    else if (btnId === "kb") { if (meta) window.Backend.action(u, "inputText", { content: meta }); }
    else if (meta && /\w+\.\w+/.test(meta)) window.Backend.action(u, "launchApp", { content: meta }); // bundle id d'app
  };

  const act = (label, btnId, meta) => {
    if (dead && label !== "Refresh") return;
    touch();
    if (btnId) { setBusyBtn(btnId); setTimeout(() => setBusyBtn(null), 360); }
    backendCtrl(btnId, meta);
    push(label, meta);
  };

  // swipe / tap sur l'écran
  const onDown = (e) => { swipeRef.current = { x: e.clientX, y: e.clientY }; touch(); };
  const onUp = (e) => {
    const s = swipeRef.current; swipeRef.current = null;
    if (!s) return;
    const dx = e.clientX - s.x, dy = e.clientY - s.y;
    if (Math.abs(dx) < 22 && Math.abs(dy) < 22) return; // c'est un tap (géré par onTap)
    const dir = Math.abs(dx) > Math.abs(dy) ? (dx > 0 ? "→" : "←") : (dy > 0 ? "↓" : "↑");
    if (liveDev && DIR2[dir]) { const [x, y, ex, ey] = DIR2[dir]; window.Backend.action(device.udid, "swipe", { x, y, endX: ex, endY: ey, duration: 0.3 }); }
    act(`Swipe ${dir}`, null, "geste écran");
  };
  const onTapScreen = (p) => {
    if (liveDev) window.Backend.action(device.udid, "click", { x: p.x, y: p.y, duration: 0.1 });
    act("Tap", null, `(${p.x}, ${p.y})`);
  };

  return (
    <div style={{ minHeight: embedded ? 0 : "100%", display: "flex", flexDirection: "column" }}>
      {/* barre device */}
      {!embedded && <header style={{ position: "sticky", top: 0, zIndex: 20, display: "flex", alignItems: "center", gap: 14, padding: "11px 20px",
        background: "color-mix(in srgb, var(--bg) 88%, transparent)", backdropFilter: "blur(14px)", borderBottom: "1px solid var(--border)" }}>
        <button onClick={onBack} style={{ display: "flex", alignItems: "center", gap: 7, background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 9, padding: "8px 12px", color: "var(--text)", fontSize: 13, fontWeight: 600 }}>
          {React.createElement(Icon.back, { size: 17 })} {admin ? "Parc" : "Devices"}
        </button>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <StatusDot status={device.status} pulse />
          <div style={{ lineHeight: 1.2 }}>
            <div style={{ fontWeight: 700, fontSize: 15 }}>{device.tag} <span className="mono" style={{ fontWeight: 400, color: "var(--faint)", fontSize: 12 }}>· {device.name}</span></div>
            <div className="mono" style={{ fontSize: 10.5, color: "var(--faint)" }}>{device.udid}</div>
          </div>
        </div>
        <div style={{ flex: 1 }} />
        <div style={{ display: "flex", alignItems: "center", gap: 8 }} className="dv-stats">
          <Stat icon="grid" label="résolution" value="390×844" />
          <Stat icon="bolt" label="latence" value={`${device.latency} ms`} tone={device.latency > 120 ? "amber" : "green"} />
          <Stat icon="play" label="fps" value={offline ? "0" : fps} tone={!offline && fps > 16 ? "amber" : "green"} />
          <Stat icon="battery" label="batterie" value={`${device.battery}%`} tone={device.battery < 35 ? "amber" : "green"} />
        </div>
        <button style={{ background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 9, width: 38, height: 38, display: "grid", placeItems: "center", color: "var(--muted)" }}>
          {React.createElement(Icon.fullscreen, { size: 18 })}
        </button>
        {!admin && <OperatorBell user={user} />}
      </header>}

      <div style={{ display: "grid", gridTemplateColumns: embedded ? "minmax(0, 0.92fr) auto minmax(0, 1.25fr)" : "330px minmax(300px, 1fr) 392px", gap: embedded ? 10 : 22, padding: embedded ? 0 : "22px 22px 40px", maxWidth: embedded ? "none" : 1500, margin: "0 auto", width: "100%", alignItems: "start" }} className="dv-grid app-fade">
        {/* gauche : tâches + journal (en split/embedded on montre les tâches aussi) */}
        <div className="dv-tasks">
          {(!admin || embedded) && <>
            <div style={{ display: "flex", alignItems: "center", gap: 8, margin: "2px 2px 13px", color: "var(--muted)" }}>
              {React.createElement(Icon.clipboard, { size: 16 })}
              <span style={{ fontSize: 12, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase" }}>Mes tâches</span>
            </div>
            <MyTasks device={device} user={user} compact={embedded} />
          </>}
          <div style={{ display: "flex", alignItems: "center", gap: 8, margin: admin ? "2px 2px 13px" : "20px 2px 13px", color: "var(--muted)" }}>
            {React.createElement(Icon.list, { size: 16 })}
            <span style={{ fontSize: 12, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase" }}>Journal</span>
          </div>
          <ActionLog actions={actions} />
        </div>

        {/* centre : écran téléphone */}
        <div className="dv-phone" style={{ display: "grid", placeItems: "center", position: embedded ? "static" : "sticky", top: 78 }}>
          <Phone device={device} w={embedded ? "clamp(180px, 18.5vw, 300px)" : undefined} big={!embedded} liveSrc={liveSrc} hqSrc={hqSrc} lost={feedLost} connecting={connecting} onDown={onDown} onUp={onUp} onTap={onTapScreen} onFeedError={onFeedError} onFeedOk={onFeedOk} />
          <div className="mono" style={{ marginTop: 14, fontSize: 11.5, color: "var(--faint)", display: "flex", alignItems: "center", gap: 8, textAlign: "center", maxWidth: 300 }}>
            <span style={{ width: 6, height: 6, borderRadius: 99, background: dead ? "var(--faint)" : "var(--accent)", animation: dead ? "none" : "streamPulse 1.2s infinite", flex: "none" }} />
            {dead ? "flux interrompu" : t("tap_swipe_hint")}
          </div>
          {liveDev && <button onClick={() => reconnect()} title="Reconnecter le flux de l'écran"
            style={{ marginTop: 12, display: "inline-flex", alignItems: "center", gap: 8, background: "var(--surface-2)", border: "1px solid var(--border-2)", borderRadius: 9, padding: "8px 14px", color: "var(--text)", fontSize: 13, fontWeight: 600, cursor: "pointer" }}>
            {React.createElement(Icon.refresh, { size: 16 })} {t("refresh_screen")}</button>}
          <div style={{ marginTop: 14, width: embedded ? "100%" : "min(330px, 78vw)" }}>
            <RecordButton device={device} by={user} dead={dead} />
          </div>
        </div>

        {/* droite : télécommande */}
        <div className="dv-remote">
          <div style={{ display: "flex", alignItems: "center", gap: 8, margin: "2px 2px 13px", color: "var(--muted)" }}>
            {React.createElement(Icon.apps, { size: 16 })}
            <span style={{ fontSize: 12, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase" }}>{t("remote")}</span>
          </div>
          <Remote act={act} busyBtn={busyBtn} dead={dead} actions={actions} fps={fps} setFps={setFps} compact={embedded} device={device} />
        </div>
      </div>
    </div>
  );
}

function Phone({ device, onDown, onUp, onTap, w, big = true, liveSrc, hqSrc, lost, connecting, onFeedError, onFeedOk }) {
  const [imgErr, setImgErr] = React.useState(false);
  // expose l'<img> du flux pour l'enregistrement (recordings.jsx dessine cet élément)
  const setLiveImg = (el) => {
    window.__LIVE_IMG = window.__LIVE_IMG || {};
    if (device && device.udid) { if (el) window.__LIVE_IMG[device.udid] = el; else delete window.__LIVE_IMG[device.udid]; }
  };
  const imgTap = (e) => {
    if (!onTap) return;
    const r = e.currentTarget.getBoundingClientRect();
    onTap({ x: +((e.clientX - r.left) / r.width).toFixed(3), y: +((e.clientY - r.top) / r.height).toFixed(3) });
  };
  return (
    <div style={{ position: "relative", width: w || "min(330px, 78vw)", aspectRatio: "390 / 844", flex: "none" }}>
      {/* bezel */}
      <div style={{ position: "absolute", inset: 0, borderRadius: 52, background: "linear-gradient(160deg,#2a3247,#11151f)", padding: 12,
        boxShadow: "0 30px 70px rgba(0,0,0,.55), inset 0 0 0 1.5px rgba(255,255,255,.05)" }}>
        <div style={{ position: "relative", width: "100%", height: "100%", borderRadius: 42, overflow: "hidden", background: "#000",
          containerType: "inline-size" }} onMouseDown={onDown} onMouseUp={onUp}>
          {liveSrc
            ? <>
                {/* la dernière frame reste affichée même pendant un raté du tunnel */}
                <img src={liveSrc} draggable={false} crossOrigin="anonymous" ref={setLiveImg} onClick={imgTap}
                  onError={() => { setImgErr(true); if (onFeedError) onFeedError(); }}
                  onLoad={() => { setImgErr(false); if (onFeedOk) onFeedOk(); }}
                  style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block", cursor: onTap ? "crosshair" : "default", background: "#0b0f17", filter: lost ? "grayscale(.6) brightness(.55)" : "none", transition: "filter .3s" }} />
                {/* "net au repos" : image HD superposée quand on ne bouge pas (clics traversent -> pointerEvents none) */}
                <img src={hqSrc || ""} draggable={false} alt=""
                  style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: hqSrc ? "block" : "none", pointerEvents: "none", zIndex: 2, background: "#0b0f17" }} />
                {(lost || imgErr) && <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", color: "#cdd3df", textAlign: "center", padding: 16, gap: 8, pointerEvents: "none", zIndex: 3 }}>
                  <div style={{ background: "rgba(7,10,18,.72)", borderRadius: 12, padding: "12px 14px", fontSize: 12, lineHeight: 1.5 }}>
                    {React.createElement(Icon.refresh, { size: 20 })}<br />{t("unstable_conn")}<br /><span style={{ color: "#9aa", fontSize: 11 }}>(si ça persiste : iPhone verrouillé ?)</span>
                  </div>
                </div>}
              </>
            : connecting
              ? <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", color: "#9aa", textAlign: "center", padding: 16, background: "#0b0f17" }}>
                  <div className="mono" style={{ fontSize: 12 }}>{t("connecting_screen")}</div>
                </div>
              : <IOSScreen device={device} big={big} onTap={onTap} />}
          {/* dynamic island — UNIQUEMENT sur l'écran fictif (le flux réel contient déjà la vraie barre de statut) */}
          {!liveSrc && !connecting && device.status !== "offline" && (
            <div style={{ position: "absolute", top: big ? 12 : "1.6%", left: "50%", transform: "translateX(-50%)", width: big ? 96 : "28%", height: big ? 28 : "3.8%", background: "#000", borderRadius: 99, zIndex: 8, display: "var(--island, block)" }} />
          )}
        </div>
      </div>
      {/* boutons physiques */}
      <div style={{ position: "absolute", left: -3, top: "20%", width: 3, height: 38, background: "#1b2233", borderRadius: 3 }} />
      <div style={{ position: "absolute", left: -3, top: "30%", width: 3, height: 58, background: "#1b2233", borderRadius: 3 }} />
      <div style={{ position: "absolute", right: -3, top: "26%", width: 3, height: 74, background: "#1b2233", borderRadius: 3 }} />
    </div>
  );
}

function Stat({ icon, label, value, tone }) {
  const c = tone === "amber" ? "var(--busy)" : tone === "green" ? "var(--accent)" : "var(--text)";
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 7, padding: "6px 11px", background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 9 }}>
      {React.createElement(Icon[icon], { size: 15, color: "var(--faint)" })}
      <div style={{ lineHeight: 1.15 }}>
        <div className="num" style={{ fontSize: 12.5, fontWeight: 700, color: c }}>{value}</div>
        <div className="mono" style={{ fontSize: 8.5, color: "var(--faint)", letterSpacing: ".06em", textTransform: "uppercase" }}>{label}</div>
      </div>
    </div>
  );
}

function now() {
  const d = new Date();
  return [d.getHours(), d.getMinutes(), d.getSeconds()].map(n => String(n).padStart(2, "0")).join(":");
}

// fps de départ (clampé à la plage du curseur), 0 si hors-ligne
function offlineFps(device) {
  if (device.status === "offline") return 0;
  return Math.min(20, Math.max(1, device.fps || 8));
}

Object.assign(window, { DeviceView, Phone, Stat, now });
