"use client"

import type React from "react"
import { useEffect, useMemo, useState } from "react"
import Link from "next/link"
import { ArrowLeft, Clock, EyeOff, Lock, Mail, PartyPopper, Plus, Trophy, Users, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"

// --- Types (matcher API-kontrakten i app/routers/tournaments.py/matches.py/courses.py)

// Fire nye org-lagturnering-formater (2026-07-30) -- se tournament-program.tsx.
type Format =
  | "foursome"
  | "greensome"
  | "scramble_2"
  | "scramble_4"
  | "fourball"
  | "singles"
  | "chapman"
  | "shamble"
  | "money_ball"
  | "high_low_high"
type HoleConfig = "full_18" | "front_9" | "back_9"
type ScoringMode = "stroke" | "hole_result"

type ApiTeam = { id: string; name: string; color: string | null }

type ApiRosterEntry = {
  id: string
  player_id: string
  display_name: string
  handicap_index_snapshot: number | null
  is_captain: boolean
  // Konkurranseklasse (2026-08-03) -- brukt KUN til å forhåndsutfylle riktig
  // utslag under, se AddSlotForm. Ingen egen resultatliste her.
  class_id: string | null
  class_name: string | null
}

// ADR-029: kjønn hører til RATINGEN, ikke selve utslaget -- riktig
// kjønnsspesifikk rating løses automatisk server-side ut fra spilleren, så
// frontend trenger kun å vise/velge blant de fysiske utslagene.
type ApiTee = {
  id: string
  name: string
}

type ApiTournamentClass = {
  id: string
  name: string
  default_tee_id: string | null
  default_tee_name: string | null
}

type ApiSession = {
  id: string
  name: string | null
  format: Format
  hole_config: HoleConfig
  course_id: string
  points_per_match: number
  scoring_mode: ScoringMode
  locked_team_ids: string[]
}

type ApiParticipant = {
  id: string
  team_side: "a" | "b"
  team_roster_id: string
  player_name: string
  tee_id: string
  tee_name: string
  // Money Ball (2026-07-30) -- fast plass i rotasjonsrekkefølgen (0-3).
  lineup_order: number | null
}

type ApiMatch = {
  id: string
  sequence: number
  team_a_id: string
  team_b_id: string
  status_text: string | null
  points_side_a: number | null
  points_side_b: number | null
  leading_side: "a" | "b" | null
  tee_time: string | null
  participants: ApiParticipant[]
}

// --- Labels ------------------------------------------------------------------

const FORMAT_LABELS: Record<Format, string> = {
  foursome: "Foursome",
  greensome: "Greensome",
  scramble_2: "Scramble (2)",
  scramble_4: "Scramble (4)",
  fourball: "Fourball",
  singles: "Singel",
  chapman: "Chapman",
  shamble: "Shamble",
  money_ball: "Money Ball",
  high_low_high: "High-low-high",
}
const HOLE_LABELS: Record<HoleConfig, string> = { full_18: "18 hull", front_9: "Front 9", back_9: "Back 9" }
const SCORING_LABELS: Record<ScoringMode, string> = { stroke: "Slag for slag", hole_result: "Kun hullresultat" }

// Minimum påkrevd per side FØR laget kan låses (brukt av teamIsComplete OG
// av "N spillere per side"-teksten). Shamble er BEVISST forskjellig fra sitt
// eget tak (se maxSlotsPerSide) -- fleksibel 2-4, ingen fast antall.
function slotsPerSide(format: Format): number {
  if (format === "singles") return 1
  if (format === "scramble_4" || format === "money_ball") return 4
  if (format === "shamble") return 2
  return 2
}

// Maks antall AddSlotForm skal tillate å legge til (backend sin egen
// 409 LIMIT_REACHED-grense) -- kun Shamble skiller seg fra slotsPerSide.
function maxSlotsPerSide(format: Format): number {
  return format === "shamble" ? 4 : slotsPerSide(format)
}

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

export function SessionBlindDraw({
  organizationId,
  tournamentId,
  sessionId,
  tournamentName,
}: {
  organizationId: string
  tournamentId: string
  sessionId: string
  tournamentName: string
}) {
  const [session, setSession] = useState<ApiSession | null>(null)
  const [teams, setTeams] = useState<[ApiTeam, ApiTeam] | null>(null)
  const [rosters, setRosters] = useState<Record<string, ApiRosterEntry[]>>({})
  const [tees, setTees] = useState<ApiTee[]>([])
  const [classes, setClasses] = useState<ApiTournamentClass[]>([])
  const [matches, setMatches] = useState<ApiMatch[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [confirmingLock, setConfirmingLock] = useState<string | null>(null)

  useEffect(() => {
    let cancelled = false
    async function load() {
      try {
        const [sessionsRes, teamsRes] = await Promise.all([
          fetch(`/orgs/${organizationId}/tournaments/${tournamentId}/sessions`, { credentials: "include" }),
          fetch(`/orgs/${organizationId}/tournaments/${tournamentId}/teams`, { credentials: "include" }),
        ])
        if (!sessionsRes.ok || !teamsRes.ok) throw new Error("load failed")
        const sessionsData: ApiSession[] = await sessionsRes.json()
        const foundSession = sessionsData.find((s) => s.id === sessionId)
        if (!foundSession) throw new Error("session not found")
        const teamsData: ApiTeam[] = await teamsRes.json()
        if (teamsData.length !== 2) throw new Error("expected 2 teams")

        const [rosterARes, rosterBRes, teesRes, matchesRes, classesRes] = await Promise.all([
          fetch(`/orgs/${organizationId}/teams/${teamsData[0].id}/roster`, { credentials: "include" }),
          fetch(`/orgs/${organizationId}/teams/${teamsData[1].id}/roster`, { credentials: "include" }),
          fetch(`/orgs/${organizationId}/courses/${foundSession.course_id}/tees`, { credentials: "include" }),
          fetch(`/orgs/${organizationId}/sessions/${sessionId}/matches`, { credentials: "include" }),
          fetch(`/orgs/${organizationId}/tournaments/${tournamentId}/classes`, { credentials: "include" }),
        ])
        if (!rosterARes.ok || !rosterBRes.ok || !teesRes.ok || !matchesRes.ok) throw new Error("load failed")

        if (cancelled) return
        setSession(foundSession)
        setTeams([teamsData[0], teamsData[1]])
        setRosters({
          [teamsData[0].id]: await rosterARes.json(),
          [teamsData[1].id]: await rosterBRes.json(),
        })
        setTees(await teesRes.json())
        setMatches(await matchesRes.json())
        if (classesRes.ok) setClasses(await classesRes.json())
      } catch {
        if (!cancelled) setError("Klarte ikke å laste blind draw-siden. Prøv å laste siden på nytt.")
      } finally {
        if (!cancelled) setLoading(false)
      }
    }
    void load()
    return () => {
      cancelled = true
    }
  }, [organizationId, tournamentId, sessionId])

  const sortedMatches = useMemo(() => [...matches].sort((a, b) => a.sequence - b.sequence), [matches])
  const nextSequence = useMemo(
    () => (matches.length === 0 ? 1 : Math.max(...matches.map((m) => m.sequence)) + 1),
    [matches],
  )

  const bothLocked = (session?.locked_team_ids.length ?? 0) >= 2

  async function refetchMatches() {
    const res = await fetch(`/orgs/${organizationId}/sessions/${sessionId}/matches`, { credentials: "include" })
    if (res.ok) setMatches(await res.json())
  }

  async function addMatch() {
    if (!teams) return
    setError(null)
    try {
      const res = await fetch(`/orgs/${organizationId}/sessions/${sessionId}/matches`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ sequence: nextSequence, team_a_id: teams[0].id, team_b_id: teams[1].id }),
      })
      if (!res.ok) throw new Error(`create match: ${res.status}`)
      const created: ApiMatch = await res.json()
      setMatches((prev) => [...prev, created])
    } catch {
      setError("Klarte ikke å legge til match. Prøv igjen.")
    }
  }

  async function addParticipant(
    matchId: string,
    teamSide: "a" | "b",
    teamRosterId: string,
    teeId: string,
    lineupOrder: number | null = null,
  ) {
    setError(null)
    try {
      const res = await fetch(`/orgs/${organizationId}/matches/${matchId}/participants`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({
          team_side: teamSide,
          team_roster_id: teamRosterId,
          tee_id: teeId,
          lineup_order: lineupOrder,
        }),
      })
      if (!res.ok) throw new Error(`add participant: ${res.status}`)
      const created: ApiParticipant = await res.json()
      setMatches((prev) =>
        prev.map((m) => (m.id === matchId ? { ...m, participants: [...m.participants, created] } : m)),
      )
    } catch {
      setError("Klarte ikke å legge til spilleren. Kanskje laget allerede har låst, eller du mangler tilgang.")
    }
  }

  async function removeParticipant(matchId: string, participantId: string) {
    setError(null)
    try {
      const res = await fetch(`/orgs/${organizationId}/matches/${matchId}/participants/${participantId}`, {
        method: "DELETE",
        credentials: "include",
      })
      if (!res.ok && res.status !== 204) throw new Error(`remove participant: ${res.status}`)
      setMatches((prev) =>
        prev.map((m) =>
          m.id === matchId
            ? { ...m, participants: m.participants.filter((p) => p.id !== participantId) }
            : m,
        ),
      )
    } catch {
      setError("Klarte ikke å fjerne spilleren. Prøv igjen.")
    }
  }

  async function lockTeam(teamId: string) {
    setError(null)
    try {
      const res = await fetch(`/orgs/${organizationId}/sessions/${sessionId}/lock`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ team_id: teamId }),
      })
      if (!res.ok) throw new Error(`lock: ${res.status}`)
      setSession((prev) =>
        prev ? { ...prev, locked_team_ids: [...prev.locked_team_ids, teamId] } : prev,
      )
      // Motstanderens deltakere er skjult server-side inntil begge har låst
      // (ADR-013) -- hent matchene på nytt for å få den avslørte visningen
      // hvis dette var det andre laget som nettopp låste.
      await refetchMatches()
    } catch {
      setError("Klarte ikke å låse oppstillingen. Prøv igjen.")
    } finally {
      setConfirmingLock(null)
    }
  }

  const detailHref = `/tournaments/${tournamentId}?org=${organizationId}&name=${encodeURIComponent(tournamentName)}`
  const programHref = `/tournaments/${tournamentId}/program?org=${organizationId}&name=${encodeURIComponent(tournamentName)}`

  if (loading || !session || !teams) {
    return (
      <div className="flex min-h-[100dvh] flex-col bg-background">
        <header className="sticky top-0 z-10 border-b border-border bg-background/80 backdrop-blur">
          <div className="mx-auto flex w-full max-w-5xl items-center gap-3 px-5 py-4">
            <Link
              href={programHref}
              aria-label="Tilbake til program"
              className="flex size-10 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
            >
              <ArrowLeft aria-hidden="true" className="size-5" />
            </Link>
            <div className="flex min-w-0 flex-col">
              <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                Turnering
              </span>
              <h1 className="truncate text-xl font-extrabold tracking-tight text-foreground">
                {tournamentName}
              </h1>
            </div>
          </div>
        </header>
        <main className="mx-auto flex w-full max-w-5xl flex-1 items-center justify-center px-5 py-6">
          {error ? (
            <p role="alert" className="text-sm font-medium text-destructive">
              {error}
            </p>
          ) : (
            <div
              aria-hidden="true"
              className="size-10 animate-spin rounded-full border-4 border-primary/20 border-t-primary"
            />
          )}
        </main>
      </div>
    )
  }

  const perSide = slotsPerSide(session.format)

  function usedPlayerIds(teamId: string, exceptMatchId: string): Set<string> {
    const side: "a" | "b" = teamId === teams![0].id ? "a" : "b"
    const used = new Set<string>()
    for (const m of matches) {
      if (m.id === exceptMatchId) continue
      for (const p of m.participants) {
        if (p.team_side === side) used.add(p.team_roster_id)
      }
    }
    return used
  }

  function teamIsComplete(teamId: string): boolean {
    const side: "a" | "b" = teamId === teams![0].id ? "a" : "b"
    if (matches.length === 0) return false
    return matches.every((m) => m.participants.filter((p) => p.team_side === side).length >= perSide)
  }

  return (
    <div className="flex min-h-[100dvh] flex-col bg-background">
      <header className="sticky top-0 z-10 border-b border-border bg-background/80 backdrop-blur">
        <div className="mx-auto flex w-full max-w-5xl items-center gap-3 px-5 py-4">
          <Link
            href={programHref}
            aria-label="Tilbake til program"
            className="flex size-10 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
          >
            <ArrowLeft aria-hidden="true" className="size-5" />
          </Link>
          <div className="flex min-w-0 flex-col">
            <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
              Turnering
            </span>
            <h1 className="truncate text-xl font-extrabold tracking-tight text-foreground">
              {tournamentName}
            </h1>
            <span className="truncate text-sm text-muted-foreground">
              {session.name?.trim() || "Økt"} · {FORMAT_LABELS[session.format]}
            </span>
          </div>
        </div>
      </header>

      <main className="mx-auto w-full max-w-5xl flex-1 px-5 py-6 sm:py-8">
        <div className="mb-4 flex flex-wrap items-center gap-2">
          <MetaBadge>{FORMAT_LABELS[session.format]}</MetaBadge>
          <MetaBadge>{HOLE_LABELS[session.hole_config]}</MetaBadge>
          <MetaBadge>{SCORING_LABELS[session.scoring_mode]}</MetaBadge>
          <MetaBadge>{formatPoints(session.points_per_match)} poeng</MetaBadge>
        </div>

        {(session.format === "singles" || session.format === "fourball") && session.scoring_mode === "stroke" && (
          <Link
            href={`/tournaments/${tournamentId}/sessions/${sessionId}/individual-leaderboard?org=${organizationId}&name=${encodeURIComponent(tournamentName)}`}
            className="mb-4 flex w-fit items-center gap-1.5 rounded-xl border border-border bg-card px-3 py-2 text-sm font-semibold text-foreground transition-colors hover:bg-accent/60"
          >
            <Trophy aria-hidden="true" className="size-4 text-primary" />
            Individuell rangering
          </Link>
        )}

        {matches.length > 0 && <SendInvitationsButton organizationId={organizationId} sessionId={sessionId} />}

        {error && (
          <p role="alert" className="mb-4 text-sm font-medium text-destructive">
            {error}
          </p>
        )}

        <StatusBanner teams={teams} lockedTeamIds={session.locked_team_ids} bothLocked={bothLocked} />

        {bothLocked ? (
          <RevealedView
            teams={teams}
            matches={sortedMatches}
            scorecardHrefFor={(matchId) =>
              `/tournaments/${tournamentId}/sessions/${sessionId}/matches/${matchId}?org=${organizationId}&name=${encodeURIComponent(tournamentName)}`
            }
          />
        ) : (
          <div className="mt-6 grid gap-4 md:grid-cols-2">
            {teams.map((team, teamIndex) => (
              <TeamColumn
                key={team.id}
                team={team}
                side={teamIndex === 0 ? "a" : "b"}
                roster={rosters[team.id] ?? []}
                tees={tees}
                classes={classes}
                matches={sortedMatches}
                perSide={maxSlotsPerSide(session.format)}
                format={session.format}
                locked={session.locked_team_ids.includes(team.id)}
                usedPlayerIds={usedPlayerIds}
                onAddParticipant={addParticipant}
                onRemoveParticipant={removeParticipant}
                onAddMatch={addMatch}
                complete={teamIsComplete(team.id)}
                confirming={confirmingLock === team.id}
                onRequestLock={() => setConfirmingLock(team.id)}
                onCancelLock={() => setConfirmingLock(null)}
                onConfirmLock={() => lockTeam(team.id)}
              />
            ))}
          </div>
        )}
      </main>
    </div>
  )
}

// --- Status banner -------------------------------------------------------------

function StatusBanner({
  teams,
  lockedTeamIds,
  bothLocked,
}: {
  teams: [ApiTeam, ApiTeam]
  lockedTeamIds: string[]
  bothLocked: boolean
}) {
  if (bothLocked) {
    return (
      <div className="flex items-center gap-3 rounded-3xl bg-primary px-5 py-4 text-primary-foreground shadow-sm">
        <div className="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-primary-foreground/15">
          <PartyPopper aria-hidden="true" className="size-6" />
        </div>
        <div className="flex flex-col">
          <span className="text-base font-extrabold tracking-tight">Oppstilling avslørt!</span>
          <span className="text-sm text-primary-foreground/80">
            Begge lag har låst. Her er paringene for økten.
          </span>
        </div>
      </div>
    )
  }

  const lockedCount = lockedTeamIds.length
  if (lockedCount === 1) {
    const lockedTeam = lockedTeamIds.includes(teams[0].id) ? teams[0] : teams[1]
    const otherTeam = lockedTeam.id === teams[0].id ? teams[1] : teams[0]
    return (
      <div className="flex items-center gap-3 rounded-3xl border border-border bg-card px-5 py-4 shadow-sm">
        <div className="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-secondary">
          <Clock aria-hidden="true" className="size-6 text-muted-foreground" />
        </div>
        <div className="flex flex-col">
          <span className="text-base font-bold tracking-tight text-foreground text-pretty">
            {lockedTeam.name} har låst
          </span>
          <span className="text-sm text-muted-foreground">
            Venter på at {otherTeam.name} låser sin oppstilling.
          </span>
        </div>
      </div>
    )
  }

  return (
    <div className="flex items-center gap-3 rounded-3xl border border-border bg-card px-5 py-4 shadow-sm">
      <div className="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-secondary">
        <EyeOff aria-hidden="true" className="size-6 text-muted-foreground" />
      </div>
      <div className="flex flex-col">
        <span className="text-base font-bold tracking-tight text-foreground">Begge lag setter opp i skjul</span>
        <span className="text-sm text-muted-foreground text-pretty">
          Ingen ser motstanderens valg før begge har låst oppstillingen.
        </span>
      </div>
    </div>
  )
}

// --- Team column ---------------------------------------------------------------

function TeamColumn({
  team,
  side,
  roster,
  tees,
  classes,
  matches,
  perSide,
  format,
  locked,
  usedPlayerIds,
  onAddParticipant,
  onRemoveParticipant,
  onAddMatch,
  complete,
  confirming,
  onRequestLock,
  onCancelLock,
  onConfirmLock,
}: {
  team: ApiTeam
  side: "a" | "b"
  roster: ApiRosterEntry[]
  tees: ApiTee[]
  classes: ApiTournamentClass[]
  matches: ApiMatch[]
  perSide: number
  format: Format
  locked: boolean
  usedPlayerIds: (teamId: string, exceptMatchId: string) => Set<string>
  onAddParticipant: (
    matchId: string,
    side: "a" | "b",
    teamRosterId: string,
    teeId: string,
    lineupOrder?: number | null,
  ) => void
  onRemoveParticipant: (matchId: string, participantId: string) => void
  onAddMatch: () => void
  complete: boolean
  confirming: boolean
  onRequestLock: () => void
  onCancelLock: () => void
  onConfirmLock: () => void
}) {
  const color = team.color ?? "#64748b"

  return (
    <section
      className="flex flex-col gap-3 rounded-3xl border border-border bg-card/40 p-4 shadow-md shadow-black/8 sm:p-5"
      style={{ borderLeftWidth: 6, borderLeftColor: color }}
      aria-label={`Oppstilling for ${team.name}`}
    >
      <div className="flex items-center justify-between gap-2">
        <div className="flex items-center gap-2.5">
          <span aria-hidden="true" className="size-4 shrink-0 rounded-full" style={{ backgroundColor: color }} />
          <h2 className="text-lg font-extrabold tracking-tight text-foreground">{team.name}</h2>
        </div>
        <span className="inline-flex items-center gap-1.5 text-sm font-medium text-muted-foreground">
          <Users aria-hidden="true" className="size-4" />
          {matches.length} {matches.length === 1 ? "match" : "matcher"}
        </span>
      </div>

      {locked && (
        <div className="inline-flex w-fit items-center gap-1.5 rounded-full bg-secondary px-3 py-1 text-xs font-bold text-secondary-foreground">
          <Lock aria-hidden="true" className="size-3.5" />
          Låst
        </div>
      )}

      <div className="flex flex-col gap-3">
        {matches.map((match) => {
          const sideParticipants = match.participants.filter((p) => p.team_side === side)
          const disabled = usedPlayerIds(team.id, match.id)
          return (
            <div key={match.id} className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4">
              <div className="flex items-center justify-between gap-2">
                <span className="flex size-8 items-center justify-center rounded-xl bg-primary text-sm font-extrabold tabular-nums text-primary-foreground">
                  {match.sequence}
                </span>
                <span className="inline-flex items-center gap-1.5 text-sm text-muted-foreground">
                  <Clock aria-hidden="true" className="size-4" />
                  {match.tee_time ? `Utslag ${formatTime(match.tee_time)}` : "Tid ikke satt"}
                </span>
              </div>

              <div className="flex flex-col gap-2 border-t border-border pt-3">
                {sideParticipants.map((p) => (
                  <div
                    key={p.id}
                    className="flex items-center justify-between gap-3 rounded-xl bg-secondary/60 px-3 py-2.5"
                  >
                    <span className="truncate text-sm font-semibold text-foreground">{p.player_name}</span>
                    <div className="flex shrink-0 items-center gap-2">
                      {format === "money_ball" && p.lineup_order !== null && (
                        <span className="inline-flex size-6 items-center justify-center rounded-full bg-muted text-xs font-extrabold text-muted-foreground">
                          {p.lineup_order}
                        </span>
                      )}
                      <span className="inline-flex items-center gap-1.5 rounded-full bg-card px-2.5 py-1 text-xs font-semibold text-foreground">
                        {p.tee_name}
                      </span>
                      {!locked && (
                        <button
                          type="button"
                          onClick={() => onRemoveParticipant(match.id, p.id)}
                          aria-label={`Fjern ${p.player_name}`}
                          className="flex size-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground"
                        >
                          <X aria-hidden="true" className="size-4" />
                        </button>
                      )}
                    </div>
                  </div>
                ))}

                {!locked && sideParticipants.length < perSide && (
                  <AddSlotForm
                    roster={roster}
                    tees={tees}
                    classes={classes}
                    disabledPlayerIds={disabled}
                    // Money Ball -- 0-3 ledig, avhengig av hva laget allerede har brukt.
                    availableLineupOrders={
                      format === "money_ball"
                        ? [0, 1, 2, 3].filter((n) => !sideParticipants.some((p) => p.lineup_order === n))
                        : null
                    }
                    onAdd={(teamRosterId, teeId, lineupOrder) =>
                      onAddParticipant(match.id, side, teamRosterId, teeId, lineupOrder)
                    }
                  />
                )}

                {perSide > 1 && (
                  <p className="text-xs text-muted-foreground">{perSide} spillere per side i dette formatet</p>
                )}
              </div>
            </div>
          )
        })}
      </div>

      {!locked && (
        <button
          type="button"
          onClick={onAddMatch}
          className="flex items-center justify-center gap-2 rounded-2xl border border-dashed border-border bg-background/50 px-4 py-3 text-sm font-semibold text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
        >
          <Plus aria-hidden="true" className="size-4" />
          Legg til match
        </button>
      )}

      {!locked && (
        <div className="mt-1">
          {confirming ? (
            <div className="flex flex-col gap-3 rounded-2xl border border-border bg-background/60 p-4">
              <p className="text-sm font-medium text-foreground text-pretty">
                Er du sikker? Du kan ikke endre oppstillingen etter at du har låst.
              </p>
              <div className="flex gap-2">
                <Button type="button" variant="outline" onClick={onCancelLock} className="h-11 flex-1 rounded-2xl font-semibold">
                  Avbryt
                </Button>
                <Button type="button" onClick={onConfirmLock} className="h-11 flex-1 rounded-2xl font-bold">
                  <Lock aria-hidden="true" className="size-4" />
                  Lås
                </Button>
              </div>
            </div>
          ) : (
            <Button
              type="button"
              onClick={onRequestLock}
              disabled={!complete}
              className="h-12 w-full rounded-2xl text-base font-bold shadow-sm"
            >
              <Lock aria-hidden="true" className="size-5" />
              Lås oppstilling
            </Button>
          )}
          {!complete && !confirming && (
            <p className="mt-2 text-center text-xs text-muted-foreground text-pretty">
              Velg spiller og tee for alle plasser før du kan låse.
            </p>
          )}
        </div>
      )}
    </section>
  )
}

// --- Add-slot inline form --------------------------------------------------

function AddSlotForm({
  roster,
  tees,
  classes,
  disabledPlayerIds,
  availableLineupOrders,
  onAdd,
}: {
  roster: ApiRosterEntry[]
  tees: ApiTee[]
  classes: ApiTournamentClass[]
  disabledPlayerIds: Set<string>
  // Money Ball (2026-07-30) -- ledige 0-3-plasser, null for alle andre formater.
  availableLineupOrders: number[] | null
  onAdd: (teamRosterId: string, teeId: string, lineupOrder: number | null) => void
}) {
  const [rosterId, setRosterId] = useState("")
  const [teeId, setTeeId] = useState("")
  const [lineupOrder, setLineupOrder] = useState("")

  // Konkurranseklasser (2026-08-03) -- forhåndsvelg spillerens klasses
  // standardutslag, MEN kun hvis det utslaget faktisk finnes blant DENNE
  // øktens baneutslag (klassens standardutslag kan tilhøre en annen bane
  // enn økten er satt opp med) -- fortsatt fritt overstyrbart under.
  useEffect(() => {
    if (!rosterId) return
    const entry = roster.find((r) => r.id === rosterId)
    const cls = entry?.class_id ? classes.find((c) => c.id === entry.class_id) : null
    const defaultTee = cls?.default_tee_id ? tees.find((t) => t.id === cls.default_tee_id) : null
    if (defaultTee) setTeeId(defaultTee.id)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [rosterId])

  const needsLineupOrder = availableLineupOrders !== null
  const canAdd = rosterId && teeId && (!needsLineupOrder || lineupOrder !== "")

  function handleAdd() {
    if (!canAdd) return
    onAdd(rosterId, teeId, needsLineupOrder ? Number(lineupOrder) : null)
    setRosterId("")
    setTeeId("")
    setLineupOrder("")
  }

  return (
    <div className="flex flex-col gap-2 rounded-xl border border-dashed border-border bg-background/20 p-2">
      <div className="grid grid-cols-[1fr_1fr_auto] gap-2">
        <label className="flex flex-col gap-1">
          <span className="sr-only">Velg spiller</span>
          <select
            value={rosterId}
            onChange={(e) => setRosterId(e.target.value)}
            className="h-11 rounded-lg border border-border bg-card px-2.5 text-sm font-medium text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
          >
            <option value="">Velg spiller</option>
            {roster
              .filter((p) => !disabledPlayerIds.has(p.id))
              .map((p) => (
                <option key={p.id} value={p.id}>
                  {p.display_name}
                  {p.handicap_index_snapshot !== null ? ` (${formatHcp(p.handicap_index_snapshot)})` : ""}
                </option>
              ))}
          </select>
        </label>
        <label className="flex flex-col gap-1">
          <span className="sr-only">Velg tee</span>
          <select
            value={teeId}
            onChange={(e) => setTeeId(e.target.value)}
            className="h-11 rounded-lg border border-border bg-card px-2.5 text-sm font-medium text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
          >
            <option value="">Velg tee</option>
            {tees.map((t) => (
              <option key={t.id} value={t.id}>
                {t.name}
              </option>
            ))}
          </select>
        </label>
        <Button
          type="button"
          size="icon"
          disabled={!canAdd}
          onClick={handleAdd}
          className="h-11 w-11 shrink-0 rounded-lg"
          aria-label="Legg til spiller"
        >
          <Plus aria-hidden="true" className="size-4" />
        </Button>
      </div>
      {needsLineupOrder && (
        <label className="flex flex-col gap-1">
          <span className="text-xs font-semibold text-muted-foreground">Plass i rekkefølgen (Money Ball)</span>
          <select
            value={lineupOrder}
            onChange={(e) => setLineupOrder(e.target.value)}
            className="h-11 rounded-lg border border-border bg-card px-2.5 text-sm font-medium text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
          >
            <option value="">Velg plass</option>
            {availableLineupOrders!.map((n) => (
              <option key={n} value={n}>
                {n}
              </option>
            ))}
          </select>
        </label>
      )}
    </div>
  )
}

// --- Revealed view ---------------------------------------------------------

function RevealedView({
  teams,
  matches,
  scorecardHrefFor,
}: {
  teams: [ApiTeam, ApiTeam]
  matches: ApiMatch[]
  scorecardHrefFor: (matchId: string) => string
}) {
  return (
    <div className="mt-6 flex flex-col gap-3">
      {matches.map((match) => {
        // Fargekoding etter ledende lag (ADR-020) -- umiddelbar visuell
        // status i stedet for kun tekst, samme mønster som TV-dekning av
        // Ryder Cup. Ingen farge når leading_side er null (ikke startet/AS).
        const leadingTeam =
          match.leading_side === "a" ? teams[0] : match.leading_side === "b" ? teams[1] : null
        const leadColor = leadingTeam?.color ?? null
        const decided = match.points_side_a !== null
        return (
          <Link
            key={match.id}
            href={scorecardHrefFor(match.id)}
            className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-4 shadow-md shadow-black/8 transition-colors hover:border-primary/50 hover:bg-accent/50 sm:p-5"
            style={leadColor ? { borderTopWidth: 4, borderTopColor: leadColor } : undefined}
          >
            <div className="flex items-center justify-between gap-2">
              <span className="flex size-9 items-center justify-center rounded-xl bg-primary text-base font-extrabold tabular-nums text-primary-foreground">
                {match.sequence}
              </span>
              {match.status_text ? (
                <span
                  className={cn(
                    "inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-extrabold uppercase tracking-wide",
                    decided ? "text-white" : "text-foreground",
                  )}
                  style={{ backgroundColor: leadColor ? `${leadColor}${decided ? "" : "26"}` : undefined }}
                >
                  {decided && <PartyPopper aria-hidden="true" className="size-3.5" />}
                  {match.status_text}
                </span>
              ) : (
                <span className="inline-flex items-center gap-1.5 text-sm text-muted-foreground">
                  <Clock aria-hidden="true" className="size-4" />
                  {match.tee_time ? `Utslag ${formatTime(match.tee_time)}` : "Tid ikke satt"}
                </span>
              )}
            </div>

            <div className="grid grid-cols-[1fr_auto_1fr] items-stretch gap-2 border-t border-border pt-3 sm:gap-3">
              <RevealSide
                team={teams[0]}
                participants={match.participants.filter((p) => p.team_side === "a")}
                align="left"
                leading={match.leading_side === "a"}
              />
              <div className="flex items-center justify-center">
                <span className="rounded-full bg-secondary px-2.5 py-1 text-xs font-extrabold uppercase tracking-wide text-secondary-foreground">
                  vs
                </span>
              </div>
              <RevealSide
                team={teams[1]}
                participants={match.participants.filter((p) => p.team_side === "b")}
                align="right"
                leading={match.leading_side === "b"}
              />
            </div>
          </Link>
        )
      })}
    </div>
  )
}

function RevealSide({
  team,
  participants,
  align,
  leading,
}: {
  team: ApiTeam
  participants: ApiParticipant[]
  align: "left" | "right"
  leading: boolean
}) {
  const right = align === "right"
  const color = team.color ?? "#64748b"
  return (
    <div
      className={cn("flex flex-col gap-2 rounded-2xl bg-background/50 p-3", right ? "items-end text-right" : "items-start text-left")}
      style={{
        ...(right ? { borderRightWidth: 4, borderRightColor: color } : { borderLeftWidth: 4, borderLeftColor: color }),
        backgroundColor: leading ? `${color}14` : undefined,
      }}
    >
      <div className={cn("flex items-center gap-2", right && "flex-row-reverse")}>
        <span aria-hidden="true" className="size-3 shrink-0 rounded-full" style={{ backgroundColor: color }} />
        <span className="text-xs font-bold uppercase tracking-wide text-muted-foreground">{team.name}</span>
      </div>
      {participants.map((p) => (
        <div key={p.id} className={cn("flex flex-col", right ? "items-end" : "items-start")}>
          <span className="text-sm font-semibold text-foreground text-pretty">{p.player_name}</span>
          <span className="text-xs text-muted-foreground">{p.tee_name}</span>
        </div>
      ))}
    </div>
  )
}

// --- Midlertidige spillere: etter-runde-invitasjon (migrasjon 034) --------
// Eksplisitt organisator-knapp, økt-nivå (FEATURE_BACKLOG.md, avklart
// 2026-07-28) -- sender scorekort + innloggingslenke KUN til spillere uten
// egen konto ennå, KUN én gang per (økt, deltaker), håndtert av backend.

type SendInvitationsResult = {
  sent: number
  skipped_has_account: number
  skipped_no_email: number
  skipped_already_sent: number
}

function SendInvitationsButton({ organizationId, sessionId }: { organizationId: string; sessionId: string }) {
  const [sending, setSending] = useState(false)
  const [result, setResult] = useState<SendInvitationsResult | null>(null)
  const [error, setError] = useState<string | null>(null)

  async function handleSend() {
    if (
      !confirm(
        "Send scorekort og innloggingslenke til alle spillere med registrert e-post i denne økten som ikke har konto ennå?",
      )
    )
      return
    setSending(true)
    setError(null)
    setResult(null)
    try {
      const res = await fetch(`/orgs/${organizationId}/sessions/${sessionId}/send-scorecard-invitations`, {
        method: "POST",
        credentials: "include",
      })
      if (!res.ok) throw new Error(`send: ${res.status}`)
      setResult(await res.json())
    } catch {
      setError("Klarte ikke å sende invitasjoner. Prøv igjen.")
    } finally {
      setSending(false)
    }
  }

  return (
    <div className="mb-4 flex flex-col gap-2">
      <Button
        type="button"
        variant="outline"
        onClick={handleSend}
        disabled={sending}
        className="h-10 w-fit rounded-xl font-semibold"
      >
        <Mail aria-hidden="true" className="size-4" />
        {sending ? "Sender …" : "Send scorekort til alle med e-post"}
      </Button>
      {result && (
        <p className="text-sm text-muted-foreground">
          {result.sent} {result.sent === 1 ? "invitasjon sendt" : "invitasjoner sendt"}
          {result.skipped_already_sent > 0 && `, ${result.skipped_already_sent} allerede sendt tidligere`}
          {result.skipped_has_account > 0 && `, ${result.skipped_has_account} har allerede konto`}
          {result.skipped_no_email > 0 && `, ${result.skipped_no_email} mangler registrert e-post`}.
        </p>
      )}
      {error && <p className="text-sm font-medium text-destructive">{error}</p>}
    </div>
  )
}

// --- Small helpers ---------------------------------------------------------

function MetaBadge({ children }: { children: React.ReactNode }) {
  return (
    <span className="inline-flex items-center rounded-full bg-secondary px-2.5 py-1 text-xs font-semibold text-secondary-foreground">
      {children}
    </span>
  )
}

function formatHcp(value: number | null) {
  if (value === null) return "–"
  return value.toFixed(1).replace(".", ",")
}

function formatTime(iso: string) {
  const date = new Date(iso)
  if (Number.isNaN(date.getTime())) return iso
  return date.toLocaleTimeString("no-NO", { hour: "2-digit", minute: "2-digit" })
}

function formatPoints(points: number) {
  return Number.isInteger(points) ? String(points) : points.toFixed(1).replace(".", ",")
}
