import React from "react";
import { Linking, StyleSheet, Text, View } from "react-native";
import type { StyleSpecification } from "@maplibre/maplibre-react-native";
import Svg, { Circle, Path, SvgXml } from "react-native-svg";
import { MAPMAP_LOCKUP_ASPECT, MAPMAP_LOCKUP_SVG } from "../brand/logo";
import type { LatLng } from "../../modules/mapmap-react-native/src/MapmapNav.types";
import { radius, spacing, theme, typeScale } from "../theme";
import type { Units } from "../state/types";

/**
 * Shared chrome for the two screens (S6 RoutePreview, S7 GuidanceScreen)
 * that render their own full-bleed MapLibre `Map`. Kept here instead of
 * duplicated in both files.
 *
 * Integration pass (see FINDINGS.md): `styleJson`, `units` and
 * `onShowAbout` now reach both screens via their prop contracts, threaded
 * from App.tsx's MainScreen. `FALLBACK_MAP_STYLE` below remains the
 * before-the-style-resolves / fetch-failed placeholder.
 */

/**
 * Offline-safe placeholder style: solid background, zero sources, zero
 * network calls. Shown until `styleJson` (from
 * `MapmapNavModule.getTerritoryStyle(territoryId, theme)`, fetched once per
 * active territory in App.tsx) resolves, or if it fails to load.
 */
export const FALLBACK_MAP_STYLE: StyleSpecification = {
  version: 8,
  sources: {},
  layers: [
    {
      id: "fallback-background",
      type: "background",
      paint: { "background-color": theme.background },
    },
  ],
};

export function toLngLat(position: LatLng): [number, number] {
  return [position.longitude, position.latitude];
}

export type Bounds = [west: number, south: number, east: number, north: number];

export function boundsOf(points: LatLng[]): Bounds | null {
  if (points.length === 0) return null;
  let west = points[0].longitude;
  let east = points[0].longitude;
  let south = points[0].latitude;
  let north = points[0].latitude;
  for (const p of points) {
    if (p.longitude < west) west = p.longitude;
    if (p.longitude > east) east = p.longitude;
    if (p.latitude < south) south = p.latitude;
    if (p.latitude > north) north = p.latitude;
  }
  return [west, south, east, north];
}

/** A GeoJSON LineString feature from a raw point list, or null under 2 points
 * (MapLibre rejects degenerate lines) — shared by S6's alt/active routes and
 * S7's full-route + consumed-trail layers. */
export function lineStringFromPoints(
  points: LatLng[]
): GeoJSON.Feature<GeoJSON.LineString> | null {
  if (points.length < 2) return null;
  return {
    type: "Feature",
    properties: {},
    geometry: { type: "LineString", coordinates: points.map((p) => toLngLat(p)) },
  };
}

/** Index of the point in `points` closest (straight-line) to `target` — used
 * to split a route's geometry into "consumed" vs "ahead" segments against
 * the current snapped guidance location. Approximate (equirectangular, no
 * projection) — fine for a visual split, not for measurement. */
export function nearestPointIndex(points: LatLng[], target: LatLng): number {
  let bestIndex = 0;
  let bestDistanceSq = Infinity;
  for (let i = 0; i < points.length; i += 1) {
    const dLat = points[i].latitude - target.latitude;
    const dLng = points[i].longitude - target.longitude;
    const distanceSq = dLat * dLat + dLng * dLng;
    if (distanceSq < bestDistanceSq) {
      bestDistanceSq = distanceSq;
      bestIndex = i;
    }
  }
  return bestIndex;
}

const METERS_PER_MILE = 1609.344;
const METERS_PER_FOOT = 0.3048;

/** Bold "12 min" style duration under an hour, "3 hr 24 min" at/above one —
 * rounds to the nearest minute. A raw "504 min" reads badly on long trips. */
export function formatDuration(seconds: number): string {
  const minutes = Math.round(seconds / 60);
  if (minutes < 1) return "<1 min";
  if (minutes < 60) return `${minutes} min`;
  const hours = Math.floor(minutes / 60);
  const remainder = minutes % 60;
  return remainder === 0 ? `${hours} hr` : `${hours} hr ${remainder} min`;
}

/** "320 m" under 1 km, "3.2 km" at/above (metric); "800 ft" under 0.1 mi,
 * "3.2 mi" at/above (imperial) — per UI spec S7, unit-aware per Settings. */
export function formatDistance(meters: number, units: Units = "km"): string {
  if (units === "mi") {
    const miles = meters / METERS_PER_MILE;
    if (miles < 0.1) {
      const feet = Math.max(0, Math.round(meters / METERS_PER_FOOT));
      return `${feet} ft`;
    }
    return `${miles.toFixed(1)} mi`;
  }
  if (meters < 1000) return `${Math.max(0, Math.round(meters))} m`;
  return `${(meters / 1000).toFixed(1)} km`;
}

/** Same rule as formatDistance but rounded to avoid jittering the banner at
 * ~1 Hz guidance updates (nearest 10 m / nearest 20 ft). */
export function formatDistanceToTurn(meters: number, units: Units = "km"): string {
  if (units === "mi") {
    const miles = meters / METERS_PER_MILE;
    if (miles < 0.1) {
      const feet = Math.max(0, Math.round(meters / METERS_PER_FOOT / 20) * 20);
      return `${feet} ft`;
    }
    return `${miles.toFixed(1)} mi`;
  }
  if (meters < 1000) {
    return `${Math.max(0, Math.round(meters / 10) * 10)} m`;
  }
  return `${(meters / 1000).toFixed(1)} km`;
}

/** 24-hour "14:32" clock time from an epoch-ms instant. */
export function formatClockTime(epochMs: number): string {
  const d = new Date(epochMs);
  const hh = `${d.getHours()}`.padStart(2, "0");
  const mm = `${d.getMinutes()}`.padStart(2, "0");
  return `${hh}:${mm}`;
}

/** "+4 min" / "-2 min" / "Same time" chip label for alternative routes. */
export function formatTimeDelta(deltaSeconds: number): string {
  const minutes = Math.round(deltaSeconds / 60);
  if (minutes === 0) return "Same time";
  return minutes > 0 ? `+${minutes} min` : `${minutes} min`;
}

const OSM_COPYRIGHT_URL = "https://www.openstreetmap.org/copyright";

/** Bottom-left brand mark required on every map surface (UI spec). */
export function MapWatermark(): React.JSX.Element {
  return (
    <View style={chromeStyles.watermark} pointerEvents="none">
      <SvgXml
        xml={MAPMAP_LOCKUP_SVG}
        width={78}
        height={78 / MAPMAP_LOCKUP_ASPECT}
      />
    </View>
  );
}

/**
 * Small-print OSM attribution, always visible per the UI spec: tappable →
 * About/Licences (S11). `onShowAbout` now reaches this screen via the prop
 * contract (integration pass, see FINDINGS.md); pass it through as `onPress`
 * to navigate there. Falls back to opening the OSM copyright page directly
 * when absent (e.g. a caller that hasn't wired navigation yet).
 */
export function MapAttribution({
  onPress,
}: {
  onPress?: () => void;
}): React.JSX.Element {
  return (
    <Text
      style={chromeStyles.attribution}
      onPress={() => {
        if (onPress) {
          onPress();
        } else {
          void Linking.openURL(OSM_COPYRIGHT_URL);
        }
      }}
      accessibilityRole="link"
      accessibilityLabel="OpenStreetMap attribution, opens licences"
    >
      © OpenStreetMap contributors
    </Text>
  );
}

/** Simple pin glyph for a selected destination — shared by S6 and S7. */
export function DestinationPin({ color = theme.signal }: { color?: string }): React.JSX.Element {
  return (
    <Svg width={28} height={36} viewBox="0 0 28 36">
      <Path
        d="M14 0C6.27 0 0 6.27 0 14c0 10.5 14 22 14 22s14-11.5 14-22C28 6.27 21.73 0 14 0Z"
        fill={color}
      />
      <Circle cx={14} cy={14} r={5.5} fill={theme.surface} />
    </Svg>
  );
}

/**
 * The navigation puck (guidance) — a Google/Apple-style directional marker:
 * a soft translucent view-cone fanning out ahead, a bold blue disc ringed in
 * white (so it reads on any basemap, light or 3D), and a crisp white chevron
 * pointing in the direction of travel. Rendered as a screen-aligned Marker;
 * in bearing-up navigation the camera rotates to the heading, so the chevron
 * points up (dead ahead) with no per-frame rotation. Pass `headingDegrees` to
 * rotate it when the map is north-up (overview / after a manual pan).
 *
 * `size` lets callers scale it — the chase view wants a larger, more legible
 * puck than the pinned-preview state.
 */
export function NavPuck({
  headingDegrees = 0,
  size = 60,
}: {
  headingDegrees?: number;
  size?: number;
}): React.JSX.Element {
  return (
    <Svg
      width={size}
      height={size}
      viewBox="0 0 60 60"
      style={{ transform: [{ rotate: `${headingDegrees}deg` }] }}
    >
      {/* soft drop-shadow halo — lifts the white disc off any basemap */}
      <Circle cx={30} cy={31} r={17} fill="#0B1020" opacity={0.14} />
      {/* white disc */}
      <Circle cx={30} cy={30} r={15} fill={theme.surface} />
      {/* dark navy chevron pointing up (direction of travel), Mapbox-style */}
      <Path
        d="M30 20 L38.5 39 L30 34.5 L21.5 39 Z"
        fill={theme.textPrimary}
        strokeLinejoin="round"
      />
    </Svg>
  );
}

const chromeStyles = StyleSheet.create({
  watermark: {
    position: "absolute",
    left: spacing[3],
    bottom: spacing[3],
    backgroundColor: "rgba(255,255,255,0.85)",
    paddingHorizontal: spacing[2],
    paddingVertical: 3,
    borderRadius: radius.sm,
  },
  watermarkText: {
    fontSize: typeScale.caption.fontSize,
    fontWeight: "700",
    color: theme.textPrimary,
    letterSpacing: 0.2,
  },
  attribution: {
    position: "absolute",
    right: spacing[2],
    bottom: spacing[1],
    fontSize: 10,
    lineHeight: 14,
    color: theme.textSecondary,
    backgroundColor: "rgba(255,255,255,0.7)",
    paddingHorizontal: 4,
  },
});
