'use client';

/**
 * Choropleth world map of visitor counts per country. Powers a single card
 * on the website-analytics page; hover any country for the visitor count,
 * click to filter the rest of the page to that country.
 *
 * Uses ECharts' own world.json (the GeoJSON shipped with their public
 * examples) - pre-cleaned, no antimeridian artifacts. Country features
 * are matched on `properties.name` (English country name from the source).
 */

import { useEffect, useMemo, useState } from 'react';
import dynamic from 'next/dynamic';
import * as echarts from 'echarts/core';
import { MapChart } from 'echarts/charts';
import {
  GeoComponent,
  TooltipComponent,
  VisualMapComponent,
  TitleComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import type { FeatureCollection } from 'geojson';

import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { getCountryName } from '@/lib/i18n/countries';
import type { UmamiMetricRow } from '@/lib/services/umami.service';

echarts.use([
  MapChart,
  GeoComponent,
  TooltipComponent,
  VisualMapComponent,
  TitleComponent,
  CanvasRenderer,
]);

const ReactEChartsCore = dynamic(() => import('echarts-for-react/lib/core'), { ssr: false });

let registrationPromise: Promise<void> | null = null;
async function ensureWorldMapRegistered(): Promise<void> {
  if (registrationPromise) return registrationPromise;
  registrationPromise = (async () => {
    const res = await fetch('/world-map/echarts-world.json');
    const geo = (await res.json()) as FeatureCollection;
    echarts.registerMap('world', { geoJSON: geo as unknown as object } as never);
  })();
  return registrationPromise;
}

interface Props {
  rows: UmamiMetricRow[] | null;
  loading: boolean;
  onCountryClick?: (iso2: string) => void;
}

export function VisitorWorldMap({ rows, loading, onCountryClick }: Props) {
  const [mapReady, setMapReady] = useState(false);

  useEffect(() => {
    let cancelled = false;
    ensureWorldMapRegistered().then(() => {
      if (!cancelled) setMapReady(true);
    });
    return () => {
      cancelled = true;
    };
  }, []);

  const data = useMemo(() => {
    if (!rows) return [];
    return rows.map((r) => ({
      name: getCountryName(r.x, 'en'),
      value: r.y,
      iso2: r.x,
    }));
  }, [rows]);

  const maxValue = useMemo(
    () => (data.length > 0 ? Math.max(...data.map((d) => d.value)) : 0),
    [data],
  );

  const option = useMemo(
    () => ({
      tooltip: {
        trigger: 'item',
        formatter: (params: { name: string; value?: number }) =>
          params.value === undefined || isNaN(params.value)
            ? `${params.name}<br/><span style="color:#94a3b8">No visitors</span>`
            : `${params.name}<br/><strong>${params.value.toLocaleString()}</strong> visitor${params.value === 1 ? '' : 's'}`,
        backgroundColor: 'rgba(15, 23, 42, 0.95)',
        borderColor: 'rgba(255, 255, 255, 0.1)',
        textStyle: { color: '#f1f5f9', fontSize: 12 },
      },
      visualMap: {
        type: 'piecewise',
        min: 0,
        max: maxValue,
        left: 16,
        bottom: 12,
        orient: 'horizontal',
        textStyle: { color: '#64748b', fontSize: 10 },
        inRange: {
          color: ['#eff6ff', '#bfdbfe', '#60a5fa', '#2563eb', '#1d4ed8', '#1e3a8a'],
        },
        itemWidth: 18,
        itemHeight: 10,
        itemGap: 2,
        showLabel: true,
        // Bucket counts into 5 piecewise segments so the legend reads
        // like a discrete heat-scale rather than a hard-to-eyeball
        // gradient bar.
        pieces: bucketizeMax(maxValue),
      },
      series: [
        {
          type: 'map',
          map: 'world',
          roam: true,
          scaleLimit: { min: 0.8, max: 8 },
          aspectScale: 0.85,
          itemStyle: {
            areaColor: '#f1f5f9',
            borderColor: '#cbd5e1',
            borderWidth: 0.4,
          },
          emphasis: {
            itemStyle: { areaColor: '#fbbf24', borderColor: '#92400e' },
            label: { show: false },
          },
          select: {
            itemStyle: { areaColor: '#f97316' },
            label: { show: false },
          },
          data,
        },
      ],
    }),
    [data, maxValue],
  );

  const onEvents = useMemo(
    () => ({
      click: (params: { data?: { iso2?: string } }) => {
        const iso2 = params?.data?.iso2;
        if (iso2 && onCountryClick) onCountryClick(iso2);
      },
    }),
    [onCountryClick],
  );

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">Visitors by country</CardTitle>
      </CardHeader>
      <CardContent>
        {loading || !mapReady ? (
          <Skeleton className="h-[560px] w-full" />
        ) : (
          <ReactEChartsCore
            echarts={echarts}
            option={option}
            onEvents={onEvents}
            style={{ height: 560, width: '100%' }}
            notMerge
            lazyUpdate
          />
        )}
      </CardContent>
    </Card>
  );
}

/**
 * Bucket the visitor-count scale into 5 readable bins. Umami country data
 * is heavily skewed (top country may have 900+, most have 0–5), so a
 * linear gradient looks visually flat. The buckets are derived from the
 * observed max so the highest bin is always saturated.
 */
function bucketizeMax(max: number): Array<{ min?: number; max?: number; label: string }> {
  if (max <= 0) return [{ min: 0, max: 0, label: '0' }];
  const step = Math.max(1, Math.ceil(max / 5));
  return [
    { min: 0, max: 0, label: '0' },
    { min: 1, max: step, label: `1–${step.toLocaleString()}` },
    {
      min: step + 1,
      max: step * 2,
      label: `${(step + 1).toLocaleString()}–${(step * 2).toLocaleString()}`,
    },
    {
      min: step * 2 + 1,
      max: step * 3,
      label: `${(step * 2 + 1).toLocaleString()}–${(step * 3).toLocaleString()}`,
    },
    { min: step * 3 + 1, label: `${(step * 3 + 1).toLocaleString()}+` },
  ];
}
