"use client"

// Venner-kjernen (ADR-036, fase 1). Presentasjon fra V0 (zip 19), datalag
// skrevet om fra mock til ekte fetch mot /people/search + /friends.
// Ruten er bevisst /my-friends, IKKE /friends -- det er nå API-prefikset
// (samme kollisjonsfelle unngått som /rounds -> /my-rounds tidligere).

import type React from "react"
import { useCallback, useEffect, useState } from "react"
import { useRouter } from "next/navigation"
import Link from "next/link"
import {
  ArrowLeft,
  Check,
  ChevronDown,
  Search,
  Tag,
  Trash2,
  UserPlus,
  Users,
  X,
} from "lucide-react"
import { Input } from "@/components/ui/input"
import { Wordmark } from "@/components/wordmark"
import { cn } from "@/lib/utils"
import { BottomNav } from "@/components/teecup/bottom-nav"

// --- Kategorier --------------------------------------------------------------
// Rekkefølge og koder MÅ matche `Category` i app/routers/friends.py nøyaktig.

const CATEGORY_OPTIONS = [
  { code: "spouse", label: "Make" },
  { code: "close_family", label: "Nær familie" },
  { code: "extended_family", label: "Storfamilie" },
  { code: "close_friends", label: "Nære venner" },
  { code: "golf_friends", label: "Golfvenner" },
  { code: "colleagues", label: "Kollegaer" },
  { code: "business", label: "Forretningsforbindelser" },
  { code: "classmates", label: "Studiekamerater" },
  { code: "acquaintances", label: "Perifere bekjente" },
  { code: "other", label: "Ymse" },
] as const

function categoryLabel(code: string): string {
  return CATEGORY_OPTIONS.find((c) => c.code === code)?.label ?? code
}

const ALL_CATEGORY_CODES = CATEGORY_OPTIONS.map((c) => c.code)

// Delt sjekkboks-rutenett (2026-07-29) -- brukt tre steder nå (ny
// forespørsel, godta innkommende, rediger en eksisterende venn), derfor
// trukket ut fremfor duplisert. "Ingen ukategoriserte venner" (samme dag,
// presisert av bruker) håndheves her ved å nekte å fjerne den SISTE
// avkrysningen -- backend krever uansett minst én, men UI-et skal aldri
// tillate å komme i den tilstanden i utgangspunktet.
function CategoryPicker({ selected, onToggle }: { selected: string[]; onToggle: (code: string) => void }) {
  return (
    <ul className="grid grid-cols-1 gap-2 sm:grid-cols-2">
      {CATEGORY_OPTIONS.map(({ code, label }) => {
        const checked = selected.includes(code)
        const blocked = checked && selected.length <= 1
        return (
          <li key={code}>
            <button
              type="button"
              role="checkbox"
              aria-checked={checked}
              disabled={blocked}
              onClick={() => onToggle(code)}
              title={blocked ? "Minst én kategori er påkrevd" : undefined}
              className={cn(
                "flex min-h-[44px] w-full items-center gap-3 rounded-xl border px-3 py-2 text-left text-base font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
                checked ? "border-primary bg-primary/10 text-foreground" : "border-border bg-card text-foreground hover:bg-accent/50",
                blocked && "cursor-not-allowed opacity-70",
              )}
            >
              <span
                aria-hidden="true"
                className={cn(
                  "flex size-6 shrink-0 items-center justify-center rounded-md border-2 transition-colors",
                  checked ? "border-primary bg-primary text-primary-foreground" : "border-border",
                )}
              >
                {checked && <Check className="size-4" strokeWidth={3} />}
              </span>
              {label}
            </button>
          </li>
        )
      })}
    </ul>
  )
}

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

type ApiPersonMatch = { id: string; first_name: string; last_name: string; avatar_url: string | null; home_club: string | null }
type ApiFriendEntry = {
  friendship_id: string
  user_id: string
  first_name: string | null
  last_name: string | null
  avatar_url: string | null
  home_club: string | null
  categories: string[]
}
type ApiPendingRequest = {
  friendship_id: string
  user_id: string
  first_name: string | null
  last_name: string | null
  avatar_url: string | null
  created_at: string
}
type ApiFriendsOut = { friends: ApiFriendEntry[]; incoming_requests: ApiPendingRequest[]; outgoing_requests: ApiPendingRequest[] }

function fullName(first: string | null, last: string | null): string {
  const name = [first, last].filter(Boolean).join(" ").trim()
  return name || "Ukjent"
}

// --- Page ------------------------------------------------------------------

export function Friends() {
  const router = useRouter()
  const [data, setData] = useState<ApiFriendsOut | null>(null)
  const [error, setError] = useState<string | null>(null)

  const loadFriends = useCallback(async () => {
    try {
      const res = await fetch("/friends", { credentials: "include" })
      if (res.status === 401) {
        router.replace("/logg-inn")
        return
      }
      if (!res.ok) throw new Error(`friends: ${res.status}`)
      setData(await res.json())
    } catch {
      setError("Klarte ikke å hente vennelisten din. Prøv igjen om litt.")
    }
  }, [router])

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

  const friendUserIds = new Set(data?.friends.map((f) => f.user_id) ?? [])
  const outgoingUserIds = new Set(data?.outgoing_requests.map((r) => r.user_id) ?? [])

  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-3xl items-center justify-between gap-4 px-5 py-4">
          <Link
            href="/dashboard"
            className="inline-flex min-h-[44px] items-center gap-2 rounded-xl px-2 py-2 text-base font-semibold text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
          >
            <ArrowLeft aria-hidden="true" className="size-5" />
            Til dashbord
          </Link>
          <Wordmark compact />
        </div>
      </header>

      <main className="mx-auto w-full max-w-3xl flex-1 px-5 pb-24 pt-8 sm:pt-10">
        <div className="flex flex-col gap-2">
          <h1 className="text-3xl font-extrabold tracking-tight text-foreground text-balance">
            Mine venner
          </h1>
          <p className="max-w-prose text-lg leading-relaxed text-muted-foreground text-pretty">
            Finn folk du spiller med, håndter forespørsler, og organiser vennene dine i egne
            kategorier.
          </p>
        </div>

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

        <div className="mt-8 flex flex-col gap-10">
          <SearchSection friendUserIds={friendUserIds} outgoingUserIds={outgoingUserIds} onRequestSent={loadFriends} />
          <RequestsSection incoming={data?.incoming_requests ?? []} outgoing={data?.outgoing_requests ?? []} onChanged={loadFriends} />
          <FriendsSection friends={data?.friends ?? []} onChanged={loadFriends} />
        </div>
      </main>

      <BottomNav />
    </div>
  )
}

// --- Section header --------------------------------------------------------

function SectionHeading({ children }: { children: React.ReactNode }) {
  return <h2 className="text-xl font-extrabold tracking-tight text-foreground">{children}</h2>
}

// --- 1. Search ---------------------------------------------------------------

function SearchSection({
  friendUserIds,
  outgoingUserIds,
  onRequestSent,
}: {
  friendUserIds: Set<string>
  outgoingUserIds: Set<string>
  onRequestSent: () => void
}) {
  const [query, setQuery] = useState("")
  const [results, setResults] = useState<ApiPersonMatch[]>([])
  const [searching, setSearching] = useState(false)
  const [justSent, setJustSent] = useState<Record<string, boolean>>({})
  // Ingen ukategoriserte venner (2026-07-29) -- kategorisering skjer nå
  // FØR forespørselen sendes, ikke som et eget steg etterpå. Alle
  // kategorier forhåndsvalgt (bruker: "man må heller velge bort") --
  // ett åpent valg-panel om gangen (personId som nøkkel).
  const [pickerFor, setPickerFor] = useState<string | null>(null)
  const [pickerCategories, setPickerCategories] = useState<string[]>(ALL_CATEGORY_CODES)
  const [sending, setSending] = useState(false)

  function openPicker(personId: string) {
    setPickerFor(personId)
    setPickerCategories(ALL_CATEGORY_CODES)
  }

  function toggleCategoryInPicker(code: string) {
    setPickerCategories((prev) => {
      if (prev.includes(code)) return prev.length <= 1 ? prev : prev.filter((c) => c !== code)
      return [...prev, code]
    })
  }

  useEffect(() => {
    const trimmed = query.trim()
    if (trimmed.length < 2) {
      setResults([])
      return
    }
    let cancelled = false
    setSearching(true)
    const timer = setTimeout(async () => {
      try {
        const res = await fetch(`/people/search?q=${encodeURIComponent(trimmed)}`, { credentials: "include" })
        if (res.ok && !cancelled) setResults(await res.json())
      } catch {
        // Stille -- listen blir bare uendret.
      } finally {
        if (!cancelled) setSearching(false)
      }
    }, 250)
    return () => {
      cancelled = true
      clearTimeout(timer)
    }
  }, [query])

  async function sendRequest(personId: string) {
    setSending(true)
    try {
      const res = await fetch("/friends", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ addressee_user_id: personId, categories: pickerCategories }),
      })
      if (res.ok) {
        setJustSent((prev) => ({ ...prev, [personId]: true }))
        setPickerFor(null)
        onRequestSent()
      }
    } finally {
      setSending(false)
    }
  }

  return (
    <section aria-label="Søk etter venner" className="flex flex-col gap-4">
      <SectionHeading>Finn venner</SectionHeading>

      <div className="relative">
        <Search
          aria-hidden="true"
          className="pointer-events-none absolute left-4 top-1/2 size-5 -translate-y-1/2 text-muted-foreground"
        />
        <Input
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Søk etter navn …"
          aria-label="Søk etter navn"
          className="h-14 rounded-2xl pl-12 text-lg"
        />
      </div>

      {query.trim().length < 2 ? (
        <p className="rounded-2xl border border-dashed border-border bg-muted/40 px-5 py-6 text-center text-base text-muted-foreground">
          Skriv minst to tegn for å søke etter spillere.
        </p>
      ) : searching ? (
        <p className="text-center text-base text-muted-foreground">Søker …</p>
      ) : results.length === 0 ? (
        <p className="rounded-2xl border border-border bg-muted/50 px-5 py-6 text-center text-base text-muted-foreground">
          Ingen spillere matcher «{query.trim()}».
        </p>
      ) : (
        <ul className="flex flex-col gap-3">
          {results.map((person) => {
            const isFriend = friendUserIds.has(person.id)
            const isSent = outgoingUserIds.has(person.id) || justSent[person.id]
            const pickerOpen = pickerFor === person.id
            return (
              <li
                key={person.id}
                className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8"
              >
                <div className="flex items-center gap-4">
                  <Avatar name={fullName(person.first_name, person.last_name)} />
                  <div className="flex min-w-0 flex-1 flex-col">
                    <span className="truncate text-lg font-bold text-foreground">
                      {fullName(person.first_name, person.last_name)}
                    </span>
                    <span className="truncate text-base text-muted-foreground">
                      {person.home_club ?? "Ingen hjemmeklubb"}
                    </span>
                  </div>
                  {isFriend ? (
                    <span className="inline-flex min-h-[44px] shrink-0 items-center gap-1.5 rounded-xl bg-muted px-3 text-sm font-semibold text-muted-foreground">
                      <Check aria-hidden="true" className="size-4" />
                      Venn
                    </span>
                  ) : isSent ? (
                    <span className="inline-flex min-h-[44px] shrink-0 items-center gap-1.5 rounded-xl bg-brand-orange/15 px-3 text-sm font-semibold text-foreground">
                      <Check aria-hidden="true" className="size-4" />
                      Sendt
                    </span>
                  ) : (
                    <button
                      type="button"
                      onClick={() => (pickerOpen ? setPickerFor(null) : openPicker(person.id))}
                      aria-expanded={pickerOpen}
                      className="inline-flex min-h-[44px] shrink-0 items-center gap-2 rounded-xl bg-primary px-4 text-base font-bold text-primary-foreground shadow-sm transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
                    >
                      <UserPlus aria-hidden="true" className="size-5" />
                      Send forespørsel
                    </button>
                  )}
                </div>

                {pickerOpen && (
                  <div className="flex flex-col gap-3 rounded-xl border border-border bg-muted/30 p-4">
                    <p className="text-sm font-semibold text-foreground">
                      Hvilke kategorier skal {fullName(person.first_name, person.last_name)} plasseres i? Alle er
                      forhåndsvalgt — velg heller bort det som ikke passer.
                    </p>
                    <CategoryPicker selected={pickerCategories} onToggle={toggleCategoryInPicker} />
                    <div className="flex items-center gap-3">
                      <button
                        type="button"
                        onClick={() => sendRequest(person.id)}
                        disabled={sending}
                        className="inline-flex min-h-[44px] flex-1 items-center justify-center gap-2 rounded-xl bg-primary px-4 text-base font-bold text-primary-foreground shadow-sm transition-colors hover:bg-primary/90 disabled:opacity-60 sm:flex-none"
                      >
                        {sending ? "Sender…" : "Bekreft og send"}
                      </button>
                      <button
                        type="button"
                        onClick={() => setPickerFor(null)}
                        className="inline-flex min-h-[44px] items-center justify-center rounded-xl border border-border bg-card px-4 text-base font-bold text-foreground transition-colors hover:bg-accent/50"
                      >
                        Avbryt
                      </button>
                    </div>
                  </div>
                )}
              </li>
            )
          })}
        </ul>
      )}
    </section>
  )
}

// --- 2. Requests -------------------------------------------------------------

function RequestsSection({
  incoming,
  outgoing,
  onChanged,
}: {
  incoming: ApiPendingRequest[]
  outgoing: ApiPendingRequest[]
  onChanged: () => void
}) {
  // Ingen ukategoriserte venner (2026-07-29) -- å godta krever nå at
  // MOTTAKEREN også velger kategorier med det samme (samme regel som
  // avsenderen i SearchSection over), ikke en frivillig senere handling.
  const [acceptPickerFor, setAcceptPickerFor] = useState<string | null>(null)
  const [acceptCategories, setAcceptCategories] = useState<string[]>(ALL_CATEGORY_CODES)
  const [accepting, setAccepting] = useState(false)

  function openAcceptPicker(friendshipId: string) {
    setAcceptPickerFor(friendshipId)
    setAcceptCategories(ALL_CATEGORY_CODES)
  }

  function toggleAcceptCategory(code: string) {
    setAcceptCategories((prev) => {
      if (prev.includes(code)) return prev.length <= 1 ? prev : prev.filter((c) => c !== code)
      return [...prev, code]
    })
  }

  async function confirmAccept(friendshipId: string) {
    setAccepting(true)
    try {
      const res = await fetch(`/friends/${friendshipId}/accept`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({ categories: acceptCategories }),
      })
      if (res.ok) {
        setAcceptPickerFor(null)
        onChanged()
      }
    } finally {
      setAccepting(false)
    }
  }

  async function decline(friendshipId: string) {
    await fetch(`/friends/${friendshipId}`, { method: "DELETE", credentials: "include" })
    onChanged()
  }

  async function cancelOutgoing(friendshipId: string) {
    await fetch(`/friends/${friendshipId}`, { method: "DELETE", credentials: "include" })
    onChanged()
  }

  const hasAny = incoming.length > 0 || outgoing.length > 0
  if (!hasAny) return null

  return (
    <section aria-label="Forespørsler" className="flex flex-col gap-4">
      <SectionHeading>Forespørsler</SectionHeading>

      {incoming.length > 0 && (
        <div className="flex flex-col gap-3">
          <h3 className="text-sm font-bold uppercase tracking-wide text-muted-foreground">Mottatt</h3>
          <ul className="flex flex-col gap-3">
            {incoming.map((person) => {
              const pickerOpen = acceptPickerFor === person.friendship_id
              return (
                <li
                  key={person.friendship_id}
                  className="flex flex-col gap-3 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8"
                >
                  <div className="flex flex-wrap items-center gap-4">
                    <Avatar name={fullName(person.first_name, person.last_name)} />
                    <div className="flex min-w-0 flex-1 basis-40 flex-col">
                      <span className="truncate text-lg font-bold text-foreground">{fullName(person.first_name, person.last_name)}</span>
                    </div>
                    <div className="flex flex-1 basis-full items-center gap-3 sm:basis-auto">
                      <button
                        type="button"
                        onClick={() => (pickerOpen ? setAcceptPickerFor(null) : openAcceptPicker(person.friendship_id))}
                        aria-expanded={pickerOpen}
                        className="inline-flex min-h-[44px] flex-1 items-center justify-center gap-2 rounded-xl bg-primary px-4 text-base font-bold text-primary-foreground shadow-sm transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background sm:flex-none"
                      >
                        <Check aria-hidden="true" className="size-5" />
                        Godta
                      </button>
                      <button
                        type="button"
                        onClick={() => decline(person.friendship_id)}
                        className="inline-flex min-h-[44px] flex-1 items-center justify-center gap-2 rounded-xl border border-border bg-card px-4 text-base font-bold text-foreground transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background sm:flex-none"
                      >
                        <X aria-hidden="true" className="size-5" />
                        Avslå
                      </button>
                    </div>
                  </div>

                  {pickerOpen && (
                    <div className="flex flex-col gap-3 rounded-xl border border-border bg-muted/30 p-4">
                      <p className="text-sm font-semibold text-foreground">
                        Hvilke kategorier skal {fullName(person.first_name, person.last_name)} plasseres i? Alle er
                        forhåndsvalgt — velg heller bort det som ikke passer.
                      </p>
                      <CategoryPicker selected={acceptCategories} onToggle={toggleAcceptCategory} />
                      <div className="flex items-center gap-3">
                        <button
                          type="button"
                          onClick={() => confirmAccept(person.friendship_id)}
                          disabled={accepting}
                          className="inline-flex min-h-[44px] flex-1 items-center justify-center gap-2 rounded-xl bg-primary px-4 text-base font-bold text-primary-foreground shadow-sm transition-colors hover:bg-primary/90 disabled:opacity-60 sm:flex-none"
                        >
                          {accepting ? "Godtar…" : "Bekreft og godta"}
                        </button>
                        <button
                          type="button"
                          onClick={() => setAcceptPickerFor(null)}
                          className="inline-flex min-h-[44px] items-center justify-center rounded-xl border border-border bg-card px-4 text-base font-bold text-foreground transition-colors hover:bg-accent/50"
                        >
                          Avbryt
                        </button>
                      </div>
                    </div>
                  )}
                </li>
              )
            })}
          </ul>
        </div>
      )}

      {outgoing.length > 0 && (
        <div className="flex flex-col gap-3">
          <h3 className="text-sm font-bold uppercase tracking-wide text-muted-foreground">Sendt</h3>
          <ul className="flex flex-col gap-3">
            {outgoing.map((person) => (
              <li
                key={person.friendship_id}
                className="flex flex-wrap items-center gap-4 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8"
              >
                <Avatar name={fullName(person.first_name, person.last_name)} />
                <div className="flex min-w-0 flex-1 basis-40 flex-col">
                  <span className="truncate text-lg font-bold text-foreground">{fullName(person.first_name, person.last_name)}</span>
                  <span className="truncate text-base text-muted-foreground">Venter på svar</span>
                </div>
                <button
                  type="button"
                  onClick={() => cancelOutgoing(person.friendship_id)}
                  className="inline-flex min-h-[44px] shrink-0 items-center justify-center gap-2 rounded-xl border border-border bg-card px-4 text-base font-bold text-foreground transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
                >
                  <X aria-hidden="true" className="size-5" />
                  Kanseller
                </button>
              </li>
            ))}
          </ul>
        </div>
      )}
    </section>
  )
}

// --- 3. Friends ----------------------------------------------------------------

function FriendsSection({ friends, onChanged }: { friends: ApiFriendEntry[]; onChanged: () => void }) {
  // Ingen ukategoriserte venner (2026-07-29) -- selv-helbredende sikkerhets-
  // nett for venner fra FØR den regelen ble håndhevet ved selve vennskaps-
  // opprettelsen (send/godta). Skal i praksis aldri ramme en NY relasjon,
  // kun eldre data. Vises som en umulig-å-overse advarsel øverst, og
  // tvinger vedkommendes kategori-panel åpent (ikke bare en lenke dit).
  const uncategorized = friends.filter((f) => f.categories.length === 0)

  return (
    <section aria-label="Venner" className="flex flex-col gap-4">
      <SectionHeading>Venner</SectionHeading>

      {uncategorized.length > 0 && (
        <div
          role="alert"
          className="flex flex-col gap-2 rounded-2xl border-2 border-brand-orange bg-brand-orange/10 p-4"
        >
          <p className="text-base font-bold text-foreground">
            {uncategorized.length} {uncategorized.length === 1 ? "venn mangler" : "venner mangler"} kategori
          </p>
          <p className="text-sm leading-relaxed text-muted-foreground">
            Rundene dine kan ikke vises riktig for {uncategorized.length === 1 ? "denne vennen" : "disse vennene"} før
            de er kategorisert. Kategoriseringen er allerede åpnet under — velg minst én kategori for hver.
          </p>
        </div>
      )}

      {friends.length === 0 ? (
        <div className="flex flex-col items-center gap-4 rounded-2xl border border-dashed border-border bg-muted/40 px-6 py-12 text-center">
          <span className="flex size-14 items-center justify-center rounded-2xl bg-muted">
            <Users aria-hidden="true" className="size-7 text-muted-foreground" />
          </span>
          <div className="flex flex-col gap-1.5">
            <p className="text-lg font-bold text-foreground">Ingen venner ennå</p>
            <p className="mx-auto max-w-sm text-base leading-relaxed text-muted-foreground text-pretty">
              Bruk søkefeltet øverst til å finne folk du spiller med, og send dem en forespørsel.
            </p>
          </div>
        </div>
      ) : (
        <ul className="flex flex-col gap-3">
          {friends.map((friend) => (
            <li key={friend.friendship_id}>
              <FriendRow friend={friend} onChanged={onChanged} />
            </li>
          ))}
        </ul>
      )}
    </section>
  )
}

function FriendRow({ friend, onChanged }: { friend: ApiFriendEntry; onChanged: () => void }) {
  // Tvinges åpent for en ukategorisert venn (se banneret i FriendsSection)
  // -- ingen vei til å la den stå lukket og utsette valget.
  const [expanded, setExpanded] = useState(friend.categories.length === 0)
  const [confirming, setConfirming] = useState(false)
  // Lokal, optimistisk kopi -- lagres til backend ved hver endring (PUT
  // erstatter HELE settet, matcher backend-kontrakten), men holdes lokalt
  // for umiddelbar UI-respons uten en full refetch per klikk.
  const [categories, setCategories] = useState<string[]>(friend.categories)

  async function toggleCategory(code: string) {
    // Ingen ukategoriserte venner (2026-07-29) -- nekter å fjerne den
    // SISTE kategorien (backend krever uansett minst én, men UI-et skal
    // aldri kunne komme dit i utgangspunktet).
    if (categories.includes(code) && categories.length <= 1) return
    const next = categories.includes(code) ? categories.filter((c) => c !== code) : [...categories, code]
    setCategories(next)
    await fetch(`/friends/${friend.user_id}/categories`, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ categories: next }),
    })
  }

  async function removeFriend() {
    await fetch(`/friends/${friend.friendship_id}`, { method: "DELETE", credentials: "include" })
    onChanged()
  }

  const name = fullName(friend.first_name, friend.last_name)

  return (
    <div className="flex flex-col gap-4 rounded-2xl border border-border bg-card p-4 shadow-md shadow-black/8">
      <Link
        href={`/my-friends/${friend.user_id}`}
        className="flex items-center gap-4 rounded-xl transition-colors hover:bg-accent/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
      >
        <Avatar name={name} />
        <div className="flex min-w-0 flex-1 flex-col">
          <span className="truncate text-lg font-bold text-foreground">{name}</span>
          <span className="truncate text-base text-muted-foreground">{friend.home_club ?? "Ingen hjemmeklubb"}</span>
        </div>
      </Link>

      {categories.length > 0 && (
        <ul className="flex flex-wrap gap-2" aria-label={`Kategorier for ${name}`}>
          {categories.map((code) => (
            <li key={code} className="inline-flex items-center gap-1.5 rounded-full bg-muted px-3 py-1 text-sm font-semibold text-foreground">
              <Tag aria-hidden="true" className="size-3.5 text-muted-foreground" />
              {categoryLabel(code)}
            </li>
          ))}
        </ul>
      )}

      <div className="overflow-hidden rounded-xl border border-border">
        <button
          type="button"
          onClick={() => setExpanded((v) => !v)}
          aria-expanded={expanded}
          className="flex min-h-[44px] w-full items-center justify-between gap-2 px-4 py-3 text-left text-base font-bold text-foreground transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
        >
          <span className="flex items-center gap-2">
            <Tag aria-hidden="true" className="size-5 text-primary" />
            Kategorier
            {categories.length > 0 && (
              <span className="rounded-full bg-primary px-2 py-0.5 text-xs font-bold text-primary-foreground tabular-nums">
                {categories.length}
              </span>
            )}
          </span>
          <ChevronDown aria-hidden="true" className={cn("size-5 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-180")} />
        </button>

        {expanded && (
          <div className="flex flex-col gap-3 border-t border-border p-4">
            <CategoryPicker selected={categories} onToggle={toggleCategory} />
            <p className="text-sm leading-relaxed text-muted-foreground">
              Kun du ser hvilke kategorier du har satt en venn i. Minst én kategori er påkrevd.
            </p>
          </div>
        )}
      </div>

      {confirming ? (
        <div className="flex flex-col gap-3 rounded-xl border border-destructive/40 bg-destructive/5 p-4">
          <p className="text-base font-semibold text-foreground">Fjerne {name} som venn?</p>
          <div className="flex items-center gap-3">
            <button
              type="button"
              onClick={removeFriend}
              className="inline-flex min-h-[44px] flex-1 items-center justify-center gap-2 rounded-xl bg-destructive px-4 text-base font-bold text-destructive-foreground shadow-sm transition-colors hover:bg-destructive/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
            >
              <Trash2 aria-hidden="true" className="size-5" />
              Ja, fjern venn
            </button>
            <button
              type="button"
              onClick={() => setConfirming(false)}
              className="inline-flex min-h-[44px] flex-1 items-center justify-center rounded-xl border border-border bg-card px-4 text-base font-bold text-foreground transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
            >
              Avbryt
            </button>
          </div>
        </div>
      ) : (
        <button
          type="button"
          onClick={() => setConfirming(true)}
          className="inline-flex min-h-[44px] items-center justify-center gap-2 self-start rounded-xl px-3 text-base font-semibold text-destructive transition-colors hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
        >
          <Trash2 aria-hidden="true" className="size-5" />
          Fjern venn
        </button>
      )}
    </div>
  )
}

// --- Avatar ------------------------------------------------------------------

function initials(name: string) {
  const parts = name.trim().split(/\s+/)
  const first = parts[0]?.[0] ?? ""
  const last = parts.length > 1 ? parts[parts.length - 1][0] : ""
  return (first + last).toUpperCase()
}

function Avatar({ name }: { name: string }) {
  return (
    <span
      aria-hidden="true"
      className="flex size-12 shrink-0 items-center justify-center rounded-full bg-muted text-base font-bold text-foreground"
    >
      {initials(name)}
    </span>
  )
}
