"use client"
// Hull-diagram v2 (2026-08-17, ADR-084) -- kategoriske venstre/senter/
// høyre-akser i stedet for kontinuerlig sideveis-forskyvning (ADR-083).
// Se ADR-084: kontinuerlig `crossMeters` ga for liten synlig forskjell
// på ekte banedata (Tjøme hull 18 -- bunker+vann havnet begge nesten på
// senterlinjen selv om de faktisk ligger på ulik side). Banevalget
// kommer nå fra `hazards[].lane`, satt av datalaget i
// hole-target-distance.tsx basert på `side_fairway` -- IKKE utledet her.
// Ren kontrollert komponent, ingen egen GPS-/geometrilogikk.
import { MapPin, TriangleAlert } from "lucide-react"
import { cn } from "@/lib/utils"
export type HazardKind = "sand" | "water" | "generic"
export type DiagramHazard = {
key: string
kind: HazardKind
label: string
lane: "left" | "center" | "right"
/** 0 = ved tee, 100 = ved green. */
alongPercent: number
/** Avstand FRA SPILLEREN, vises rett under ikonet. Alltid satt. */
below: number
/** Avstand til bakkant, vises rett over ikonet -- KUN satt når
* hindringen er et ekte forkant+bakkant-par (se hazard_group i
* datalaget). `null` = enkeltpunkt, ingen tekst over ikonet. */
above: number | null
}
type Props = {
distances: { front: number; middle: number; back: number }
playerPosition: { alongPercent: number }
hazards: DiagramHazard[]
size?: "compact" | "full"
className?: string
}
const TRACK_TOP = 12 // where alongPercent === 100 lands (leaves room for the flag)
const TRACK_BOTTOM = 88 // where alongPercent === 0 lands (leaves room for the tee dot)
const LABEL_MIN_GAP_PCT = 16 // hindringer i samme bane trenger mer luft nå (ikon + to tall)
function clamp(n: number, lo: number, hi: number) {
return Math.min(hi, Math.max(lo, n))
}
function alongToTopPct(alongPercent: number) {
const a = clamp(alongPercent, 0, 100)
return TRACK_TOP + ((100 - a) / 100) * (TRACK_BOTTOM - TRACK_TOP)
}
function formatMeters(m: number) {
return `${Math.round(m)} m`
}
function HazardIcon({ kind }: { kind: HazardKind }) {
if (kind === "sand") {
return
}
if (kind === "water") {
return
}
return
}
/** Nudger ikoner i SAMME bane fra øverst til nederst hvis de er for
* nære vertikalt -- samme kollisjons-mønster som før, men kjørt per
* bane siden hindringer nå er spredt over tre baner. */
function layoutLane(hazards: DiagramHazard[]) {
const sorted = [...hazards].sort((a, b) => alongToTopPct(a.alongPercent) - alongToTopPct(b.alongPercent))
const placed: (DiagramHazard & { topPct: number })[] = []
let lastTop = Number.NEGATIVE_INFINITY
for (const h of sorted) {
const desired = alongToTopPct(h.alongPercent)
const topPct = Math.max(desired, lastTop + LABEL_MIN_GAP_PCT)
placed.push({ ...h, topPct })
lastTop = topPct
}
return placed
}
function HazardMarker({ hazard, topPct }: { hazard: DiagramHazard; topPct: number }) {
return (
{hazard.above !== null && (
{formatMeters(hazard.above)}
)}
{formatMeters(hazard.below)}
)
}
export function HoleDiagram({ distances, playerPosition, hazards, size = "full", className }: Props) {
if (size === "compact") {
return (
{hazards.length > 0 && (
{hazards.map((h) => (
-
{h.label}
{formatMeters(h.below)}
))}
)}
)
}
const leftHazards = layoutLane(hazards.filter((h) => h.lane === "left"))
const centerHazards = layoutLane(hazards.filter((h) => h.lane === "center"))
const rightHazards = layoutLane(hazards.filter((h) => h.lane === "right"))
const playerTop = alongToTopPct(playerPosition.alongPercent)
return (
{/* Venstre bane */}
{leftHazards.map((h) => (
))}
{/* Midtbane -- eneste bane med green/tee/spiller, alltid faste ankre her */}
Green
Tee
Deg
{centerHazards.map((h) => (
))}
{/* Høyre bane */}
{rightHazards.map((h) => (
))}
{hazards.length > 0 && (
Hindringer
{hazards.flatMap((h) =>
h.above !== null
? [
,
,
]
: [],
)}
)}
)
}
function HazardRow({ label, value }: { label: string; value: number }) {
return (
{label}
{formatMeters(value)}
)
}
function BigNumber({ label, value, emphasized }: { label: string; value: number; emphasized?: boolean }) {
return (
{Math.round(value)}
{label}
)
}
function CompactNumber({ label, value, emphasized }: { label: string; value: number; emphasized?: boolean }) {
return (
{Math.round(value)}
{label}
)
}
function buildDiagramAria(
distances: Props["distances"],
hazards: DiagramHazard[],
player: Props["playerPosition"],
) {
void player
const base = `Baneskisse. Senter ${Math.round(distances.middle)} meter til green. Din posisjon markert.`
if (hazards.length === 0) return `${base} Ingen hindringer.`
const list = hazards
.map((h) =>
h.above !== null
? `${h.label} forkant ${Math.round(h.below)} meter, bakkant ${Math.round(h.above)} meter`
: `${h.label} ${Math.round(h.below)} meter`,
)
.join(", ")
return `${base} Hindringer: ${list}.`
}