"use client"

// Individuell/flerrunde-turnering (ADR-037) -- flatt felt av spillere over
// én eller flere runder, ingen lag. Egen skjerm, adskilt fra
// tournament-detail.tsx (lagformat, ADR-011) -- rutet dit av
// tournament-router.tsx basert på tournament.format_type.
//
// Håndkodet (ikke V0) på brukerens eksplisitte instruks. Bruker
// --info (blå) for "Individuell"-merket og runde-sammenheng, og --gold
// for lederen på leaderboardet -- samme etablerte, validerte tokens som
// round-card.tsx allerede bruker (HCP-spilt-til/personlig rekord), ikke
// nye, ukalibrerte farger.

import type React from "react"
import { useEffect, useMemo, useState } from "react"
import Link from "next/link"
import {
  ArrowLeft,
  Check,
  ChevronDown,
  Copy,
  Flag,
  KeyRound,
  Medal,
  Plus,
  Trash2,
  Trophy,
  UserPlus,
  Users,
  X,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { TournamentStatusPicker, type TournamentStatus } from "@/components/tournament-status-badge"
import { cn } from "@/lib/utils"

// --- Types (matcher app/routers/individual_tournaments.py/tournaments.py) --

type ApiPlayer = {
  id: string
  display_name: string
  handicap_index: number | null
  gender: "m" | "f" | "x" | null
}

type ApiTournamentInfo = {
  id: string
  status: TournamentStatus
  join_code: string
  format_type: string
  scoring_method: string | null
  // KUN meningsfullt når scoring_method="bingo_bango_bongo" (2026-07-30).
  bbb_sweep_bonus_enabled: boolean
}

type ApiParticipant = {
  id: string
  player_id: string
  player_name: string
  handicap_index_snapshot: number | null
}

type ApiRound = {
  id: string
  tournament_id: string
  sequence: number
  name: string | null
  hole_config: "full_18" | "front_9" | "back_9"
  course_id: string
  course_name: string
  scheduled_at: string | null
  tee_interval_minutes: number | null
  start_hole: number
}

type ApiRoundParticipant = {
  id: string
  tournament_participant_id: string
  player_name: string
  tee_id: string
  tee_name: string
  course_handicap: number | null
  playing_handicap: number | null
}

type ApiHole = {
  hole_number: number
  par: number
  stroke_index: number
  gross_strokes: number | null
  strokes_received: number | null
}

type ApiLeaderboardEntry = {
  tournament_participant_id: string
  player_name: string
  rounds_played: number
  gross_total: number | null
  net_total: number | null
  stableford_total: number | null
  copenhagen_total: number | null
  bbb_total: number | null
}

type ApiFlagResult = {
  tournament_participant_id: string
  player_name: string
  holes_completed: number
  ran_out: boolean
  strokes_remaining: number
}

type ApiBBBHole = {
  hole_number: number
  bingo_participant_id: string | null
  bango_participant_id: string | null
  bongo_participant_id: string | null
}

type ApiCourse = { id: string; name: string; source: string }
type ApiTeeRating = { gender: string; course_rating: number; slope_rating: number; par: number }
type ApiTee = { id: string; name: string; ratings: ApiTeeRating[] }

const HOLE_CONFIG_LABELS: Record<ApiRound["hole_config"], string> = {
  full_18: "18 hull",
  front_9: "Hull 1–9",
  back_9: "Hull 10–18",
}

const SCORING_METHOD_LABELS: Record<string, string> = {
  stroke_gross: "Bruttoslagspill",
  stroke_net: "Nettoslagspill",
  stableford: "Stableford",
  copenhagen: "Københavner",
  bingo_bango_bongo: "Bingo Bango Bongo",
  flag: "Flaggturnering",
}

function holeNumbersFor(config: ApiRound["hole_config"]): number[] {
  if (config === "front_9") return Array.from({ length: 9 }, (_, i) => i + 1)
  if (config === "back_9") return Array.from({ length: 9 }, (_, i) => i + 10)
  return Array.from({ length: 18 }, (_, i) => i + 1)
}

async function getJson<T>(url: string): Promise<T | null> {
  const res = await fetch(url, { credentials: "include" })
  if (!res.ok) return null
  return (await res.json()) as T
}

async function errorMessage(res: Response, fallback: string): Promise<string> {
  try {
    const body = await res.json()
    return body?.detail?.message ?? fallback
  } catch {
    return fallback
  }
}

// --- Component ---------------------------------------------------------

export function IndividualTournamentDetail({
  organizationId,
  tournamentId,
  tournamentName,
}: {
  organizationId: string
  tournamentId: string
  tournamentName: string
}) {
  const [tournament, setTournament] = useState<ApiTournamentInfo | null>(null)
  const [participants, setParticipants] = useState<ApiParticipant[]>([])
  const [rounds, setRounds] = useState<ApiRound[]>([])
  const [courses, setCourses] = useState<ApiCourse[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [tab, setTab] = useState<"setup" | "score" | "leaderboard">("setup")

  const base = `/orgs/${organizationId}/tournaments/${tournamentId}`

  useEffect(() => {
    let cancelled = false
    async function load() {
      try {
        const [tournamentsData, participantsData, roundsData, coursesData] = await Promise.all([
          getJson<ApiTournamentInfo[]>(`/orgs/${organizationId}/tournaments`),
          getJson<ApiParticipant[]>(`${base}/participants`),
          getJson<ApiRound[]>(`${base}/rounds`),
          getJson<ApiCourse[]>(`/orgs/${organizationId}/courses`),
        ])
        if (cancelled) return
        const mine = tournamentsData?.find((t) => t.id === tournamentId) ?? null
        if (!mine) {
          setError("Fant ikke turneringen.")
          return
        }
        setTournament(mine)
        setParticipants(participantsData ?? [])
        setRounds(roundsData ?? [])
        setCourses(coursesData ?? [])
      } catch {
        if (!cancelled) setError("Klarte ikke å laste turneringen. Prøv å laste siden på nytt.")
      } finally {
        if (!cancelled) setLoading(false)
      }
    }
    load()
    return () => {
      cancelled = true
    }
  }, [organizationId, tournamentId, base])

  async function updateStatus(newStatus: TournamentStatus) {
    if (!tournament) return
    const previous = tournament
    setTournament({ ...tournament, status: newStatus })
    try {
      const res = await fetch(`/orgs/${organizationId}/tournaments/${tournamentId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ status: newStatus }),
      })
      if (!res.ok) throw new Error()
    } catch {
      setTournament(previous)
      setError("Klarte ikke å endre turnering-status. Prøv igjen.")
    }
  }

  async function updateScoringMethod(method: string) {
    if (!tournament) return
    const previous = tournament
    setTournament({ ...tournament, scoring_method: method })
    try {
      const res = await fetch(`/orgs/${organizationId}/tournaments/${tournamentId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ scoring_method: method }),
      })
      if (!res.ok) throw new Error()
    } catch {
      setTournament(previous)
      setError("Klarte ikke å endre scoringsmetode. Prøv igjen.")
    }
  }

  async function updateBbbSweepBonus(enabled: boolean) {
    if (!tournament) return
    const previous = tournament
    setTournament({ ...tournament, bbb_sweep_bonus_enabled: enabled })
    try {
      const res = await fetch(`/orgs/${organizationId}/tournaments/${tournamentId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ bbb_sweep_bonus_enabled: enabled }),
      })
      if (!res.ok) throw new Error()
    } catch {
      setTournament(previous)
      setError("Klarte ikke å endre bonuspoeng-innstillingen. Prøv igjen.")
    }
  }

  async function addParticipant(playerId: string) {
    setError(null)
    const res = await fetch(`${base}/participants`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ player_id: playerId }),
    })
    if (!res.ok) {
      setError(await errorMessage(res, "Klarte ikke å legge til deltakeren."))
      return
    }
    const created: ApiParticipant = await res.json()
    setParticipants((prev) => [...prev, created].sort((a, b) => a.player_name.localeCompare(b.player_name)))
  }

  async function addNewPlayerAndParticipant(name: string, handicap?: number, gender?: "m" | "f" | "x") {
    setError(null)
    const playerRes = await fetch(`/orgs/${organizationId}/players`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ display_name: name, handicap_index: handicap ?? null, gender: gender ?? null }),
    })
    if (!playerRes.ok) {
      setError("Klarte ikke å opprette spilleren.")
      return
    }
    const player: ApiPlayer = await playerRes.json()
    await addParticipant(player.id)
  }

  async function removeParticipant(participantId: string) {
    setError(null)
    const res = await fetch(`${base}/participants/${participantId}`, {
      method: "DELETE",
      credentials: "include",
    })
    if (!res.ok) {
      setError(await errorMessage(res, "Klarte ikke å fjerne deltakeren -- kanskje fortsatt lagt til i en runde?"))
      return
    }
    setParticipants((prev) => prev.filter((p) => p.id !== participantId))
  }

  async function addRound(name: string, courseId: string, holeConfig: ApiRound["hole_config"]) {
    setError(null)
    const res = await fetch(`${base}/rounds`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({
        sequence: rounds.length + 1,
        name: name.trim() || null,
        course_id: courseId,
        hole_config: holeConfig,
      }),
    })
    if (!res.ok) {
      setError(await errorMessage(res, "Klarte ikke å opprette runden."))
      return
    }
    const created: ApiRound = await res.json()
    setRounds((prev) => [...prev, created].sort((a, b) => a.sequence - b.sequence))
  }

  async function deleteRound(roundId: string) {
    setError(null)
    const res = await fetch(`${base}/rounds/${roundId}`, { method: "DELETE", credentials: "include" })
    if (!res.ok) {
      setError(await errorMessage(res, "Klarte ikke å slette runden -- har den allerede deltakere?"))
      return
    }
    setRounds((prev) => prev.filter((r) => r.id !== roundId))
  }

  async function addCourse(name: string): Promise<ApiCourse | null> {
    const res = await fetch(`/orgs/${organizationId}/courses`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ name }),
    })
    if (!res.ok) {
      setError("Klarte ikke å opprette banen.")
      return null
    }
    const created: ApiCourse = await res.json()
    setCourses((prev) => [...prev, created].sort((a, b) => a.name.localeCompare(b.name)))
    return created
  }

  if (loading) {
    return (
      <div className="flex min-h-[100dvh] flex-col items-center justify-center gap-4 bg-background">
        <div
          aria-hidden="true"
          className="size-10 animate-spin rounded-full border-4 border-primary/20 border-t-primary"
        />
      </div>
    )
  }

  if (error && !tournament) {
    return (
      <main className="flex min-h-[100dvh] flex-col items-center justify-center gap-3 bg-background px-5 text-center">
        <p className="text-sm font-medium text-destructive">{error}</p>
        <Link href="/dashboard" className="text-sm font-semibold text-primary underline underline-offset-2">
          Tilbake til dashbordet
        </Link>
      </main>
    )
  }

  if (!tournament) return null

  return (
    <div className="flex min-h-[100dvh] flex-col bg-background">
      <header className="sticky top-0 z-10 border-b border-border bg-background/80 backdrop-blur">
        <div className="mx-auto flex w-full max-w-4xl flex-col gap-3 px-5 py-4">
          <div className="flex items-center gap-3">
            <Link
              href="/dashboard"
              aria-label="Tilbake til dashbord"
              className="flex size-10 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
            >
              <ArrowLeft aria-hidden="true" className="size-5" />
            </Link>
            <div className="flex min-w-0 flex-1 flex-col">
              <span className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                <span className="inline-flex items-center gap-1 rounded-full bg-info/15 px-2 py-0.5 text-info">
                  <Users aria-hidden="true" className="size-3" />
                  Individuell
                </span>
                Turnering
              </span>
              <h1 className="truncate text-xl font-extrabold tracking-tight text-foreground">{tournamentName}</h1>
            </div>
          </div>
          <div className="flex flex-wrap items-center gap-2 pl-13">
            <TournamentStatusPicker status={tournament.status} onChange={updateStatus} />
            <JoinCodeChip code={tournament.join_code} />
          </div>
        </div>

        <nav className="mx-auto flex w-full max-w-4xl items-center gap-2 px-5 pb-3" aria-label="Turneringsseksjoner">
          {(
            [
              ["setup", "Oppsett"],
              ["score", "Scorekort"],
              ["leaderboard", "Leaderboard"],
            ] as const
          ).map(([key, label]) => (
            <button
              key={key}
              type="button"
              onClick={() => setTab(key)}
              aria-current={tab === key ? "page" : undefined}
              className={cn(
                "min-h-11 rounded-full px-4 py-2 text-sm font-bold transition-colors",
                tab === key
                  ? "bg-primary text-primary-foreground"
                  : "text-muted-foreground hover:bg-accent/60 hover:text-foreground",
              )}
            >
              {label}
            </button>
          ))}
        </nav>
      </header>

      <main className="mx-auto w-full max-w-4xl flex-1 px-5 py-6 sm:py-8">
        {error && (
          <div className="mb-5 rounded-2xl border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm font-medium text-destructive">
            {error}
          </div>
        )}

        {tab === "setup" && (
          <SetupTab
            base={base}
            organizationId={organizationId}
            tournament={tournament}
            participants={participants}
            rounds={rounds}
            courses={courses}
            onUpdateScoringMethod={updateScoringMethod}
            onUpdateBbbSweepBonus={updateBbbSweepBonus}
            onAddParticipant={addParticipant}
            onAddNewPlayer={addNewPlayerAndParticipant}
            onRemoveParticipant={removeParticipant}
            onAddRound={addRound}
            onDeleteRound={deleteRound}
            onAddCourse={addCourse}
            onError={setError}
          />
        )}
        {tab === "score" && (
          <ScoreTab base={base} rounds={rounds} scoringMethod={tournament.scoring_method} onError={setError} />
        )}
        {tab === "leaderboard" && (
          <LeaderboardTab base={base} tournament={tournament} />
        )}
      </main>
    </div>
  )
}

function JoinCodeChip({ code }: { code: string }) {
  const [copied, setCopied] = useState(false)
  async function handleCopy() {
    try {
      await navigator.clipboard.writeText(code)
      setCopied(true)
      setTimeout(() => setCopied(false), 1500)
    } catch {
      // stille -- ikke kritisk
    }
  }
  return (
    <button
      type="button"
      onClick={handleCopy}
      className="inline-flex min-h-11 items-center gap-1.5 rounded-full border border-border bg-card px-3 py-2 text-sm font-bold tabular-nums text-foreground transition-colors hover:bg-accent/50"
      aria-label={`Invitasjonskode ${code}, trykk for å kopiere`}
    >
      {copied ? (
        <Check aria-hidden="true" className="size-4 text-primary" />
      ) : (
        <KeyRound aria-hidden="true" className="size-4 text-muted-foreground" />
      )}
      {code}
      {!copied && <Copy aria-hidden="true" className="size-3.5 text-muted-foreground" />}
    </button>
  )
}

// =========================================================================
// Oppsett-fane: scoringsmetode, deltakere, runder
// =========================================================================

function SetupTab({
  base,
  organizationId,
  tournament,
  participants,
  rounds,
  courses,
  onUpdateScoringMethod,
  onUpdateBbbSweepBonus,
  onAddParticipant,
  onAddNewPlayer,
  onRemoveParticipant,
  onAddRound,
  onDeleteRound,
  onAddCourse,
  onError,
}: {
  base: string
  organizationId: string
  tournament: ApiTournamentInfo
  participants: ApiParticipant[]
  rounds: ApiRound[]
  courses: ApiCourse[]
  onUpdateScoringMethod: (method: string) => void
  onUpdateBbbSweepBonus: (enabled: boolean) => void
  onAddParticipant: (playerId: string) => Promise<void>
  onAddNewPlayer: (name: string, handicap?: number, gender?: "m" | "f" | "x") => Promise<void>
  onRemoveParticipant: (id: string) => Promise<void>
  onAddRound: (name: string, courseId: string, holeConfig: ApiRound["hole_config"]) => Promise<void>
  onDeleteRound: (id: string) => Promise<void>
  onAddCourse: (name: string) => Promise<ApiCourse | null>
  onError: (message: string) => void
}) {
  const [pool, setPool] = useState<ApiPlayer[]>([])

  useEffect(() => {
    let cancelled = false
    getJson<ApiPlayer[]>(`/orgs/${organizationId}/players`).then((data) => {
      if (!cancelled) setPool(data ?? [])
    })
    return () => {
      cancelled = true
    }
  }, [organizationId])

  return (
    <div className="flex flex-col gap-6">
      <section className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8">
        <h2 className="text-base font-bold text-foreground">Scoringsmetode</h2>
        <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
          Avgjør hvordan totalen på leaderboardet regnes ut, og om deltakere trenger
          registrert handicap og kjønn.
        </p>
        <DropdownMenu>
          <DropdownMenuTrigger className="flex min-h-11 w-fit items-center gap-2 rounded-xl border border-border bg-background px-4 text-sm font-bold text-foreground transition-colors hover:bg-accent/50">
            {tournament.scoring_method ? SCORING_METHOD_LABELS[tournament.scoring_method] : "Ikke satt"}
            <ChevronDown aria-hidden="true" className="size-4 text-muted-foreground" />
          </DropdownMenuTrigger>
          <DropdownMenuContent align="start" className="w-56 rounded-2xl p-1.5">
            {Object.entries(SCORING_METHOD_LABELS).map(([value, label]) => (
              <DropdownMenuItem
                key={value}
                onClick={() => onUpdateScoringMethod(value)}
                className="cursor-pointer rounded-xl px-3 py-2.5 text-sm font-semibold"
              >
                {label}
              </DropdownMenuItem>
            ))}
          </DropdownMenuContent>
        </DropdownMenu>
        {tournament.scoring_method === "bingo_bango_bongo" && (
          <label className="flex min-h-11 items-start gap-3 rounded-xl border border-border bg-background px-4 py-3">
            <input
              type="checkbox"
              checked={tournament.bbb_sweep_bonus_enabled}
              onChange={(e) => onUpdateBbbSweepBonus(e.target.checked)}
              className="mt-1 size-5 shrink-0 accent-primary"
            />
            <span className="flex flex-col gap-0.5">
              <span className="text-sm font-bold text-foreground">Bonuspoeng ved "sveip"</span>
              <span className="text-xs text-muted-foreground">
                Ekstra poeng til en spiller som vinner alle tre kategoriene på samme hull.
              </span>
            </span>
          </label>
        )}
      </section>

      <ParticipantsCard
        participants={participants}
        pool={pool}
        onAddParticipant={onAddParticipant}
        onAddNewPlayer={onAddNewPlayer}
        onRemoveParticipant={onRemoveParticipant}
      />

      <RoundsCard
        base={base}
        organizationId={organizationId}
        rounds={rounds}
        courses={courses}
        participants={participants}
        scoringMethod={tournament.scoring_method}
        onAddRound={onAddRound}
        onDeleteRound={onDeleteRound}
        onAddCourse={onAddCourse}
        onError={onError}
      />
    </div>
  )
}

function ParticipantsCard({
  participants,
  pool,
  onAddParticipant,
  onAddNewPlayer,
  onRemoveParticipant,
}: {
  participants: ApiParticipant[]
  pool: ApiPlayer[]
  onAddParticipant: (playerId: string) => Promise<void>
  onAddNewPlayer: (name: string, handicap?: number, gender?: "m" | "f" | "x") => Promise<void>
  onRemoveParticipant: (id: string) => Promise<void>
}) {
  return (
    <section className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8">
      <div className="flex items-center justify-between gap-2">
        <h2 className="text-base font-bold text-foreground">Deltakere</h2>
        <span className="rounded-full bg-muted px-2.5 py-1 text-xs font-bold tabular-nums text-muted-foreground">
          {participants.length}
        </span>
      </div>

      {participants.length === 0 && (
        <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
          Ingen deltakere lagt til ennå -- legg til hele feltet her, uavhengig av hvilke runder de spiller.
        </p>
      )}

      <ul className="flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border">
        {participants.map((p) => (
          <li key={p.id} className="flex items-center justify-between gap-3 px-4 py-3">
            <div className="flex min-w-0 flex-col">
              <span className="truncate text-sm font-bold text-foreground">{p.player_name}</span>
              <span className="text-xs font-semibold text-muted-foreground">
                {p.handicap_index_snapshot !== null ? `Hcp ${p.handicap_index_snapshot}` : "Ingen HCP registrert"}
              </span>
            </div>
            <Button
              type="button"
              variant="ghost"
              size="icon"
              onClick={() => onRemoveParticipant(p.id)}
              aria-label={`Fjern ${p.player_name}`}
              className="size-9 shrink-0 rounded-lg text-muted-foreground hover:text-destructive"
            >
              <Trash2 aria-hidden="true" className="size-4" />
            </Button>
          </li>
        ))}
      </ul>

      <AddParticipantControl
        pool={pool}
        alreadyIn={new Set(participants.map((p) => p.player_id))}
        onAddExisting={onAddParticipant}
        onAddNew={onAddNewPlayer}
      />
    </section>
  )
}

function AddParticipantControl({
  pool,
  alreadyIn,
  onAddExisting,
  onAddNew,
}: {
  pool: ApiPlayer[]
  alreadyIn: Set<string>
  onAddExisting: (playerId: string) => Promise<void>
  onAddNew: (name: string, handicap?: number, gender?: "m" | "f" | "x") => Promise<void>
}) {
  const [open, setOpen] = useState(false)
  const [query, setQuery] = useState("")
  const [creating, setCreating] = useState(false)
  const [newHandicap, setNewHandicap] = useState("")
  const [newGender, setNewGender] = useState<"m" | "f" | "x" | "">("")

  const trimmed = query.trim()
  const matches = useMemo(() => {
    if (!trimmed) return []
    const q = trimmed.toLowerCase()
    return pool.filter((p) => !alreadyIn.has(p.id) && p.display_name.toLowerCase().includes(q))
  }, [pool, trimmed, alreadyIn])
  const exactMatch = useMemo(
    () => pool.some((p) => p.display_name.toLowerCase() === trimmed.toLowerCase()),
    [pool, trimmed],
  )
  const showCreate = trimmed.length >= 2 && !exactMatch

  function reset() {
    setQuery("")
    setCreating(false)
    setNewHandicap("")
    setNewGender("")
  }

  async function handleAddExisting(playerId: string) {
    await onAddExisting(playerId)
    reset()
    setOpen(false)
  }

  async function handleCreate() {
    if (trimmed.length < 2) return
    const hcpValue = newHandicap.trim() === "" ? undefined : Number(newHandicap.replace(",", "."))
    const hcp = hcpValue !== undefined && !Number.isNaN(hcpValue) ? hcpValue : undefined
    await onAddNew(trimmed, hcp, newGender || undefined)
    reset()
    setOpen(false)
  }

  if (!open) {
    return (
      <Button
        type="button"
        variant="secondary"
        onClick={() => setOpen(true)}
        className="h-11 w-full rounded-2xl text-sm font-bold"
      >
        <UserPlus aria-hidden="true" className="size-4" />
        Legg til deltaker
      </Button>
    )
  }

  return (
    <div className="flex flex-col gap-2">
      <div className="flex items-center gap-2">
        <Input
          autoFocus
          placeholder="Søk eller skriv navn…"
          value={query}
          onChange={(e) => {
            setQuery(e.target.value)
            setCreating(false)
          }}
          onKeyDown={(e) => {
            if (e.key === "Escape") {
              setOpen(false)
              reset()
            }
          }}
          className="h-11 flex-1 rounded-2xl text-base"
        />
        <Button
          type="button"
          variant="ghost"
          size="icon"
          onClick={() => {
            setOpen(false)
            reset()
          }}
          aria-label="Lukk"
          className="size-11 shrink-0 rounded-2xl text-muted-foreground"
        >
          <X aria-hidden="true" className="size-5" />
        </Button>
      </div>

      {matches.length > 0 && (
        <ul className="flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border">
          {matches.slice(0, 6).map((p) => (
            <li key={p.id}>
              <button
                type="button"
                onClick={() => handleAddExisting(p.id)}
                className="flex min-h-11 w-full items-center justify-between gap-2 px-3 py-2.5 text-left text-sm font-semibold text-foreground transition-colors hover:bg-accent/50"
              >
                {p.display_name}
                <Plus aria-hidden="true" className="size-4 shrink-0 text-primary" />
              </button>
            </li>
          ))}
        </ul>
      )}

      {showCreate && !creating && (
        <button
          type="button"
          onClick={() => setCreating(true)}
          className="flex min-h-11 items-center gap-2 rounded-xl border border-dashed border-border px-3 py-2.5 text-left text-sm font-semibold text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
        >
          <UserPlus aria-hidden="true" className="size-4" />
          Opprett ny spiller: «{trimmed}»
        </button>
      )}

      {showCreate && creating && (
        <div className="flex flex-col gap-2 rounded-xl border border-border bg-background p-3">
          <div className="flex gap-2">
            <div className="flex flex-1 flex-col gap-1">
              <Label htmlFor="new-participant-hcp" className="text-xs font-semibold">
                HCP (valgfritt)
              </Label>
              <Input
                id="new-participant-hcp"
                inputMode="decimal"
                placeholder="F.eks. 18,4"
                value={newHandicap}
                onChange={(e) => setNewHandicap(e.target.value)}
                className="h-10 rounded-xl text-sm"
              />
            </div>
            <div className="flex flex-1 flex-col gap-1">
              <Label htmlFor="new-participant-gender" className="text-xs font-semibold">
                Kjønn (for HCP-beregning)
              </Label>
              <select
                id="new-participant-gender"
                value={newGender}
                onChange={(e) => setNewGender(e.target.value as "m" | "f" | "x" | "")}
                className="h-10 rounded-xl border border-border bg-card px-2 text-sm font-medium text-foreground outline-none"
              >
                <option value="">Ikke satt</option>
                <option value="f">Kvinne</option>
                <option value="m">Mann</option>
                <option value="x">Annet</option>
              </select>
            </div>
          </div>
          <Button type="button" onClick={handleCreate} className="h-10 rounded-xl text-sm font-bold">
            Opprett og legg til «{trimmed}»
          </Button>
        </div>
      )}
    </div>
  )
}

function RoundsCard({
  base,
  organizationId,
  rounds,
  courses,
  participants,
  scoringMethod,
  onAddRound,
  onDeleteRound,
  onAddCourse,
  onError,
}: {
  base: string
  organizationId: string
  rounds: ApiRound[]
  courses: ApiCourse[]
  participants: ApiParticipant[]
  scoringMethod: string | null
  onAddRound: (name: string, courseId: string, holeConfig: ApiRound["hole_config"]) => Promise<void>
  onDeleteRound: (id: string) => Promise<void>
  onAddCourse: (name: string) => Promise<ApiCourse | null>
  onError: (message: string) => void
}) {
  const [creating, setCreating] = useState(false)

  return (
    <section className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8">
      <div className="flex items-center justify-between gap-2">
        <h2 className="text-base font-bold text-foreground">Runder</h2>
        <span className="rounded-full bg-muted px-2.5 py-1 text-xs font-bold tabular-nums text-muted-foreground">
          {rounds.length}
        </span>
      </div>

      {rounds.length === 0 && (
        <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
          Ingen runder ennå -- en enkeltdags turnering trenger kun én, en flerdagers
          turnering kan ha flere.
        </p>
      )}

      <div className="flex flex-col gap-3">
        {rounds.map((r) => (
          <RoundCard
            key={r.id}
            base={base}
            organizationId={organizationId}
            round={r}
            participants={participants}
            scoringMethod={scoringMethod}
            onDelete={() => onDeleteRound(r.id)}
            onError={onError}
          />
        ))}
      </div>

      {!creating ? (
        <Button
          type="button"
          variant="secondary"
          onClick={() => setCreating(true)}
          className="h-11 w-full rounded-2xl text-sm font-bold"
        >
          <Plus aria-hidden="true" className="size-4" />
          Ny runde
        </Button>
      ) : (
        <NewRoundForm
          courses={courses}
          onCreate={async (name, courseId, holeConfig) => {
            await onAddRound(name, courseId, holeConfig)
            setCreating(false)
          }}
          onCreateCourse={onAddCourse}
          onCancel={() => setCreating(false)}
        />
      )}
    </section>
  )
}

function NewRoundForm({
  courses,
  onCreate,
  onCreateCourse,
  onCancel,
}: {
  courses: ApiCourse[]
  onCreate: (name: string, courseId: string, holeConfig: ApiRound["hole_config"]) => Promise<void>
  onCreateCourse: (name: string) => Promise<ApiCourse | null>
  onCancel: () => void
}) {
  const [name, setName] = useState("")
  const [courseId, setCourseId] = useState(courses[0]?.id ?? "")
  const [holeConfig, setHoleConfig] = useState<ApiRound["hole_config"]>("full_18")
  const [newCourseName, setNewCourseName] = useState("")
  const [addingCourse, setAddingCourse] = useState(false)
  const [submitting, setSubmitting] = useState(false)

  async function handleCreateCourse() {
    const trimmed = newCourseName.trim()
    if (trimmed.length < 2) return
    const created = await onCreateCourse(trimmed)
    if (created) {
      setCourseId(created.id)
      setAddingCourse(false)
      setNewCourseName("")
    }
  }

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    if (!courseId || submitting) return
    setSubmitting(true)
    await onCreate(name, courseId, holeConfig)
    setSubmitting(false)
  }

  return (
    <form onSubmit={handleSubmit} className="flex flex-col gap-3 rounded-xl border border-border bg-background p-3">
      <div className="flex flex-col gap-1">
        <Label htmlFor="round-name" className="text-xs font-semibold">
          Navn (valgfritt)
        </Label>
        <Input
          id="round-name"
          autoFocus
          placeholder="F.eks. Runde 1 -- fredag"
          value={name}
          onChange={(e) => setName(e.target.value)}
          className="h-10 rounded-xl text-sm"
        />
      </div>

      <div className="flex flex-col gap-1">
        <Label htmlFor="round-course" className="text-xs font-semibold">
          Bane
        </Label>
        {courses.length > 0 && !addingCourse ? (
          <div className="flex gap-2">
            <select
              id="round-course"
              value={courseId}
              onChange={(e) => setCourseId(e.target.value)}
              className="h-10 flex-1 rounded-xl border border-border bg-card px-2 text-sm font-medium text-foreground outline-none"
            >
              {courses.map((c) => (
                <option key={c.id} value={c.id}>
                  {c.name}
                </option>
              ))}
            </select>
            <Button
              type="button"
              variant="ghost"
              onClick={() => setAddingCourse(true)}
              className="h-10 shrink-0 rounded-xl px-3 text-xs font-bold text-muted-foreground"
            >
              + Ny bane
            </Button>
          </div>
        ) : (
          <div className="flex gap-2">
            <Input
              placeholder="Banens navn"
              value={newCourseName}
              onChange={(e) => setNewCourseName(e.target.value)}
              className="h-10 flex-1 rounded-xl text-sm"
            />
            <Button
              type="button"
              onClick={handleCreateCourse}
              className="h-10 shrink-0 rounded-xl px-3 text-xs font-bold"
            >
              Opprett
            </Button>
            {courses.length > 0 && (
              <Button
                type="button"
                variant="ghost"
                onClick={() => setAddingCourse(false)}
                className="h-10 shrink-0 rounded-xl px-2 text-xs font-bold text-muted-foreground"
              >
                Avbryt
              </Button>
            )}
          </div>
        )}
        <p className="text-xs text-muted-foreground">
          Banen må ha registrert hull (par/hcp-indeks) og utslag før scoring kan
          begynne -- sett opp dette under organisasjonens baner om det mangler.
        </p>
      </div>

      <div className="flex flex-col gap-1">
        <Label htmlFor="round-hole-config" className="text-xs font-semibold">
          Hullomfang
        </Label>
        <select
          id="round-hole-config"
          value={holeConfig}
          onChange={(e) => setHoleConfig(e.target.value as ApiRound["hole_config"])}
          className="h-10 rounded-xl border border-border bg-card px-2 text-sm font-medium text-foreground outline-none"
        >
          {Object.entries(HOLE_CONFIG_LABELS).map(([value, label]) => (
            <option key={value} value={value}>
              {label}
            </option>
          ))}
        </select>
      </div>

      <div className="flex gap-2">
        <Button type="submit" disabled={!courseId || submitting} className="h-10 flex-1 rounded-xl text-sm font-bold">
          {submitting ? "Oppretter…" : "Opprett runde"}
        </Button>
        <Button type="button" variant="ghost" onClick={onCancel} className="h-10 rounded-xl text-sm font-bold text-muted-foreground">
          Avbryt
        </Button>
      </div>
    </form>
  )
}

function RoundCard({
  base,
  organizationId,
  round,
  participants,
  scoringMethod,
  onDelete,
  onError,
}: {
  base: string
  organizationId: string
  round: ApiRound
  participants: ApiParticipant[]
  scoringMethod: string | null
  onDelete: () => Promise<void>
  onError: (message: string) => void
}) {
  const [expanded, setExpanded] = useState(false)
  const [roundParticipants, setRoundParticipants] = useState<ApiRoundParticipant[] | null>(null)
  const [tees, setTees] = useState<ApiTee[]>([])
  const [confirmingDelete, setConfirmingDelete] = useState(false)

  async function loadRoundParticipants() {
    const [rpData, teeData] = await Promise.all([
      getJson<ApiRoundParticipant[]>(`${base}/rounds/${round.id}/participants`),
      getJson<ApiTee[]>(`/orgs/${organizationId}/courses/${round.course_id}/tees`),
    ])
    setRoundParticipants(rpData ?? [])
    setTees(teeData ?? [])
  }

  useEffect(() => {
    if (expanded && roundParticipants === null) {
      loadRoundParticipants()
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [expanded])

  async function addRoundParticipant(tournamentParticipantId: string, teeId: string) {
    onError("")
    const res = await fetch(`${base}/rounds/${round.id}/participants`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ tournament_participant_id: tournamentParticipantId, tee_id: teeId }),
    })
    if (!res.ok) {
      onError(await errorMessage(res, "Klarte ikke å legge til deltakeren i runden."))
      return
    }
    const created: ApiRoundParticipant = await res.json()
    setRoundParticipants((prev) => [...(prev ?? []), created])
  }

  async function removeRoundParticipant(id: string) {
    const res = await fetch(`${base}/rounds/${round.id}/participants/${id}`, {
      method: "DELETE",
      credentials: "include",
    })
    if (!res.ok) {
      onError("Klarte ikke å fjerne deltakeren fra runden.")
      return
    }
    setRoundParticipants((prev) => (prev ?? []).filter((rp) => rp.id !== id))
  }

  const assignedIds = new Set((roundParticipants ?? []).map((rp) => rp.tournament_participant_id))
  const unassigned = participants.filter((p) => !assignedIds.has(p.id))

  return (
    <div className="overflow-hidden rounded-xl border border-border">
      <div className="flex items-center gap-3 bg-background px-4 py-3">
        <button
          type="button"
          onClick={() => setExpanded((v) => !v)}
          className="flex min-w-0 flex-1 items-center gap-3 text-left"
          aria-expanded={expanded}
        >
          <span className="flex size-9 shrink-0 items-center justify-center rounded-full bg-info/15 text-sm font-extrabold tabular-nums text-info">
            {round.sequence}
          </span>
          <span className="flex min-w-0 flex-col">
            <span className="truncate text-sm font-bold text-foreground">
              {round.name || `Runde ${round.sequence}`}
            </span>
            <span className="truncate text-xs font-semibold text-muted-foreground">
              {round.course_name} · {HOLE_CONFIG_LABELS[round.hole_config]}
            </span>
          </span>
          <ChevronDown
            aria-hidden="true"
            className={cn("size-4 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-180")}
          />
        </button>
        {!confirmingDelete ? (
          <Button
            type="button"
            variant="ghost"
            size="icon"
            onClick={() => setConfirmingDelete(true)}
            aria-label={`Slett ${round.name || `runde ${round.sequence}`}`}
            className="size-9 shrink-0 rounded-lg text-muted-foreground hover:text-destructive"
          >
            <Trash2 aria-hidden="true" className="size-4" />
          </Button>
        ) : (
          <div className="flex shrink-0 gap-1">
            <Button
              type="button"
              variant="destructive"
              size="sm"
              onClick={onDelete}
              className="h-9 rounded-lg text-xs font-bold"
            >
              Slett
            </Button>
            <Button
              type="button"
              variant="ghost"
              size="sm"
              onClick={() => setConfirmingDelete(false)}
              className="h-9 rounded-lg text-xs font-bold text-muted-foreground"
            >
              Avbryt
            </Button>
          </div>
        )}
      </div>

      {expanded && (
        <div className="flex flex-col gap-2 border-t border-border p-3">
          {roundParticipants === null ? (
            <p className="text-xs text-muted-foreground">Laster…</p>
          ) : (
            <>
              {roundParticipants.length === 0 && (
                <p className="text-xs text-muted-foreground">Ingen deltakere i denne runden ennå.</p>
              )}
              <ul className="flex flex-col divide-y divide-border overflow-hidden rounded-lg border border-border">
                {roundParticipants.map((rp) => (
                  <li key={rp.id} className="flex items-center justify-between gap-3 px-3 py-2.5">
                    <div className="flex min-w-0 flex-col">
                      <span className="truncate text-sm font-bold text-foreground">{rp.player_name}</span>
                      <span className="truncate text-xs font-semibold text-muted-foreground">
                        {rp.tee_name}
                        {rp.playing_handicap !== null ? ` · HCP ${rp.playing_handicap}` : ""}
                      </span>
                    </div>
                    <Button
                      type="button"
                      variant="ghost"
                      size="icon"
                      onClick={() => removeRoundParticipant(rp.id)}
                      aria-label={`Fjern ${rp.player_name} fra runden`}
                      className="size-8 shrink-0 rounded-lg text-muted-foreground hover:text-destructive"
                    >
                      <X aria-hidden="true" className="size-4" />
                    </Button>
                  </li>
                ))}
              </ul>
              {unassigned.length > 0 && tees.length > 0 && (
                <AssignRoundParticipantControl
                  candidates={unassigned}
                  tees={tees}
                  onAssign={addRoundParticipant}
                />
              )}
              {unassigned.length > 0 && tees.length === 0 && (
                <p className="text-xs font-medium text-brand-orange">
                  Banen «{round.course_name}» har ingen registrerte utslag ennå -- kan ikke legge til
                  deltakere før det er på plass.
                </p>
              )}
            </>
          )}
        </div>
      )}
    </div>
  )
}

function AssignRoundParticipantControl({
  candidates,
  tees,
  onAssign,
}: {
  candidates: ApiParticipant[]
  tees: ApiTee[]
  onAssign: (tournamentParticipantId: string, teeId: string) => Promise<void>
}) {
  const [participantId, setParticipantId] = useState(candidates[0]?.id ?? "")
  const [teeId, setTeeId] = useState(tees[0]?.id ?? "")
  const [submitting, setSubmitting] = useState(false)

  useEffect(() => {
    if (!candidates.some((c) => c.id === participantId)) setParticipantId(candidates[0]?.id ?? "")
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [candidates])

  async function handleAssign() {
    if (!participantId || !teeId || submitting) return
    setSubmitting(true)
    await onAssign(participantId, teeId)
    setSubmitting(false)
  }

  return (
    <div className="flex flex-col gap-2 rounded-lg border border-dashed border-border p-2.5 sm:flex-row sm:items-end">
      <div className="flex flex-1 flex-col gap-1">
        <Label htmlFor="assign-participant" className="text-xs font-semibold text-muted-foreground">
          Deltaker
        </Label>
        <select
          id="assign-participant"
          value={participantId}
          onChange={(e) => setParticipantId(e.target.value)}
          className="h-10 rounded-lg border border-border bg-card px-2 text-sm font-medium text-foreground outline-none"
        >
          {candidates.map((c) => (
            <option key={c.id} value={c.id}>
              {c.player_name}
            </option>
          ))}
        </select>
      </div>
      <div className="flex flex-1 flex-col gap-1">
        <Label htmlFor="assign-tee" className="text-xs font-semibold text-muted-foreground">
          Utslag
        </Label>
        <select
          id="assign-tee"
          value={teeId}
          onChange={(e) => setTeeId(e.target.value)}
          className="h-10 rounded-lg border border-border bg-card px-2 text-sm font-medium text-foreground outline-none"
        >
          {tees.map((t) => (
            <option key={t.id} value={t.id}>
              {t.name}
            </option>
          ))}
        </select>
      </div>
      <Button
        type="button"
        onClick={handleAssign}
        disabled={submitting}
        className="h-10 shrink-0 rounded-lg text-xs font-bold"
      >
        Legg til
      </Button>
    </div>
  )
}

// =========================================================================
// Scorekort-fane
// =========================================================================

function ScoreTab({
  base,
  rounds,
  scoringMethod,
  onError,
}: {
  base: string
  rounds: ApiRound[]
  scoringMethod: string | null
  onError: (message: string) => void
}) {
  const [roundId, setRoundId] = useState(rounds[0]?.id ?? "")
  const [roundParticipants, setRoundParticipants] = useState<ApiRoundParticipant[]>([])
  const [participantId, setParticipantId] = useState("")
  const [holes, setHoles] = useState<ApiHole[] | null>(null)
  const round = rounds.find((r) => r.id === roundId) ?? null
  // Bumpes ved hver hull-registrering, slik at BBB-/Flag-panelene (egen
  // fetch) vet de skal hente på nytt.
  const [refreshTick, setRefreshTick] = useState(0)

  useEffect(() => {
    if (!roundId) return
    let cancelled = false
    getJson<ApiRoundParticipant[]>(`${base}/rounds/${roundId}/participants`).then((data) => {
      if (cancelled) return
      const list = data ?? []
      setRoundParticipants(list)
      setParticipantId(list[0]?.id ?? "")
    })
    return () => {
      cancelled = true
    }
  }, [base, roundId])

  useEffect(() => {
    if (!roundId || !participantId) {
      setHoles(null)
      return
    }
    let cancelled = false
    getJson<ApiHole[]>(`${base}/rounds/${roundId}/participants/${participantId}/holes`).then((data) => {
      if (!cancelled) setHoles(data ?? [])
    })
    return () => {
      cancelled = true
    }
  }, [base, roundId, participantId])

  async function updateHole(holeNumber: number, grossStrokes: number) {
    const res = await fetch(`${base}/rounds/${roundId}/participants/${participantId}/holes/${holeNumber}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ gross_strokes: grossStrokes }),
    })
    if (!res.ok) {
      onError("Klarte ikke å lagre slaget. Prøv igjen.")
      return
    }
    const updated: ApiHole = await res.json()
    setHoles((prev) => (prev ? prev.map((h) => (h.hole_number === holeNumber ? updated : h)) : prev))
    setRefreshTick((n) => n + 1)
  }

  if (rounds.length === 0) {
    return (
      <div className="rounded-2xl border border-dashed border-border p-6 text-center">
        <p className="text-sm font-medium text-muted-foreground text-pretty">
          Opprett en runde under «Oppsett» først.
        </p>
      </div>
    )
  }

  return (
    <div className="flex flex-col gap-5">
      <div className="flex flex-wrap gap-2">
        {rounds.map((r) => (
          <button
            key={r.id}
            type="button"
            onClick={() => setRoundId(r.id)}
            aria-pressed={r.id === roundId}
            className={cn(
              "flex min-h-11 items-center gap-2 rounded-full border px-4 text-sm font-bold transition-colors",
              r.id === roundId
                ? "border-info bg-info/15 text-info"
                : "border-border bg-card text-foreground hover:bg-accent/50",
            )}
          >
            <Flag aria-hidden="true" className="size-4" />
            {r.name || `Runde ${r.sequence}`}
          </button>
        ))}
      </div>

      {roundParticipants.length === 0 ? (
        <p className="text-sm font-medium text-muted-foreground">
          Ingen deltakere i denne runden ennå -- legg til under «Oppsett».
        </p>
      ) : (
        <>
          <div className="flex flex-wrap gap-2">
            {roundParticipants.map((rp) => (
              <button
                key={rp.id}
                type="button"
                onClick={() => setParticipantId(rp.id)}
                aria-pressed={rp.id === participantId}
                className={cn(
                  "min-h-11 rounded-full border px-4 text-sm font-bold transition-colors",
                  rp.id === participantId
                    ? "border-primary bg-primary text-primary-foreground"
                    : "border-border bg-card text-foreground hover:bg-accent/50",
                )}
              >
                {rp.player_name}
              </button>
            ))}
          </div>

          {round && holes && (
            <HoleGrid holes={holes} holeConfig={round.hole_config} onUpdate={updateHole} />
          )}

          {/* Bingo Bango Bongo (2026-07-30) -- MANUELL per-hull-observasjon,
              uavhengig av gross_strokes-registreringen over. */}
          {round && scoringMethod === "bingo_bango_bongo" && (
            <BBBRoundPanel base={base} round={round} participants={roundParticipants} refreshTick={refreshTick} />
          )}

          {/* Flaggturnering (2026-07-30) -- per-runde-resultat (ikke
              akkumulerbart på tvers av runder, se LeaderboardTab). */}
          {round && scoringMethod === "flag" && (
            <FlagRoundPanel base={base} round={round} refreshTick={refreshTick} />
          )}
        </>
      )}
    </div>
  )
}

// --- Bingo Bango Bongo -- per-hull-observasjon (2026-07-30) -----------------

function BBBCategoryRow({
  label,
  participants,
  selectedId,
  onSelect,
}: {
  label: string
  participants: ApiRoundParticipant[]
  selectedId: string | null
  onSelect: (id: string | null) => void
}) {
  return (
    <div className="flex flex-col gap-2">
      <span className="text-xs font-semibold text-foreground">{label}</span>
      <div className="flex flex-wrap gap-1.5">
        {participants.map((p) => {
          const active = selectedId === p.tournament_participant_id
          return (
            <button
              key={p.tournament_participant_id}
              type="button"
              onClick={() => onSelect(active ? null : p.tournament_participant_id)}
              aria-pressed={active}
              className={cn(
                "min-h-9 rounded-lg border px-2.5 text-xs font-semibold transition-colors",
                active
                  ? "border-primary bg-primary text-primary-foreground"
                  : "border-border bg-background text-foreground hover:bg-accent/50",
              )}
            >
              {p.player_name}
            </button>
          )
        })}
      </div>
    </div>
  )
}

function BBBRoundPanel({
  base,
  round,
  participants,
  refreshTick,
}: {
  base: string
  round: ApiRound
  participants: ApiRoundParticipant[]
  refreshTick: number
}) {
  const numbers = holeNumbersFor(round.hole_config)
  const [selectedHole, setSelectedHole] = useState(numbers[0] ?? 1)
  const [bbbHoles, setBbbHoles] = useState<Record<number, ApiBBBHole>>({})

  useEffect(() => {
    let cancelled = false
    getJson<ApiBBBHole[]>(`${base}/rounds/${round.id}/bbb`).then((data) => {
      if (!cancelled) setBbbHoles(Object.fromEntries((data ?? []).map((h) => [h.hole_number, h])))
    })
    return () => {
      cancelled = true
    }
  }, [base, round.id, refreshTick])

  async function set(field: "bingo_participant_id" | "bango_participant_id" | "bongo_participant_id", id: string | null) {
    const res = await fetch(`${base}/rounds/${round.id}/bbb/${selectedHole}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ [field]: id }),
    })
    if (!res.ok) return
    const updated: ApiBBBHole = await res.json()
    setBbbHoles((prev) => ({ ...prev, [updated.hole_number]: updated }))
  }

  const selection = bbbHoles[selectedHole] ?? null

  return (
    <div className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8">
      <div className="flex items-center justify-between gap-2">
        <span className="text-sm font-bold text-foreground">Bingo Bango Bongo</span>
        <select
          value={selectedHole}
          onChange={(e) => setSelectedHole(Number(e.target.value))}
          className="h-9 rounded-lg border border-border bg-background px-2 text-sm font-semibold text-foreground outline-none"
        >
          {numbers.map((n) => (
            <option key={n} value={n}>
              Hull {n}
            </option>
          ))}
        </select>
      </div>
      <BBBCategoryRow
        label="Bingo (først på green)"
        participants={participants}
        selectedId={selection?.bingo_participant_id ?? null}
        onSelect={(id) => void set("bingo_participant_id", id)}
      />
      <BBBCategoryRow
        label="Bango (nærmest hull, når alle er på green)"
        participants={participants}
        selectedId={selection?.bango_participant_id ?? null}
        onSelect={(id) => void set("bango_participant_id", id)}
      />
      <BBBCategoryRow
        label="Bongo (først i hull)"
        participants={participants}
        selectedId={selection?.bongo_participant_id ?? null}
        onSelect={(id) => void set("bongo_participant_id", id)}
      />
    </div>
  )
}

// --- Flaggturnering -- per-runde-resultat (2026-07-30) ----------------------

function FlagRoundPanel({
  base,
  round,
  refreshTick,
}: {
  base: string
  round: ApiRound
  refreshTick: number
}) {
  const [results, setResults] = useState<ApiFlagResult[] | null>(null)

  useEffect(() => {
    let cancelled = false
    getJson<ApiFlagResult[]>(`${base}/rounds/${round.id}/flag-result`).then((data) => {
      if (!cancelled) setResults(data)
    })
    return () => {
      cancelled = true
    }
  }, [base, round.id, refreshTick])

  if (!results) return null
  if (results.length === 0) {
    return (
      <p className="text-sm text-muted-foreground">
        Venter på at handicap beregnes for minst én spiller.
      </p>
    )
  }

  return (
    <div className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8">
      <span className="text-sm font-bold text-foreground">Flaggturnering</span>
      <ul className="flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border">
        {results.map((r) => (
          <li key={r.tournament_participant_id} className="flex items-center justify-between gap-3 px-4 py-2.5">
            <span className="text-sm font-semibold text-foreground">{r.player_name}</span>
            <span className="flex flex-col items-end">
              <span className="text-sm font-extrabold tabular-nums text-foreground">{r.holes_completed} hull</span>
              <span className="text-xs tabular-nums text-muted-foreground">
                {r.ran_out ? "gikk tom for slag" : `${r.strokes_remaining} slag igjen`}
              </span>
            </span>
          </li>
        ))}
      </ul>
    </div>
  )
}

type ScoreClass = "eagle" | "birdie" | "par" | "bogey" | "double"

function classify(diff: number): ScoreClass {
  if (diff <= -2) return "eagle"
  if (diff === -1) return "birdie"
  if (diff === 0) return "par"
  if (diff === 1) return "bogey"
  return "double"
}

// Samme "form + farge, aldri farge alene"-språk som round-scorecard.tsx sin
// ScoreMark: sirkel = under par, firkant = over par, fylt = 2+ slag fra par.
// "Par" er nøytral (ingen retning å vise), men beholder en synlig kant siden
// disse cellene -- ulikt originalens rene visning -- er trykkbare knapper.
function scoreMarkClasses(kind: ScoreClass): string {
  switch (kind) {
    case "eagle":
      return "rounded-full border-2 border-primary bg-primary text-primary-foreground"
    case "birdie":
      return "rounded-full border-2 border-primary bg-primary/10 text-primary"
    case "bogey":
      return "rounded-[4px] border-2 border-brand-orange bg-brand-orange/10 text-brand-orange"
    case "double":
      return "rounded-[4px] border-2 border-brand-orange bg-brand-orange text-brand-orange-foreground"
    default:
      return "rounded-[4px] border-2 border-border bg-card text-foreground"
  }
}

function HoleGrid({
  holes,
  holeConfig,
  onUpdate,
}: {
  holes: ApiHole[]
  holeConfig: ApiRound["hole_config"]
  onUpdate: (holeNumber: number, grossStrokes: number) => Promise<void>
}) {
  const [editingHole, setEditingHole] = useState<number | null>(null)
  const [draft, setDraft] = useState("")
  const numbers = holeNumbersFor(holeConfig)
  const byNumber = new Map(holes.map((h) => [h.hole_number, h]))

  const played = holes.filter((h) => h.gross_strokes !== null)
  const totalGross = played.length > 0 ? played.reduce((sum, h) => sum + (h.gross_strokes as number), 0) : null
  const totalPar = played.length > 0 ? played.reduce((sum, h) => sum + h.par, 0) : null

  function openEditor(holeNumber: number, current: number | null) {
    setEditingHole(holeNumber)
    setDraft(current !== null ? String(current) : "")
  }

  async function commit(holeNumber: number) {
    const value = Number(draft)
    if (Number.isFinite(value) && value >= 1 && value <= 20) {
      await onUpdate(holeNumber, value)
    }
    setEditingHole(null)
    setDraft("")
  }

  return (
    <div className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8">
      <div className="overflow-x-auto">
        <div className="inline-grid min-w-full gap-y-1" style={{ gridTemplateColumns: `repeat(${numbers.length}, minmax(2.5rem, 1fr))` }}>
          {numbers.map((n) => (
            <span key={`num-${n}`} className="text-center text-[11px] font-bold text-muted-foreground">
              {n}
            </span>
          ))}
          {numbers.map((n) => (
            <span key={`par-${n}`} className="text-center text-[10px] font-semibold tabular-nums text-muted-foreground">
              Par {byNumber.get(n)?.par ?? "–"}
            </span>
          ))}
          {numbers.map((n) => {
            const hole = byNumber.get(n)
            const gross = hole?.gross_strokes ?? null
            const net = gross !== null && hole?.strokes_received !== null && hole?.strokes_received !== undefined
              ? gross - hole.strokes_received
              : gross
            const diff = net !== null && hole ? net - hole.par : null
            const kind = diff !== null ? classify(diff) : null
            const isEditing = editingHole === n

            if (isEditing) {
              return (
                <input
                  key={`cell-${n}`}
                  autoFocus
                  inputMode="numeric"
                  value={draft}
                  onChange={(e) => setDraft(e.target.value.replace(/[^0-9]/g, ""))}
                  onBlur={() => commit(n)}
                  onKeyDown={(e) => {
                    if (e.key === "Enter") commit(n)
                    if (e.key === "Escape") {
                      setEditingHole(null)
                      setDraft("")
                    }
                  }}
                  className="mx-auto flex size-9 items-center justify-center rounded-[4px] border-2 border-info bg-background text-center text-sm font-extrabold tabular-nums text-foreground outline-none"
                />
              )
            }

            return (
              <button
                key={`cell-${n}`}
                type="button"
                onClick={() => openEditor(n, gross)}
                aria-label={
                  gross !== null
                    ? `Hull ${n}, ${gross} slag, trykk for å endre`
                    : `Hull ${n}, ikke registrert, trykk for å registrere`
                }
                className={cn(
                  "mx-auto flex size-9 items-center justify-center text-sm font-extrabold tabular-nums transition-colors",
                  kind
                    ? scoreMarkClasses(kind)
                    : "rounded-[4px] border-2 border-dashed border-border text-muted-foreground hover:bg-accent/50",
                )}
              >
                {gross ?? "–"}
              </button>
            )
          })}
        </div>
      </div>

      {totalGross !== null && totalPar !== null && (
        <div className="flex items-center justify-between border-t border-border pt-3 text-sm">
          <span className="font-semibold text-muted-foreground">
            {played.length} av {numbers.length} hull spilt
          </span>
          <span className="font-extrabold tabular-nums text-foreground">
            {totalGross} slag ({totalGross - totalPar >= 0 ? "+" : ""}
            {totalGross - totalPar} til par)
          </span>
        </div>
      )}
    </div>
  )
}

// =========================================================================
// Leaderboard-fane
// =========================================================================

function LeaderboardTab({ base, tournament }: { base: string; tournament: ApiTournamentInfo }) {
  const [entries, setEntries] = useState<ApiLeaderboardEntry[] | null>(null)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    let cancelled = false
    async function load() {
      const res = await fetch(`${base}/individual-leaderboard`, { credentials: "include" })
      if (!res.ok) {
        if (!cancelled) setError("Klarte ikke å hente leaderboardet.")
        return
      }
      const data: ApiLeaderboardEntry[] = await res.json()
      if (!cancelled) setEntries(data)
    }
    load()
    return () => {
      cancelled = true
    }
  }, [base])

  const method = tournament.scoring_method

  function valueFor(entry: ApiLeaderboardEntry): { label: string; value: number | null } {
    if (method === "stableford") return { label: "poeng", value: entry.stableford_total }
    if (method === "stroke_net") return { label: "netto", value: entry.net_total }
    if (method === "copenhagen") return { label: "poeng", value: entry.copenhagen_total }
    if (method === "bingo_bango_bongo") return { label: "poeng", value: entry.bbb_total }
    return { label: "slag", value: entry.gross_total }
  }

  if (error) {
    return <p className="text-sm font-medium text-destructive">{error}</p>
  }

  if (!entries) {
    return (
      <div className="flex justify-center py-10">
        <div
          aria-hidden="true"
          className="size-8 animate-spin rounded-full border-4 border-primary/20 border-t-primary"
        />
      </div>
    )
  }

  if (!method) {
    return (
      <p className="text-sm font-medium text-muted-foreground text-pretty">
        Sett en scoringsmetode under «Oppsett» først -- avgjør hvilket tall leaderboardet
        skal rangere etter.
      </p>
    )
  }

  // Flaggturnering er en PER-RUNDE-konkurranse ("hvem kommer lengst DENNE
  // runden"), ikke et akkumulerbart tall på tvers av flere runder -- se
  // egen resultatvisning per runde under "Scorekort" i stedet.
  if (method === "flag") {
    return (
      <p className="text-sm font-medium text-muted-foreground text-pretty">
        Flaggturnering vises per runde -- se resultatet under «Scorekort» for hver runde.
      </p>
    )
  }

  if (entries.length === 0) {
    return (
      <p className="text-sm font-medium text-muted-foreground text-pretty">Ingen deltakere ennå.</p>
    )
  }

  return (
    <ul className="flex flex-col divide-y divide-border overflow-hidden rounded-2xl border border-border bg-card shadow-md shadow-black/8">
      {entries.map((entry, index) => {
        const { label, value } = valueFor(entry)
        const rank = index + 1
        const isLeader = rank === 1 && value !== null
        return (
          <li
            key={entry.tournament_participant_id}
            className={cn("flex items-center gap-3 px-4 py-3", isLeader && "bg-gold/10")}
          >
            <span
              className={cn(
                "flex size-9 shrink-0 items-center justify-center rounded-full text-sm font-extrabold tabular-nums",
                isLeader ? "bg-gold text-gold-foreground" : "bg-muted text-muted-foreground",
              )}
            >
              {isLeader ? <Medal aria-hidden="true" className="size-4" /> : rank}
            </span>
            <div className="flex min-w-0 flex-1 flex-col">
              <span className="truncate text-sm font-bold text-foreground">{entry.player_name}</span>
              <span className="text-xs font-semibold text-muted-foreground">
                {entry.rounds_played} {entry.rounds_played === 1 ? "runde" : "runder"} spilt
              </span>
            </div>
            <span className="shrink-0 text-right text-lg font-extrabold tabular-nums text-foreground">
              {value !== null ? (
                <>
                  {value}
                  <span className="ml-1 text-xs font-semibold text-muted-foreground">{label}</span>
                </>
              ) : (
                <span className="text-sm font-medium text-muted-foreground">–</span>
              )}
            </span>
          </li>
        )
      })}
    </ul>
  )
}
