import { useCallback, useRef, useEffect, useState, useMemo } from 'react';
import { Map as MapGL } from 'react-map-gl/maplibre';
import DeckGL from '@deck.gl/react';
import { H3HexagonLayer } from '@deck.gl/geo-layers';
import { IconLayer } from '@deck.gl/layers';
import type { PickingInfo } from '@deck.gl/core';
import 'maplibre-gl/dist/maplibre-gl.css';
import type { HexagonData, ViewState, ViewChangeParams, Bounds, POI, ColorMode } from '../types';

interface MapProps {
  data: HexagonData[];
  pois: POI[];
  onViewChange: (params: ViewChangeParams) => void;
  colorMode: ColorMode;
}

// Twemoji CDN base URL
const TWEMOJI_BASE = 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/';

// Map category to Twemoji codepoint (emoji unicode -> hex)
const POI_EMOJI_CODES: Record<string, string> = {
  // Schools
  elementary_school: '1f3eb', // 🏫
  school: '1f3eb',
  high_school: '1f393', // 🎓
  preschool: '1f476', // 👶
  college_university: '1f393',
  private_school: '1f3eb',
  // Healthcare
  doctor: '1f3e5', // 🏥
  dentist: '1f9b7', // 🦷
  pharmacy: '1f48a', // 💊
  hospital: '1f3e5',
  public_health_clinic: '1f3e5',
  // Transport
  train_station: '1f689', // 🚉
  bus_station: '1f68c', // 🚌
  metro_station: '1f687', // 🚇
  light_rail_and_subway_stations: '1f687',
  // Parks
  park: '1f333', // 🌳
  national_park: '1f3de', // 🏞
  dog_park: '1f415', // 🐕
  // Emergency
  police_department: '1f694', // 🚔
  fire_department: '1f692', // 🚒
  // Supermarkets
  supermarket: '1f6d2', // 🛒
  grocery_store: '1f6d2',
  convenience_store: '1f3ea', // 🏪
};

function getPOIIconUrl(category: string): string {
  const code = POI_EMOJI_CODES[category] || '1f4cd'; // 📍 default
  return `${TWEMOJI_BASE}${code}.png`;
}

// Tooltip emojis (these render fine in HTML)
const TOOLTIP_EMOJIS: Record<string, string> = {
  elementary_school: '🏫',
  school: '🏫',
  high_school: '🎓',
  preschool: '👶',
  college_university: '🎓',
  private_school: '🏫',
  doctor: '👨‍⚕️',
  dentist: '🦷',
  pharmacy: '💊',
  hospital: '🏥',
  public_health_clinic: '🏥',
  train_station: '🚉',
  bus_station: '🚌',
  metro_station: '🚇',
  light_rail_and_subway_stations: '🚇',
  park: '🌳',
  national_park: '🏞️',
  dog_park: '🐕',
  police_department: '🚔',
  fire_department: '🚒',
  supermarket: '🛒',
  grocery_store: '🛒',
  convenience_store: '🏪',
};

function getTooltipEmoji(category: string): string {
  return TOOLTIP_EMOJIS[category] || '📍';
}

const INITIAL_VIEW: ViewState = {
  longitude: -1.5,
  latitude: 53.5,
  zoom: 6,
  pitch: 0,
};

const MAP_STYLE = 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';

interface ColorStop {
  price: number;
  color: [number, number, number];
}

// Continuous color scale from green (low) -> yellow -> red -> purple (high)
const COLOR_SCALE: ColorStop[] = [
  { price: 0, color: [46, 204, 113] }, // Green
  { price: 200000, color: [241, 196, 15] }, // Yellow
  { price: 400000, color: [231, 76, 60] }, // Red
  { price: 800000, color: [142, 68, 173] }, // Purple
];

function interpolateColor(
  c1: [number, number, number],
  c2: [number, number, number],
  t: number
): [number, number, number] {
  return [
    Math.round(c1[0] + (c2[0] - c1[0]) * t),
    Math.round(c1[1] + (c2[1] - c1[1]) * t),
    Math.round(c1[2] + (c2[2] - c1[2]) * t),
  ];
}

function scaleToColor(
  value: number | null | undefined,
  scale: ColorStop[]
): [number, number, number] {
  if (value == null || isNaN(value)) return [128, 128, 128];

  if (value <= scale[0].price) return scale[0].color;
  if (value >= scale[scale.length - 1].price) return scale[scale.length - 1].color;

  for (let i = 0; i < scale.length - 1; i++) {
    const lower = scale[i];
    const upper = scale[i + 1];
    if (value >= lower.price && value <= upper.price) {
      const t = (value - lower.price) / (upper.price - lower.price);
      return interpolateColor(lower.color, upper.color, t);
    }
  }

  return scale[scale.length - 1].color;
}

function priceToColor(price: number | null | undefined): [number, number, number] {
  return scaleToColor(price, COLOR_SCALE);
}

// Journey time color scale: green (short) -> yellow -> orange -> red (long)
const JOURNEY_COLOR_SCALE: ColorStop[] = [
  { price: 0, color: [46, 204, 113] }, // Green
  { price: 30, color: [241, 196, 15] }, // Yellow
  { price: 60, color: [231, 76, 60] }, // Red
  { price: 120, color: [142, 68, 173] }, // Purple
];

function journeyTimeToColor(minutes: number | null | undefined): [number, number, number] {
  return scaleToColor(minutes, JOURNEY_COLOR_SCALE);
}

function zoomToResolution(zoom: number): number {
  if (zoom < 8.5) return 7;
  if (zoom < 9.5) return 8;
  if (zoom < 11) return 9;
  if (zoom < 13) return 10;
  return 11;
}

function getBoundsFromViewState(viewState: ViewState, width: number, height: number): Bounds {
  const { longitude, latitude, zoom } = viewState;

  // Clamp latitude to valid Mercator range to avoid math errors
  const clampedLat = Math.max(-85, Math.min(85, latitude));

  // Web Mercator projection math
  const TILE_SIZE = 256;
  const scale = Math.pow(2, zoom);
  const worldSize = TILE_SIZE * scale;

  // Longitude is linear
  const degreesPerPixelLng = 360 / worldSize;
  const halfWidthDeg = (width / 2) * degreesPerPixelLng;

  // Latitude uses Mercator projection (non-linear)
  // Convert center lat to pixel y, offset by half height, convert back to lat
  const latRad = (clampedLat * Math.PI) / 180;
  const mercatorY = (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2;
  const centerPixelY = mercatorY * worldSize;

  const topPixelY = centerPixelY - height / 2;
  const bottomPixelY = centerPixelY + height / 2;

  // Convert pixel Y back to latitude
  const pixelYToLat = (pixelY: number): number => {
    const mercY = Math.max(0.001, Math.min(0.999, pixelY / worldSize)); // Clamp to avoid edge cases
    const latRadians = Math.atan(Math.sinh(Math.PI * (1 - 2 * mercY)));
    return (latRadians * 180) / Math.PI;
  };

  const north = Math.min(85, pixelYToLat(topPixelY));
  const south = Math.max(-85, pixelYToLat(bottomPixelY));
  const west = Math.max(-180, longitude - halfWidthDeg);
  const east = Math.min(180, longitude + halfWidthDeg);

  return { south, west, north, east };
}

interface Dimensions {
  width: number;
  height: number;
}

export default function Map({ data, pois, onViewChange, colorMode }: MapProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [viewState, setViewState] = useState<ViewState>(INITIAL_VIEW);
  const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 });

  // Track container dimensions with ResizeObserver
  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const observer = new ResizeObserver((entries) => {
      const { width, height } = entries[0].contentRect;
      if (width > 0 && height > 0) {
        setDimensions({ width, height });
      }
    });

    observer.observe(container);
    return () => observer.disconnect();
  }, []);

  // Notify parent when view or dimensions change
  useEffect(() => {
    if (dimensions.width === 0 || dimensions.height === 0) return;

    const bounds = getBoundsFromViewState(viewState, dimensions.width, dimensions.height);
    const resolution = zoomToResolution(viewState.zoom);

    onViewChange({ resolution, bounds, zoom: viewState.zoom });
  }, [viewState, dimensions, onViewChange]);

  const handleViewStateChange = useCallback((params: { viewState: unknown }) => {
    const newViewState = params.viewState as ViewState;
    setViewState(newViewState);
  }, []);

  // Popup state for POI hover (using screen coordinates)
  const [popupInfo, setPopupInfo] = useState<{
    x: number;
    y: number;
    name: string;
    category: string;
  } | null>(null);

  const handlePoiHover = useCallback((info: PickingInfo<POI>) => {
    if (info.object && info.x !== undefined && info.y !== undefined) {
      setPopupInfo({
        x: info.x,
        y: info.y,
        name: info.object.name,
        category: info.object.category,
      });
    } else {
      setPopupInfo(null);
    }
  }, []);

  const layers = useMemo(
    () => [
      new H3HexagonLayer<HexagonData>({
        id: 'h3-hexagons',
        data,
        getHexagon: (d) => d.h3,
        getFillColor: (d) =>
          colorMode === 'journey_time'
            ? journeyTimeToColor(d.median_journey_minutes)
            : priceToColor(d.avg_price),
        updateTriggers: {
          getFillColor: colorMode,
        },
        extruded: false,
        pickable: true,
        opacity: 0.5,
        highPrecision: true,
      }),
      new IconLayer<POI>({
        id: 'poi-icons',
        data: pois,
        getPosition: (d) => [d.lng, d.lat],
        getIcon: (d) => ({
          url: getPOIIconUrl(d.category),
          width: 72,
          height: 72,
        }),
        getSize: 24,
        sizeMinPixels: 20,
        sizeMaxPixels: 40,
        pickable: true,
        onHover: handlePoiHover,
      }),
    ],
    [data, pois, handlePoiHover, colorMode]
  );

  // Tooltip for hexagons only (POIs use MapLibre popup)
  const getTooltip = useCallback(({ object }: { object?: HexagonData }) => {
    if (!object || !('h3' in object)) return null;

    const hex = object as HexagonData;
    const journeyLines: string[] = [];
    if (hex.median_pt_quick_minutes != null)
      journeyLines.push(`🚇 Quick PT: ${hex.median_pt_quick_minutes} min`);
    if (hex.median_pt_easy_minutes != null)
      journeyLines.push(`🚌 Easy PT: ${hex.median_pt_easy_minutes} min`);
    if (hex.median_cycling_minutes != null)
      journeyLines.push(`🚲 Cycling: ${hex.median_cycling_minutes} min`);
    const journeyTimeHtml =
      journeyLines.length > 0
        ? `<div style="color: #0066cc; margin-top: 4px; font-size: 12px;">${journeyLines.join('<br/>')}</div>`
        : '';

    return {
      html: `<div style="padding: 8px; font-size: 14px;">
        <strong>Avg: £${hex.avg_price?.toLocaleString() || 'N/A'}</strong>
        <div style="color: #666; font-size: 12px;">
          ${hex.count} sales<br/>
          Range: £${hex.min_price?.toLocaleString()} - £${hex.max_price?.toLocaleString()}
        </div>
        ${journeyTimeHtml}
      </div>`,
      style: {
        backgroundColor: 'white',
        borderRadius: '4px',
        boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
      },
    };
  }, []);

  return (
    <div className="flex-1 h-full relative" ref={containerRef}>
      <DeckGL
        viewState={viewState}
        controller
        layers={layers}
        onViewStateChange={handleViewStateChange as never}
        getTooltip={getTooltip as never}
      >
        <MapGL mapStyle={MAP_STYLE} />
      </DeckGL>
      {popupInfo && (
        <div
          className="absolute pointer-events-none bg-white rounded shadow-lg p-2 text-sm"
          style={{
            left: popupInfo.x,
            top: popupInfo.y - 40,
            transform: 'translateX(-50%)',
            zIndex: 9999,
          }}
        >
          <strong>
            {getTooltipEmoji(popupInfo.category)} {popupInfo.name}
          </strong>
          <div className="text-gray-500 text-xs">{popupInfo.category.replace(/_/g, ' ')}</div>
        </div>
      )}
    </div>
  );
}
