"use client"
// V0-eksportert visning (2026-08-17, ADR-083) -- ren kontrollert
// komponent, ingen egen GPS-/geometrilogikk. All geometri-utregning
// (projectOntoAxis) bor i hole-target-distance.tsx, allerede bevist
// riktig med enhetstester + scratch-verifisering FØR denne visningen
// ble bestilt. Erstattet en midlertidig hånd-kodet stand-in med samme
// props-kontrakt -- samme mønster som FlagPlantSheet/PlayerImportPanel.
import { Flag, MapPin, TriangleAlert } from "lucide-react"
import { cn } from "@/lib/utils"
export type DiagramHazard = {
label: string
/** Live distance FROM THE PLAYER, always accurate. */
distanceMeters: number
/** 0 = at tee, 100 = at green. May be <0 or >100. */
alongPercent: number
/** Positive = right of the tee->green axis, negative = left. */
crossMeters: number
}
type Props = {
distances: { front: number; middle: number; back: number }
playerPosition: { alongPercent: number; crossMeters: number }
/** Already sorted nearest-to-player first. */
hazards: DiagramHazard[]
size?: "compact" | "full"
className?: string
}
// --- Diagram geometry (percentages of the field box) ---------------------
// Fixed endpoints: green stays at the top, tee stays at the bottom, always.
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)
// Sideways scale: this many meters of cross offset maps to the max visual swing.
const MAX_CROSS_METERS = 30
const MAX_OFFSET_PCT = 40 // ±40% of the field width, per spec
// Label collision handling: minimum vertical gap between stacked labels.
const LABEL_MIN_GAP_PCT = 11
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 crossToLeftPct(crossMeters: number) {
const offset = clamp(
(crossMeters / MAX_CROSS_METERS) * MAX_OFFSET_PCT,
-MAX_OFFSET_PCT,
MAX_OFFSET_PCT,
)
return 50 + offset
}
function formatMeters(m: number) {
return `${Math.round(m)} m`
}
export function HoleDiagram({
distances,
playerPosition,
hazards,
size = "full",
className,
}: Props) {
if (size === "compact") {
return (
{hazards.length > 0 && (
{hazards.map((h, i) => (
-
{h.label}
{formatMeters(h.distanceMeters)}
))}
)}
)
}
// --- Full variant -------------------------------------------------------
// Precompute placements and a light label-collision nudge (top -> bottom).
const placed = hazards.map((h, i) => ({
...h,
key: `${h.label}-${i}`,
topPct: alongToTopPct(h.alongPercent),
leftPct: crossToLeftPct(h.crossMeters),
}))
const byTop = [...placed].sort((a, b) => a.topPct - b.topPct)
const labelTopByKey = new Map()
let lastLabelTop = Number.NEGATIVE_INFINITY
for (const p of byTop) {
const desired = p.topPct
const labelTop = Math.max(desired, lastLabelTop + LABEL_MIN_GAP_PCT)
labelTopByKey.set(p.key, labelTop)
lastLabelTop = labelTop
}
const playerTop = alongToTopPct(playerPosition.alongPercent)
const playerLeft = crossToLeftPct(playerPosition.crossMeters)
return (
{/* Distance readout: center emphasized + largest, in the tee-green color. */}
{/* The hole diagram. Green fixed top, tee fixed bottom. */}
{/* Discreet tee->green centerline */}
{/* Green marker (fixed top) */}
Green
{/* Tee marker (fixed bottom) */}
Tee
{/* Hazard markers + always-visible distance labels */}
{placed.map((p) => {
const labelTop = labelTopByKey.get(p.key) ?? p.topPct
const labelOnLeft = p.leftPct > 55
return (
{/* marker dot */}
{/* distance label (nudged vertically on collision) */}
{formatMeters(p.distanceMeters)}
)
})}
{/* Player position ("you are here") — a pin, shape-distinct from hazards */}
Deg
{/* Accessible, screen-reader-friendly text list — the same info as the diagram. */}
{hazards.length > 0 && (
Hindringer
{hazards.map((h, i) => (
-
{h.label}
{formatMeters(h.distanceMeters)}
))}
)}
)
}
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"],
) {
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.label} ${Math.round(h.distanceMeters)} meter`)
.join(", ")
return `${base} Hindringer: ${list}.`
}