"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import Supercluster from "supercluster";
import type { Restaurant } from "@/lib/api";
import Icon from "@/components/Icon";
import { getCuisineIcon } from "@/lib/cuisine-icons";
import type { MapBounds, MapViewProps } from "@/components/MapView.types";

declare global {
  interface Window {
    naver?: { maps: NaverMaps };
  }
}
type LatLng = { lat: () => number; lng: () => number };
type NaverMaps = {
  LatLng: new (lat: number, lng: number) => LatLng;
  Map: new (el: HTMLElement, opts: Record<string, unknown>) => NaverMapInstance;
  Marker: new (opts: Record<string, unknown>) => NaverMarker;
  InfoWindow: new (opts: Record<string, unknown>) => NaverInfoWindow;
  Event: { addListener: (target: unknown, type: string, fn: (...args: unknown[]) => void) => unknown; removeListener: (handler: unknown) => void };
  Size: new (w: number, h: number) => unknown;
  Point: new (x: number, y: number) => unknown;
};
type NaverMapInstance = {
  setCenter: (latlng: unknown) => void;
  setZoom: (zoom: number, useEffect?: boolean) => void;
  getZoom: () => number;
  getBounds: () => { getNE: () => LatLng; getSW: () => LatLng };
  panTo: (latlng: unknown, opts?: Record<string, unknown>) => void;
  morph: (latlng: unknown, zoom?: number, transitionOptions?: Record<string, unknown>) => void;
  refresh: (noEffect?: boolean) => void;
};
type NaverMarker = {
  setMap: (map: NaverMapInstance | null) => void;
  setIcon: (icon: Record<string, unknown>) => void;
  setPosition: (latlng: unknown) => void;
  getPosition: () => LatLng;
};
type NaverInfoWindow = {
  open: (map: NaverMapInstance, marker: NaverMarker) => void;
  close: () => void;
  setContent: (content: string) => void;
  getMap: () => NaverMapInstance | null;
};

const NAVER_CLIENT_ID = process.env.NEXT_PUBLIC_NAVER_MAP_CLIENT_ID || "";

// Channel color palette — GoogleMapView와 동일
const CHANNEL_COLORS = [
  { bg: "#fff7ed", text: "#78350f", border: "#f59e0b", arrow: "#f59e0b" }, // amber (default)
  { bg: "#eff6ff", text: "#1e3a5f", border: "#3b82f6", arrow: "#3b82f6" }, // blue
  { bg: "#f0fdf4", text: "#14532d", border: "#22c55e", arrow: "#22c55e" }, // green
  { bg: "#fdf2f8", text: "#831843", border: "#ec4899", arrow: "#ec4899" }, // pink
  { bg: "#faf5ff", text: "#581c87", border: "#a855f7", arrow: "#a855f7" }, // purple
  { bg: "#fff1f2", text: "#7f1d1d", border: "#ef4444", arrow: "#ef4444" }, // red
  { bg: "#f0fdfa", text: "#134e4a", border: "#14b8a6", arrow: "#14b8a6" }, // teal
  { bg: "#fefce8", text: "#713f12", border: "#eab308", arrow: "#eab308" }, // yellow
];

function getChannelColorMap(restaurants: Restaurant[]) {
  const channels = new Set<string>();
  restaurants.forEach((r) => r.channels?.forEach((ch) => channels.add(ch)));
  const map: Record<string, typeof CHANNEL_COLORS[0]> = {};
  let i = 0;
  for (const ch of channels) {
    map[ch] = CHANNEL_COLORS[i % CHANNEL_COLORS.length];
    i++;
  }
  return map;
}

function useNaverMaps(): { ready: boolean; error: string | null } {
  const [ready, setReady] = useState(typeof window !== "undefined" && !!window.naver?.maps);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!NAVER_CLIENT_ID) { setError("NEXT_PUBLIC_NAVER_MAP_CLIENT_ID 미설정"); return; }
    if (window.naver?.maps) { setReady(true); return; }
    const existing = document.querySelector<HTMLScriptElement>(`script[data-naver-maps]`);
    if (existing) {
      existing.addEventListener("load", () => setReady(true), { once: true });
      return;
    }
    const s = document.createElement("script");
    s.src = `https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=${NAVER_CLIENT_ID}`;
    s.async = true;
    s.dataset.naverMaps = "1";
    s.onload = () => setReady(true);
    s.onerror = () => setError("naver maps v3 스크립트 로드 실패");
    document.head.appendChild(s);
  }, []);

  return { ready, error };
}

type RestaurantProps = { restaurant: Restaurant };
type RestaurantFeature = Supercluster.PointFeature<RestaurantProps>;

function useSupercluster(restaurants: Restaurant[]) {
  const points: RestaurantFeature[] = useMemo(
    () => restaurants.map((r) => ({
      type: "Feature" as const,
      geometry: { type: "Point" as const, coordinates: [r.longitude, r.latitude] },
      properties: { restaurant: r },
    })),
    [restaurants]
  );
  const index = useMemo(() => {
    const sc = new Supercluster<{ restaurant: Restaurant }>({ radius: 60, maxZoom: 16, minPoints: 2 });
    sc.load(points);
    return sc;
  }, [points]);
  const getClusters = useCallback((bounds: MapBounds, zoom: number) =>
    index.getClusters([bounds.west, bounds.south, bounds.east, bounds.north], Math.floor(zoom))
  , [index]);
  const getExpansionZoom = useCallback((clusterId: number) => {
    try { return index.getClusterExpansionZoom(clusterId); } catch { return 17; }
  }, [index]);
  return { getClusters, getExpansionZoom };
}

function getClusterSize(count: number): number {
  if (count < 10) return 36;
  if (count < 50) return 42;
  if (count < 100) return 48;
  return 54;
}

// 단일 마커 — 식당명 박스 + 화살표 핀 (GoogleMapView와 동일 디자인)
function markerIconHtml(
  name: string,
  cuisineIcon: string,
  c: typeof CHANNEL_COLORS[0],
  opts: { isSelected: boolean; isClosed: boolean }
): string {
  const { isSelected, isClosed } = opts;
  const bg = isSelected ? "#2563eb" : isClosed ? "#f3f4f6" : c.bg;
  const text = isSelected ? "#fff" : isClosed ? "#9ca3af" : c.text;
  const border = isSelected ? "2px solid #1d4ed8" : `1.5px solid ${c.border}`;
  const shadow = isSelected ? "0 2px 8px rgba(37,99,235,0.4)" : `0 1px 4px ${c.border}40`;
  const arrowColor = isSelected ? "#1d4ed8" : c.arrow;
  const opacity = isClosed ? 0.5 : 1;
  const deco = isClosed ? "line-through" : "none";
  return `
    <div style="display:flex;flex-direction:column;align-items:center;transition:transform .2s ease;transform:scale(${isSelected ? 1.15 : 1});opacity:${opacity};">
      <div style="padding:4px 8px;background:${bg};color:${text};font-size:12px;font-weight:600;border-radius:6px;border:${border};box-shadow:${shadow};white-space:nowrap;max-width:120px;overflow:hidden;text-overflow:ellipsis;text-decoration:${deco};">
        <span class="material-symbols-rounded" style="font-size:14px;width:14px;height:14px;overflow:hidden;display:inline-flex;align-items:center;justify-content:center;margin-right:3px;vertical-align:middle;color:#E8720C;">${escapeHtml(cuisineIcon)}</span>${escapeHtml(name)}
      </div>
      <div style="width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid ${arrowColor};margin-top:-1px;"></div>
    </div>
  `;
}
// SVG data URL — 클러스터(숫자)
function clusterIconHtml(count: number, size: number): string {
  return `<div style="width:${size}px;height:${size}px;border-radius:9999px;background:rgba(245,158,11,.92);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:${size > 44 ? 14 : 12}px;border:2px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,.3);">${count}</div>`;
}

export default function NaverMapView({
  restaurants,
  selected,
  onSelectRestaurant,
  onBoundsChanged,
  flyTo,
  onMyLocation,
  activeChannel,
}: MapViewProps) {
  const channelColors = useMemo(() => getChannelColorMap(restaurants), [restaurants]);
  const { ready, error } = useNaverMaps();
  const divRef = useRef<HTMLDivElement | null>(null);
  const mapRef = useRef<NaverMapInstance | null>(null);
  const markersRef = useRef<NaverMarker[]>([]);
  const infoWindowRef = useRef<NaverInfoWindow | null>(null);
  const [bounds, setBounds] = useState<MapBounds | null>(null);
  const [zoom, setZoom] = useState(13);
  const [initError, setInitError] = useState<string | null>(null);
  const { getClusters, getExpansionZoom } = useSupercluster(restaurants);

  // 지도 1회 생성
  useEffect(() => {
    if (!ready || !divRef.current || mapRef.current) return;
    try {
      const n = window.naver!.maps;
      const initLat = flyTo?.lat ?? selected?.latitude ?? 37.5665;
      const initLng = flyTo?.lng ?? selected?.longitude ?? 126.978;
      const initZoom = flyTo?.zoom ?? 13;
      const m = new n.Map(divRef.current, {
        center: new n.LatLng(initLat, initLng),
        zoom: initZoom,
        logoControl: false,
        mapDataControl: false,
        scaleControl: false,
        zoomControl: false,
      });
      mapRef.current = m;
      infoWindowRef.current = new n.InfoWindow({
        borderWidth: 0,
        anchorSize: new n.Size(10, 10),
        pixelOffset: new n.Point(0, -8),
        backgroundColor: "transparent",
        disableAnchor: false,
      });

      const ro = new ResizeObserver(() => {
        try { m.refresh(true); } catch { /* noop */ }
      });
      ro.observe(divRef.current);

      // bounds_changed가 줌/팬 끝나는 시점에 한 번만 emit (SDK가 throttle)
      const sync = () => {
        try {
          const b = m.getBounds();
          const ne = b.getNE(), sw = b.getSW();
          const nb: MapBounds = { north: ne.lat(), south: sw.lat(), east: ne.lng(), west: sw.lng() };
          setBounds(nb);
          setZoom(m.getZoom());
          onBoundsChanged?.(nb);
        } catch (e) {
          console.warn("[NaverMap] sync failed", e);
        }
      };
      requestAnimationFrame(() => {
        try { m.refresh(true); } catch { /* noop */ }
        sync();
      });
      // idle = 줌/팬 끝났을 때 한 번 (bounds_changed보다 적게 발화 → 성능)
      n.Event.addListener(m, "idle", sync);
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e);
      console.error("[NaverMap] init failed", e);
      setInitError(msg);
    }
  }, [ready, flyTo, selected, onBoundsChanged]);

  // flyTo 변경 반영 — morph로 panTo+setZoom 충돌 회피
  useEffect(() => {
    const m = mapRef.current;
    if (!m || !flyTo || !window.naver?.maps) return;
    const latlng = new window.naver.maps.LatLng(flyTo.lat, flyTo.lng);
    if (flyTo.zoom) m.morph(latlng, flyTo.zoom);
    else m.panTo(latlng);
  }, [flyTo]);

  // selected 변경 시 자동 중앙 — 현재 줌이 16 이상이면 그대로, 미만이면 16으로 확대
  // (클러스터 expansion으로 18까지 들어간 상태에서 마커 클릭 시 다시 16으로 줄어 클러스터링되는 것 방지)
  useEffect(() => {
    const m = mapRef.current;
    if (!m || !selected || !window.naver?.maps) return;
    if (selected.latitude == null || selected.longitude == null) return;
    const latlng = new window.naver.maps.LatLng(selected.latitude, selected.longitude);
    const targetZoom = Math.max(m.getZoom(), 16);
    m.morph(latlng, targetZoom);
  }, [selected]);

  // 클러스터 계산 (bounds/zoom 변경 시)
  const clusters = useMemo(() => {
    if (!bounds) return [];
    return getClusters(bounds, zoom);
  }, [bounds, zoom, getClusters]);

  // 마커를 SDK 네이티브로 그림 — clusters 바뀌면 기존 마커 모두 제거 후 새로 생성
  useEffect(() => {
    const m = mapRef.current;
    const naver = window.naver?.maps;
    if (!m || !naver) return;

    // 기존 마커 제거
    for (const mk of markersRef.current) mk.setMap(null);
    markersRef.current = [];

    for (const feature of clusters) {
      const [lng, lat] = feature.geometry.coordinates;
      const isCluster = feature.properties && "cluster" in feature.properties && feature.properties.cluster;
      if (isCluster) {
        const { cluster_id, point_count } = feature.properties as Supercluster.ClusterProperties;
        const size = getClusterSize(point_count);
        const marker = new naver.Marker({
          position: new naver.LatLng(lat, lng),
          map: m,
          icon: {
            content: clusterIconHtml(point_count, size),
            anchor: new naver.Point(size / 2, size / 2),
          },
        });
        naver.Event.addListener(marker, "click", () => {
          const z = Math.min(getExpansionZoom(cluster_id), 18);
          m.morph(new naver.LatLng(lat, lng), z);
        });
        markersRef.current.push(marker);
      } else {
        const r = (feature.properties as RestaurantProps).restaurant;
        const chKey = activeChannel && r.channels?.includes(activeChannel) ? activeChannel : r.channels?.[0];
        const chColor = chKey ? channelColors[chKey] : CHANNEL_COLORS[0];
        const isSelected = selected?.id === r.id;
        const isClosed = r.business_status === "CLOSED_PERMANENTLY" || r.business_status === "CLOSED_TEMPORARILY";
        const cuisineIcon = getCuisineIcon(r.cuisine_type);
        const marker = new naver.Marker({
          position: new naver.LatLng(lat, lng),
          map: m,
          title: r.name,
          zIndex: isSelected ? 1000 : 1,
          icon: {
            content: markerIconHtml(r.name, cuisineIcon, chColor ?? CHANNEL_COLORS[0], { isSelected, isClosed }),
            // 박스 폭 가변 — 화살표 끝(하단 중앙)이 좌표에 위치하도록 추정 anchor
            // approxWidth = textLen * 7 + 30 (icon+padding), height = box 24 + arrow 6 = 30
            anchor: new naver.Point(Math.min(r.name.length * 4 + 18, 64), 30),
          },
        });
        naver.Event.addListener(marker, "click", () => {
          onSelectRestaurant?.(r);
        });
        markersRef.current.push(marker);
      }
    }
  }, [clusters, getExpansionZoom, onSelectRestaurant, channelColors, activeChannel, selected]);

  // 컴포넌트 unmount 시 마커 정리
  useEffect(() => {
    return () => {
      for (const mk of markersRef.current) mk.setMap(null);
      markersRef.current = [];
      infoWindowRef.current?.close();
    };
  }, []);

  return (
    <div className="relative w-full h-full">
      <div
        ref={divRef}
        className="absolute inset-0"
        style={{ width: "100%", height: "100%", backgroundColor: "#e5e7eb" }}
      />
      {(error || initError) && (
        <div className="absolute inset-0 flex items-center justify-center text-xs text-red-600 bg-white/80 pointer-events-none">
          {error ?? initError}
        </div>
      )}
      {!ready && !error && (
        <div className="absolute inset-0 flex items-center justify-center text-xs text-gray-500 bg-white/80 pointer-events-none">
          네이버 지도 로딩 중…
        </div>
      )}
      {onMyLocation && (
        <button
          onClick={onMyLocation}
          aria-label="내 위치"
          className="absolute right-3 bottom-3 size-11 rounded-full bg-white shadow-lg flex items-center justify-center z-10"
        >
          <Icon name="my-location" size={22} />
        </button>
      )}
    </div>
  );
}

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