"use client"

import { useEffect, useMemo, useState } from "react"
import { ChevronDown, Plus, Ruler, Trash2 } from "lucide-react"
import { ShotMeasurementSheet } from "@/components/shot/shot-measurement-sheet"
import { bearingDegrees } from "@/lib/geo"
import { midpoint } from "@/lib/hole-geometry"
import { cn } from "@/lib/utils"

/**
 * Slag-for-slag GPS-avstandsmåling (ADR-048), UI-laget som binder
 * `ShotMeasurementSheet`/`MapPointPicker` (begge helt generiske, ingen
 * endring) mot et faktisk backend-endepunkt. Generalisert 2026-08-22
 * (ADR-103) fra en lokal funksjon i round-detail.tsx til denne delte
 * filen -- kalleren bygger nå URL-ene selv i stedet for at komponenten
 * antar `/rounds/{roundId}/...`-formen, slik at BÅDE frittstående
 * runder OG org-turneringer (helt ulike REST-stier) kan gjenbruke den
 * uendret.
 */

export type ShotRecord = {
  id: string
  club: string
  distance_meters: number
  shared: boolean
  start_lat: number
  start_lng: number
  end_lat: number
  end_lng: number
}

/** De to backendene navngir delings-lenken ulikt (`shared_round_message_id`
 *  vs. `shared_tournament_round_message_id`, ulike måltabeller) --
 *  normaliseres her til ett felles `shared`-flagg, resten av feltene er
 *  identiske på tvers av begge API-kontraktene. */
function normalizeShot(raw: Record<string, unknown>): ShotRecord {
  return {
    id: raw.id as string,
    club: raw.club as string,
    distance_meters: raw.distance_meters as number,
    shared: Boolean(raw.shared_round_message_id ?? raw.shared_tournament_round_message_id),
    start_lat: raw.start_lat as number,
    start_lng: raw.start_lng as number,
    end_lat: raw.end_lat as number,
    end_lng: raw.end_lng as number,
  }
}

export type ShotMeasurementEntryProps = {
  /** GET (liste) + POST (opprett) -- fullt sti, f.eks.
   *  `/rounds/{roundId}/participants/{id}/holes/{n}/shots`. */
  shotsUrl: string
  /** Rundescopet base for DELETE `${x}/${id}` og POST `${x}/${id}/share`
   *  -- f.eks. `/rounds/{roundId}/shots`. */
  shotsRoundBaseUrl: string
  /** Banegeometri (utslag->green) for kartretning -- utelates helt for
   *  en bane uten kjent geometri, faller da tilbake til forrige-slag-
   *  retning (uendret atferd). */
  targetPointsUrl?: string
  holeNumber: number
  ownBagClubs: string[]
  readOnly: boolean
  variant: "inline" | "badge"
  label?: string
}

export function ShotMeasurementEntry({
  shotsUrl,
  shotsRoundBaseUrl,
  targetPointsUrl,
  holeNumber,
  ownBagClubs,
  readOnly,
  variant,
  label,
}: ShotMeasurementEntryProps) {
  const [open, setOpen] = useState(false)
  // Brukertilbakemelding 2026-08-08: "jeg burde jo se slaget selv, selv om
  // det ikke er delt" -- merkelappen viste tidligere KUN et antall, ingen
  // måte å se kølle/avstand for egne, private slag. Lagrer nå hele listen
  // (ikke bare .length), viser den i en utvidbar liste, med sletting av et
  // feilmålt slag.
  const [shots, setShots] = useState<ShotRecord[]>([])
  const [listOpen, setListOpen] = useState(false)
  const [submitting, setSubmitting] = useState(false)
  const [submitError, setSubmitError] = useState<string | null>(null)
  const [shareError, setShareError] = useState<string | null>(null)
  useEffect(() => {
    let cancelled = false
    fetch(shotsUrl, { credentials: "include" })
      .then((res) => (res.ok ? res.json() : []))
      .then((data: Record<string, unknown>[]) => {
        if (!cancelled) setShots(data.map(normalizeShot))
      })
      .catch(() => {})
    return () => {
      cancelled = true
    }
  }, [shotsUrl])
  const count = shots.length

  // Ekte banegeometri (2026-08-21, brukerønske) -- når hullet har BÅDE
  // utslags- OG green-koordinater kjent (samme datakilde som
  // HoleTargetDistance/HoleDiagram bruker), skal kartretningen ALLTID
  // følge utslag->green, ikke gjettes fra spillerens forrige slag (som
  // kan avvike, f.eks. en slice/hook).
  const [courseBearing, setCourseBearing] = useState<number | undefined>(undefined)
  useEffect(() => {
    let cancelled = false
    setCourseBearing(undefined)
    if (!targetPointsUrl) return
    fetch(targetPointsUrl, { credentials: "include" })
      .then((res) => (res.ok ? res.json() : []))
      .then((points: { poi_type: string; location: string | null; latitude: number; longitude: number }[]) => {
        if (cancelled) return
        const middle = points.find((p) => p.poi_type === "green" && p.location === "middle")
        const teeFront = points.find((p) => p.poi_type === "tee_front")
        const teeBack = points.find((p) => p.poi_type === "tee_back")
        const teeRef =
          teeFront && teeBack
            ? midpoint({ lat: teeFront.latitude, lng: teeFront.longitude }, { lat: teeBack.latitude, lng: teeBack.longitude })
            : teeFront
              ? { lat: teeFront.latitude, lng: teeFront.longitude }
              : teeBack
                ? { lat: teeBack.latitude, lng: teeBack.longitude }
                : null
        setCourseBearing(
          teeRef && middle ? bearingDegrees(teeRef, { lat: middle.latitude, lng: middle.longitude }) : undefined,
        )
      })
      .catch(() => {
        if (!cancelled) setCourseBearing(undefined)
      })
    return () => {
      cancelled = true
    }
  }, [targetPointsUrl])

  // Kartretning ved slagmåling (brukerønske 2026-08-10, utvidet
  // 2026-08-21): "opp på skjermen" følger banens EGEN spillretning
  // (utslag->green, courseBearing over) når den er kjent -- ellers
  // faller tilbake til spillerens EGEN gangretning, regnet ut fra det
  // SISTE tidligere målte slaget på dette hullet (start→slutt-retningen).
  // `undefined` (ingen rotasjon regnet ut ennå) for det aller første
  // slaget på et hull uten kjent banegeometri -- MapPointPicker faller
  // da selv tilbake til enhetens GPS-heading/nord.
  const initialBearing = useMemo(() => {
    if (typeof courseBearing === "number") return courseBearing
    const previous = shots[shots.length - 1]
    if (!previous) return undefined
    return bearingDegrees(
      { lat: previous.start_lat, lng: previous.start_lng },
      { lat: previous.end_lat, lng: previous.end_lng },
    )
  }, [shots, courseBearing])

  async function deleteShot(shotId: string) {
    const res = await fetch(`${shotsRoundBaseUrl}/${shotId}`, { method: "DELETE", credentials: "include" })
    if (res.ok) {
      setShots((prev) => prev.filter((s) => s.id !== shotId))
    }
  }

  // Reell bug funnet 2026-08-08: en avvist innsending (f.eks. serverens
  // distance_meters > 0-sjekk, hvis brukeren rakk å målt begge punkter for
  // fort) lukket tidligere arket STILLE -- ingen feilmelding, brukeren så
  // aldri hvorfor ingenting ble lagret/delt. Viser nå en reell feil og
  // holder arket åpent i stedet for å anta suksess.
  async function submitShot(result: {
    club: string
    distanceMeters: number
    startMethod: "gps" | "map_tap"
    startLat: number
    startLng: number
    endMethod: "gps" | "map_tap"
    endLat: number
    endLng: number
    share: boolean
    shareText: string
  }) {
    setSubmitError(null)
    setSubmitting(true)
    try {
      const res = await fetch(shotsUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({
          club: result.club,
          distance_meters: result.distanceMeters,
          start_method: result.startMethod,
          start_lat: result.startLat,
          start_lng: result.startLng,
          end_method: result.endMethod,
          end_lat: result.endLat,
          end_lng: result.endLng,
        }),
      })
      if (!res.ok) {
        const body = await res.json().catch(() => null)
        const detail = body?.detail
        const message =
          typeof detail === "string"
            ? detail
            : typeof detail?.message === "string"
              ? detail.message
              : "Kunne ikke lagre slaget. Sjekk at avstanden er gyldig (over 0 meter) og prøv igjen."
        setSubmitError(message)
        return
      }
      let shot = normalizeShot(await res.json())
      setShareError(null)
      if (result.share) {
        const shareRes = await fetch(`${shotsRoundBaseUrl}/${shot.id}/share`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          credentials: "include",
          body: JSON.stringify({ body: result.shareText }),
        })
        // Reell bug funnet 2026-08-08: dette svaret ble aldri sjekket -- en
        // avvist deling (f.eks. en driftsfeil) lot slaget lagres som om alt
        // gikk bra, ingen feilmelding, ingen melding i feeden. Slaget er
        // uansett lagret på dette tidspunktet -- arket lukkes derfor alltid
        // her (unngår en duplikat-innsending ved gjentatt "Lagre slag"), og
        // en mislykket deling vises separat ved siden av merkelappen i
        // stedet for å holde arket åpent.
        if (shareRes.ok) {
          shot = normalizeShot(await shareRes.json())
        } else {
          setShareError("Slaget ble lagret, men delingen feilet. Prøv å dele på nytt fra listen.")
        }
      }
      setShots((prev) => [...prev, shot])
      setOpen(false)
    } catch {
      setSubmitError("Klarte ikke å nå serveren. Sjekk tilkoblingen og prøv igjen.")
    } finally {
      setSubmitting(false)
    }
  }

  async function shareExisting(shot: ShotRecord) {
    const res = await fetch(`${shotsRoundBaseUrl}/${shot.id}/share`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({ body: `⛳ ${shot.club}, ${shot.distance_meters} m – hull ${holeNumber}` }),
    })
    if (res.ok) {
      const updated = normalizeShot(await res.json())
      setShots((prev) => prev.map((s) => (s.id === shot.id ? updated : s)))
      setShareError(null)
    } else {
      setShareError("Delingen feilet. Prøv igjen om litt.")
    }
  }

  if (readOnly) {
    // Skrivebeskyttet visning (fullført/eldre runde): fortsatt nyttig å SE
    // egne målte slag, bare uten å kunne måle/dele/slette nye.
    if (count === 0) return null
    return (
      <div className={cn("flex flex-col gap-1.5", variant === "inline" ? "" : "items-end")}>
        <button
          type="button"
          onClick={(e) => {
            e.stopPropagation()
            setListOpen((o) => !o)
          }}
          aria-expanded={listOpen}
          className="inline-flex min-h-11 items-center gap-1.5 rounded-full border border-dashed border-border bg-background px-3 text-xs font-semibold text-muted-foreground transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
        >
          <Ruler className="size-3.5 shrink-0" aria-hidden="true" />
          {label ? `${label}: ` : ""}
          {count} slag målt
          <ChevronDown className={cn("size-3.5 shrink-0 transition-transform", listOpen && "rotate-180")} aria-hidden="true" />
        </button>
        {listOpen && <ShotList shots={shots} onDelete={undefined} onShare={undefined} />}
      </div>
    )
  }

  const addLabel = label ? `${label}: Mål et slag` : "Mål et slag"

  return (
    <div className={cn("flex flex-col gap-1.5", variant === "inline" ? "" : "items-end")}>
      <div className="flex items-center gap-1.5">
        {count > 0 && (
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation()
              setListOpen((o) => !o)
            }}
            aria-expanded={listOpen}
            className={cn(
              "inline-flex items-center gap-1.5 rounded-full border border-dashed transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
              variant === "inline"
                ? "min-h-11 justify-center border-border bg-card px-4 text-base font-semibold text-foreground hover:bg-accent/50"
                : "min-h-11 border-border bg-background px-3 text-xs font-semibold text-muted-foreground hover:bg-accent/50",
            )}
          >
            <Ruler className={variant === "inline" ? "size-5 shrink-0" : "size-3.5 shrink-0"} aria-hidden="true" />
            {label ? `${label}: ` : ""}
            {count} slag målt
            <ChevronDown className={cn("size-3.5 shrink-0 transition-transform", listOpen && "rotate-180")} aria-hidden="true" />
          </button>
        )}
        <button
          type="button"
          onClick={(e) => {
            e.stopPropagation()
            setSubmitError(null)
            setOpen(true)
          }}
          aria-label={addLabel}
          className={cn(
            "inline-flex items-center justify-center gap-1.5 rounded-full border border-dashed transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
            count > 0
              ? "size-11 shrink-0 border-border bg-card text-foreground hover:bg-accent/50"
              : variant === "inline"
                ? "min-h-11 justify-center border-border bg-card px-4 text-base font-semibold text-foreground hover:bg-accent/50"
                : "min-h-11 border-border bg-background px-3 text-xs font-semibold text-muted-foreground hover:bg-accent/50",
          )}
        >
          <Plus className={cn(count > 0 ? "size-5" : variant === "inline" ? "size-5 shrink-0" : "size-3.5 shrink-0")} aria-hidden="true" />
          {count === 0 && addLabel}
        </button>
      </div>

      {shareError && (
        <p role="alert" className="text-xs font-medium text-destructive">
          {shareError}
        </p>
      )}

      {listOpen && count > 0 && <ShotList shots={shots} onDelete={deleteShot} onShare={shareExisting} />}

      {open && (
        <ShotMeasurementSheet
          holeNumber={holeNumber}
          existingShotCount={count}
          ownBagClubs={ownBagClubs}
          initialBearing={initialBearing}
          submitError={submitError}
          submitting={submitting}
          onSubmit={submitShot}
          onCancel={() => setOpen(false)}
        />
      )}
    </div>
  )
}

// Satellittutsnitt for et allerede målt slag (brukerønske 2026-08-08: "jeg
// burde kunne se på et satellittfoto hvor jeg har slått hvert slag" -- listen
// viste tidligere kun tekst, ingen måte å se selve plasseringen). Samme
// mønster som forhåndsvisningen i shot-measurement-sheet.tsx sitt
// resultat-steg: klient-side, OFFENTLIG (URL-restriktert) Mapbox-token,
// ingen server-tur-retur -- koordinatene er allerede en del av ShotRecord.
function shotThumbnailUrl(shot: ShotRecord): string | null {
  if (!process.env.NEXT_PUBLIC_MAPBOX_TOKEN) return null
  return `https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/static/pin-s-a+ff5a1f(${shot.start_lng},${shot.start_lat}),pin-s-b+2f7a3f(${shot.end_lng},${shot.end_lat})/auto/160x160@2x?padding=30&access_token=${process.env.NEXT_PUBLIC_MAPBOX_TOKEN}`
}

function ShotList({
  shots,
  onDelete,
  onShare,
}: {
  shots: ShotRecord[]
  onDelete?: (shotId: string) => void
  onShare?: (shot: ShotRecord) => void
}) {
  return (
    <ul className="flex w-full min-w-[16rem] flex-col gap-1.5 rounded-xl border border-border bg-card p-1.5">
      {shots.map((shot) => {
        const thumbUrl = shotThumbnailUrl(shot)
        return (
          <li key={shot.id} className="flex items-center gap-3 rounded-lg p-1.5 text-sm">
            {thumbUrl ? (
              // eslint-disable-next-line @next/next/no-img-element -- ekstern Mapbox-URL, ikke next/image-verdt
              <img
                src={thumbUrl || "/placeholder.svg"}
                alt={`Satellittutsnitt av ${shot.club}-slaget, ${shot.distance_meters} meter, med start- og sluttpunkt markert`}
                className="size-14 shrink-0 rounded-lg border border-border object-cover"
              />
            ) : null}
            <span className="flex-1 truncate font-medium text-foreground">
              {shot.club} <span className="tabular-nums text-muted-foreground">— {shot.distance_meters} m</span>
            </span>
            {shot.shared ? (
              <span className="text-[11px] font-semibold text-primary">Delt</span>
            ) : onShare ? (
              <button
                type="button"
                onClick={(e) => {
                  e.stopPropagation()
                  onShare(shot)
                }}
                className="text-[11px] font-semibold text-primary underline-offset-2 hover:underline"
              >
                Del
              </button>
            ) : null}
            {onDelete && (
              <button
                type="button"
                onClick={(e) => {
                  e.stopPropagation()
                  onDelete(shot.id)
                }}
                aria-label={`Slett ${shot.club}, ${shot.distance_meters} meter`}
                className="flex size-8 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent/50 hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              >
                <Trash2 className="size-4" aria-hidden="true" />
              </button>
            )}
          </li>
        )
      })}
    </ul>
  )
}
