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

const ORIGIN_BASE = window.location.origin.replace(/\/+$/, "");
const SAVED_BASE = (localStorage.getItem("sneaksolve_api_base") || "").trim();
const DEFAULT_BASE = (SAVED_BASE || ORIGIN_BASE).replace(/\/+$/, "");

function normalizeBase(value) {
  return String(value || "").trim().replace(/\/+$/, "");
}

function formatTime(isoValue) {
  if (!isoValue) return "-";
  const date = new Date(isoValue);
  if (Number.isNaN(date.getTime())) return String(isoValue);
  return date.toLocaleString();
}

function classForProblemStatus(status) {
  if (status === "\ud574\uacb0") return "bg-emerald-50 text-emerald-700 border-emerald-200";
  if (status === "\ud574\uacb0\uc911") return "bg-amber-50 text-amber-700 border-amber-200";
  return "bg-slate-50 text-slate-700 border-slate-200";
}

function classForPodStatus(status) {
  if (status === "IDLE") return "bg-emerald-50 text-emerald-700 border-emerald-200";
  if (status === "BUSY") return "bg-blue-50 text-blue-700 border-blue-200";
  if (status === "AUTH_REQUIRED") return "bg-amber-50 text-amber-700 border-amber-200";
  return "bg-slate-100 text-slate-700 border-slate-200";
}

function statusPill(label, extraClass = "") {
  return `inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-medium ${extraClass} ${label}`;
}

function useCurrentBaseState() {
  const [apiBase, setApiBase] = useState(DEFAULT_BASE);
  const base = useMemo(() => normalizeBase(apiBase) || ORIGIN_BASE, [apiBase]);
  return { apiBase, setApiBase, base };
}

function App() {
  const { apiBase, setApiBase, base } = useCurrentBaseState();
  const [contestInput, setContestInput] = useState("");
  const [contestId, setContestId] = useState(null);
  const [contests, setContests] = useState([]);
  const [contestStatus, setContestStatus] = useState(null);
  const [pods, setPods] = useState([]);
  const [selectedProblemId, setSelectedProblemId] = useState(null);
  const [problemDetail, setProblemDetail] = useState(null);
  const [lastError, setLastError] = useState("");
  const [loading, setLoading] = useState({
    contests: false,
    contestStatus: false,
    pods: false,
    detail: false,
    authAction: "",
  });
  const [authDialog, setAuthDialog] = useState({
    open: false,
    podId: "",
    code: "",
    verificationUri: "",
    expiresAt: "",
    podError: "",
    popup: null,
  });

  const eventSourceRef = useRef(null);

  const requestJson = useCallback(async (requestBase, path, init = {}) => {
    const response = await fetch(`${requestBase}${path}`, {
      ...init,
      headers: {
        "Content-Type": "application/json",
        ...(init.headers || {}),
      },
    });
    if (!response.ok) {
      const text = await response.text();
      throw new Error(text || `HTTP ${response.status}`);
    }
    return response.json();
  }, []);

  const api = useCallback(
    async (path, init = {}) => {
      try {
        return await requestJson(base, path, init);
      } catch (error) {
        if (base !== ORIGIN_BASE) {
          console.warn("[SneakSolve] fallback to origin base", {
            from: base,
            to: ORIGIN_BASE,
            path,
            error: String(error),
          });
          setApiBase(ORIGIN_BASE);
          localStorage.setItem("sneaksolve_api_base", ORIGIN_BASE);
          return requestJson(ORIGIN_BASE, path, init);
        }
        throw error;
      }
    },
    [base, requestJson, setApiBase]
  );

  const loadPods = useCallback(
    async ({ silent = false } = {}) => {
      if (!silent) setLoading((prev) => ({ ...prev, pods: true }));
      try {
        const payload = await api("/v1/pods");
        const list = Array.isArray(payload) ? payload : [];
        setPods(list);
        return list;
      } catch (error) {
        setLastError(String(error));
        console.error(error);
        return [];
      } finally {
        if (!silent) setLoading((prev) => ({ ...prev, pods: false }));
      }
    },
    [api]
  );

  const loadContests = useCallback(async () => {
    setLoading((prev) => ({ ...prev, contests: true }));
    try {
      const payload = await api("/v1/contests");
      const list = Array.isArray(payload) ? payload : [];
      setContests(list);
      return list;
    } catch (error) {
      setLastError(String(error));
      console.error(error);
      return [];
    } finally {
      setLoading((prev) => ({ ...prev, contests: false }));
    }
  }, [api]);

  const loadContestStatus = useCallback(
    async (nextContestId) => {
      if (!nextContestId) return null;
      setLoading((prev) => ({ ...prev, contestStatus: true }));
      try {
        const payload = await api(`/v1/contests/${nextContestId}/status`);
        setContestStatus(payload);
        return payload;
      } catch (error) {
        setLastError(String(error));
        console.error(error);
        return null;
      } finally {
        setLoading((prev) => ({ ...prev, contestStatus: false }));
      }
    },
    [api]
  );

  const loadProblemDetail = useCallback(
    async (problemId, { silent = false } = {}) => {
      if (!problemId) return;
      if (!silent) setLoading((prev) => ({ ...prev, detail: true }));
      try {
        const payload = await api(`/v1/problems/${problemId}/solution`);
        setProblemDetail(payload);
      } catch (error) {
        setLastError(String(error));
        console.error(error);
      } finally {
        if (!silent) setLoading((prev) => ({ ...prev, detail: false }));
      }
    },
    [api]
  );

  const closeAuthDialog = useCallback(() => {
    setAuthDialog((prev) => {
      if (prev.popup && !prev.popup.closed) {
        prev.popup.close();
      }
      return {
        open: false,
        podId: "",
        code: "",
        verificationUri: "",
        expiresAt: "",
        podError: "",
        popup: null,
      };
    });
  }, []);

  const startAuth = useCallback(
    async (podId) => {
      setLoading((prev) => ({ ...prev, authAction: podId }));
      setLastError("");
      try {
        const payload = await api(`/v1/pods/${podId}/auth/start`, {
          method: "POST",
          body: JSON.stringify({ provider: "codex" }),
        });
        setAuthDialog({
          open: true,
          podId: payload.id,
          code: payload.auth_code || "",
          verificationUri: payload.auth_verification_uri || "",
          expiresAt: payload.auth_expires_at || "",
          podError: payload.last_error || "",
          popup: null,
        });
        await loadPods({ silent: true });
      } catch (error) {
        setLastError(String(error));
        console.error(error);
      } finally {
        setLoading((prev) => ({ ...prev, authAction: "" }));
      }
    },
    [api, loadPods]
  );

  const authOff = useCallback(
    async (podId) => {
      setLoading((prev) => ({ ...prev, authAction: podId }));
      try {
        await api(`/v1/pods/${podId}/auth`, {
          method: "PATCH",
          body: JSON.stringify({ authenticated: false }),
        });
        if (authDialog.open && authDialog.podId === podId) closeAuthDialog();
        await loadPods();
      } catch (error) {
        setLastError(String(error));
        console.error(error);
      } finally {
        setLoading((prev) => ({ ...prev, authAction: "" }));
      }
    },
    [api, authDialog.open, authDialog.podId, closeAuthDialog, loadPods]
  );

  const clearAuthCode = useCallback(
    async (podId) => {
      setLoading((prev) => ({ ...prev, authAction: podId }));
      try {
        await api(`/v1/pods/${podId}/auth/challenge`, { method: "DELETE" });
        await loadPods();
      } catch (error) {
        setLastError(String(error));
        console.error(error);
      } finally {
        setLoading((prev) => ({ ...prev, authAction: "" }));
      }
    },
    [api, loadPods]
  );

  const markAuthComplete = useCallback(
    async (podId) => {
      setLoading((prev) => ({ ...prev, authAction: podId }));
      try {
        await api(`/v1/pods/${podId}/auth/complete`, {
          method: "POST",
          body: JSON.stringify({ authenticated: true }),
        });
        await loadPods();
      } catch (error) {
        setLastError(String(error));
        console.error(error);
      } finally {
        setLoading((prev) => ({ ...prev, authAction: "" }));
      }
    },
    [api, loadPods]
  );

  useEffect(() => {
    const run = async () => {
      await loadPods();
      const fetchedContests = await loadContests();
      const params = new URLSearchParams(window.location.search);
      const fromUrl = Number(params.get("contestId") || "");
      const initialContestId =
        Number.isFinite(fromUrl) && fromUrl > 0
          ? fromUrl
          : fetchedContests.length > 0
            ? fetchedContests[0].contestId
            : null;
      if (initialContestId) {
        setContestId(initialContestId);
        setContestInput(String(initialContestId));
        await loadContestStatus(initialContestId);
      }
    };
    run().catch((error) => {
      setLastError(String(error));
      console.error(error);
    });
  }, [loadContestStatus, loadContests, loadPods]);

  useEffect(() => {
    if (!contestId) return;
    if (eventSourceRef.current) {
      eventSourceRef.current.close();
    }
    const source = new EventSource(`${base}/v1/contests/${contestId}/stream`);
    source.onmessage = async () => {
      await Promise.all([
        loadContestStatus(contestId),
        loadPods({ silent: true }),
        selectedProblemId ? loadProblemDetail(selectedProblemId, { silent: true }) : Promise.resolve(),
      ]);
    };
    source.onerror = (error) => {
      console.warn("[SneakSolve] stream error", error);
    };
    eventSourceRef.current = source;
    return () => {
      source.close();
      if (eventSourceRef.current === source) {
        eventSourceRef.current = null;
      }
    };
  }, [base, contestId, loadContestStatus, loadPods, loadProblemDetail, selectedProblemId]);

  useEffect(() => {
    if (!authDialog.open || !authDialog.podId) return undefined;
    const interval = setInterval(async () => {
      const list = await loadPods({ silent: true });
      const pod = list.find((item) => item.id === authDialog.podId);
      if (!pod) return;
      if (pod.auth_state) {
        closeAuthDialog();
        return;
      }
      setAuthDialog((prev) => ({
        ...prev,
        code: pod.auth_code || "",
        verificationUri: pod.auth_verification_uri || "",
        expiresAt: pod.auth_expires_at || "",
        podError: pod.last_error || "",
      }));
    }, 2000);
    return () => clearInterval(interval);
  }, [authDialog.open, authDialog.podId, closeAuthDialog, loadPods]);

  useEffect(() => {
    if (!authDialog.open || !authDialog.podId || !authDialog.verificationUri) return;
    if (authDialog.popup && !authDialog.popup.closed) return;
    const popup = window.open(
      authDialog.verificationUri,
      `sneaksolve-auth-${authDialog.podId}`,
      "width=560,height=760,noopener"
    );
    setAuthDialog((prev) => ({
      ...prev,
      popup: popup || null,
    }));
  }, [authDialog.open, authDialog.podId, authDialog.verificationUri, authDialog.popup]);

  const podCounts = useMemo(() => {
    const counts = { IDLE: 0, BUSY: 0, AUTH_REQUIRED: 0, OFFLINE: 0 };
    for (const pod of pods) {
      counts[pod.status] = (counts[pod.status] || 0) + 1;
    }
    return counts;
  }, [pods]);

  const onContestLoadClick = async () => {
    const value = Number(contestInput || "");
    if (!Number.isFinite(value) || value <= 0) return;
    setContestId(value);
    setSelectedProblemId(null);
    setProblemDetail(null);
    await loadContestStatus(value);
  };

  const openProblem = async (problem) => {
    setSelectedProblemId(problem.bojProblemId);
    await loadProblemDetail(problem.bojProblemId);
  };

  const copyAuthCode = async () => {
    if (!authDialog.code) return;
    try {
      await navigator.clipboard.writeText(authDialog.code);
    } catch (error) {
      console.warn("[SneakSolve] clipboard copy failed", error);
    }
  };

  return (
    <div className="mx-auto w-full max-w-[1280px] px-4 py-6 md:px-8">
      <header className="mb-6 rounded-2xl border border-notion-line bg-notion-paper p-5 shadow-card">
        <div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
          <div>
            <h1 className="text-2xl font-semibold tracking-tight">SneakSolve</h1>
            <p className="mt-1 text-sm text-notion-muted">
              Notion-style control room for contest solving and pod authentication.
            </p>
          </div>
          <div className="grid w-full gap-2 md:w-[520px]">
            <label className="text-xs font-medium uppercase tracking-wide text-notion-muted">
              API Base
            </label>
            <div className="flex gap-2">
              <input
                className="w-full rounded-lg border border-notion-line bg-white px-3 py-2 text-sm focus:border-slate-400 focus:outline-none"
                value={apiBase}
                onChange={(e) => setApiBase(e.target.value)}
                placeholder="https://ssv.0xroot.dev"
              />
              <button
                className="rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm font-medium hover:bg-slate-50"
                onClick={() => {
                  const normalized = normalizeBase(apiBase) || ORIGIN_BASE;
                  setApiBase(normalized);
                  localStorage.setItem("sneaksolve_api_base", normalized);
                }}
              >
                Save
              </button>
            </div>
            <div className="flex gap-2">
              <input
                className="w-full rounded-lg border border-notion-line bg-white px-3 py-2 text-sm focus:border-slate-400 focus:outline-none"
                type="number"
                placeholder="Contest ID"
                value={contestInput}
                onChange={(e) => setContestInput(e.target.value)}
              />
              <button
                className="rounded-lg bg-slate-900 px-4 py-2 text-sm font-semibold text-white hover:bg-slate-800"
                onClick={onContestLoadClick}
              >
                Load
              </button>
            </div>
          </div>
        </div>
        {lastError ? (
          <p className="mt-3 rounded-md border border-rose-200 bg-rose-50 px-3 py-2 text-sm text-rose-700">
            {lastError}
          </p>
        ) : null}
      </header>

      <div className="grid grid-cols-1 gap-4 xl:grid-cols-[380px_1fr]">
        <section className="space-y-4">
          <article className="rounded-2xl border border-notion-line bg-notion-paper p-4 shadow-card">
            <div className="mb-3 flex items-center justify-between">
              <h2 className="text-lg font-semibold">Pod Auth</h2>
              <button
                className="rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium hover:bg-slate-50"
                onClick={() => loadPods()}
              >
                {loading.pods ? "Refreshing..." : "Refresh"}
              </button>
            </div>
            <div className="mb-4 flex flex-wrap gap-2">
              {Object.entries(podCounts).map(([status, count]) => (
                <span key={status} className={statusPill("", classForPodStatus(status))}>
                  {status}: {count}
                </span>
              ))}
            </div>
            <div className="space-y-3">
              {pods.map((pod) => (
                <div key={pod.id} className="rounded-xl border border-notion-line bg-white p-3">
                  <div className="mb-1 flex items-center justify-between">
                    <p className="text-base font-semibold">{pod.id}</p>
                    <span className={statusPill("", classForPodStatus(pod.status))}>{pod.status}</span>
                  </div>
                  <p className="text-sm text-notion-muted">
                    auth={String(pod.auth_state)} | current={pod.current_job_id || "-"}
                  </p>
                  {pod.auth_state ? (
                    <p className="mt-1 text-sm text-notion-muted">
                      account={pod.auth_account_email || "-"}
                    </p>
                  ) : null}
                  {pod.auth_code ? (
                    <div className="mt-2 rounded-lg border border-amber-200 bg-amber-50 p-2 text-xs text-amber-900">
                      <p>
                        code: <span className="font-semibold">{pod.auth_code}</span>
                        {pod.auth_code_expired ? " (expired)" : ""}
                      </p>
                      <p>provider: {pod.auth_provider || "-"}</p>
                      <p>expires: {formatTime(pod.auth_expires_at)}</p>
                    </div>
                  ) : (
                    <p className="mt-2 text-xs text-notion-muted">No auth code issued</p>
                  )}
                  <div className="mt-3 flex flex-wrap gap-2">
                    <button
                      className="rounded-lg bg-slate-900 px-3 py-1.5 text-sm font-semibold text-white hover:bg-slate-800 disabled:opacity-50"
                      onClick={() => startAuth(pod.id)}
                      disabled={loading.authAction === pod.id || pod.status === "BUSY"}
                    >
                      Start Auth
                    </button>
                    <button
                      className="rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium hover:bg-slate-50 disabled:opacity-50"
                      onClick={() => authOff(pod.id)}
                      disabled={loading.authAction === pod.id}
                    >
                      Auth OFF
                    </button>
                    <button
                      className="rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium hover:bg-slate-50 disabled:opacity-50"
                      onClick={() => clearAuthCode(pod.id)}
                      disabled={loading.authAction === pod.id}
                    >
                      Clear Code
                    </button>
                  </div>
                </div>
              ))}
            </div>
          </article>
        </section>

        <section className="space-y-4">
          <article className="rounded-2xl border border-notion-line bg-notion-paper p-4 shadow-card">
            <div className="mb-3 flex items-center justify-between">
              <h2 className="text-lg font-semibold">Contest Overview</h2>
              <div className="text-sm text-notion-muted">
                {contestStatus ? `Contest ${contestStatus.contestId}` : "No contest selected"}
              </div>
            </div>
            <div className="mb-4 flex flex-wrap gap-2">
              {contests.slice(0, 8).map((contest) => (
                <button
                  key={contest.contestId}
                  className={`rounded-full border px-3 py-1 text-xs font-medium ${
                    contestId === contest.contestId
                      ? "border-slate-900 bg-slate-900 text-white"
                      : "border-notion-line bg-white text-slate-700 hover:bg-slate-50"
                  }`}
                  onClick={async () => {
                    setContestId(contest.contestId);
                    setContestInput(String(contest.contestId));
                    await loadContestStatus(contest.contestId);
                  }}
                >
                  {contest.contestId} ({contest.solvedCount}/{contest.problemCount})
                </button>
              ))}
              {loading.contests ? (
                <span className="text-xs text-notion-muted">loading contests...</span>
              ) : null}
            </div>
            <div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
              {(contestStatus?.problems || []).map((problem) => (
                <button
                  key={`${problem.orderIndex}-${problem.bojProblemId}`}
                  className={`rounded-xl border p-3 text-left transition hover:shadow-sm ${
                    selectedProblemId === problem.bojProblemId
                      ? "border-slate-400 bg-slate-50"
                      : "border-notion-line bg-white"
                  }`}
                  onClick={() => openProblem(problem)}
                >
                  <div className="mb-2 flex items-center justify-between gap-2">
                    <p className="text-sm font-semibold">
                      {String.fromCharCode(65 + (problem.orderIndex || 0))} · #{problem.bojProblemId}
                    </p>
                    <span className={statusPill("", classForProblemStatus(problem.status))}>
                      {problem.status}
                    </span>
                  </div>
                  <div className="flex flex-wrap gap-1">
                    {(problem.algorithmTags || []).map((tag) => (
                      <span
                        key={tag}
                        className="inline-flex items-center rounded-full border border-blue-200 bg-blue-50 px-2 py-0.5 text-[11px] text-blue-700"
                      >
                        {tag}
                      </span>
                    ))}
                    {problem.estimatedBojDifficulty ? (
                      <span className="inline-flex items-center rounded-full border border-violet-200 bg-violet-50 px-2 py-0.5 text-[11px] text-violet-700">
                        {problem.estimatedBojDifficulty}
                      </span>
                    ) : null}
                  </div>
                </button>
              ))}
            </div>
            {!loading.contestStatus && (contestStatus?.problems || []).length === 0 ? (
              <p className="mt-4 text-sm text-notion-muted">
                No problems loaded yet. Run Tampermonkey sync first.
              </p>
            ) : null}
          </article>

          <article className="rounded-2xl border border-notion-line bg-notion-paper p-4 shadow-card">
            <h2 className="mb-3 text-lg font-semibold">Problem Detail</h2>
            {!problemDetail ? (
              <p className="text-sm text-notion-muted">Select a problem card to view detail.</p>
            ) : (
              <div className="space-y-3">
                <p className="text-sm font-semibold">
                  #{problemDetail.bojProblemId} · {problemDetail.status}
                </p>
                <div className="rounded-xl border border-notion-line bg-white p-3">
                  <p className="mb-1 text-xs font-medium uppercase tracking-wide text-notion-muted">
                    Core Summary
                  </p>
                  <pre className="whitespace-pre-wrap text-sm leading-relaxed text-slate-800">
                    {problemDetail.coreSummary || "No summary yet."}
                  </pre>
                </div>
                <div className="rounded-xl border border-notion-line bg-white p-3">
                  <p className="mb-1 text-xs font-medium uppercase tracking-wide text-notion-muted">
                    Detailed Explanation
                  </p>
                  <pre className="whitespace-pre-wrap text-sm leading-relaxed text-slate-800">
                    {problemDetail.detailedExplanation || "No explanation yet."}
                  </pre>
                </div>
              </div>
            )}
          </article>
        </section>
      </div>

      {authDialog.open ? (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/35 p-4">
          <div className="w-full max-w-lg rounded-2xl border border-notion-line bg-white p-5 shadow-2xl">
            <h3 className="text-lg font-semibold">Pod Auth In Progress</h3>
            <p className="mt-1 text-sm text-notion-muted">
              pod: <span className="font-medium text-slate-800">{authDialog.podId}</span>
            </p>
            <div className="mt-4 space-y-3 rounded-xl border border-amber-200 bg-amber-50 p-3">
              <div>
                <p className="text-xs uppercase tracking-wide text-amber-700">Device Code</p>
                <p className="mt-1 font-mono text-xl font-bold tracking-widest text-amber-900">
                  {authDialog.code || "-"}
                </p>
              </div>
              <p className="text-xs text-amber-800">expires: {formatTime(authDialog.expiresAt)}</p>
              {authDialog.verificationUri ? (
                <a
                  className="inline-flex items-center text-sm font-medium text-blue-700 underline"
                  href={authDialog.verificationUri}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  Open verification page
                </a>
              ) : null}
            </div>
            <p className="mt-3 text-sm text-notion-muted">
              Waiting for pod-side challenge and verification. Dialog closes automatically when
              auth_state becomes true.
            </p>
            {authDialog.podError ? (
              <p className="mt-2 rounded-lg border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700">
                {authDialog.podError}
              </p>
            ) : null}
            <div className="mt-4 flex flex-wrap gap-2">
              <button
                className="rounded-lg bg-slate-900 px-3 py-1.5 text-sm font-semibold text-white hover:bg-slate-800"
                onClick={copyAuthCode}
                disabled={!authDialog.code}
              >
                Copy Code
              </button>
              <button
                className="rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium hover:bg-slate-50"
                onClick={() => markAuthComplete(authDialog.podId)}
              >
                Mark Login Complete
              </button>
              <button
                className="rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium hover:bg-slate-50"
                onClick={closeAuthDialog}
              >
                Close
              </button>
            </div>
          </div>
        </div>
      ) : null}
    </div>
  );
}

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