'use client';

/* eslint-disable import-x/no-named-as-default-member */
import mapboxgl from 'mapbox-gl';
import * as React from 'react';
import 'mapbox-gl/dist/mapbox-gl.css';
import { ComponentErrorBoundary } from '@/components/error-boundary';
import { formatCompact } from '@/lib/currency';
import type { ListingDetail } from '@/lib/listings-api';
import { useMapboxStyle } from '@/lib/mapbox-style';

interface ListingMapProps {
  listings: ListingDetail[];
  onMarkerClick?: (listing: ListingDetail) => void;
  selectedListingId?: string;
  className?: string;
}

interface MapMarker {
  listing: ListingDetail;
  lat: number;
  lng: number;
}

const CITY_COORDS: Record<string, [number, number]> = {
  'Hồ Chí Minh': [10.8231, 106.6297],
  'Hà Nội': [21.0285, 105.8542],
  'Đà Nẵng': [16.0544, 108.2022],
  'Nha Trang': [12.2388, 109.1967],
  'Cần Thơ': [10.0452, 105.7469],
};

const DEFAULT_CENTER: [number, number] = [106.6297, 10.8231]; // HCMC [lng, lat]
const DEFAULT_ZOOM = 12;
const CLUSTER_SOURCE_ID = 'listings-cluster-source';
const CLUSTER_LAYER_ID = 'listings-cluster-circles';
const CLUSTER_COUNT_LAYER_ID = 'listings-cluster-count';
const UNCLUSTERED_LAYER_ID = 'listings-unclustered';
const UNCLUSTERED_SELECTED_LAYER_ID = 'listings-unclustered-selected';

function getMarkerCoords(listing: ListingDetail, index: number): { lat: number; lng: number } {
  if (listing.property.latitude != null && listing.property.longitude != null) {
    return { lat: listing.property.latitude, lng: listing.property.longitude };
  }
  const base = CITY_COORDS[listing.property.city] || [10.8231, 106.6297];
  const seed = listing.id.charCodeAt(0) + index;
  return {
    lat: base[0] + ((seed % 100) - 50) * 0.001,
    lng: base[1] + ((seed % 73) - 36) * 0.001,
  };
}

function buildGeoJSON(
  markers: MapMarker[],
  selectedListingId?: string,
): GeoJSON.FeatureCollection<GeoJSON.Point> {
  return {
    type: 'FeatureCollection',
    features: markers.map(({ listing, lat, lng }) => ({
      type: 'Feature',
      geometry: { type: 'Point', coordinates: [lng, lat] },
      properties: {
        id: listing.id,
        price: formatCompact(listing.priceVND),
        title: listing.property.title,
        district: listing.property.district ?? '',
        city: listing.property.city ?? '',
        areaM2: listing.property.areaM2,
        bedrooms: listing.property.bedrooms ?? null,
        bathrooms: listing.property.bathrooms ?? null,
        imageUrl: listing.property.media?.[0]?.url ?? null,
        selected: listing.id === selectedListingId ? 1 : 0,
      },
    })),
  };
}

function buildPopupContent(listing: ListingDetail): HTMLDivElement {
  const container = document.createElement('div');
  container.setAttribute('role', 'dialog');
  container.setAttribute('aria-label', `Chi tiết: ${listing.property.title}`);
  container.style.cssText =
    'font-family:system-ui,sans-serif;background:hsl(var(--card));color:hsl(var(--card-foreground));padding:8px;border-radius:6px;';

  if ((listing.property.media?.length ?? 0) > 0) {
    const img = document.createElement('img');
    img.src = listing.property.media![0]!.url;
    img.alt = listing.property.title;
    img.style.cssText =
      'width:100%;height:96px;object-fit:cover;border-radius:6px;margin-bottom:8px;';
    container.appendChild(img);
  }

  const price = document.createElement('p');
  price.style.cssText =
    'font-weight:700;color:hsl(var(--primary));font-size:14px;margin:0 0 4px;';
  price.textContent = `${formatCompact(listing.priceVND)} VND`;
  container.appendChild(price);

  const title = document.createElement('p');
  title.style.cssText =
    'font-size:13px;font-weight:500;margin:0 0 2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
  title.textContent = listing.property.title;
  container.appendChild(title);

  const location = document.createElement('p');
  location.style.cssText = 'font-size:12px;color:hsl(var(--muted-foreground));margin:0 0 8px;';
  location.textContent = `${listing.property.district}, ${listing.property.city}`;
  container.appendChild(location);

  const details = document.createElement('div');
  details.style.cssText = 'display:flex;gap:4px;font-size:11px;margin-bottom:8px;';
  const tagStyle =
    'background:hsl(var(--secondary));color:hsl(var(--secondary-foreground));padding:2px 6px;border-radius:4px;';

  const areaTag = document.createElement('span');
  areaTag.style.cssText = tagStyle;
  areaTag.textContent = `${listing.property.areaM2} m²`;
  details.appendChild(areaTag);

  if (listing.property.bedrooms != null) {
    const bedTag = document.createElement('span');
    bedTag.style.cssText = tagStyle;
    bedTag.textContent = `${listing.property.bedrooms} PN`;
    details.appendChild(bedTag);
  }
  if (listing.property.bathrooms != null) {
    const bathTag = document.createElement('span');
    bathTag.style.cssText = tagStyle;
    bathTag.textContent = `${listing.property.bathrooms} WC`;
    details.appendChild(bathTag);
  }
  container.appendChild(details);

  const link = document.createElement('a');
  link.href = `/listings/${listing.id}`;
  link.style.cssText =
    'display:block;text-align:center;font-size:12px;font-weight:500;color:hsl(var(--primary));text-decoration:none;';
  link.textContent = 'Xem chi tiết →';
  container.appendChild(link);

  return container;
}

export function ListingMap(props: ListingMapProps) {
  return (
    <ComponentErrorBoundary label="bản đồ">
      <ListingMapInner {...props} />
    </ComponentErrorBoundary>
  );
}

function ListingMapInner({
  listings,
  onMarkerClick,
  selectedListingId,
  className,
}: ListingMapProps) {
  const mapContainerRef = React.useRef<HTMLDivElement>(null);
  const mapRef = React.useRef<mapboxgl.Map | null>(null);
  const popupRef = React.useRef<mapboxgl.Popup | null>(null);
  const layersReadyRef = React.useRef(false);
  const mapStyle = useMapboxStyle();

  // Stable ref so click handlers never form a closure on stale callbacks
  const onMarkerClickRef = React.useRef(onMarkerClick);
  React.useEffect(() => {
    onMarkerClickRef.current = onMarkerClick;
  });

  const markers: MapMarker[] = React.useMemo(
    () => listings.map((listing, index) => ({ listing, ...getMarkerCoords(listing, index) })),
    [listings],
  );

  // Build GeoJSON without depending on selectedListingId so the source update
  // path (for selection highlights) stays separate from the full data reload.
  const geojson = React.useMemo(() => buildGeoJSON(markers), [markers]);

  // Helper: add or replace the cluster + unclustered layers on a loaded map
  const addLayers = React.useCallback((map: mapboxgl.Map) => {
    // Remove if they already exist (e.g. after a style reload)
    for (const id of [
      UNCLUSTERED_SELECTED_LAYER_ID,
      UNCLUSTERED_LAYER_ID,
      CLUSTER_COUNT_LAYER_ID,
      CLUSTER_LAYER_ID,
    ]) {
      if (map.getLayer(id)) map.removeLayer(id);
    }
    if (map.getSource(CLUSTER_SOURCE_ID)) map.removeSource(CLUSTER_SOURCE_ID);

    map.addSource(CLUSTER_SOURCE_ID, {
      type: 'geojson',
      data: { type: 'FeatureCollection', features: [] },
      cluster: true,
      clusterMaxZoom: 14,
      clusterRadius: 50,
    });

    // Cluster circles
    map.addLayer({
      id: CLUSTER_LAYER_ID,
      type: 'circle',
      source: CLUSTER_SOURCE_ID,
      filter: ['has', 'point_count'],
      paint: {
        'circle-color': [
          'step',
          ['get', 'point_count'],
          'hsl(var(--primary))',
          10,
          '#f1a928',
          30,
          '#e5633a',
        ],
        'circle-radius': ['step', ['get', 'point_count'], 20, 10, 28, 30, 36],
        'circle-opacity': 0.85,
      },
    });

    // Cluster counts
    map.addLayer({
      id: CLUSTER_COUNT_LAYER_ID,
      type: 'symbol',
      source: CLUSTER_SOURCE_ID,
      filter: ['has', 'point_count'],
      layout: {
        'text-field': ['get', 'point_count_abbreviated'],
        'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
        'text-size': 13,
      },
      paint: { 'text-color': '#ffffff' },
    });

    // Unclustered — normal markers
    map.addLayer({
      id: UNCLUSTERED_LAYER_ID,
      type: 'symbol',
      source: CLUSTER_SOURCE_ID,
      filter: ['all', ['!', ['has', 'point_count']], ['==', ['get', 'selected'], 0]],
      layout: {
        'text-field': ['get', 'price'],
        'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
        'text-size': 12,
        'text-anchor': 'center',
        'icon-allow-overlap': true,
        'text-allow-overlap': true,
      },
      paint: {
        'text-color': 'hsl(var(--card-foreground))',
        'text-halo-color': 'hsl(var(--card))',
        'text-halo-width': 8,
      },
    });

    // Unclustered — selected marker (on top, different style)
    map.addLayer({
      id: UNCLUSTERED_SELECTED_LAYER_ID,
      type: 'symbol',
      source: CLUSTER_SOURCE_ID,
      filter: ['all', ['!', ['has', 'point_count']], ['==', ['get', 'selected'], 1]],
      layout: {
        'text-field': ['get', 'price'],
        'text-font': ['DIN Offc Pro Bold', 'Arial Unicode MS Bold'],
        'text-size': 13,
        'text-anchor': 'center',
        'icon-allow-overlap': true,
        'text-allow-overlap': true,
      },
      paint: {
        'text-color': 'hsl(var(--primary-foreground))',
        'text-halo-color': 'hsl(var(--primary))',
        'text-halo-width': 10,
      },
    });

    layersReadyRef.current = true;
  }, []);

  // Initialize map once
  React.useEffect(() => {
    if (!mapContainerRef.current) return;

    const token = process.env['NEXT_PUBLIC_MAPBOX_TOKEN'];
    if (!token) {
      console.warn('NEXT_PUBLIC_MAPBOX_TOKEN is not set. Map will not render.');
      return;
    }

    mapboxgl.accessToken = token;

    const map = new mapboxgl.Map({
      container: mapContainerRef.current,
      style: mapStyle,
      center: DEFAULT_CENTER,
      zoom: DEFAULT_ZOOM,
      attributionControl: false,
    });

    map.addControl(new mapboxgl.NavigationControl(), 'top-right');
    map.addControl(new mapboxgl.AttributionControl({ compact: true }), 'bottom-right');

    map.once('load', () => {
      const container = mapContainerRef.current;
      if (!container) return;
      const zoomIn = container.querySelector('.mapboxgl-ctrl-zoom-in') as HTMLButtonElement | null;
      const zoomOut = container.querySelector(
        '.mapboxgl-ctrl-zoom-out',
      ) as HTMLButtonElement | null;
      const compass = container.querySelector(
        '.mapboxgl-ctrl-compass',
      ) as HTMLButtonElement | null;
      if (zoomIn) zoomIn.setAttribute('aria-label', 'Phóng to');
      if (zoomOut) zoomOut.setAttribute('aria-label', 'Thu nhỏ');
      if (compass) compass.setAttribute('aria-label', 'Đặt lại hướng bắc');

      addLayers(map);
    });

    // Re-add layers after style hot-swap
    map.on('style.load', () => {
      if (mapRef.current) addLayers(mapRef.current);
    });

    // --- Click: cluster → zoom in ---
    map.on('click', CLUSTER_LAYER_ID, (e) => {
      const features = map.queryRenderedFeatures(e.point, { layers: [CLUSTER_LAYER_ID] });
      if (!features.length) return;
      const feature = features[0]!;
      const source = map.getSource(CLUSTER_SOURCE_ID) as mapboxgl.GeoJSONSource;
      source.getClusterExpansionZoom(
        (feature.properties as { cluster_id: number }).cluster_id,
        (err: Error | null | undefined, zoom: number | null | undefined) => {
          if (err || zoom == null) return;
          const geom = feature.geometry as GeoJSON.Point;
          map.easeTo({ center: geom.coordinates as [number, number], zoom });
        },
      );
    });

    // --- Click: unclustered marker → show popup ---
    const handleUnclusteredClick = (
      e: mapboxgl.MapLayerMouseEvent & { features?: mapboxgl.MapboxGeoJSONFeature[] },
    ) => {
      if (!e.features?.length) return;
      const props = e.features[0]!.properties as {
        id: string;
        price: string;
        title: string;
        district: string;
        city: string;
        areaM2: number;
        bedrooms: number | null;
        bathrooms: number | null;
        imageUrl: string | null;
      };
      const geom = e.features[0]!.geometry as GeoJSON.Point;
      const [lng, lat] = geom.coordinates as [number, number];

      // Find full listing for popup
      const listing = listings.find((l) => l.id === props.id);
      if (!listing) return;

      onMarkerClickRef.current?.(listing);

      popupRef.current?.remove();
      const popup = new mapboxgl.Popup({
        offset: 25,
        maxWidth: 'min(260px, 85vw)',
        closeButton: true,
      })
        .setLngLat([lng, lat])
        .setDOMContent(buildPopupContent(listing))
        .addTo(map);
      popupRef.current = popup;
    };

    map.on('click', UNCLUSTERED_LAYER_ID, handleUnclusteredClick);
    map.on('click', UNCLUSTERED_SELECTED_LAYER_ID, handleUnclusteredClick);

    // Cursor changes
    for (const layer of [CLUSTER_LAYER_ID, UNCLUSTERED_LAYER_ID, UNCLUSTERED_SELECTED_LAYER_ID]) {
      map.on('mouseenter', layer, () => {
        map.getCanvas().style.cursor = 'pointer';
      });
      map.on('mouseleave', layer, () => {
        map.getCanvas().style.cursor = '';
      });
    }

    mapRef.current = map;

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

  // Sync style changes with theme (without reinitializing map)
  React.useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    layersReadyRef.current = false;
    map.setStyle(mapStyle);
    // layers are re-added via the 'style.load' listener above
  }, [mapStyle]);

  // Push GeoJSON data update when listings array identity changes.
  // This fires on full filter changes but NOT on selectedListingId-only changes,
  // which avoids full marker teardown for selection highlighting.
  React.useEffect(() => {
    const map = mapRef.current;
    if (!map || !layersReadyRef.current) return;
    const source = map.getSource(CLUSTER_SOURCE_ID) as mapboxgl.GeoJSONSource | undefined;
    if (!source) return;
    source.setData(geojson);

    // Only fit bounds when listings actually changed (not on mount w/ 0 markers)
    if (markers.length > 1) {
      const bounds = new mapboxgl.LngLatBounds();
      markers.forEach(({ lat, lng }) => bounds.extend([lng, lat]));
      map.fitBounds(bounds, { padding: 60, maxZoom: 15, duration: 400 });
    } else if (markers.length === 1) {
      map.flyTo({ center: [markers[0]!.lng, markers[0]!.lat], zoom: 14, duration: 400 });
    }
  }, [geojson, markers]);

  // ── Selection highlight: update only the `selected` property on each feature.
  // No source reload needed — setData is cheap for 1-property diff but we use
  // a filter-based approach instead: simply regenerate GeoJSON with updated
  // `selected` flags. Because `geojson` memo doesn't include selectedListingId,
  // we build a tiny delta update here.
  React.useEffect(() => {
    const map = mapRef.current;
    if (!map || !layersReadyRef.current) return;
    const source = map.getSource(CLUSTER_SOURCE_ID) as mapboxgl.GeoJSONSource | undefined;
    if (!source) return;

    // Rebuild GeoJSON with updated selection flags (no viewport/bounds change)
    source.setData(buildGeoJSON(markers, selectedListingId));
  }, [selectedListingId, markers]);

  const hasToken = typeof process !== 'undefined' && process.env['NEXT_PUBLIC_MAPBOX_TOKEN'];

  return (
    <div
      role="region"
      aria-label="Bản đồ bất động sản"
      className={`relative overflow-hidden rounded-lg border ${className || 'h-[300px] md:h-[500px]'}`}
    >
      <div ref={mapContainerRef} className="h-full w-full" />

      {/* Fallback when no Mapbox token */}
      {!hasToken && (
        <div className="absolute inset-0 flex items-center justify-center bg-gradient-to-b from-blue-50 to-green-50">
          <div className="text-center">
            <svg
              className="mx-auto mb-2 h-10 w-10 text-muted-foreground"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={1.5}
                d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"
              />
            </svg>
            <p className="text-sm text-muted-foreground">
              Thiết lập NEXT_PUBLIC_MAPBOX_TOKEN để hiển thị bản đồ
            </p>
          </div>
        </div>
      )}

      {/* Listing count overlay */}
      <div
        aria-live="polite"
        aria-atomic="true"
        className="absolute bottom-3 left-3 rounded bg-card/90 px-2 py-1 text-xs text-card-foreground shadow"
      >
        {markers.length} bất động sản trên bản đồ
      </div>

      {/* Empty state */}
      {markers.length === 0 && hasToken && (
        <div role="status" className="absolute inset-0 flex items-center justify-center bg-card/60">
          <p className="text-muted-foreground">Không có bất động sản để hiển thị trên bản đồ</p>
        </div>
      )}

      <style jsx global>{`
        .mapboxgl-popup-content {
          border-radius: 8px !important;
          padding: 12px !important;
          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
        }
      `}</style>
    </div>
  );
}
