"use client";

import Image from "next/image";
import Link from "next/link";
import type { ReactNode } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import {
  ArrowLeft,
  BarChart3,
  ChevronDown,
  Copy,
  Gauge,
  Loader2,
  Settings,
  Sparkles,
  Star,
  Target,
  X
} from "lucide-react";
import { CasinoStakeInput } from "@/components/casino/casino-stake-input";
import { GameDetailsPanel, type CasinoGameRoundSummary } from "@/components/casino/game-details-panel";
import { StakeLimitNote } from "@/components/casino/stake-limit-note";
import { formatAmountInput, parseAmountInput } from "@/lib/amount-input";
import { miniGames } from "@/lib/casino-games";
import { notifyWalletUpdated, notifyWalletVisualAdjustment } from "@/lib/client-events";
import { cn } from "@/lib/utils";

type PlinkoRisk = "low" | "medium" | "high";
type PlinkoPathStep = "L" | "R";
type Mode = "manual" | "auto";

type PlinkoRound = CasinoGameRoundSummary & {
  nonce: number;
  serverSeedHash: string;
  rows: number;
  risk: PlinkoRisk;
  path: PlinkoPathStep[];
  bucket: number;
};

type PlinkoMultipliers = Record<number, Record<PlinkoRisk, number[]>>;

type PlinkoState = {
  config: {
    minStake: string;
    maxStake: string;
    maxPayout: string;
    defaultRows: number;
    rowOptions: number[];
    defaultRisk: PlinkoRisk;
    risks: PlinkoRisk[];
    houseEdgeBps: number;
    multipliers: PlinkoMultipliers;
  };
  currency: string;
  seedPair: {
    id: string;
    clientSeed: string;
    serverSeedHash: string;
    nonce: number;
  };
  stats: {
    stake: string;
    profit: string;
    wins: number;
    losses: number;
  };
  recentRounds: PlinkoRound[];
};

type BallPosition = {
  id: string;
  x: number;
  y: number;
};

const DEFAULT_MULTIPLIERS: PlinkoMultipliers = {
  8: {
    low: [5.6, 2.1, 1.2, 1, 0.5, 1, 1.2, 2.1, 5.6],
    medium: [13, 3.9, 1.6, 0.7, 0.4, 0.7, 1.6, 3.9, 13],
    high: [29, 7, 2, 0.4, 0.2, 0.4, 2, 7, 29]
  },
  10: {
    low: [8.9, 3, 1.4, 1.1, 0.7, 0.5, 0.7, 1.1, 1.4, 3, 8.9],
    medium: [22, 5, 2, 1.1, 0.5, 0.3, 0.5, 1.1, 2, 5, 22],
    high: [76, 14, 4, 1.4, 0.4, 0.2, 0.4, 1.4, 4, 14, 76]
  },
  12: {
    low: [10, 4, 1.9, 1.3, 0.9, 0.6, 0.4, 0.6, 0.9, 1.3, 1.9, 4, 10],
    medium: [33, 11, 4, 1.9, 1, 0.5, 0.3, 0.5, 1, 1.9, 4, 11, 33],
    high: [170, 24, 8, 2, 0.7, 0.2, 0.1, 0.2, 0.7, 2, 8, 24, 170]
  },
  14: {
    low: [14, 5, 2.2, 1.4, 1, 0.7, 0.5, 0.4, 0.5, 0.7, 1, 1.4, 2.2, 5, 14],
    medium: [56, 18, 5, 2.4, 1.3, 0.6, 0.3, 0.2, 0.3, 0.6, 1.3, 2.4, 5, 18, 56],
    high: [420, 56, 18, 5, 1.5, 0.3, 0.2, 0.1, 0.2, 0.3, 1.5, 5, 18, 56, 420]
  },
  16: {
    low: [16, 9, 3, 1.6, 1.3, 1, 0.7, 0.5, 0.4, 0.5, 0.7, 1, 1.3, 1.6, 3, 9, 16],
    medium: [110, 41, 10, 5, 2, 1.1, 0.5, 0.3, 0.2, 0.3, 0.5, 1.1, 2, 5, 10, 41, 110],
    high: [1000, 130, 26, 9, 4, 2, 0.2, 0.2, 0.1, 0.2, 0.2, 2, 4, 9, 26, 130, 1000]
  }
};

const DEFAULT_STATE: PlinkoState = {
  config: {
    minStake: "0.01",
    maxStake: "10000",
    maxPayout: "100000",
    defaultRows: 12,
    rowOptions: [8, 10, 12, 14, 16],
    defaultRisk: "medium",
    risks: ["low", "medium", "high"],
    houseEdgeBps: 100,
    multipliers: DEFAULT_MULTIPLIERS
  },
  currency: "EUR",
  seedPair: {
    id: "",
    clientSeed: "",
    serverSeedHash: "",
    nonce: 0
  },
  stats: {
    stake: "0",
    profit: "0",
    wins: 0,
    losses: 0
  },
  recentRounds: []
};

const riskLabels: Record<PlinkoRisk, string> = {
  low: "Faible",
  medium: "Moyen",
  high: "Élevé"
};

const riskAccent: Record<PlinkoRisk, string> = {
  low: "bg-felt text-ink-950 shadow-[0_10px_26px_rgba(15,191,143,0.22)]",
  medium: "bg-ember text-ink-950 shadow-[0_10px_26px_rgba(239,111,60,0.2)]",
  high: "bg-rose-500 text-ink-950 shadow-[0_10px_26px_rgba(244,63,94,0.22)]"
};

export function PlinkoGame() {
  const [state, setState] = useState<PlinkoState>(DEFAULT_STATE);
  const [loading, setLoading] = useState(true);
  const [guest, setGuest] = useState(false);
  const [message, setMessage] = useState("");
  const [mode, setMode] = useState<Mode>("manual");
  const [stake, setStake] = useState("0");
  const [betCount, setBetCount] = useState("0");
  const [rows, setRows] = useState(DEFAULT_STATE.config.defaultRows);
  const [risk, setRisk] = useState<PlinkoRisk>(DEFAULT_STATE.config.defaultRisk);
  const [autoRunning, setAutoRunning] = useState(false);
  const [autoStopping, setAutoStopping] = useState(false);
  const [activeBalls, setActiveBalls] = useState<BallPosition[]>([]);
  const [highlightedBucket, setHighlightedBucket] = useState<number | null>(null);
  const [settingsOpen, setSettingsOpen] = useState(false);
  const [statsOpen, setStatsOpen] = useState(false);
  const [fairnessOpen, setFairnessOpen] = useState(false);
  const [clientSeedInput, setClientSeedInput] = useState("");
  const [revealedSeed, setRevealedSeed] = useState<null | { serverSeed: string; clientSeed: string; nonce: number; serverSeedHash: string }>(null);
  const [verifyInput, setVerifyInput] = useState({ serverSeed: "", clientSeed: "", nonce: "" });
  const [verifyResult, setVerifyResult] = useState<null | { serverSeedHash: string; bucket: number; multiplier: string; chance: string; path: PlinkoPathStep[] }>(null);
  const autoStopRef = useRef(false);
  const pendingRequestsRef = useRef(0);
  const runningAnimationsRef = useRef(0);

  const multipliers = state.config.multipliers[rows]?.[risk] ?? DEFAULT_MULTIPLIERS[rows]?.[risk] ?? [];
  const otherGames = miniGames.filter((game) => game.slug !== "plinko");
  const recentDrops = state.recentRounds.slice(0, 14);
  const rowIndex = Math.max(0, state.config.rowOptions.indexOf(rows));

  useEffect(() => {
    void loadState();
  }, []);

  async function loadState() {
    try {
      const response = await fetch("/api/casino/plinko/state", { cache: "no-store" });
      const payload = await response.json();
      if (!response.ok || !payload.ok) {
        const unauthenticated = response.status === 401 || payload.error?.code === "UNAUTHORIZED";
        setGuest(unauthenticated);
        setMessage(unauthenticated ? "Connecte-toi pour jouer à Plinko." : payload.error?.message ?? "Plinko indisponible.");
        return;
      }

      setState(payload.data);
      setClientSeedInput(payload.data.seedPair.clientSeed);
      setRows(payload.data.recentRounds[0]?.rows ?? payload.data.config.defaultRows);
      setRisk(payload.data.recentRounds[0]?.risk ?? payload.data.config.defaultRisk);
      setHighlightedBucket(payload.data.recentRounds[0]?.bucket ?? null);
    } catch {
      setMessage("Impossible de charger Plinko pour le moment.");
    } finally {
      setLoading(false);
    }
  }

  async function placeRound() {
    void launchRound();
  }

  async function requestRound(amount: number) {
    if (guest) {
      window.location.href = "/login";
      return null;
    }

    if (amount <= 0) {
      setMessage("Saisis une mise valide.");
      return null;
    }

    try {
      const response = await fetch("/api/casino/plinko/play", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          currency: state.currency,
          stake: amount,
          rows,
          risk,
          clientRequestId: crypto.randomUUID()
        })
      });
      const payload = await response.json();

      if (!response.ok || !payload.ok) {
        setMessage(payload.error?.message ?? "Pari refusé.");
        return null;
      }

      const round: PlinkoRound = payload.data.round;
      return round;
    } catch {
      setMessage("Erreur réseau pendant le tirage.");
      return null;
    }
  }

  async function launchRound() {
    const amount = parseAmountInput(stake);
    if (amount <= 0) {
      setMessage("Saisis une mise valide.");
      return false;
    }

    setMessage("");
    pendingRequestsRef.current += 1;
    let round: PlinkoRound | null = null;
    try {
      round = await requestRound(amount);
    } finally {
      pendingRequestsRef.current = Math.max(0, pendingRequestsRef.current - 1);
    }

    if (!round) {
      syncWalletWhenIdle();
      return false;
    }

    runningAnimationsRef.current += 1;
    void animateDrop(round).finally(() => {
      runningAnimationsRef.current = Math.max(0, runningAnimationsRef.current - 1);
      syncWalletWhenIdle();
    });
    return true;
  }

  function recordRound(round: PlinkoRound) {
    setState((current) => ({
      ...current,
      seedPair: { ...current.seedPair, nonce: current.seedPair.nonce + 1 },
      stats: {
        stake: String(Number(current.stats.stake) + Number(round.stake)),
        profit: String(Number(current.stats.profit) + Number(round.profit)),
        wins: current.stats.wins + (round.status === "WON" ? 1 : 0),
        losses: current.stats.losses + (round.status === "LOST" ? 1 : 0)
      },
      recentRounds: [round, ...current.recentRounds].slice(0, 25)
    }));
  }

  async function startAuto() {
    if (autoRunning) return;
    autoStopRef.current = false;
    setAutoStopping(false);
    const limit = readPositiveInteger(betCount, 0, 100, true);
    let played = 0;
    setMessage("");
    setAutoRunning(true);

    try {
      while (!autoStopRef.current && (limit <= 0 || played < limit)) {
        const launched = await launchRound();
        if (!launched) break;
        played += 1;
        await wait(420);
      }
    } finally {
      setAutoRunning(false);
      setAutoStopping(false);
    }
  }

  function stopAuto() {
    autoStopRef.current = true;
    setAutoStopping(true);
  }

  function syncWalletWhenIdle() {
    if (pendingRequestsRef.current === 0 && runningAnimationsRef.current === 0) {
      notifyWalletUpdated({ force: true });
    }
  }

  async function animateDrop(round: PlinkoRound, delay = 0) {
    const ballId = `${round.id}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
    const path = round.path;
    const bucket = round.bucket;
    const rowCount = round.rows;

    if (delay > 0) await wait(delay);
    notifyWalletVisualAdjustment(-Number(round.stake), { holdMs: 12000 });
    setHighlightedBucket(null);
    setActiveBalls((current) => [...current, { id: ballId, x: 50, y: 7 }]);
    await wait(130);

    let rights = 0;
    for (let index = 0; index < path.length; index += 1) {
      if (path[index] === "R") rights += 1;
      const progress = index + 1;
      const normalized = (rights - progress / 2) / Math.max(1, rowCount / 2);
      const jitter = (path[index] === "R" ? 0.9 : -0.9) * (index % 2 === 0 ? 1 : 0.45);
      const x = clamp(50 + normalized * 43 + jitter, 6, 94);
      const y = 11 + (progress / (rowCount + 1)) * 76;
      setActiveBalls((current) => current.map((ball) => (ball.id === ballId ? { ...ball, x, y } : ball)));
      await wait(150);
    }

    setActiveBalls((current) => current.map((ball) => (ball.id === ballId ? { ...ball, x: 5 + (bucket / Math.max(1, rowCount)) * 90, y: 89 } : ball)));
    await wait(220);
    setHighlightedBucket(bucket);
    recordRound(round);
    if (Number(round.payout) > 0) {
      notifyWalletVisualAdjustment(Number(round.payout), { holdMs: 8000 });
    }
    await wait(140);
    setActiveBalls((current) => current.filter((ball) => ball.id !== ballId));
  }

  async function rotateSeeds() {
    setMessage("");
    try {
      const response = await fetch("/api/casino/plinko/seeds/rotate", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ clientSeed: clientSeedInput })
      });
      const payload = await response.json();
      if (!response.ok || !payload.ok) {
        setMessage(payload.error?.message ?? "Rotation refusée.");
        return;
      }
      setRevealedSeed(payload.data.previous);
      setState((current) => ({
        ...current,
        seedPair: payload.data.current
      }));
      setClientSeedInput(payload.data.current.clientSeed);
      setMessage("Nouvelle paire de seeds active.");
    } catch {
      setMessage("Impossible de faire pivoter la seed.");
    }
  }

  async function verifySeed() {
    try {
      const response = await fetch("/api/casino/plinko/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          ...verifyInput,
          rows,
          risk
        })
      });
      const payload = await response.json();
      if (!response.ok || !payload.ok) {
        setMessage(payload.error?.message ?? "Vérification impossible.");
        return;
      }
      setVerifyResult(payload.data);
    } catch {
      setMessage("Erreur pendant la vérification.");
    }
  }

  function setStakeMultiplier(factor: number) {
    setStake((value) => formatAmountInput(parseAmountInput(value) * factor));
  }

  if (loading) {
    return (
      <div className="grid min-h-[360px] place-items-center rounded-lg border border-white/10 bg-[#111720]">
        <div className="text-center">
          <Loader2 className="mx-auto h-7 w-7 animate-spin text-felt" />
          <p className="mt-3 text-xs font-bold text-slate-400">Chargement de Plinko...</p>
        </div>
      </div>
    );
  }

  return (
    <div className="space-y-3.5 sm:space-y-5">
      <div className="flex items-center gap-2">
        <Link href="/casino" className="grid h-9 w-9 place-items-center rounded-lg border border-white/10 bg-white/[0.04] text-slate-200 transition hover:bg-white/[0.07]">
          <ArrowLeft className="h-4 w-4" />
        </Link>
        <div className="rounded-lg border border-white/10 bg-white/[0.045] px-3 py-2">
          <p className="text-[10px] font-black uppercase tracking-[0.08em] text-felt">Original</p>
          <h1 className="text-lg font-black leading-none text-white">Plinko</h1>
        </div>
      </div>

      <section className="overflow-hidden rounded-lg border border-white/10 bg-[#111720] shadow-panel">
        <div className="border-b border-white/10 bg-black/24 px-3 py-2">
          <div className="flex items-center justify-between gap-3">
            <p className="text-[10px] font-black uppercase tracking-[0.08em] text-slate-500">Historique</p>
            <p className="shrink-0 text-[11px] font-extrabold text-white">{rows} lignes · {riskLabels[risk]}</p>
          </div>
          <div className="mt-1.5 flex min-w-0 gap-1 overflow-x-auto whitespace-nowrap pb-0.5 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
            {recentDrops.length ? (
              recentDrops.map((round) => <HistoryPill key={round.id} round={round} />)
            ) : (
              <span className="rounded-md bg-white/[0.055] px-2 py-1 text-[10px] font-black text-slate-500">Aucun tirage</span>
            )}
          </div>
        </div>

        <PlinkoBoard
          rows={rows}
          multipliers={multipliers}
          activeBalls={activeBalls}
          highlightedBucket={highlightedBucket}
        />

        <div className="space-y-3 border-t border-white/10 p-3">
          <button
            disabled={autoStopping}
            onClick={mode === "auto" ? (autoRunning ? stopAuto : startAuto) : placeRound}
            className={cn(
              "h-12 w-full rounded-lg text-sm font-black shadow-[0_12px_30px_rgba(15,191,143,0.24)] transition disabled:cursor-not-allowed disabled:opacity-55",
              autoRunning ? "bg-rose-500 text-ink-950 hover:bg-rose-400" : "bg-felt text-ink-950 hover:bg-emerald-300"
            )}
          >
            {autoRunning
              ? autoStopping
                ? "Arrêt en cours..."
                : "Stopper le pari automatique"
              : mode === "auto"
                ? "Démarrer le pari automatique"
                : "Lancer 1 bille"}
          </button>

          <div>
            <CasinoStakeInput
              value={stake}
              currency={state.currency}
              onChange={setStake}
              onHalf={() => setStakeMultiplier(0.5)}
              onDouble={() => setStakeMultiplier(2)}
            />
            <StakeLimitNote min={state.config.minStake} max={state.config.maxStake} currency={state.currency} />
          </div>

          <div>
            <p className="text-xs font-black text-white">Risque</p>
            <div className="mt-1.5 grid grid-cols-3 gap-2">
              {state.config.risks.map((option) => (
                <button
                  key={option}
                  onClick={() => setRisk(option)}
                  className={cn(
                    "h-11 rounded-lg border text-sm font-black transition",
                    risk === option
                      ? cn("border-transparent", riskAccent[option])
                      : "border-white/10 bg-[#272d38] text-slate-300 hover:bg-white/[0.08]"
                  )}
                >
                  {riskLabels[option]}
                </button>
              ))}
            </div>
          </div>

          <div>
            <div className="flex items-center justify-between">
              <label className="text-xs font-black text-white">Lignes</label>
              <span className="text-base font-black text-white">{rows}</span>
            </div>
            <input
              type="range"
              min={0}
              max={state.config.rowOptions.length - 1}
              step={1}
              value={rowIndex}
              onChange={(event) => setRows(state.config.rowOptions[Number(event.target.value)] ?? rows)}
              className="mt-2 h-2 w-full accent-felt"
            />
          </div>

          {mode === "auto" ? (
            <div>
              <div className="flex items-center justify-between">
                <label className="text-xs font-black text-white">Nombre de paris</label>
                <span className="text-[11px] font-black text-slate-500">{autoRunning ? "Auto actif" : "0 = illimité"}</span>
              </div>
              <div className="mt-1.5 flex h-11 overflow-hidden rounded-lg bg-[#20252e]">
                <input
                  value={betCount}
                  onChange={(event) => setBetCount(event.target.value)}
                  inputMode="numeric"
                  className="min-w-0 flex-1 bg-transparent px-3 text-sm font-bold text-white outline-none placeholder:text-slate-600"
                  placeholder="0"
                />
                <button onClick={() => setBetCount("0")} className="grid w-14 place-items-center border-l border-white/10 text-lg font-black text-white" aria-label="Illimité">
                  ∞
                </button>
              </div>
            </div>
          ) : null}

          <div className="grid grid-cols-2 gap-1.5 rounded-lg bg-[#20252e] p-1.5">
            {(["manual", "auto"] as const).map((item) => (
              <button
                key={item}
                onClick={() => setMode(item)}
                className={cn(
                  "h-9 rounded-md text-sm font-black transition",
                  mode === item ? "bg-[#3a404e] text-white" : "text-slate-400 hover:text-white"
                )}
              >
                {item === "manual" ? "Manuel" : "Auto"}
              </button>
            ))}
          </div>

          {message ? <p className="text-center text-[11px] font-bold text-slate-400">{message}</p> : null}
        </div>

        <div className="flex items-center justify-between border-t border-white/10 px-3 py-3">
          <div className="flex gap-5">
            <button onClick={() => setSettingsOpen(true)} className="text-slate-200 transition hover:text-white" aria-label="Paramètres">
              <Settings className="h-5 w-5" />
            </button>
            <button onClick={() => setStatsOpen(true)} className="text-slate-200 transition hover:text-white" aria-label="Statistiques">
              <BarChart3 className="h-5 w-5" />
            </button>
            <button className="text-slate-200 transition hover:text-white" aria-label="Favori">
              <Star className="h-5 w-5" />
            </button>
          </div>
          <button onClick={() => setFairnessOpen(true)} className="text-xs font-black text-slate-400 underline underline-offset-2 transition hover:text-white">
            Équité
          </button>
        </div>
      </section>

      <GameDetailsPanel kind="plinko" rounds={state.recentRounds} currency={state.currency} />

      <section className="space-y-2">
        <div className="flex items-center justify-between">
          <div className="flex items-center gap-2">
            <span className="grid h-7 w-7 place-items-center rounded-md bg-felt text-ink-950">
              <Sparkles className="h-4 w-4" />
            </span>
            <h2 className="text-lg font-black text-white">Plus de ZiraBet Games</h2>
          </div>
        </div>
        <div className="scrollbar-thin flex gap-2.5 overflow-x-auto pb-1">
          {otherGames.map((game) => (
            <Link key={game.slug} href={`/casino/games/${game.slug}`} className="group relative aspect-[150/205] w-[128px] shrink-0 overflow-hidden rounded-lg border border-white/10 bg-[#151a22] sm:w-[150px]">
              <Image src={game.image} alt="" fill sizes="150px" className="object-cover transition duration-300 group-hover:scale-105" />
              <div className="absolute inset-0 bg-gradient-to-t from-black via-black/25 to-transparent" />
              <div className="absolute inset-x-0 bottom-0 p-2.5">
                <p className="truncate text-[15px] font-black uppercase leading-none text-white">{game.title}</p>
                <span className="mt-1 inline-flex text-[9px] font-black uppercase text-felt">ZiraBet</span>
              </div>
            </Link>
          ))}
        </div>
      </section>

      {settingsOpen ? (
        <BottomSheet title="Paramètres" onClose={() => setSettingsOpen(false)}>
          <ToggleRow icon={<Gauge className="h-4 w-4" />} label="Animation complète" value={activeBalls.length === 0 ? "Disponible" : "En cours"} />
          <ToggleRow icon={<Target className="h-4 w-4" />} label="Risque actuel" value={riskLabels[risk]} />
        </BottomSheet>
      ) : null}

      {statsOpen ? (
        <BottomSheet title="Live Stats" onClose={() => setStatsOpen(false)}>
          <div className="grid grid-cols-2 gap-2">
            <StatCard label="Misé" value={formatMoney(state.stats.stake, state.currency)} />
            <StatCard label="Profit" value={formatMoney(state.stats.profit, state.currency)} tone={Number(state.stats.profit) >= 0 ? "green" : "red"} />
            <StatCard label="Gains" value={String(state.stats.wins)} tone="green" />
            <StatCard label="Pertes" value={String(state.stats.losses)} tone="red" />
          </div>
        </BottomSheet>
      ) : null}

      {fairnessOpen ? (
        <BottomSheet title="Équité" onClose={() => setFairnessOpen(false)}>
          <div className="space-y-3">
            <div className="grid grid-cols-2 gap-1.5 rounded-lg bg-white/[0.055] p-1.5">
              <button className="h-9 rounded-md bg-[#373d4b] text-xs font-black text-white">Seeds</button>
              <button className="h-9 rounded-md text-xs font-black text-felt">Vérifier</button>
            </div>
            <SeedField label="Seed de client actif" value={state.seedPair.clientSeed} />
            <SeedField label="Seed de serveur actif (haché)" value={state.seedPair.serverSeedHash} />
            <SeedField label="Total des paris effectués avec la paire" value={String(state.seedPair.nonce)} />
            <div className="grid grid-cols-[1fr_auto] gap-2">
              <input
                value={clientSeedInput}
                onChange={(event) => setClientSeedInput(event.target.value)}
                className="h-11 rounded-lg border border-white/10 bg-white/[0.06] px-3 text-sm font-bold text-white outline-none"
                placeholder="Nouvelle seed de client"
              />
              <button onClick={rotateSeeds} className="h-11 rounded-lg bg-felt px-4 text-sm font-black text-ink-950">
                Changer
              </button>
            </div>
            {revealedSeed ? <SeedField label="Seed de serveur révélée" value={revealedSeed.serverSeed} /> : null}
            <div className="border-t border-white/10 pt-3">
              <p className="text-xs font-black text-white">Vérifier un tirage</p>
              <div className="mt-2 space-y-2">
                <input value={verifyInput.serverSeed} onChange={(event) => setVerifyInput((current) => ({ ...current, serverSeed: event.target.value }))} className="h-10 w-full rounded-lg border border-white/10 bg-white/[0.06] px-3 text-xs font-bold text-white outline-none" placeholder="Seed serveur" />
                <input value={verifyInput.clientSeed} onChange={(event) => setVerifyInput((current) => ({ ...current, clientSeed: event.target.value }))} className="h-10 w-full rounded-lg border border-white/10 bg-white/[0.06] px-3 text-xs font-bold text-white outline-none" placeholder="Seed client" />
                <input value={verifyInput.nonce} onChange={(event) => setVerifyInput((current) => ({ ...current, nonce: event.target.value }))} className="h-10 w-full rounded-lg border border-white/10 bg-white/[0.06] px-3 text-xs font-bold text-white outline-none" placeholder="Nonce" />
                <button onClick={verifySeed} className="h-10 w-full rounded-lg bg-felt text-xs font-black text-black">Vérifier</button>
              </div>
              {verifyResult ? (
                <div className="mt-2 rounded-lg border border-felt/20 bg-felt/10 p-2 text-[11px] font-bold text-felt">
                  Case {verifyResult.bucket} · {formatMultiplier(verifyResult.multiplier)}x · {Number(verifyResult.chance).toLocaleString("fr-FR", { maximumFractionDigits: 4 })} %
                </div>
              ) : null}
            </div>
          </div>
        </BottomSheet>
      ) : null}
    </div>
  );
}

function PlinkoBoard({
  rows,
  multipliers,
  highlightedBucket,
  activeBalls
}: {
  rows: number;
  multipliers: number[];
  highlightedBucket: number | null;
  activeBalls: BallPosition[];
}) {
  const pins = useMemo(() => {
    const items: Array<{ id: string; x: number; y: number }> = [];
    for (let row = 0; row < rows; row += 1) {
      const count = row + 3;
      const y = 12.5 + (row / Math.max(1, rows - 1)) * 72;
      const spread = 13 + row * (72 / Math.max(1, rows));
      for (let index = 0; index < count; index += 1) {
        const ratio = count === 1 ? 0.5 : index / (count - 1);
        items.push({ id: `${row}-${index}`, x: 50 - spread / 2 + ratio * spread, y });
      }
    }
    return items;
  }, [rows]);

  return (
    <div className="relative overflow-hidden bg-[#090b0f] p-2 sm:p-3">
      <div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_0%,rgba(15,191,143,0.12),transparent_46%),radial-gradient(circle_at_8%_92%,rgba(15,191,143,0.08),transparent_32%)]" />
      <div
        aria-label="Plateau Plinko"
        className="relative h-[344px] rounded-lg border border-white/10 bg-[#10151d] shadow-inner sm:h-[424px]"
      >
        <div className="absolute left-1/2 top-3 h-7 w-7 -translate-x-1/2 rounded-full border border-felt/30 bg-felt/12 shadow-[0_0_24px_rgba(15,191,143,0.26)]">
          <div className="mx-auto mt-1.5 h-2.5 w-2.5 rounded-full bg-emerald-200" />
        </div>

        {pins.map((pin) => (
          <span
            key={pin.id}
            data-plinko-pin="true"
            className="absolute h-[5px] w-[5px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-slate-100 shadow-[0_0_8px_rgba(241,245,249,0.18)]"
            style={{ left: `${pin.x}%`, top: `${pin.y}%` }}
          />
        ))}

        {activeBalls.map((ball) => (
          <span
            key={ball.id}
            className={cn(
              "absolute z-20 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/70 bg-[radial-gradient(circle_at_32%_28%,#ffffff,#b8fff0_30%,#0fbf8f_70%,#047857)] shadow-[0_0_14px_rgba(15,191,143,0.5)]",
              "animate-plinko-ball-glow"
            )}
            style={{
              left: `${ball.x}%`,
              top: `${ball.y}%`,
              transition: "left 150ms cubic-bezier(0.18, 0.82, 0.22, 1), top 150ms cubic-bezier(0.18, 0.82, 0.22, 1)"
            }}
          />
        ))}

        <div className="absolute inset-x-1.5 bottom-4 grid gap-[2px]" style={{ gridTemplateColumns: `repeat(${multipliers.length}, minmax(0, 1fr))` }}>
          {multipliers.map((multiplier, index) => (
            <div
              key={`${multiplier}-${index}`}
              data-plinko-bucket={index}
              title={`${formatMultiplier(multiplier)}x`}
              className={cn(
                "grid h-6 min-w-0 place-items-center overflow-hidden rounded-[6px] px-0 text-center font-bold leading-none tracking-normal shadow-[inset_0_-1px_0_rgba(0,0,0,0.18)] transition",
                multipliers.length >= 17 ? "text-[6px] sm:text-[7px]" : "text-[7px] sm:text-[8px]",
                highlightedBucket === index
                  ? "bg-felt text-black ring-1 ring-inset ring-felt/70 shadow-[0_0_14px_rgba(15,191,143,0.32)]"
                  : getBucketTone(index, multipliers.length)
              )}
            >
              {formatBucketMultiplier(multiplier, multipliers.length)}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function HistoryPill({ round }: { round: PlinkoRound }) {
  const won = Number(round.profit) >= 0;
  return (
    <span
      className={cn(
        "shrink-0 rounded-md px-2 py-1 text-[10px] font-black",
        won ? "bg-felt/15 text-felt" : "bg-rose-500/12 text-rose-300"
      )}
    >
      {formatMultiplier(round.multiplier)}x
    </span>
  );
}

function ControlSelect({
  label,
  value,
  onChange,
  children
}: {
  label: string;
  value: string | number;
  onChange: (value: string) => void;
  children: ReactNode;
}) {
  return (
    <label className="block">
      <span className="text-xs font-black text-white">{label}</span>
      <span className="relative mt-1.5 block">
        <select
          value={value}
          onChange={(event) => onChange(event.target.value)}
          className="h-10 w-full appearance-none rounded-lg border border-white/10 bg-[#20252e] px-3 pr-8 text-xs font-black text-white outline-none"
        >
          {children}
        </select>
        <ChevronDown className="pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
      </span>
    </label>
  );
}

function BottomSheet({ title, onClose, children }: { title: string; onClose: () => void; children: ReactNode }) {
  return (
    <div className="fixed inset-0 z-50 bg-black/55">
      <div className="absolute inset-x-0 bottom-0 mx-auto max-h-[82vh] max-w-[520px] overflow-y-auto rounded-t-2xl border border-white/10 bg-[#111720] p-4 shadow-2xl">
        <div className="mx-auto mb-4 h-1 w-10 rounded-full bg-slate-600" />
        <div className="mb-4 flex items-center justify-between">
          <h2 className="text-xl font-black text-white">{title}</h2>
          <button onClick={onClose} className="grid h-9 w-9 place-items-center rounded-full bg-white/[0.08] text-white">
            <X className="h-5 w-5" />
          </button>
        </div>
        {children}
      </div>
    </div>
  );
}

function SeedField({ label, value }: { label: string; value: string }) {
  return (
    <div>
      <p className="text-xs font-black text-white">{label}</p>
      <div className="mt-1.5 flex h-10 items-center gap-2 rounded-lg border border-white/10 bg-white/[0.04] px-3">
        <span className="min-w-0 flex-1 truncate text-xs font-bold text-slate-400">{value}</span>
        <button onClick={() => void navigator.clipboard.writeText(value)} className="text-slate-200">
          <Copy className="h-4 w-4" />
        </button>
      </div>
    </div>
  );
}

function ToggleRow({ icon, label, value }: { icon: ReactNode; label: string; value: ReactNode }) {
  return (
    <div className="flex h-12 items-center justify-between rounded-lg border border-white/10 bg-white/[0.04] px-3">
      <span className="flex items-center gap-2 text-xs font-black text-white">
        {icon}
        {label}
      </span>
      <span className="text-xs font-black text-felt">{value}</span>
    </div>
  );
}

function StatBox({ label, value }: { label: string; value: string }) {
  return (
    <div className="rounded-lg border border-white/10 bg-black/18 p-2.5">
      <p className="text-[10px] font-black uppercase tracking-[0.06em] text-slate-500">{label}</p>
      <p className="mt-1 truncate text-xs font-black text-white">{value}</p>
    </div>
  );
}

function StatCard({ label, value, tone = "neutral" }: { label: string; value: string; tone?: "neutral" | "green" | "red" }) {
  return (
    <div className="rounded-lg bg-white/[0.055] p-3">
      <p className="text-xs font-bold text-slate-500">{label}</p>
      <p className={cn("mt-1 text-sm font-black", tone === "green" ? "text-felt" : tone === "red" ? "text-rose-300" : "text-white")}>{value}</p>
    </div>
  );
}

function getBucketTone(index: number, length: number) {
  const edge = Math.min(index, length - 1 - index);
  if (edge <= 1) return "bg-rose-600/90 text-white";
  if (edge <= 3) return "bg-ember/90 text-ink-950";
  return "bg-gold/85 text-ink-950";
}

function clamp(value: number, min: number, max: number) {
  return Math.max(min, Math.min(max, value));
}

function wait(ms: number) {
  return new Promise((resolve) => window.setTimeout(resolve, ms));
}

function readPositiveInteger(value: string, fallback: number, max: number, allowZero = false) {
  const parsed = Number.parseInt(value, 10);
  if (!Number.isFinite(parsed) || parsed < 0) return fallback;
  if (!allowZero && parsed === 0) return fallback;
  return Math.min(parsed, max);
}

function formatMoney(value: string | number, currency: string) {
  return new Intl.NumberFormat("fr-FR", { style: "currency", currency }).format(Number(value));
}

function formatMultiplier(value: string | number) {
  return Number(value).toLocaleString("fr-FR", { maximumFractionDigits: 2, useGrouping: false });
}

function formatBucketMultiplier(value: string | number, bucketCount: number) {
  const numeric = Number(value);
  if (bucketCount >= 15 && numeric >= 100) {
    return numeric.toLocaleString("fr-FR", { maximumFractionDigits: 0, useGrouping: false });
  }
  return `${formatMultiplier(value)}x`;
}
