"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, useRef, useState } from "react"
import Link from "next/link"
import dynamic from "next/dynamic"
import { useRouter } from "next/navigation"
import type { FlagMapEntry } from "@/components/flag-map-overview"
import {
  ArrowLeft,
  Check,
  ChevronDown,
  ChevronLeft,
  ChevronRight,
  Clock,
  Copy,
  Crosshair,
  Flag,
  KeyRound,
  MapPin,
  Medal,
  Plus,
  Trash2,
  Trophy,
  Upload,
  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 { PlayerImportPanel } from "@/components/player-import-panel"
import { RoundGroupsPanel } from "@/components/round-groups-panel"
import { TournamentSettingsCard, type TournamentSettingsValues } from "@/components/tournament-settings-card"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
  ChoiceRow,
  DirectionCross,
  NumberPicker,
  Stepper,
  WizardSection,
} from "@/components/hole-stat-inputs"
import { HoleHistoryDetail } from "@/components/hole-history-detail"
import { HoleTargetDistance } from "@/components/hole-target-distance"
import { ClubPicker } from "@/components/teecup/club-picker"
import { TournamentPresentationPanel } from "@/components/tournament-presentation"
import { StrokePlayLeaderboard, type LeaderboardRow as StrokePlayRow } from "@/components/stroke-play-leaderboard"
import { TournamentStatusPicker, type TournamentStatus } from "@/components/tournament-status-badge"
import { CourseTemplatePicker } from "@/components/course-template-editor"
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
  // Utvidet registreringsskjema (2026-08-14, migrasjon 074).
  first_name: string | null
  last_name: string | null
  birth_date: string | null // ISO yyyy-mm-dd
  email: string | null
  club_member_number: string | null
  club: string | null
  country: string | null
  paid: boolean
  comment: string | null
}

// Feltene sendt inn ved "Opprett ny spiller" (AddParticipantControl) --
// egen type fremfor å gjenbruke ApiPlayer direkte, siden dette er et
// SKJEMA (før spilleren finnes), ikke en allerede opprettet rad.
type NewPlayerInput = {
  firstName: string
  lastName: string
  gender?: "m" | "f" | "x"
  birthDate?: string
  email?: string
  clubMemberNumber?: string
  club?: string
  country?: string
  handicapIndex?: number
  paid: boolean
  comment?: string
}

type ApiTournamentInfo = {
  id: string
  name: string
  status: TournamentStatus
  join_code: string
  format_type: string
  scoring_method: string | null
  // Turneringsinnstillinger (2026-08-18) -- eksisterte alle på backend
  // (fullt fungerende, ekte selvregistrerings-logikk i registration.py)
  // uten noen UI noe sted til å sette dem, før TournamentSettingsCard.
  start_date: string | null
  end_date: string | null
  visibility: string
  description: string | null
  registration_deadline: string | null
  registration_capacity: number | null
  registration_overflow_policy: string
  registration_requires_approval: boolean
  // KUN meningsfullt når scoring_method="bingo_bango_bongo" (2026-07-30).
  bbb_sweep_bonus_enabled: boolean
  // Flaggturnering: kartoversikt-bryter (migrasjon 071, "Del B", ADR-067).
  flag_map_visible: boolean
  // Cut (migrasjon 083, 2026-08-18) -- kun meningsfullt for brutto/netto/
  // stableford. cut_applied_at er null inntil organisator faktisk trykker
  // "Anvend cut" -- cut_after_round/cut_size alene er bare konfigurasjon.
  cut_after_round: number | null
  cut_size: number | null
  cut_applied_at: string | null
  // Tiebreak (migrasjon 085, 2026-08-18) -- kun meningsfullt for brutto/
  // netto/stableford. To UAVHENGIGE innstillinger (vinner/felt kan ha
  // ulik metode). Påvirker ALDRI Cut-grensen, kun sluttresultatets
  // plasseringsvisning. "none" = delt plassering (uendret oppførsel).
  tiebreak_winner_method: string
  tiebreak_field_method: string
  // Omspill (migrasjon 086, 2026-08-18) -- "playoff" er kun gyldig for
  // tiebreak_winner_method. Fritekst-regel vist proaktivt i Oppsett OG
  // på leaderboardet, ikke bare når en uavgjort faktisk skjer.
  tiebreak_playoff_rule: string | null
}

type ApiParticipant = {
  id: string
  player_id: string
  player_name: string
  handicap_index_snapshot: number | null
  class_id: string | null
  class_name: string | null
  // Statistikknivå (migrasjon 075, ADR-071) -- styrer hvor mye HoleGrid/
  // HoleStatsSheet ber om ved scoring, tournament-bredt (ikke per runde).
  stat_level: "strokes_only" | "strokes_and_putts" | "full"
}

// Konkurranseklasser (2026-08-03) -- fritt navngitte, med et valgfritt
// standardutslag. Her (individuelle turneringer) brukes klasse til BÅDE
// utslag-forhåndsutfylling (AssignRoundParticipantControl) OG en egen
// resultatliste-seksjon per klasse (LeaderboardTab) -- se
// 053_tournament_classes.sql for full begrunnelse og forskjellen fra
// lagturneringer (tournament-detail.tsx), hvor klasse KUN styrer utslag.
type ApiTournamentClass = {
  id: string
  name: string
  default_tee_id: string | null
  default_tee_name: string | 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
  // Statistikknivå (migrasjon 075, ADR-071) -- "strokes_only" /
  // "strokes_and_putts" / "full", styrer om HoleGrid sin celle åpner det
  // enkle inline-tallfeltet (uendret oppførsel) eller HoleStatsSheet.
  stat_level: "strokes_only" | "strokes_and_putts" | "full"
}

type ApiHole = {
  hole_number: number
  par: number
  stroke_index: number
  gross_strokes: number | null
  // Full statistikkdybde (migrasjon 075, ADR-071) -- speiler round_hole
  // (frittstående runder), styrt av deltakerens stat_level. Alltid til
  // stede i responsen (null når ikke registrert), uansett stat_level --
  // frontend avgjør selv hva som faktisk VISES/redigeres.
  putts: number | null
  club_off_tee: string | null
  tee_shot_result: string | null
  approach_result: string | null
  chip_count: number | null
  bunker_shot_count: number | null
  penalty_strokes: number | null
  first_putt_distance_bucket: string | null
  anyway_strokes: number | null
  version: number
  strokes_received: number | null
}

type ApiRoundCell = {
  round_number: number
  label: string
  tone: "under" | "even" | "over" | null
}

type ApiEclecticHoleCell = {
  hole_number: number
  par: number
  value: number
  round_number: number
}

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
  class_id: string | null
  class_name: string | null
  // Augusta-stil resultattavle (2026-08-04) -- kun populert for
  // scoring_method brutto/netto/stableford, se stroke-play-leaderboard.tsx.
  position: string | null
  is_leader: boolean
  today_label: string | null
  thru_label: string | null
  total_label: string | null
  // Nøytral "ikke startet"-info (2026-08-18) -- kun satt når deltakeren
  // ikke har startet gjeldende/neste runde (thru_label "-"), se
  // stroke-play-leaderboard.tsx.
  next_tee_time: string | null
  next_start_hole: number | null
  rounds: ApiRoundCell[]
  // Eclectic (ADR-067-tillegget "Del C", 2026-08-14) -- kun populert for
  // scoring_method eclectic_gross/eclectic_net/eclectic_stableford.
  eclectic_total: number | null
  eclectic_holes: ApiEclecticHoleCell[]
}

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

// GPS-flaggplanting + runde 2+ (migrasjon 070, 2026-08-14) -- speiler
// rounds.py sin frittstående-variant (round-detail.tsx), se der for full
// begrunnelse. Duplisert her (ikke importert på tvers av de to store
// sidefilene) -- samme selvstendighets-konvensjon som round-scorecard.tsx
// allerede følger for sine egne typer/hjelpefunksjoner.
type ApiFlagPlant = {
  id: string
  lap: number
  hole_number: number
  lat: number
  lng: number
  on_green: boolean
  distance_to_pin_cm: number | null
  planted_by_user_id: string
  planted_at: string
}

type ApiFlagOverflowHole = {
  lap: number
  hole_number: number
  gross_strokes: number | null
  played: boolean
}

function playedHoleNumbersOrg(holeConfig: "full_18" | "front_9" | "back_9"): number[] {
  if (holeConfig === "front_9") return Array.from({ length: 9 }, (_, i) => i + 1)
  if (holeConfig === "back_9") return Array.from({ length: 9 }, (_, i) => i + 10)
  return Array.from({ length: 18 }, (_, i) => i + 1)
}

function computeFlagCurrentPositionOrg(
  playOrder: number[],
  holes: ApiHole[] | null,
  overflowHoles: ApiFlagOverflowHole[],
): { lap: number; holeNumber: number } {
  const lap1Played = new Set((holes ?? []).filter((h) => h.gross_strokes !== null).map((h) => h.hole_number))
  for (const h of playOrder) {
    if (!lap1Played.has(h)) return { lap: 1, holeNumber: h }
  }
  let lap = 2
  while (true) {
    const lapPlayed = new Set(overflowHoles.filter((h) => h.lap === lap && h.played).map((h) => h.hole_number))
    for (const h of playOrder) {
      if (!lapPlayed.has(h)) return { lap, holeNumber: h }
    }
    lap += 1
  }
}

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",
  eclectic_gross: "Eclectic (brutto)",
  eclectic_net: "Eclectic (netto)",
  eclectic_stableford: "Eclectic (Stableford)",
}

const ECLECTIC_SCORING_METHODS = new Set(["eclectic_gross", "eclectic_net", "eclectic_stableford"])

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 router = useRouter()
  const [tournament, setTournament] = useState<ApiTournamentInfo | null>(null)
  const [participants, setParticipants] = useState<ApiParticipant[]>([])
  const [rounds, setRounds] = useState<ApiRound[]>([])
  const [courses, setCourses] = useState<ApiCourse[]>([])
  const [classes, setClasses] = useState<ApiTournamentClass[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [tab, setTab] = useState<"setup" | "score" | "leaderboard" | "presentation">("setup")
  // Kun org-eier/admin får se/bruke "Slett turnering" (ADR-086) -- samme
  // sperre/begrunnelse som tournament-detail.tsx (lagformatet).
  const [myRole, setMyRole] = useState<"owner" | "admin" | "member" | null>(null)
  const [deleting, setDeleting] = useState(false)

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

  useEffect(() => {
    let cancelled = false
    fetch("/auth/me", { credentials: "include" })
      .then((res) => (res.ok ? res.json() : null))
      .then((me: { organizations: { organization_id: string; role: "owner" | "admin" | "member" }[] } | null) => {
        if (cancelled || !me) return
        setMyRole(me.organizations.find((o) => o.organization_id === organizationId)?.role ?? null)
      })
      .catch(() => {})
    return () => {
      cancelled = true
    }
  }, [organizationId])
  const isOrgAdmin = myRole === "owner" || myRole === "admin"

  async function deleteTournament() {
    if (
      !confirm(
        `Slette "${tournamentName}" permanent? Dette fjerner ALT -- deltakere, alle runder og all scoring. Kan ikke angres.`,
      )
    ) {
      return
    }
    setDeleting(true)
    setError(null)
    try {
      const res = await fetch(base, { method: "DELETE", credentials: "include" })
      if (!res.ok && res.status !== 204) {
        const body = await res.json().catch(() => null)
        throw new Error(body?.detail?.message ?? "Klarte ikke å slette turneringen.")
      }
      router.replace("/dashboard")
    } catch (err) {
      setError(err instanceof Error ? err.message : "Klarte ikke å slette turneringen.")
      setDeleting(false)
    }
  }

  useEffect(() => {
    let cancelled = false
    async function load() {
      try {
        const [tournamentsData, participantsData, roundsData, coursesData, classesData] = await Promise.all([
          getJson<ApiTournamentInfo[]>(`/orgs/${organizationId}/tournaments`),
          getJson<ApiParticipant[]>(`${base}/participants`),
          getJson<ApiRound[]>(`${base}/rounds`),
          getJson<ApiCourse[]>(`/orgs/${organizationId}/courses`),
          getJson<ApiTournamentClass[]>(`${base}/classes`),
        ])
        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 ?? [])
        setClasses(classesData ?? [])
      } 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 updateFlagMapVisible(visible: boolean) {
    if (!tournament) return
    const previous = tournament
    setTournament({ ...tournament, flag_map_visible: visible })
    try {
      const res = await fetch(`/orgs/${organizationId}/tournaments/${tournamentId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ flag_map_visible: visible }),
      })
      if (!res.ok) throw new Error()
    } catch {
      setTournament(previous)
      setError("Klarte ikke å endre kartoversikt-innstillingen. Prøv igjen.")
    }
  }

  // Cut (migrasjon 083, 2026-08-18) -- ren KONFIGURASJON, fritt endre/
  // nullstille helt til "Anvend cut" faktisk trykkes (applyCut under).
  async function updateCutConfig(afterRound: number | null, size: number | null) {
    if (!tournament) return
    const previous = tournament
    setTournament({ ...tournament, cut_after_round: afterRound, cut_size: size })
    try {
      const res = await fetch(`/orgs/${organizationId}/tournaments/${tournamentId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ cut_after_round: afterRound, cut_size: size }),
      })
      if (!res.ok) throw new Error()
    } catch {
      setTournament(previous)
      setError("Klarte ikke å lagre cut-innstillingene. Prøv igjen.")
    }
  }

  // Tiebreak (migrasjon 085, 2026-08-18) -- ren KONFIGURASJON, samme
  // optimistiske PATCH-mønster som scoringsmetode/cut over. Påvirker
  // ALDRI Cut-grensen, kun sluttresultatets plasseringsvisning.
  async function updateTiebreakConfig(winner: string, field: string, playoffRule: string | null) {
    if (!tournament) return
    const previous = tournament
    setTournament({
      ...tournament,
      tiebreak_winner_method: winner,
      tiebreak_field_method: field,
      tiebreak_playoff_rule: playoffRule,
    })
    try {
      const res = await fetch(`/orgs/${organizationId}/tournaments/${tournamentId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({
          tiebreak_winner_method: winner,
          tiebreak_field_method: field,
          tiebreak_playoff_rule: playoffRule,
        }),
      })
      if (!res.ok) throw new Error()
    } catch {
      setTournament(previous)
      setError("Klarte ikke å lagre tiebreak-innstillingene. Prøv igjen.")
    }
  }

  // Turneringsinnstillinger (2026-08-18, TournamentSettingsCard --
  // V0-eksportert, se ADR/CHANGELOG #118). Komponenten viser sin EGEN
  // lagre-/feil-tilstand rundt onSave-løftet -- IKKE fang feilen her
  // (til forskjell fra de andre optimistiske PATCH-hjelperne over), la
  // den kaste videre slik komponenten forventer.
  async function updateSettings(next: TournamentSettingsValues) {
    if (!tournament) return
    const registrationDeadlineIso = fromDatetimeLocalValue(next.registrationDeadline)
    const res = await fetch(`/orgs/${organizationId}/tournaments/${tournamentId}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({
        name: next.name,
        start_date: next.startDate,
        end_date: next.endDate,
        visibility: next.visibility,
        description: next.description || null,
        registration_deadline: registrationDeadlineIso,
        registration_capacity: next.registrationCapacity,
        registration_overflow_policy: next.registrationOverflowPolicy,
        registration_requires_approval: next.registrationRequiresApproval,
      }),
    })
    if (!res.ok) throw new Error("Klarte ikke å lagre innstillingene.")
    setTournament({
      ...tournament,
      name: next.name,
      start_date: next.startDate,
      end_date: next.endDate,
      visibility: next.visibility,
      description: next.description || null,
      registration_deadline: registrationDeadlineIso,
      registration_capacity: next.registrationCapacity,
      registration_overflow_policy: next.registrationOverflowPolicy,
      registration_requires_approval: next.registrationRequiresApproval,
    })
  }

  // MANUELL anvendelse (bruker bekreftet eksplisitt 2026-08-18: IKKE
  // automatisk ved fullspilt runde) -- idempotent på serveren, men likevel
  // en reell, konsekvensfull handling for ekte spillere (blokkerer dem fra
  // videre scoring), derfor samme confirm()-mønster som andre irreversible
  // handlinger i appen (slett turnering osv.).
  async function applyCut() {
    if (!tournament) return
    if (
      !confirm(
        "Anvende cut nå? Spillere utenfor grensen blokkeres fra å registrere score i senere runder. Kan kjøres på nytt (regner alt på nytt), men bør ikke trykkes før alle er ferdige med runden.",
      )
    ) {
      return
    }
    try {
      const res = await fetch(`/orgs/${organizationId}/tournaments/${tournamentId}/apply-cut`, {
        method: "POST",
        credentials: "include",
      })
      if (!res.ok) throw new Error()
      const result: { survivors: number; cut_count: number } = await res.json()
      setTournament({ ...tournament, cut_applied_at: new Date().toISOString() })
      await refreshParticipants()
      setError(null)
      alert(`Cut anvendt: ${result.survivors} går videre, ${result.cut_count} er kuttet.`)
    } catch {
      setError("Klarte ikke å anvende cutten. 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)))
  }

  // Etter massimport (PlayerImportPanel) -- panelet gjør sine egne
  // POST-kall direkte (bulk + participants), enklest å hente
  // deltakerlisten på nytt etterpå fremfor å slå sammen delvis ukjent
  // respons-tilstand manuelt.
  async function refreshParticipants() {
    const data = await getJson<ApiParticipant[]>(`${base}/participants`)
    if (data) setParticipants(data)
  }

  async function addNewPlayerAndParticipant(input: NewPlayerInput) {
    setError(null)
    const displayName = `${input.firstName} ${input.lastName}`.trim()
    const playerRes = await fetch(`/orgs/${organizationId}/players`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({
        display_name: displayName,
        first_name: input.firstName.trim() || null,
        last_name: input.lastName.trim() || null,
        handicap_index: input.handicapIndex ?? null,
        gender: input.gender ?? null,
        birth_date: input.birthDate || null,
        email: input.email?.trim() || null,
        club_member_number: input.clubMemberNumber?.trim() || null,
        club: input.club?.trim() || null,
        country: input.country?.trim() || null,
        paid: input.paid,
        comment: input.comment?.trim() || null,
      }),
    })
    if (!playerRes.ok) {
      setError(await errorMessage(playerRes, "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 setParticipantClass(participantId: string, classId: string | null) {
    setError(null)
    const res = await fetch(`${base}/participants/${participantId}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ class_id: classId }),
    })
    if (!res.ok) {
      setError(await errorMessage(res, "Klarte ikke å endre klasse."))
      return
    }
    const updated: ApiParticipant = await res.json()
    setParticipants((prev) => prev.map((p) => (p.id === updated.id ? updated : p)))
  }

  async function setParticipantStatLevel(
    participantId: string,
    statLevel: "strokes_only" | "strokes_and_putts" | "full",
  ) {
    setError(null)
    const res = await fetch(`${base}/participants/${participantId}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ stat_level: statLevel }),
    })
    if (!res.ok) {
      setError(await errorMessage(res, "Klarte ikke å endre statistikknivå."))
      return
    }
    const updated: ApiParticipant = await res.json()
    setParticipants((prev) => prev.map((p) => (p.id === updated.id ? updated : p)))
  }

  // Retting av EN deltakers HCP direkte i turneringen (2026-08-18) --
  // spillerpoolen (ADR-085) endrer kun grunnlaget for FREMTIDIGE
  // rundepåmeldinger, aldri en allerede satt handicap_index_snapshot her.
  // Rører ALDRI allerede opprettede tournament_round_participant.course_
  // handicap for runder deltakeren allerede er satt opp i (samme "frosset
  // snapshot"-prinsipp som ADR-085 sin bulk-import allerede har).
  async function setParticipantHandicap(participantId: string, handicap: number | null) {
    setError(null)
    const res = await fetch(`${base}/participants/${participantId}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ handicap_index_snapshot: handicap }),
    })
    if (!res.ok) {
      setError(await errorMessage(res, "Klarte ikke å endre handicap."))
      return
    }
    const updated: ApiParticipant = await res.json()
    setParticipants((prev) => prev.map((p) => (p.id === updated.id ? updated : p)))
  }

  async function createClass(name: string, defaultTeeId: string | null) {
    setError(null)
    const res = await fetch(`${base}/classes`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ name, default_tee_id: defaultTeeId }),
    })
    if (!res.ok) {
      setError(await errorMessage(res, "Klarte ikke å opprette klassen."))
      return
    }
    const created: ApiTournamentClass = await res.json()
    setClasses((prev) => [...prev, created])
  }

  async function deleteClass(classId: string) {
    setError(null)
    const res = await fetch(`${base}/classes/${classId}`, { method: "DELETE", credentials: "include" })
    if (res.status !== 204) {
      setError(await errorMessage(res, "Klarte ikke å fjerne klassen."))
      return
    }
    setClasses((prev) => prev.filter((c) => c.id !== classId))
    setParticipants((prev) =>
      prev.map((p) => (p.class_id === classId ? { ...p, class_id: null, class_name: null } : p)),
    )
  }

  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
  }

  function handleCourseImported(course: ApiCourse) {
    setCourses((prev) => [...prev, course].sort((a, b) => a.name.localeCompare(b.name)))
  }

  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} />
            {isOrgAdmin && (
              <button
                type="button"
                onClick={() => void deleteTournament()}
                disabled={deleting}
                aria-label="Slett turnering"
                className="flex size-10 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-colors hover:border-destructive/40 hover:bg-destructive/10 hover:text-destructive disabled:pointer-events-none disabled:opacity-50"
              >
                <Trash2 aria-hidden="true" className="size-5" />
              </button>
            )}
          </div>
        </div>

        <nav
          className="mx-auto flex w-full max-w-4xl items-center gap-2 overflow-x-auto px-5 pb-3"
          aria-label="Turneringsseksjoner"
        >
          {(
            [
              ["setup", "Oppsett"],
              ["score", "Scorekort"],
              ["leaderboard", "Leaderboard"],
              ["presentation", "Presentasjon"],
            ] as const
          ).map(([key, label]) => (
            <button
              key={key}
              type="button"
              onClick={() => setTab(key)}
              aria-current={tab === key ? "page" : undefined}
              className={cn(
                "min-h-11 shrink-0 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>

      {/* Leaderboard-fanen er BEVISST unntatt fra den delte 4xl-bredden
          (2026-08-18, brukerrapport: "fyller ikke hele bredden på skjermen")
          -- StrokePlayLeaderboard er selv bygget for å skalere opp og fylle
          en stor skjerm (egne `lg:`-brytningspunkt-klasser, se komponentens
          egen kommentar om "storskjerm uten skrolling"), men satt inni denne
          delte 4xl-wrapperen (896px, samme wrapper som skjema-tunge Oppsett/
          Presentasjon-fanene bruker) fikk den aldri sjansen til det -- felles
          med resten av siden siden komponenten ble bygget/testet isolert. */}
      <main
        className={cn(
          "mx-auto w-full flex-1 px-5 py-6 sm:py-8",
          tab === "leaderboard" ? "max-w-[110rem]" : "max-w-4xl",
        )}
      >
        {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}
            classes={classes}
            onUpdateScoringMethod={updateScoringMethod}
            onUpdateBbbSweepBonus={updateBbbSweepBonus}
            onUpdateFlagMapVisible={updateFlagMapVisible}
            onUpdateCutConfig={updateCutConfig}
            onUpdateTiebreakConfig={updateTiebreakConfig}
            onUpdateSettings={updateSettings}
            onApplyCut={applyCut}
            onAddParticipant={addParticipant}
            onAddNewPlayer={addNewPlayerAndParticipant}
            onImportComplete={refreshParticipants}
            onRemoveParticipant={removeParticipant}
            onAddRound={addRound}
            onDeleteRound={deleteRound}
            onAddCourse={addCourse}
            onCourseImported={handleCourseImported}
            onCreateClass={createClass}
            onDeleteClass={deleteClass}
            onSetParticipantClass={setParticipantClass}
            onSetParticipantStatLevel={setParticipantStatLevel}
            onSetParticipantHandicap={setParticipantHandicap}
            onError={setError}
          />
        )}
        {tab === "score" && (
          <ScoreTab base={base} rounds={rounds} scoringMethod={tournament.scoring_method} onError={setError} />
        )}
        {tab === "leaderboard" && (
          <LeaderboardTab base={base} tournament={tournament} />
        )}
        {tab === "presentation" && (
          <TournamentPresentationPanel organizationId={organizationId} tournamentId={tournamentId} />
        )}
      </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
// =========================================================================

// <input type="datetime-local"> krever "YYYY-MM-DDTHH:mm" (ingen sekunder/
// tidssone) -- backend (Pydantic datetime) serialiserer/forventer full ISO
// med tidssone. Samme konverteringsbehov -- og samme tidssone-bug hvis det
// hoppes over -- som allerede funnet og fikset i round-groups-panel.tsx
// (ADR-088): new Date(...).toISOString() FØR sending, nettleserens egen
// lokal-tolkning ved visning.
function toDatetimeLocalValue(iso: string | null): string | null {
  if (!iso) return null
  const date = new Date(iso)
  if (Number.isNaN(date.getTime())) return null
  const pad = (n: number) => String(n).padStart(2, "0")
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`
}

function fromDatetimeLocalValue(value: string | null): string | null {
  if (!value) return null
  const date = new Date(value)
  return Number.isNaN(date.getTime()) ? null : date.toISOString()
}

function SetupTab({
  base,
  organizationId,
  tournament,
  participants,
  rounds,
  courses,
  classes,
  onUpdateScoringMethod,
  onUpdateBbbSweepBonus,
  onUpdateFlagMapVisible,
  onUpdateCutConfig,
  onApplyCut,
  onUpdateTiebreakConfig,
  onUpdateSettings,
  onAddParticipant,
  onAddNewPlayer,
  onImportComplete,
  onRemoveParticipant,
  onAddRound,
  onDeleteRound,
  onAddCourse,
  onCourseImported,
  onCreateClass,
  onDeleteClass,
  onSetParticipantClass,
  onSetParticipantStatLevel,
  onSetParticipantHandicap,
  onError,
}: {
  base: string
  organizationId: string
  tournament: ApiTournamentInfo
  participants: ApiParticipant[]
  rounds: ApiRound[]
  courses: ApiCourse[]
  classes: ApiTournamentClass[]
  onUpdateScoringMethod: (method: string) => void
  onUpdateBbbSweepBonus: (enabled: boolean) => void
  onUpdateFlagMapVisible: (visible: boolean) => void
  onUpdateCutConfig: (afterRound: number | null, size: number | null) => Promise<void>
  onApplyCut: () => Promise<void>
  onUpdateTiebreakConfig: (winner: string, field: string, playoffRule: string | null) => Promise<void>
  onUpdateSettings: (next: TournamentSettingsValues) => Promise<void>
  onAddParticipant: (playerId: string) => Promise<void>
  onAddNewPlayer: (input: NewPlayerInput) => Promise<void>
  onImportComplete: () => 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>
  onCourseImported: (course: ApiCourse) => void
  onCreateClass: (name: string, defaultTeeId: string | null) => Promise<void>
  onDeleteClass: (id: string) => Promise<void>
  onSetParticipantClass: (participantId: string, classId: string | null) => Promise<void>
  onSetParticipantStatLevel: (
    participantId: string,
    statLevel: "strokes_only" | "strokes_and_putts" | "full",
  ) => Promise<void>
  onSetParticipantHandicap: (participantId: string, handicap: number | null) => Promise<void>
  onError: (message: string) => void
}) {
  const [pool, setPool] = useState<ApiPlayer[]>([])

  async function refreshPool() {
    const data = await getJson<ApiPlayer[]>(`/orgs/${organizationId}/players`)
    setPool(data ?? [])
  }

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

  const settingsValues: TournamentSettingsValues = {
    name: tournament.name,
    startDate: tournament.start_date,
    endDate: tournament.end_date,
    visibility: (tournament.visibility as TournamentSettingsValues["visibility"]) ?? "org",
    description: tournament.description ?? "",
    registrationDeadline: toDatetimeLocalValue(tournament.registration_deadline),
    registrationCapacity: tournament.registration_capacity,
    registrationOverflowPolicy:
      (tournament.registration_overflow_policy as TournamentSettingsValues["registrationOverflowPolicy"]) ??
      "waitlist",
    registrationRequiresApproval: tournament.registration_requires_approval,
  }

  return (
    <div className="flex flex-col gap-6">
      <TournamentSettingsCard values={settingsValues} onSave={onUpdateSettings} />

      <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>
        )}
        {tournament.scoring_method === "flag" && (
          <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.flag_map_visible}
              onChange={(e) => onUpdateFlagMapVisible(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">Kartoversikt over flagg</span>
              <span className="text-xs text-muted-foreground">
                Når dette er PÅ, kan alle deltakere se hvor alle andre har plantet flagget. Av som standard.
              </span>
            </span>
          </label>
        )}
      </section>

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

      {tournament.scoring_method != null &&
        ["stroke_gross", "stroke_net", "stableford"].includes(tournament.scoring_method) && (
          <>
            <CutCard tournament={tournament} rounds={rounds} onUpdateConfig={onUpdateCutConfig} onApplyCut={onApplyCut} />
            <TiebreakCard tournament={tournament} onUpdateConfig={onUpdateTiebreakConfig} />
          </>
        )}

      <ClassesCard
        classes={classes}
        courses={courses}
        organizationId={organizationId}
        onCreate={onCreateClass}
        onDelete={onDeleteClass}
      />

      <ParticipantsCard
        organizationId={organizationId}
        tournamentId={tournament.id}
        participants={participants}
        pool={pool}
        classes={classes}
        onAddParticipant={onAddParticipant}
        onAddNewPlayer={onAddNewPlayer}
        onImportComplete={async () => {
          await Promise.all([refreshPool(), onImportComplete()])
        }}
        onRemoveParticipant={onRemoveParticipant}
        onSetClass={onSetParticipantClass}
        onSetStatLevel={onSetParticipantStatLevel}
        onSetHandicap={onSetParticipantHandicap}
      />
    </div>
  )
}

// Cut (migrasjon 083, 2026-08-18) -- topp N og delt plass, etter et
// organisator-valgt rundenummer, MANUELT anvendt (bruker bekreftet
// eksplisitt: ikke automatisk ved fullspilt runde). Samme "vanlig
// innstillingskort"-mønster som Scoringsmetode-seksjonen over -- ingen
// V0-runde nødvendig, dette er en liten tilføyelse til et allerede
// håndkodet oppsett-skjema, ikke en ny selvstendig UI-flate.
function CutCard({
  tournament,
  rounds,
  onUpdateConfig,
  onApplyCut,
}: {
  tournament: ApiTournamentInfo
  rounds: ApiRound[]
  onUpdateConfig: (afterRound: number | null, size: number | null) => Promise<void>
  onApplyCut: () => Promise<void>
}) {
  const [afterRound, setAfterRound] = useState(tournament.cut_after_round?.toString() ?? "")
  const [size, setSize] = useState(tournament.cut_size?.toString() ?? "")
  const [saving, setSaving] = useState(false)
  const [applying, setApplying] = useState(false)

  const sortedRounds = [...rounds].sort((a, b) => a.sequence - b.sequence)
  const dirty = afterRound !== (tournament.cut_after_round?.toString() ?? "") || size !== (tournament.cut_size?.toString() ?? "")
  const configComplete = afterRound.trim() !== "" && size.trim() !== ""

  async function handleSave() {
    setSaving(true)
    await onUpdateConfig(afterRound.trim() ? Number(afterRound) : null, size.trim() ? Number(size) : null)
    setSaving(false)
  }

  async function handleApply() {
    setApplying(true)
    await onApplyCut()
    setApplying(false)
  }

  return (
    <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">Cut</h2>
      <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
        Topp N og delt plass slipper videre etter valgt runde. Kuttede spillere vises i egen
        seksjon på leaderboardet og blokkeres fra å registrere score i senere runder.
        {sortedRounds.length > 0 && (
          <> Turneringen har {sortedRounds.length} {sortedRounds.length === 1 ? "runde" : "runder"} satt opp.</>
        )}
      </p>

      <div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:gap-4">
        <label className="flex flex-1 flex-col gap-1.5">
          <span className="text-sm font-bold text-foreground">Etter runde nr.</span>
          <input
            type="number"
            min={1}
            value={afterRound}
            onChange={(e) => setAfterRound(e.target.value)}
            placeholder="f.eks. 2"
            className="h-11 rounded-xl border border-border bg-background px-4 text-sm font-semibold text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          />
        </label>
        <label className="flex flex-1 flex-col gap-1.5">
          <span className="text-sm font-bold text-foreground">Antall som går videre</span>
          <input
            type="number"
            min={1}
            value={size}
            onChange={(e) => setSize(e.target.value)}
            placeholder="f.eks. 20"
            className="h-11 rounded-xl border border-border bg-background px-4 text-sm font-semibold text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          />
          <span className="text-xs text-muted-foreground">
            Plassen dette tallet havner på blir grensen -- ikke et fast tak. Er noen uavgjort
            nøyaktig der, slipper alle de gjennom (kan altså bli litt flere enn {size || "N"}).
          </span>
        </label>
        <button
          type="button"
          onClick={() => void handleSave()}
          disabled={!dirty || saving}
          className="inline-flex h-11 shrink-0 items-center justify-center gap-2 rounded-xl border border-border bg-background px-5 text-sm font-bold text-foreground transition-colors hover:bg-accent/50 disabled:cursor-not-allowed disabled:opacity-50"
        >
          {saving ? "Lagrer..." : "Lagre innstilling"}
        </button>
      </div>

      <div className="flex flex-col items-start gap-2 border-t border-border pt-3 sm:flex-row sm:items-center sm:justify-between">
        <p className="text-xs font-medium text-muted-foreground">
          {tournament.cut_applied_at
            ? `Cut anvendt ${new Date(tournament.cut_applied_at).toLocaleString("no-NO", { dateStyle: "medium", timeStyle: "short" })}. Kan anvendes på nytt hvis noe rettes.`
            : "Cut er ikke anvendt ennå -- ingen spillere er blokkert."}
        </p>
        <button
          type="button"
          onClick={() => void handleApply()}
          disabled={!configComplete || applying}
          className="inline-flex h-11 shrink-0 items-center justify-center gap-2 rounded-xl bg-primary px-5 text-sm font-bold text-primary-foreground shadow-sm transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
        >
          {applying ? "Anvender..." : tournament.cut_applied_at ? "Anvend cut på nytt" : "Anvend cut"}
        </button>
      </div>
    </section>
  )
}

// Tiebreak (migrasjon 085, 2026-08-18) -- "Ved likt resultat ... Skal
// laveste hcp plasseres foran, beste siste 18/9/6/3/siste hull plasseres
// foran, eller deles plasseringene. Her kan svaret være forskjellig for
// vinner og resten av feltet." Bruker: metoden skal defineres av
// organisator ved oppsett, IKKE fastlåst i koden -- derfor to
// uavhengige nedtrekk. Samme "vanlig innstillingskort"-mønster som
// CutCard rett over.
// "Omspill" (migrasjon 086, 2026-08-18) -- KUN et gyldig valg for
// vinneren (bruker sin egen formulering: "ved lik score for LEDER"),
// ikke for resten av feltet -- appen kan ikke selv avgjøre et omspill,
// det skjer på banen. tiebreak_playoff_rule er fritekst vist proaktivt
// her OG på leaderboardet, ikke bare når en uavgjort faktisk skjer.
const TIEBREAK_OPTIONS_WINNER: { value: string; label: string; hint: string }[] = [
  { value: "none", label: "Delt plassering", hint: "Ingen automatisk avgjørelse -- vises som «T1»." },
  { value: "countback", label: "Countback", hint: "Beste siste 9 -> 6 -> 3 -> siste hull i siste runde" },
  { value: "lowest_hcp", label: "Laveste HCP", hint: "Spilleren med lavest handicap-indeks vinner uavgjortheten" },
  { value: "playoff", label: "Omspill", hint: "Avgjøres på banen -- beskriv formatet under, vises til alle før turneringen" },
]

const TIEBREAK_OPTIONS_FIELD: { value: string; label: string; hint: string }[] = [
  { value: "none", label: "Delt plassering", hint: "Ingen automatisk avgjørelse -- vises som «T5» osv." },
  { value: "countback", label: "Countback", hint: "Beste siste 9 -> 6 -> 3 -> siste hull i siste runde" },
  { value: "lowest_hcp", label: "Laveste HCP", hint: "Spilleren med lavest handicap-indeks vinner uavgjortheten" },
]

function TiebreakCard({
  tournament,
  onUpdateConfig,
}: {
  tournament: ApiTournamentInfo
  onUpdateConfig: (winner: string, field: string, playoffRule: string | null) => Promise<void>
}) {
  const [winner, setWinner] = useState(tournament.tiebreak_winner_method)
  const [field, setField] = useState(tournament.tiebreak_field_method)
  const [playoffRule, setPlayoffRule] = useState(tournament.tiebreak_playoff_rule ?? "")
  const [saving, setSaving] = useState(false)

  const dirty =
    winner !== tournament.tiebreak_winner_method ||
    field !== tournament.tiebreak_field_method ||
    playoffRule !== (tournament.tiebreak_playoff_rule ?? "")

  async function handleSave() {
    setSaving(true)
    await onUpdateConfig(winner, field, winner === "playoff" ? playoffRule.trim() || null : null)
    setSaving(false)
  }

  return (
    <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">Tiebreak ved likt resultat</h2>
      <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
        Velges uavhengig for vinneren og resten av feltet. Påvirker kun sluttresultatets
        plassering -- Cut-grensen (over) bruker alltid full delt plass, uansett denne innstillingen.
      </p>

      <div className="flex flex-col gap-4 sm:flex-row">
        <label className="flex flex-1 flex-col gap-1.5">
          <span className="text-sm font-bold text-foreground">Ved likt resultat for vinneren</span>
          <select
            value={winner}
            onChange={(e) => setWinner(e.target.value)}
            className="h-11 rounded-xl border border-border bg-background px-3 text-sm font-semibold text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          >
            {TIEBREAK_OPTIONS_WINNER.map((o) => (
              <option key={o.value} value={o.value}>
                {o.label}
              </option>
            ))}
          </select>
          <span className="text-xs text-muted-foreground">
            {TIEBREAK_OPTIONS_WINNER.find((o) => o.value === winner)?.hint}
          </span>
        </label>
        <label className="flex flex-1 flex-col gap-1.5">
          <span className="text-sm font-bold text-foreground">Ved likt resultat i resten av feltet</span>
          <select
            value={field}
            onChange={(e) => setField(e.target.value)}
            className="h-11 rounded-xl border border-border bg-background px-3 text-sm font-semibold text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          >
            {TIEBREAK_OPTIONS_FIELD.map((o) => (
              <option key={o.value} value={o.value}>
                {o.label}
              </option>
            ))}
          </select>
          <span className="text-xs text-muted-foreground">
            {TIEBREAK_OPTIONS_FIELD.find((o) => o.value === field)?.hint}
          </span>
        </label>
      </div>

      {winner === "playoff" && (
        <label className="flex flex-col gap-1.5">
          <span className="text-sm font-bold text-foreground">Omspill-format</span>
          <input
            type="text"
            value={playoffRule}
            onChange={(e) => setPlayoffRule(e.target.value)}
            placeholder="F.eks. Sudden death fra hull 18, til vinner er kåret."
            className="h-11 rounded-xl border border-border bg-background px-3 text-sm font-semibold text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          />
          <span className="text-xs text-muted-foreground">
            Vises på leaderboardet slik at deltakerne kjenner formatet før turneringen.
          </span>
        </label>
      )}

      <button
        type="button"
        onClick={() => void handleSave()}
        disabled={!dirty || saving}
        className="inline-flex h-11 w-fit items-center justify-center gap-2 self-end rounded-xl border border-border bg-background px-5 text-sm font-bold text-foreground transition-colors hover:bg-accent/50 disabled:cursor-not-allowed disabled:opacity-50"
      >
        {saving ? "Lagrer..." : "Lagre innstilling"}
      </button>
    </section>
  )
}

// Konkurranseklasser (2026-08-03) -- se tournament-detail.tsx sin
// ClassesCard for lagturnering-varianten (samme mønster, men uten
// leaderboard-koblingen -- her deler klasse OGSÅ opp resultatlisten).
function ClassesCard({
  classes,
  courses,
  organizationId,
  onCreate,
  onDelete,
}: {
  classes: ApiTournamentClass[]
  courses: ApiCourse[]
  organizationId: string
  onCreate: (name: string, defaultTeeId: string | null) => Promise<void>
  onDelete: (classId: string) => Promise<void>
}) {
  const [name, setName] = useState("")
  const [courseId, setCourseId] = useState("")
  const [teeId, setTeeId] = useState("")
  const [tees, setTees] = useState<ApiTee[]>([])
  const [adding, setAdding] = useState(false)

  useEffect(() => {
    if (!courseId) {
      setTees([])
      setTeeId("")
      return
    }
    let cancelled = false
    getJson<ApiTee[]>(`/orgs/${organizationId}/courses/${courseId}/tees`).then((data) => {
      if (!cancelled) {
        setTees(data ?? [])
        setTeeId(data?.[0]?.id ?? "")
      }
    })
    return () => {
      cancelled = true
    }
  }, [organizationId, courseId])

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    if (name.trim().length === 0 || adding) return
    setAdding(true)
    await onCreate(name.trim(), teeId || null)
    setAdding(false)
    setName("")
    setCourseId("")
    setTeeId("")
  }

  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">Klasser</h2>
        <span className="rounded-full bg-muted px-2.5 py-1 text-xs font-bold tabular-nums text-muted-foreground">
          {classes.length}
        </span>
      </div>
      <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
        Foreslår riktig utslag ved oppsett av en runde, og gir hver klasse sin egen seksjon på
        leaderboardet.
      </p>

      {classes.length > 0 && (
        <ul className="flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border">
          {classes.map((c) => (
            <li key={c.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">{c.name}</span>
                {c.default_tee_name && (
                  <span className="text-xs text-muted-foreground">
                    Standardutslag: {c.default_tee_name}
                  </span>
                )}
              </div>
              <Button
                type="button"
                variant="ghost"
                size="icon"
                onClick={() => onDelete(c.id)}
                aria-label={`Fjern klassen ${c.name}`}
                className="size-9 shrink-0 rounded-lg text-muted-foreground hover:text-destructive"
              >
                <Trash2 aria-hidden="true" className="size-4" />
              </Button>
            </li>
          ))}
        </ul>
      )}

      <form onSubmit={handleSubmit} className="flex flex-col gap-2 sm:flex-row sm:items-end">
        <div className="flex flex-1 flex-col gap-1">
          <Label htmlFor="ind-class-name" className="text-xs font-semibold text-muted-foreground">
            Klassenavn
          </Label>
          <Input
            id="ind-class-name"
            value={name}
            onChange={(e) => setName(e.target.value)}
            placeholder="F.eks. Damer"
            className="h-11"
          />
        </div>
        <div className="flex flex-1 flex-col gap-1">
          <Label htmlFor="ind-class-course" className="text-xs font-semibold text-muted-foreground">
            Bane (for standardutslag)
          </Label>
          <select
            id="ind-class-course"
            value={courseId}
            onChange={(e) => setCourseId(e.target.value)}
            className="h-11 rounded-lg border border-border bg-card px-2.5 text-sm font-medium text-foreground outline-none"
          >
            <option value="">Ingen standardutslag</option>
            {courses.map((c) => (
              <option key={c.id} value={c.id}>
                {c.name}
              </option>
            ))}
          </select>
        </div>
        {tees.length > 0 && (
          <div className="flex flex-1 flex-col gap-1">
            <Label htmlFor="ind-class-tee" className="text-xs font-semibold text-muted-foreground">
              Standardutslag
            </Label>
            <select
              id="ind-class-tee"
              value={teeId}
              onChange={(e) => setTeeId(e.target.value)}
              className="h-11 rounded-lg border border-border bg-card px-2.5 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="submit"
          variant="secondary"
          disabled={adding || name.trim().length === 0}
          className="h-11 shrink-0 rounded-xl font-semibold"
        >
          <Plus aria-hidden="true" className="size-4" />
          Legg til
        </Button>
      </form>
    </section>
  )
}

const STAT_LEVEL_LABELS: Record<string, string> = {
  strokes_only: "Kun slag",
  strokes_and_putts: "Slag og putter",
  full: "All statistikk",
}

function ParticipantsCard({
  organizationId,
  tournamentId,
  participants,
  pool,
  classes,
  onAddParticipant,
  onAddNewPlayer,
  onImportComplete,
  onRemoveParticipant,
  onSetClass,
  onSetStatLevel,
  onSetHandicap,
}: {
  organizationId: string
  tournamentId: string
  participants: ApiParticipant[]
  pool: ApiPlayer[]
  classes: ApiTournamentClass[]
  onAddParticipant: (playerId: string) => Promise<void>
  onAddNewPlayer: (input: NewPlayerInput) => Promise<void>
  onImportComplete: () => Promise<void>
  onRemoveParticipant: (id: string) => Promise<void>
  onSetClass: (participantId: string, classId: string | null) => Promise<void>
  onSetStatLevel: (
    participantId: string,
    statLevel: "strokes_only" | "strokes_and_putts" | "full",
  ) => Promise<void>
  onSetHandicap: (participantId: string, handicap: number | null) => Promise<void>
}) {
  const [showImport, setShowImport] = 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">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>

      <Button type="button" variant="outline" onClick={() => setShowImport(true)} className="min-h-11 self-start">
        <Upload aria-hidden="true" className="mr-2 size-4" />
        Importer fra CSV
      </Button>

      {showImport && (
        <PlayerImportPanel
          organizationId={organizationId}
          tournamentId={tournamentId}
          mode={{ kind: "individual", classes: classes.map((c) => ({ id: c.id, name: c.name })) }}
          onClose={() => setShowImport(false)}
          onImportComplete={() => {
            void onImportComplete()
          }}
        />
      )}

      {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 flex-wrap 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>
              <HcpInput
                playerName={p.player_name}
                value={p.handicap_index_snapshot}
                onCommit={(v) => onSetHandicap(p.id, v)}
              />
            </div>
            {classes.length > 0 && (
              <select
                value={p.class_id ?? ""}
                onChange={(e) => onSetClass(p.id, e.target.value || null)}
                aria-label={`Klasse for ${p.player_name}`}
                className="h-9 shrink-0 rounded-lg border border-border bg-card px-2 text-xs font-semibold text-foreground outline-none"
              >
                <option value="">Ingen klasse</option>
                {classes.map((c) => (
                  <option key={c.id} value={c.id}>
                    {c.name}
                  </option>
                ))}
              </select>
            )}
            {/* Statistikknivå (migrasjon 075, ADR-071) -- styrer hvor mye
                HoleGrid/HoleStatsSheet ber om ved scoring for DENNE
                deltakeren, tournament-bredt (ikke per runde). */}
            <select
              value={p.stat_level}
              onChange={(e) =>
                onSetStatLevel(p.id, e.target.value as "strokes_only" | "strokes_and_putts" | "full")
              }
              aria-label={`Statistikknivå for ${p.player_name}`}
              className="h-9 shrink-0 rounded-lg border border-border bg-card px-2 text-xs font-semibold text-foreground outline-none"
            >
              {Object.entries(STAT_LEVEL_LABELS).map(([value, label]) => (
                <option key={value} value={value}>
                  {label}
                </option>
              ))}
            </select>
            <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>
  )
}

// Retting av EN deltakers HCP direkte i turneringen (2026-08-18) --
// lokal utkast-tilstand, forpliktet KUN på blur (ikke per tastetrykk) --
// samme mønster som tallfelt ellers i appen. Tomt felt = fjern HCP-en
// (spilleren mister evt. adgang til netto-/Stableford-runder som krever
// den, samme validering som ved ny påmelding).
function HcpInput({
  playerName,
  value,
  onCommit,
}: {
  playerName: string
  value: number | null
  onCommit: (next: number | null) => Promise<void>
}) {
  const [draft, setDraft] = useState(value !== null ? String(value) : "")

  useEffect(() => {
    setDraft(value !== null ? String(value) : "")
  }, [value])

  function commit() {
    const trimmed = draft.trim()
    if (trimmed === "") {
      if (value !== null) void onCommit(null)
      return
    }
    const n = Number(trimmed)
    if (Number.isFinite(n) && n !== value) void onCommit(n)
  }

  return (
    <label className="flex items-center gap-1.5">
      <span className="text-xs font-semibold text-muted-foreground">Hcp</span>
      <input
        type="number"
        inputMode="decimal"
        step={0.1}
        min={-10}
        max={54}
        value={draft}
        onChange={(e) => setDraft(e.target.value)}
        onBlur={commit}
        aria-label={`HCP for ${playerName}`}
        placeholder="—"
        className="h-8 w-16 rounded-lg border border-border bg-background px-2 text-xs font-semibold text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
      />
    </label>
  )
}

// Splitter et fritekst-navn i for-/etternavn -- samme "første ord =
// fornavn, resten = etternavn"-heuristikk som migrasjon 052/074 sin
// backfill, brukt her til å FORHÅNDSUTFYLLE opprett-skjemaet fra
// søkefeltet (fortsatt redigerbart -- kun et fornuftig utgangspunkt).
function splitName(fullName: string): { first: string; last: string } {
  const trimmed = fullName.trim()
  const spaceIndex = trimmed.indexOf(" ")
  if (spaceIndex === -1) return { first: trimmed, last: "" }
  return { first: trimmed.slice(0, spaceIndex), last: trimmed.slice(spaceIndex + 1).trim() }
}

const MAX_VISIBLE_MATCHES = 8

function AddParticipantControl({
  pool,
  alreadyIn,
  onAddExisting,
  onAddNew,
}: {
  pool: ApiPlayer[]
  alreadyIn: Set<string>
  onAddExisting: (playerId: string) => Promise<void>
  onAddNew: (input: NewPlayerInput) => Promise<void>
}) {
  const [open, setOpen] = useState(false)
  const [query, setQuery] = useState("")
  const [creating, setCreating] = useState(false)
  const [firstName, setFirstName] = useState("")
  const [lastName, setLastName] = useState("")
  const [newHandicap, setNewHandicap] = useState("")
  const [newGender, setNewGender] = useState<"m" | "f" | "x" | "">("")
  const [showMoreDetails, setShowMoreDetails] = useState(false)
  const [birthDate, setBirthDate] = useState("")
  const [email, setEmail] = useState("")
  const [clubMemberNumber, setClubMemberNumber] = useState("")
  const [club, setClub] = useState("")
  const [country, setCountry] = useState("")
  const [paid, setPaid] = useState(false)
  const [comment, setComment] = useState("")

  const trimmed = query.trim()
  const available = useMemo(() => pool.filter((p) => !alreadyIn.has(p.id)), [pool, alreadyIn])
  const matches = useMemo(() => {
    if (!trimmed) return available
    const q = trimmed.toLowerCase()
    return available.filter((p) => p.display_name.toLowerCase().includes(q))
  }, [available, trimmed])
  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)
    setFirstName("")
    setLastName("")
    setNewHandicap("")
    setNewGender("")
    setShowMoreDetails(false)
    setBirthDate("")
    setEmail("")
    setClubMemberNumber("")
    setClub("")
    setCountry("")
    setPaid(false)
    setComment("")
  }

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

  function startCreating() {
    const { first, last } = splitName(trimmed)
    setFirstName(first)
    setLastName(last)
    setCreating(true)
  }

  async function handleCreate() {
    if (firstName.trim().length === 0) return
    const hcpValue = newHandicap.trim() === "" ? undefined : Number(newHandicap.replace(",", "."))
    const hcp = hcpValue !== undefined && !Number.isNaN(hcpValue) ? hcpValue : undefined
    await onAddNew({
      firstName,
      lastName,
      gender: newGender || undefined,
      handicapIndex: hcp,
      birthDate: birthDate || undefined,
      email: email || undefined,
      clubMemberNumber: clubMemberNumber || undefined,
      club: club || undefined,
      country: country || undefined,
      paid,
      comment: comment || 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 blant eksisterende, eller skriv navnet på en ny…"
          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>

      {!creating && (
        <>
          {matches.length === 0 && available.length === 0 && (
            <p className="px-1 text-sm text-muted-foreground">
              {pool.length === 0
                ? "Ingen eksisterende spillere i organisasjonen ennå -- opprett den første under."
                : "Alle eksisterende spillere er allerede lagt til -- opprett en ny under."}
            </p>
          )}
          {matches.length === 0 && available.length > 0 && trimmed.length > 0 && (
            <p className="px-1 text-sm text-muted-foreground">Ingen treff blant eksisterende spillere.</p>
          )}
          {matches.length > 0 && (
            <ul className="flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border">
              {matches.slice(0, MAX_VISIBLE_MATCHES).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>
          )}
          {matches.length > MAX_VISIBLE_MATCHES && (
            <p className="px-1 text-xs text-muted-foreground">
              +{matches.length - MAX_VISIBLE_MATCHES} flere -- fortsett å skrive for å begrense listen.
            </p>
          )}
        </>
      )}

      {showCreate && !creating && (
        <button
          type="button"
          onClick={startCreating}
          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-3 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-first-name" className="text-xs font-semibold">
                Fornavn
              </Label>
              <Input
                id="new-participant-first-name"
                value={firstName}
                onChange={(e) => setFirstName(e.target.value)}
                className="h-10 rounded-xl text-sm"
              />
            </div>
            <div className="flex flex-1 flex-col gap-1">
              <Label htmlFor="new-participant-last-name" className="text-xs font-semibold">
                Etternavn
              </Label>
              <Input
                id="new-participant-last-name"
                value={lastName}
                onChange={(e) => setLastName(e.target.value)}
                className="h-10 rounded-xl text-sm"
              />
            </div>
          </div>

          <div className="flex gap-2">
            <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 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>

          {!showMoreDetails ? (
            <button
              type="button"
              onClick={() => setShowMoreDetails(true)}
              className="min-h-9 text-left text-xs font-bold text-primary"
            >
              + Flere detaljer (fødselsdato, e-post, klubb, betalt m.m.)
            </button>
          ) : (
            <div className="flex flex-col gap-2 border-t border-border pt-3">
              <div className="flex gap-2">
                <div className="flex flex-1 flex-col gap-1">
                  <Label htmlFor="new-participant-birth-date" className="text-xs font-semibold">
                    Fødselsdato
                  </Label>
                  <Input
                    id="new-participant-birth-date"
                    type="date"
                    value={birthDate}
                    onChange={(e) => setBirthDate(e.target.value)}
                    className="h-10 rounded-xl text-sm"
                  />
                </div>
                <div className="flex flex-1 flex-col gap-1">
                  <Label htmlFor="new-participant-email" className="text-xs font-semibold">
                    E-postadresse
                  </Label>
                  <Input
                    id="new-participant-email"
                    type="email"
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                    className="h-10 rounded-xl text-sm"
                  />
                </div>
              </div>

              <div className="flex gap-2">
                <div className="flex flex-1 flex-col gap-1">
                  <Label htmlFor="new-participant-club" className="text-xs font-semibold">
                    Hjemmeklubb
                  </Label>
                  <Input
                    id="new-participant-club"
                    value={club}
                    onChange={(e) => setClub(e.target.value)}
                    className="h-10 rounded-xl text-sm"
                  />
                </div>
                <div className="flex flex-1 flex-col gap-1">
                  <Label htmlFor="new-participant-club-member-number" className="text-xs font-semibold">
                    Medlemsnummer i hjemmeklubb
                  </Label>
                  <Input
                    id="new-participant-club-member-number"
                    value={clubMemberNumber}
                    onChange={(e) => setClubMemberNumber(e.target.value)}
                    className="h-10 rounded-xl text-sm"
                  />
                </div>
              </div>

              <div className="flex flex-col gap-1">
                <Label htmlFor="new-participant-country" className="text-xs font-semibold">
                  Land
                </Label>
                <Input
                  id="new-participant-country"
                  value={country}
                  onChange={(e) => setCountry(e.target.value)}
                  className="h-10 w-full rounded-xl text-sm sm:w-1/2"
                />
              </div>

              <label className="flex min-h-9 items-center gap-2 text-sm font-medium text-foreground">
                <input
                  type="checkbox"
                  checked={paid}
                  onChange={(e) => setPaid(e.target.checked)}
                  className="size-5 shrink-0 accent-primary"
                />
                Betalt
              </label>

              <div className="flex flex-col gap-1">
                <Label htmlFor="new-participant-comment" className="text-xs font-semibold">
                  Kommentar
                </Label>
                <textarea
                  id="new-participant-comment"
                  value={comment}
                  onChange={(e) => setComment(e.target.value)}
                  rows={2}
                  className="w-full resize-y rounded-xl border border-border bg-card px-3 py-2 text-sm text-foreground outline-none"
                />
              </div>
            </div>
          )}

          <Button
            type="button"
            onClick={handleCreate}
            disabled={firstName.trim().length === 0}
            className="h-10 rounded-xl text-sm font-bold"
          >
            Opprett og legg til «{`${firstName} ${lastName}`.trim()}»
          </Button>
        </div>
      )}
    </div>
  )
}

function RoundsCard({
  base,
  organizationId,
  rounds,
  courses,
  participants,
  classes,
  scoringMethod,
  onAddRound,
  onDeleteRound,
  onAddCourse,
  onCourseImported,
  onError,
}: {
  base: string
  organizationId: string
  rounds: ApiRound[]
  courses: ApiCourse[]
  participants: ApiParticipant[]
  classes: ApiTournamentClass[]
  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>
  onCourseImported: (course: ApiCourse) => void
  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}
            classes={classes}
            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
          organizationId={organizationId}
          courses={courses}
          onCourseImported={onCourseImported}
          onCreate={async (name, courseId, holeConfig) => {
            await onAddRound(name, courseId, holeConfig)
            setCreating(false)
          }}
          onCreateCourse={onAddCourse}
          onCancel={() => setCreating(false)}
        />
      )}
    </section>
  )
}

function NewRoundForm({
  organizationId,
  courses,
  onCreate,
  onCreateCourse,
  onCourseImported,
  onCancel,
}: {
  organizationId: string
  courses: ApiCourse[]
  onCreate: (name: string, courseId: string, holeConfig: ApiRound["hole_config"]) => Promise<void>
  onCreateCourse: (name: string) => Promise<ApiCourse | null>
  onCourseImported: (course: ApiCourse) => void
  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 [officialSearchOpen, setOfficialSearchOpen] = useState(false)
  const [templatePickerOpen, setTemplatePickerOpen] = 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>
        {!officialSearchOpen && !templatePickerOpen ? (
          <div className="flex flex-wrap gap-x-4 gap-y-1">
            <button
              type="button"
              onClick={() => setOfficialSearchOpen(true)}
              className="self-start text-sm font-semibold text-primary underline-offset-2 hover:underline"
            >
              Hent bane fra teeoff i stedet
            </button>
            <button
              type="button"
              onClick={() => setTemplatePickerOpen(true)}
              className="self-start text-sm font-semibold text-primary underline-offset-2 hover:underline"
            >
              Opprett bane med hull/utslag i stedet
            </button>
          </div>
        ) : officialSearchOpen ? (
          <OfficialCourseSearch
            organizationId={organizationId}
            onClose={() => setOfficialSearchOpen(false)}
            onImported={(course) => {
              onCourseImported(course)
              setCourseId(course.id)
              setAddingCourse(false)
              setOfficialSearchOpen(false)
            }}
          />
        ) : (
          <CourseTemplatePicker
            organizationId={organizationId}
            onCreated={(created) => {
              onCourseImported({ id: created.id, name: created.name, source: "custom" })
              setCourseId(created.id)
              setAddingCourse(false)
              setTemplatePickerOpen(false)
            }}
            onCancel={() => setTemplatePickerOpen(false)}
          />
        )}
      </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,
  classes,
  scoringMethod,
  onDelete,
  onError,
}: {
  base: string
  organizationId: string
  round: ApiRound
  participants: ApiParticipant[]
  classes: ApiTournamentClass[]
  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)
  const [groupsOpen, setGroupsOpen] = 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}
                  classes={classes}
                  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>
              )}
              {roundParticipants.length > 0 && (
                <Button
                  type="button"
                  variant="secondary"
                  onClick={() => setGroupsOpen(true)}
                  className="h-10 w-full rounded-xl text-sm font-bold"
                >
                  <Clock aria-hidden="true" className="size-4" />
                  Utslagsgrupper -- hvem spiller med hvem, klokka når
                </Button>
              )}
            </>
          )}
        </div>
      )}

      {groupsOpen && (
        <RoundGroupsPanel
          base={base}
          roundId={round.id}
          roundLabel={round.name || `Runde ${round.sequence}`}
          onClose={() => setGroupsOpen(false)}
        />
      )}
    </div>
  )
}

function AssignRoundParticipantControl({
  candidates,
  tees,
  classes,
  onAssign,
}: {
  candidates: ApiParticipant[]
  tees: ApiTee[]
  classes: ApiTournamentClass[]
  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])

  // Konkurranseklasser (2026-08-03) -- forhåndsvelg deltakerens klasses
  // standardutslag, kun hvis det utslaget faktisk finnes på DENNE rundens
  // bane (klassens standardutslag kan tilhøre en annen bane). Fritt
  // overstyrbart under.
  useEffect(() => {
    if (!participantId) return
    const candidate = candidates.find((c) => c.id === participantId)
    const cls = candidate?.class_id ? classes.find((c) => c.id === candidate.class_id) : null
    const defaultTee = cls?.default_tee_id ? tees.find((t) => t.id === cls.default_tee_id) : null
    if (defaultTee) setTeeId(defaultTee.id)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [participantId])

  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)
  }

  // Full statistikkdybde (migrasjon 075, ADR-071) -- samme endepunkt som
  // updateHole over, men med hele HolePatch-kroppen fra HoleStatsSheet i
  // stedet for kun gross_strokes. Kaster videre ved feil (409 STALE_VERSION
  // inkludert) -- HoleStatsSheet fanger og viser en generisk feilmelding,
  // beholder brukerens utfylte skjema i stedet for å lukke sheet-et blindt.
  async function updateHoleFull(holeNumber: number, patch: HolePatch) {
    const res = await fetch(`${base}/rounds/${roundId}/participants/${participantId}/holes/${holeNumber}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify(patch),
    })
    if (!res.ok) {
      throw new Error("Klarte ikke å lagre.")
    }
    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
              base={base}
              roundId={roundId}
              participantId={participantId}
              holes={holes}
              holeConfig={round.hole_config}
              statLevel={roundParticipants.find((rp) => rp.id === participantId)?.stat_level ?? "strokes_only"}
              playerName={roundParticipants.find((rp) => rp.id === participantId)?.player_name ?? ""}
              strokesReceived={(n) => holes.find((h) => h.hole_number === n)?.strokes_received ?? null}
              ownBagClubs={[]}
              onUpdate={updateHole}
              onUpdateFull={updateHoleFull}
            />
          )}

          {/* 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} />
          )}

          {/* GPS-flaggplanting + runde 2+ (2026-08-14) -- for DEN VALGTE
              deltakeren (samme selv-only autorisasjon som selve
              scoreføringen over, ingen vits å vise for andre enn den man
              faktisk fører for). */}
          {round && scoringMethod === "flag" && roundParticipants.find((rp) => rp.id === participantId) && (
            <FlagPlantPanel
              base={base}
              round={round}
              participant={roundParticipants.find((rp) => rp.id === participantId)!}
              refreshTick={refreshTick}
              onChanged={() => setRefreshTick((n) => n + 1)}
            />
          )}

          {/* Kartoversikt over flagg (migrasjon 071, "Del B", ADR-067) --
              kun for org-medlem/deltaker (ingen offentlig tilskuer-gren
              for individuelle turneringer, se ADR-067). */}
          {round && scoringMethod === "flag" && (
            <FlagMapSectionOrg 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>
  )
}

// --- GPS-flaggplanting + runde 2+ (2026-08-14) -------------------------------
// V0-eksport (zip 11), samme komponent som round-detail.tsx sin
// FlagPlantSheet -- egen kopi her (fil-lokalt konvensjon, se
// FlagPlantSheet i round-detail.tsx for samme innmat/kommentar).

function FlagPlantSheetOrg({
  open,
  onClose,
  expectedHoleNumber,
  expectedLap,
  onConfirm,
}: {
  open: boolean
  onClose: () => void
  expectedHoleNumber: number
  expectedLap: number
  onConfirm: (result: {
    lat: number
    lng: number
    onGreen: boolean
    distanceToPinCm: number | null
  }) => Promise<{ ok: true } | { ok: false; message: string }>
}) {
  const [step, setStep] = useState<"gps" | "holedOut" | "green" | "done">("gps")
  const [geoStatus, setGeoStatus] = useState<"loading" | "error">("loading")
  const [position, setPosition] = useState<{ lat: number; lng: number } | null>(null)

  // Step 2 "Ja" reveals a dead-end guidance card (no submission).
  const [holedOutYes, setHoledOutYes] = useState(false)

  // Step 3 answer + distance inputs (kept as strings for clean input UX).
  const [onGreen, setOnGreen] = useState<boolean | null>(null)
  const [meters, setMeters] = useState("")
  const [centimeters, setCentimeters] = useState("")

  const [submitting, setSubmitting] = useState(false)
  const [serverError, setServerError] = useState<string | null>(null)
  const [confirmedSummary, setConfirmedSummary] = useState<{
    onGreen: boolean
    distanceToPinCm: number | null
  } | null>(null)

  // Reset everything each time the sheet is (re)opened.
  useEffect(() => {
    if (!open) return
    setStep("gps")
    setGeoStatus("loading")
    setPosition(null)
    setHoledOutYes(false)
    setOnGreen(null)
    setMeters("")
    setCentimeters("")
    setSubmitting(false)
    setServerError(null)
    setConfirmedSummary(null)
  }, [open])

  // Step 1 — automatic GPS acquisition. Runs on entering the gps step.
  const geoRequested = useRef(false)
  useEffect(() => {
    if (!open || step !== "gps") {
      geoRequested.current = false
      return
    }
    if (geoRequested.current) return
    geoRequested.current = true

    if (typeof navigator === "undefined" || !navigator.geolocation) {
      setGeoStatus("error")
      return
    }
    setGeoStatus("loading")
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        setPosition({ lat: pos.coords.latitude, lng: pos.coords.longitude })
        setStep("holedOut")
      },
      () => setGeoStatus("error"),
      { enableHighAccuracy: true, timeout: 10000 },
    )
  }, [open, step])

  function retryGeo() {
    geoRequested.current = false
    setGeoStatus("loading")
    if (typeof navigator === "undefined" || !navigator.geolocation) {
      setGeoStatus("error")
      return
    }
    geoRequested.current = true
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        setPosition({ lat: pos.coords.latitude, lng: pos.coords.longitude })
        setStep("holedOut")
      },
      () => setGeoStatus("error"),
      { enableHighAccuracy: true, timeout: 10000 },
    )
  }

  if (!open) return null

  // ---- validation for the on-green distance --------------------------------
  const metersNum = meters === "" ? null : Number(meters)
  const cmNum = centimeters === "" ? null : Number(centimeters)
  const metersValid = metersNum !== null && Number.isInteger(metersNum) && metersNum >= 0
  const cmValid = cmNum !== null && Number.isInteger(cmNum) && cmNum >= 0 && cmNum <= 99
  const greenInputValid = metersValid && cmValid

  const canConfirm = onGreen === false || (onGreen === true && greenInputValid)

  async function handleConfirm() {
    if (!position || onGreen === null || submitting) return
    const distanceToPinCm =
      onGreen && metersNum !== null && cmNum !== null ? metersNum * 100 + cmNum : null

    setSubmitting(true)
    setServerError(null)
    try {
      const res = await onConfirm({
        lat: position.lat,
        lng: position.lng,
        onGreen,
        distanceToPinCm,
      })
      if (res.ok) {
        setConfirmedSummary({ onGreen, distanceToPinCm })
        setStep("done")
      } else {
        setServerError(res.message)
      }
    } catch {
      setServerError("Noe gikk galt. Sjekk nettforbindelsen og prøv igjen.")
    } finally {
      setSubmitting(false)
    }
  }

  function goBack() {
    setServerError(null)
    if (step === "green") {
      setOnGreen(null)
      setMeters("")
      setCentimeters("")
      setStep("holedOut")
    }
  }

  const showBack = step === "green"

  return (
    <div
      className="fixed inset-0 z-50 flex flex-col bg-background text-foreground"
      role="dialog"
      aria-modal="true"
      aria-label={`Plant flagget, hull ${expectedHoleNumber}`}
    >
      {/* Header */}
      <header className="flex shrink-0 items-start justify-between gap-3 border-b border-border px-4 pb-4 pt-[max(1rem,env(safe-area-inset-top))]">
        <div className="flex items-start gap-2">
          {showBack ? (
            <button
              type="button"
              onClick={goBack}
              className="inline-flex min-h-11 items-center gap-1 rounded-xl px-2 text-base font-semibold text-muted-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
            >
              <ChevronLeft className="size-5 shrink-0" aria-hidden="true" />
              Tilbake
            </button>
          ) : null}
          <div className="flex items-start gap-2 pt-1.5">
            <span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-brand-orange/15 text-brand-orange">
              <Flag className="size-5" aria-hidden="true" />
            </span>
            <div>
              <h2 className="text-lg font-bold leading-tight text-balance">Plant flagget</h2>
              <p className="mt-0.5 text-sm text-muted-foreground">
                {`Hull ${expectedHoleNumber} · runde ${expectedLap}`}
              </p>
            </div>
          </div>
        </div>

        <button
          type="button"
          onClick={onClose}
          className="inline-flex min-h-11 items-center gap-1 rounded-xl px-3 text-base font-semibold text-muted-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
        >
          <X className="size-5 shrink-0" aria-hidden="true" />
          Lukk
        </button>
      </header>

      {/* Main */}
      <main className="flex-1 overflow-y-auto p-4">
        {/* Step 1 — GPS */}
        {step === "gps" ? (
          <div className="mx-auto flex max-w-md flex-col items-center gap-4 pt-12 text-center">
            {geoStatus === "loading" ? (
              <>
                <div className="size-10 animate-spin rounded-full border-4 border-primary/20 border-t-primary" />
                <p className="text-lg font-semibold">Finner posisjonen din …</p>
                <p className="text-base text-muted-foreground text-pretty">
                  Stå der du gikk tom for slag mens vi henter GPS-posisjonen.
                </p>
              </>
            ) : (
              <div className="flex w-full flex-col gap-3 rounded-2xl border border-border bg-card p-5 text-left">
                <p role="alert" className="text-base text-destructive text-pretty">
                  Fant ikke posisjonen din. Sjekk at posisjonstjenester er på for
                  TeeCup, og prøv igjen.
                </p>
                <button
                  type="button"
                  onClick={retryGeo}
                  className="inline-flex min-h-12 items-center justify-center gap-2 rounded-xl bg-primary px-4 text-base font-bold text-primary-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                >
                  <Crosshair className="size-5 shrink-0" aria-hidden="true" />
                  Prøv igjen
                </button>
              </div>
            )}
          </div>
        ) : null}

        {/* Step 2 — Holed out on this hole? */}
        {step === "holedOut" ? (
          <div className="mx-auto flex max-w-md flex-col gap-5 pt-2">
            <h3 className="text-2xl font-bold text-balance">
              {`Hullet du ut på hull ${expectedHoleNumber}?`}
            </h3>
            <p className="text-base text-muted-foreground text-pretty">
              Er hullet fullført, må scoren føres inn før du kan plante flagget.
            </p>

            <div className="grid grid-cols-2 gap-3">
              <button
                type="button"
                onClick={() => setHoledOutYes(true)}
                aria-pressed={holedOutYes}
                className={cn(
                  "inline-flex min-h-14 items-center justify-center rounded-xl border-2 px-6 text-lg font-bold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
                  holedOutYes
                    ? "border-brand-orange bg-brand-orange text-brand-orange-foreground"
                    : "border-border bg-card text-foreground hover:bg-muted",
                )}
              >
                Ja
              </button>
              <button
                type="button"
                onClick={() => {
                  setHoledOutYes(false)
                  setStep("green")
                }}
                className="inline-flex min-h-14 items-center justify-center rounded-xl border-2 border-border bg-card px-6 text-lg font-bold text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                Nei
              </button>
            </div>

            {/* "Ja" is a friendly dead end — guidance + Lukk, no submission. */}
            {holedOutYes ? (
              <div className="flex flex-col gap-4 rounded-2xl border border-brand-orange/40 bg-brand-orange/5 p-4">
                <p role="alert" className="text-base text-foreground text-pretty">
                  {`Da må du først føre inn scoren for hull ${expectedHoleNumber}. Trykk «Plant flagget» på nytt når du er ved neste utslag.`}
                </p>
                <button
                  type="button"
                  onClick={onClose}
                  className="inline-flex min-h-14 items-center justify-center rounded-xl bg-primary px-6 text-lg font-bold text-primary-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                >
                  Lukk
                </button>
              </div>
            ) : null}
          </div>
        ) : null}

        {/* Step 3 — On the green? + distance */}
        {step === "green" ? (
          <div className="mx-auto flex max-w-md flex-col gap-5 pt-2">
            <h3 className="text-2xl font-bold text-balance">Landet du på green?</h3>

            <div className="grid grid-cols-2 gap-3">
              <button
                type="button"
                onClick={() => setOnGreen(true)}
                aria-pressed={onGreen === true}
                className={cn(
                  "inline-flex min-h-14 items-center justify-center rounded-xl border-2 px-6 text-lg font-bold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
                  onGreen === true
                    ? "border-primary bg-primary text-primary-foreground"
                    : "border-border bg-card text-foreground hover:bg-muted",
                )}
              >
                Ja
              </button>
              <button
                type="button"
                onClick={() => {
                  setOnGreen(false)
                  setMeters("")
                  setCentimeters("")
                }}
                aria-pressed={onGreen === false}
                className={cn(
                  "inline-flex min-h-14 items-center justify-center rounded-xl border-2 px-6 text-lg font-bold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
                  onGreen === false
                    ? "border-primary bg-primary text-primary-foreground"
                    : "border-border bg-card text-foreground hover:bg-muted",
                )}
              >
                Nei
              </button>
            </div>

            {onGreen === true ? (
              <div className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4">
                <p className="text-sm text-muted-foreground text-pretty">
                  Dette avgjør rekkefølgen mot andre spillere som gikk tom på
                  samme hull.
                </p>
                <div className="grid grid-cols-2 gap-3">
                  <div className="flex flex-col gap-1.5">
                    <label htmlFor="fp-org-meters" className="text-base font-semibold">
                      Meter
                    </label>
                    <input
                      id="fp-org-meters"
                      inputMode="numeric"
                      pattern="[0-9]*"
                      value={meters}
                      onChange={(e) => setMeters(e.target.value.replace(/[^0-9]/g, ""))}
                      aria-invalid={meters !== "" && !metersValid}
                      className="h-14 rounded-xl border border-input bg-background px-4 text-lg font-semibold tabular-nums text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                      placeholder="0"
                    />
                  </div>
                  <div className="flex flex-col gap-1.5">
                    <label htmlFor="fp-org-cm" className="text-base font-semibold">
                      Centimeter
                    </label>
                    <input
                      id="fp-org-cm"
                      inputMode="numeric"
                      pattern="[0-9]*"
                      value={centimeters}
                      onChange={(e) => setCentimeters(e.target.value.replace(/[^0-9]/g, ""))}
                      aria-invalid={centimeters !== "" && !cmValid}
                      className="h-14 rounded-xl border border-input bg-background px-4 text-lg font-semibold tabular-nums text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                      placeholder="0"
                    />
                  </div>
                </div>
                {centimeters !== "" && !cmValid ? (
                  <p role="alert" className="text-sm font-medium text-destructive">
                    Centimeter må være mellom 0 og 99.
                  </p>
                ) : null}
              </div>
            ) : null}

            {serverError ? (
              <p
                role="alert"
                className="rounded-xl border border-destructive/40 bg-destructive/10 p-3 text-base font-medium text-destructive text-pretty"
              >
                {serverError}
              </p>
            ) : null}
          </div>
        ) : null}

        {/* Step 4 — Success */}
        {step === "done" && confirmedSummary ? (
          <div className="mx-auto flex max-w-md flex-col items-center gap-4 pt-12 text-center">
            <span className="flex size-20 items-center justify-center rounded-full bg-primary/10 text-primary">
              <Check className="size-11" strokeWidth={2.5} aria-hidden="true" />
            </span>
            <h3 className="text-2xl font-bold">Flagget er plantet!</h3>
            <div className="flex flex-col items-center gap-1">
              <span className="inline-flex items-center gap-1.5 rounded-full bg-muted px-3 py-1 text-base font-semibold">
                <MapPin className="size-4 shrink-0" aria-hidden="true" />
                {`Hull ${expectedHoleNumber}`}
              </span>
              {confirmedSummary.onGreen && confirmedSummary.distanceToPinCm !== null ? (
                <span className="text-base text-muted-foreground tabular-nums">
                  {`${Math.floor(confirmedSummary.distanceToPinCm / 100)} m ${
                    confirmedSummary.distanceToPinCm % 100
                  } cm fra hullet`}
                </span>
              ) : null}
            </div>
          </div>
        ) : null}
      </main>

      {/* Footer — contextual primary action */}
      {step === "green" ? (
        <footer className="shrink-0 border-t border-border bg-background p-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
          <button
            type="button"
            disabled={!canConfirm || submitting}
            onClick={handleConfirm}
            className="inline-flex min-h-14 w-full items-center justify-center gap-2 rounded-xl bg-primary px-6 text-lg font-bold text-primary-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
          >
            {submitting ? (
              <>
                <span className="size-5 animate-spin rounded-full border-2 border-primary-foreground/40 border-t-primary-foreground" />
                Sender …
              </>
            ) : (
              <>
                <Flag className="size-5 shrink-0" aria-hidden="true" />
                Bekreft plassering
              </>
            )}
          </button>
        </footer>
      ) : null}

      {step === "done" ? (
        <footer className="shrink-0 border-t border-border bg-background p-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
          <button
            type="button"
            onClick={onClose}
            className="inline-flex min-h-14 w-full items-center justify-center rounded-xl bg-primary px-6 text-lg font-bold text-primary-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          >
            Ferdig
          </button>
        </footer>
      ) : null}
    </div>
  )
}

function FlagPlantPanel({
  base,
  round,
  participant,
  refreshTick,
  onChanged,
}: {
  base: string
  round: ApiRound
  participant: ApiRoundParticipant
  refreshTick: number
  onChanged: () => void
}) {
  const playOrder = useMemo(() => playedHoleNumbersOrg(round.hole_config), [round.hole_config])
  const [holes, setHoles] = useState<ApiHole[] | null>(null)
  const [plant, setPlant] = useState<ApiFlagPlant | null>(null)
  const [overflow, setOverflow] = useState<ApiFlagOverflowHole[]>([])
  const [sheetOpen, setSheetOpen] = useState(false)
  const [draft, setDraft] = useState<Record<number, string>>({})
  const [overflowError, setOverflowError] = useState<string | null>(null)
  const [busy, setBusy] = useState(false)

  useEffect(() => {
    let cancelled = false
    Promise.all([
      getJson<ApiHole[]>(`${base}/rounds/${round.id}/participants/${participant.id}/holes`),
      getJson<ApiFlagPlant | null>(`${base}/rounds/${round.id}/participants/${participant.id}/flag-plant`),
      getJson<ApiFlagOverflowHole[]>(`${base}/rounds/${round.id}/participants/${participant.id}/flag-overflow`),
    ]).then(([h, p, o]) => {
      if (cancelled) return
      setHoles(h)
      setPlant(p)
      setOverflow(o ?? [])
    })
    return () => {
      cancelled = true
    }
  }, [base, round.id, participant.id, refreshTick])

  const currentPosition = computeFlagCurrentPositionOrg(playOrder, holes, overflow)

  async function handleUnplant() {
    setBusy(true)
    await fetch(`${base}/rounds/${round.id}/participants/${participant.id}/flag-plant`, {
      method: "DELETE",
      credentials: "include",
    })
    setBusy(false)
    setPlant(null)
    onChanged()
  }

  async function submitOverflowHole(holeNumber: number) {
    const raw = draft[holeNumber]
    const score = parseInt(raw ?? "", 10)
    if (!raw || Number.isNaN(score) || score <= 0) return
    setOverflowError(null)
    const res = await fetch(
      `${base}/rounds/${round.id}/participants/${participant.id}/flag-overflow/${currentPosition.lap}/holes/${holeNumber}`,
      {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ gross_strokes: score }),
      },
    )
    if (!res.ok) {
      const body = await res.json().catch(() => null)
      setOverflowError(body?.detail?.message ?? "Klarte ikke å registrere score.")
      return
    }
    const updated: ApiFlagOverflowHole = await res.json()
    setOverflow((prev) => [...prev.filter((h) => !(h.lap === updated.lap && h.hole_number === updated.hole_number)), updated])
    setDraft((prev) => ({ ...prev, [holeNumber]: "" }))
    onChanged()
  }

  if (!holes) return null
  const byOverflowHole = new Map(overflow.filter((h) => h.lap === currentPosition.lap).map((h) => [h.hole_number, h]))

  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">GPS-flaggplanting -- {participant.player_name}</span>
      <div className="flex flex-wrap items-center gap-2">
        {plant ? (
          <>
            <span className="inline-flex items-center gap-1.5 rounded-full bg-primary/10 px-2.5 py-1.5 text-sm font-bold text-primary">
              <Flag aria-hidden="true" className="size-4" />
              Flagg plantet · hull {plant.hole_number}
              {plant.lap > 1 ? ` (runde ${plant.lap})` : ""}
              {plant.on_green && plant.distance_to_pin_cm !== null
                ? ` · ${Math.floor(plant.distance_to_pin_cm / 100)}m ${plant.distance_to_pin_cm % 100}cm fra hullet`
                : ""}
            </span>
            <button
              type="button"
              disabled={busy}
              onClick={handleUnplant}
              className="flex min-h-11 items-center justify-center rounded-xl px-2 text-sm font-semibold text-muted-foreground underline underline-offset-2 hover:text-foreground disabled:opacity-60"
            >
              Angre
            </button>
          </>
        ) : (
          <button
            type="button"
            onClick={() => setSheetOpen(true)}
            className="flex min-h-11 items-center justify-center gap-1.5 rounded-xl border border-border bg-background px-3 text-sm font-bold text-foreground transition-colors hover:bg-accent/50"
          >
            <Flag aria-hidden="true" className="size-4" />
            Plant flagget
          </button>
        )}
      </div>

      {currentPosition.lap >= 2 && (
        <div className="flex flex-col gap-2 border-t border-border pt-3">
          <span className="text-sm font-bold text-foreground">Runde {currentPosition.lap} -- før inn slag per hull</span>
          <div className="flex flex-wrap gap-2">
            {playOrder.map((h) => {
              const existing = byOverflowHole.get(h)
              return (
                <div key={h} className="flex items-center gap-1.5">
                  <span className="text-xs font-semibold text-muted-foreground">Hull {h}</span>
                  {existing?.played ? (
                    <span className="flex size-9 items-center justify-center rounded-lg bg-primary/10 text-sm font-bold text-primary tabular-nums">
                      {existing.gross_strokes}
                    </span>
                  ) : (
                    <Input
                      type="number"
                      inputMode="numeric"
                      min={1}
                      max={20}
                      value={draft[h] ?? ""}
                      onChange={(e) => setDraft((prev) => ({ ...prev, [h]: e.target.value }))}
                      onBlur={() => submitOverflowHole(h)}
                      className="h-9 w-14 text-center"
                    />
                  )}
                </div>
              )
            })}
          </div>
          {overflowError && <p role="alert" className="text-sm font-medium text-destructive">{overflowError}</p>}
        </div>
      )}

      <FlagPlantSheetOrg
        open={sheetOpen}
        onClose={() => setSheetOpen(false)}
        expectedHoleNumber={currentPosition.holeNumber}
        expectedLap={currentPosition.lap}
        onConfirm={async (result) => {
          const res = await fetch(`${base}/rounds/${round.id}/participants/${participant.id}/flag-plant`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            credentials: "include",
            body: JSON.stringify({
              lat: result.lat, lng: result.lng,
              hole_number: currentPosition.holeNumber, lap: currentPosition.lap,
              on_green: result.onGreen, distance_to_pin_cm: result.distanceToPinCm,
            }),
          })
          if (!res.ok) {
            const body = await res.json().catch(() => null)
            return { ok: false, message: body?.detail?.message ?? "Klarte ikke å plante flagget." }
          }
          const updated: ApiFlagPlant = await res.json()
          setPlant(updated)
          onChanged()
          return { ok: true }
        }}
      />
    </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>
  )
}

// --- Flaggturnering: kartoversikt (migrasjon 071, "Del B", ADR-067) --------
// Samme "egen fetch + refreshTick"-mønster som FlagRoundPanel over. Ingen
// offentlig tilskuer-gren her (org-medlem/deltaker-only, se ADR-067 --
// individuelle turneringer har ingen offentlig spectator-side ennå).

const FlagMapOverviewLazy = dynamic(() => import("@/components/flag-map-overview"), { ssr: false })

type ApiFlagMapOrg = { visible_to_all: boolean; flags: FlagMapEntry[] }

function FlagMapSectionOrg({
  base,
  round,
  refreshTick,
}: {
  base: string
  round: ApiRound
  refreshTick: number
}) {
  const [data, setData] = useState<ApiFlagMapOrg | null>(null)
  const [expanded, setExpanded] = useState(false)

  useEffect(() => {
    let cancelled = false
    getJson<{ visible_to_all: boolean; flags: Array<Record<string, unknown>> }>(
      `${base}/rounds/${round.id}/flag-map`,
    ).then((json) => {
      if (cancelled || !json) return
      setData({
        visible_to_all: json.visible_to_all,
        flags: json.flags.map((f) => ({
          participantId: f.participant_id as string,
          displayName: f.display_name as string,
          lap: f.lap as number,
          holeNumber: f.hole_number as number,
          lat: f.lat as number,
          lng: f.lng as number,
          onGreen: f.on_green as boolean,
          distanceToPinCm: f.distance_to_pin_cm as number | null,
        })),
      })
    })
    return () => {
      cancelled = true
    }
  }, [base, round.id, refreshTick])

  if (!data) return null

  return (
    <div className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8">
      <button
        type="button"
        onClick={() => setExpanded((v) => !v)}
        className="flex min-h-11 items-center justify-between gap-2 text-left"
        aria-expanded={expanded}
      >
        <span className="text-sm font-bold text-foreground">Kartoversikt over flagg</span>
        <span className="text-sm font-semibold text-primary">{expanded ? "Skjul" : "Vis kart"}</span>
      </button>
      {expanded && (
        <div className="h-80 overflow-hidden rounded-xl border border-border">
          <FlagMapOverviewLazy flags={data.flags} visibleToAll={data.visible_to_all} />
        </div>
      )}
    </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"
  }
}

// --- Full statistikkregistrering per hull (migrasjon 075, ADR-071) --------
// Enkelt-skjerm (IKKE en flerstegs-veiviser som ScoringWizard i round-
// detail.tsx -- HoleGrid åpner ETT hull om gangen for ÉN allerede valgt
// deltaker, ikke en hel spillerekkefølge, så et enkelt skjermbilde med
// alle aktuelle felt gruppert er tilstrekkelig her). Viser kun feltene
// deltakerens stat_level faktisk ber om.

type HolePatch = {
  gross_strokes: number
  putts?: number | null
  club_off_tee?: string | null
  tee_shot_result?: string | null
  approach_result?: string | null
  chip_count?: number | null
  bunker_shot_count?: number | null
  penalty_strokes?: number | null
  first_putt_distance_bucket?: string | null
  anyway_strokes?: number | null
  expected_version?: number | null
}

// ADR-078 (2026-08-16): HoleStatsSheet bygget om fra ÉN lang scrollbar
// skjerm til en liten intern steg-flyt -- samme visuelle språk (fulskjerm,
// header/footer) og samme stegrekkefølge som ScoringWizard (frittstående
// runder), etter eksplisitt ønske om at "opplevelsen for brukeren skal
// være lik uansett". IKKE en gjenbruk av ScoringWizard-koden -- lagringen
// er bevisst UENDRET (ÉN batched save() ved siste steg, ikke PATCH per
// felt slik runde-siden gjør) for å unngå å røre en fungerende, annerledes
// arkitektur mekanikk som ikke var en del av forespørselen. Se
// wizardStepsFor()/WizardStep i round-detail.tsx for det speilede
// mønsteret på runde-siden.
// ADR-079 (2026-08-16): "overview" er et nytt FØRSTE steg -- personlig
// hull-historikk flyttet hit ut av selve "strokes"-steget, samme
// begrunnelse som round-siden (se PlayerHoleCards i round-detail.tsx).
// Turnering-siden har ingen ekstern "skjerm før" å legge den kompakte
// oppsummeringen i slik runde-siden har (HoleGrid er et tett rutenett,
// ikke en kortliste) -- blir derfor et eget steg i stedet. For å unngå å
// legge en ekstra obligatorisk trykk-runde til for HVER hull-registrering
// (18 hull × N spillere), hopper steget automatisk videre av seg selv når
// spilleren ikke har noen historikk å vise her (samme selv-skjulende
// oppførsel som HoleHistoryPanel selv har) -- kun synlig når det faktisk
// er noe å vise.
type HoleHistorySummary = {
  times_played: number
  times_personal: number
  times_tournament: number
  average_score: number
  average_score_vs_par: number
  gir_percent: number | null
  average_putts: number | null
}

type SheetStep = "overview" | "strokes" | "putts" | "puttDistance" | "direction" | "holeDetails"

function sheetStepsFor(statLevel: "strokes_and_putts" | "full"): SheetStep[] {
  if (statLevel === "full") return ["overview", "strokes", "putts", "puttDistance", "direction", "holeDetails"]
  return ["overview", "strokes", "putts", "puttDistance"]
}

function HoleStatsSheet({
  hole,
  playerName,
  statLevel,
  strokesReceived,
  ownBagClubs,
  historyUrl,
  targetPointsBaseUrl,
  onSave,
  onClose,
}: {
  hole: ApiHole
  playerName: string
  statLevel: "strokes_and_putts" | "full"
  strokesReceived: number | null
  ownBagClubs: string[]
  historyUrl: string
  targetPointsBaseUrl: string
  onSave: (patch: HolePatch) => Promise<void>
  onClose: () => void
}) {
  const [strokes, setStrokes] = useState<number | null>(hole.gross_strokes)
  const [putts, setPutts] = useState<number | null>(hole.putts)
  const [puttBucket, setPuttBucket] = useState<string | null>(hole.first_putt_distance_bucket)
  const [club, setClub] = useState<string | null>(hole.club_off_tee)
  const [teeShot, setTeeShot] = useState<string | null>(hole.tee_shot_result)
  const [approach, setApproach] = useState<string | null>(hole.approach_result)
  const [chip, setChip] = useState(hole.chip_count ?? 0)
  const [bunker, setBunker] = useState(hole.bunker_shot_count ?? 0)
  const [penalty, setPenalty] = useState(hole.penalty_strokes ?? 0)
  const [anywayStrokes, setAnywayStrokes] = useState(hole.anyway_strokes ?? 0)
  const [saving, setSaving] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const steps = sheetStepsFor(statLevel)
  const [stepIndex, setStepIndex] = useState(0)
  const step = steps[Math.min(stepIndex, steps.length - 1)]
  const isFirstStep = stepIndex === 0
  const isLastStep = stepIndex === steps.length - 1
  const canProceed = step === "strokes" ? strokes !== null : true

  // "overview"-steget (ADR-079): hentet én gang ved åpning, ikke re-hentet
  // per steg-bytte. undefined = laster ennå, null = ingen historikk.
  const [historySummary, setHistorySummary] = useState<HoleHistorySummary | null | undefined>(undefined)
  const [showFullHistory, setShowFullHistory] = useState(false)
  useEffect(() => {
    let cancelled = false
    fetch(historyUrl, { credentials: "include" })
      .then((res) => (res.ok ? res.json() : null))
      .then((data) => {
        if (!cancelled) setHistorySummary(data)
      })
      .catch(() => {
        if (!cancelled) setHistorySummary(null)
      })
    return () => {
      cancelled = true
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])
  // Hopp automatisk forbi "overview" når det ikke er noe å vise -- unngår
  // et ekstra obligatorisk trykk for HVER hull-registrering (se
  // begrunnelsen på SheetStep over).
  useEffect(() => {
    if (step === "overview" && historySummary === null) forward()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [step, historySummary])

  // Auto-hopp, samme mønster som ScoringWizard (round-detail.tsx) --
  // hopper videre av seg selv når steget sitt eget felt får en verdi.
  const autoField = step === "strokes" ? "strokes" : step === "putts" ? "putts" : step === "puttDistance" ? "puttBucket" : null
  const enteredWithValueRef = useRef(false)
  const firedRef = useRef(false)
  useEffect(() => {
    firedRef.current = false
    enteredWithValueRef.current =
      autoField === "strokes" ? strokes !== null : autoField === "putts" ? putts !== null : autoField === "puttBucket" ? puttBucket !== null : false
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [step])
  useEffect(() => {
    if (!autoField || firedRef.current || enteredWithValueRef.current) return
    const value = autoField === "strokes" ? strokes : autoField === "putts" ? putts : puttBucket
    if (value !== null) {
      firedRef.current = true
      forward()
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [strokes, putts, puttBucket])

  // GIR-auto-inferens, samme formel/begrunnelse som ScoringWizard.
  useEffect(() => {
    if (step !== "direction") return
    if (approach !== null) return
    if (strokes === null || putts === null) return
    if (strokes - putts <= hole.par - 2) setApproach("hit")
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [step, strokes, putts, approach, hole.par])

  async function save() {
    if (strokes === null) {
      setError("Registrer antall slag først.")
      return
    }
    setSaving(true)
    setError(null)
    try {
      await onSave({
        gross_strokes: strokes,
        putts,
        first_putt_distance_bucket: puttBucket,
        club_off_tee: club,
        tee_shot_result: teeShot,
        approach_result: approach,
        chip_count: chip,
        bunker_shot_count: bunker,
        penalty_strokes: penalty,
        anyway_strokes: anywayStrokes,
        expected_version: hole.version,
      })
      onClose()
    } catch {
      setError("Klarte ikke å lagre. Prøv igjen.")
      setSaving(false)
    }
  }

  function back() {
    if (isFirstStep) {
      onClose()
      return
    }
    setStepIndex((i) => i - 1)
  }
  function forward() {
    if (!isLastStep) {
      setStepIndex((i) => i + 1)
      return
    }
    void save()
  }

  return (
    <div className="fixed inset-0 z-50 flex flex-col bg-background">
      <header className="flex min-h-14 shrink-0 items-center gap-2 border-b border-border px-3">
        <button
          type="button"
          onClick={onClose}
          aria-label="Lukk"
          className="flex size-11 shrink-0 items-center justify-center rounded-xl text-foreground transition-colors hover:bg-accent"
        >
          <X aria-hidden="true" className="size-5" />
        </button>
        <div className="flex min-w-0 flex-1 flex-col items-center text-center">
          <span className="truncate text-base font-extrabold text-foreground">
            Hull {hole.hole_number} · Par {hole.par}
          </span>
          <span className="truncate text-sm font-semibold text-muted-foreground">{playerName}</span>
        </div>
        <div className="size-11 shrink-0" aria-hidden="true" />
      </header>

      <main className="flex min-h-0 flex-1 flex-col overflow-y-auto p-5 sm:p-6">
        <div className="mx-auto flex w-full max-w-sm flex-col gap-6">
          {/* Rangefinder (migrasjon 079) -- samme selvskjulende komponent
              som frittstående runder, persistent uansett hvilket steg
              brukeren er på (samme plassering/prinsipp som round-detail.tsx). */}
          <HoleTargetDistance baseUrl={targetPointsBaseUrl} holeNumber={hole.hole_number} size="full" />

          {/* ADR-079: personlig historikk, flyttet hit ut av "strokes"-
              steget -- se begrunnelsen for auto-hopp ved SheetStep over.
              Samme kompakte oppsummering + klikk-for-full-historikk-
              mønster som HoleHistoryPanel (hole-stat-inputs.tsx), men
              lokal her siden foreldren allerede har hentet dataene (for
              auto-hopp-avgjørelsen) -- unngår et redundant andre kall. */}
          {step === "overview" && historySummary && (
            <button
              type="button"
              onClick={() => setShowFullHistory(true)}
              className="flex items-center justify-between gap-3 rounded-2xl border border-border bg-card p-4 text-left transition-colors hover:bg-accent/50"
            >
              <span className="flex flex-col gap-1">
                <span className="text-sm font-bold text-foreground">Din historikk på dette hullet</span>
                <span className="text-xs leading-relaxed text-muted-foreground text-pretty">
                  Spilt {historySummary.times_played} {historySummary.times_played === 1 ? "gang" : "ganger"} før
                  {" "}({historySummary.times_personal} frittstående, {historySummary.times_tournament} turnering) --
                  {" "}snitt {historySummary.average_score.toFixed(1)} slag (
                  {historySummary.average_score_vs_par >= 0 ? "+" : ""}
                  {historySummary.average_score_vs_par.toFixed(1)} til par)
                  {historySummary.gir_percent !== null && `, GIR ${Math.round(historySummary.gir_percent)}%`}
                  {historySummary.average_putts !== null && `, ${historySummary.average_putts.toFixed(1)} putter i snitt`}
                </span>
              </span>
              <ChevronRight aria-hidden="true" className="size-4 shrink-0 text-muted-foreground" />
            </button>
          )}

          {step === "strokes" && (
            <NumberPicker
              label="Slag"
              value={strokes}
              directValues={[1, 2, 3, 4, 5, 6, 7, 8, 9]}
              expandValues={[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]}
              expandLabel="10+"
              parValue={hole.par}
              showGolfTerms
              strokesReceived={strokesReceived}
              onChange={setStrokes}
              readOnly={false}
            />
          )}

          {step === "putts" && (
            <NumberPicker
              label="Putter"
              value={putts}
              directValues={[0, 1, 2, 3, 4, 5, 6]}
              expandValues={[7, 8, 9, 10]}
              expandLabel="7+"
              maxValue={strokes ?? undefined}
              accent="orange"
              onChange={setPutts}
              readOnly={false}
            />
          )}

          {step === "puttDistance" && (
            <ChoiceRow
              label="Avstand første putt"
              options={[
                { value: "<1m", label: "0-1m" },
                { value: "<2m", label: "1-2m" },
                { value: "<3m", label: "2-3m" },
                { value: "<5m", label: "3-5m" },
                { value: "<8m", label: "5-8m" },
                { value: "8m+", label: "8m+" },
              ]}
              value={puttBucket}
              onChange={setPuttBucket}
              readOnly={false}
            />
          )}

          {step === "direction" && (
            <>
              <ClubPicker
                label="Kølle brukt ved utslaget"
                value={club ?? ""}
                onChange={setClub}
                ownBagClubs={ownBagClubs}
                readOnly={false}
              />

              <WizardSection title="Retning">
                {hole.par !== 3 && (
                  <DirectionCross
                    label="Utslag"
                    variant="horizontal"
                    centerLabel="Fairway"
                    value={teeShot}
                    onChange={setTeeShot}
                    readOnly={false}
                  />
                )}
                <DirectionCross
                  label="Innspill"
                  variant="full"
                  centerLabel="Traff"
                  value={approach}
                  onChange={setApproach}
                  readOnly={false}
                />
              </WizardSection>
            </>
          )}

          {/* Teller-flis-rutenett (V0-designet, ADR-078, 2026-08-16) --
              samme 2x2-mønster som round-detail.tsx sitt speilede
              holeDetails-steg, se kommentaren der for begrunnelsen. */}
          {step === "holeDetails" && (
            <div className="grid grid-cols-2 gap-3">
              <Stepper label="Chip" value={chip} onChange={setChip} readOnly={false} max={strokes ?? undefined} />
              <Stepper label="Bunker" value={bunker} onChange={setBunker} readOnly={false} max={strokes ?? undefined} />
              <Stepper label="Straffeslag" value={penalty} onChange={setPenalty} readOnly={false} max={strokes ?? undefined} />
              <Stepper label="Anywayslag" value={anywayStrokes} onChange={setAnywayStrokes} readOnly={false} max={strokes ?? undefined} />
            </div>
          )}

          {error && <p className="text-sm font-medium text-destructive">{error}</p>}
        </div>
      </main>

      <footer className="grid shrink-0 grid-cols-2 gap-3 border-t border-border p-4">
        <Button type="button" variant="outline" onClick={back} className="h-14 rounded-2xl text-base font-bold">
          {isFirstStep ? "Avbryt" : "Forrige"}
        </Button>
        <Button
          type="button"
          disabled={!canProceed || saving}
          onClick={forward}
          className="h-14 rounded-2xl text-base font-bold disabled:opacity-40"
        >
          {isLastStep ? (saving ? "Lagrer…" : "Lagre") : "Neste"}
        </Button>
      </footer>

      {showFullHistory && (
        <HoleHistoryDetail url={historyUrl} holeNumber={hole.hole_number} onClose={() => setShowFullHistory(false)} />
      )}
    </div>
  )
}

function HoleGrid({
  base,
  roundId,
  participantId,
  holes,
  holeConfig,
  statLevel,
  playerName,
  strokesReceived,
  ownBagClubs,
  onUpdate,
  onUpdateFull,
}: {
  base: string
  roundId: string
  participantId: string
  holes: ApiHole[]
  holeConfig: ApiRound["hole_config"]
  statLevel: "strokes_only" | "strokes_and_putts" | "full"
  playerName: string
  strokesReceived: (holeNumber: number) => number | null
  ownBagClubs: string[]
  onUpdate: (holeNumber: number, grossStrokes: number) => Promise<void>
  onUpdateFull: (holeNumber: number, patch: HolePatch) => Promise<void>
}) {
  const [editingHole, setEditingHole] = useState<number | null>(null)
  const [draft, setDraft] = useState("")
  // Full statistikkdybde (migrasjon 075, ADR-071): en annen deltaker-
  // celle enn den enkle inline-tekstboksen under -- kun for statLevel !=
  // "strokes_only". HoleGrid sin egen strokes_only-vei (editingHole/
  // draft/openEditor/commit over) er BEVISST uendret, ingen regresjon.
  const [sheetHole, setSheetHole] = useState<number | null>(null)
  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) {
    if (statLevel !== "strokes_only") {
      setSheetHole(holeNumber)
      return
    }
    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>
      )}

      {sheetHole !== null && statLevel !== "strokes_only" && byNumber.get(sheetHole) && (
        <HoleStatsSheet
          hole={byNumber.get(sheetHole)!}
          playerName={playerName}
          statLevel={statLevel}
          strokesReceived={strokesReceived(sheetHole)}
          ownBagClubs={ownBagClubs}
          historyUrl={`${base}/rounds/${roundId}/participants/${participantId}/holes/${sheetHole}/history`}
          targetPointsBaseUrl={`${base}/rounds/${roundId}`}
          onSave={(patch) => onUpdateFull(sheetHole, patch)}
          onClose={() => setSheetHole(null)}
        />
      )}
    </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)
  // Eclectic (ADR-067-tillegget "Del C") -- "per-hull beste-kilde"-visning,
  // én deltaker utvidet om gangen (samme "trykk for detaljer"-mønster som
  // resten av appen, ikke alle utvidet samtidig).
  const [expandedEclecticId, setExpandedEclecticId] = 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 }
    if (method === "eclectic_gross") return { label: "slag", value: entry.eclectic_total }
    if (method === "eclectic_net") return { label: "netto", value: entry.eclectic_total }
    if (method === "eclectic_stableford") return { label: "poeng", value: entry.eclectic_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>
    )
  }

  // Konkurranseklasser (2026-08-03) -- den mottatte listen er ALLEREDE
  // sortert riktig av backend (individual_leaderboard, uendret
  // sorteringslogikk); her grupperes den samme rekkefølgen visuelt på
  // class_id (stabil gruppering bevarer riktig rangering INNAD i hver
  // klasse). Ingen klasser opprettet ennå => alle entries havner i samme
  // "Ingen klasse"-gruppe => identisk med tidligere flat visning,
  // bakoverkompatibelt uten migrasjonsflagg.
  const groups: { key: string; label: string; entries: ApiLeaderboardEntry[] }[] = []
  const indexByKey = new Map<string, number>()
  for (const entry of entries) {
    const key = entry.class_id ?? "__none__"
    let idx = indexByKey.get(key)
    if (idx === undefined) {
      idx = groups.length
      indexByKey.set(key, idx)
      groups.push({ key, label: entry.class_name ?? "Ingen klasse", entries: [] })
    }
    groups[idx].entries.push(entry)
  }
  // "Ingen klasse" sist, uansett hvor den først dukket opp i sorteringen.
  groups.sort((a, b) => (a.key === "__none__" ? 1 : b.key === "__none__" ? -1 : 0))
  const showSectionHeaders = groups.length > 1 || groups[0]?.key !== "__none__"

  // Augusta-stil resultattavle (2026-08-04) -- kun for brutto/netto/
  // stableford, backend har allerede regnet ut alt (POS med uavgjort,
  // TODAY/THRU for gjeldende runde, TOTAL, R1-Rn). Denne funksjonen er ren
  // formvending, ingen domenelogikk.
  const isStrokePlay = method === "stroke_gross" || method === "stroke_net" || method === "stableford"

  function toStrokePlayRow(entry: ApiLeaderboardEntry): StrokePlayRow {
    return {
      position: entry.position ?? "-",
      playerName: entry.player_name,
      todayLabel: entry.today_label,
      todayIsUnderPar: null,
      thruLabel: entry.thru_label ?? "-",
      // total_label er null når deltakeren ikke har spilt ett eneste hull
      // ennå -- nøytral tilstand, IKKE til forveksling med "E" (jevnt med
      // par, en ekte score). stroke-play-leaderboard.tsx viser tee-tid/
      // starthull i stedet når totalLabel er null.
      totalLabel: entry.total_label,
      totalIsUnderPar: false,
      isLeader: entry.is_leader,
      nextTeeTime: entry.next_tee_time,
      nextStartHole: entry.next_start_hole,
      rounds: entry.rounds.map((r) => ({ roundNumber: r.round_number, label: r.label, tone: r.tone })),
    }
  }

  const isEclectic = method !== null && ECLECTIC_SCORING_METHODS.has(method)

  function renderRow(entry: ApiLeaderboardEntry, rank: number) {
    const { label, value } = valueFor(entry)
    const isLeader = rank === 1 && value !== null
    const canExpand = isEclectic && entry.eclectic_holes.length > 0
    const expanded = expandedEclecticId === entry.tournament_participant_id
    const row = (
      <div className="flex items-center gap-3 px-4 py-3">
        <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
            {canExpand ? (expanded ? " · skjul hull-for-hull" : " · vis hull-for-hull") : ""}
          </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>
      </div>
    )
    return (
      <li key={entry.tournament_participant_id} className={cn(isLeader && "bg-gold/10")}>
        {canExpand ? (
          <button
            type="button"
            onClick={() => setExpandedEclecticId(expanded ? null : entry.tournament_participant_id)}
            aria-expanded={expanded}
            className="min-h-11 w-full text-left transition-colors hover:bg-accent/40"
          >
            {row}
          </button>
        ) : (
          row
        )}
        {expanded ? <EclecticHoleTable holes={entry.eclectic_holes} valueLabel={label} /> : null}
      </li>
    )
  }

  // Omspill-varsel (migrasjon 086, 2026-08-18) -- vist proaktivt her,
  // FØR en eventuell uavgjort faktisk oppstår ("Dette bør informeres om
  // før turneringen"), ikke bare når leaderboardet viser en delt
  // førsteplass. Én global innstilling, vises én gang over gruppene
  // (ikke per klasse-seksjon).
  const playoffNote =
    isStrokePlay && tournament.tiebreak_winner_method === "playoff" && tournament.tiebreak_playoff_rule
      ? tournament.tiebreak_playoff_rule
      : null

  return (
    <div className="flex flex-col gap-4">
      {playoffNote && (
        <div className="flex items-start gap-3 rounded-2xl border border-gold bg-gold/10 px-4 py-3">
          <Trophy aria-hidden="true" className="mt-0.5 size-5 shrink-0 text-gold-foreground" />
          <p className="text-sm font-semibold leading-relaxed text-foreground text-pretty">
            Ved likt resultat for 1. plass: {playoffNote}
          </p>
        </div>
      )}
      {groups.map((group) => (
        <div key={group.key} className="flex flex-col gap-2">
          {showSectionHeaders && (
            <h3 className="text-xs font-bold uppercase tracking-wide text-muted-foreground">
              {group.label}
            </h3>
          )}
          {isStrokePlay ? (
            <StrokePlayLeaderboard
              rows={group.entries.map(toStrokePlayRow)}
              caption={showSectionHeaders ? `Resultattavle – ${group.label}` : "Resultattavle"}
            />
          ) : (
            <ul className="flex flex-col divide-y divide-border overflow-hidden rounded-2xl border border-border bg-card shadow-md shadow-black/8">
              {group.entries.map((entry, index) => renderRow(entry, index + 1))}
            </ul>
          )}
        </div>
      ))}
    </div>
  )
}

// Eclectic (ADR-067-tillegget "Del C") -- "per-hull beste-kilde"-visning:
// hvilket hull, hva slaget/poenget ble, og HVILKEN runde det kom fra.
// Beviser for spilleren at "drømmerunden" faktisk er satt sammen av det
// beste fra flere runder, ikke bare den beste hele runden.
function EclecticHoleTable({ holes, valueLabel }: { holes: ApiEclecticHoleCell[]; valueLabel: string }) {
  const sorted = [...holes].sort((a, b) => a.hole_number - b.hole_number)
  return (
    <div className="overflow-x-auto border-t border-border bg-muted/30 px-4 py-3">
      <table className="w-full min-w-[420px] border-collapse text-sm">
        <thead>
          <tr className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
            <th scope="col" className="px-2 py-1 text-left">
              Hull
            </th>
            <th scope="col" className="px-2 py-1 text-right">
              Par
            </th>
            <th scope="col" className="px-2 py-1 text-right">
              {valueLabel === "poeng" ? "Poeng" : valueLabel === "netto" ? "Netto" : "Slag"}
            </th>
            <th scope="col" className="px-2 py-1 text-right">
              Fra runde
            </th>
          </tr>
        </thead>
        <tbody>
          {sorted.map((h) => (
            <tr key={h.hole_number} className="border-t border-border/60">
              <td className="px-2 py-1 font-semibold text-foreground">{h.hole_number}</td>
              <td className="px-2 py-1 text-right tabular-nums text-muted-foreground">{h.par}</td>
              <td className="px-2 py-1 text-right font-bold tabular-nums text-foreground">{h.value}</td>
              <td className="px-2 py-1 text-right tabular-nums text-muted-foreground">Runde {h.round_number}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

// --- TeeOff-baneimport (2026-08-04) -----------------------------------------
// Samme mønster/endepunkter som tournament-program.tsx sin OfficialCourse
// Search (ADR-019) -- kopiert hit i stedet for delt via import, samme
// duplisering-mellom-de-to-turneringstype-filene-konvensjon som ClassesCard
// allerede fulgte. Var tidligere KUN tilgjengelig for lagturneringer; denne
// runden fikset gapet der individuelle turneringer manglet TeeOff-import helt.

type ApiOfficialFacility = {
  slug: string
  name: string
  city: string | null
  county: string | null
}

type ApiOfficialCourseOption = {
  teeoff_course_id: number
  name: string
  is_main_course: boolean
}

function OfficialCourseSearch({
  organizationId,
  onClose,
  onImported,
}: {
  organizationId: string
  onClose: () => void
  onImported: (course: ApiCourse) => void
}) {
  const [query, setQuery] = useState("")
  const [facilities, setFacilities] = useState<ApiOfficialFacility[] | null>(null)
  const [selectedFacility, setSelectedFacility] = useState<ApiOfficialFacility | null>(null)
  const [courseOptions, setCourseOptions] = useState<ApiOfficialCourseOption[] | null>(null)
  const [searching, setSearching] = useState(false)
  const [importing, setImporting] = useState(false)
  const [error, setError] = useState<string | null>(null)

  async function runSearch() {
    setSearching(true)
    setError(null)
    try {
      const res = await fetch(
        `/orgs/${organizationId}/courses/official-search?q=${encodeURIComponent(query.trim())}`,
        { credentials: "include" },
      )
      if (!res.ok) throw new Error(`search: ${res.status}`)
      setFacilities(await res.json())
    } catch {
      setError("Klarte ikke å søke i teeoff sine baner akkurat nå.")
      setFacilities([])
    } finally {
      setSearching(false)
    }
  }

  async function pickFacility(facility: ApiOfficialFacility) {
    setSelectedFacility(facility)
    setError(null)
    setCourseOptions(null)
    try {
      const res = await fetch(`/orgs/${organizationId}/courses/official-search/${facility.slug}`, {
        credentials: "include",
      })
      if (!res.ok) throw new Error(`facility detail: ${res.status}`)
      const detail: { courses: ApiOfficialCourseOption[] } = await res.json()
      setCourseOptions(detail.courses)
    } catch {
      setError("Klarte ikke å hente baner for dette anlegget.")
      setCourseOptions([])
    }
  }

  async function importCourse(course: ApiOfficialCourseOption) {
    if (!selectedFacility) return
    setImporting(true)
    setError(null)
    try {
      const res = await fetch(`/orgs/${organizationId}/courses/official-import`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({
          facility_slug: selectedFacility.slug,
          teeoff_course_id: course.teeoff_course_id,
        }),
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        const code = body?.detail?.code
        if (code === "EXTERNAL_DATA_INCOMPLETE") {
          setError("Denne banen mangler nok data i teeoff til å importeres ennå.")
        } else if (code === "DUPLICATE") {
          setError("Denne banen er allerede importert til organisasjonen.")
        } else {
          setError("Klarte ikke å importere banen. Prøv igjen.")
        }
        return
      }
      onImported(await res.json())
    } catch {
      setError("Klarte ikke å importere banen. Prøv igjen.")
    } finally {
      setImporting(false)
    }
  }

  return (
    <div className="flex flex-col gap-3 rounded-2xl border border-border bg-background p-4">
      <div className="flex items-center justify-between gap-2">
        <span className="text-sm font-bold text-foreground">Hent bane fra teeoff</span>
        <Button
          type="button"
          variant="ghost"
          size="icon"
          onClick={onClose}
          className="size-8 rounded-lg text-muted-foreground"
          aria-label="Lukk"
        >
          <X aria-hidden="true" className="size-4" />
        </Button>
      </div>

      {error && <p className="text-sm font-medium text-destructive">{error}</p>}

      {!selectedFacility ? (
        <>
          {/* Bevisst IKKE et eget <form> her -- denne komponenten rendres inne
              i NewRoundForm sitt <form onSubmit={handleSubmit}>, og nestede
              <form>-elementer er ugyldig HTML (samme feilrapport-funn som i
              tournament-program.tsx sin variant -- "Søk"-knappen ville ellers
              i praksis submitte det YTRE runde-skjemaet). */}
          <div className="flex items-center gap-2">
            <Input
              autoFocus
              placeholder="Søk anleggsnavn…"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === "Enter") {
                  e.preventDefault()
                  runSearch()
                }
              }}
              className="h-11 flex-1 rounded-xl text-base"
            />
            <Button
              type="button"
              onClick={() => runSearch()}
              disabled={searching}
              className="h-11 shrink-0 rounded-xl font-bold"
            >
              {searching ? "Søker…" : "Søk"}
            </Button>
          </div>
          {facilities && (
            <ul className="flex max-h-56 flex-col overflow-auto rounded-xl border border-border">
              {facilities.map((f) => (
                <li key={f.slug} className="border-b border-border last:border-b-0">
                  <button
                    type="button"
                    onClick={() => pickFacility(f)}
                    className="flex w-full flex-col px-4 py-2.5 text-left transition-colors hover:bg-accent/60"
                  >
                    <span className="text-sm font-semibold text-foreground">{f.name}</span>
                    {(f.city || f.county) && (
                      <span className="text-xs text-muted-foreground">
                        {[f.city, f.county].filter(Boolean).join(", ")}
                      </span>
                    )}
                  </button>
                </li>
              ))}
              {facilities.length === 0 && (
                <li className="px-4 py-3 text-center text-sm text-muted-foreground">Ingen treff.</li>
              )}
            </ul>
          )}
        </>
      ) : (
        <>
          <button
            type="button"
            onClick={() => {
              setSelectedFacility(null)
              setCourseOptions(null)
            }}
            className="self-start text-sm font-semibold text-muted-foreground hover:text-foreground"
          >
            ← {selectedFacility.name}
          </button>
          {courseOptions === null ? (
            <p className="text-sm text-muted-foreground">Laster baner…</p>
          ) : (
            <ul className="flex flex-col overflow-hidden rounded-xl border border-border">
              {courseOptions.map((c) => (
                <li key={c.teeoff_course_id} className="border-b border-border last:border-b-0">
                  <button
                    type="button"
                    disabled={importing}
                    onClick={() => importCourse(c)}
                    className="flex w-full items-center justify-between px-4 py-2.5 text-left text-sm font-semibold text-foreground transition-colors hover:bg-accent/60 disabled:opacity-50"
                  >
                    {c.name}
                    {importing && <span className="text-xs font-normal text-muted-foreground">Importerer…</span>}
                  </button>
                </li>
              ))}
              {courseOptions.length === 0 && (
                <li className="px-4 py-3 text-center text-sm text-muted-foreground">
                  Ingen 18-hulls baner å importere hos dette anlegget ennå.
                </li>
              )}
            </ul>
          )}
        </>
      )}
    </div>
  )
}
