"use client"

import { useEffect, useRef, useState } from "react"
import { TargetDistance } from "@/components/target-distance"
import { HoleDiagram } from "@/components/hole-diagram-view"
import { haversineMeters, type LatLng } from "@/lib/geo"
import { midpoint, projectOntoAxis } from "@/lib/hole-geometry"

// Rangefinder-datalag (ADR-064) -- henter rundens rå banepunkter (green
// front/senter/bak, hindringer) og kombinerer dem klient-side med spillerens
// LIVE GPS-posisjon via samme watchPosition-mønster som map-point-picker.tsx
// allerede bruker for slagmåling (ADR-048 tillegg 2026-08-08 del 2) --
// `enableHighAccuracy`, grasiøs degradering ved avslått tillatelse,
// `clearWatch` ved avmontering. `TargetDistance` selv forblir "dum"
// (V0-eksportert, ren visning) -- all logikk bor her. Ingen egen
// rad-wrapper (border/padding) her -- kalleren styrer plassering/bredde.
// Montert i PlayerHoleCards sin hull-navigator (round-detail.tsx),
// size="full" -- IKKE i ScoringWizard-headeren (der den satt først,
// 2026-08-12): brukeren pekte ut hovedvisningen som riktig sted og ba
// om mer bredde enn den opprinnelige kompaktvarianten, 2026-08-13.
//
// `baseUrl` (ikke `roundId`) siden migrasjon 079 -- komponenten gjenbrukes
// nå av tre kallesteder med tre ulike URL-familier (frittstående runde
// `/rounds/{id}`, individuell org-turnering `/orgs/{org}/tournaments/{t}/
// rounds/{r}`, lag-matchplay `/orgs/{org}/matches/{m}`) -- kalleren bygger
// selv `${baseUrl}/holes/{n}/target-points`, komponenten trenger ikke vite
// hvilken flyt den sitter i.
//
// Strømsparing (2026-08-13, brukerrapport om høyt batteriforbruk):
// (1) `maximumAge` satt til 4000ms (var 1000ms) -- en golfer beveger seg
// ~1,5 m/s, oppdatering hvert 4. sekund er umerkelig i praksis men ber
// GPS-brikken om et ferskt fix mye sjeldnere. (2) `watchPosition` pauses
// helt når fanen/appen ikke er synlig (`document.visibilitychange`) --
// ingen vits å holde GPS-en aktiv for en skjerm ingen ser på (skjerm låst,
// annen app i forgrunnen), og gjenopptas automatisk når brukeren kommer
// tilbake. `enableHighAccuracy` beholdt UENDRET -- lav nøyaktighet ville
// gitt titalls meters feilmargin, for upresist til at front/midt/bak-
// tallene gir mening.

type ApiTargetPoint = {
  poi_type: string
  location: "front" | "middle" | "back" | null
  side_fairway: "left" | "center" | "right" | null
  latitude: number
  longitude: number
  // Eksplisitt, menneske-satt paring av forkant/bakkant til SAMME
  // fysiske hindring (migrasjon 080/ADR-084). `null` = enkeltstående
  // punkt, ALDRI slått sammen med noe -- se groupDiagramHazards().
  hazard_group: string | null
}

const _HAZARD_LABELS: Record<string, string> = {
  green_bunker: "Bunker (green)",
  fairway_bunker: "Bunker (fairway)",
  water: "Vannhinder",
  creek: "Bekk",
  rock: "Fjellknaus",
  trees: "Trær",
  dogleg: "Dogleg",
  layup: "Layup-punkt",
  landmark: "Landemerke",
  road: "Vei",
  fairway: "Fairway",
  stone_fence: "Voll",
}

// ADR-084: hvilket ikon en hindringstype får i diagrammet. "generic"
// (typer uten dedikert ikon -- i dag ingen) faller tilbake til lucide
// sin TriangleAlert -- se hole-diagram-view.tsx.
type HazardKind =
  | "sand"
  | "water"
  | "creek"
  | "rock"
  | "tree"
  | "dogleg"
  | "layup"
  | "landmark"
  | "road"
  | "fairway"
  | "stone_fence"
  | "generic"
const _HAZARD_KIND: Record<string, HazardKind> = {
  green_bunker: "sand",
  fairway_bunker: "sand",
  water: "water",
  creek: "creek",
  rock: "rock",
  trees: "tree",
  dogleg: "dogleg",
  layup: "layup",
  landmark: "landmark",
  road: "road",
  fairway: "fairway",
  stone_fence: "stone_fence",
}

// Hvor mange prosent av hull-lengden en hindring får "henge igjen" etter at
// spilleren i teorien har passert den (ADR-085, brukerønske 2026-08-20:
// hindringer bak spilleren er unødvendig informasjon -- skal ikke lenger
// vises). Ren GPS-støy-margin, ikke en spilleregel -- uten denne ville en
// hindring rett ved spillerens fot kunne blinke inn/ut av listen mellom to
// GPS-oppdateringer idet spilleren går forbi.
const HAZARD_PASSED_TOLERANCE_PCT = 1.5

// Grupperer rå hindringspunkter til hindrings-ENTITETER for diagrammet.
// VIKTIG (bruker-rettelse, ADR-084): to punkter er KUN samme fysiske
// hindring hvis de deler samme, ikke-null hazard_group -- ALDRI basert
// på poi_type+side_fairway alene (ville feilaktig slått sammen to
// atskilte hindringer av samme type på samme side). Punkter med
// hazard_group=null forblir alltid enkeltstående.
// `playerAlongPercent` (ADR-085) -- en hindring vises KUN hvis den fortsatt
// ligger MELLOM spilleren og greenen (brukerønske: passerte hindringer er
// unødvendig informasjon). For et parret forkant+bakkant-hinder brukes
// bakkanten (den ytterste kanten, nærmest green) -- hele hindringen regnes
// som passert først når spilleren er forbi BEGGE kantene, ikke bare
// forkanten.
function groupDiagramHazards(
  hazardPoints: ApiTargetPoint[],
  livePosition: LatLng,
  teeRef: LatLng,
  greenRef: LatLng,
  playerAlongPercent: number,
) {
  const grouped = new Map<string, ApiTargetPoint[]>()
  const standalone: ApiTargetPoint[] = []
  for (const p of hazardPoints) {
    if (p.hazard_group) {
      const list = grouped.get(p.hazard_group) ?? []
      list.push(p)
      grouped.set(p.hazard_group, list)
    } else {
      standalone.push(p)
    }
  }

  type Entity = {
    key: string
    kind: HazardKind
    label: string
    lane: "left" | "center" | "right"
    alongPercent: number
    below: number
    above: number | null
    // Kun meningsfullt for enkeltstående punkt (above === null) --
    // et parret forkant+bakkant-hinder (above !== null) trenger ikke
    // dette, UI-en viser da alltid eksplisitt "forkant"/"bakkant".
    location: "front" | "middle" | "back" | null
  }
  const entities: Entity[] = []

  function distanceTo(p: ApiTargetPoint) {
    return Math.round(haversineMeters(livePosition, { lat: p.latitude, lng: p.longitude }))
  }
  function along(p: ApiTargetPoint) {
    return projectOntoAxis(teeRef, greenRef, { lat: p.latitude, lng: p.longitude }).alongPercent
  }

  for (const [groupKey, pts] of grouped) {
    const front = pts.find((p) => p.location === "front")
    const back = pts.find((p) => p.location === "back")
    const primary = front ?? back ?? pts[0]
    const kind = _HAZARD_KIND[primary.poi_type]
    if (!kind) continue
    const alongValues = [front, back].filter((p): p is ApiTargetPoint => !!p).map(along)
    const farEdgeAlong = Math.max(...alongValues)
    if (farEdgeAlong < playerAlongPercent - HAZARD_PASSED_TOLERANCE_PCT) continue
    entities.push({
      key: `group-${groupKey}`,
      kind,
      label: _HAZARD_LABELS[primary.poi_type],
      lane: primary.side_fairway ?? "center",
      alongPercent: alongValues.reduce((a, b) => a + b, 0) / alongValues.length,
      below: front ? distanceTo(front) : distanceTo(primary),
      above: front && back ? distanceTo(back) : null,
      location: front && back ? null : primary.location,
    })
  }
  for (const p of standalone) {
    const kind = _HAZARD_KIND[p.poi_type]
    if (!kind) continue
    if (along(p) < playerAlongPercent - HAZARD_PASSED_TOLERANCE_PCT) continue
    entities.push({
      key: `point-${p.latitude}-${p.longitude}-${p.poi_type}`,
      kind,
      label: _HAZARD_LABELS[p.poi_type],
      lane: p.side_fairway ?? "center",
      alongPercent: along(p),
      below: distanceTo(p),
      above: null,
      location: p.location,
    })
  }
  // ADR-083-konvensjonen ("sortert nærmest spilleren først") manglet her --
  // uten denne falt rekkefølgen tilbake til rå API-/innsettingsrekkefølge,
  // som ikke sier noe om avstand og heller ikke oppdaterer seg når
  // spilleren beveger seg. `below` er alltid den nærmeste av forkant/
  // enkeltpunkt-avstanden, så stigende sortering på den gir riktig
  // "nærmest spilleren først" for både parrede og enkeltstående hindringer.
  return entities.sort((a, b) => a.below - b.below)
}

export function HoleTargetDistance({
  baseUrl,
  holeNumber,
  size = "compact",
  className,
  onAvailabilityChange,
}: {
  baseUrl: string
  holeNumber: number
  size?: "compact" | "full"
  className?: string
  /** Kalles hver gang det blir klart om banen faktisk har koordinatdata for
   *  hullet eller ikke -- lar en kaller (f.eks. en utvid/skjul-knapp rundt
   *  komponenten) vite om det er noe å vise, uten selv å duplisere fetch-en. */
  onAvailabilityChange?: (available: boolean) => void
}) {
  const [points, setPoints] = useState<ApiTargetPoint[] | null>(null)
  const [livePosition, setLivePosition] = useState<LatLng | null>(null)
  const watchIdRef = useRef<number | null>(null)

  useEffect(() => {
    let cancelled = false
    setPoints(null)
    fetch(`${baseUrl}/holes/${holeNumber}/target-points`, { credentials: "include" })
      .then((res) => (res.ok ? res.json() : []))
      .then((data: ApiTargetPoint[]) => {
        if (!cancelled) setPoints(data)
      })
      .catch(() => {
        if (!cancelled) setPoints([])
      })
    return () => {
      cancelled = true
    }
  }, [baseUrl, holeNumber])

  useEffect(() => {
    if (points === null) return
    onAvailabilityChange?.(points.length > 0)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [points])

  useEffect(() => {
    // Ingen vits å spore GPS når banen uansett ikke har koordinatdata for
    // dette hullet -- unngår unødvendig batteribruk/tillatelses-prompt.
    if (!points || points.length === 0) return
    if (typeof navigator === "undefined" || !navigator.geolocation) return
    if (typeof document === "undefined") return

    function startWatch() {
      watchIdRef.current = navigator.geolocation.watchPosition(
        (pos) => {
          setLivePosition({ lat: pos.coords.latitude, lng: pos.coords.longitude })
        },
        () => {
          // Avslått/feilet -- degraderer grasiøst, komponenten viser bare "ingen data".
        },
        { enableHighAccuracy: true, maximumAge: 4000 },
      )
    }

    function stopWatch() {
      if (watchIdRef.current !== null && typeof navigator !== "undefined" && navigator.geolocation) {
        navigator.geolocation.clearWatch(watchIdRef.current)
      }
      watchIdRef.current = null
    }

    // Pauser GPS-sporingen helt når fanen/appen ikke er synlig (skjerm
    // låst, annen app i forgrunnen) -- gjenopptas automatisk når brukeren
    // kommer tilbake.
    function handleVisibilityChange() {
      if (document.hidden) {
        stopWatch()
      } else {
        startWatch()
      }
    }

    if (!document.hidden) startWatch()
    document.addEventListener("visibilitychange", handleVisibilityChange)

    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange)
      stopWatch()
    }
  }, [points])

  // Fortsatt laster, ELLER banen har rett og slett ingen koordinatdata
  // (det store flertallet av runder, TeeOff-baner har ingen GolfAPI-kobling)
  // -- ingen synlig rad i det hele tatt, ikke en "ingen data"-lapp på HVER
  // eneste runde. Lappen (TargetDistance sin egen null-tilstand) er forbeholdt
  // "banen HAR data, men venter på GPS-posisjon ennå" -- da vet vi det er
  // verdt å vise noe.
  if (points === null || points.length === 0) return null
  if (!livePosition) {
    return <TargetDistance size={size} distances={null} className={className} />
  }

  const greenPoints = points.filter((p) => p.poi_type === "green" && p.location)
  const front = greenPoints.find((p) => p.location === "front")
  const middle = greenPoints.find((p) => p.location === "middle")
  const back = greenPoints.find((p) => p.location === "back")
  if (!front || !middle || !back) {
    return <TargetDistance size={size} distances={null} className={className} />
  }

  const distances = {
    front: Math.round(haversineMeters(livePosition, { lat: front.latitude, lng: front.longitude })),
    middle: Math.round(haversineMeters(livePosition, { lat: middle.latitude, lng: middle.longitude })),
    back: Math.round(haversineMeters(livePosition, { lat: back.latitude, lng: back.longitude })),
  }

  // ADR-083: ALLE hindringer, ikke bare nærmeste (tidligere reduserte denne
  // løkken til ett enkelt nearestHazard-objekt) -- sortert nærmest
  // spilleren først. Avstandene er ALLTID live fra spilleren, uavhengig av
  // om et geometrisk diagram kan tegnes under.
  const hazardPoints = points.filter((p) => p.poi_type in _HAZARD_LABELS)
  // ADR-085 (brukerønske 2026-08-20): passerte hindringer skal ikke vises.
  // Denne (tee-referanse mangler) grenen har ingen akse å projisere langs,
  // så "mellom spiller og green" tilnærmes med "nærmere green enn
  // spilleren selv" -- mindre presist enn along-percent-varianten under
  // (tar ikke hensyn til sideveis avstand), men fungerer som en fornuftig
  // proxy for det sjeldne tilfellet en bane mangler tee-koordinater.
  const hazardPointsAhead = hazardPoints.filter(
    (h) => haversineMeters({ lat: h.latitude, lng: h.longitude }, { lat: middle.latitude, lng: middle.longitude }) < distances.middle,
  )
  const hazards = hazardPointsAhead
    .map((h) => {
      const base = _HAZARD_LABELS[h.poi_type]
      const label =
        h.location === "front" || h.location === "back"
          ? `${base} (${h.location === "front" ? "front" : "bak"})`
          : base
      return { label, distanceMeters: Math.round(haversineMeters(livePosition, { lat: h.latitude, lng: h.longitude })), lat: h.latitude, lng: h.longitude }
    })
    .sort((a, b) => a.distanceMeters - b.distanceMeters)

  // Fast tee-referanse (IKKE spillerens bevegelige livePosition) for selve
  // hull-diagrammet -- se ADR-083. Midtpunkt hvis begge tee_front/tee_back
  // finnes, ellers den ene som finnes. Mangler begge (forekommer, ikke alle
  // baner har tee-koordinater): faller tilbake til den flate listen under,
  // ingen regresjon.
  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

  if (teeRef) {
    const greenRef = { lat: middle.latitude, lng: middle.longitude }
    const player = projectOntoAxis(teeRef, greenRef, livePosition)
    const diagramHazards = groupDiagramHazards(hazardPoints, livePosition, teeRef, greenRef, player.alongPercent)
    return (
      <HoleDiagram
        size={size}
        className={className}
        distances={distances}
        playerPosition={player}
        hazards={diagramHazards}
      />
    )
  }

  return (
    <TargetDistance size={size} distances={distances} hazards={hazards} className={className} />
  )
}
