/**
 * S6. Route preview — this file owns the prop contract (`RoutePreviewProps`,
 * below); src/screens/index.ts just re-exports it. Integration pass (see
 * FINDINGS.md) closed the original gap: `styleJson`, `units` and
 * `onShowAbout` now reach this screen as props, threaded from App.tsx.
 */
import React, { useCallback, useMemo, useRef, useState } from "react";
import { type NativeSyntheticEvent, Pressable, StyleSheet, Text, View } from "react-native";
import {
  Camera,
  type CameraRef,
  GeoJSONSource,
  Layer,
  Map as MapLibreMap,
  type MapRef,
  Marker,
  type PressEvent,
} from "@maplibre/maplibre-react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { Button, Card, Chip, SegmentedControl } from "../../components";
import { tapToleranceBox } from "../../map/tapSelection";
import type { SegmentedControlOption } from "../../components";
import { minTouchTarget, radius, spacing, theme, typeScale } from "../../theme";
import type {
  BridgeError,
  RouteProfile,
  RouteResult,
  SelectedPlace,
  Units,
} from "../../state/types";
import type {
  LatLng,
  Route,
  RouteAvoid,
  TruckOptions,
} from "../../../modules/mapmap-react-native/src/MapmapNav.types";
import { TruckOptionsSheet } from "./TruckOptionsSheet";
import { RouteCalculating } from "./RouteCalculating";
import {
  DestinationPin,
  FALLBACK_MAP_STYLE,
  MapAttribution,
  MapWatermark,
  boundsOf,
  formatClockTime,
  formatDistance,
  formatDuration,
  formatTimeDelta,
  toLngLat,
} from "../mapChrome";

export type RoutePreviewProps = {
  place: SelectedPlace;
  profile: RouteProfile;
  onProfileChange: (profile: RouteProfile) => void;
  /** Truck/ADR options (shown when profile === "truck"). */
  truckOptions: TruckOptions;
  onTruckOptionsChange: (next: TruckOptions) => void;
  /** Route-preference exclusions (motorised profiles). */
  avoid: RouteAvoid;
  onAvoidChange: (next: RouteAvoid) => void;
  routeResult?: RouteResult;
  activeRouteIndex: number;
  onSelectRoute: (routeIndex: number) => void;
  isComputing: boolean;
  error?: BridgeError;
  onRetry: () => void;
  onStart: () => void;
  onOverview: () => void;
  /** X — back to S5 (Place card), per spec. */
  onBack: () => void;
  /** Resolved territory style JSON (App.tsx fetches once per active
   * territory); null before it resolves or if the fetch failed — the map
   * falls back to a background-only placeholder either way. */
  styleJson?: string | null;
  units: Units;
  /** Attribution tap → About/Licences (S11); falls back to the OSM
   * copyright URL when absent. */
  onShowAbout?: () => void;
};

const PROFILE_OPTIONS: Array<SegmentedControlOption<RouteProfile>> = [
  { value: "car", label: "Car" },
  { value: "truck", label: "Lorry" },
  { value: "bicycle", label: "Bike" },
  { value: "walking", label: "Walk" },
];

/** Static line paint/layout — module scope so identity is stable across
 * renders (line-cap/line-join are layout properties, not paint, in the
 * MapLibre style spec). */
const LINE_LAYOUT = {
  "line-cap": "round" as const,
  "line-join": "round" as const,
};
const CASING_PAINT = {
  "line-color": theme.surface,
  "line-width": 10,
};
const ACTIVE_LINE_PAINT = {
  "line-color": theme.signal,
  "line-width": 6,
};
const ALT_LINE_PAINT = {
  "line-color": theme.textSecondary,
  "line-width": 4,
  "line-opacity": 0.55,
};

function routeToLineString(route: Route): GeoJSON.Feature<GeoJSON.LineString> {
  return {
    type: "Feature",
    // routeIndex lets a tap on the line resolve back to which route it is.
    properties: { routeIndex: route.index },
    geometry: {
      type: "LineString",
      coordinates: route.geometry.map((p: LatLng) => toLngLat(p)),
    },
  };
}

/** MapLibre map fitting the whole route, grey dimmed alternatives with
 * tappable time-delta chips, profile switch, and Start/Overview/X — S6. */
export function RoutePreview({
  place,
  profile,
  onProfileChange,
  truckOptions,
  onTruckOptionsChange,
  avoid,
  onAvoidChange,
  routeResult,
  activeRouteIndex,
  onSelectRoute,
  isComputing,
  error,
  onRetry,
  onStart,
  onOverview,
  onBack,
  styleJson,
  units,
  onShowAbout,
}: RoutePreviewProps): React.JSX.Element {
  const insets = useSafeAreaInsets();
  const cameraRef = useRef<CameraRef>(null);
  const mapRef = useRef<MapRef>(null);
  const [cardHeight, setCardHeight] = useState(240);
  const [truckSheetOpen, setTruckSheetOpen] = useState(false);

  const routes = routeResult?.routes ?? [];
  const activeRoute = routes.find((r) => r.index === activeRouteIndex) ?? routes[activeRouteIndex];

  // Tap an alternative route line to select it (the primary affordance — the
  // time-delta chip is a secondary hint). Query only the alternative line
  // layers so a tap near the active route doesn't reselect it, with a generous
  // tolerance (a 4px line needs a fat touch target).
  const altLineLayerIds = useMemo(
    () =>
      activeRoute
        ? routes
            .filter((r) => r.index !== activeRoute.index)
            .map((r) => `route-alt-line-${r.index}`)
        : [],
    [routes, activeRoute]
  );

  const handleRoutePress = useCallback(
    (event: NativeSyntheticEvent<PressEvent>) => {
      const map = mapRef.current;
      if (!map || altLineLayerIds.length === 0) return;
      const { point } = event.nativeEvent;
      map
        .queryRenderedFeatures(tapToleranceBox(point, 22), { layers: altLineLayerIds })
        .then((features) => {
          const hit = features.find((f) => typeof f.properties?.routeIndex === "number");
          if (hit) onSelectRoute(hit.properties!.routeIndex as number);
        })
        .catch(() => {});
    },
    [altLineLayerIds, onSelectRoute]
  );

  const bounds = useMemo(() => {
    const allPoints = routes.flatMap((r) => r.geometry);
    return boundsOf(allPoints.length > 0 ? allPoints : [place.position]);
  }, [routes, place.position]);

  const padding = useMemo(
    () => ({
      top: insets.top + spacing[12],
      bottom: cardHeight + insets.bottom + spacing[3],
      left: spacing[6],
      right: spacing[6],
    }),
    [insets.top, insets.bottom, cardHeight]
  );

  const handleOverviewPress = useCallback(() => {
    onOverview();
    if (bounds) {
      cameraRef.current?.fitBounds(bounds, {
        padding,
        duration: 700,
        easing: "fly",
      });
    }
  }, [onOverview, bounds, padding]);

  const showFailure = !!error;
  const showComputing = !showFailure && (isComputing || !routeResult);
  const canStart = !!routeResult && !!activeRoute && !showFailure && !showComputing;

  const arrivalLabel = activeRoute
    ? formatClockTime(Date.now() + activeRoute.durationSeconds * 1000)
    : null;

  return (
    <View style={styles.fill}>
      <MapLibreMap
        ref={mapRef}
        style={StyleSheet.absoluteFill}
        mapStyle={styleJson ?? FALLBACK_MAP_STYLE}
        logo={false}
        attribution={false}
        compass
        compassPosition={{ top: insets.top + spacing[3], right: spacing[3] }}
        onPress={handleRoutePress}
      >
        <Camera
          ref={cameraRef}
          bounds={bounds ?? undefined}
          padding={padding}
          pitch={0}
          bearing={0}
          duration={700}
          easing="fly"
        />

        {/* Dimmed alternatives, each carrying a tappable time-delta chip. */}
        {activeRoute &&
          routes
            .filter((r) => r.index !== activeRoute.index)
            .map((route) => {
              const midpoint = route.geometry[Math.floor(route.geometry.length / 2)];
              const delta = route.durationSeconds - activeRoute.durationSeconds;
              return (
                <React.Fragment key={`alt-${route.index}`}>
                  <GeoJSONSource id={`route-alt-${route.index}`} data={routeToLineString(route)}>
                    <Layer
                      id={`route-alt-line-${route.index}`}
                      type="line"
                      paint={ALT_LINE_PAINT}
                      layout={LINE_LAYOUT}
                    />
                  </GeoJSONSource>
                  {midpoint && (
                    <Marker lngLat={toLngLat(midpoint)} anchor="center">
                      <Chip
                        label={formatTimeDelta(delta)}
                        tone="neutral"
                        onPress={() => onSelectRoute(route.index)}
                      />
                    </Marker>
                  )}
                </React.Fragment>
              );
            })}

        {/* Active route: signal-blue 6px over a white casing (two LineLayers). */}
        {activeRoute && (
          <GeoJSONSource id="route-active" data={routeToLineString(activeRoute)}>
            <Layer id="route-active-casing" type="line" paint={CASING_PAINT} layout={LINE_LAYOUT} />
            <Layer id="route-active-line" type="line" paint={ACTIVE_LINE_PAINT} layout={LINE_LAYOUT} />
          </GeoJSONSource>
        )}

        <Marker lngLat={toLngLat(place.position)} anchor="bottom">
          <DestinationPin />
        </Marker>
      </MapLibreMap>

      <MapWatermark />
      <MapAttribution onPress={onShowAbout} />

      <Pressable
        onPress={onBack}
        accessibilityRole="button"
        accessibilityLabel="Close route preview"
        style={[styles.closeButton, { top: insets.top + spacing[3] }]}
      >
        <Text style={styles.closeGlyph}>✕</Text>
      </Pressable>

      <View
        style={[styles.card, { paddingBottom: insets.bottom + spacing[4] }]}
        onLayout={(e) => setCardHeight(e.nativeEvent.layout.height)}
      >
        <SegmentedControl options={PROFILE_OPTIONS} value={profile} onChange={onProfileChange} />

        {(profile === "car" || profile === "truck") && (
          <View style={styles.optionRow}>
            <Chip
              label="Avoid tolls"
              selected={!!avoid.tolls}
              onPress={() => onAvoidChange({ ...avoid, tolls: !avoid.tolls })}
            />
            <Chip
              label="Avoid motorways"
              selected={!!avoid.motorways}
              onPress={() => onAvoidChange({ ...avoid, motorways: !avoid.motorways })}
            />
            <Chip
              label="Avoid ferries"
              selected={!!avoid.ferries}
              onPress={() => onAvoidChange({ ...avoid, ferries: !avoid.ferries })}
            />
          </View>
        )}

        {profile === "truck" && (
          <Pressable
            style={styles.truckRow}
            onPress={() => setTruckSheetOpen(true)}
            accessibilityRole="button"
          >
            <Text style={styles.truckLabel}>
              Lorry: {truckOptions.weightT ?? 44}t · {truckOptions.heightM ?? 4.2}m
              {truckOptions.hazmat ? ` · ADR${truckOptions.tunnelCode ? ` ${truckOptions.tunnelCode}` : ""}` : ""}
            </Text>
            <Text style={styles.truckEdit}>Edit</Text>
          </Pressable>
        )}

        {showFailure ? (
          <View style={styles.messageBlock}>
            <Text style={styles.errorTitle}>Couldn&rsquo;t find a route</Text>
            <Text style={styles.errorBody}>
              The destination may be outside the territory. Try a different profile or retry.
            </Text>
            <View style={styles.buttonRow}>
              <Button label="Cancel" variant="secondary" onPress={onBack} style={styles.flexButton} />
              <Button label="Retry" variant="primary" onPress={onRetry} style={styles.flexButton} />
            </View>
          </View>
        ) : showComputing ? (
          <RouteCalculating />
        ) : (
          activeRoute && (
            <View style={styles.summaryBlock}>
              <Text style={styles.duration}>{formatDuration(activeRoute.durationSeconds)}</Text>
              <Text style={styles.subtitle}>
                {formatDistance(activeRoute.distanceMeters, units)}
                {arrivalLabel ? ` · Arrives ${arrivalLabel}` : ""}
              </Text>
              <View style={styles.buttonRow}>
                <Button
                  label="Overview"
                  variant="secondary"
                  onPress={handleOverviewPress}
                  style={styles.flexButton}
                />
                <Button
                  label="Start"
                  variant="primary"
                  onPress={onStart}
                  disabled={!canStart}
                  style={styles.startButton}
                />
              </View>
            </View>
          )
        )}
      </View>

      <TruckOptionsSheet
        visible={truckSheetOpen}
        value={truckOptions}
        onChange={onTruckOptionsChange}
        onClose={() => setTruckSheetOpen(false)}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  fill: {
    flex: 1,
    backgroundColor: theme.background,
  },
  optionRow: {
    flexDirection: "row",
    flexWrap: "wrap",
    gap: spacing[2],
    marginTop: spacing[3],
  },
  truckRow: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
    marginTop: spacing[3],
    paddingVertical: spacing[2],
    paddingHorizontal: spacing[3],
    backgroundColor: theme.surfaceAlt,
    borderRadius: radius.md,
  },
  truckLabel: { ...typeScale.body, color: theme.textPrimary, flexShrink: 1 },
  truckEdit: { ...typeScale.label, color: theme.signal },
  closeButton: {
    position: "absolute",
    left: spacing[3],
    width: minTouchTarget,
    height: minTouchTarget,
    borderRadius: radius.pill,
    backgroundColor: theme.surface,
    alignItems: "center",
    justifyContent: "center",
    ...theme.shadow,
  },
  closeGlyph: {
    fontSize: 18,
    color: theme.textPrimary,
    fontWeight: "600",
  },
  card: {
    position: "absolute",
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: theme.surface,
    borderTopLeftRadius: radius.lg,
    borderTopRightRadius: radius.lg,
    paddingHorizontal: spacing[4],
    paddingTop: spacing[4],
    gap: spacing[4],
    ...theme.shadow,
  },
  messageBlock: {
    gap: spacing[2],
  },
  errorTitle: {
    fontSize: typeScale.headline.fontSize,
    fontWeight: typeScale.headline.fontWeight,
    color: theme.textPrimary,
  },
  errorBody: {
    fontSize: typeScale.body.fontSize,
    color: theme.textSecondary,
  },
  computingText: {
    fontSize: typeScale.body.fontSize,
    color: theme.textSecondary,
    paddingVertical: spacing[3],
  },
  summaryBlock: {
    gap: spacing[1],
  },
  duration: {
    fontSize: typeScale.title.fontSize,
    lineHeight: typeScale.title.lineHeight,
    fontWeight: typeScale.title.fontWeight,
    color: theme.textPrimary,
  },
  subtitle: {
    fontSize: typeScale.body.fontSize,
    color: theme.textSecondary,
    marginBottom: spacing[2],
  },
  buttonRow: {
    flexDirection: "row",
    gap: spacing[3],
  },
  flexButton: {
    flex: 1,
  },
  startButton: {
    flex: 2,
  },
});
