'use client';

import './world-map.scss';
import { useEffect, useMemo, useRef, useState } from 'react';
import { geoNaturalEarth1, geoPath } from 'd3-geo';
import { feature } from 'topojson-client';
import type { Feature, Geometry } from 'geojson';
import worldTopo from 'world-atlas/countries-110m.json';
import {
	EXCHANGE_GEO,
	REGION_LABELS,
	REGION_ORDER,
	moveDir,
	formatFlucRate,
	formatIndexClose,
	type WorldIndexRow,
	type MarketRegion,
	TV_SYMBOL
} from '@/types/worldIndex';
import TradingViewChart from './TradingViewChart';

// 등거리 대신 d3-geo geoNaturalEarth1(곡면) 투영. 국가별 topojson feature 를 GeoJSON 으로 디코드해
// 육지 path 와 핀 좌표를 "동일 projection" 으로 산출. 팬/줌은 <g> transform 으로 처리(드래그 이동·확대).
// (Wave1 — .claude/plan/global-market-research-pages.md)
const VW = 1000;
const VH = 500;
const MIN_K = 1;
const MAX_K = 6;
const DEFAULT_K = 1; // 기본: 전체 세계지도 표시 (확대는 휠/드래그/지역탭)
const PIN_H = 15; // 핀 막대 높이 — 머리를 지점 위로 띄워 겹침 완화(map-pin)

type GeoFeature = Feature<Geometry>;
type RegionTab = 'major'|MarketRegion;
type View = { x: number; y: number; k: number };

const COUNTRIES: GeoFeature[] = (() => {
	const decoded = feature(worldTopo as never, (worldTopo as unknown as { objects: { countries: never } }).objects.countries) as unknown as { features: GeoFeature[] };
	return decoded.features.filter((f) => String(f.id) !== '010'); // 남극 제외
})();

const PROJECTION = geoNaturalEarth1().fitSize([VW, VH], { type: 'FeatureCollection', features: COUNTRIES } as never);
const PATH_FN = geoPath(PROJECTION);
const COUNTRY_PATHS: { id: string; d: string }[] = COUNTRIES
	.map((f) => ({ id: String(f.id), d: PATH_FN(f as never) ?? '' }))
	.filter((c) => c.d.length > 0);

const clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));

// viewBox 중심 기준으로 k 배 확대한 초기 view
const centeredView = (k: number): View => ({ x: (VW / 2) * (1 - k), y: (VH / 2) * (1 - k), k });

type Props = {
	rows: WorldIndexRow[];
};

type Pin = {
	row: WorldIndexRow;
	label: string;
	x: number;
	y: number;
	dir: 'up'|'down'|'flat';
	radius: number;
};

function formatChangeVal(changeVal: number): string {
	const sign = changeVal > 0 ? '+' : '';
	return `${sign}${changeVal.toLocaleString('ko-KR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}

export default function WorldMarketMap({ rows }: Props)
{
	const [region, setRegion] = useState<RegionTab>('major');
	const [active, setActive] = useState<string|null>(null);
	const [chartRow, setChartRow] = useState<WorldIndexRow|null>(null);
	const [view, setView] = useState<View>(() => centeredView(DEFAULT_K));
	const [panning, setPanning] = useState(false);

	const svgRef = useRef<SVGSVGElement>(null);
	const dragRef = useRef<{ px: number; py: number; vx: number; vy: number; moved: boolean }|null>(null);
	const suppressClickRef = useRef(false);

	// 마우스 휠 확대/축소 (커서 위치 기준). React onWheel 은 passive 라 네이티브 리스너로 등록(preventDefault 필요).
	useEffect(() => {
		const svg = svgRef.current;
		if (!svg) {
			return;
		}
		const onWheel = (e: WheelEvent) => {
			e.preventDefault();
			const rect = svg.getBoundingClientRect();
			if (rect.width === 0) {
				return;
			}
			const cx = (e.clientX - rect.left) * (VW / rect.width);
			const cy = (e.clientY - rect.top) * (VH / rect.height);
			const factor = e.deltaY < 0 ? 1.12 : 1 / 1.12;
			setView((v) => {
				const k = clamp(v.k * factor, MIN_K, MAX_K);
				return { x: cx - (cx - v.x) * (k / v.k), y: cy - (cy - v.y) * (k / v.k), k };
			});
		};
		svg.addEventListener('wheel', onWheel, { passive: false });
		return () => {
			svg.removeEventListener('wheel', onWheel);
		};
	}, []);

	// 좌표를 아는 국가만 취급 + 핀 좌표 산출
	const pins = useMemo<Pin[]>(() => {
		return rows
			.map((row) => {
				const geo = EXCHANGE_GEO[row.countryCode];
				if (!geo) {
					return null;
				}
				const xy = PROJECTION([geo.lng, geo.lat]);
				if (!xy) {
					return null;
				}
				const radius = Math.min(8, 4.5 + Math.abs(row.flucRateBp) / 110);
				return { row, label: geo.label, x: xy[0], y: xy[1], dir: moveDir(row.flucRateBp), radius };
			})
			.filter((c): c is Pin => c !== null);
	}, [rows]);

	// 지역 탭 (데이터 있는 탭만)
	const tabs = useMemo<RegionTab[]>(() => {
		const counts: Record<RegionTab, number> = { major: 0, asia: 0, europe: 0, america: 0, mideast: 0 };
		pins.forEach((p) => {
			const geo = EXCHANGE_GEO[p.row.countryCode];
			counts[geo.region] += 1;
			if (geo.major) {
				counts.major += 1;
			}
		});
		return REGION_ORDER.filter((t) => counts[t] > 0);
	}, [pins]);

	const activeRegion: RegionTab = tabs.includes(region) ? region : (tabs[0] ?? 'major');

	const gridRows = useMemo(() => {
		return pins
			.map((p) => p.row)
			.filter((r) => {
				const geo = EXCHANGE_GEO[r.countryCode];
				return activeRegion === 'major' ? geo.major === true : geo.region === activeRegion;
			})
			.sort((a, b) => b.flucRateBp - a.flucRateBp);
	}, [pins, activeRegion]);

	const asOf = useMemo(() => {
		return pins.reduce<string|null>((max, c) => (max === null || c.row.tradeDate > max ? c.row.tradeDate : max), null);
	}, [pins]);

	// 라벨 겹침 자동 회피 — 화면좌표(변환 후)에서 큰 등락 순으로 배치, 이미 놓인 라벨과 겹치면 숨김.
	// 확대(k↑)하면 핀 간격이 벌어져 겹침이 풀리므로 더 많은 라벨이 나타난다. (핀 머리/막대는 항상 표시)
	const labeledSet = useMemo(() => {
		const ordered = [...pins].sort((a, b) => Math.abs(b.row.flucRateBp) - Math.abs(a.row.flucRateBp));
		const placed: { x1: number; y1: number; x2: number; y2: number }[] = [];
		const show = new Set<string>();
		for (const pin of ordered) {
			const sx = view.x + pin.x * view.k;
			const sy = view.y + pin.y * view.k;
			if (sx < -24 || sx > VW + 24 || sy < -24 || sy > VH + 24) {
				continue;
			}
			const hy = sy - PIN_H;
			const left = sx > VW * 0.72;
			const w = Math.max(pin.label.length * 11.5, 46) + 6;
			const x1 = left ? sx - (pin.radius + 4) - w : sx + (pin.radius + 4);
			const box = { x1, y1: hy - 13, x2: x1 + w, y2: hy + 12 };
			const hit = placed.some((p) => !(box.x2 < p.x1 || box.x1 > p.x2 || box.y2 < p.y1 || box.y1 > p.y2));
			if (!hit) {
				placed.push(box);
				show.add(pin.row.countryCode);
			}
		}
		return show;
	}, [pins, view]);

	// 지역 탭 클릭 → 그리드 필터 + 지도를 해당 지역으로 이동/확대
	function selectRegion(t: RegionTab)
	{
		setRegion(t);

		if (t === 'major') {
			setView(centeredView(DEFAULT_K));
			return;
		}

		const regionPins = pins.filter((p) => EXCHANGE_GEO[p.row.countryCode].region === t);
		if (regionPins.length === 0) {
			return;
		}

		const xs = regionPins.map((p) => p.x);
		const ys = regionPins.map((p) => p.y);
		const cx = (Math.min(...xs) + Math.max(...xs)) / 2;
		const cy = (Math.min(...ys) + Math.max(...ys)) / 2;
		const spreadX = Math.max(Math.max(...xs) - Math.min(...xs), 70);
		const spreadY = Math.max(Math.max(...ys) - Math.min(...ys), 70);
		const k = clamp(Math.min(VW / (spreadX * 2.2), VH / (spreadY * 2.2)), 1.6, 4);
		setView({ x: VW / 2 - cx * k, y: VH / 2 - cy * k, k });
	}

	function zoomBy(factor: number)
	{
		setView((v) => {
			const k = clamp(v.k * factor, MIN_K, MAX_K);
			const cx = VW / 2;
			const cy = VH / 2;
			return { x: cx - (cx - v.x) * (k / v.k), y: cy - (cy - v.y) * (k / v.k), k };
		});
	}

	function onPointerDown(e: React.PointerEvent<SVGSVGElement>)
	{
		if (e.button !== 0) {
			return;
		}
		dragRef.current = { px: e.clientX, py: e.clientY, vx: view.x, vy: view.y, moved: false };
		setPanning(true);
	}

	function onPointerMove(e: React.PointerEvent<SVGSVGElement>)
	{
		const d = dragRef.current;
		if (!d) {
			return;
		}
		const rect = svgRef.current?.getBoundingClientRect();
		const factor = rect && rect.width > 0 ? VW / rect.width : 1;
		const dx = (e.clientX - d.px) * factor;
		const dy = (e.clientY - d.py) * factor;
		if (Math.abs(e.clientX - d.px) + Math.abs(e.clientY - d.py) > 3) {
			d.moved = true;
		}
		setView((v) => ({ ...v, x: d.vx + dx, y: d.vy + dy }));
	}

	function endPan()
	{
		suppressClickRef.current = dragRef.current?.moved ?? false;
		dragRef.current = null;
		setPanning(false);
	}

	function onPinClick(row: WorldIndexRow)
	{
		if (suppressClickRef.current) {
			suppressClickRef.current = false;
			return;
		}
		if (TV_SYMBOL[row.countryCode]) {
			setChartRow(row);
		}
	}

	return (
		<section className='world-map' aria-label='세계 주요국 증시지수'>
			<div className='world-map__head'>
				<h2 className='world-map__title'>세계 증시</h2>
				<div className='world-map__meta'>
					<span className='world-map__legend'>
						<i className='world-map__dot world-map__dot--up' aria-hidden='true' />상승
						<i className='world-map__dot world-map__dot--down' aria-hidden='true' />하락
					</span>
					{asOf && <span className='world-map__asof'>전일 마감 기준 {asOf}</span>}
				</div>
			</div>

			<div className='world-map__body'>
				<div className='world-map__stage'>
					<svg
						ref={svgRef}
						className={`world-map__svg${panning ? ' is-panning' : ''}`}
						viewBox={`0 0 ${VW} ${VH}`}
						role='img'
						aria-label='세계지도 위 주요국 증시지수 핀 (드래그로 이동, 확대 가능)'
						preserveAspectRatio='xMidYMid meet'
						onPointerDown={onPointerDown}
						onPointerMove={onPointerMove}
						onPointerUp={endPan}
						onPointerLeave={endPan}
					>
						<defs>
							<radialGradient id='wm-ocean' cx='50%' cy='42%' r='75%'>
								<stop offset='0%' stopColor='var(--az-map-ocean-1)' />
								<stop offset='100%' stopColor='var(--az-map-ocean-2)' />
							</radialGradient>
						</defs>

						{/* 대양 (고정 배경 — 팬/줌 영향 없음) */}
						<rect x='0' y='0' width={VW} height={VH} rx='16' fill='url(#wm-ocean)' />

						{/* 팬/줌 그룹 — 육지만. 핀/라벨은 변환 밖에서 화면좌표로 그려 확대 시 크기 고정 + 라벨 겹침 회피 */}
						<g transform={`translate(${view.x} ${view.y}) scale(${view.k})`}>
							<g className='world-map__land'>
								{COUNTRY_PATHS.map((c, i) => (
									<path key={i} d={c.d} />
								))}
							</g>
						</g>

						<g className='world-map__pins'>
							{pins.map((pin) => {
								const sx = view.x + pin.x * view.k;
								const sy = view.y + pin.y * view.k;
								if (sx < -24 || sx > VW + 24 || sy < -24 || sy > VH + 24) {
									return null;
								}
								const isActive = pin.row.countryCode === active;
								const showLabel = isActive || labeledSet.has(pin.row.countryCode);
								const leftSide = sx > VW * 0.72;
								const lx = leftSide ? -(pin.radius + 4) : pin.radius + 4;
								const anchor = leftSide ? 'end' : 'start';
								return (
									<g
										key={pin.row.countryCode}
										className={`world-map__pin world-map__pin--${pin.dir}${isActive ? ' is-active' : ''}`}
										transform={`translate(${sx.toFixed(1)} ${sy.toFixed(1)})`}
										role='button'
										tabIndex={0}
										aria-label={`${pin.label} ${pin.row.name} ${formatFlucRate(pin.row.flucRateBp)}`}
										onMouseEnter={() => setActive(pin.row.countryCode)}
										onMouseLeave={() => setActive((cur) => (cur === pin.row.countryCode ? null : cur))}
										onFocus={() => setActive(pin.row.countryCode)}
										onBlur={() => setActive((cur) => (cur === pin.row.countryCode ? null : cur))}
										onClick={() => onPinClick(pin.row)}
										onKeyDown={(e) => { if ((e.key === 'Enter' || e.key === ' ') && TV_SYMBOL[pin.row.countryCode]) { e.preventDefault(); setChartRow(pin.row); } }}
									>
										{/* map-pin: 지점에서 위로 뻗은 막대 + 머리(원) */}
										<line className='world-map__pin-stem' x1='0' y1='0' x2='0' y2={-PIN_H} />
										<circle className='world-map__pin-head' cx='0' cy={-PIN_H} r={pin.radius} />

										{/* 라벨 — 겹치지 않는 것만(활성 핀은 항상). 국가명 + 등락률 */}
										{showLabel && (
											<g className='world-map__label' transform={`translate(${lx} ${-PIN_H})`} textAnchor={anchor}>
												<text className='world-map__label-name' y='-2'>{pin.label}</text>
												<text className={`world-map__label-rate world-map__label-rate--${pin.dir}`} y='8.5'>{formatFlucRate(pin.row.flucRateBp)}</text>
												{isActive && <text className='world-map__label-close' y='18'>{formatIndexClose(pin.row.close)}</text>}
											</g>
										)}
									</g>
								);
							})}
						</g>
					</svg>

					{/* 줌 컨트롤 */}
					<div className='world-map__zoom'>
						<button type='button' className='world-map__zoom-btn' aria-label='확대' onClick={() => zoomBy(1.4)}>+</button>
						<button type='button' className='world-map__zoom-btn' aria-label='축소' onClick={() => zoomBy(1 / 1.4)}>−</button>
						<button type='button' className='world-map__zoom-btn' aria-label='초기화' title='초기화' onClick={() => setView(centeredView(DEFAULT_K))}>⤢</button>
					</div>
				</div>

				{/* 지수 패널 — 지역 탭 + 데이터 그리드 */}
				<div className='world-map__panel'>
					<div className='world-map__tabs' role='tablist' aria-label='지역'>
						{tabs.map((t) => (
							<button
								key={t}
								type='button'
								role='tab'
								aria-selected={t === activeRegion}
								className={`world-map__tab${t === activeRegion ? ' is-active' : ''}`}
								onClick={() => selectRegion(t)}
							>
								{REGION_LABELS[t]}
							</button>
						))}
					</div>

					<div className='world-map__grid-wrap'>
						<table className='world-map__grid'>
							<thead>
								<tr>
									<th scope='col' className='world-map__c-name'>지수명</th>
									<th scope='col' className='world-map__c-num'>지수</th>
									<th scope='col' className='world-map__c-num'>전일비</th>
									<th scope='col' className='world-map__c-num'>등락률</th>
									<th scope='col' className='world-map__c-time'>시간</th>
									<th scope='col' className='world-map__c-chart'>차트</th>
								</tr>
							</thead>
							<tbody>
								{gridRows.map((row) => {
									const geo = EXCHANGE_GEO[row.countryCode];
									const dir = moveDir(row.flucRateBp);
									const isActive = row.countryCode === active;
									return (
										<tr
											key={row.countryCode}
											className={`world-map__row${isActive ? ' is-active' : ''}`}
											onMouseEnter={() => setActive(row.countryCode)}
											onMouseLeave={() => setActive((cur) => (cur === row.countryCode ? null : cur))}
										>
											<td className='world-map__c-name'>
												<span className='world-map__row-country'>{geo.label}</span>
												<span className='world-map__row-name'>{row.name}</span>
											</td>
											<td className='world-map__c-num world-map__c-close'>{formatIndexClose(row.close)}</td>
											<td className={`world-map__c-num world-map__c-${dir}`}>{formatChangeVal(row.changeVal)}</td>
											<td className={`world-map__c-num world-map__c-${dir}`}>{formatFlucRate(row.flucRateBp)}</td>
											<td className='world-map__c-time'>{row.tradeDate}</td>
											<td className='world-map__c-chart'>
												<button
													type='button'
													className='world-map__chart-btn'
													title={TV_SYMBOL[row.countryCode] ? `${row.name} 차트 보기` : `${geo.label} 위치 보기`}
													aria-label={TV_SYMBOL[row.countryCode] ? `${geo.label} ${row.name} 차트 보기` : `${geo.label} ${row.name} 지도에서 보기`}
													onClick={() => { if (TV_SYMBOL[row.countryCode]) { setChartRow(row); } else { setActive(row.countryCode); } }}
												>
													<svg viewBox='0 0 24 24' width='16' height='16' aria-hidden='true'>
														<polyline points='3,17 9,11 13,15 21,6' fill='none' stroke='currentColor' strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' />
													</svg>
												</button>
											</td>
										</tr>
									);
								})}
							</tbody>
						</table>
						{gridRows.length === 0 && <div className='world-map__empty'>이 지역 지수 데이터가 없습니다.</div>}
					</div>
				</div>
			</div>

			{chartRow && TV_SYMBOL[chartRow.countryCode] && (
				<div
					className='world-map__modal'
					role='dialog'
					aria-modal='true'
					aria-label={`${EXCHANGE_GEO[chartRow.countryCode]?.label ?? ''} ${chartRow.name} 차트`}
					onClick={() => setChartRow(null)}
				>
					<div className='world-map__modal-panel' onClick={(e) => e.stopPropagation()}>
						<div className='world-map__modal-head'>
							<span className='world-map__modal-title'>
								{EXCHANGE_GEO[chartRow.countryCode]?.label} · {chartRow.name}
							</span>
							<button type='button' className='world-map__modal-close' aria-label='닫기' onClick={() => setChartRow(null)}>×</button>
						</div>
						<div className='world-map__modal-body'>
							<TradingViewChart symbol={TV_SYMBOL[chartRow.countryCode]} />
						</div>
						<p className='world-map__modal-note'>실시간 차트 제공 · TradingView</p>
					</div>
				</div>
			)}
		</section>
	);
}
