/* app.jsx — Root. */

const { useState, useReducer, useEffect, useRef, useMemo } = React;

function hexToRgb(hex) {
  const h = hex.replace("#", "");
  const n = parseInt(h.length === 3 ? h.split("").map(c => c + c).join("") : h, 16);
  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
}
function shade(hex, amt) {
  let [r, g, b] = hexToRgb(hex);
  const f = (c) => Math.max(0, Math.min(255, Math.round(c + amt * 255)));
  return `rgb(${f(r)},${f(g)},${f(b)})`;
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#ff7a00",
  "glow": 1,
  "anim": 1
}/*EDITMODE-END*/;
const ACCENTS = ["#ff7a00", "#f5b942", "#ff4d4d", "#8b5cf6", "#2dd4a7", "#3b82f6"];

function App() {
  const { Store, FX, Ic, Api } = window;
  const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
  const [state, dispatch] = useReducer(Store.reducer, undefined, Store.defaultState);
  const stateRef = useRef(state);
  stateRef.current = state;

  useEffect(() => {
    (async () => {
      try {
        const me = await Api.me();
        dispatch({ type: "BOOT", user: me.user });
        if (me.user) {
          const tk = await Api.tasks.list();
          dispatch({ type: "SET_TASKS", tasks: tk.tasks });
          try {
            const g = await Api.game.me();
            dispatch({ type: "SET_GAME", game: g.game });
          } catch (_) {}
        }
      } catch (e) {
        dispatch({ type: "BOOT", user: null });
      }
    })();
  }, []);

  useEffect(() => {
    const root = document.documentElement.style;
    root.setProperty("--accent", t.accent);
    root.setProperty("--accent-hi", shade(t.accent, 0.16));
    root.setProperty("--accent-lo", shade(t.accent, -0.22));
    root.setProperty("--accent-rgb", hexToRgb(t.accent).join(", "));
    root.setProperty("--glow", String(t.glow));
    root.setProperty("--anim", String(t.anim));
  }, [t.accent, t.glow, t.anim]);

  const [muted, setMuted] = useState(() => localStorage.getItem("ds_muted") === "1");
  useEffect(() => { FX.sfx.muted = muted; localStorage.setItem("ds_muted", muted ? "1" : "0"); }, [muted]);

  const [query, setQuery] = useState("");
  const [catFilter, setCatFilter] = useState(null);
  const [prioFilter, setPrioFilter] = useState(null);
  const [openId, setOpenId] = useState(null);
  const [modal, setModal] = useState(null);
  const [popId, setPopId] = useState(null);
  const [justLeveled, setJustLeveled] = useState(false);
  const [toasts, setToasts] = useState([]);
  const [entering, setEntering] = useState(false);
  const [settings, setSettings] = useState(null);
  const [showAdmin, setShowAdmin] = useState(false);
  const [showApprovals, setShowApprovals] = useState(false);
  const [pendingCount, setPendingCount] = useState(0);
  const [viewer, setViewer] = useState(null);

  const openTask = state.tasks.find(x => x.id === openId) || null;

  const pushToast = (e, text) => {
    const id = Store.uid();
    setToasts(ts => [...ts, { id, e, t: text }]);
    setTimeout(() => setToasts(ts => ts.map(x => x.id === id ? { ...x, out: true } : x)), 2200);
    setTimeout(() => setToasts(ts => ts.filter(x => x.id !== id)), 2600);
  };

  const celebrate = (opts = {}) => {
    const x = innerWidth / 2, y = innerHeight * 0.32;
    FX.fireConfetti(x, y, { count: opts.big ? 160 : 95, spread: opts.big ? 1.3 : 1, up: true });
    FX.sfx.play("complete");
    if (!opts.silentToast) {
      const m = Store.REINFORCE[(Math.random() * Store.REINFORCE.length) | 0];
      pushToast(m.e, opts.message || m.t);
    }
  };
  const pop = (id) => { setPopId(id); setTimeout(() => setPopId(p => (p === id ? null : p)), 520); };
  const flashLevel = () => {
    setJustLeveled(true);
    FX.sfx.play("levelup");
    setTimeout(() => setJustLeveled(false), 1300);
    setTimeout(() => FX.fireConfetti(innerWidth / 2, innerHeight * 0.4, { count: 180, spread: 1.5, up: true,
      colors: ["#f5b942", t.accent, "#ffffff", "#ffd28a"] }), 250);
    pushToast("⭐", "Subiu de nível!");
  };

  // Refs para polling e fila de upload
  const lastTasksJsonRef = useRef("");
  const pollInFlightRef = useRef(false);
  const uploadChainRef = useRef(Promise.resolve());
  const uploadsInFlightRef = useRef(0);

  // ---- POLLING: sincroniza com a equipe a cada 5s ----
  useEffect(() => {
    if (!state.loggedIn) return;
    const isEditing = () => {
      const el = document.activeElement;
      if (!el) return false;
      const tag = (el.tagName || "").toLowerCase();
      if (tag === "input" || tag === "textarea" || tag === "select") return true;
      if (el.isContentEditable) return true;
      return false;
    };
    const tick = async () => {
      if (pollInFlightRef.current) return;
      if (document.hidden) return;
      if (isEditing()) return;
      if (modal) return;
      if (uploadsInFlightRef.current > 0) return;
      if (document.querySelector(".card.ghost")) return;
      pollInFlightRef.current = true;
      try {
        const tk = await Api.tasks.list();
        const next = JSON.stringify(tk.tasks);
        if (next !== lastTasksJsonRef.current) {
          lastTasksJsonRef.current = next;
          dispatch({ type: "SET_TASKS", tasks: tk.tasks });
        }
      } catch (e) {
        if (e && e.status === 401) dispatch({ type: "LOGOUT" });
      } finally {
        pollInFlightRef.current = false;
      }
    };
    const t0 = setTimeout(tick, 1500);
    const id = setInterval(tick, 5000);
    const onVis = () => { if (!document.hidden) tick(); };
    document.addEventListener("visibilitychange", onVis);
    return () => {
      clearTimeout(t0);
      clearInterval(id);
      document.removeEventListener("visibilitychange", onVis);
    };
  }, [state.loggedIn, modal]);

  // ---- Polling do contador de pendentes (admin/gerente, a cada 30s) ----
  const canApprove = state.user && (state.user.role === "admin" || state.user.role === "gerente");
  useEffect(() => {
    if (!state.loggedIn || !canApprove) { setPendingCount(0); return; }
    let alive = true;
    const tick = async () => {
      try {
        const r = await Api.approvals.count();
        if (alive) setPendingCount(r.count || 0);
      } catch (_) {}
    };
    tick();
    const id = setInterval(tick, 30000);
    return () => { alive = false; clearInterval(id); };
  }, [state.loggedIn, canApprove]);

  const apiSafe = (fn) => async (...args) => {
    try { return await fn(...args); }
    catch (e) {
      if (e.status === 401) {
        pushToast("⚠️", "Sessão expirada");
        setTimeout(() => location.reload(), 1200);
        return;
      }
      pushToast("⚠️", e.message || "Erro");
      throw e;
    }
  };

  const handleMove = apiSafe(async (id, toStatus, toIndex) => {
    const task = stateRef.current.tasks.find(x => x.id === id);
    if (!task) return;
    const prev = task.status;
    const r = await Api.tasks.move(id, toStatus, toIndex);
    dispatch({ type: "UPSERT_TASK", task: r.task });
    if (toStatus === "done" && prev !== "done") {
      const xp = Store.xpForTask(task);
      const before = Store.levelInfo(stateRef.current.game.xp).level;
      dispatch({ type: "SET_GAME", game: { ...stateRef.current.game, xp: (stateRef.current.game.xp||0)+xp } });
      const after = Store.levelInfo(stateRef.current.game.xp + xp).level;
      pop(id);
      celebrate();
      if (after > before) setTimeout(flashLevel, 350);
    } else if (prev === "done" && toStatus !== "done") {
      const xp = Store.xpForTask(task);
      dispatch({ type: "SET_GAME", game: { ...stateRef.current.game, xp: Math.max(0,(stateRef.current.game.xp||0)-xp) } });
    } else FX.sfx.play("move");
  });

  const moveStatus = (id, toStatus) => handleMove(id, toStatus, 999);

  const createTask = apiSafe(async (data) => {
    const payload = {
      title: data.title, description: data.desc || "",
      status: data.status, category: data.category, priority: data.priority,
      thumb: data.thumb || null,
      checklist: (data.checklist || []).map(c => ({ text: c.text, done: !!c.done })),
    };
    const r = await Api.tasks.create(payload);
    dispatch({ type: "UPSERT_TASK", task: r.task });
    setModal(null);
    FX.sfx.play("click");
    pushToast("✅", "Tarefa criada!");
  });

  const updateTask = apiSafe(async (id, patch) => {
    const p = {};
    if ("title" in patch)    p.title = patch.title;
    if ("desc" in patch)     p.description = patch.desc;
    if ("status" in patch)   p.status = patch.status;
    if ("category" in patch) p.category = patch.category;
    if ("priority" in patch) p.priority = patch.priority;
    if ("progress" in patch) p.progress = patch.progress;
    if ("thumb" in patch)    p.thumb = patch.thumb;
    const r = await Api.tasks.update(id, p);
    dispatch({ type: "UPSERT_TASK", task: r.task });
  });

  const deleteTask = apiSafe(async (id) => {
    await Api.tasks.remove(id);
    dispatch({ type: "REMOVE_TASK", id });
    if (openId === id) setOpenId(null);
    pushToast("🗑️", "Tarefa removida");
  });

  // ---- DetailPanel dispatch: traduz ações em chamadas de API ----
  const detailDispatch = apiSafe(async (action) => {
    const id = action.id;
    if (action.type === "TOGGLE_CHECK") {
      const task = stateRef.current.tasks.find(x => x.id === id);
      const item = task.checklist.find(c => c.id === action.cid);
      const r = await Api.tasks.chkUpd(id, action.cid, { done: !item.done });
      dispatch({ type: "UPSERT_TASK", task: r.task });
      const before = Store.taskProgress(task);
      const after  = Store.taskProgress(Store.normalizeTask(r.task));
      if (before < 100 && after === 100 && task.status !== "done") {
        setTimeout(() => { celebrate({ message: "Checklist completo! 🎯" }); pop(task.id); }, 80);
      }
      return;
    }
    if (action.type === "ADD_CHECK") {
      const r = await Api.tasks.chkAdd(id, action.text);
      dispatch({ type: "UPSERT_TASK", task: r.task });
      return;
    }
    if (action.type === "DEL_CHECK") {
      const r = await Api.tasks.chkDel(id, action.cid);
      dispatch({ type: "UPSERT_TASK", task: r.task });
      return;
    }
    if (action.type === "ADD_IMAGE") {
      // Upload sequencial: cada novo upload aguarda os anteriores.
      // Polling pausa enquanto uploadsInFlightRef > 0.
      uploadsInFlightRef.current++;
      const job = uploadChainRef.current.then(async () => {
        try {
          let file = action.file;
          if (!file && action.url && action.url.startsWith("data:")) {
            const blob = await (await fetch(action.url)).blob();
            file = new File([blob], "image", { type: blob.type });
          }
          if (!file) return;
          const r = await Api.tasks.imgAdd(id, file);
          dispatch({ type: "UPSERT_TASK", task: r.task });
        } catch (e) {
          pushToast("⚠️", "Falha ao subir imagem: " + (e.message || "erro"));
        } finally {
          uploadsInFlightRef.current = Math.max(0, uploadsInFlightRef.current - 1);
        }
      });
      uploadChainRef.current = job.catch(() => {});
      return job;
    }
    if (action.type === "DEL_IMAGE") {
      const r = await Api.tasks.imgDel(id, action.index);
      dispatch({ type: "UPSERT_TASK", task: r.task });
      return;
    }
    if (action.type === "REPLACE_IMAGE") {
      uploadsInFlightRef.current++;
      const job = uploadChainRef.current.then(async () => {
        try {
          await Api.tasks.imgDel(id, action.index);
          let file = action.file;
          if (!file && action.url && action.url.startsWith("data:")) {
            const blob = await (await fetch(action.url)).blob();
            file = new File([blob], "image", { type: blob.type });
          }
          if (file) {
            const r = await Api.tasks.imgAdd(id, file);
            dispatch({ type: "UPSERT_TASK", task: r.task });
          } else {
            const r = await Api.tasks.list();
            dispatch({ type: "SET_TASKS", tasks: r.tasks });
          }
        } catch (e) {
          pushToast("⚠️", "Falha ao trocar imagem: " + (e.message || "erro"));
        } finally {
          uploadsInFlightRef.current = Math.max(0, uploadsInFlightRef.current - 1);
        }
      });
      uploadChainRef.current = job.catch(() => {});
      return job;
    }
    if (action.type === "ADD_VIDEO") {
      const r = await Api.tasks.videoAdd(id, action.video);
      dispatch({ type: "UPSERT_TASK", task: r.task });
      return;
    }
    if (action.type === "DEL_VIDEO") {
      const r = await Api.tasks.videoDel(id, action.index);
      dispatch({ type: "UPSERT_TASK", task: r.task });
      return;
    }
    if (action.type === "UPDATE") {
      return updateTask(action.id, action.patch);
    }
  });

  // ---- login flow ----
  const onLogin = async (user) => {
    dispatch({ type: "LOGIN", user });
    setEntering(true);
    try {
      const tk = await Api.tasks.list();
      dispatch({ type: "SET_TASKS", tasks: tk.tasks });
      const g = await Api.game.me();
      dispatch({ type: "SET_GAME", game: g.game });
    } catch (_) {}
    setTimeout(() => setEntering(false), 700);
  };
  const onLogout = async () => {
    try { await Api.logout(); } catch (_) {}
    dispatch({ type: "LOGOUT" });
    setOpenId(null);
  };
  const saveUser = (newUser) => dispatch({ type: "SET_USER", user: newUser });

  useEffect(() => {
    const h = (e) => { if (e.key === "Escape") { setOpenId(null); setModal(null); setSettings(null); setShowAdmin(false); setShowApprovals(false); } };
    addEventListener("keydown", h); return () => removeEventListener("keydown", h);
  }, []);

  if (state.loading) {
    return <div className="login" style={{ alignItems: "center", justifyContent: "center" }}>
      <div style={{ color: "var(--txt-3)" }}>Carregando…</div>
    </div>;
  }

  if (!state.loggedIn) {
    return <>
      <window.LoginScreen onLogin={onLogin} />
      <Tweaks t={t} setTweak={setTweak} />
    </>;
  }

  const sidebarUser = {
    name:   state.user.name,
    role:   state.user.role_title || "Membro",
    avatar: state.user.avatar,
  };

  return (
    <div className={"app" + (entering ? " entering" : "")}>
      <window.Sidebar
        game={state.game}
        justLeveled={justLeveled}
        user={sidebarUser}
        onOpenSettings={(tab) => setSettings(tab || "perfil")}
        onLogout={onLogout}
        canAdmin={state.user.role === "admin"}
        onOpenAdmin={() => setShowAdmin(true)}
        canApprove={canApprove}
        pendingCount={pendingCount}
        onOpenApprovals={() => setShowApprovals(true)}
      />
      <window.MobileChrome
        game={state.game}
        justLeveled={justLeveled}
        user={sidebarUser}
        muted={muted}
        toggleMute={() => setMuted(m => !m)}
        onNew={() => setModal({ status: "todo" })}
        onOpenSettings={(tab) => setSettings(tab || "perfil")}
        onLogout={onLogout}
        canAdmin={state.user.role === "admin"}
        onOpenAdmin={() => setShowAdmin(true)}
        canApprove={canApprove}
        pendingCount={pendingCount}
        onOpenApprovals={() => setShowApprovals(true)}
      />
      <main className="main">
        <window.MobileHero />
        <window.Topbar query={query} setQuery={setQuery} onNew={() => setModal({ status: "todo" })}
          muted={muted} toggleMute={() => setMuted(m => !m)} />
        <window.StatBar game={state.game} totalTasks={state.tasks.length} />
        <window.Filters catFilter={catFilter} setCatFilter={setCatFilter}
          prioFilter={prioFilter} setPrioFilter={setPrioFilter} />
        <window.Board tasks={state.tasks} query={query} catFilter={catFilter} prioFilter={prioFilter}
          onOpen={(task) => { setOpenId(task.id); FX.sfx.play("click"); }}
          onOpenImages={(task, index) => setViewer({ taskId: task.id, index })}
          onMove={handleMove} onAdd={(status) => setModal({ status })}
          selectedId={openId} popId={popId} />
      </main>

      {openTask && (
        <window.DetailPanel task={openTask} onClose={() => setOpenId(null)}
          onUpdate={updateTask} onMoveStatus={moveStatus} onDelete={deleteTask}
          dispatch={detailDispatch} />
      )}
      {modal && (
        <window.NewTaskModal defaultStatus={modal.status} onClose={() => setModal(null)} onCreate={createTask} />
      )}
      {settings && (
        <window.SettingsModal user={state.user} initialTab={settings}
          onClose={() => setSettings(null)} onSave={saveUser} pushToast={pushToast} />
      )}
      {showAdmin && (
        <window.AdminPanel onClose={() => setShowAdmin(false)} pushToast={pushToast}
          currentUserId={state.user.id} />
      )}
      {showApprovals && (
        <window.ApprovalsPanel onClose={() => setShowApprovals(false)} pushToast={pushToast}
          currentRole={state.user.role}
          onChanged={(n) => setPendingCount(n)} />
      )}
      {viewer && (() => {
        const vt = state.tasks.find(x => x.id === viewer.taskId);
        if (!vt || !vt.images.length) return null;
        return <window.Lightbox images={vt.images} index={Math.min(viewer.index, vt.images.length - 1)}
          onClose={() => setViewer(null)}
          onNav={(i) => setViewer(v => ({ ...v, index: i }))}
          onDelete={async (i) => {
            try {
              const r = await Api.tasks.imgDel(vt.id, i);
              dispatch({ type: "UPSERT_TASK", task: r.task });
              const left = (r.task.images || []).length;
              if (left <= 0) setViewer(null);
              else setViewer(v => ({ ...v, index: Math.min(v.index, left - 1) }));
            } catch (e) { pushToast("⚠️", e.message); }
          }} />;
      })()}

      <window.ToastHost toasts={toasts} />
      <Tweaks t={t} setTweak={setTweak} />
    </div>
  );
}

function Tweaks({ t, setTweak }) {
  const { TweaksPanel, TweakSection, TweakColor, TweakSlider } = window;
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Cor de destaque" />
      <TweakColor label="Accent" value={t.accent} options={ACCENTS}
        onChange={(v) => setTweak("accent", v)} />
      <TweakSection label="Atmosfera" />
      <TweakSlider label="Glow" value={t.glow} min={0} max={1.6} step={0.1}
        onChange={(v) => setTweak("glow", v)} />
      <TweakSlider label="Animação" value={t.anim} min={0} max={1.4} step={0.1}
        onChange={(v) => setTweak("anim", v)} />
    </TweaksPanel>
  );
}

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