"use client";

import Link from "next/link";
import { createContext, useContext, useEffect, useMemo, useState } from "react";
import {
  CheckCircle2,
  ChevronDown,
  ChevronUp,
  CircleDollarSign,
  Loader2,
  ReceiptText,
  ShieldAlert,
  Trash2,
  X
} from "lucide-react";
import { notifyWalletUpdated } from "@/lib/client-events";
import { cn, formatMoney, formatOdds } from "@/lib/utils";

export type SlipSelection = {
  selectionId: string;
  eventId: string;
  marketId: string;
  eventName: string;
  marketName: string;
  selectionName: string;
  odds: string;
  oddsVersion: number;
  boosted?: boolean;
  originalOdds?: string;
  maxStake?: string;
  allowCombinations?: boolean;
  terms?: string;
  sameGame?: {
    legs: Array<{
      selectionId: string;
      marketId: string;
      marketName: string;
      selectionName: string;
      odds: string;
      oddsVersion: number;
    }>;
  };
};

type BetSlipContextValue = {
  selections: SlipSelection[];
  addSelection: (selection: SlipSelection) => void;
  removeSelection: (selectionId: string) => void;
  clear: () => void;
};

type CouponMode = "single" | "combo";

const BetSlipContext = createContext<BetSlipContextValue | null>(null);

export function BetSlipProvider({ children }: { children: React.ReactNode }) {
  const [selections, setSelections] = useState<SlipSelection[]>([]);

  const value = useMemo<BetSlipContextValue>(
    () => ({
      selections,
      addSelection(selection) {
        setSelections((current) => {
          const exists = current.some((item) => item.selectionId === selection.selectionId);
          if (exists) {
            return current.filter((item) => item.selectionId !== selection.selectionId);
          }
          const next = selection.sameGame ? current.filter((item) => !(item.sameGame && item.eventId === selection.eventId)) : current;
          return [...next, selection];
        });
      },
      removeSelection(selectionId) {
        setSelections((current) => current.filter((item) => item.selectionId !== selectionId));
      },
      clear() {
        setSelections([]);
      }
    }),
    [selections]
  );

  return <BetSlipContext.Provider value={value}>{children}</BetSlipContext.Provider>;
}

export function useBetSlip() {
  const context = useContext(BetSlipContext);
  if (!context) {
    throw new Error("useBetSlip must be used within BetSlipProvider");
  }
  return context;
}

export function BetSlip({
  compact = false,
  open = true,
  isAuthenticated = true,
  onClose
}: {
  compact?: boolean;
  open?: boolean;
  isAuthenticated?: boolean;
  onClose?: () => void;
}) {
  const { selections, removeSelection, clear } = useBetSlip();
  const [mode, setMode] = useState<CouponMode>("single");
  const [singleStakes, setSingleStakes] = useState<Record<string, string>>({});
  const [comboStake, setComboStake] = useState("");
  const [state, setState] = useState<"idle" | "placing" | "success" | "error">("idle");
  const [message, setMessage] = useState("");
  const [lastPlacedAt, setLastPlacedAt] = useState(0);
  const comboValidation = validateCombo(selections);
  const hasSameGameSelection = selections.some((selection) => selection.sameGame);
  const totalOdds = selections.reduce((acc, item) => acc * Number(item.odds), 1);
  const singleTotalStake = selections.reduce((total, selection) => total + moneyValue(singleStakes[selection.selectionId]), 0);
  const singlePotential = selections.reduce((total, selection) => total + moneyValue(singleStakes[selection.selectionId]) * Number(selection.odds), 0);
  const comboStakeValue = moneyValue(comboStake);
  const comboPotential = comboValidation.ok ? comboStakeValue * totalOdds : 0;
  const totalStake = mode === "single" ? singleTotalStake : comboStakeValue;
  const potential = mode === "single" ? singlePotential : comboPotential;
  const singleStakeLimitsOk = selections.every((selection) => !isBoostedStakeTooHigh(selection, moneyValue(singleStakes[selection.selectionId])));
  const comboStakeLimit = getComboBoostedStakeLimit(selections);
  const comboStakeLimitsOk = comboStakeLimit == null || comboStakeValue <= comboStakeLimit;
  const canPlace =
    isAuthenticated &&
    selections.length > 0 &&
    state !== "placing" &&
    totalStake > 0 &&
    (mode === "single"
      ? selections.some((selection) => moneyValue(singleStakes[selection.selectionId]) > 0) && singleStakeLimitsOk
      : comboValidation.ok && comboStakeLimitsOk);

  useEffect(() => {
    setSingleStakes((current) => {
      const next: Record<string, string> = {};
      for (const selection of selections) {
        next[selection.selectionId] = current[selection.selectionId] ?? "";
      }
      return next;
    });
  }, [selections]);

  useEffect(() => {
    if (lastPlacedAt > 0 && selections.length === 0) return;

    if (lastPlacedAt > 0) {
      setLastPlacedAt(0);
    }

    setState("idle");
    setMessage("");
  }, [lastPlacedAt, mode, selections.length]);

  useEffect(() => {
    if (mode === "combo" && hasSameGameSelection) {
      setMode("single");
    }
  }, [hasSameGameSelection, mode]);

  async function place() {
    if (!canPlace) return;

    setState("placing");
    setMessage("");
    setLastPlacedAt(0);

    try {
      let successMessage = "Votre pari a bien été placé.";

      if (mode === "combo") {
        await placeTicket(comboStakeValue, selections);
        successMessage = "Votre combiné a bien été placé.";
      } else {
        const singlesToPlace = selections.filter((selection) => moneyValue(singleStakes[selection.selectionId]) > 0);
        for (const selection of singlesToPlace) {
          await placeTicket(moneyValue(singleStakes[selection.selectionId]), [selection]);
        }
        successMessage =
          singlesToPlace.length > 1
            ? "Vos paris simples ont bien été placés."
            : "Votre pari simple a bien été placé.";
      }

      setState("success");
      setMessage(successMessage);
      setLastPlacedAt(Date.now());
      clear();
      setSingleStakes({});
      setComboStake("");
      notifyWalletUpdated();
    } catch (error) {
      setState("error");
      setMessage(error instanceof Error ? error.message : "Placement refusé.");
    }
  }

  async function placeTicket(stake: number, items: SlipSelection[]) {
    const sameGameItem = items.find((selection) => selection.sameGame);
    if (sameGameItem && items.length > 1) {
      throw new Error("Un combiné sur le même match ne peut pas être rejoué dans un combiné classique.");
    }
    const payloadSelections = sameGameItem?.sameGame
      ? sameGameItem.sameGame.legs.map((leg) => ({
          selectionId: leg.selectionId,
          odds: Number(leg.odds),
          oddsVersion: leg.oddsVersion
        }))
      : items.map((selection) => ({
          selectionId: selection.selectionId,
          odds: Number(selection.odds),
          oddsVersion: selection.oddsVersion
        }));

    const response = await fetch("/api/bets/place", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        currency: "EUR",
        stake,
        acceptOddsChange: "higher",
        sameGame: Boolean(sameGameItem),
        selections: payloadSelections
      })
    });
    const payload = await response.json();

    if (!response.ok || !payload.ok) {
      throw new Error(payload.error?.message ?? "Placement refusé.");
    }
  }

  function clearCoupon() {
    clear();
    setSingleStakes({});
    setComboStake("");
    setState("idle");
    setMessage("");
    setLastPlacedAt(0);
  }

  if (compact && !open) return null;

  return (
    <section
      className={cn(
        "overflow-hidden border-white/10 bg-[#0d1017] text-white shadow-panel",
        compact
          ? "fixed inset-x-0 top-0 bottom-[calc(4.65rem+env(safe-area-inset-bottom))] z-[49] flex flex-col border-b"
          : "surface rounded-lg"
      )}
      aria-label="Coupon de pari"
    >
      <header className={cn("flex items-center justify-between border-b border-white/10 px-4", compact ? "h-[72px]" : "h-14")}>
        <div className="flex min-w-0 items-center gap-2">
          <h2 className={cn("truncate font-extrabold text-white", compact ? "text-[22px]" : "text-base")}>Coupon de pari</h2>
          <span className="grid h-6 min-w-6 place-items-center rounded-md bg-felt px-1.5 text-sm font-black text-ink-950">{selections.length}</span>
          <ChevronDown className="h-4 w-4 text-slate-300" />
        </div>
        <div className="flex items-center gap-2">
          {compact ? (
            <button onClick={onClose} className="grid h-9 w-9 place-items-center rounded-full text-slate-300 hover:bg-white/[0.07] hover:text-white" aria-label="Fermer le coupon">
              <X className="h-5 w-5" />
            </button>
          ) : null}
        </div>
      </header>

      <div className="grid grid-cols-2 gap-2 border-b border-white/10 px-4 py-3">
        <ModeButton active={mode === "single"} label={`Simples (${selections.length})`} onClick={() => setMode("single")} />
        <ModeButton active={mode === "combo"} label="Combiné" disabled={hasSameGameSelection} onClick={() => setMode("combo")} />
      </div>

      <div className={cn("flex-1 overflow-y-auto px-4 py-4 scrollbar-thin", compact ? "min-h-0" : "max-h-[52vh]")}>
        {selections.length === 0 ? (
          state === "success" && message ? (
            <div className="rounded-lg border border-felt/25 bg-felt/10 p-4 text-center">
              <CheckCircle2 className="mx-auto mb-2 h-8 w-8 text-felt" />
              <p className="text-sm font-extrabold text-white">Pari confirmé</p>
              <p className="mt-1 text-xs font-semibold leading-5 text-slate-300">{message}</p>
            </div>
          ) : (
            <div className="grid min-h-32 place-items-center rounded-lg border border-dashed border-white/10 p-4 text-center">
              <div>
                <ReceiptText className="mx-auto mb-2 h-7 w-7 text-slate-500" />
                <p className="text-sm font-semibold text-slate-400">Ajoutez une cote pour préparer votre coupon.</p>
              </div>
            </div>
          )
        ) : mode === "single" ? (
          <SingleBets
            selections={selections}
            stakes={singleStakes}
            onStakeChange={(selectionId, value) => setSingleStakes((current) => ({ ...current, [selectionId]: value }))}
            onRemove={removeSelection}
          />
        ) : (
          <ComboBet
            selections={selections}
            stake={comboStake}
            totalOdds={totalOdds}
            validation={comboValidation}
            onStakeChange={setComboStake}
            onRemove={removeSelection}
          />
        )}
      </div>

      <footer className="shrink-0 border-t border-white/10 bg-[#10131a] px-4 py-3">
        {!isAuthenticated ? (
          <div className="mb-3 rounded-lg border border-felt/20 bg-felt/10 px-3 py-2 text-xs font-semibold leading-5 text-felt">
            Connectez-vous pour placer un pari. Vous pouvez consulter les cotes sans compte.
          </div>
        ) : null}
        <div className="space-y-1.5">
          <SummaryLine label="Mise Totale" value={formatMoney(totalStake)} />
          <SummaryLine label="Est. Paiement" value={formatMoney(Number.isFinite(potential) ? potential : 0)} highlight />
        </div>
        <div className="mt-3 grid grid-cols-2 gap-3">
          <button
            onClick={clearCoupon}
            disabled={selections.length === 0 || state === "placing"}
            className="h-11 rounded-lg bg-[#2a303d] text-sm font-extrabold text-white transition hover:bg-[#333b4b] disabled:cursor-not-allowed disabled:opacity-50"
          >
            Effacer les paris
          </button>
          <button
            onClick={() => void place()}
            disabled={!canPlace}
            className="flex h-11 items-center justify-center gap-2 rounded-lg bg-felt text-sm font-extrabold text-ink-950 transition hover:bg-emerald-300 disabled:cursor-not-allowed disabled:bg-slate-400 disabled:text-white"
          >
            {state === "placing" ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
            {isAuthenticated ? "Faire des paris" : "Connexion requise"}
          </button>
        </div>
        <Link
          href="/bets"
          onClick={onClose}
          className="mt-2 flex h-10 items-center justify-center rounded-lg border border-white/10 bg-white/[0.04] text-xs font-extrabold text-slate-100 transition hover:bg-white/[0.08] hover:text-white"
        >
          Voir mes paris
        </Link>
        {message && state === "error" ? <p className="mt-2 text-xs font-semibold text-ember">{message}</p> : null}
      </footer>
    </section>
  );
}

function ModeButton({ active, label, disabled = false, onClick }: { active: boolean; label: string; disabled?: boolean; onClick: () => void }) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={cn(
        "h-9 rounded-lg border text-xs font-extrabold transition disabled:cursor-not-allowed disabled:opacity-45",
        active ? "border-felt bg-felt/12 text-felt" : "border-white/10 bg-white/[0.04] text-slate-300 hover:bg-white/[0.07]"
      )}
    >
      {label}
    </button>
  );
}

function SingleBets({
  selections,
  stakes,
  onStakeChange,
  onRemove
}: {
  selections: SlipSelection[];
  stakes: Record<string, string>;
  onStakeChange: (selectionId: string, value: string) => void;
  onRemove: (selectionId: string) => void;
}) {
  return (
    <div className="space-y-4">
      <SectionTitle label={`Simples (${selections.length})`} />
      {selections.map((selection, index) => {
        const stake = stakes[selection.selectionId] ?? "";
        const potential = moneyValue(stake) * Number(selection.odds);
        const maxStake = getBoostedMaxStake(selection);
        const stakeTooHigh = isBoostedStakeTooHigh(selection, moneyValue(stake));

        return (
          <article key={selection.selectionId} className="overflow-hidden rounded-lg bg-[#202531] shadow-[0_14px_34px_rgba(0,0,0,.22)]">
            <div className="flex min-h-12 items-center justify-between gap-3 bg-[#2a303d] px-3">
              <div className="flex min-w-0 items-center gap-2">
                <ReceiptText className="h-4 w-4 shrink-0 text-felt" />
                <span className="truncate text-sm font-semibold text-white">{selection.eventName}</span>
              </div>
              <button onClick={() => onRemove(selection.selectionId)} className="grid h-8 w-8 shrink-0 place-items-center rounded-full text-slate-400 hover:bg-white/[0.07] hover:text-white" aria-label="Retirer">
                <X className="h-4 w-4" />
              </button>
            </div>
            <div className="border-t border-dashed border-[#11151c]" />
            <div className="px-3 py-3">
              <div className="flex items-start justify-between gap-3">
                <div className="min-w-0">
                  <div className="truncate text-sm font-extrabold text-felt">{selection.selectionName}</div>
                  <div className="mt-1 truncate text-[11px] font-semibold text-slate-500">{selection.marketName}</div>
                </div>
                <div className="shrink-0 text-sm font-extrabold text-white">{formatOdds(selection.odds)}</div>
              </div>
              {selection.sameGame ? <SameGameLegList selection={selection} /> : null}
              {selection.boosted ? <BoostedMeta selection={selection} /> : null}
              {index > 0 ? <div className="mt-3 text-xs font-extrabold text-slate-600">Réutiliser la mise</div> : null}
              <div className="mt-3 grid grid-cols-[minmax(0,1fr)_auto] items-end gap-3">
                <StakeInput value={stake} onChange={(value) => onStakeChange(selection.selectionId, value)} />
                <div className="min-w-[118px] text-right">
                  <div className="text-[11px] font-semibold text-slate-300">Est. Paiement</div>
                  <CurrencyAmount value={potential} />
                </div>
              </div>
              {stakeTooHigh && maxStake != null ? <StakeLimitError maxStake={maxStake} /> : null}
            </div>
          </article>
        );
      })}
    </div>
  );
}

function ComboBet({
  selections,
  stake,
  totalOdds,
  validation,
  onStakeChange,
  onRemove
}: {
  selections: SlipSelection[];
  stake: string;
  totalOdds: number;
  validation: ComboValidation;
  onStakeChange: (value: string) => void;
  onRemove: (selectionId: string) => void;
}) {
  if (selections.length < 2) {
    return (
      <div className="space-y-4">
        <SectionTitle label="Combinés" />
        <div className="rounded-lg border border-felt/60 bg-felt/5 p-4 text-sm font-semibold leading-6 text-slate-200">
          Ajoutez plus de sélections valides pour créer un combiné et augmenter vos gains potentiels.
        </div>
      </div>
    );
  }

  if (!validation.ok) {
    return (
      <div className="space-y-4">
        <SectionTitle label="Combinés" />
        <div className="rounded-lg border border-ember/50 bg-ember/10 p-4 text-sm font-semibold leading-6 text-ember">
          <div className="mb-2 flex items-center gap-2 font-extrabold">
            <ShieldAlert className="h-4 w-4" />
            Combiné impossible
          </div>
          {validation.message}
        </div>
        <SelectionList selections={selections} onRemove={onRemove} />
      </div>
    );
  }

  const potential = moneyValue(stake) * totalOdds;
  const comboStakeValue = moneyValue(stake);
  const comboMaxStake = getComboBoostedStakeLimit(selections);
  const stakeTooHigh = comboMaxStake != null && comboStakeValue > comboMaxStake;

  return (
    <div className="space-y-4">
      <SectionTitle label="Combinés" />
      <article className="overflow-hidden rounded-lg bg-[#202531] shadow-[0_14px_34px_rgba(0,0,0,.22)]">
        <div className="flex min-h-12 items-center justify-between gap-3 bg-[#2a303d] px-3">
          <div className="flex min-w-0 items-center gap-2">
            <ReceiptText className="h-4 w-4 shrink-0 text-felt" />
            <span className="truncate text-sm font-semibold text-white">{selections.length} Standard Multi</span>
          </div>
          <span className="shrink-0 text-sm font-extrabold text-white">{formatOdds(totalOdds)}</span>
        </div>
        <div className="border-t border-dashed border-[#11151c]" />
        <div className="px-3 py-3">
          <div className="flex items-start justify-between gap-3">
            <div className="text-sm font-extrabold text-felt">{selections.length} Étapes</div>
            <div className="text-sm font-extrabold text-white">{formatOdds(totalOdds)}</div>
          </div>
          <div className="mt-3 grid grid-cols-[minmax(0,1fr)_auto] items-end gap-3">
            <StakeInput value={stake} onChange={onStakeChange} />
            <div className="min-w-[118px] text-right">
              <div className="text-[11px] font-semibold text-slate-300">Est. Paiement</div>
              <CurrencyAmount value={potential} />
            </div>
          </div>
          {stakeTooHigh && comboMaxStake != null ? <StakeLimitError maxStake={comboMaxStake} /> : null}
          <div className="mt-4 space-y-2">
            {selections.map((selection) => (
              <div key={selection.selectionId} className="rounded-lg bg-white/[0.045] p-3">
                <div className="flex items-center justify-between gap-3">
                  <div className="min-w-0">
                    <div className="truncate text-xs font-semibold text-slate-300">{selection.eventName}</div>
                    <div className="mt-2 truncate text-sm font-extrabold text-felt">{selection.selectionName}</div>
                    <div className="mt-1 text-[11px] font-semibold text-slate-500">{selection.marketName}</div>
                    {selection.boosted ? <BoostedMeta selection={selection} compact /> : null}
                  </div>
                  <button onClick={() => onRemove(selection.selectionId)} className="grid h-8 w-8 shrink-0 place-items-center rounded-full text-slate-400 hover:bg-white/[0.07] hover:text-white" aria-label="Retirer">
                    <X className="h-4 w-4" />
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
      </article>
    </div>
  );
}

function SelectionList({ selections, onRemove }: { selections: SlipSelection[]; onRemove: (selectionId: string) => void }) {
  return (
    <div className="space-y-2">
      {selections.map((selection) => (
        <div key={selection.selectionId} className="flex items-center justify-between gap-3 rounded-lg bg-white/[0.04] px-3 py-2">
          <div className="min-w-0">
            <div className="truncate text-xs font-semibold text-white">{selection.selectionName}</div>
            <div className="truncate text-[11px] text-slate-500">{selection.eventName}</div>
          </div>
          <button onClick={() => onRemove(selection.selectionId)} className="text-slate-500 hover:text-white" aria-label="Retirer">
            <Trash2 className="h-4 w-4" />
          </button>
        </div>
      ))}
    </div>
  );
}

function SameGameLegList({ selection }: { selection: SlipSelection }) {
  if (!selection.sameGame) return null;

  return (
    <div className="mt-3 rounded-lg bg-white/[0.045] p-3">
      <div className="mb-2 text-[11px] font-bold text-slate-400">Combiné en {selection.sameGame.legs.length} sélections sur le même match</div>
      <div className="space-y-2">
        {selection.sameGame.legs.map((leg) => (
          <div key={leg.selectionId} className="border-t border-white/10 pt-2 first:border-t-0 first:pt-0">
            <div className="truncate text-[12px] font-extrabold text-felt">{leg.selectionName}</div>
            <div className="mt-0.5 flex items-center justify-between gap-2 text-[11px] font-semibold text-slate-500">
              <span className="truncate">{leg.marketName}</span>
              <span className="shrink-0 text-slate-300">{formatOdds(leg.odds)}</span>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function StakeInput({ value, onChange }: { value: string; onChange: (value: string) => void }) {
  return (
    <label className="flex h-11 min-w-0 items-center gap-2 rounded-lg bg-[#11141b] px-2.5 ring-1 ring-white/[0.04]">
      <span className="grid h-6 w-6 shrink-0 place-items-center rounded-full bg-felt text-ink-950">
        <CircleDollarSign className="h-3.5 w-3.5" />
      </span>
      <input
        value={value}
        onChange={(event) => onChange(sanitizeMoneyInput(event.target.value))}
        inputMode="decimal"
        placeholder="Entrez votre mise"
        className="h-full min-w-0 flex-1 border-0 bg-transparent p-0 text-xs font-semibold text-white placeholder:text-slate-500 focus:ring-0"
      />
    </label>
  );
}

function CurrencyAmount({ value }: { value: number }) {
  return (
    <div className="mt-1 inline-flex items-center justify-end gap-1.5 text-sm font-extrabold text-white">
      <span className="grid h-5 w-5 place-items-center rounded-full bg-sky-500/20 text-sky-300">
        <CircleDollarSign className="h-3.5 w-3.5" />
      </span>
      {formatMoney(Number.isFinite(value) ? value : 0)}
    </div>
  );
}

function SummaryLine({ label, value, highlight = false }: { label: string; value: string; highlight?: boolean }) {
  return (
    <div className="flex items-center justify-between gap-3">
      <span className="text-sm font-semibold text-slate-400">{label}</span>
      <span className={cn("text-sm font-extrabold", highlight ? "text-white" : "text-slate-100")}>{value}</span>
    </div>
  );
}

function SectionTitle({ label }: { label: string }) {
  return (
    <div className="flex items-center justify-between">
      <h3 className="text-sm font-semibold text-slate-400">{label}</h3>
      <ChevronUp className="h-4 w-4 text-slate-300" />
    </div>
  );
}

function BoostedMeta({ selection, compact = false }: { selection: SlipSelection; compact?: boolean }) {
  const maxStake = getBoostedMaxStake(selection);
  return (
    <div className={cn("mt-2 rounded-md border border-felt/15 bg-felt/5 px-2.5 py-2 text-[11px] font-semibold text-slate-300", compact ? "max-w-full" : "")}>
      Boostée de {selection.originalOdds ? formatOdds(selection.originalOdds) : "-"} · Mise max {maxStake != null ? formatMoney(maxStake) : "-"}
      {selection.terms ? ` · ${selection.terms}` : ""}
    </div>
  );
}

function StakeLimitError({ maxStake }: { maxStake: number }) {
  return <p className="mt-2 text-[11px] font-bold text-ember">Mise maximale autorisée sur cette cote boostée : {formatMoney(maxStake)}.</p>;
}

type ComboValidation = { ok: true } | { ok: false; message: string };

function validateCombo(selections: SlipSelection[]): ComboValidation {
  if (selections.length < 2) return { ok: false, message: "Ajoutez au moins deux sélections." };

  const sameGameSelection = selections.find((selection) => selection.sameGame);
  if (sameGameSelection) {
    return {
      ok: false,
      message: `Le ticket "${sameGameSelection.marketName}" doit rester en pari simple. Il ne peut pas être combiné avec d'autres matchs.`
    };
  }

  const blockedBoost = selections.find((selection) => selection.boosted && selection.allowCombinations === false);
  if (blockedBoost) {
    return {
      ok: false,
      message: `La cote boostée "${blockedBoost.selectionName}" est disponible en pari simple uniquement. Retirez-la pour créer un combiné.`
    };
  }

  const events = new Map<string, SlipSelection>();
  const markets = new Map<string, SlipSelection>();

  for (const selection of selections) {
    const sameEvent = events.get(selection.eventId);
    if (sameEvent) {
      return {
        ok: false,
        message: `Les sélections "${sameEvent.selectionName}" et "${selection.selectionName}" concernent le même match. Elles peuvent être jouées en simple, mais pas dans le même combiné.`
      };
    }

    const sameMarket = markets.get(selection.marketId);
    if (sameMarket) {
      return {
        ok: false,
        message: `Les sélections "${sameMarket.selectionName}" et "${selection.selectionName}" appartiennent au même marché. Retirez une sélection pour créer un combiné.`
      };
    }

    events.set(selection.eventId, selection);
    markets.set(selection.marketId, selection);
  }

  return { ok: true };
}

function sanitizeMoneyInput(value: string) {
  return value.replace(",", ".").replace(/[^0-9.]/g, "").replace(/(\..*)\./g, "$1");
}

function moneyValue(value?: string) {
  const parsed = Number(value || 0);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
}

function getBoostedMaxStake(selection: SlipSelection) {
  if (!selection.boosted || !selection.maxStake) return null;
  const parsed = Number(selection.maxStake);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}

function isBoostedStakeTooHigh(selection: SlipSelection, stake: number) {
  const maxStake = getBoostedMaxStake(selection);
  return maxStake != null && stake > maxStake;
}

function getComboBoostedStakeLimit(selections: SlipSelection[]) {
  const limits = selections.map(getBoostedMaxStake).filter((value): value is number => value != null);
  return limits.length ? Math.min(...limits) : null;
}
