"use client"

// Hull-for-hull-registrering for en frittstående runde (ADR-033).
// Presentasjon fra V0, datalag skrevet om fra mock til ekte fetch/PATCH.
//
// VIKTIG kontraktsdetalj (bekreftet i scratch): hull-PATCH-endepunktet er
// IKKE et ekte delvis-PATCH -- det skriver ALLE felt ved hvert kall. Derfor
// slår updateStat() alltid sammen med gjeldende hull-data FØR den sender,
// aldri kun det ene feltet som ble endret.

import type React from "react"
import { useCallback, useEffect, useRef, useState } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import {
  ArrowDown,
  ArrowLeft,
  ArrowRight,
  ArrowUp,
  CalendarDays,
  Check,
  ChevronDown,
  Clock,
  Trash2,
  ChevronLeft,
  ChevronRight,
  Flag,
  MapPin,
  Minus,
  Plus,
  Search,
  Settings2,
  Target,
  Trophy,
  X,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { cn } from "@/lib/utils"

// --- Types -----------------------------------------------------------------

type Gender = "male" | "female" | "other"
type ApiGender = "m" | "f" | "x"
type TeeShot = "left" | "fairway" | "right"
type Approach = "left" | "short" | "hit" | "long" | "right"
type StatLevel = "strokes_only" | "strokes_and_putts" | "full"
type PuttBucket = "<1m" | "<2m" | "<3m" | "<5m" | "<8m" | "8m+"

type Player = {
  id: string
  name: string
  gender: Gender
  hcp: number | null
  isSelf: boolean
  countsForHandicap: boolean
  scoreDifferential: number | null
  statLevel: StatLevel
}

type HoleStat = {
  played: boolean
  strokes: number | null
  putts: number | null
  club: string
  teeShot: TeeShot | null
  approach: Approach | null
  chip: number
  bunker: number
  penalty: number
  firstPuttBucket: PuttBucket | null
  anywayStrokes: number | null
}

type Hole = {
  holeNumber: number
  par: number
  index: number
}

function emptyStat(): HoleStat {
  return {
    played: false,
    strokes: null,
    putts: null,
    club: "",
    teeShot: null,
    approach: null,
    chip: 0,
    bunker: 0,
    penalty: 0,
    firstPuttBucket: null,
    anywayStrokes: null,
  }
}

function statKey(playerId: string, holeNumber: number) {
  return `${playerId}:${holeNumber}`
}

function apiGenderToUi(g: ApiGender): Gender {
  return g === "m" ? "male" : g === "f" ? "female" : "other"
}
function uiGenderToApi(g: Gender): ApiGender {
  return g === "male" ? "m" : g === "female" ? "f" : "x"
}

const dateFormatter = new Intl.DateTimeFormat("no-NO", { day: "numeric", month: "long", year: "numeric" })
function formatDate(value: string) {
  const parsed = new Date(value)
  if (Number.isNaN(parsed.getTime())) return value
  return dateFormatter.format(parsed)
}

// --- API-typer ---------------------------------------------------------------

type ApiParticipant = {
  id: string
  user_id: string | null
  guest_name: string | null
  is_owner: boolean
  gender: ApiGender
  handicap_index_snapshot: number | null
  course_handicap_snapshot: number | null
  counts_for_handicap: boolean
  score_differential: number | null
  stat_level: StatLevel
}

type ApiRound = {
  id: string
  name: string | null
  course_name_snapshot: string
  tee_name_snapshot: string
  played_at: string
  start_hole: number
  holes_planned: number
  started_at: string | null
  completed_at: string | null
  participants: ApiParticipant[]
}

// Utslagstid vises i klokkeslett-format, og tidsbruk beregnes som
// completed_at - started_at (begge ekte tidsstempler) -- ingen egen
// lagret varighet, kun utledet ved visning.
const timeFormatter = new Intl.DateTimeFormat("no-NO", { hour: "2-digit", minute: "2-digit" })
function formatTime(value: string) {
  const parsed = new Date(value)
  if (Number.isNaN(parsed.getTime())) return value
  return timeFormatter.format(parsed)
}
function formatDuration(startedAt: string, completedAt: string): string | null {
  const start = new Date(startedAt).getTime()
  const end = new Date(completedAt).getTime()
  if (Number.isNaN(start) || Number.isNaN(end) || end <= start) return null
  const totalMinutes = Math.round((end - start) / 60000)
  const hours = Math.floor(totalMinutes / 60)
  const minutes = totalMinutes % 60
  if (hours === 0) return `${minutes} min`
  return `${hours}t ${minutes}min`
}

type ApiHole = {
  hole_number: number
  par: number
  stroke_index: number
  played: boolean
  score: number | null
  putts: number | null
  club_off_tee: string | null
  tee_shot_result: TeeShot | null
  approach_result: Approach | null
  chip_count: number | null
  bunker_shot_count: number | null
  penalty_strokes: number | null
  first_putt_distance_bucket: PuttBucket | null
  anyway_strokes: number | null
  strokes_received: number | null
}

function apiHoleToStat(h: ApiHole): HoleStat {
  return {
    played: h.played,
    strokes: h.score,
    putts: h.putts,
    club: h.club_off_tee ?? "",
    teeShot: h.tee_shot_result,
    approach: h.approach_result,
    chip: h.chip_count ?? 0,
    bunker: h.bunker_shot_count ?? 0,
    penalty: h.penalty_strokes ?? 0,
    firstPuttBucket: h.first_putt_distance_bucket,
    anywayStrokes: h.anyway_strokes,
  }
}

function statToPatchBody(s: HoleStat) {
  return {
    played: s.played,
    score: s.strokes,
    putts: s.putts,
    club_off_tee: s.club.trim() === "" ? null : s.club,
    tee_shot_result: s.teeShot,
    approach_result: s.approach,
    chip_count: s.chip,
    bunker_shot_count: s.bunker,
    penalty_strokes: s.penalty,
    first_putt_distance_bucket: s.firstPuttBucket,
    anyway_strokes: s.anywayStrokes,
  }
}

function playerLabel(p: ApiParticipant): string {
  return p.is_owner ? "Deg" : p.guest_name ?? "Gjest"
}

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

export function RoundDetail({ roundId }: { roundId: string }) {
  const router = useRouter()
  const [round, setRound] = useState<ApiRound | null>(null)
  const [error, setError] = useState<string | null>(null)
  const [activePlayerId, setActivePlayerId] = useState<string | null>(null)
  const [holesByParticipant, setHolesByParticipant] = useState<Record<string, ApiHole[]>>({})
  // Eierens egen kølle-bag (personlig profil) -- brukt til å tilby et
  // knapp-utvalg for "Kølle brukt ved utslaget" i stedet for fritekst, kun
  // for eieren selv (gjester har ingen profil å hente dette fra).
  const [ownBagClubs, setOwnBagClubs] = useState<string[]>([])
  // `null` betyr "ikke satt ennå" -- MÅ være null, ikke f.eks. 1, siden 1
  // er en gyldig, truthy hullverdi og ville gjort `prev || start_hole`
  // lenger ned til en no-op (funnet 2026-07-24: runden åpnet alltid på
  // hull 1 uansett faktisk starthull).
  const [currentHole, setCurrentHole] = useState<number | null>(null)
  const [showAddGuest, setShowAddGuest] = useState(false)
  const [completing, setCompleting] = useState(false)
  const [deleting, setDeleting] = useState(false)
  const [showEditRound, setShowEditRound] = useState(false)
  // "Flere detaljer" er kollapset som default (etterspurt av bruker
  // 2026-07-25 -- scorekortet var "veldig dårlig designet", altfor mye
  // stablet oppå hverandre for et enkelt slag-registrering) slik at en
  // rask slag-/putt-registrering ikke krever noe scrolling i det hele tatt.
  const [detailsOpen, setDetailsOpen] = useState(false)
  // Hull-panelet gjenbrukes ved bytte av hull -- "Forrige"/"Neste hull" ligger
  // NEDERST i panelet, så uten dette ville brukeren blitt stående scrollet
  // helt ned (der knappene er) mens det NYE hullets Slag-felt (øverst i
  // panelet) er utenfor skjermen (rapportert av bruker 2026-07-25).
  const holePanelRef = useRef<HTMLElement>(null)
  function scrollToHolePanel() {
    holePanelRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })
  }

  const loadRound = useCallback(async () => {
    try {
      const res = await fetch(`/rounds/${roundId}`, { credentials: "include" })
      if (res.status === 403 || res.status === 404) {
        setError("Denne runden finnes ikke, eller du har ikke tilgang til den.")
        return
      }
      if (!res.ok) throw new Error(`round: ${res.status}`)
      const data: ApiRound = await res.json()
      setRound(data)
      setActivePlayerId((prev) => prev ?? data.participants.find((p) => p.is_owner)?.id ?? data.participants[0]?.id ?? null)
      setCurrentHole((prev) => prev ?? data.start_hole)
    } catch {
      setError("Klarte ikke å hente runden. Prøv igjen om litt.")
    }
  }, [roundId])

  useEffect(() => {
    void loadRound()
  }, [loadRound])

  useEffect(() => {
    let cancelled = false
    fetch("/auth/me", { credentials: "include" })
      .then((res) => (res.ok ? res.json() : null))
      .then((data: { bag_clubs: string[] } | null) => {
        if (!cancelled && data) setOwnBagClubs(data.bag_clubs)
      })
      .catch(() => {})
    return () => {
      cancelled = true
    }
  }, [])

  const loadHoles = useCallback(
    async (participantId: string) => {
      const res = await fetch(`/rounds/${roundId}/participants/${participantId}/holes`, { credentials: "include" })
      if (!res.ok) return
      const data: ApiHole[] = await res.json()
      setHolesByParticipant((prev) => ({ ...prev, [participantId]: data }))
    },
    [roundId],
  )

  useEffect(() => {
    if (activePlayerId && !holesByParticipant[activePlayerId]) {
      void loadHoles(activePlayerId)
    }
  }, [activePlayerId, holesByParticipant, loadHoles])

  const completed = round?.completed_at != null
  const readOnly = completed

  const players: Player[] =
    round?.participants.map((p) => ({
      id: p.id,
      name: playerLabel(p),
      gender: apiGenderToUi(p.gender),
      hcp: p.handicap_index_snapshot,
      isSelf: p.is_owner,
      countsForHandicap: p.counts_for_handicap,
      scoreDifferential: p.score_differential,
      statLevel: p.stat_level,
    })) ?? []

  const activePlayer = players.find((p) => p.id === activePlayerId) ?? players[0] ?? null
  const apiHoles = activePlayerId ? holesByParticipant[activePlayerId] : undefined
  const holes: Hole[] = (apiHoles ?? []).map((h) => ({ holeNumber: h.hole_number, par: h.par, index: h.stroke_index }))
  // Alltid en tallverdi å regne videre på selv rett etter mount, FØR
  // loadRound() har satt currentHole fra round.start_hole.
  const activeHole = currentHole ?? round?.start_hole ?? 1
  const hole = holes.find((h) => h.holeNumber === activeHole) ?? null

  // Navigasjonsrekkefølge starter på øktens starthull og går rundt
  // (sirkulært over 18 hull), men KUN så mange hull som runden faktisk
  // planlegger (holes_planned) -- en 9-hulls runde 10-18 skal aldri vise
  // hull 1-9 i det hele tatt, og "Forrige" fra hull 10 skal gå til hull
  // 18 (siste i settet), ikke hull 9 (funnet 2026-07-24: brukte tidligere
  // alltid 18, uansett holes_planned).
  const holeOrder = round
    ? Array.from({ length: round.holes_planned }, (_, i) => ((round.start_hole - 1 + i) % 18) + 1)
    : []
  const orderedHoles = holeOrder.map((n) => holes.find((h) => h.holeNumber === n)).filter((h): h is Hole => h !== undefined)

  const currentApiHole = apiHoles?.find((h) => h.hole_number === activeHole) ?? null
  const currentStat = currentApiHole ? apiHoleToStat(currentApiHole) : emptyStat()

  const showGir =
    currentStat.played &&
    currentStat.strokes !== null &&
    currentStat.putts !== null &&
    hole !== null &&
    currentStat.strokes - currentStat.putts <= hole.par - 2

  async function updateStat(patch: Partial<HoleStat>) {
    if (readOnly || !activePlayerId || !currentApiHole) return
    const merged: HoleStat = { ...currentStat, ...patch }
    const res = await fetch(`/rounds/${roundId}/participants/${activePlayerId}/holes/${activeHole}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify(statToPatchBody(merged)),
    })
    if (!res.ok) return
    const updated: ApiHole = await res.json()
    setHolesByParticipant((prev) => ({
      ...prev,
      [activePlayerId]: (prev[activePlayerId] ?? []).map((h) => (h.hole_number === updated.hole_number ? updated : h)),
    }))
  }

  function holeIsPlayed(holeNumber: number) {
    return apiHoles?.find((h) => h.hole_number === holeNumber)?.played ?? false
  }

  function goPrev() {
    const i = holeOrder.indexOf(activeHole)
    setCurrentHole(holeOrder[(i - 1 + holeOrder.length) % holeOrder.length])
    scrollToHolePanel()
  }
  function goNext() {
    const i = holeOrder.indexOf(activeHole)
    setCurrentHole(holeOrder[(i + 1) % holeOrder.length])
    scrollToHolePanel()
  }

  async function addGuest(name: string, gender: Gender, hcp: number | null, statLevel: StatLevel) {
    const res = await fetch(`/rounds/${roundId}/participants`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({
        guest_name: name,
        gender: uiGenderToApi(gender),
        handicap_index: hcp,
        stat_level: statLevel,
      }),
    })
    if (!res.ok) {
      setError("Klarte ikke å legge til spilleren. Sjekk at banen har en rating for valgt kjønn.")
      return
    }
    setShowAddGuest(false)
    const created: ApiParticipant = await res.json()
    await loadRound()
    setActivePlayerId(created.id)
  }

  async function removeGuest(id: string) {
    if (!confirm("Fjerne denne spilleren fra runden?")) return
    const res = await fetch(`/rounds/${roundId}/participants/${id}`, { method: "DELETE", credentials: "include" })
    if (!res.ok) return
    if (activePlayerId === id) setActivePlayerId(round?.participants.find((p) => p.is_owner)?.id ?? null)
    await loadRound()
  }

  async function updateStatLevel(participantId: string, statLevel: StatLevel) {
    const res = await fetch(`/rounds/${roundId}/participants/${participantId}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ stat_level: statLevel }),
    })
    if (!res.ok) return
    const updated: ApiParticipant = await res.json()
    setRound((prev) =>
      prev ? { ...prev, participants: prev.participants.map((p) => (p.id === updated.id ? updated : p)) } : prev,
    )
  }

  async function finishRound() {
    if (!confirm("Fullføre runden? Du kan fortsatt se den, men ikke lenger endre registrerte hull.")) return
    setCompleting(true)
    try {
      const res = await fetch(`/rounds/${roundId}/complete`, { method: "POST", credentials: "include" })
      if (!res.ok) throw new Error()
      setRound(await res.json())
    } catch {
      setError("Klarte ikke å fullføre runden. Prøv igjen.")
    } finally {
      setCompleting(false)
    }
  }

  async function deleteRound() {
    if (!confirm("Slette denne runden permanent? Dette kan ikke angres, og gjelder for alle spillerne i runden.")) return
    setDeleting(true)
    try {
      const res = await fetch(`/rounds/${roundId}`, { method: "DELETE", credentials: "include" })
      if (!res.ok) throw new Error()
      router.replace("/my-rounds")
    } catch {
      setError("Klarte ikke å slette runden. Prøv igjen.")
      setDeleting(false)
    }
  }

  // Retter opp feil bane/utslag eller feil antall hull underveis i runden
  // (2026-07-24) -- rører ALDRI allerede registrerte slag/putter/etc.,
  // kun rating-grunnlaget. Backend avviser tydelig (400) hvis en deltaker
  // ville mistet HCP-sporing, uten å skrive noe.
  async function patchRound(body: Record<string, unknown>): Promise<{ ok: true } | { ok: false; message: string }> {
    const res = await fetch(`/rounds/${roundId}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify(body),
    })
    if (!res.ok) {
      const errBody = await res.json().catch(() => null)
      return { ok: false, message: errBody?.detail?.message ?? "Klarte ikke å oppdatere runden. Prøv igjen." }
    }
    const updated: ApiRound = await res.json()
    setRound(updated)
    // Par/stroke-indeks kan ha endret seg for ALLE deltakere -- hent hull-
    // dataen på nytt for alle vi allerede har lastet, ikke bare aktiv spiller.
    const participantIds = Object.keys(holesByParticipant)
    for (const pid of participantIds) {
      await loadHoles(pid)
    }
    return { ok: true }
  }

  if (error) {
    return (
      <div className="flex min-h-[100dvh] flex-col items-center justify-center gap-4 bg-background px-5 text-center">
        <p className="text-base font-medium text-destructive">{error}</p>
        <Link href="/my-rounds" className="text-base font-semibold text-primary underline underline-offset-2">
          Tilbake til egne runder
        </Link>
      </div>
    )
  }

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

  return (
    <div className="flex min-h-[100dvh] flex-col bg-background">
      {/* Sticky header */}
      <header className="sticky top-0 z-20 border-b border-border bg-background/90 backdrop-blur">
        <div className="mx-auto flex w-full max-w-3xl items-center gap-3 px-5 py-4">
          <Link
            href="/my-rounds"
            aria-label="Tilbake til egne runder"
            className="flex size-11 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-foreground transition-colors hover:bg-accent/50"
          >
            <ArrowLeft aria-hidden="true" className="size-5" />
          </Link>
          <div className="flex min-w-0 flex-col gap-0.5">
            <h1 className="flex items-center gap-2 truncate text-xl font-extrabold tracking-tight text-foreground">
              <MapPin aria-hidden="true" className="size-5 shrink-0 text-primary" />
              <span className="truncate">{round.name?.trim() || round.course_name_snapshot}</span>
            </h1>
            <div className="flex flex-wrap items-center gap-x-4 gap-y-0.5 text-sm font-medium text-muted-foreground">
              {round.name?.trim() && (
                <span className="flex items-center gap-1.5 truncate">
                  <MapPin aria-hidden="true" className="size-4 shrink-0" />
                  {round.course_name_snapshot}
                </span>
              )}
              <span className="flex items-center gap-1.5">
                <Flag aria-hidden="true" className="size-4 shrink-0" />
                {round.tee_name_snapshot}
              </span>
              <span className="flex items-center gap-1.5 tabular-nums">
                <CalendarDays aria-hidden="true" className="size-4 shrink-0" />
                {formatDate(round.played_at)}
              </span>
              {round.started_at && (
                <span className="flex items-center gap-1.5 tabular-nums">
                  <Clock aria-hidden="true" className="size-4 shrink-0" />
                  {formatTime(round.started_at)}
                </span>
              )}
            </div>
          </div>
        </div>
      </header>

      <main className="mx-auto w-full max-w-3xl flex-1 px-5 py-6 sm:py-8">
        {error && (
          <p role="alert" className="mb-4 text-base font-medium text-destructive">
            {error}
          </p>
        )}

        {/* Rediger/Fullfør/Slett samlet ØVERST, bevisst tonet ned (samme
            nøytrale trigger-stil som hverandre) -- disse lå tidligere som
            store, fremtredende knapper RETT under "Neste hull"-navigasjonen
            nederst i hull-panelet, som gjorde det altfor lett å trykke feil
            ved et uhell mens man bare skulle bla mellom hull (rapportert av
            bruker 2026-07-25). Ved å flytte dem hit må man aktivt scrolle OPP
            forbi hull-registreringen for å nå dem i det hele tatt. */}
        <div className="mb-5 flex flex-col gap-3">
          <div className="flex flex-wrap gap-2">
            <button
              type="button"
              onClick={() => setShowEditRound((v) => !v)}
              aria-expanded={showEditRound}
              className="inline-flex min-h-11 items-center gap-1.5 rounded-xl border border-dashed border-border bg-card px-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
            >
              <Settings2 aria-hidden="true" className="size-4" />
              Rediger runde
            </button>

            {!completed && (
              <button
                type="button"
                disabled={completing}
                onClick={finishRound}
                className="inline-flex min-h-11 items-center gap-1.5 rounded-xl border border-dashed border-border bg-card px-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground disabled:opacity-60"
              >
                <Check aria-hidden="true" className="size-4" />
                {completing ? "Fullfører…" : "Fullfør runde"}
              </button>
            )}

            <button
              type="button"
              disabled={deleting}
              onClick={deleteRound}
              className="inline-flex min-h-11 items-center gap-1.5 rounded-xl border border-dashed border-border bg-card px-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive disabled:opacity-60"
            >
              <Trash2 aria-hidden="true" className="size-4" />
              {deleting ? "Sletter…" : "Slett runde"}
            </button>
          </div>

          {showEditRound && (
            <EditRoundPanel round={round} onPatch={patchRound} onClose={() => setShowEditRound(false)} />
          )}
        </div>

        {/* Completed summary banner */}
        {completed && (
          <CompletedBanner
            players={players}
            duration={round.started_at && round.completed_at ? formatDuration(round.started_at, round.completed_at) : null}
            roundId={roundId}
          />
        )}

        {/* Participant tabs */}
        <PlayerTabs
          players={players}
          activePlayerId={activePlayer.id}
          onSelect={setActivePlayerId}
          onRemove={removeGuest}
          onAdd={() => setShowAddGuest((v) => !v)}
          addOpen={showAddGuest}
          readOnly={readOnly}
        />

        {!readOnly && (
          <StatLevelPicker
            value={activePlayer.statLevel}
            onChange={(v) => updateStatLevel(activePlayer.id, v)}
          />
        )}

        {/* Skjules når runden er fullført (2026-07-25, rapportert av bruker
            som "veldig mye dobbel informasjon") -- CompletedBanner sin
            "Se full rundestatistikk"-lenke dekker nå akkurat det samme,
            langt grundigere. Fortsatt nyttig som live fremdriftsoversikt
            mens runden pågår. */}
        {!completed && <ScoreSoFar holes={apiHoles} order={holeOrder} />}

        {showAddGuest && !readOnly && <AddGuestForm onAdd={addGuest} onCancel={() => setShowAddGuest(false)} />}

        {/* Hole navigation */}
        {holes.length === 0 ? (
          <div className="mt-5 flex justify-center py-8">
            <div aria-hidden="true" className="size-8 animate-spin rounded-full border-4 border-primary/20 border-t-primary" />
          </div>
        ) : (
          <>
            <HoleNav
              holes={orderedHoles}
              currentHole={activeHole}
              isPlayed={holeIsPlayed}
              onSelect={(n) => {
                setCurrentHole(n)
                scrollToHolePanel()
              }}
            />

            {/* Current hole panel */}
            {hole && (
              <section
                ref={holePanelRef}
                className="mt-5 flex scroll-mt-28 flex-col gap-6 rounded-3xl border border-border bg-card p-5 shadow-sm shadow-black/5 sm:p-6"
              >
                {/* Høyre kolonne reserverer plass ved siden av hull-headeren
                    til en fremtidig avstandsmåling-indikator (ikke bygget
                    ennå, men avklart 2026-07-25 at det kommer) -- GIR-merket
                    ligger under den reserverte plassen slik at et senere
                    avstand-chip her ikke krever noen layout-endring. */}
                <div className="flex items-start justify-between gap-3">
                  <h2 className="text-2xl font-extrabold tracking-tight text-foreground text-balance">
                    Hull {hole.holeNumber} · Par {hole.par} · Hcp {hole.index}
                  </h2>
                  <div className="flex shrink-0 flex-col items-end gap-2">
                    {showGir && (
                      <span className="rounded-full bg-primary px-3 py-1.5 text-sm font-bold text-primary-foreground">
                        GIR
                      </span>
                    )}
                  </div>
                </div>

                {/* Slag/Putter og (ved statLevel="full") alle detaljene vises
                    ALLTID samlet under hverandre -- ikke bak en fane man må
                    oppdage og trykke på (funnet 2026-07-25: brukeren huket
                    av "All statistikk", men fikk aldri se noe utover
                    slag/putter siden det lå bak en fane). */}
                <div className="flex flex-col gap-6">
                  <NumberPicker
                    label="Slag"
                    value={currentStat.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}
                    onChange={(v) => updateStat({ strokes: v, played: true })}
                    readOnly={readOnly}
                  />

                  {activePlayer.statLevel !== "strokes_only" && (
                    <NumberPicker
                      label="Putter"
                      value={currentStat.putts}
                      directValues={[0, 1, 2, 3, 4, 5, 6]}
                      expandValues={[7, 8, 9, 10]}
                      expandLabel="7+"
                      maxValue={currentStat.strokes ?? undefined}
                      onChange={(v) => updateStat({ putts: v })}
                      readOnly={readOnly}
                    />
                  )}

                  {activePlayer.statLevel === "full" && (
                    <>
                      {/* Rett under antall putter (etterspurt av bruker 2026-07-25) --
                          hører naturlig sammen med Putter-feltet rett over, ikke med
                          kølle/retning/chip-gruppen lenger ned. */}
                      <ChoiceRow
                        label="Avstand første putt"
                        options={[
                          { value: "<1m", label: "<1m" },
                          { value: "<2m", label: "<2m" },
                          { value: "<3m", label: "<3m" },
                          { value: "<5m", label: "<5m" },
                          { value: "<8m", label: "<8m" },
                          { value: "8m+", label: "8m+" },
                        ]}
                        value={currentStat.firstPuttBucket}
                        onChange={(v) => updateStat({ firstPuttBucket: v as PuttBucket })}
                        readOnly={readOnly}
                      />

                      {/* Alt annet lever bak én kollapsbar seksjon, lukket som
                          default -- panelet forblir kort for det vanlige
                          slag-/putt-registreringstilfellet. */}
                      <div className="overflow-hidden rounded-2xl border border-border">
                        <button
                          type="button"
                          onClick={() => setDetailsOpen((v) => !v)}
                          aria-expanded={detailsOpen}
                          className="flex min-h-14 w-full items-center justify-between gap-2 px-4 py-3 text-left text-base font-bold text-foreground transition-colors hover:bg-accent/50"
                        >
                          Flere detaljer
                          <ChevronDown
                            aria-hidden="true"
                            className={cn(
                              "size-5 shrink-0 text-muted-foreground transition-transform",
                              detailsOpen && "rotate-180",
                            )}
                          />
                        </button>
                        {detailsOpen && (
                          <div className="flex flex-col gap-6 border-t border-border p-4 sm:p-5">
                            <div className="flex flex-col gap-2">
                              <Label className="text-base font-semibold">Kølle brukt ved utslaget</Label>
                              {activePlayer.isSelf && ownBagClubs.length > 0 ? (
                                <div className="flex flex-wrap gap-2">
                                  {ownBagClubs.map((club) => {
                                    const selected = currentStat.club === club
                                    return (
                                      <button
                                        key={club}
                                        type="button"
                                        disabled={readOnly}
                                        onClick={() => updateStat({ club })}
                                        aria-pressed={selected}
                                        className={cn(
                                          "flex min-h-11 items-center justify-center rounded-xl border px-3 text-sm font-bold transition-colors disabled:opacity-100",
                                          selected
                                            ? "border-primary bg-primary text-primary-foreground"
                                            : "border-border bg-card text-foreground hover:bg-accent/50",
                                        )}
                                      >
                                        {club}
                                      </button>
                                    )
                                  })}
                                </div>
                              ) : (
                                <Input
                                  id="club"
                                  value={currentStat.club}
                                  onChange={(e) => updateStat({ club: e.target.value })}
                                  placeholder="F.eks. Driver, 3-jern"
                                  disabled={readOnly}
                                  className="h-12 rounded-2xl text-base"
                                />
                              )}
                            </div>

                            {hole.par !== 3 && (
                              <DirectionCross
                                label="Utslag"
                                variant="horizontal"
                                centerLabel="Fairway"
                                value={currentStat.teeShot}
                                onChange={(v) => updateStat({ teeShot: v as TeeShot })}
                                readOnly={readOnly}
                              />
                            )}

                            <DirectionCross
                              label="Innspill"
                              variant="full"
                              centerLabel="Traff"
                              value={currentStat.approach}
                              onChange={(v) => updateStat({ approach: v as Approach })}
                              readOnly={readOnly}
                            />

                            <div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
                              <Stepper label="Chip" value={currentStat.chip} onChange={(v) => updateStat({ chip: v })} readOnly={readOnly} max={currentStat.strokes ?? undefined} />
                              <Stepper label="Bunker" value={currentStat.bunker} onChange={(v) => updateStat({ bunker: v })} readOnly={readOnly} max={currentStat.strokes ?? undefined} />
                              <Stepper label="Straffeslag" value={currentStat.penalty} onChange={(v) => updateStat({ penalty: v })} readOnly={readOnly} max={currentStat.strokes ?? undefined} />
                            </div>

                            <NumberPicker
                              label="Anywayslag"
                              value={currentStat.anywayStrokes}
                              directValues={[0, 1, 2, 3, 4, 5, 6]}
                              expandValues={[7, 8, 9, 10]}
                              expandLabel="7+"
                              maxValue={currentStat.strokes ?? undefined}
                              onChange={(v) => updateStat({ anywayStrokes: v })}
                              readOnly={readOnly}
                            />
                          </div>
                        )}
                      </div>
                    </>
                  )}
                </div>

                <div className="grid grid-cols-2 gap-3">
                  <Button type="button" variant="outline" onClick={goPrev} className="h-14 rounded-2xl text-base font-bold">
                    <ChevronLeft aria-hidden="true" className="size-5" />
                    Forrige
                  </Button>
                  <Button type="button" variant="outline" onClick={goNext} className="h-14 rounded-2xl text-base font-bold">
                    Neste hull
                    <ChevronRight aria-hidden="true" className="size-5" />
                  </Button>
                </div>
              </section>
            )}
          </>
        )}

      </main>
    </div>
  )
}

// --- Completed banner ------------------------------------------------------

function CompletedBanner({
  players,
  duration,
  roundId,
}: {
  players: Player[]
  duration: string | null
  roundId: string
}) {
  return (
    <div className="mb-6 flex flex-col gap-4 rounded-3xl border border-primary/30 bg-primary/10 p-5 sm:p-6">
      <div className="flex items-center gap-3">
        <div className="flex size-12 shrink-0 items-center justify-center rounded-2xl bg-primary text-primary-foreground shadow-sm">
          <Trophy aria-hidden="true" className="size-6" />
        </div>
        <div className="flex flex-col">
          <span className="text-xs font-semibold uppercase tracking-wide text-primary">Ferdigspilt</span>
          <h2 className="text-xl font-extrabold tracking-tight text-foreground">Runde fullført</h2>
        </div>
        {duration && (
          <div className="ml-auto flex flex-col items-end">
            <span className="text-xs font-medium text-muted-foreground">Tid brukt</span>
            <span className="flex items-center gap-1.5 text-lg font-extrabold tabular-nums text-foreground">
              <Clock aria-hidden="true" className="size-4 text-primary" />
              {duration}
            </span>
          </div>
        )}
      </div>
      <ul className="flex flex-col divide-y divide-primary/20 overflow-hidden rounded-2xl border border-primary/20 bg-card">
        {players.map((player) => {
          const counts = player.countsForHandicap && player.scoreDifferential !== null
          return (
            <li key={player.id} className="flex items-center justify-between gap-3 px-4 py-3">
              <span className="text-base font-bold text-foreground">{player.name}</span>
              {counts ? (
                <span className="flex items-baseline gap-1.5">
                  <span className="text-xs font-medium text-muted-foreground">Differensial</span>
                  <span className="text-lg font-extrabold tabular-nums text-foreground">
                    {player.scoreDifferential!.toFixed(1).replace(".", ",")}
                  </span>
                </span>
              ) : (
                <span className="text-sm font-semibold text-muted-foreground">Telte ikke mot HCP</span>
              )}
            </li>
          )
        })}
      </ul>
      <div className="flex flex-col gap-2 sm:flex-row">
        <Link
          href={`/my-rounds/${roundId}/scorecard`}
          className="flex min-h-12 flex-1 items-center justify-center gap-1.5 rounded-2xl bg-primary px-5 text-base font-bold text-primary-foreground shadow-sm transition-colors hover:bg-primary/90"
        >
          Se scorekort
          <ChevronRight aria-hidden="true" className="size-5" />
        </Link>
        <Link
          href={`/my-rounds/${roundId}/stats`}
          className="flex min-h-12 flex-1 items-center justify-center gap-1.5 rounded-2xl border border-primary/40 bg-card px-5 text-base font-bold text-foreground transition-colors hover:bg-accent/50"
        >
          Se full rundestatistikk
          <ChevronRight aria-hidden="true" className="size-5" />
        </Link>
      </div>
    </div>
  )
}

// --- Score så langt (kompakt linje + utvidbar full oversikt) --------------
// Etterspurt av bruker 2026-07-25 med referanse til en annen golf-app --
// samme "Vis full oversikt"-mønster som allerede etablert i
// session-scorecard.tsx (turnering-scoring), tilpasset til en enkelt
// spillers frittstående runde (ingen motstander-side å vise her).
//
// Utvidet SAMME DAG, etterspurt av bruker: netto-/stableford-sum, putt-/
// kølle-statistikk-totaler, og grafisk fairway-/innspill-fordeling +
// gjennomsnittlig score-til-par splittet på treff/bom. Appen har ingen
// egen "spilleform"-innstilling for frittstående runder -- stableford
// beregnes derfor alltid ut fra netto score der det er mulig (krever
// registrert HCP), uavhengig av om brukeren "egentlig" spiller slagspill
// eller stableford -- rent informativt, ikke en offisiell poengsum.

// Standard stableford-poengtabell: netto dobbel bogey eller dårligere = 0,
// netto par = 2, hvert slag bedre/dårligere enn par gir ett poeng mer/mindre.
function stablefordPoints(netScore: number, par: number): number {
  return Math.max(0, 2 - (netScore - par))
}

function avgScoreToPar(holes: ApiHole[]): number | null {
  const played = holes.filter((h) => h.played && h.score !== null)
  if (played.length === 0) return null
  const diffSum = played.reduce((sum, h) => sum + ((h.score as number) - h.par), 0)
  return diffSum / played.length
}

function formatSignedAvg(n: number): string {
  const fixed = Math.abs(n).toFixed(2)
  return n > 0 ? `+${fixed}` : n < 0 ? `-${fixed}` : fixed
}

function ScoreSoFar({ holes, order }: { holes: ApiHole[] | undefined; order: number[] }) {
  const [expanded, setExpanded] = useState(false)
  if (!holes || holes.length === 0) return null

  const orderedHoles = order
    .map((n) => holes.find((h) => h.hole_number === n))
    .filter((h): h is ApiHole => h !== undefined)
  const playedSoFar = orderedHoles.filter((h) => h.played && h.score !== null)
  if (playedSoFar.length === 0) return null

  const totalStrokes = playedSoFar.reduce((sum, h) => sum + (h.score ?? 0), 0)
  const totalPar = playedSoFar.reduce((sum, h) => sum + h.par, 0)
  const toPar = totalStrokes - totalPar
  const toParLabel = toPar === 0 ? "Par" : toPar > 0 ? `+${toPar}` : `${toPar}`

  // Netto/stableford krever at deltakeren har en beregnet course handicap
  // (strokes_received er da satt på ALLE hull, ellers på ingen) -- uten
  // det vises rett og slett ikke disse to (f.eks. en gjest uten HCP).
  const withNetto = playedSoFar.filter((h) => h.strokes_received !== null)
  const hasNetto = withNetto.length > 0
  const nettoTotal = hasNetto
    ? withNetto.reduce((sum, h) => sum + ((h.score as number) - (h.strokes_received as number)), 0)
    : null
  const stablefordTotal = hasNetto
    ? withNetto.reduce(
        (sum, h) => sum + stablefordPoints((h.score as number) - (h.strokes_received as number), h.par),
        0,
      )
    : null

  const puttsPlayed = orderedHoles.filter((h) => h.putts !== null)
  const puttsTotal = puttsPlayed.length > 0 ? puttsPlayed.reduce((sum, h) => sum + (h.putts as number), 0) : null
  const chipPlayed = orderedHoles.filter((h) => h.chip_count !== null)
  const chipTotal = chipPlayed.length > 0 ? chipPlayed.reduce((sum, h) => sum + (h.chip_count as number), 0) : null
  const bunkerPlayed = orderedHoles.filter((h) => h.bunker_shot_count !== null)
  const bunkerTotal =
    bunkerPlayed.length > 0 ? bunkerPlayed.reduce((sum, h) => sum + (h.bunker_shot_count as number), 0) : null
  const penaltyPlayed = orderedHoles.filter((h) => h.penalty_strokes !== null)
  const penaltyTotal =
    penaltyPlayed.length > 0 ? penaltyPlayed.reduce((sum, h) => sum + (h.penalty_strokes as number), 0) : null
  const anywayPlayed = orderedHoles.filter((h) => h.anyway_strokes !== null)
  const anywayTotal =
    anywayPlayed.length > 0 ? anywayPlayed.reduce((sum, h) => sum + (h.anyway_strokes as number), 0) : null

  // Fairwaytreff spores kun på hull som ikke er par 3 (samme regel som
  // registrerings-skjemaet -- DirectionCross for Utslag vises ikke der).
  const fairwayTracked = orderedHoles.filter((h) => h.par !== 3 && h.tee_shot_result !== null)
  const fairwayHit = fairwayTracked.filter((h) => h.tee_shot_result === "fairway")
  const fairwayLeft = fairwayTracked.filter((h) => h.tee_shot_result === "left")
  const fairwayRight = fairwayTracked.filter((h) => h.tee_shot_result === "right")
  const fairwayMiss = [...fairwayLeft, ...fairwayRight]
  const avgToParFairwayHit = avgScoreToPar(fairwayHit)
  const avgToParFairwayMiss = avgScoreToPar(fairwayMiss)

  const approachTracked = orderedHoles.filter((h) => h.approach_result !== null)
  const approachHit = approachTracked.filter((h) => h.approach_result === "hit")
  const approachShort = approachTracked.filter((h) => h.approach_result === "short")
  const approachLong = approachTracked.filter((h) => h.approach_result === "long")
  const approachLeft = approachTracked.filter((h) => h.approach_result === "left")
  const approachRight = approachTracked.filter((h) => h.approach_result === "right")
  const approachMiss = [...approachShort, ...approachLong, ...approachLeft, ...approachRight]
  const avgToParApproachHit = avgScoreToPar(approachHit)
  const avgToParApproachMiss = avgScoreToPar(approachMiss)

  let running = 0

  return (
    <div className="mb-4 overflow-hidden rounded-2xl border border-border bg-card">
      <button
        type="button"
        onClick={() => setExpanded((v) => !v)}
        aria-expanded={expanded}
        className="flex w-full items-center justify-between gap-3 px-4 py-3 text-left"
      >
        <span className="text-sm font-semibold text-foreground text-pretty">
          Så langt: {playedSoFar.length} hull · {totalStrokes} slag · {toParLabel} til par
          {hasNetto && ` · Netto ${nettoTotal! > 0 ? "+" : ""}${nettoTotal}`}
        </span>
        <span className="shrink-0 text-sm font-semibold text-primary">{expanded ? "Skjul" : "Vis full oversikt"}</span>
      </button>
      {expanded && (
        <div className="border-t border-border">
          <div className="grid grid-cols-2 gap-2 p-4 sm:grid-cols-3">
            <StatPill label="Slag" value={String(totalStrokes)} />
            <StatPill label="Til par" value={toParLabel} />
            {hasNetto && <StatPill label="Netto" value={nettoTotal! > 0 ? `+${nettoTotal}` : String(nettoTotal)} />}
            {hasNetto && <StatPill label="Stableford" value={`${stablefordTotal} poeng`} />}
            {puttsTotal !== null && <StatPill label="Putt" value={String(puttsTotal)} />}
            {chipTotal !== null && <StatPill label="Chip" value={String(chipTotal)} />}
            {bunkerTotal !== null && <StatPill label="Bunker" value={String(bunkerTotal)} />}
            {penaltyTotal !== null && <StatPill label="Straffeslag" value={String(penaltyTotal)} />}
            {anywayTotal !== null && <StatPill label="Anywayslag" value={String(anywayTotal)} />}
          </div>

          <div className="overflow-x-auto border-t border-border">
            <table className="w-full min-w-[420px] text-sm">
              <thead>
                <tr className="border-b border-border text-left text-xs font-bold uppercase tracking-wide text-muted-foreground">
                  <th className="px-3 py-2">Hull</th>
                  <th className="px-3 py-2">Par</th>
                  <th className="px-3 py-2">Score</th>
                  <th className="px-3 py-2">Netto</th>
                  {hasNetto && <th className="px-3 py-2">Stableford</th>}
                  <th className="px-3 py-2">Sum</th>
                </tr>
              </thead>
              <tbody>
                {orderedHoles.map((h) => {
                  const played = h.played && h.score !== null
                  if (played) running += h.score as number
                  const netto = played && h.strokes_received !== null ? (h.score as number) - h.strokes_received : null
                  const points = played && netto !== null ? stablefordPoints(netto, h.par) : null
                  return (
                    <tr key={h.hole_number} className="border-b border-border last:border-b-0">
                      <td className="px-3 py-2 font-bold tabular-nums text-foreground">{h.hole_number}</td>
                      <td className="px-3 py-2 tabular-nums text-muted-foreground">{h.par}</td>
                      <td className="px-3 py-2 tabular-nums font-semibold text-foreground">{played ? h.score : "–"}</td>
                      <td className="px-3 py-2 tabular-nums text-muted-foreground">{netto ?? "–"}</td>
                      {hasNetto && (
                        <td className="px-3 py-2 tabular-nums text-muted-foreground">{points ?? "–"}</td>
                      )}
                      <td className="px-3 py-2 tabular-nums font-bold text-foreground">{played ? running : "–"}</td>
                    </tr>
                  )
                })}
              </tbody>
            </table>
          </div>

          {fairwayTracked.length > 0 && (
            <div className="flex flex-col gap-2 border-t border-border p-4">
              <span className="text-sm font-bold text-foreground">Fairwaytreff</span>
              <DistributionBar
                segments={[
                  { label: "Fairway", count: fairwayHit.length, colorClass: "bg-emerald-500" },
                  { label: "Venstre", count: fairwayLeft.length, colorClass: "bg-amber-500" },
                  { label: "Høyre", count: fairwayRight.length, colorClass: "bg-rose-500" },
                ]}
              />
              {(avgToParFairwayHit !== null || avgToParFairwayMiss !== null) && (
                <div className="mt-1 flex flex-wrap gap-x-4 gap-y-1 text-xs font-semibold text-muted-foreground">
                  {avgToParFairwayHit !== null && (
                    <span>
                      Til par ved fairwaytreff:{" "}
                      <strong className="text-foreground">{formatSignedAvg(avgToParFairwayHit)}</strong>
                    </span>
                  )}
                  {avgToParFairwayMiss !== null && (
                    <span>
                      Til par ved bom på fairway:{" "}
                      <strong className="text-foreground">{formatSignedAvg(avgToParFairwayMiss)}</strong>
                    </span>
                  )}
                </div>
              )}
            </div>
          )}

          {approachTracked.length > 0 && (
            <div className="flex flex-col gap-2 border-t border-border p-4">
              <span className="text-sm font-bold text-foreground">Innspill</span>
              <DistributionBar
                segments={[
                  { label: "Traff", count: approachHit.length, colorClass: "bg-emerald-500" },
                  { label: "Kort", count: approachShort.length, colorClass: "bg-amber-500" },
                  { label: "Langt", count: approachLong.length, colorClass: "bg-orange-500" },
                  { label: "Venstre", count: approachLeft.length, colorClass: "bg-sky-500" },
                  { label: "Høyre", count: approachRight.length, colorClass: "bg-rose-500" },
                ]}
              />
              {(avgToParApproachHit !== null || avgToParApproachMiss !== null) && (
                <div className="mt-1 flex flex-wrap gap-x-4 gap-y-1 text-xs font-semibold text-muted-foreground">
                  {avgToParApproachHit !== null && (
                    <span>
                      Til par ved treff på innspill:{" "}
                      <strong className="text-foreground">{formatSignedAvg(avgToParApproachHit)}</strong>
                    </span>
                  )}
                  {avgToParApproachMiss !== null && (
                    <span>
                      Til par ved bom på innspill:{" "}
                      <strong className="text-foreground">{formatSignedAvg(avgToParApproachMiss)}</strong>
                    </span>
                  )}
                </div>
              )}
            </div>
          )}
        </div>
      )}
    </div>
  )
}

function StatPill({ label, value }: { label: string; value: string }) {
  return (
    <div className="flex flex-col items-center justify-center gap-0.5 rounded-xl border border-border bg-background px-2 py-2.5 text-center">
      <span className="text-lg font-extrabold tabular-nums text-foreground">{value}</span>
      <span className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">{label}</span>
    </div>
  )
}

// Ett rektangel delt i fargede segmenter proporsjonalt med antall --
// samme visuelle idé som SegmentedBar i tournament-leaderboard.tsx, men
// generalisert til N kategorier (ikke bare to lag).
function DistributionBar({ segments }: { segments: { label: string; count: number; colorClass: string }[] }) {
  const total = segments.reduce((sum, s) => sum + s.count, 0)
  if (total === 0) return null
  return (
    <div className="flex flex-col gap-2">
      <div className="flex h-5 w-full overflow-hidden rounded-full border border-border">
        {segments
          .filter((s) => s.count > 0)
          .map((s) => (
            <div key={s.label} aria-hidden="true" style={{ width: `${(s.count / total) * 100}%` }} className={s.colorClass} />
          ))}
      </div>
      <div className="flex flex-wrap gap-x-4 gap-y-1">
        {segments
          .filter((s) => s.count > 0)
          .map((s) => (
            <span key={s.label} className="flex items-center gap-1.5 text-xs font-semibold text-muted-foreground">
              <span aria-hidden="true" className={cn("inline-block size-2.5 rounded-full", s.colorClass)} />
              {s.label} {Math.round((s.count / total) * 100)}% ({s.count})
            </span>
          ))}
      </div>
    </div>
  )
}

// --- Player tabs -----------------------------------------------------------

function PlayerTabs({
  players,
  activePlayerId,
  onSelect,
  onRemove,
  onAdd,
  addOpen,
  readOnly,
}: {
  players: Player[]
  activePlayerId: string
  onSelect: (id: string) => void
  onRemove: (id: string) => void
  onAdd: () => void
  addOpen: boolean
  readOnly: boolean
}) {
  return (
    <div className="-mx-5 overflow-x-auto px-5">
      <div role="tablist" aria-label="Velg spiller" className="flex items-center gap-2">
        {players.map((player) => {
          const active = player.id === activePlayerId
          const canRemove = !player.isSelf && !readOnly
          return (
            <div
              key={player.id}
              className={cn(
                "flex min-h-12 shrink-0 items-center gap-1 rounded-2xl border pl-4 transition-colors",
                canRemove ? "pr-1.5" : "pr-4",
                active ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-foreground hover:bg-accent/50",
              )}
            >
              <button type="button" role="tab" aria-selected={active} onClick={() => onSelect(player.id)} className="text-base font-bold">
                {player.name}
              </button>
              {canRemove && (
                <button
                  type="button"
                  onClick={() => onRemove(player.id)}
                  aria-label={`Fjern ${player.name}`}
                  className={cn(
                    "flex size-8 shrink-0 items-center justify-center rounded-xl transition-colors",
                    active ? "text-primary-foreground/80 hover:bg-primary-foreground/20" : "text-muted-foreground hover:bg-accent",
                  )}
                >
                  <X aria-hidden="true" className="size-4" />
                </button>
              )}
            </div>
          )
        })}
        {!readOnly && (
          <button
            type="button"
            onClick={onAdd}
            aria-label="Legg til gjestespiller"
            aria-expanded={addOpen}
            className="flex min-h-12 shrink-0 items-center gap-1.5 rounded-2xl border border-dashed border-border bg-card px-4 text-base font-semibold text-foreground transition-colors hover:bg-accent/50"
          >
            <Plus aria-hidden="true" className="size-5" />
            Gjest
          </button>
        )}
      </div>
    </div>
  )
}

// --- Add guest form --------------------------------------------------------

function AddGuestForm({
  onAdd,
  onCancel,
}: {
  onAdd: (name: string, gender: Gender, hcp: number | null, statLevel: StatLevel) => void
  onCancel: () => void
}) {
  const [name, setName] = useState("")
  const [gender, setGender] = useState<Gender>("male")
  const [hcp, setHcp] = useState("")
  const [statLevel, setStatLevel] = useState<StatLevel>("strokes_only")

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    const trimmed = name.trim()
    if (!trimmed) return
    const parsedHcp = hcp.trim() === "" ? null : Number(hcp.replace(",", "."))
    onAdd(trimmed, gender, parsedHcp !== null && !Number.isNaN(parsedHcp) ? parsedHcp : null, statLevel)
  }

  return (
    <form onSubmit={handleSubmit} className="mt-3 flex flex-col gap-5 rounded-2xl border border-dashed border-border bg-card/50 p-4 sm:p-5">
      <div className="flex flex-col gap-2">
        <Label htmlFor="guest-name" className="text-base font-semibold">
          Navn
        </Label>
        <Input id="guest-name" autoFocus value={name} onChange={(e) => setName(e.target.value)} placeholder="Gjestens navn" className="h-12 rounded-2xl text-base" />
      </div>

      <ChoiceRow
        label="Kjønn"
        options={[
          { value: "male", label: "Mann" },
          { value: "female", label: "Kvinne" },
          { value: "other", label: "Annet" },
        ]}
        value={gender}
        onChange={(v) => setGender(v as Gender)}
        readOnly={false}
      />

      <div className="flex flex-col gap-2">
        <Label htmlFor="guest-hcp" className="text-base font-semibold">
          HCP <span className="font-normal text-muted-foreground">(valgfritt)</span>
        </Label>
        <Input
          id="guest-hcp"
          inputMode="decimal"
          type="number"
          step="0.1"
          value={hcp}
          onChange={(e) => setHcp(e.target.value)}
          placeholder="F.eks. 18"
          className="h-12 rounded-2xl text-base"
        />
      </div>

      <ChoiceRow
        label="Statistikk for denne spilleren"
        options={[
          { value: "strokes_only", label: "Kun slag" },
          { value: "strokes_and_putts", label: "Slag og putter" },
          { value: "full", label: "All statistikk" },
        ]}
        value={statLevel}
        onChange={(v) => setStatLevel(v as StatLevel)}
        readOnly={false}
      />

      <div className="flex gap-3">
        <Button type="submit" className="h-12 flex-1 rounded-2xl text-base font-bold">
          Legg til
        </Button>
        <Button type="button" variant="ghost" onClick={onCancel} className="h-12 rounded-2xl px-6 text-base font-semibold">
          Avbryt
        </Button>
      </div>
    </form>
  )
}

// --- Rediger runde (bane/utslag/antall hull) -------------------------------
// Retter opp feil bane eller feil antall hull underveis -- rører ALDRI
// allerede registrerte slag/putter/etc. (backend garanterer dette, se
// PATCH /rounds/{id}). Kun tilgjengelig før runden er fullført.

type ApiFacility = { slug: string; name: string; city: string | null; county: string | null }
type ApiOfficialCourseOption = { teeoff_course_id: number; name: string; is_main_course: boolean; tees: { name: string }[] }
type ApiPersonalCourseSummary = { id: string; name: string }
type ApiPersonalCourseDetail = { id: string; name: string; tees: { name: string }[] }

// Konverterer til/fra <input type="datetime-local">-verdiformatet
// ("YYYY-MM-DDTHH:mm", lokal tid) -- selve lagringen skjer som ekte
// tidsstempler (UTC), kun visningen er lokal.
function toDatetimeLocalValue(iso: string | null): string {
  if (!iso) return ""
  const d = new Date(iso)
  if (Number.isNaN(d.getTime())) return ""
  const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000)
  return local.toISOString().slice(0, 16)
}
function fromDatetimeLocalValue(value: string): string | null {
  if (!value) return null
  const d = new Date(value)
  return Number.isNaN(d.getTime()) ? null : d.toISOString()
}

function EditRoundPanel({
  round,
  onPatch,
  onClose,
}: {
  round: ApiRound
  onPatch: (body: Record<string, unknown>) => Promise<{ ok: true } | { ok: false; message: string }>
  onClose: () => void
}) {
  const isCompleted = round.completed_at !== null
  const [name, setName] = useState(round.name ?? "")
  const [holesPlanned, setHolesPlanned] = useState<9 | 18>(round.holes_planned === 9 ? 9 : 18)
  const [startHole, setStartHole] = useState(String(round.start_hole))
  const [startedAt, setStartedAt] = useState(toDatetimeLocalValue(round.started_at))
  const [completedAt, setCompletedAt] = useState(toDatetimeLocalValue(round.completed_at))
  const [saving, setSaving] = useState(false)
  const [showChangeCourse, setShowChangeCourse] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const hasMetadataChanges =
    name.trim() !== (round.name ?? "") ||
    (!isCompleted && (holesPlanned !== round.holes_planned || Number(startHole) !== round.start_hole)) ||
    toDatetimeLocalValue(round.started_at) !== startedAt ||
    (isCompleted && toDatetimeLocalValue(round.completed_at) !== completedAt)

  async function saveMetadata() {
    setSaving(true)
    setError(null)
    const body: Record<string, unknown> = {}
    // Tom streng betyr "fjern navnet" (backend-kontrakt) -- send den derfor
    // alltid med når feltet faktisk er endret, aldri utelatt for å tømme.
    if (name.trim() !== (round.name ?? "")) body.name = name.trim()
    if (!isCompleted) {
      if (holesPlanned !== round.holes_planned) body.holes_planned = holesPlanned
      if (Number(startHole) !== round.start_hole) body.start_hole = Number(startHole)
    }
    if (toDatetimeLocalValue(round.started_at) !== startedAt) body.started_at = fromDatetimeLocalValue(startedAt)
    if (isCompleted && toDatetimeLocalValue(round.completed_at) !== completedAt) {
      body.completed_at = fromDatetimeLocalValue(completedAt)
    }
    const result = await onPatch(body)
    if (!result.ok) setError(result.message)
    setSaving(false)
  }

  return (
    <div className="flex flex-col gap-4 rounded-2xl border border-border bg-card p-4 sm:p-5">
      <div className="flex items-center justify-between gap-2">
        <span className="text-sm font-bold text-foreground">Rediger runde</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>}

      <div className="flex flex-col gap-2">
        <Label htmlFor="edit-round-name" className="text-sm font-semibold">
          Navn på runden <span className="font-normal text-muted-foreground">(valgfritt)</span>
        </Label>
        <Input
          id="edit-round-name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          placeholder={round.course_name_snapshot}
          maxLength={200}
          className="h-11 rounded-xl text-base"
        />
      </div>

      {!isCompleted && (
        <>
          <div className="flex flex-col gap-2">
            <span className="text-sm font-semibold text-foreground">Antall hull</span>
            <div className="flex gap-2">
              {([9, 18] as const).map((n) => (
                <button
                  key={n}
                  type="button"
                  onClick={() => setHolesPlanned(n)}
                  aria-pressed={holesPlanned === n}
                  className={cn(
                    "h-11 flex-1 rounded-xl border text-base font-bold transition-colors",
                    holesPlanned === n
                      ? "border-primary bg-primary text-primary-foreground"
                      : "border-border bg-background text-foreground hover:bg-accent/50",
                  )}
                >
                  {n} hull
                </button>
              ))}
            </div>
          </div>

          <div className="flex flex-col gap-2">
            <Label htmlFor="edit-start-hole" className="text-sm font-semibold">
              Starthull
            </Label>
            <select
              id="edit-start-hole"
              value={startHole}
              onChange={(e) => setStartHole(e.target.value)}
              className="h-11 rounded-xl border border-border bg-background px-3 text-base font-semibold text-foreground"
            >
              {Array.from({ length: 18 }, (_, i) => i + 1).map((n) => (
                <option key={n} value={String(n)}>
                  Hull {n}
                </option>
              ))}
            </select>
          </div>
        </>
      )}

      <div className="flex flex-col gap-2">
        <Label htmlFor="edit-started-at" className="text-sm font-semibold">
          Utslagstid
        </Label>
        <Input
          id="edit-started-at"
          type="datetime-local"
          value={startedAt}
          onChange={(e) => setStartedAt(e.target.value)}
          className="h-11 rounded-xl text-base"
        />
      </div>

      {isCompleted && (
        <div className="flex flex-col gap-2">
          <Label htmlFor="edit-completed-at" className="text-sm font-semibold">
            Fullført-tidspunkt
          </Label>
          <Input
            id="edit-completed-at"
            type="datetime-local"
            value={completedAt}
            onChange={(e) => setCompletedAt(e.target.value)}
            className="h-11 rounded-xl text-base"
          />
        </div>
      )}

      {hasMetadataChanges && (
        <Button type="button" disabled={saving} onClick={saveMetadata} className="h-11 rounded-xl text-sm font-bold">
          {saving ? "Lagrer…" : "Lagre endringer"}
        </Button>
      )}

      {!isCompleted && (
        <div className="border-t border-border pt-4">
          {showChangeCourse ? (
            <ChangeCourseForm onPatch={onPatch} onCancel={() => setShowChangeCourse(false)} />
          ) : (
            <button
              type="button"
              onClick={() => setShowChangeCourse(true)}
              className="inline-flex min-h-11 items-center gap-1.5 text-sm font-semibold text-primary"
            >
              <Search aria-hidden="true" className="size-4" />
              Bytt bane
            </button>
          )}
        </div>
      )}
    </div>
  )
}

function ChangeCourseForm({
  onPatch,
  onCancel,
}: {
  onPatch: (body: Record<string, unknown>) => Promise<{ ok: true } | { ok: false; message: string }>
  onCancel: () => void
}) {
  const [source, setSource] = useState<"teeoff" | "custom" | null>(null)
  const [error, setError] = useState<string | null>(null)
  const [submitting, setSubmitting] = useState(false)

  // Teeoff-søk
  const [query, setQuery] = useState("")
  const [facilities, setFacilities] = useState<ApiFacility[] | null>(null)
  const [selectedFacility, setSelectedFacility] = useState<ApiFacility | null>(null)
  const [officialCourses, setOfficialCourses] = useState<ApiOfficialCourseOption[] | null>(null)
  const [selectedOfficialCourse, setSelectedOfficialCourse] = useState<ApiOfficialCourseOption | null>(null)

  // Egen bane-søk
  const [customQuery, setCustomQuery] = useState("")
  const [customResults, setCustomResults] = useState<ApiPersonalCourseSummary[]>([])
  const [selectedCustomCourse, setSelectedCustomCourse] = useState<ApiPersonalCourseDetail | null>(null)

  const [teeName, setTeeName] = useState("")

  useEffect(() => {
    if (source !== "custom") return
    let cancelled = false
    const timer = setTimeout(async () => {
      try {
        const res = await fetch(`/personal-courses?q=${encodeURIComponent(customQuery.trim())}`, { credentials: "include" })
        if (res.ok && !cancelled) setCustomResults(await res.json())
      } catch {
        // Stille -- listen blir bare uendret, ingen kritisk feil å vise her.
      }
    }, 250)
    return () => {
      cancelled = true
      clearTimeout(timer)
    }
  }, [source, customQuery])

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

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

  async function pickCustomCourse(course: ApiPersonalCourseSummary) {
    setError(null)
    try {
      const res = await fetch(`/personal-courses/${course.id}`, { credentials: "include" })
      if (!res.ok) throw new Error()
      setSelectedCustomCourse(await res.json())
    } catch {
      setError("Klarte ikke å hente banedetaljer.")
    }
  }

  const availableTees = selectedOfficialCourse?.tees ?? selectedCustomCourse?.tees ?? []

  async function handleSubmit() {
    if (!teeName) return
    setSubmitting(true)
    setError(null)
    const body =
      source === "teeoff" && selectedFacility && selectedOfficialCourse
        ? {
            course_source: "teeoff",
            teeoff_facility_slug: selectedFacility.slug,
            teeoff_course_id: selectedOfficialCourse.teeoff_course_id,
            tee_name: teeName,
          }
        : selectedCustomCourse
          ? { course_source: "custom", personal_course_id: selectedCustomCourse.id, tee_name: teeName }
          : null
    if (!body) {
      setSubmitting(false)
      return
    }
    const result = await onPatch(body)
    setSubmitting(false)
    if (!result.ok) {
      setError(result.message)
      return
    }
    onCancel()
  }

  return (
    <div className="flex flex-col gap-3">
      <p className="text-xs leading-relaxed text-muted-foreground text-pretty">
        Endrer kun banen og utslaget videre -- allerede registrerte slag/putter for spilte hull røres ikke.
      </p>
      {error && <p className="text-sm font-medium text-destructive">{error}</p>}

      {source === null && (
        <div className="grid grid-cols-2 gap-2">
          <Button type="button" variant="outline" onClick={() => setSource("teeoff")} className="h-11 rounded-xl text-sm font-bold">
            Offisiell bane
          </Button>
          <Button type="button" variant="outline" onClick={() => setSource("custom")} className="h-11 rounded-xl text-sm font-bold">
            Egen bane
          </Button>
        </div>
      )}

      {source === "teeoff" && !selectedFacility && (
        <div className="flex flex-col gap-2">
          <div className="flex gap-2">
            <Input
              autoFocus
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === "Enter") {
                  e.preventDefault()
                  searchFacilities()
                }
              }}
              placeholder="Søk klubbnavn…"
              className="h-11 flex-1 rounded-xl text-base"
            />
            <Button type="button" onClick={searchFacilities} className="h-11 shrink-0 rounded-xl text-sm font-bold">
              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-3 py-2.5 text-left 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-3 py-2.5 text-center text-sm text-muted-foreground">Ingen treff.</li>}
            </ul>
          )}
        </div>
      )}

      {source === "teeoff" && selectedFacility && !selectedOfficialCourse && (
        <div className="flex flex-col gap-2">
          <button type="button" onClick={() => setSelectedFacility(null)} className="self-start text-sm font-semibold text-muted-foreground">
            ← {selectedFacility.name}
          </button>
          {officialCourses === null ? (
            <p className="text-sm text-muted-foreground">Laster baner…</p>
          ) : (
            <ul className="flex flex-col overflow-hidden rounded-xl border border-border">
              {officialCourses.map((c) => (
                <li key={c.teeoff_course_id} className="border-b border-border last:border-b-0">
                  <button
                    type="button"
                    onClick={() => setSelectedOfficialCourse(c)}
                    className="flex w-full items-center justify-between px-3 py-2.5 text-left text-sm font-semibold text-foreground hover:bg-accent/60"
                  >
                    {c.name}
                  </button>
                </li>
              ))}
            </ul>
          )}
        </div>
      )}

      {source === "custom" && !selectedCustomCourse && (
        <div className="flex flex-col gap-2">
          <Input
            autoFocus
            value={customQuery}
            onChange={(e) => setCustomQuery(e.target.value)}
            placeholder="Søk egendefinert bane…"
            className="h-11 rounded-xl text-base"
          />
          <ul className="flex max-h-56 flex-col overflow-auto rounded-xl border border-border">
            {customResults.map((c) => (
              <li key={c.id} className="border-b border-border last:border-b-0">
                <button
                  type="button"
                  onClick={() => pickCustomCourse(c)}
                  className="flex w-full px-3 py-2.5 text-left text-sm font-semibold text-foreground hover:bg-accent/60"
                >
                  {c.name}
                </button>
              </li>
            ))}
            {customResults.length === 0 && (
              <li className="px-3 py-2.5 text-center text-sm text-muted-foreground">Ingen treff.</li>
            )}
          </ul>
        </div>
      )}

      {(selectedOfficialCourse || selectedCustomCourse) && (
        <div className="flex flex-col gap-3">
          <span className="text-sm font-bold text-foreground">
            {selectedOfficialCourse?.name ?? selectedCustomCourse?.name}
          </span>
          <div className="flex flex-col gap-2">
            <span className="text-sm font-semibold text-foreground">Utslag</span>
            <div className="flex flex-wrap gap-2">
              {availableTees.map((t) => (
                <button
                  key={t.name}
                  type="button"
                  onClick={() => setTeeName(t.name)}
                  aria-pressed={teeName === t.name}
                  className={cn(
                    "h-10 rounded-lg border px-3 text-sm font-semibold transition-colors",
                    teeName === t.name
                      ? "border-primary bg-primary text-primary-foreground"
                      : "border-border bg-background text-foreground hover:bg-accent/50",
                  )}
                >
                  {t.name}
                </button>
              ))}
            </div>
          </div>
          <Button type="button" disabled={submitting || !teeName} onClick={handleSubmit} className="h-11 rounded-xl text-sm font-bold">
            {submitting ? "Bytter bane…" : "Bytt bane"}
          </Button>
        </div>
      )}

      <Button type="button" variant="ghost" onClick={onCancel} className="h-10 self-start rounded-xl text-sm font-semibold text-muted-foreground">
        Avbryt
      </Button>
    </div>
  )
}

// --- Hole navigation -------------------------------------------------------

function HoleNav({
  holes,
  currentHole,
  isPlayed,
  onSelect,
}: {
  holes: Hole[]
  currentHole: number
  isPlayed: (holeNumber: number) => boolean
  onSelect: (holeNumber: number) => void
}) {
  return (
    <nav aria-label="Velg hull" className="-mx-5 mt-5 overflow-x-auto px-5">
      <div className="flex gap-2 pb-1">
        {holes.map((h) => {
          const active = h.holeNumber === currentHole
          const played = isPlayed(h.holeNumber)
          return (
            <button
              key={h.holeNumber}
              type="button"
              onClick={() => onSelect(h.holeNumber)}
              aria-current={active ? "true" : undefined}
              aria-label={`Hull ${h.holeNumber}${played ? ", spilt" : ""}`}
              className={cn(
                "flex size-12 shrink-0 items-center justify-center rounded-2xl border text-base font-bold tabular-nums transition-colors",
                active
                  ? "border-primary bg-primary text-primary-foreground"
                  : played
                    ? "border-primary/40 bg-primary/10 text-foreground hover:bg-primary/20"
                    : "border-border bg-card text-foreground hover:bg-accent/50",
              )}
            >
              {h.holeNumber}
            </button>
          )
        })}
      </div>
    </nav>
  )
}

// --- Statistikknivå-velger ---------------------------------------------------
// "Hullet er spilt" fantes tidligere som egen avkrysning, men var reelt
// overflødig -- score settes allerede automatisk til "spilt" idet et
// slagtall velges (se onChange på Slag-NumberPicker under). Fjernet
// 2026-07-24 på brukerens eksplisitte bekreftelse.

function StatLevelPicker({ value, onChange }: { value: StatLevel; onChange: (v: StatLevel) => void }) {
  const options: { value: StatLevel; label: string }[] = [
    { value: "strokes_only", label: "Kun slag" },
    { value: "strokes_and_putts", label: "Slag og putter" },
    { value: "full", label: "All statistikk" },
  ]
  return (
    <div className="mt-3 flex flex-wrap items-center gap-2">
      <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Statistikk</span>
      <div className="flex gap-1.5">
        {options.map((opt) => (
          <button
            key={opt.value}
            type="button"
            onClick={() => onChange(opt.value)}
            aria-pressed={value === opt.value}
            className={cn(
              "rounded-full px-3 py-1.5 text-xs font-bold transition-colors",
              value === opt.value
                ? "bg-primary text-primary-foreground"
                : "bg-muted text-muted-foreground hover:bg-accent",
            )}
          >
            {opt.label}
          </button>
        ))}
      </div>
    </div>
  )
}

// --- Number picker ---------------------------------------------------------

function NumberPicker({
  label,
  value,
  directValues,
  expandValues,
  expandLabel,
  parValue,
  maxValue,
  onChange,
  readOnly,
}: {
  label: string
  value: number | null
  directValues: number[]
  expandValues: number[]
  expandLabel: string
  // Merker knappen som tilsvarer hullets par med en liten "par"-bildetekst
  // (kun brukt for Slag-velgeren) -- gjør det tydelig hva som er par uten
  // å måtte huske det fra hull-overskriften.
  parValue?: number
  // Skjuler valg høyere enn dette (f.eks. Putter kan aldri overstige
  // antall slag registrert på hullet) -- ingen vits i å tilby et tall som
  // uansett ville vært selvmotsigende.
  maxValue?: number
  onChange: (value: number) => void
  readOnly: boolean
}) {
  const cap = (values: number[]) => (maxValue === undefined ? values : values.filter((n) => n <= maxValue))
  const cappedDirect = cap(directValues)
  const cappedExpand = cap(expandValues)

  const valueInExpand = value !== null && cappedExpand.includes(value)
  const [expanded, setExpanded] = useState(false)
  const showExpanded = expanded || valueInExpand

  const visibleValues = showExpanded ? [...cappedDirect, ...cappedExpand] : cappedDirect

  return (
    <div className="flex flex-col gap-2">
      <span className="text-base font-semibold text-foreground">{label}</span>
      {/* Numpad: tre kolonner, knappene fyller bredden -- store trykkflater
          for bruk utendørs, ofte med sol på skjermen (V0-runde 2026-07-24). */}
      <div className="grid max-w-sm grid-cols-3 gap-2.5">
        {visibleValues.map((n) => {
          const selected = value === n
          const isPar = n === parValue
          return (
            <button
              key={n}
              type="button"
              disabled={readOnly}
              onClick={() => onChange(n)}
              aria-label={isPar ? `${label} ${n}, par` : `${label} ${n}`}
              aria-pressed={selected}
              className={cn(
                "flex h-16 flex-col items-center justify-center gap-0 rounded-2xl border text-2xl font-extrabold leading-none tabular-nums transition-colors disabled:opacity-100",
                selected
                  ? "border-primary bg-primary text-primary-foreground shadow-sm"
                  : "border-border bg-card text-foreground hover:bg-accent/50",
                readOnly && !selected && "opacity-40",
              )}
            >
              <span>{n}</span>
              {isPar && (
                <span className={cn("text-[11px] font-bold leading-none", selected ? "text-primary-foreground/80" : "text-muted-foreground")}>
                  par
                </span>
              )}
            </button>
          )
        })}
        {!showExpanded && !readOnly && cappedExpand.length > 0 && (
          <button
            type="button"
            onClick={() => setExpanded(true)}
            className="col-span-3 flex h-14 items-center justify-center rounded-2xl border border-dashed border-border bg-card text-lg font-bold text-foreground transition-colors hover:bg-accent/50"
          >
            {expandLabel}
          </button>
        )}
      </div>
    </div>
  )
}

// --- Retningskors (D-pad for Utslag / Innspill) -----------------------------

function DirButton({
  icon: Icon,
  label,
  selected,
  onClick,
  readOnly,
}: {
  icon: typeof ArrowUp
  label: string
  selected: boolean
  onClick: () => void
  readOnly: boolean
}) {
  return (
    <button
      type="button"
      disabled={readOnly}
      onClick={onClick}
      aria-pressed={selected}
      className={cn(
        "flex min-h-16 flex-col items-center justify-center gap-1 rounded-2xl border px-2 py-2 transition-colors disabled:opacity-100",
        selected
          ? "border-primary bg-primary text-primary-foreground shadow-sm"
          : "border-border bg-card text-foreground hover:bg-accent/50",
        readOnly && !selected && "opacity-40",
      )}
    >
      <Icon aria-hidden="true" className="size-6" />
      <span className="text-sm font-bold">{label}</span>
    </button>
  )
}

function DirectionCross({
  label,
  value,
  onChange,
  readOnly,
  variant,
  centerLabel,
}: {
  label: string
  value: string | null
  onChange: (value: string) => void
  readOnly: boolean
  // "horizontal" = kun venstre/senter/høyre (Utslag); "full" = 5-veis (Innspill)
  variant: "horizontal" | "full"
  centerLabel: string
}) {
  const spacer = <div aria-hidden="true" />
  return (
    <div className="flex flex-col gap-2">
      <span className="text-base font-semibold text-foreground">{label}</span>
      <div className="grid max-w-sm grid-cols-3 gap-2.5">
        {variant === "full" && (
          <>
            {spacer}
            <DirButton icon={ArrowUp} label="Langt" selected={value === "long"} onClick={() => onChange("long")} readOnly={readOnly} />
            {spacer}
          </>
        )}
        <DirButton icon={ArrowLeft} label="Venstre" selected={value === "left"} onClick={() => onChange("left")} readOnly={readOnly} />
        <DirButton
          icon={Target}
          label={centerLabel}
          selected={value === (variant === "full" ? "hit" : "fairway")}
          onClick={() => onChange(variant === "full" ? "hit" : "fairway")}
          readOnly={readOnly}
        />
        <DirButton icon={ArrowRight} label="Høyre" selected={value === "right"} onClick={() => onChange("right")} readOnly={readOnly} />
        {variant === "full" && (
          <>
            {spacer}
            <DirButton icon={ArrowDown} label="Kort" selected={value === "short"} onClick={() => onChange("short")} readOnly={readOnly} />
            {spacer}
          </>
        )}
      </div>
    </div>
  )
}

// --- Choice row (segmented buttons) ----------------------------------------

function ChoiceRow({
  label,
  options,
  value,
  onChange,
  readOnly,
}: {
  label: string
  options: { value: string; label: string }[]
  value: string | null
  onChange: (value: string) => void
  readOnly: boolean
}) {
  return (
    <div className="flex flex-col gap-2">
      <span className="text-base font-semibold text-foreground">{label}</span>
      <div className="flex flex-wrap gap-2">
        {options.map((opt) => {
          const selected = value === opt.value
          return (
            <button
              key={opt.value}
              type="button"
              disabled={readOnly}
              onClick={() => onChange(opt.value)}
              aria-pressed={selected}
              className={cn(
                "flex min-h-12 flex-1 items-center justify-center rounded-2xl border px-4 text-base font-bold transition-colors disabled:opacity-100",
                selected ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-foreground hover:bg-accent/50",
                readOnly && !selected && "opacity-40",
              )}
            >
              {opt.label}
            </button>
          )
        })}
      </div>
    </div>
  )
}

// --- Stepper (+/-) ---------------------------------------------------------

function Stepper({
  label,
  value,
  onChange,
  readOnly,
  max,
}: {
  label: string
  value: number
  onChange: (value: number) => void
  readOnly: boolean
  // Kan ikke telle høyere enn antall slag registrert på hullet -- f.eks.
  // umulig å ha chippet flere ganger enn totalt antall slag.
  max?: number
}) {
  const atMax = max !== undefined && value >= max
  return (
    <div className="flex flex-col gap-2">
      <span className="text-base font-semibold text-foreground">{label}</span>
      <div className="flex items-center justify-between gap-2 rounded-2xl border border-border bg-card p-1.5">
        <button
          type="button"
          disabled={readOnly || value <= 0}
          onClick={() => onChange(Math.max(0, value - 1))}
          aria-label={`Færre ${label}`}
          className="flex size-11 shrink-0 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40"
        >
          <Minus aria-hidden="true" className="size-5" />
        </button>
        <span className="min-w-8 text-center text-xl font-extrabold tabular-nums text-foreground">{value}</span>
        <button
          type="button"
          disabled={readOnly || atMax}
          onClick={() => onChange(value + 1)}
          aria-label={`Flere ${label}`}
          className="flex size-11 shrink-0 items-center justify-center rounded-xl border border-border bg-background text-foreground transition-colors hover:bg-accent/50 disabled:opacity-40"
        >
          <Plus aria-hidden="true" className="size-5" />
        </button>
      </div>
    </div>
  )
}
