"use client"

import { useEffect, useRef, useState } from "react"
import mapboxgl from "mapbox-gl"
import "mapbox-gl/dist/mapbox-gl.css"
import { Crosshair, MapPin } from "lucide-react"
import { haversineMeters } from "@/lib/geo"

type LngLat = { lng: number; lat: number }

/**
 * MapPointPicker
 *
 * To ulike modi, styrt av `referencePoint`:
 * - Start-steget (referencePoint IKKE satt): rendret kun når brukeren
 *   velger "Velg punkt på kart". Trykk-for-å-plassere en markør, ett
 *   engangs-GPS-oppslag kun for sentrering.
 * - Ball-steget (referencePoint satt, ADR-048/049-tillegg): rendres
 *   ALLTID. Rent informativt kart (referansepunkt + løpende sporet
 *   posisjon + sanntidsavstand) -- IKKE trykkbart for å plassere ballen.
 *   Ballens posisjon bekreftes alltid med sporet GPS-posisjon
 *   ("Jeg er ved ballen nå"), aldri ved kart-trykk (brukerønske
 *   2026-08-09: "bekreft ballens posisjon er unødvendig" -- fjernet
 *   trykk-alternativet som fantes en kort periode før dette).
 *
 * Alltid via next/dynamic(..., { ssr: false }) fra foreldre-komponenten.
 * Mountes én gang per steg og forblir mountet til det steget forlates, så
 * selve Mapbox-KARTET initialiseres nøyaktig én gang (empty-deps
 * useEffect) — pans/zoom re-initialiserer det aldri. Dette holder
 * Mapbox-kartlastninger (som faktureres) til én per steg-åpning.
 */
export function MapPointPicker({
  onConfirm,
  referencePoint,
  referenceLabel = "Utslag",
  initialBearing,
}: {
  /** method er "map_tap" ved trykk-bekreftelse (kun start-steget), "gps"
   *  ved "Jeg er ved ballen nå"-bekreftelse (kun ball-steget). */
  onConfirm: (lngLat: LngLat, method: "gps" | "map_tap") => void
  /**
   * Satt KUN på ballposisjon-steget: et fast, ikke-flyttbart
   * referansepunkt (utslagspunktet) vises på kartet, og kartet forsøker å
   * vise BÅDE dette og brukerens nåværende posisjon i samme bilde --
   * brukerønske: "slagpunktet må være en del av det jeg ser". Trigger
   * samtidig løpende posisjonssporing (se lenger ned) -- ALDRI satt på
   * start-steget, som fortsatt kun gjør ett engangs-oppslag.
   */
  referencePoint?: LngLat
  referenceLabel?: string
  /**
   * Kompassgrader (0-360) kartet roteres til ved oppstart, så "opp på
   * skjermen" følger spillerens egen gangretning på hullet i stedet for
   * alltid å peke nord -- brukerønske 2026-08-10, løst UTEN lagrede
   * green-koordinater (se bearingForShotMap() i shot-measurement-sheet.tsx,
   * som regner denne ut fra spillerens EGET forrige slag på hullet).
   * Satt ÉN gang ved kart-initialisering, aldri oppdatert løpende -- en
   * kontinuerlig rotasjon etter live GPS-heading ville vært urolig/svimlende
   * i lav fart, så dette er bevisst en statisk, ikke en sanntids-rotasjon.
   * Udefinert/`undefined` betyr "ingen kjent retning ennå" -- kartet
   * forsøker da ett engangs-fall-tilbake til enhetens `coords.heading` fra
   * det første posisjons-oppslaget (se initMap under), og lander til slutt
   * på nord (0) hvis heller ikke det finnes.
   */
  initialBearing?: number
}) {
  const containerRef = useRef<HTMLDivElement | null>(null)
  const mapRef = useRef<mapboxgl.Map | null>(null)
  const markerRef = useRef<mapboxgl.Marker | null>(null)
  const referenceMarkerRef = useRef<mapboxgl.Marker | null>(null)
  const liveMarkerRef = useRef<mapboxgl.Marker | null>(null)
  const watchIdRef = useRef<number | null>(null)

  const [loaded, setLoaded] = useState(false)
  const [point, setPoint] = useState<LngLat | null>(null)
  const [error, setError] = useState<string | null>(null)
  // Løpende posisjon + avstand-fra-referansepunkt-så-langt (ADR-048-tillegg
  // 2026-08-08, brukerønske: "jeg ønsker å se lengden så langt i sanntid
  // mens jeg nærmer meg ballen"). Kun i bruk når referencePoint er satt.
  const [livePoint, setLivePoint] = useState<LngLat | null>(null)
  const [liveDistance, setLiveDistance] = useState<number | null>(null)

  useEffect(() => {
    const token = process.env.NEXT_PUBLIC_MAPBOX_TOKEN
    const container = containerRef.current
    if (!container) return

    if (!token) {
      setError("Kartet er ikke tilgjengelig akkurat nå.")
      return
    }

    let cancelled = false

    // Reell bug funnet 2026-08-08: kartet sentrerte ALLTID på en hardkodet
    // Oslo-koordinat -- en kommentar her hevdet "real app centers on
    // last-known position", men den logikken fantes aldri, kun påstanden.
    // Henter nå brukerens faktiske GPS-posisjon FØR kartet initialiseres
    // (ett engangs-oppslag for selve sentreringen), med Oslo som fallback
    // KUN hvis posisjon ikke kan hentes (avslått tillatelse, tidsavbrudd,
    // ingen støtte).
    // fitTo: satt når vi HAR en brukerposisjon OG et referansepunkt --
    // kartet zoomer/panorerer da til å vise begge (padding rundt), i stedet
    // for bare å sentrere på ett av dem, slik at referansepunktet garantert
    // er synlig med det samme.
    function initMap(center: [number, number], fitTo?: [[number, number], [number, number]], bearing = 0) {
      if (cancelled || !container) return
      mapboxgl.accessToken = token as string
      const map = new mapboxgl.Map({
        container,
        style: "mapbox://styles/mapbox/satellite-v9",
        center,
        zoom: referencePoint ? 17 : 16,
        bearing,
        attributionControl: false,
      })
      mapRef.current = map

      map.on("load", () => {
        setLoaded(true)
        if (fitTo) {
          const bounds = new mapboxgl.LngLatBounds(fitTo[0], fitTo[0])
          bounds.extend(fitTo[1])
          map.fitBounds(bounds, { padding: 80, maxZoom: 18, duration: 0 })
        }
      })
      map.on("error", () => setError("Kunne ikke laste kartet."))

      // Fast referansemarkør (utslagspunktet) -- kan ikke flyttes, kun til
      // orientering. Egen farge (samme "pin-a"-oransje som brukes i
      // delings-bildet) for å skille tydelig fra punktet som plasseres.
      if (referencePoint) {
        referenceMarkerRef.current = new mapboxgl.Marker({ color: "#ff5a1f" })
          .setLngLat([referencePoint.lng, referencePoint.lat])
          .addTo(map)
      }

      // Tap to place/move a single draggable marker — never re-inits the map.
      // KUN på start-steget (ingen referencePoint): brukerønske 2026-08-09,
      // "bekreft ballens posisjon er unødvendig" -- ballens posisjon
      // bekreftes nå ALLTID med sporet GPS-posisjon ("Jeg er ved ballen
      // nå"), aldri ved å trykke på kartet. Kartet på ball-steget er dermed
      // rent informativt (referansepunkt + sanntidsposisjon/-avstand), ikke
      // interaktivt for plassering.
      if (!referencePoint) {
        map.on("click", (e) => {
          const lngLat = { lng: e.lngLat.lng, lat: e.lngLat.lat }
          if (!markerRef.current) {
            const marker = new mapboxgl.Marker({ draggable: true, color: "#d2551a" })
              .setLngLat(e.lngLat)
              .addTo(map)
            marker.on("dragend", () => {
              const p = marker.getLngLat()
              setPoint({ lng: p.lng, lat: p.lat })
            })
            markerRef.current = marker
          } else {
            markerRef.current.setLngLat(e.lngLat)
          }
          setPoint(lngLat)
        })
      }
    }

    const OSLO_FALLBACK: [number, number] = [10.7522, 59.9139]
    const fallbackCenter: [number, number] = referencePoint
      ? [referencePoint.lng, referencePoint.lat]
      : OSLO_FALLBACK
    // Retning kartet roteres til: kjent spillretning (initialBearing, regnet
    // ut av kalleren fra spillerens eget forrige slag på hullet) vinner
    // alltid. Uten den: ett engangs-forsøk på enhetens `coords.heading` fra
    // AKKURAT dette posisjonsoppslaget (kun populert når enheten beveger
    // seg -- ofte null/NaN for et enkeltstående oppslag, og det er en
    // akseptert, bevisst fallback til nord (0) i så fall, ikke en feil).
    function resolveBearing(headingFromFix: number | null | undefined): number {
      if (typeof initialBearing === "number" && Number.isFinite(initialBearing)) return initialBearing
      if (typeof headingFromFix === "number" && Number.isFinite(headingFromFix)) return headingFromFix
      return 0
    }

    if (typeof navigator !== "undefined" && navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        (pos) => {
          const here: [number, number] = [pos.coords.longitude, pos.coords.latitude]
          const bearing = resolveBearing(pos.coords.heading)
          if (referencePoint) {
            initMap(here, [here, [referencePoint.lng, referencePoint.lat]], bearing)
          } else {
            initMap(here, undefined, bearing)
          }
        },
        () => initMap(fallbackCenter, undefined, resolveBearing(null)),
        { enableHighAccuracy: true, timeout: 8000 },
      )
    } else {
      initMap(fallbackCenter, undefined, resolveBearing(null))
    }

    // Løpende posisjonssporing -- KUN på ballposisjon-steget (referencePoint
    // satt). Egen, bevisst unntak fra ADR-048s opprinnelige "ingen løpende
    // watchPosition"-prinsipp (som gjaldt kostnadskontroll for KART-
    // lastninger): watchPosition i seg selv koster ingenting hos Mapbox --
    // det er ren nettleser-GPS, ikke et kartlastnings-kall -- og brukeren
    // ba eksplisitt om sanntids-avstand mens de går mot ballen. Kartet
    // forblir uansett kun ÉN instans (samme map-objekt gjenbrukes, aldri
    // re-initialisert), kun MARKØRPOSISJONEN oppdateres løpende.
    if (referencePoint && typeof navigator !== "undefined" && navigator.geolocation) {
      watchIdRef.current = navigator.geolocation.watchPosition(
        (pos) => {
          if (cancelled) return
          const p = { lng: pos.coords.longitude, lat: pos.coords.latitude }
          setLivePoint(p)
          setLiveDistance(haversineMeters(referencePoint, p))
          if (mapRef.current) {
            if (!liveMarkerRef.current) {
              const el = document.createElement("div")
              el.className = "size-4 rounded-full border-2 border-white bg-[#2b7fff] shadow-md"
              liveMarkerRef.current = new mapboxgl.Marker({ element: el }).setLngLat([p.lng, p.lat]).addTo(mapRef.current)
            } else {
              liveMarkerRef.current.setLngLat([p.lng, p.lat])
            }
          }
        },
        () => {
          // Feil/avslått tillatelse underveis -- degraderer grasiøst, bruker
          // kan fortsatt trykke selv ut ballens posisjon på kartet.
        },
        { enableHighAccuracy: true, maximumAge: 1000 },
      )
    }

    return () => {
      cancelled = true
      if (watchIdRef.current !== null && typeof navigator !== "undefined" && navigator.geolocation) {
        navigator.geolocation.clearWatch(watchIdRef.current)
      }
      liveMarkerRef.current?.remove()
      liveMarkerRef.current = null
      referenceMarkerRef.current?.remove()
      referenceMarkerRef.current = null
      markerRef.current?.remove()
      markerRef.current = null
      mapRef.current?.remove()
      mapRef.current = null
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- montert én gang per steg, se komponent-docstring
  }, [])

  return (
    <div className="flex h-full flex-col">
      <div className="relative flex-1">
        {/* Map canvas host — kept mounted for the map's whole lifetime.
            `h-full w-full`, IKKE `absolute inset-0`: mapbox-gl.css sin egen
            `.mapboxgl-map { position: relative }`-regel overstyrer en
            `absolute`-Tailwind-klasse her (samme klasse-navn, senere i
            cascade siden mapbox-gl.css importeres i denne komponentfilen)
            -- når posisjonen dermed blir `relative` i stedet for
            `absolute`, mister `inset-0` all effekt på størrelsen (den
            styrer kun posisjon/størrelse for absolutt/fixed-plasserte
            elementer), og beholderen kollapser til 0 høyde uansett hvilken
            posisjonsverdi som vinner kappløpet. `h-full`/`w-full` løser
            størrelsen via prosent-arv i stedet, som fungerer likt for
            både `relative` og `absolute`. Reell bug funnet 2026-08-08,
            se CHANGELOG.md. */}
        <div ref={containerRef} className="absolute inset-0 h-full w-full" aria-label="Satellittkart" role="application" />

        {!loaded && !error ? (
          <div className="absolute inset-0 flex items-center justify-center bg-background">
            <div className="flex flex-col items-center gap-4">
              <div className="size-8 animate-spin rounded-full border-4 border-primary/20 border-t-primary" />
              <p className="text-base text-muted-foreground">Laster kart …</p>
            </div>
          </div>
        ) : null}

        {error ? (
          <div className="absolute inset-0 flex items-center justify-center p-6">
            <p role="alert" className="max-w-sm text-center text-base text-destructive">
              {error}
            </p>
          </div>
        ) : null}

        {referencePoint ? (
          <div className="pointer-events-none absolute left-4 top-4 flex items-center gap-2 rounded-full bg-card/95 px-3 py-1.5 text-xs font-semibold text-foreground shadow-md">
            <span className="size-2.5 shrink-0 rounded-full" style={{ backgroundColor: "#ff5a1f" }} aria-hidden="true" />
            {referenceLabel}
          </div>
        ) : null}

        {/* Trykk-instruksjon KUN på start-steget -- ball-steget har ingen
            trykk-handling lenger (se click-handler-kommentaren over). */}
        {loaded && !point && !error && !referencePoint ? (
          <div className="pointer-events-none absolute inset-x-0 top-4 flex justify-center px-4">
            <p className="flex items-center gap-2 rounded-full bg-card/95 px-4 py-2 text-sm font-semibold text-foreground shadow-md">
              <MapPin className="size-4 shrink-0" aria-hidden="true" />
              Trykk på kartet for å plassere punktet
            </p>
          </div>
        ) : null}

        {/* Sanntids-avstand så langt -- kun ball-steget. Brukerønske
            2026-08-08: "jeg ønsker å se lengden så langt i sanntid mens
            jeg nærmer meg ballen". */}
        {referencePoint && liveDistance !== null ? (
          <div className="pointer-events-none absolute inset-x-0 bottom-4 flex justify-center px-4">
            <div className="flex flex-col items-center gap-0.5 rounded-2xl bg-card/95 px-5 py-2.5 shadow-md">
              <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                Avstand så langt
              </span>
              <span className="text-3xl font-extrabold tabular-nums leading-none text-foreground">
                {Math.round(liveDistance)}
                <span className="ml-1 text-lg font-bold text-muted-foreground">m</span>
              </span>
            </div>
          </div>
        ) : null}
      </div>

      <div className="shrink-0 border-t border-border bg-background p-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
        {referencePoint ? (
          <button
            type="button"
            disabled={!livePoint}
            onClick={() => livePoint && onConfirm(livePoint, "gps")}
            className="inline-flex min-h-14 w-full items-center justify-center gap-2 rounded-xl bg-primary px-6 text-lg font-bold text-primary-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
          >
            <Crosshair className="size-5 shrink-0" aria-hidden="true" />
            Jeg er ved ballen nå
          </button>
        ) : (
          <button
            type="button"
            disabled={!point}
            onClick={() => point && onConfirm(point, "map_tap")}
            className="inline-flex min-h-14 w-full items-center justify-center rounded-xl bg-primary px-6 text-lg font-bold text-primary-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
          >
            Bekreft punkt
          </button>
        )}
      </div>
    </div>
  )
}

export default MapPointPicker
