"use client"

// Leaderboard for en frittstående runde (ADR-033), 2026-07-26.
// Håndkodet (ikke via V0 -- brukeren gikk tom for credits, ba meg bygge
// direkte etter samme designspesifikasjon som var skrevet til V0-prompten,
// se FEATURE_BACKLOG.md). Gjenbruker etablerte mønstre fra
// round-scorecard.tsx (signedToPar-formatering, form-før-farge-språket fra
// ScoreMark, stableford-formelen) og round-stats.tsx/round-scorecard.tsx sin
// WS-sanntid-/viewerId-oppkobling, i stedet for å finne opp nye.
//
// Radene er BEVISST minimale i kollapset tilstand (kun rangering/navn/tall,
// 2026-07-26 -- brukeren ba om å fjerne "Deg"/"Eier"/pokal/hull-spilt-
// teksten derfra og la radene flyte sømløst i hverandre uten egne
// kort-rammer) -- den informasjonen flyttet inn i den utvidbare seksjonen
// sammen med hullscorene, ikke fjernet helt.

import { useEffect, useState } from "react"
import Link from "next/link"
import { ArrowLeft, ChevronDown, ChevronRight, Trophy } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { cn } from "@/lib/utils"

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

type ApiLeaderboardHole = {
  hole_number: number
  par: number
  stroke_index: number
  played: boolean
  score: number | null
  picked_up: boolean
  strokes_received: number | null
}

type ApiLeaderboardEntry = {
  participant_id: string
  display_name: string
  is_owner: boolean
  holes_played: number
  total_score: number | null
  score_to_par: number | null
  net_score_to_par: number | null
  total_points: number | null
  holes: ApiLeaderboardHole[]
}

type ApiLeaderboard = {
  holes_planned: number
  completed: boolean
  entries: ApiLeaderboardEntry[]
}

type ApiRoundParticipant = {
  id: string
  user_id: string | null
}

type ApiSide = {
  id: string
  label: string | null
}

type ApiRoundHeader = {
  id: string
  name: string | null
  course_name_snapshot: string
  played_at: string
  start_hole: number
  holes_planned: number
  play_format: string
  sides: ApiSide[]
  participants: ApiRoundParticipant[]
}

// To-sidede formater har ingen individuell "rangering" -- to lag/spillere i
// en match har en STATUS (hvem leder, hull for hull), ikke en topp-3-liste.
// Delt-ball-formatene (foursome/greensome/scramble) har i tillegg ALDRI
// noen individuell round_hole-rad å rangere fra (ADR-039 Beslutning C: én
// score PER SIDE) -- den vanlige brutto/netto/poeng-rangeringen under ville
// derfor vist en tom/meningsløs liste for dem uansett.
const TWO_SIDED_FORMATS = [
  "match", "fourball", "foursome", "greensome", "scramble_2", "scramble_4", "chapman", "high_low_high",
]

const dateFormatter = new Intl.DateTimeFormat("no-NO", { day: "numeric", month: "long", year: "numeric" })

// --- Rangering ---------------------------------------------------------------
// Delt plassering (likt resultat) skal vises som "T-N", ikke to ulike tall.
// Deltakere uten et tall for gjeldende visning (brutto/netto/poeng) havner
// samlet nederst, uten rangeringstall -- IKKE skjult, IKKE en krasj.
// Poeng (stableford) rangeres SYNKENDE (høyest vinner) -- motsatt av
// brutto/netto til-par (lavest vinner).

type Mode = "gross" | "net" | "points"

type RankedEntry = ApiLeaderboardEntry & {
  rank: number | null
  tied: boolean
  isSelf: boolean
}

function valueFor(entry: ApiLeaderboardEntry, mode: Mode): number | null {
  if (mode === "gross") return entry.score_to_par
  if (mode === "net") return entry.net_score_to_par
  return entry.total_points
}

function rankEntries(entries: ApiLeaderboardEntry[], mode: Mode, viewerParticipantId: string | null): RankedEntry[] {
  const withValue = entries.filter((e) => valueFor(e, mode) !== null)
  const withoutValue = entries.filter((e) => valueFor(e, mode) === null)
  const direction = mode === "points" ? -1 : 1
  withValue.sort((a, b) => direction * ((valueFor(a, mode) as number) - (valueFor(b, mode) as number)))

  const ranked: RankedEntry[] = []
  let rank = 0
  let prevValue: number | null = null
  for (let i = 0; i < withValue.length; i++) {
    const e = withValue[i]
    const v = valueFor(e, mode) as number
    if (v !== prevValue) {
      rank = i + 1
      prevValue = v
    }
    const tied = withValue.filter((o) => valueFor(o, mode) === v).length > 1
    ranked.push({ ...e, rank, tied, isSelf: e.participant_id === viewerParticipantId })
  }
  for (const e of withoutValue) {
    ranked.push({ ...e, rank: null, tied: false, isSelf: e.participant_id === viewerParticipantId })
  }
  return ranked
}

function formatToPar(value: number): string {
  if (value === 0) return "E"
  return value > 0 ? `+${value}` : `−${Math.abs(value)}`
}

// --- Verdi-merke (brutto/netto til-par, eller poeng) ------------------------
// Samme "form + farge, aldri kun farge"-språk som ScoreMark i
// round-scorecard.tsx: sirkel = under par, firkant = over par, ren tekst ved
// jevnt med par. Poeng har ingen retning (høyere er alltid bedre) -- én fast
// sirkelform, men samme visuelle vekt/fyll-mønster for lederen. Lederen
// (emphasize) får en FYLT variant for litt ekstra visuell vekt.

function ToParMark({ value, emphasize }: { value: number; emphasize: boolean }) {
  if (value === 0) {
    return <span className="text-lg font-extrabold tabular-nums text-foreground">E</span>
  }
  const under = value < 0
  return (
    <span
      className={cn(
        "flex h-10 min-w-10 shrink-0 items-center justify-center px-2 text-base font-extrabold tabular-nums",
        under ? "rounded-full" : "rounded-[6px]",
        emphasize
          ? under
            ? "bg-primary text-primary-foreground"
            : "bg-brand-orange text-brand-orange-foreground"
          : under
            ? "border-2 border-primary bg-primary/10 text-primary"
            : "border-2 border-brand-orange bg-brand-orange/10 text-brand-orange",
      )}
    >
      {formatToPar(value)}
    </span>
  )
}

function PointsMark({ value, emphasize }: { value: number; emphasize: boolean }) {
  return (
    <span
      className={cn(
        "flex h-10 min-w-10 shrink-0 items-center justify-center rounded-full px-2 text-base font-extrabold tabular-nums",
        emphasize ? "bg-primary text-primary-foreground" : "border-2 border-primary bg-primary/10 text-primary",
      )}
    >
      {value}
    </span>
  )
}

function ValueMark({ mode, value, emphasize }: { mode: Mode; value: number; emphasize: boolean }) {
  return mode === "points" ? <PointsMark value={value} emphasize={emphasize} /> : <ToParMark value={value} emphasize={emphasize} />
}

function RankBadge({ rank, tied, isLeader }: { rank: number | null; tied: boolean; isLeader: boolean }) {
  return (
    <span
      className={cn(
        "flex size-10 shrink-0 items-center justify-center rounded-full text-sm font-extrabold tabular-nums",
        isLeader ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground",
      )}
      aria-hidden="true"
    >
      {rank !== null ? (tied ? `T-${rank}` : `#${rank}`) : "–"}
    </span>
  )
}

function ModeToggle({ mode, onChange }: { mode: Mode; onChange: (m: Mode) => void }) {
  const options: { value: Mode; label: string }[] = [
    { value: "gross", label: "Brutto" },
    { value: "net", label: "Netto" },
    { value: "points", label: "Poeng" },
  ]
  return (
    <div role="tablist" aria-label="Brutto, netto eller poeng" className="inline-flex gap-1 rounded-full bg-muted p-1">
      {options.map((opt) => (
        <button
          key={opt.value}
          type="button"
          role="tab"
          aria-selected={mode === opt.value}
          onClick={() => onChange(opt.value)}
          className={cn(
            "min-h-9 rounded-full px-3.5 text-sm font-bold transition-colors",
            mode === opt.value ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground",
          )}
        >
          {opt.label}
        </button>
      ))}
    </div>
  )
}

// --- Hullscorer per deltaker (2026-07-26, etterspurt av bruker) -----------
// Samme "form + farge, aldri kun farge"-visuelle språk som ScoreMark i
// round-scorecard.tsx (sirkel/firkant/fylt-ved-2+-av) -- egen, liten
// lokal versjon her (ikke en delt import) siden round-scorecard.tsx sin
// ScoreMark er skreddersydd for ETT hull om gangen i en tabell, ikke en
// kompakt horisontal strip for MANGE deltakere i en liste.

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

function classifyHoleScore(gross: number, par: number): HoleScoreClass {
  const diff = gross - par
  if (diff <= -2) return "eagle"
  if (diff === -1) return "birdie"
  if (diff === 0) return "par"
  if (diff === 1) return "bogey"
  return "double"
}

// Netto/poeng PER HULL (2026-07-26, etterspurt av bruker etter å ha vist
// referansebilder fra en konkurrentapp -- bevisst IKKE en kopi av den appens
// layout/farger, kun det strukturelle prinsippet "brutto fremhevet øverst,
// netto/poeng plassert rolig under" gjenskapt i TeeCups egen stil).
// `strokes_received` er allerede backend sin ferdigberegnede allokering
// (samme som resten av appen) -- ingen egen slagfordeling regnes ut her.

function netForHole(hole: ApiLeaderboardHole): number | null {
  if (!hole.played || hole.score === null || hole.strokes_received === null) return null
  return hole.score - hole.strokes_received
}

function pointsForHole(hole: ApiLeaderboardHole): number | null {
  const net = netForHole(hole)
  if (net === null) return null
  return Math.max(0, hole.par - net + 2)
}

function HoleMark({ hole, secondaryValue }: { hole: ApiLeaderboardHole; secondaryValue: number | null | undefined }) {
  const shared = "flex size-7 items-center justify-center text-xs font-extrabold tabular-nums"
  // "Plukket opp" (migrasjon 038, Stableford) -- 0 poeng er impliserte,
  // vises som "PU" i stedet for det underliggende Net Double Bogey-tallet.
  const markClass = hole.picked_up
    ? "rounded-[4px] border-2 border-brand-orange bg-brand-orange/10 text-[10px] text-brand-orange"
    : !hole.played || hole.score === null
      ? "text-muted-foreground"
      : (() => {
          const kind = classifyHoleScore(hole.score, hole.par)
          if (kind === "eagle") return "rounded-full bg-primary text-primary-foreground"
          if (kind === "birdie") return "rounded-full border-2 border-primary bg-primary/10 text-primary"
          if (kind === "bogey") return "rounded-[4px] border-2 border-brand-orange bg-brand-orange/10 text-brand-orange"
          if (kind === "double") return "rounded-[4px] bg-brand-orange text-brand-orange-foreground"
          return "font-bold text-foreground"
        })()
  return (
    <span className="flex w-8 shrink-0 flex-col items-center gap-0.5">
      <span aria-hidden="true" className="text-[10px] font-semibold text-muted-foreground">
        {hole.hole_number}
      </span>
      {/* Brutto -- fremhevet, samme "form + farge"-språk som resten av
          appen (fylt/farget merke), øverst -- den fremste raden. */}
      <span className={cn(shared, markClass)}>
        {hole.picked_up ? "PU" : hole.played && hole.score !== null ? hole.score : "–"}
      </span>
      {/* Netto/poeng -- rolig, umerket tekst RETT under, samme prinsipp
          som referansebildene (aldri fremhevet på samme måte som brutto).
          Utelates helt i brutto-modus (ingenting nytt å vise der). */}
      {secondaryValue !== undefined && (
        <span className="text-[11px] font-semibold tabular-nums text-muted-foreground">
          {secondaryValue !== null ? secondaryValue : "–"}
        </span>
      )}
    </span>
  )
}

// Par/Hcp-referanserad (2026-07-29, etterspurt av bruker -- manglet helt)
// -- vist ÉN gang for hele leaderboardet (par/stroke-index er banedata,
// identisk for alle deltakere), i stedet for gjentatt i hver enkelt
// deltakers utvidbare HoleStrip. Samme kolonnebredde (w-8) som HoleMark,
// slik at radene visuelt stemmer overens med hver deltakers strip under.
function HoleReferenceStrip({
  holes,
  startHole,
  holesPlanned,
}: {
  holes: ApiLeaderboardHole[]
  startHole: number
  holesPlanned: number
}) {
  const order = Array.from({ length: holesPlanned }, (_, i) => ((startHole - 1 + i) % 18) + 1)
  const ordered = order.map((n) => holes.find((h) => h.hole_number === n)).filter((h): h is ApiLeaderboardHole => h !== undefined)
  if (ordered.length === 0) return null
  return (
    <div className="flex flex-col gap-1 rounded-2xl border border-border bg-card p-3">
      <span className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">Hull · Par · Hcp</span>
      <div className="-mx-1 overflow-x-auto px-1">
        <div className="flex gap-1.5">
          {ordered.map((h) => (
            <span key={h.hole_number} className="flex w-8 shrink-0 flex-col items-center gap-0.5">
              <span className="text-[10px] font-semibold text-muted-foreground">{h.hole_number}</span>
              <span className="text-xs font-bold tabular-nums text-foreground">{h.par}</span>
              <span className="text-[10px] font-medium tabular-nums text-muted-foreground">{h.stroke_index}</span>
            </span>
          ))}
        </div>
      </div>
    </div>
  )
}

function HoleStrip({
  holes,
  startHole,
  holesPlanned,
  mode,
}: {
  holes: ApiLeaderboardHole[]
  startHole: number
  holesPlanned: number
  mode: Mode
}) {
  // Samme sirkulære start_hole-rekkefølge som round-detail.tsx/
  // round-scorecard.tsx/round-stats.tsx -- viser hullene i FAKTISK
  // spillerekkefølge, ikke nødvendigvis rå hullnummer 1-18.
  const order = Array.from({ length: holesPlanned }, (_, i) => ((startHole - 1 + i) % 18) + 1)
  const ordered = order.map((n) => holes.find((h) => h.hole_number === n)).filter((h): h is ApiLeaderboardHole => h !== undefined)
  const secondaryLabel = mode === "net" ? "Netto" : mode === "points" ? "Poeng" : null
  return (
    <div className="flex flex-col gap-1">
      {secondaryLabel && (
        <span className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
          Slag · {secondaryLabel}
        </span>
      )}
      <div className="-mx-1 overflow-x-auto px-1 pb-0.5">
        <div className="flex gap-1.5">
          {ordered.map((h) => (
            <HoleMark
              key={h.hole_number}
              hole={h}
              secondaryValue={mode === "net" ? netForHole(h) : mode === "points" ? pointsForHole(h) : undefined}
            />
          ))}
        </div>
      </div>
    </div>
  )
}

// --- Matchstatus for to-sidede formater (2026-07-28) ------------------------
// En match/fourball/foursome/greensome/scramble har ingen individuell
// rangering -- kun en status (hvem leder, hull for hull). Egen, liten
// versjon her (samme mønster som HoleMark over -- lokal duplisering
// fremfor cross-file-import for et lite presentasjons-stykke), ikke en
// full brutto/netto-tabell (den finnes allerede på scorekort-siden sin
// "Matchforløp"-seksjon -- lenket til herfra i stedet for duplisert).

type ApiFormatResult = {
  play_format: string
  ready: boolean
  match_lead: number | null
  match_holes_played: number | null
  match_holes_remaining: number | null
  match_is_closed: boolean | null
  match_is_dormie: boolean | null
  match_status_text: string | null
  hole_results: string[] | null
}

// Territorium-bar (2026-07-29, samme visuelle språk som identitetsbanneret i
// round-detail.tsx/round-scorecard.tsx/session-scorecard.tsx) -- den ledende
// sidens sone strekker seg proporsjonalt forbi midtlinjen. Ingen navn her
// (vises allerede andre steder på leaderboard-siden), kun fargesonene +
// den sentrerte statuspillen, for full visuell konsistens på tvers av alle
// steder en matchstatus vises.
function leadZoneFraction(lead: number, totalHoles: number): number {
  if (totalHoles <= 0) return 0.5
  const fraction = 0.5 + (lead / totalHoles) * 0.5
  return Math.min(0.82, Math.max(0.18, fraction))
}

function useFormatResult(roundId: string, refreshKey: number): ApiFormatResult | null {
  const [result, setResult] = useState<ApiFormatResult | null>(null)
  useEffect(() => {
    let cancelled = false
    fetch(`/rounds/${roundId}/format-result`, { credentials: "include" })
      .then((res) => (res.ok ? res.json() : null))
      .then((data: ApiFormatResult | null) => {
        if (!cancelled) setResult(data)
      })
      .catch(() => {})
    return () => {
      cancelled = true
    }
  }, [roundId, refreshKey])
  return result
}

// match_status_text bruker bokstavelig "(A)"/"(B)" -- disse tilsvarer alltid
// round.sides[0]/[1] (begge sortert på id, samme rekkefølge som backend sin
// egen sortering av side-id-er). Erstatt med det faktiske side-navnet.
function substituteSideLabels(text: string, sides: ApiSide[]): string {
  const labelFor = (idx: number) => sides[idx]?.label?.trim() || `Side ${idx === 0 ? "A" : "B"}`
  return text.replace(/\(A\)/, `(${labelFor(0)})`).replace(/\(B\)/, `(${labelFor(1)})`)
}

function MatchStatusSection({ roundId, sides, refreshKey }: { roundId: string; sides: ApiSide[]; refreshKey: number }) {
  const result = useFormatResult(roundId, refreshKey)
  if (!result) return null

  if (!result.ready) {
    return (
      <div className="rounded-3xl border border-dashed border-border bg-card p-4 text-sm text-muted-foreground sm:p-5">
        Venter på at begge sider er komplette og har beregnet handicap før matchstatus kan vises.
      </div>
    )
  }

  const statusText = result.match_status_text ? substituteSideLabels(result.match_status_text, sides) : null
  const lead = result.match_lead ?? 0
  const holesPlayed = result.match_holes_played ?? 0
  const holesRemaining = result.match_holes_remaining ?? 0
  const totalHoles = holesPlayed + holesRemaining
  const tone: "a" | "b" | "neutral" = lead > 0 ? "a" : lead < 0 ? "b" : "neutral"
  const widthA = holesPlayed > 0 ? leadZoneFraction(lead, totalHoles) * 100 : 50
  const widthB = 100 - widthA
  const zoneAClass = tone === "a" ? "bg-primary" : tone === "b" ? "bg-primary/12" : "bg-muted"
  const zoneBClass = tone === "b" ? "bg-brand-orange" : tone === "a" ? "bg-brand-orange/12" : "bg-muted"

  return (
    <div className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-4 shadow-md shadow-black/8 sm:p-5">
      <div className="flex flex-wrap items-center justify-between gap-2">
        <span className="text-sm font-bold text-muted-foreground">Matchstatus</span>
        {result.match_is_closed && <Badge variant="default">Avgjort</Badge>}
        {result.match_is_dormie && !result.match_is_closed && <Badge variant="outline">Dormie</Badge>}
      </div>
      <div
        className="grid h-20 w-full overflow-hidden rounded-2xl"
        style={{ gridTemplateColumns: `minmax(0, ${widthA}fr) auto minmax(0, ${widthB}fr)` }}
      >
        <div className={cn("h-full transition-[grid-template-columns] duration-300", zoneAClass)} />
        <div className="flex h-full items-center justify-center px-3">
          <div className="flex flex-col items-center rounded-xl border border-border/60 bg-card/95 px-3 py-1.5 shadow-sm shadow-black/10 backdrop-blur-sm">
            {statusText ? (
              <>
                <span
                  className={cn(
                    "text-xl font-extrabold leading-none tabular-nums sm:text-2xl",
                    tone === "a" ? "text-primary" : tone === "b" ? "text-brand-orange" : "text-foreground",
                  )}
                >
                  {statusText}
                </span>
                <span className="mt-1 text-[11px] font-semibold text-muted-foreground">
                  {holesPlayed} hull spilt{holesRemaining > 0 && `, ${holesRemaining} igjen`}
                </span>
              </>
            ) : (
              <span className="text-xs font-bold text-muted-foreground text-pretty">Ingen hull spilt ennå</span>
            )}
          </div>
        </div>
        <div className={cn("h-full transition-[grid-template-columns] duration-300", zoneBClass)} />
      </div>
      {result.hole_results && result.hole_results.length > 0 && (
        <ul className="flex flex-wrap gap-1">
          {result.hole_results.map((r, i) => (
            <li
              key={i}
              aria-label={
                r === "halved"
                  ? `Hull ${i + 1}: delt`
                  : `Hull ${i + 1}: ${r === "a" ? sides[0]?.label?.trim() || "Side A" : sides[1]?.label?.trim() || "Side B"} vant`
              }
              className={cn(
                "flex size-7 items-center justify-center rounded-full text-xs font-extrabold",
                r === "a" && "bg-primary text-primary-foreground",
                r === "b" && "bg-brand-orange text-brand-orange-foreground",
                r === "halved" && "border border-border bg-muted text-muted-foreground",
              )}
            >
              {r === "halved" ? "½" : r.toUpperCase()}
            </li>
          ))}
        </ul>
      )}
      <Link
        href={`/my-rounds/${roundId}/scorecard`}
        className="text-sm font-semibold text-primary underline underline-offset-2"
      >
        Se hull for hull med brutto/netto →
      </Link>
    </div>
  )
}

// --- Full leaderboard-side -----------------------------------------------

function useLeaderboardData(roundId: string) {
  const [round, setRound] = useState<ApiRoundHeader | null>(null)
  const [leaderboard, setLeaderboard] = useState<ApiLeaderboard | null>(null)
  const [viewerId, setViewerId] = useState<string | null>(null)
  const [error, setError] = useState<string | null>(null)
  // Sanntid (samme "noe endret seg, hent på nytt"-mønster som
  // round-stats.tsx/round-scorecard.tsx sin refreshKey).
  const [refreshKey, setRefreshKey] = useState(0)

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

  useEffect(() => {
    const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"
    const socket = new WebSocket(`${protocol}//${window.location.host}/ws/rounds/${roundId}/live`)
    socket.onmessage = () => setRefreshKey((k) => k + 1)
    return () => socket.close()
  }, [roundId])

  useEffect(() => {
    let cancelled = false
    async function load() {
      const [roundRes, leaderboardRes] = await Promise.all([
        fetch(`/rounds/${roundId}`, { credentials: "include" }),
        fetch(`/rounds/${roundId}/leaderboard`, { credentials: "include" }),
      ])
      if (roundRes.status === 403 || roundRes.status === 404) {
        if (!cancelled) setError("Denne runden finnes ikke, eller du har ikke tilgang til den.")
        return
      }
      if (!roundRes.ok || !leaderboardRes.ok) {
        if (!cancelled) setError("Klarte ikke å hente leaderboardet. Prøv igjen om litt.")
        return
      }
      const roundData: ApiRoundHeader = await roundRes.json()
      const leaderboardData: ApiLeaderboard = await leaderboardRes.json()
      if (!cancelled) {
        setRound(roundData)
        setLeaderboard(leaderboardData)
      }
    }
    void load()
    return () => {
      cancelled = true
    }
  }, [roundId, refreshKey])

  const viewerParticipantId = round?.participants.find((p) => p.user_id === viewerId)?.id ?? null

  return { round, leaderboard, viewerParticipantId, error, refreshKey }
}

const MODE_MISSING_LABEL: Record<Mode, string> = {
  gross: "",
  net: "netto-tall",
  points: "poengsum",
}

export function RoundLeaderboard({ roundId }: { roundId: string }) {
  const { round, leaderboard, viewerParticipantId, error, refreshKey } = useLeaderboardData(roundId)
  const [mode, setMode] = useState<Mode>("gross")
  // Hullscorer vises som en utvidbar strip PER deltaker (2026-07-26,
  // etterspurt av bruker) -- kollapset som default (samme "trekkspill
  // starter lukket"-konvensjon som round-stats.tsx sine StatCard-seksjoner)
  // slik at listen forblir kompakt og skalerer til mange spillere.
  const [expandedId, setExpandedId] = useState<string | null>(null)

  if (error) {
    return (
      <div className="flex min-h-dvh 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 || !leaderboard) {
    return (
      <div className="flex min-h-dvh 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>
    )
  }

  const ranked = rankEntries(leaderboard.entries, mode, viewerParticipantId)
  const isTwoSided = TWO_SIDED_FORMATS.includes(round.play_format)

  return (
    <div className="min-h-dvh bg-background">
      <header className="sticky top-0 z-10 border-b border-border bg-background/95 backdrop-blur">
        <div className="mx-auto flex min-h-14 max-w-xl items-center gap-2 px-4 py-2">
          <Link
            href={`/my-rounds/${roundId}`}
            className="flex min-h-11 items-center gap-1.5 rounded-xl pr-3 text-base font-bold text-foreground transition-colors hover:text-primary"
          >
            <ArrowLeft aria-hidden="true" className="size-6" />
            Leaderboard
          </Link>
        </div>
      </header>

      <main className="mx-auto flex max-w-xl flex-col gap-4 px-4 py-5 pb-16">
        <div className="flex flex-col gap-1 rounded-3xl border border-border bg-card p-4 shadow-md shadow-black/8">
          <span className="text-xl font-extrabold tracking-tight text-foreground text-balance">
            {round.name?.trim() || round.course_name_snapshot}
          </span>
          <span className="text-sm font-semibold text-muted-foreground">
            {round.name?.trim() ? `${round.course_name_snapshot} · ` : ""}
            {round.holes_planned} hull · {dateFormatter.format(new Date(round.played_at))}
          </span>
        </div>

        {/* To-sidede formater (match/fourball/foursome/greensome/scramble)
            har ingen individuell rangering å vise -- kun en matchstatus
            (2026-07-28, se MatchStatusSection over). Delt-ball-formatene
            har i tillegg ALDRI individuelle round_hole-rader (ADR-039
            Beslutning C), så den vanlige listen under ville uansett vært
            tom for dem. */}
        {isTwoSided ? (
          <MatchStatusSection roundId={roundId} sides={round.sides} refreshKey={refreshKey} />
        ) : (
          <>
            <div className="flex items-center justify-between gap-3">
              <h2 className="text-base font-bold text-foreground">Stilling</h2>
              <ModeToggle mode={mode} onChange={setMode} />
            </div>

            {leaderboard.entries[0]?.holes && (
              <HoleReferenceStrip
                holes={leaderboard.entries[0].holes}
                startHole={round.start_hole}
                holesPlanned={leaderboard.holes_planned}
              />
            )}

            {/* Én sammenhengende liste (2026-07-26, etterspurt av bruker) --
            IKKE separate kort med mellomrom lenger. Kun ytterkanten er
            avrundet/rammet inn; radene skilles med en tynn linje
            (divide-y) og flyter sømløst i hverandre. En utvidet rad får en
            svak bakgrunnstone som eneste "dette er åpnet"-signal. */}
        <ul className="divide-y divide-border overflow-hidden rounded-2xl border border-border bg-card">
          {ranked.map((entry) => {
            const value = valueFor(entry, mode)
            const isLeader = entry.rank === 1
            const expanded = expandedId === entry.participant_id
            return (
              <li key={entry.participant_id} className={cn(expanded && "bg-muted/30")}>
                <button
                  type="button"
                  onClick={() => setExpandedId((prev) => (prev === entry.participant_id ? null : entry.participant_id))}
                  aria-expanded={expanded}
                  aria-label={`Vis detaljer for ${entry.display_name}`}
                  className="flex w-full items-center gap-3 p-3 text-left transition-colors hover:bg-accent/30 sm:p-4"
                >
                  <RankBadge rank={entry.rank} tied={entry.tied} isLeader={isLeader} />
                  <span className="min-w-0 flex-1 truncate text-base font-bold text-foreground">{entry.display_name}</span>
                  {value !== null ? (
                    <ValueMark mode={mode} value={value} emphasize={isLeader} />
                  ) : (
                    <span className="shrink-0 text-sm font-semibold text-muted-foreground">–</span>
                  )}
                  <ChevronDown
                    aria-hidden="true"
                    className={cn("size-5 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-180")}
                  />
                </button>

                {expanded && (
                  <div className="flex flex-col gap-2 px-3 pb-3 pt-0.5 sm:px-4">
                    {/* Det som lå i selve raden før 2026-07-26 (Deg/Eier/
                        leder-ikon/fremdrift) -- fortsatt tilgjengelig, bare
                        flyttet hit slik at den kollapsede listen forblir
                        ren "rangering, navn, tall". */}
                    <div className="flex flex-wrap items-center gap-1.5">
                      {entry.isSelf && <Badge variant="default">Deg</Badge>}
                      {entry.is_owner && <Badge variant="outline">Eier</Badge>}
                      {isLeader && <Trophy aria-hidden="true" className="size-4 text-primary" />}
                      <span className="text-sm text-muted-foreground">
                        {leaderboard.completed
                          ? "Ferdig"
                          : entry.holes_played > 0
                            ? `${entry.holes_played}/${leaderboard.holes_planned} hull spilt`
                            : "Ikke startet"}
                      </span>
                    </div>
                    {entry.holes_played > 0 ? (
                      <HoleStrip holes={entry.holes} startHole={round.start_hole} holesPlanned={leaderboard.holes_planned} mode={mode} />
                    ) : (
                      <p className="py-1 text-sm text-muted-foreground">Ingen hull registrert ennå.</p>
                    )}
                  </div>
                )}
              </li>
            )
          })}
        </ul>

            {mode !== "gross" && ranked.some((e) => e.rank === null) && (
              <p className="text-sm text-muted-foreground">
                Spillere uten {MODE_MISSING_LABEL[mode]} (ingen HCP registrert for runden) vises uten rangering.
              </p>
            )}
          </>
        )}
      </main>
    </div>
  )
}

// --- Kompakt mini-variant, til rundens egen detaljside --------------------
// Topp 3 (brutto) + "og N til", lenke til hele leaderboardet. Kun relevant
// for runder med mer enn én deltaker.

export function RoundLeaderboardMini({ roundId }: { roundId: string }) {
  const { round, leaderboard, viewerParticipantId } = useLeaderboardData(roundId)

  if (!round || !leaderboard || round.participants.length < 2) return null

  // To-sidede formater (match/fourball/foursome/greensome/scramble) vises
  // allerede av FormatResultPanel rett under denne komponenten sitt
  // kallsted i round-detail.tsx -- ingen individuell brutto-rangering å
  // vise her (og for delt-ball-formatene ville leaderboard.entries uansett
  // alltid vært tom, ADR-039 Beslutning C).
  if (TWO_SIDED_FORMATS.includes(round.play_format)) return null

  const ranked = rankEntries(leaderboard.entries, "gross", viewerParticipantId)
  const top = ranked.slice(0, 3)
  const restCount = ranked.length - top.length

  return (
    <Link
      href={`/my-rounds/${roundId}/leaderboard`}
      className="mb-5 flex flex-col gap-2 rounded-2xl border border-border bg-card p-4 transition-colors hover:border-primary/50 hover:bg-accent/30"
    >
      <span className="flex items-center justify-between gap-2">
        <span className="flex items-center gap-1.5 text-sm font-bold text-foreground">
          <Trophy aria-hidden="true" className="size-4 text-primary" />
          Leaderboard
        </span>
        <ChevronRight aria-hidden="true" className="size-5 text-muted-foreground" />
      </span>
      <span className="flex flex-col gap-1">
        {top.map((entry) => (
          <span key={entry.participant_id} className="flex items-center gap-2 text-sm">
            <span className="w-8 shrink-0 font-bold tabular-nums text-muted-foreground">
              {entry.rank !== null ? (entry.tied ? `T-${entry.rank}` : `#${entry.rank}`) : "–"}
            </span>
            <span className="min-w-0 flex-1 truncate font-semibold text-foreground">{entry.display_name}</span>
            <span className="shrink-0 tabular-nums text-muted-foreground">
              {entry.score_to_par !== null ? formatToPar(entry.score_to_par) : "–"}
            </span>
          </span>
        ))}
      </span>
      {restCount > 0 && <span className="text-sm text-muted-foreground">og {restCount} til</span>}
    </Link>
  )
}
