"use client"

// Kontoinnstillinger (ADR-021): sett/fjern passord (valgfritt tillegg til
// magic-link), aktiver/deaktiver 2FA. Frivillig 2FA-oppsett herfra bruker
// SAMME TwoFactorSetupForm som den tvungne flyten rett etter innlogging
// (forced=false her -- kun tekst-copyen skiller de to).

import type React from "react"
import { useEffect, useRef, useState } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import {
  ArrowLeft,
  Bell,
  BellRing,
  Camera,
  Copy,
  Flag,
  KeyRound,
  Lock,
  LogOut,
  Mail,
  Merge,
  Pencil,
  Plus,
  ShieldCheck,
  ShieldOff,
  Trash2,
  User,
  X,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { getExistingSubscription, isPushSupported, subscribeToPush, unsubscribeFromPush } from "@/lib/push-subscribe"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Wordmark } from "@/components/wordmark"
import { TwoFactorSetupForm } from "@/components/two-factor-flow"
import { CourseTemplateEditor, type CourseTemplateValues } from "@/components/course-template-editor"
import { BottomNav } from "@/components/teecup/bottom-nav"

// ADR-031: personlig profil, ETT sett per konto (app_user), atskilt fra de
// org-scopede `player`-radene organisatorer administrerer.
// Kølle-bag (2026-07-24): fast, ikke-utvidbar liste -- MÅ matche
// BAG_CLUBS i app/routers/auth.py nøyaktig (backend validerer strengt).
const BAG_CLUBS = [
  "Driver", "Mini-driver", "Driving iron", "3-wood", "4-wood", "5-wood",
  "7-wood", "9-wood", "2-hybrid", "3-hybrid", "4-hybrid", "5-hybrid",
  "1-iron", "2-iron", "3-iron", "4-iron", "5-iron", "6-iron", "7-iron",
  "8-iron", "9-iron", "Pitching wedge", "Gap wedge", "Sand wedge",
  "Lob wedge", "Ultra-lob wedge", "Chipper", "Putter",
] as const
const MAX_BAG_CLUBS = 14

// Land+hjemmeklubb (2026-07-25): "Land" er klargjøring for fremtidig
// flerspråklighet -- kun norske klubber finnes i teeoff ennå, så listen har
// bevisst kun ett valg foreløpig. Flere land legges til her når teeoff (eller
// en fremtidig tilsvarende kilde) faktisk har data for dem -- INGEN
// backend-endring trengs da, kun denne listen utvides.
const COUNTRIES = ["Norge"] as const

// Aldersgrense (2026-08-10, se ARCHITECTURE_DECISIONS.md): brukt av BÅDE
// ProfileOnboarding og ProfileSection for umiddelbar tilbakemelding -- den
// ekte, håndhevede grensen ligger server-side i PATCH /auth/profile.
// `dateStr` er "YYYY-MM-DD" (rå verdi fra en <input type="date">).
function computeAge(dateStr: string): number {
  const birth = new Date(dateStr)
  const today = new Date()
  let age = today.getFullYear() - birth.getFullYear()
  const hasHadBirthdayThisYear =
    today.getMonth() > birth.getMonth() ||
    (today.getMonth() === birth.getMonth() && today.getDate() >= birth.getDate())
  if (!hasHadBirthdayThisYear) age -= 1
  return age
}

function CountryField({ id, value, onChange }: { id: string; value: string; onChange: (v: string) => void }) {
  return (
    <div className="flex flex-col gap-1.5">
      <Label htmlFor={id} className="text-sm font-semibold">
        Land
      </Label>
      <select
        id={id}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        className="h-11 rounded-xl border border-border bg-card px-3 text-sm font-medium text-foreground outline-none"
      >
        {COUNTRIES.map((c) => (
          <option key={c} value={c}>
            {c}
          </option>
        ))}
      </select>
    </div>
  )
}

// Hjemmeklubb (2026-07-25): søkbar liste mot teeoffs klubbregister --
// gjenbruker `/rounds/official-search` (allerede org-uavhengig, tilgjengelig
// for enhver innlogget bruker, ADR-033), samme mønster som bane-søket ved ny
// runde. Teeoff filtrerer allerede bort upubliserte/nedlagte anlegg
// server-side (`is_published`), så ingen egen filtrering trengs her.
type HomeClubOption = { slug: string; name: string; city: string | null; county: string | null }

function HomeClubField({ id, value, onChange }: { id: string; value: string; onChange: (v: string) => void }) {
  const [query, setQuery] = useState(value)
  const [results, setResults] = useState<HomeClubOption[]>([])
  const [open, setOpen] = useState(false)
  const containerRef = useRef<HTMLDivElement>(null)

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

  useEffect(() => {
    function handleClickOutside(e: MouseEvent) {
      if (containerRef.current && !containerRef.current.contains(e.target as Node)) setOpen(false)
    }
    document.addEventListener("mousedown", handleClickOutside)
    return () => document.removeEventListener("mousedown", handleClickOutside)
  }, [])

  function selectClub(option: HomeClubOption) {
    onChange(option.name)
    setQuery(option.name)
    setOpen(false)
  }

  return (
    <div ref={containerRef} className="relative flex flex-col gap-1.5">
      <Label htmlFor={id} className="text-sm font-semibold">
        Hjemmeklubb
      </Label>
      <Input
        id={id}
        value={query}
        onChange={(e) => {
          setQuery(e.target.value)
          onChange(e.target.value)
          setOpen(true)
        }}
        onFocus={() => setOpen(true)}
        placeholder="Søk etter klubb …"
        autoComplete="off"
        className="h-11 rounded-xl"
      />
      {open && results.length > 0 && (
        <ul className="absolute top-full z-20 mt-1 max-h-64 w-full overflow-auto rounded-xl border border-border bg-card shadow-md shadow-black/10">
          {results.map((option) => (
            <li key={option.slug}>
              <button
                type="button"
                onClick={() => selectClub(option)}
                className="flex min-h-11 w-full flex-col items-start justify-center px-3 py-1.5 text-left transition-colors hover:bg-accent/60"
              >
                <span className="text-sm font-semibold text-foreground">{option.name}</span>
                {(option.city || option.county) && (
                  <span className="text-xs text-muted-foreground">
                    {[option.city, option.county].filter(Boolean).join(", ")}
                  </span>
                )}
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}

type Me = {
  id: string
  email: string
  display_name: string
  has_password: boolean
  two_factor_method: "totp" | "email" | null
  first_name: string | null
  last_name: string | null
  birth_date: string | null
  gender: "m" | "f" | "x" | null
  handicap_index: number | null
  // Faktisk (beregnet) HCP, ADR-038 -- WHS Handicap Index regnet
  // automatisk fra frittstående runder. Aldri direkte redigerbar.
  computed_handicap_index: number | null
  computed_handicap_index_updated_at: string | null
  home_club: string | null
  country: string | null
  bio: string | null
  mobile_country_code: string | null
  mobile_number: string | null
  bag_clubs: string[]
  avatar_url: string | null
  // 2026-07-22: alle obligatoriske profilfelt utfylt (bilde/beskrivelse
  // teller ikke). Se AccountSettings sin bruk under.
  profile_complete: boolean
  secondary_emails: { id: string; email: string }[]
}

export function AccountSettings() {
  const router = useRouter()
  const [me, setMe] = useState<Me | null>(null)
  const [loading, setLoading] = useState(true)
  const [settingUp2fa, setSettingUp2fa] = useState(false)

  async function loadMe() {
    try {
      const res = await fetch("/auth/me", { credentials: "include" })
      if (res.ok) setMe(await res.json())
    } finally {
      setLoading(false)
    }
  }

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

  async function handleDisable2fa() {
    if (!confirm("Er du sikker på at du vil slå av topartsautentisering?")) return
    const res = await fetch("/auth/2fa/disable", { method: "POST", credentials: "include" })
    if (res.ok) void loadMe()
  }

  if (loading) {
    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>
    )
  }

  if (!me) return null

  // Obligatorisk profil-fullføring (2026-07-22): en ny/ufullstendig konto
  // ser KUN dette skjemaet -- ingen tilgang til resten av kontosidene eller
  // dashbordet før alle obligatoriske felt er utfylt. Se page.tsx og
  // Dashboard for de to inngangspunktene som sender brukeren hit.
  if (!me.profile_complete) {
    return (
      <ProfileOnboarding
        me={me}
        onComplete={() => router.replace("/dashboard")}
      />
    )
  }

  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-2xl items-center gap-3 px-5 py-4">
          <Link
            href="/dashboard"
            aria-label="Tilbake til dashbord"
            className="flex size-10 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
          >
            <ArrowLeft aria-hidden="true" className="size-5" />
          </Link>
          <div className="flex min-w-0 flex-col">
            <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Konto</span>
            <h1 className="truncate text-xl font-extrabold tracking-tight text-foreground">{me.email}</h1>
          </div>
        </div>
      </header>

      <main className="mx-auto w-full max-w-2xl flex-1 px-5 pb-24 pt-6 sm:pt-8">
        {settingUp2fa ? (
          <div className="rounded-3xl border border-border bg-card p-6 shadow-md shadow-black/8 sm:p-8">
            <TwoFactorSetupForm
              forced={false}
              onSuccess={() => {
                setSettingUp2fa(false)
                void loadMe()
              }}
            />
          </div>
        ) : (
          <div className="flex flex-col gap-6">
            <ProfileSection me={me} onChanged={loadMe} />

            <EmailSection email={me.email} />

            <SecondaryEmailSection secondaryEmails={me.secondary_emails} onChanged={loadMe} />

            <AccountMergeSection />

            <PasswordSection hasPassword={me.has_password} onChanged={loadMe} />

            <NotificationEmailPrefsSection />

            <PushNotificationSection />

            <MyCoursesSection />

            <section className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6">
              <div className="flex items-center gap-2.5">
                <div className="flex size-10 items-center justify-center rounded-xl bg-muted">
                  <ShieldCheck aria-hidden="true" className="size-5 text-muted-foreground" />
                </div>
                <h2 className="text-base font-bold text-foreground">Topartsautentisering (2FA)</h2>
              </div>

              {me.two_factor_method ? (
                <>
                  <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
                    Aktivert med{" "}
                    <span className="font-semibold text-foreground">
                      {me.two_factor_method === "totp" ? "autentisator-app" : "engangskode på e-post"}
                    </span>
                    .
                  </p>
                  <Button
                    type="button"
                    variant="outline"
                    onClick={handleDisable2fa}
                    className="h-12 w-fit rounded-2xl font-semibold text-destructive hover:text-destructive"
                  >
                    <ShieldOff aria-hidden="true" className="size-4" />
                    Slå av 2FA
                  </Button>
                </>
              ) : (
                <>
                  <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
                    Ikke aktivert. Anbefales for organisasjonseiere/administratorer -- da vil du
                    bli bedt om å sette det opp ved neste innlogging uansett.
                  </p>
                  <Button
                    type="button"
                    onClick={() => setSettingUp2fa(true)}
                    className="h-12 w-fit rounded-2xl font-semibold"
                  >
                    <KeyRound aria-hidden="true" className="size-4" />
                    Sett opp 2FA
                  </Button>
                </>
              )}
            </section>
          </div>
        )}
      </main>

      <BottomNav />
    </div>
  )
}

// --- Obligatorisk profil-fullføring (2026-07-22) ----------------------------
// Vises i STEDET for resten av appen (dashbord, øvrige kontoseksjoner) helt
// til alle obligatoriske felt er utfylt -- se AccountSettings sin
// profile_complete-gren. Kun bilde og beskrivelse er valgfrie her.

function ProfileOnboarding({ me, onComplete }: { me: Me; onComplete: () => void }) {
  const router = useRouter()
  const [firstName, setFirstName] = useState(me.first_name ?? "")
  const [lastName, setLastName] = useState(me.last_name ?? "")
  const [birthDate, setBirthDate] = useState(me.birth_date ?? "")
  const [gender, setGender] = useState(me.gender ?? "")
  // WHS-maksimum (54) er riktig utgangspunkt for en spiller uten offisiell
  // HCP ennå -- forhåndsutfylt, ikke tomt, jf. brukerens eksplisitte ønske.
  const [hcp, setHcp] = useState(me.handicap_index === null ? "54" : String(me.handicap_index))
  // Landfeltet er nå en nedtrekksliste (2026-07-25) -- default til det
  // eneste tilgjengelige valget, samme "forhåndsutfylt fremfor tomt"-prinsipp
  // som HCP-standarden 54 over.
  const [country, setCountry] = useState(me.country ?? COUNTRIES[0])
  const [homeClub, setHomeClub] = useState(me.home_club ?? "")
  const [bio, setBio] = useState(me.bio ?? "")
  const [saving, setSaving] = useState(false)
  const [error, setError] = useState<string | null>(null)

  // Aldersgrense (2026-08-10): TeeCup skal aldri la noen under 13 år
  // registrere seg (GDPR/personopplysningsloven -- se ARCHITECTURE_
  // DECISIONS.md). Denne klientside-sjekken er kun for rask tilbakemelding
  // -- den ekte, håndhevede grensen ligger server-side i PATCH /auth/profile.
  const underMinimumAge = birthDate !== "" && computeAge(birthDate) < 13

  const valid =
    firstName.trim() !== "" &&
    lastName.trim() !== "" &&
    birthDate !== "" &&
    !underMinimumAge &&
    gender !== "" &&
    hcp.trim() !== "" &&
    homeClub.trim() !== "" &&
    country.trim() !== ""

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    if (!valid || saving) return
    setSaving(true)
    setError(null)
    try {
      const res = await fetch("/auth/profile", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({
          first_name: firstName.trim(),
          last_name: lastName.trim(),
          birth_date: birthDate,
          gender,
          handicap_index: Number(hcp.replace(",", ".")),
          home_club: homeClub.trim(),
          country: country.trim(),
          bio: bio.trim() === "" ? null : bio.trim(),
        }),
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        throw new Error(body?.detail?.message ?? "Klarte ikke å lagre profilen.")
      }
      const updated: Me = await res.json()
      if (updated.profile_complete) {
        onComplete()
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
    } finally {
      setSaving(false)
    }
  }

  async function handleLogout() {
    try {
      await fetch("/auth/logout", { method: "POST", credentials: "include" })
    } finally {
      router.replace("/logg-inn")
    }
  }

  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-2xl items-center justify-between gap-4 px-5 py-4">
          <Wordmark compact />
          <button
            type="button"
            onClick={handleLogout}
            className="inline-flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-sm font-semibold text-muted-foreground transition-colors hover:text-foreground"
          >
            <LogOut aria-hidden="true" className="size-4" />
            Logg ut
          </button>
        </div>
      </header>

      <main className="mx-auto w-full max-w-2xl flex-1 px-5 py-8 sm:py-10">
        <div className="flex flex-col gap-2 pb-6 text-center sm:text-left">
          <h1 className="text-2xl font-extrabold tracking-tight text-foreground text-balance">
            Fullfør profilen din
          </h1>
          <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
            Før du kan komme videre trenger vi noen opplysninger om deg. Alt utenom beskrivelse
            er obligatorisk. Du kan redigere alt dette senere under «Konto».
          </p>
        </div>

        <form
          onSubmit={handleSubmit}
          className="flex flex-col gap-4 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6"
        >
          <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="ob-first-name" className="text-sm font-semibold">
                Fornavn
              </Label>
              <Input
                id="ob-first-name"
                autoFocus
                value={firstName}
                onChange={(e) => setFirstName(e.target.value)}
                className="h-11 rounded-xl"
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="ob-last-name" className="text-sm font-semibold">
                Etternavn
              </Label>
              <Input
                id="ob-last-name"
                value={lastName}
                onChange={(e) => setLastName(e.target.value)}
                className="h-11 rounded-xl"
              />
            </div>
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="ob-birth-date" className="text-sm font-semibold">
                Fødselsdato
              </Label>
              <Input
                id="ob-birth-date"
                type="date"
                value={birthDate}
                onChange={(e) => setBirthDate(e.target.value)}
                aria-invalid={underMinimumAge}
                className="h-11 rounded-xl"
              />
              {underMinimumAge ? (
                <p role="alert" className="text-sm text-destructive text-pretty">
                  Du må være minst 13 år for å bruke TeeCup.
                </p>
              ) : null}
            </div>
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="ob-gender" className="text-sm font-semibold">
                Kjønn
              </Label>
              <select
                id="ob-gender"
                value={gender}
                onChange={(e) => setGender(e.target.value)}
                className="h-11 rounded-xl border border-border bg-card px-3 text-sm font-medium text-foreground outline-none"
              >
                <option value="">Velg …</option>
                <option value="f">Dame</option>
                <option value="m">Herre</option>
                <option value="x">Annet</option>
              </select>
            </div>
            <div className="flex flex-col gap-1.5">
              <Label htmlFor="ob-hcp" className="text-sm font-semibold">
                HCP
              </Label>
              <Input
                id="ob-hcp"
                inputMode="decimal"
                value={hcp}
                onChange={(e) => setHcp(e.target.value)}
                className="h-11 rounded-xl"
              />
              <p className="text-xs text-muted-foreground">
                Ferske spillere har 54 -- la stå om du ikke har en offisiell HCP ennå.
              </p>
            </div>
            <CountryField id="ob-country" value={country} onChange={setCountry} />
            <HomeClubField id="ob-home-club" value={homeClub} onChange={setHomeClub} />
            <div className="flex flex-col gap-1.5 sm:col-span-2">
              <Label htmlFor="ob-bio" className="text-sm font-semibold">
                Beskrivelse <span className="font-normal text-muted-foreground">(valgfritt)</span>
              </Label>
              <textarea
                id="ob-bio"
                value={bio}
                onChange={(e) => setBio(e.target.value)}
                rows={2}
                placeholder="F.eks. Golf nut, running teeoff.no"
                className="rounded-xl border border-border bg-card px-3 py-2 text-sm text-foreground outline-none"
              />
            </div>
          </div>

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

          <Button type="submit" disabled={!valid || saving} className="h-12 w-fit rounded-xl font-semibold">
            {saving ? "Lagrer …" : "Fortsett"}
          </Button>
        </form>
      </main>
    </div>
  )
}

// --- Personlig profil (ADR-031) ---------------------------------------------

function ProfileSection({ me, onChanged }: { me: Me; onChanged: () => void }) {
  const [firstName, setFirstName] = useState(me.first_name ?? "")
  const [lastName, setLastName] = useState(me.last_name ?? "")
  const [birthDate, setBirthDate] = useState(me.birth_date ?? "")
  const [gender, setGender] = useState(me.gender ?? "")
  const [hcp, setHcp] = useState(me.handicap_index === null ? "" : String(me.handicap_index))
  const [country, setCountry] = useState(me.country ?? COUNTRIES[0])
  const [homeClub, setHomeClub] = useState(me.home_club ?? "")
  const [bio, setBio] = useState(me.bio ?? "")
  const [mobileCountryCode, setMobileCountryCode] = useState(me.mobile_country_code ?? "+47")
  const [mobileNumber, setMobileNumber] = useState(me.mobile_number ?? "")
  const [bagClubs, setBagClubs] = useState<string[]>(me.bag_clubs)
  const [saving, setSaving] = useState(false)
  const [uploadingAvatar, setUploadingAvatar] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [success, setSuccess] = useState(false)
  const fileInputRef = useRef<HTMLInputElement>(null)

  // Aldersgrense (2026-08-10) -- se tilsvarende kommentar i ProfileOnboarding.
  const underMinimumAge = birthDate !== "" && computeAge(birthDate) < 13

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    if (underMinimumAge) return
    setSaving(true)
    setError(null)
    setSuccess(false)
    try {
      const res = await fetch("/auth/profile", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({
          first_name: firstName.trim() === "" ? null : firstName.trim(),
          last_name: lastName.trim() === "" ? null : lastName.trim(),
          birth_date: birthDate === "" ? null : birthDate,
          gender: gender === "" ? null : gender,
          handicap_index: hcp.trim() === "" ? null : Number(hcp.replace(",", ".")),
          home_club: homeClub.trim() === "" ? null : homeClub.trim(),
          country: country.trim() === "" ? null : country.trim(),
          bio: bio.trim() === "" ? null : bio.trim(),
          mobile_country_code: mobileNumber.trim() === "" ? null : mobileCountryCode.trim(),
          mobile_number: mobileNumber.trim() === "" ? null : mobileNumber.trim(),
          bag_clubs: bagClubs,
        }),
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        throw new Error(body?.detail?.message ?? "Klarte ikke å lagre profilen.")
      }
      setSuccess(true)
      onChanged()
    } catch (err) {
      setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
    } finally {
      setSaving(false)
    }
  }

  async function handleAvatarSelected(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0]
    if (!file) return
    setUploadingAvatar(true)
    setError(null)
    try {
      const formData = new FormData()
      formData.append("file", file)
      const res = await fetch("/auth/profile/avatar", {
        method: "POST",
        credentials: "include",
        body: formData,
      })
      if (!res.ok) throw new Error("Klarte ikke å laste opp profilbildet.")
      onChanged()
    } catch (err) {
      setError(err instanceof Error ? err.message : "Klarte ikke å laste opp profilbildet.")
    } finally {
      setUploadingAvatar(false)
      if (fileInputRef.current) fileInputRef.current.value = ""
    }
  }

  async function handleRemoveAvatar() {
    setUploadingAvatar(true)
    try {
      const res = await fetch("/auth/profile/avatar", { method: "DELETE", credentials: "include" })
      if (res.ok) onChanged()
    } finally {
      setUploadingAvatar(false)
    }
  }

  return (
    <section className="flex flex-col gap-4 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6">
      <div className="flex items-center gap-2.5">
        <div className="flex size-10 items-center justify-center rounded-xl bg-muted">
          <User aria-hidden="true" className="size-5 text-muted-foreground" />
        </div>
        <h2 className="text-base font-bold text-foreground">Personlig profil</h2>
      </div>

      <div className="flex items-center gap-4">
        <div className="relative flex size-16 shrink-0 items-center justify-center overflow-hidden rounded-full bg-secondary">
          {me.avatar_url ? (
            // eslint-disable-next-line @next/next/no-img-element
            <img src={me.avatar_url} alt="" className="size-full object-cover" />
          ) : (
            <User aria-hidden="true" className="size-7 text-muted-foreground" />
          )}
        </div>
        <div className="flex flex-col gap-1.5">
          <input
            ref={fileInputRef}
            type="file"
            // "image/*" -- mange mobilnettlesere viser bare galleri, ikke
            // kamera, når accept lister flere eksplisitte MIME-typer.
            accept="image/*"
            className="hidden"
            onChange={handleAvatarSelected}
          />
          <Button
            type="button"
            variant="outline"
            size="sm"
            disabled={uploadingAvatar}
            onClick={() => fileInputRef.current?.click()}
            className="h-9 rounded-xl font-semibold"
          >
            <Camera aria-hidden="true" className="size-4" />
            {me.avatar_url ? "Bytt bilde" : "Last opp bilde"}
          </Button>
          {me.avatar_url && (
            <button
              type="button"
              onClick={handleRemoveAvatar}
              disabled={uploadingAvatar}
              className="inline-flex w-fit items-center gap-1 text-xs font-semibold text-muted-foreground transition-colors hover:text-destructive"
            >
              <X aria-hidden="true" className="size-3" />
              Fjern bilde
            </button>
          )}
        </div>
      </div>

      <form onSubmit={handleSubmit} className="flex flex-col gap-3">
        <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="first-name" className="text-sm font-semibold">
              Fornavn
            </Label>
            <Input
              id="first-name"
              value={firstName}
              onChange={(e) => setFirstName(e.target.value)}
              className="h-11 rounded-xl"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="last-name" className="text-sm font-semibold">
              Etternavn
            </Label>
            <Input
              id="last-name"
              value={lastName}
              onChange={(e) => setLastName(e.target.value)}
              className="h-11 rounded-xl"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="birth-date" className="text-sm font-semibold">
              Fødselsdato
            </Label>
            <Input
              id="birth-date"
              type="date"
              value={birthDate}
              onChange={(e) => setBirthDate(e.target.value)}
              aria-invalid={underMinimumAge}
              className="h-11 rounded-xl"
            />
            {underMinimumAge ? (
              <p role="alert" className="text-sm text-destructive text-pretty">
                Du må være minst 13 år for å bruke TeeCup.
              </p>
            ) : null}
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="gender" className="text-sm font-semibold">
              Kjønn
            </Label>
            <select
              id="gender"
              value={gender}
              onChange={(e) => setGender(e.target.value)}
              className="h-11 rounded-xl border border-border bg-card px-3 text-sm font-medium text-foreground outline-none"
            >
              <option value="">Ikke satt</option>
              <option value="f">Dame</option>
              <option value="m">Herre</option>
              <option value="x">Annet</option>
            </select>
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="hcp" className="text-sm font-semibold">
              HCP
            </Label>
            <Input
              id="hcp"
              inputMode="decimal"
              value={hcp}
              onChange={(e) => setHcp(e.target.value)}
              className="h-11 rounded-xl"
            />
          </div>
          <CountryField id="country" value={country} onChange={setCountry} />
          <HomeClubField id="home-club" value={homeClub} onChange={setHomeClub} />
          <div className="flex flex-col gap-1.5 sm:col-span-2">
            <Label htmlFor="bio" className="text-sm font-semibold">
              Beskrivelse <span className="font-normal text-muted-foreground">(valgfritt)</span>
            </Label>
            <textarea
              id="bio"
              value={bio}
              onChange={(e) => setBio(e.target.value)}
              rows={2}
              placeholder="F.eks. Golf nut, running teeoff.no"
              className="rounded-xl border border-border bg-card px-3 py-2 text-sm text-foreground outline-none"
            />
          </div>
          <div className="flex flex-col gap-1.5 sm:col-span-2">
            <Label htmlFor="mobile-number" className="text-sm font-semibold">
              Mobil
            </Label>
            <div className="flex gap-2">
              <Input
                id="mobile-country-code"
                aria-label="Landsnummer"
                value={mobileCountryCode}
                onChange={(e) => setMobileCountryCode(e.target.value)}
                placeholder="+47"
                className="h-11 w-20 shrink-0 rounded-xl text-center"
              />
              <Input
                id="mobile-number"
                type="tel"
                value={mobileNumber}
                onChange={(e) => setMobileNumber(e.target.value)}
                placeholder="912 34 567"
                className="h-11 flex-1 rounded-xl"
              />
            </div>
          </div>
        </div>

        <div className="flex flex-col gap-2">
          <div className="flex items-baseline justify-between gap-2">
            <Label className="text-sm font-semibold">Køllebag</Label>
            <span className="text-xs font-medium text-muted-foreground">
              {bagClubs.length}/{MAX_BAG_CLUBS} valgt
            </span>
          </div>
          <p className="text-xs leading-relaxed text-muted-foreground text-pretty">
            Velg køllene du spiller med (maks {MAX_BAG_CLUBS}). Disse blir tilgjengelige som
            valg når du registrerer hvilken kølle du slo ut med i en runde.
          </p>
          <div className="grid grid-cols-2 gap-1.5 sm:grid-cols-3">
            {BAG_CLUBS.map((club) => {
              const checked = bagClubs.includes(club)
              const disabled = !checked && bagClubs.length >= MAX_BAG_CLUBS
              return (
                <label
                  key={club}
                  className={`flex min-h-11 items-center gap-2 rounded-xl border px-3 py-2 text-sm font-medium ${
                    checked ? "border-primary bg-primary/10 text-foreground" : "border-border text-foreground"
                  } ${disabled ? "opacity-50" : "cursor-pointer"}`}
                >
                  <input
                    type="checkbox"
                    checked={checked}
                    disabled={disabled}
                    onChange={(e) =>
                      setBagClubs((prev) =>
                        e.target.checked ? [...prev, club] : prev.filter((c) => c !== club),
                      )
                    }
                    className="size-4 shrink-0 rounded border-border"
                  />
                  {club}
                </label>
              )
            })}
          </div>
        </div>

        {error && <p className="text-sm font-medium text-destructive">{error}</p>}
        {success && <p className="text-sm font-medium text-primary">Profilen er oppdatert.</p>}

        <Button type="submit" disabled={saving || underMinimumAge} className="h-11 w-fit rounded-xl font-semibold">
          {saving ? "Lagrer …" : "Lagre profil"}
        </Button>
      </form>

      <ActualHandicapCard me={me} onChanged={onChanged} />
      <HandicapHistorySection reloadKey={me.handicap_index} />
    </section>
  )
}

// Faktisk (beregnet) HCP, ADR-038 -- WHS Handicap Index regnet automatisk
// fra frittstående runder (round.py sin _recompute_computed_handicap_index).
// Aldri direkte redigerbar -- kun "manuelt satt HCP" over er det. Denne
// kortet lar brukeren EKSPLISITT kopiere over verdien, ingen automatikk.
function ActualHandicapCard({ me, onChanged }: { me: Me; onChanged: () => void }) {
  const [applying, setApplying] = useState(false)
  const [error, setError] = useState<string | null>(null)

  async function applyComputed() {
    setApplying(true)
    setError(null)
    try {
      const res = await fetch("/auth/profile/handicap/apply-computed", {
        method: "POST",
        credentials: "include",
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        throw new Error(body?.detail?.message ?? "Klarte ikke å overføre HCP.")
      }
      onChanged()
    } catch (err) {
      setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
    } finally {
      setApplying(false)
    }
  }

  return (
    <div className="flex flex-col gap-2 rounded-xl border border-border bg-card p-4">
      <span className="text-sm font-semibold text-foreground">Faktisk HCP (beregnet)</span>
      {me.computed_handicap_index === null ? (
        <p className="text-sm text-muted-foreground">
          Ingen beregning ennå -- trengs minst 3 tellende frittstående runder.
        </p>
      ) : (
        <>
          <p className="text-sm text-muted-foreground">
            Basert på dine siste tellende runder (WHS Rule 5.2), sist beregnet{" "}
            {me.computed_handicap_index_updated_at
              ? new Date(me.computed_handicap_index_updated_at).toLocaleDateString("no-NO", {
                  day: "numeric",
                  month: "short",
                  year: "numeric",
                })
              : "nylig"}
            .
          </p>
          <div className="flex flex-wrap items-center gap-3">
            <span className="text-2xl font-extrabold tabular-nums text-foreground">
              {me.computed_handicap_index}
            </span>
            <Button
              type="button"
              variant="outline"
              disabled={applying || me.computed_handicap_index === me.handicap_index}
              onClick={applyComputed}
              className="h-9 rounded-xl text-sm font-semibold"
            >
              {applying ? "Overfører …" : "Bruk som mitt HCP →"}
            </Button>
          </div>
        </>
      )}
      {error && <p className="text-sm font-medium text-destructive">{error}</p>}
    </div>
  )
}

// HCP-historikk (ADR-031 sitt "naturlig neste steg"-punkt): en append-only
// logg bygget opp av selve PATCH-endepunktet (app/routers/auth.py) hver
// gang HCP-feltet faktisk endres til en tallverdi. `reloadKey` (gjeldende
// HCP) sørger for at listen hentes på nytt rett etter en lagring, uten en
// egen refetch-prop å tre gjennom fra ProfileSection.
function HandicapHistorySection({ reloadKey }: { reloadKey: number | null }) {
  const [open, setOpen] = useState(false)
  const [entries, setEntries] = useState<
    { handicap_index: number; recorded_at: string; source: "manual" | "computed" }[] | null
  >(null)

  useEffect(() => {
    if (!open) return
    let cancelled = false
    fetch("/auth/profile/handicap-history", { credentials: "include" })
      .then((res) => (res.ok ? res.json() : []))
      .then((data) => {
        if (!cancelled) setEntries(data)
      })
      .catch(() => {
        if (!cancelled) setEntries([])
      })
    return () => {
      cancelled = true
    }
  }, [open, reloadKey])

  return (
    <div className="border-t border-border pt-4">
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className="text-sm font-semibold text-primary underline-offset-2 hover:underline"
      >
        {open ? "Skjul HCP-historikk" : "Vis HCP-historikk"}
      </button>

      {open && (
        <div className="mt-3">
          {entries === null ? (
            <p className="text-sm text-muted-foreground">Laster …</p>
          ) : entries.length === 0 ? (
            <p className="text-sm text-muted-foreground text-pretty">
              Ingen historikk ennå — registreres automatisk neste gang du endrer HCP.
            </p>
          ) : (
            <ul className="flex flex-col gap-1.5">
              {[...entries].reverse().map((entry, i) => (
                <li
                  key={`${entry.recorded_at}-${i}`}
                  className="flex items-center justify-between gap-3 text-sm"
                >
                  <span className="text-muted-foreground">
                    {new Date(entry.recorded_at).toLocaleDateString("no-NO", {
                      day: "numeric",
                      month: "short",
                      year: "numeric",
                    })}
                  </span>
                  <span className="flex items-center gap-2">
                    <span className="font-semibold tabular-nums text-foreground">{entry.handicap_index}</span>
                    <span className="text-xs font-medium text-muted-foreground">
                      {entry.source === "computed" ? "beregnet" : "manuelt"}
                    </span>
                  </span>
                </li>
              ))}
            </ul>
          )}
        </div>
      )}
    </div>
  )
}

// --- E-post (identifikatoren) -----------------------------------------------
// Bevisst IKKE en del av ProfileSection sin vanlige PATCH -- e-post er
// innloggings-identifikatoren, endring krever at den NYE adressen beviser
// eierskap først (se app/routers/auth.py sin request_email_change/
// confirm_email_change).

function EmailSection({ email }: { email: string }) {
  const [editing, setEditing] = useState(false)
  const [newEmail, setNewEmail] = useState("")
  const [submitting, setSubmitting] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [sent, setSent] = useState(false)

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setSubmitting(true)
    setError(null)
    try {
      const res = await fetch("/auth/profile/email", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ new_email: newEmail.trim() }),
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        throw new Error(body?.detail?.message ?? "Klarte ikke å sende bekreftelseslenken.")
      }
      setSent(true)
    } catch (err) {
      setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
    } finally {
      setSubmitting(false)
    }
  }

  return (
    <section className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6">
      <div className="flex items-center gap-2.5">
        <div className="flex size-10 items-center justify-center rounded-xl bg-muted">
          <Mail aria-hidden="true" className="size-5 text-muted-foreground" />
        </div>
        <h2 className="text-base font-bold text-foreground">E-post</h2>
      </div>

      <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
        Dette er identifikatoren du logger inn med: <span className="font-semibold text-foreground">{email}</span>
      </p>

      {sent ? (
        <p className="text-sm font-medium text-primary text-pretty">
          Sjekk innboksen til {newEmail.trim()} — åpne lenken der for å fullføre byttet. Adressen
          endres ikke før den er bekreftet.
        </p>
      ) : editing ? (
        <form onSubmit={handleSubmit} className="flex flex-col gap-3 sm:flex-row sm:items-end">
          <div className="flex flex-1 flex-col gap-1.5">
            <Label htmlFor="new-email" className="text-sm font-semibold">
              Ny e-postadresse
            </Label>
            <Input
              id="new-email"
              type="email"
              autoFocus
              value={newEmail}
              onChange={(e) => setNewEmail(e.target.value)}
              className="h-12 rounded-xl"
            />
          </div>
          <div className="flex gap-2">
            <Button
              type="submit"
              disabled={submitting || newEmail.trim() === ""}
              className="h-12 shrink-0 rounded-xl font-semibold"
            >
              {submitting ? "Sender …" : "Send bekreftelse"}
            </Button>
            <Button
              type="button"
              variant="ghost"
              onClick={() => setEditing(false)}
              className="h-12 shrink-0 rounded-xl font-semibold"
            >
              Avbryt
            </Button>
          </div>
        </form>
      ) : (
        <Button
          type="button"
          variant="outline"
          onClick={() => setEditing(true)}
          className="h-11 w-fit rounded-xl font-semibold"
        >
          Endre e-post
        </Button>
      )}

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

// Én person, flere e-postadresser (FEATURE_BACKLOG.md "Del 1") -- kun det
// enkle tilfellet: en FRI, ukrevd adresse legges til og verifiseres, og kan
// deretter brukes til innlogging i tillegg til hovedadressen. Tilhører
// adressen ALLEREDE en annen konto, avviser backend med 409 DUPLICATE og
// henviser til AccountMergeSection under ("Del 2", ADR-080).
function SecondaryEmailSection({
  secondaryEmails,
  onChanged,
}: {
  secondaryEmails: { id: string; email: string }[]
  onChanged: () => void
}) {
  const [adding, setAdding] = useState(false)
  const [newEmail, setNewEmail] = useState("")
  const [submitting, setSubmitting] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [sent, setSent] = useState(false)
  const [removingId, setRemovingId] = useState<string | null>(null)

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setSubmitting(true)
    setError(null)
    try {
      const res = await fetch("/auth/secondary-email", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ email: newEmail.trim() }),
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        throw new Error(body?.detail?.message ?? "Klarte ikke å sende bekreftelseslenken.")
      }
      setSent(true)
    } catch (err) {
      setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
    } finally {
      setSubmitting(false)
    }
  }

  async function handleRemove(id: string) {
    setRemovingId(id)
    try {
      const res = await fetch(`/auth/secondary-email/${id}`, { method: "DELETE", credentials: "include" })
      if (res.ok || res.status === 404) onChanged()
    } finally {
      setRemovingId(null)
    }
  }

  return (
    <section className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6">
      <div className="flex items-center gap-2.5">
        <div className="flex size-10 items-center justify-center rounded-xl bg-muted">
          <Mail aria-hidden="true" className="size-5 text-muted-foreground" />
        </div>
        <h2 className="text-base font-bold text-foreground">Andre e-postadresser</h2>
      </div>

      <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
        Har du fått en turneringsinvitasjon på en annen adresse enn {""}
        {"hovedadressen din"}? Legg den til her, så kan du logge inn med begge — og turneringer/
        data knyttet til den andre adressen dukker opp på kontoen din.
      </p>

      {secondaryEmails.length > 0 && (
        <ul className="flex flex-col gap-2">
          {secondaryEmails.map((se) => (
            <li
              key={se.id}
              className="flex items-center justify-between gap-2 rounded-xl border border-border bg-background px-3 py-2.5"
            >
              <span className="truncate text-sm font-semibold text-foreground">{se.email}</span>
              <Button
                type="button"
                variant="ghost"
                size="icon"
                disabled={removingId === se.id}
                onClick={() => handleRemove(se.id)}
                className="size-8 shrink-0 rounded-lg text-muted-foreground hover:text-destructive"
                aria-label={`Fjern ${se.email}`}
              >
                <Trash2 aria-hidden="true" className="size-4" />
              </Button>
            </li>
          ))}
        </ul>
      )}

      {sent ? (
        <p className="text-sm font-medium text-primary text-pretty">
          Sjekk innboksen til {newEmail.trim()} — åpne lenken der for å bekrefte at du eier
          adressen. Ingenting legges til før den er bekreftet.
        </p>
      ) : adding ? (
        <form onSubmit={handleSubmit} className="flex flex-col gap-3 sm:flex-row sm:items-end">
          <div className="flex flex-1 flex-col gap-1.5">
            <Label htmlFor="secondary-email" className="text-sm font-semibold">
              Ny adresse
            </Label>
            <Input
              id="secondary-email"
              type="email"
              autoFocus
              value={newEmail}
              onChange={(e) => setNewEmail(e.target.value)}
              className="h-12 rounded-xl"
            />
          </div>
          <div className="flex gap-2">
            <Button
              type="submit"
              disabled={submitting || newEmail.trim() === ""}
              className="h-12 shrink-0 rounded-xl font-semibold"
            >
              {submitting ? "Sender …" : "Send bekreftelse"}
            </Button>
            <Button
              type="button"
              variant="ghost"
              onClick={() => setAdding(false)}
              className="h-12 shrink-0 rounded-xl font-semibold"
            >
              Avbryt
            </Button>
          </div>
        </form>
      ) : (
        <Button
          type="button"
          variant="outline"
          onClick={() => setAdding(true)}
          className="h-11 w-fit rounded-xl font-semibold"
        >
          <Plus aria-hidden="true" className="size-4" />
          Legg til adresse
        </Button>
      )}

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

// --- Kontosammenslåing ("Del 2", ADR-080, 2026-08-16) -----------------------
// Selvbetjent: bruker taster inn e-posten til en ANNEN, egen konto, ser en
// forhåndsvisning av hva som vil bli slått sammen, og ber om en
// bekreftelseslenke som sendes til DEN kontoens e-post (beviser eierskap av
// den, samme prinsipp som e-postbytte). Selve bekreftelsen/utførelsen skjer
// på en egen side (/kontosammenslaing/[token]) når lenken åpnes -- ALDRI her,
// denne seksjonen sender kun forespørselen.

type ApiOrgMembershipPreview = {
  organization_id: string
  organization_name: string
  keeper_role: string | null
  loser_role: string | null
  resulting_role: string
}

type ApiOrgPlayerPreview = {
  organization_id: string
  organization_name: string
  keeper_has_player: boolean
  loser_has_player: boolean
  colliding_registrations: number
}

type ApiMergePreview = {
  keeper_id: string
  keeper_display_name: string
  keeper_email: string | null
  loser_id: string
  loser_display_name: string
  loser_email: string | null
  organization_memberships: ApiOrgMembershipPreview[]
  player_links: ApiOrgPlayerPreview[]
  friend_count: number
  shared_friend_count: number
  were_friends_with_each_other: boolean
  round_count: number
  secondary_email_count: number
}

const ROLE_LABELS: Record<string, string> = { owner: "eier", admin: "administrator", member: "medlem" }

function AccountMergeSection() {
  const [email, setEmail] = useState("")
  const [preview, setPreview] = useState<ApiMergePreview | null>(null)
  const [loadingPreview, setLoadingPreview] = useState(false)
  const [requesting, setRequesting] = useState(false)
  const [sent, setSent] = useState(false)
  const [error, setError] = useState<string | null>(null)

  async function handlePreview(e: React.FormEvent) {
    e.preventDefault()
    setError(null)
    setLoadingPreview(true)
    setPreview(null)
    try {
      const res = await fetch("/auth/account-merge/preview", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ email: email.trim() }),
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        throw new Error(body?.detail?.message ?? "Fant ingen konto med denne e-posten.")
      }
      setPreview(await res.json())
    } catch (err) {
      setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
    } finally {
      setLoadingPreview(false)
    }
  }

  async function handleRequest() {
    setRequesting(true)
    setError(null)
    try {
      const res = await fetch("/auth/account-merge/request", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ email: email.trim() }),
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        throw new Error(body?.detail?.message ?? "Klarte ikke å sende bekreftelseslenken.")
      }
      setSent(true)
    } catch (err) {
      setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
    } finally {
      setRequesting(false)
    }
  }

  return (
    <section className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6">
      <div className="flex items-center gap-2.5">
        <div className="flex size-10 items-center justify-center rounded-xl bg-muted">
          <Merge aria-hidden="true" className="size-5 text-muted-foreground" />
        </div>
        <h2 className="text-base font-bold text-foreground">Slå sammen med en annen konto</h2>
      </div>

      <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
        Har du endt opp med to TeeCup-kontoer (f.eks. logget inn med to ulike
        adresser)? Slå dem sammen til én -- alt fra den andre kontoen
        (organisasjoner, spiller-koblinger, runder, venner) flyttes over hit.
      </p>

      {sent ? (
        <p className="text-sm font-medium text-primary text-pretty">
          Sjekk innboksen til {email.trim()} -- åpne lenken der for å se
          nøyaktig hva som slås sammen og bekrefte. Ingenting slås sammen før
          det er bekreftet der.
        </p>
      ) : preview ? (
        <div className="flex flex-col gap-3">
          <p className="text-sm font-semibold text-foreground">
            {preview.loser_display_name}
            {preview.loser_email ? ` (${preview.loser_email})` : ""} slås inn i denne kontoen:
          </p>
          <ul className="flex flex-col gap-1.5 rounded-2xl border border-border bg-background p-3 text-sm text-foreground">
            {preview.organization_memberships.map((m) => (
              <li key={m.organization_id}>
                {m.organization_name}: du blir {ROLE_LABELS[m.resulting_role] ?? m.resulting_role}
                {m.keeper_role && m.loser_role && m.keeper_role !== m.resulting_role
                  ? ` (oppgradert fra ${ROLE_LABELS[m.keeper_role] ?? m.keeper_role})`
                  : ""}
              </li>
            ))}
            {preview.player_links.map((p) => (
              <li key={p.organization_id}>
                {p.organization_name}: spiller-koblingen slås sammen
                {p.colliding_registrations > 0
                  ? ` (${p.colliding_registrations} dobbeltregistrering${p.colliding_registrations === 1 ? "" : "er"} ryddes opp)`
                  : ""}
              </li>
            ))}
            {preview.round_count > 0 && <li>{preview.round_count} runde{preview.round_count === 1 ? "" : "r"} flyttes over</li>}
            {preview.friend_count > 0 && (
              <li>
                {preview.friend_count} venn{preview.friend_count === 1 ? "" : "er"} flyttes over
                {preview.shared_friend_count > 0 ? ` (${preview.shared_friend_count} felles, slås sammen)` : ""}
              </li>
            )}
            {preview.were_friends_with_each_other && <li>Dere var venner med hverandre -- fjernes ved sammenslåing</li>}
            {preview.secondary_email_count > 0 && (
              <li>
                {preview.secondary_email_count} ekstra e-postadresse{preview.secondary_email_count === 1 ? "" : "r"} flyttes over
              </li>
            )}
          </ul>

          <div className="flex flex-col gap-2 rounded-2xl border border-destructive/40 bg-destructive/5 p-3">
            <p className="text-sm font-semibold text-foreground text-pretty">
              Dette kan ikke angres. Den andre kontoen slettes helt så snart
              sammenslåingen er bekreftet.
            </p>
            <div className="flex gap-2">
              <Button
                type="button"
                variant="outline"
                onClick={() => setPreview(null)}
                className="min-h-11 flex-1 justify-center rounded-xl font-bold"
              >
                Avbryt
              </Button>
              <Button
                type="button"
                variant="destructive"
                disabled={requesting}
                onClick={handleRequest}
                className="min-h-11 flex-1 justify-center gap-1.5 rounded-xl font-bold"
              >
                <Merge aria-hidden="true" className="size-4" />
                {requesting ? "Sender …" : "Send bekreftelse"}
              </Button>
            </div>
          </div>
        </div>
      ) : (
        <form onSubmit={handlePreview} className="flex flex-col gap-3 sm:flex-row sm:items-end">
          <div className="flex flex-1 flex-col gap-1.5">
            <Label htmlFor="merge-email" className="text-sm font-semibold">
              E-post til den andre kontoen
            </Label>
            <Input
              id="merge-email"
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              className="h-12 rounded-xl"
            />
          </div>
          <Button
            type="submit"
            disabled={loadingPreview || email.trim() === ""}
            className="h-12 shrink-0 rounded-xl font-semibold"
          >
            {loadingPreview ? "Ser etter …" : "Se hva som slås sammen"}
          </Button>
        </form>
      )}

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

function PasswordSection({ hasPassword, onChanged }: { hasPassword: boolean; onChanged: () => void }) {
  const [password, setPassword] = useState("")
  const [submitting, setSubmitting] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [success, setSuccess] = useState(false)

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    if (password.length < 8 || submitting) return
    setSubmitting(true)
    setError(null)
    setSuccess(false)
    try {
      const res = await fetch("/auth/set-password", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ password }),
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        throw new Error(body?.detail?.message ?? "Klarte ikke å sette passordet.")
      }
      setPassword("")
      setSuccess(true)
      onChanged()
    } catch (err) {
      setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
    } finally {
      setSubmitting(false)
    }
  }

  async function handleRemove() {
    if (!confirm("Fjerne passordet? Du kan fortsatt logge inn med magic-link.")) return
    const res = await fetch("/auth/remove-password", { method: "POST", credentials: "include" })
    if (res.ok) onChanged()
  }

  return (
    <section className="flex flex-col gap-4 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6">
      <div className="flex items-center gap-2.5">
        <div className="flex size-10 items-center justify-center rounded-xl bg-muted">
          <Lock aria-hidden="true" className="size-5 text-muted-foreground" />
        </div>
        <h2 className="text-base font-bold text-foreground">Passord</h2>
      </div>

      <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
        {hasPassword
          ? "Du har et passord satt. Du kan fortsatt logge inn med magic-link når som helst."
          : "Ikke satt -- du logger i dag kun inn med magic-link. Passord er et valgfritt tillegg, aldri påkrevd."}
      </p>

      <form onSubmit={handleSubmit} className="flex flex-col gap-3 sm:flex-row sm:items-end">
        <div className="flex flex-1 flex-col gap-1.5">
          <Label htmlFor="new-password" className="text-sm font-semibold">
            {hasPassword ? "Nytt passord" : "Sett et passord"}
          </Label>
          <Input
            id="new-password"
            type="password"
            autoComplete="new-password"
            placeholder="Minst 8 tegn"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            className="h-12 rounded-xl"
          />
        </div>
        <Button
          type="submit"
          disabled={password.length < 8 || submitting}
          className="h-12 shrink-0 rounded-xl font-semibold"
        >
          {submitting ? "Lagrer …" : hasPassword ? "Bytt passord" : "Sett passord"}
        </Button>
      </form>

      {error && <p className="text-sm font-medium text-destructive">{error}</p>}
      {success && <p className="text-sm font-medium text-primary">Passordet er oppdatert.</p>}

      {hasPassword && (
        <button
          type="button"
          onClick={handleRemove}
          className="w-fit text-sm font-semibold text-muted-foreground transition-colors hover:text-destructive"
        >
          Fjern passordet
        </button>
      )}
    </section>
  )
}

// --- E-post-fallback for varsler (2026-07-28 oppfølging) --------------------
// Trygg standard: ingen typer valgt inntil brukeren selv velger inn --
// PUT /notifications/email-prefs er en FULL erstatning (samme mønster som
// venne-kategoriseringen), ikke en delvis PATCH.
const NOTIFICATION_TYPE_OPTIONS: { value: string; label: string; description: string }[] = [
  { value: "friend", label: "Venneforespørsler", description: "Noen sender deg en venneforespørsel, eller godtar din." },
  { value: "round", label: "Runder", description: "Du blir lagt til som medspiller på en runde." },
  { value: "result", label: "Resultater", description: "En runde du er koblet til blir fullført." },
  { value: "tournament", label: "Turneringer", description: "Reservert for fremtidige turnering-varsler." },
]

// Push-varsler til telefonens OS (Web Push/VAPID, 2026-07-28). Egen seksjon
// ved siden av e-post-varianten over -- selve abonnementet ER samtykket her
// (ulikt e-post, som trenger en egen per-type opt-in), så dette er kun en
// enkel av/på-bryter for DENNE enheten/nettleseren.
function PushNotificationSection() {
  const [supported, setSupported] = useState<boolean | null>(null)
  const [subscribed, setSubscribed] = useState(false)
  const [busy, setBusy] = useState(false)
  const [message, setMessage] = useState<string | null>(null)
  const [isIOS, setIsIOS] = useState(false)
  const [isStandalone, setIsStandalone] = useState(false)

  useEffect(() => {
    setSupported(isPushSupported())
    setIsIOS(/iphone|ipad|ipod/i.test(window.navigator.userAgent))
    setIsStandalone(window.matchMedia("(display-mode: standalone)").matches)
    void getExistingSubscription().then((sub) => setSubscribed(sub !== null))
  }, [])

  async function toggle() {
    setBusy(true)
    setMessage(null)
    try {
      if (subscribed) {
        await unsubscribeFromPush()
        setSubscribed(false)
        setMessage("Push-varsler er slått av på denne enheten.")
      } else {
        const result = await subscribeToPush()
        if (result.ok) {
          setSubscribed(true)
          setMessage("Push-varsler er slått på for denne enheten.")
        } else if (result.reason === "denied") {
          setMessage("Nettleseren din blokkerer varsler -- gi TeeCup tillatelse i nettleserinnstillingene.")
        } else if (result.reason === "not-configured") {
          setMessage("Push er ikke satt opp på serveren ennå.")
        } else {
          setMessage("Klarte ikke å slå på push-varsler. Prøv igjen.")
        }
      }
    } finally {
      setBusy(false)
    }
  }

  return (
    <section className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6">
      <div className="flex items-center gap-2.5">
        <div className="flex size-10 items-center justify-center rounded-xl bg-muted">
          <BellRing aria-hidden="true" className="size-5 text-muted-foreground" />
        </div>
        <h2 className="text-base font-bold text-foreground">Push-varsler på denne enheten</h2>
      </div>
      <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
        Få varsler direkte fra telefonen/nettleseren din, selv når TeeCup ikke er åpen -- for eksempel
        når noen sender deg en venneforespørsel eller en runde du følger blir ferdig.
      </p>

      {supported === false && isIOS && !isStandalone && (
        <p className="rounded-xl border border-dashed border-border bg-muted/40 px-3 py-2.5 text-sm text-muted-foreground">
          På iPhone/iPad må TeeCup først legges til på hjemskjermen (Del-ikonet i Safari → «Legg til på
          Hjem-skjerm») før push-varsler kan slås på.
        </p>
      )}
      {supported === false && !isIOS && (
        <p className="rounded-xl border border-dashed border-border bg-muted/40 px-3 py-2.5 text-sm text-muted-foreground">
          Nettleseren din støtter ikke push-varsler.
        </p>
      )}

      {supported && (
        <button
          type="button"
          onClick={() => void toggle()}
          disabled={busy}
          className={`inline-flex min-h-11 w-fit items-center gap-2 rounded-xl border px-4 text-sm font-bold transition-colors disabled:opacity-60 ${
            subscribed
              ? "border-primary bg-primary text-primary-foreground"
              : "border-border bg-card text-foreground hover:bg-accent/50"
          }`}
        >
          <BellRing aria-hidden="true" className="size-4" />
          {busy ? "Vent…" : subscribed ? "Push-varsler er PÅ -- slå av" : "Slå på push-varsler"}
        </button>
      )}

      {message && <p className="text-sm text-muted-foreground">{message}</p>}
    </section>
  )
}

// --- Mine baner (2026-08-04) -------------------------------------------------
// Administrasjon av `personal_course`-rader brukeren eier -- det delte,
// plattform-omfattende bane-biblioteket som både turneringsmodulen og
// single-runde-modulen nå bruker som mal-kilde. Redigering skjer i sted
// (PATCH, samme eier -- ingen fork utløses); dupliser/slett er egne,
// eksplisitte handlinger. Andres baner vises IKKE her -- kun i mal-søket
// i de respektive modulene (denne siden er ren eierskapsadministrasjon).

type ApiMyCourse = { id: string; name: string; is_mine: boolean; created_by_display_name: string }
type ApiMyCourseDetail = {
  id: string
  name: string
  holes: CourseTemplateValues["holes"]
  full_tees: CourseTemplateValues["tees"]
}

function MyCoursesSection() {
  const [courses, setCourses] = useState<ApiMyCourse[] | null>(null)
  const [editingId, setEditingId] = useState<string | null>(null)
  const [editingInitial, setEditingInitial] = useState<CourseTemplateValues | null>(null)
  const [submitting, setSubmitting] = useState(false)
  const [error, setError] = useState<string | null>(null)

  async function load() {
    const res = await fetch("/personal-courses?mine=true", { credentials: "include" })
    if (res.ok) setCourses(await res.json())
  }

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

  async function startEdit(id: string) {
    setError(null)
    const res = await fetch(`/personal-courses/${id}`, { credentials: "include" })
    if (!res.ok) {
      setError("Klarte ikke å hente banedetaljer.")
      return
    }
    const detail: ApiMyCourseDetail = await res.json()
    setEditingInitial({ name: detail.name, holes: detail.holes, tees: detail.full_tees })
    setEditingId(id)
  }

  async function saveEdit(values: CourseTemplateValues) {
    if (!editingId) return
    setSubmitting(true)
    setError(null)
    try {
      const res = await fetch(`/personal-courses/${editingId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify(values),
      })
      if (!res.ok) throw new Error(`update: ${res.status}`)
      setEditingId(null)
      setEditingInitial(null)
      await load()
    } catch {
      setError("Klarte ikke å lagre endringene.")
    } finally {
      setSubmitting(false)
    }
  }

  async function duplicate(id: string) {
    setError(null)
    const res = await fetch(`/personal-courses/${id}/duplicate`, { method: "POST", credentials: "include" })
    if (!res.ok) {
      setError("Klarte ikke å duplisere banen.")
      return
    }
    await load()
  }

  async function remove(id: string) {
    if (!confirm("Slette denne banen? Dette kan ikke angres.")) return
    setError(null)
    const res = await fetch(`/personal-courses/${id}`, { method: "DELETE", credentials: "include" })
    if (res.status === 204) {
      await load()
      return
    }
    const body = await res.json().catch(() => null)
    setError(
      body?.detail?.code === "IN_USE"
        ? "Denne banen er i bruk i én eller flere runder og kan ikke slettes."
        : "Klarte ikke å slette banen.",
    )
  }

  return (
    <section className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6">
      <div className="flex items-center gap-2.5">
        <div className="flex size-10 items-center justify-center rounded-xl bg-muted">
          <Flag aria-hidden="true" className="size-5 text-muted-foreground" />
        </div>
        <div className="flex flex-col">
          <h2 className="text-base font-bold text-foreground">Mine baner</h2>
          <p className="text-xs text-muted-foreground text-pretty">
            Egendefinerte baner du har opprettet -- offentlig synlige og brukbare som mal av andre,
            i både turneringsmodulen og single-runde-modulen.
          </p>
        </div>
      </div>

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

      {editingId ? (
        <CourseTemplateEditor
          initial={editingInitial ?? undefined}
          submitLabel="Lagre endringer"
          submitting={submitting}
          error={null}
          onSubmit={saveEdit}
          onCancel={() => {
            setEditingId(null)
            setEditingInitial(null)
          }}
        />
      ) : courses === null ? (
        <p className="text-sm text-muted-foreground">Laster…</p>
      ) : courses.length === 0 ? (
        <p className="text-sm text-muted-foreground">
          Ingen egne baner ennå -- opprett en via "Egen bane" i ny-runde-veiviseren, eller "Opprett
          bane med hull/utslag" i en turnering.
        </p>
      ) : (
        <ul className="flex flex-col divide-y divide-border rounded-2xl border border-border">
          {courses.map((c) => (
            <li key={c.id} className="flex items-center gap-3 px-4 py-3">
              <span className="min-w-0 flex-1 truncate text-sm font-semibold text-foreground">{c.name}</span>
              <Button
                type="button"
                variant="ghost"
                size="icon"
                onClick={() => startEdit(c.id)}
                aria-label={`Rediger ${c.name}`}
                className="size-9 rounded-lg text-muted-foreground"
              >
                <Pencil aria-hidden="true" className="size-4" />
              </Button>
              <Button
                type="button"
                variant="ghost"
                size="icon"
                onClick={() => duplicate(c.id)}
                aria-label={`Dupliser ${c.name}`}
                className="size-9 rounded-lg text-muted-foreground"
              >
                <Copy aria-hidden="true" className="size-4" />
              </Button>
              <Button
                type="button"
                variant="ghost"
                size="icon"
                onClick={() => remove(c.id)}
                aria-label={`Slett ${c.name}`}
                className="size-9 rounded-lg text-muted-foreground hover:text-destructive"
              >
                <Trash2 aria-hidden="true" className="size-4" />
              </Button>
            </li>
          ))}
        </ul>
      )}
    </section>
  )
}

function NotificationEmailPrefsSection() {
  const [types, setTypes] = useState<string[]>([])
  const [loaded, setLoaded] = useState(false)
  const [saving, setSaving] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [success, setSuccess] = useState(false)

  useEffect(() => {
    fetch("/notifications/email-prefs", { credentials: "include" })
      .then((res) => (res.ok ? res.json() : null))
      .then((data: { types: string[] } | null) => {
        if (data) setTypes(data.types)
        setLoaded(true)
      })
      .catch(() => setLoaded(true))
  }, [])

  async function save(next: string[]) {
    setTypes(next)
    setSaving(true)
    setError(null)
    setSuccess(false)
    try {
      const res = await fetch("/notifications/email-prefs", {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ types: next }),
      })
      if (!res.ok) throw new Error("Klarte ikke å lagre varselinnstillingene.")
      setSuccess(true)
    } catch (err) {
      setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
    } finally {
      setSaving(false)
    }
  }

  return (
    <section className="flex flex-col gap-3 rounded-3xl border border-border bg-card p-5 shadow-md shadow-black/8 sm:p-6">
      <div className="flex items-center gap-2.5">
        <div className="flex size-10 items-center justify-center rounded-xl bg-muted">
          <Bell aria-hidden="true" className="size-5 text-muted-foreground" />
        </div>
        <h2 className="text-base font-bold text-foreground">Varsler på e-post</h2>
      </div>
      <p className="text-sm leading-relaxed text-muted-foreground text-pretty">
        Du får alltid varsler i appen (klokke-ikonet). Velg her hvilke typer du i tillegg vil ha på
        e-post -- ingen sendes med mindre du krysser av.
      </p>

      {loaded && (
        <div className="flex flex-col gap-1.5">
          {NOTIFICATION_TYPE_OPTIONS.map((opt) => {
            const checked = types.includes(opt.value)
            return (
              <label
                key={opt.value}
                className={`flex min-h-11 items-start gap-3 rounded-xl border px-3 py-2.5 ${
                  checked ? "border-primary bg-primary/10" : "border-border"
                } ${saving ? "opacity-70" : "cursor-pointer"}`}
              >
                <input
                  type="checkbox"
                  checked={checked}
                  disabled={saving}
                  onChange={(e) =>
                    void save(e.target.checked ? [...types, opt.value] : types.filter((t) => t !== opt.value))
                  }
                  className="mt-0.5 size-4 shrink-0 rounded border-border"
                />
                <span className="flex flex-col">
                  <span className="text-sm font-semibold text-foreground">{opt.label}</span>
                  <span className="text-xs text-muted-foreground text-pretty">{opt.description}</span>
                </span>
              </label>
            )
          })}
        </div>
      )}

      {error && <p className="text-sm font-medium text-destructive">{error}</p>}
      {success && <p className="text-sm font-medium text-primary">Lagret.</p>}
    </section>
  )
}
