import { useEffect, useRef, useMemo } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import type { UatStats, UatMetric, ChoroplethMetric } from './types';
import { formatRON, formatCount } from './utils';

interface Props {
  choroplethData: UatStats[];
  metricData?: UatMetric[];
  metric?: ChoroplethMetric;
  onUatSelect: (siruta: string | null) => void;
  selectedUat: string | null;
  onMapReady?: (map: maplibregl.Map) => void;
  flyToSiruta?: string | null;
}

const ROMANIA_CENTER: [number, number] = [25.0, 45.9];

const TILE_SOURCES = [
  { id: 'uats-z0', table: 'gis_uats_z0', layer: 'gis_uats_z0', minzoom: 0, maxzoom: 5 },
  { id: 'uats-z5', table: 'gis_uats_z5', layer: 'gis_uats_z5', minzoom: 5, maxzoom: 8 },
  { id: 'uats-z8', table: 'gis_uats_z8', layer: 'gis_uats_z8', minzoom: 8, maxzoom: 12 },
  { id: 'uats-z12', table: 'gis_uats_z12', layer: 'gis_uats_z12', minzoom: 12, maxzoom: 16 },
] as const;

const FILL_LAYERS = TILE_SOURCES.map((s) => `${s.id}-fill`);

function mvtUrl(table: string): string {
  const origin = typeof window !== 'undefined' ? window.location.origin : '';
  return `${origin}/api/harta/mvt/${table}/{z}/{x}_{y}`;
}

// Choropleth color ramp scales — different breakpoints per metric.
// All ramps share the same 8-stop slate→primary→deep-navy progression for value-like metrics,
// switch to amber→red for risk-like metrics.
const RAMP_BLUE = ['#EEF2F7', '#DBE5F1', '#B5CCE6', '#7AA8D8', '#3F7FC4', '#1F58A6', '#0F3473', '#0A2150'];
const RAMP_RISK = ['#EEF2F7', '#FEF3C7', '#FDE68A', '#FCD34D', '#F59E0B', '#D97706', '#B45309', '#7C2D12'];

interface RampStop { stop: number; color: string }

function rampForMetric(metric: ChoroplethMetric, stats: { p50: number; p90: number; p99: number }): RampStop[] {
  // Stops are spaced perceptually using 8 colors; we anchor low/mid/high to data percentiles.
  const colors = (metric === 'value') ? RAMP_BLUE : RAMP_RISK;
  switch (metric) {
    case 'value': {
      // Logarithmic anchors for skewed RON distribution
      return [
        { stop: 0,           color: colors[0] },
        { stop: 1,           color: colors[1] },
        { stop: 50_000,      color: colors[2] },
        { stop: 250_000,     color: colors[3] },
        { stop: 1_000_000,   color: colors[4] },
        { stop: 5_000_000,   color: colors[5] },
        { stop: 25_000_000,  color: colors[6] },
        { stop: 100_000_000, color: colors[7] },
      ];
    }
    case 'direct':   // 0..1
    case 'topshare': // 0..1
      return [
        { stop: 0,    color: colors[0] },
        { stop: 0.10, color: colors[1] },
        { stop: 0.20, color: colors[2] },
        { stop: 0.30, color: colors[3] },
        { stop: 0.40, color: colors[4] },
        { stop: 0.55, color: colors[5] },
        { stop: 0.70, color: colors[6] },
        { stop: 0.90, color: colors[7] },
      ];
    case 'hhi':      // 0..10000
      return [
        { stop: 0,    color: colors[0] },
        { stop: 800,  color: colors[1] },
        { stop: 1500, color: colors[2] },
        { stop: 2500, color: colors[3] },
        { stop: 4000, color: colors[4] },
        { stop: 6000, color: colors[5] },
        { stop: 8000, color: colors[6] },
        { stop: 9500, color: colors[7] },
      ];
    case 'q4spike':  // ratio
      return [
        { stop: 0,    color: colors[0] },
        { stop: 0.5,  color: colors[1] },
        { stop: 1.0,  color: colors[2] },
        { stop: 1.3,  color: colors[3] },
        { stop: 1.7,  color: colors[4] },
        { stop: 2.5,  color: colors[5] },
        { stop: 4.0,  color: colors[6] },
        { stop: 8.0,  color: colors[7] },
      ];
    default:
      return [{ stop: 0, color: '#E5E9F0' }];
  }
}

export function getRampStops(metric: ChoroplethMetric): RampStop[] {
  return rampForMetric(metric, { p50: 0, p90: 0, p99: 0 });
}

function buildColorExpressionGeneric(
  entries: { siruta: string; value: number }[],
  ramp: RampStop[],
): maplibregl.ExpressionSpecification {
  if (entries.length === 0) {
    return ramp[0].color as any;
  }
  const matchEntries: (string | number)[] = [];
  for (const e of entries) {
    matchEntries.push(e.siruta, e.value);
  }
  const expr: any[] = [
    'interpolate',
    ['linear'],
    ['match', ['get', 'siruta'], ...matchEntries, 0],
  ];
  for (const r of ramp) {
    expr.push(r.stop, r.color);
  }
  return expr as any;
}

export function MapView({ choroplethData, metricData, metric = 'value', onUatSelect, selectedUat, onMapReady, flyToSiruta }: Props) {
  const mapContainer = useRef<HTMLDivElement>(null);
  const mapRef = useRef<maplibregl.Map | null>(null);
  const popupRef = useRef<maplibregl.Popup | null>(null);
  const hoveredRef = useRef<{ source: string; sourceLayer: string; id: string | number } | null>(null);

  const valueLookup = useMemo(() => {
    const lookup = new Map<string, UatStats>();
    for (const item of choroplethData) lookup.set(item.siruta, item);
    return lookup;
  }, [choroplethData]);

  const metricLookup = useMemo(() => {
    const lookup = new Map<string, UatMetric>();
    if (metricData) for (const item of metricData) lookup.set(item.siruta, item);
    return lookup;
  }, [metricData]);

  const colorExpr = useMemo(() => {
    const ramp = rampForMetric(metric, { p50: 0, p90: 0, p99: 0 });
    if (metric === 'value' || !metricData || metricData.length === 0) {
      // Fall back to legacy value-based mapping using choroplethData
      return buildColorExpressionGeneric(
        choroplethData.map((c) => ({ siruta: c.siruta, value: Number(c.total_value) || 0 })),
        ramp,
      );
    }
    return buildColorExpressionGeneric(
      metricData.map((m) => ({ siruta: m.siruta, value: Number(m.metric_value) || 0 })),
      ramp,
    );
  }, [choroplethData, metricData, metric]);

  useEffect(() => {
    const map = mapRef.current;
    if (!map?.isStyleLoaded()) return;
    for (const src of TILE_SOURCES) {
      try {
        map.setPaintProperty(`${src.id}-fill`, 'fill-color', colorExpr);
      } catch { /* layer may not exist yet */ }
    }
  }, [colorExpr]);

  // Imperative fly-to from command palette
  useEffect(() => {
    const map = mapRef.current;
    if (!map || !flyToSiruta) return;

    // Try to find feature in any rendered source
    const tryFly = () => {
      let found: any = null;
      for (const src of TILE_SOURCES) {
        const f = map.querySourceFeatures(src.id, {
          sourceLayer: src.layer,
          filter: ['==', ['get', 'siruta'], flyToSiruta],
        });
        if (f.length > 0) { found = f[0]; break; }
      }
      if (found?.geometry) {
        try {
          // Compute bbox manually
          const coords = flattenCoords(found.geometry);
          if (coords.length > 0) {
            let minLng = Infinity, minLat = Infinity, maxLng = -Infinity, maxLat = -Infinity;
            for (const [lng, lat] of coords) {
              if (lng < minLng) minLng = lng;
              if (lng > maxLng) maxLng = lng;
              if (lat < minLat) minLat = lat;
              if (lat > maxLat) maxLat = lat;
            }
            map.fitBounds([[minLng, minLat], [maxLng, maxLat]], { padding: 80, duration: 800 });
            return true;
          }
        } catch { /* */ }
      }
      // fallback: just zoom in to country center if not found at this zoom
      map.flyTo({ zoom: 9, duration: 600 });
      return false;
    };

    // Wait briefly for tiles
    setTimeout(tryFly, 200);
  }, [flyToSiruta]);

  useEffect(() => {
    if (!mapContainer.current) return;

    const map = new maplibregl.Map({
      container: mapContainer.current,
      style: {
        version: 8,
        sources: {
          osm: {
            type: 'raster',
            tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
            tileSize: 256,
            attribution: '&copy; OpenStreetMap',
            maxzoom: 19,
          },
        },
        layers: [
          {
            id: 'osm-tiles',
            type: 'raster',
            source: 'osm',
            minzoom: 0,
            maxzoom: 19,
            paint: {
              // Subtle desaturation — keeps map readable while letting choropleth dominate.
              'raster-saturation': -0.4,
              'raster-brightness-max': 0.95,
              'raster-opacity': 0.85,
            },
          },
        ],
      },
      center: ROMANIA_CENTER,
      zoom: 6.5,
      maxBounds: [[18, 42], [32, 50]],
    });

    // Force resize once visible (Astro client:only mounts after layout settles)
    requestAnimationFrame(() => map.resize());
    setTimeout(() => map.resize(), 300);

    map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-right');

    map.on('load', () => {
      for (const src of TILE_SOURCES) {
        map.addSource(src.id, {
          type: 'vector',
          tiles: [mvtUrl(src.table)],
          minzoom: src.minzoom,
          maxzoom: src.maxzoom,
          promoteId: 'siruta',
        });

        map.addLayer({
          id: `${src.id}-fill`,
          type: 'fill',
          source: src.id,
          'source-layer': src.layer,
          minzoom: src.minzoom,
          maxzoom: src.maxzoom,
          paint: {
            'fill-color': colorExpr,
            'fill-opacity': [
              'case',
              ['boolean', ['feature-state', 'hover'], false], 0.92,
              ['boolean', ['feature-state', 'selected'], false], 0.95,
              0.78,
            ],
            'fill-outline-color': 'rgba(15,23,42,0.05)',
          },
        });

        map.addLayer({
          id: `${src.id}-outline`,
          type: 'line',
          source: src.id,
          'source-layer': src.layer,
          minzoom: src.minzoom,
          maxzoom: src.maxzoom,
          paint: {
            'line-color': [
              'case',
              ['boolean', ['feature-state', 'selected'], false], '#0A2150',
              ['boolean', ['feature-state', 'hover'], false], '#0A2150',
              'rgba(71,85,105,0.55)',
            ],
            'line-width': [
              'case',
              ['boolean', ['feature-state', 'selected'], false], 2.2,
              ['boolean', ['feature-state', 'hover'], false], 1.4,
              ['interpolate', ['linear'], ['zoom'], 5, 0.25, 10, 0.7, 14, 1.2],
            ],
          },
        });
      }

      const setHover = (feature: { source: string; sourceLayer: string; id: any }, on: boolean) => {
        try {
          map.setFeatureState(
            { source: feature.source, sourceLayer: feature.sourceLayer, id: feature.id },
            { hover: on },
          );
        } catch { /* feature may not be rendered */ }
      };

      for (const layerId of FILL_LAYERS) {
        map.on('mousemove', layerId, (e) => {
          if (!e.features?.length) return;
          map.getCanvas().style.cursor = 'pointer';
          const feature = e.features[0];
          const siruta = feature.properties?.siruta;
          if (!siruta) return;

          // hover state
          const ref = {
            source: feature.source as string,
            sourceLayer: feature.sourceLayer as string,
            id: feature.id as any,
          };
          if (
            hoveredRef.current &&
            (hoveredRef.current.id !== ref.id || hoveredRef.current.source !== ref.source)
          ) {
            setHover(hoveredRef.current, false);
          }
          setHover(ref, true);
          hoveredRef.current = ref;

          const stats = valueLookup.get(siruta);
          const metricRow = metricLookup.get(siruta);
          const name = feature.properties?.name || 'N/A';
          const county = feature.properties?.county || '';

          // Format metric value depending on type
          const formatMetric = () => {
            if (!metricRow) return null;
            const v = Number(metricRow.metric_value);
            switch (metric) {
              case 'value':    return `${formatRON(v)} <span>RON</span>`;
              case 'direct':   return `${(v * 100).toFixed(0)}<span>%</span> direct`;
              case 'topshare': return `${(v * 100).toFixed(0)}<span>%</span> top furnizor`;
              case 'hhi':      return `HHI <strong>${Math.round(v)}</strong>`;
              case 'q4spike':  return `Q4 <strong>${v.toFixed(2)}×</strong>`;
              default:         return `${formatRON(v)} <span>RON</span>`;
            }
          };

          const html = stats || metricRow
            ? `
              <div class="harta-tip">
                <div class="harta-tip-head">
                  <div class="harta-tip-name">${escapeHtml(name)}</div>
                  ${county ? `<div class="harta-tip-county">${escapeHtml(county)}</div>` : ''}
                </div>
                ${metricRow ? `<div class="harta-tip-value">${formatMetric()}</div>` :
                  stats ? `<div class="harta-tip-value">${formatRON(Number(stats.total_value))} <span>RON</span></div>` : ''}
                <div class="harta-tip-meta">
                  <span>${formatCount(Number(metricRow?.total_contracts || stats?.total_contracts || 0))} contracte</span>
                  ${stats ? `<span class="harta-tip-sep">·</span><span>${formatCount(stats.da_count)} achiziții directe</span>` : ''}
                </div>
                <div class="harta-tip-cta">click pentru detalii</div>
              </div>`
            : `
              <div class="harta-tip">
                <div class="harta-tip-head">
                  <div class="harta-tip-name">${escapeHtml(name)}</div>
                  ${county ? `<div class="harta-tip-county">${escapeHtml(county)}</div>` : ''}
                </div>
                <div class="harta-tip-empty">Fără anunțuri SEAP</div>
              </div>`;

          if (!popupRef.current) {
            popupRef.current = new maplibregl.Popup({
              closeButton: false,
              closeOnClick: false,
              offset: 14,
              className: 'harta-tip-popup',
              maxWidth: '260px',
            });
          }
          popupRef.current.setLngLat(e.lngLat).setHTML(html).addTo(map);
        });

        map.on('mouseleave', layerId, () => {
          map.getCanvas().style.cursor = '';
          if (hoveredRef.current) {
            setHover(hoveredRef.current, false);
            hoveredRef.current = null;
          }
          popupRef.current?.remove();
        });

        map.on('click', layerId, (e) => {
          if (!e.features?.length) return;
          const siruta = e.features[0].properties?.siruta;
          if (siruta) onUatSelect(siruta);
        });
      }

      map.on('click', (e) => {
        const features = map.queryRenderedFeatures(e.point, { layers: FILL_LAYERS });
        if (features.length === 0) onUatSelect(null);
      });

      onMapReady?.(map);
    });

    mapRef.current = map;
    return () => {
      map.remove();
      mapRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Update selected feature state
  useEffect(() => {
    const map = mapRef.current;
    if (!map?.isStyleLoaded()) return;

    // Clear all selected states; with promoteId='siruta' the id IS the siruta string
    for (const src of TILE_SOURCES) {
      try { map.removeFeatureState({ source: src.id, sourceLayer: src.layer }); } catch { /* */ }
    }

    if (selectedUat) {
      for (const src of TILE_SOURCES) {
        try {
          map.setFeatureState(
            { source: src.id, sourceLayer: src.layer, id: selectedUat },
            { selected: true },
          );
        } catch { /* */ }
      }
    }
  }, [selectedUat]);

  return (
    <div style={{ position: 'absolute', inset: 0 }}>
      <div ref={mapContainer} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }} />
      <Legend metric={metric} />
    </div>
  );
}

const LEGEND_LABELS: Record<ChoroplethMetric, { title: string; format: (stop: number) => string }> = {
  value:    { title: 'Valoare contracte (RON)', format: (s) => s === 0 ? '0' : s >= 1_000_000 ? `${s / 1_000_000}M` : s >= 1_000 ? `${s / 1_000}K` : String(s) },
  direct:   { title: '% achiziție directă',     format: (s) => `${Math.round(s * 100)}%` },
  topshare: { title: '% top furnizor',          format: (s) => `${Math.round(s * 100)}%` },
  hhi:      { title: 'Concentrare (HHI)',       format: (s) => s >= 1000 ? `${(s / 1000).toFixed(0)}K` : String(s) },
  q4spike:  { title: 'Spike Q4 (×)',            format: (s) => `${s.toFixed(1)}×` },
};

function Legend({ metric }: { metric: ChoroplethMetric }) {
  const ramp = getRampStops(metric);
  const cfg = LEGEND_LABELS[metric] || LEGEND_LABELS.value;
  return (
    <div className="harta-legend">
      <div className="harta-legend-title">{cfg.title}</div>
      <div className="harta-legend-bar">
        {ramp.map((r) => (
          <div key={r.color} className="harta-legend-stop">
            <div className="harta-legend-swatch" style={{ background: r.color }} />
            <div className="harta-legend-label tabular-nums">{cfg.format(r.stop)}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

function escapeHtml(s: string): string {
  return s.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]!));
}

function flattenCoords(geom: any): [number, number][] {
  const out: [number, number][] = [];
  const visit = (v: any) => {
    if (!v) return;
    if (typeof v[0] === 'number' && typeof v[1] === 'number') {
      out.push([v[0], v[1]]);
      return;
    }
    if (Array.isArray(v)) v.forEach(visit);
  };
  if (geom.coordinates) visit(geom.coordinates);
  return out;
}
