/**
 * ChaseLayer — the follow camera + the OVERVIEW/panned puck.
 *
 * The key smoothness lesson (verified against the maplibre-react-native source
 * and the maplibre-navigation-ios issue tracker, see FINDINGS): while FOLLOWING,
 * the puck must NOT be animated as a map feature. The camera animates natively
 * (`easeTo`) and a map-anchored puck animated separately on the JS thread
 * (`Animated.Point`) is a second, drifting clock — that desync is exactly the
 * "stutters back and forth, worse at low zoom, doesn't stick on corners" bug.
 *
 * So: while following, this component ONLY drives the camera, and the puck is a
 * FIXED screen-space overlay rendered by GuidanceScreen at the camera focal
 * point (it never moves, so it can't stutter; the map slides under it). When
 * NOT following (overview / user panned), there's no competing camera animation,
 * so a plain map-anchored `Marker` puck is fine and correct — that's what this
 * renders.
 */
import React, { useEffect, useRef } from "react";
import { Image, StyleSheet } from "react-native";
import { Marker } from "@maplibre/maplibre-react-native";
import type { CameraRef } from "@maplibre/maplibre-react-native";

export type PuckStyle = "chevron" | "arrow" | "beam";

/** The selectable puck artworks. Drop a replacement PNG at any of these paths
 * (300×300, transparent, pointing UP) to restyle a puck. Exported so the
 * follow-mode screen overlay can render the same image. */
export const PUCK_IMAGE_SOURCES: Record<PuckStyle, number> = {
  chevron: require("../../../assets/navpuck.png"),
  arrow: require("../../../assets/puck-arrow.png"),
  beam: require("../../../assets/puck-beam.png"),
};

/** Puck on-screen size (px) for a zoom — bigger at street level, smaller when
 * zoomed out. Shared by the follow overlay and the overview marker. */
export function puckPxForZoom(zoom: number): number {
  const t = Math.max(0, Math.min(1, (zoom - 13) / (19 - 13)));
  return Math.round(40 + t * (64 - 40));
}

export type ChaseTarget = {
  longitude: number;
  latitude: number;
  bearing: number;
  zoom: number;
  pitch: number;
};

export type ChasePadding = {
  top: number;
  bottom: number;
  left: number;
  right: number;
};

export type ChaseLayerProps = {
  cameraRef: React.RefObject<CameraRef | null>;
  /** Latest desired camera/puck state from guidance; null before it starts. */
  target: ChaseTarget | null;
  /** True only while actively chasing (following, not overview, not panned). */
  active: boolean;
  padding: ChasePadding;
  /** Which puck artwork to show — user-selectable in Driving options. */
  puckStyle?: PuckStyle;
  /** Current map zoom — sizes the overview marker puck. */
  zoom?: number;
};

/** Fallback duration for the very first fix. After that it adapts to the real
 * interval so each animation exactly fills the gap (constant velocity). */
const FIRST_FIX_MS = 320;
const DURATION_PADDING_MS = 60;

export function ChaseLayer({
  cameraRef,
  target,
  active,
  padding,
  puckStyle = "chevron",
  zoom = 18,
}: ChaseLayerProps): React.JSX.Element | null {
  const paddingRef = useRef<ChasePadding>(padding);
  paddingRef.current = padding;
  const lastFixRef = useRef<number | null>(null);

  useEffect(() => {
    if (!target || !active) {
      lastFixRef.current = null;
      return;
    }
    // Duration = the real gap since the last fix, so each linear ease exactly
    // fills the interval and hands off at matching velocity (no stall/overlap).
    const now = Date.now();
    const gap = lastFixRef.current != null ? now - lastFixRef.current : FIRST_FIX_MS;
    lastFixRef.current = now;
    const duration = Math.max(140, Math.min(1400, gap)) + DURATION_PADDING_MS;
    cameraRef.current?.easeTo({
      center: [target.longitude, target.latitude],
      zoom: target.zoom,
      bearing: target.bearing,
      pitch: target.pitch,
      padding: paddingRef.current,
      duration,
      easing: "linear",
    });
  }, [target, active, cameraRef]);

  // Following → the puck is a fixed screen overlay (GuidanceScreen). Not
  // following → a map-anchored marker at the current position (no competing
  // camera animation here, so no desync).
  if (active || !target) return null;

  const size = puckPxForZoom(zoom);
  return (
    <Marker lngLat={[target.longitude, target.latitude]} anchor="center">
      <Image
        source={PUCK_IMAGE_SOURCES[puckStyle]}
        style={[
          styles.puck,
          { width: size, height: size, transform: [{ rotate: `${target.bearing}deg` }] },
        ]}
      />
    </Marker>
  );
}

const styles = StyleSheet.create({
  puck: {
    resizeMode: "contain",
  },
});
