/**
 * WorldMap — SVG world map showing per-country ban counts.
 *
 * Uses react-simple-maps with the Natural Earth 110m TopoJSON data from
 * jsDelivr CDN.  For each country that has bans in the selected time window,
 * the total count is displayed inside the country's borders.  Clicking a
 * country filters the companion table.
 */

import { useCallback } from "react";
import { ComposableMap, Geography, useGeographies } from "react-simple-maps";
import { makeStyles, tokens } from "@fluentui/react-components";
import type { GeoPermissibleObjects } from "d3-geo";
import { ISO_NUMERIC_TO_ALPHA2 } from "../data/isoNumericToAlpha2";

// ---------------------------------------------------------------------------
// Static data URL — world-atlas 110m TopoJSON (No-fill, outline-only)
// ---------------------------------------------------------------------------

const GEO_URL =
  "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json";

// ---------------------------------------------------------------------------
// Styles
// ---------------------------------------------------------------------------

const useStyles = makeStyles({
  mapWrapper: {
    width: "100%",
    backgroundColor: tokens.colorNeutralBackground2,
    borderRadius: tokens.borderRadiusMedium,
    border: `1px solid ${tokens.colorNeutralStroke1}`,
    overflow: "hidden",
  },
  countLabel: {
    fontSize: "9px",
    fontWeight: "600",
    fill: tokens.colorNeutralForeground1,
    pointerEvents: "none",
    userSelect: "none",
  },
});

// ---------------------------------------------------------------------------
// Colour utilities
// ---------------------------------------------------------------------------

/** Map a ban count to a fill colour intensity. */
function getFill(count: number, maxCount: number): string {
  if (count === 0 || maxCount === 0) return "#E8E8E8";
  const intensity = count / maxCount;
  // Interpolate from light amber to deep red
  const r = Math.round(220 + (220 - 220) * intensity);
  const g = Math.round(200 - 180 * intensity);
  const b = Math.round(160 - 160 * intensity);
  return `rgb(${String(r)},${String(g)},${String(b)})`;
}

// ---------------------------------------------------------------------------
// GeoLayer — must be rendered inside ComposableMap to access map context
// ---------------------------------------------------------------------------

interface GeoLayerProps {
  countries: Record<string, number>;
  maxCount: number;
  selectedCountry: string | null;
  onSelectCountry: (cc: string | null) => void;
}

function GeoLayer({
  countries,
  maxCount,
  selectedCountry,
  onSelectCountry,
}: GeoLayerProps): React.JSX.Element {
  const styles = useStyles();
  const { geographies, path } = useGeographies({ geography: GEO_URL });

  const handleClick = useCallback(
    (cc: string | null): void => {
      onSelectCountry(selectedCountry === cc ? null : cc);
    },
    [selectedCountry, onSelectCountry],
  );

  // react-simple-maps types declare `path` as always defined, but it is
  // undefined during early renders before the MapProvider context initialises.
  // Cast through unknown to reflect the true runtime type and guard safely.
  const safePath = path as unknown as typeof path | null;
  if (safePath == null) return <></>;

  return (
    <>
      {(geographies as { rsmKey: string; id: string | number }[]).map(
        (geo) => {
          const numericId = String(geo.id);
          const cc: string | null = ISO_NUMERIC_TO_ALPHA2[numericId] ?? null;
          const count: number = cc !== null ? (countries[cc] ?? 0) : 0;
          const isSelected = cc !== null && selectedCountry === cc;
          const centroid = path.centroid(geo as unknown as GeoPermissibleObjects);
          const [cx, cy] = centroid;

          const fill = isSelected
            ? tokens.colorBrandBackground
            : getFill(count, maxCount);

          return (
            <g
              key={geo.rsmKey}
              style={{ cursor: cc ? "pointer" : "default" }}
              role={cc ? "button" : undefined}
              tabIndex={cc ? 0 : undefined}
              aria-label={cc
                ? `${cc}: ${String(count)} ban${count !== 1 ? "s" : ""}${
                    isSelected ? " (selected)" : ""
                  }`
                : undefined}
              aria-pressed={isSelected || undefined}
              onClick={(): void => {
                if (cc) handleClick(cc);
              }}
              onKeyDown={(e): void => {
                if (cc && (e.key === "Enter" || e.key === " ")) {
                  e.preventDefault();
                  handleClick(cc);
                }
              }}
            >
              <Geography
                geography={geo}
                style={{
                  default: {
                    fill,
                    stroke: tokens.colorNeutralBackground1,
                    strokeWidth: 0.5,
                    outline: "none",
                  },
                  hover: {
                    fill: cc ? tokens.colorBrandBackgroundHover : fill,
                    stroke: tokens.colorNeutralBackground1,
                    strokeWidth: 0.5,
                    outline: "none",
                  },
                  pressed: {
                    fill: tokens.colorBrandBackgroundPressed,
                    stroke: tokens.colorNeutralBackground1,
                    strokeWidth: 0.5,
                    outline: "none",
                  },
                }}
              />
              {count > 0 && isFinite(cx) && isFinite(cy) && (
                <text
                  x={cx}
                  y={cy}
                  textAnchor="middle"
                  dominantBaseline="central"
                  className={styles.countLabel}
                >
                  {count}
                </text>
              )}
            </g>
          );
        },
      )}
    </>
  );
}

// ---------------------------------------------------------------------------
// WorldMap — public component
// ---------------------------------------------------------------------------

export interface WorldMapProps {
  /** ISO alpha-2 country code → ban count. */
  countries: Record<string, number>;
  /** Currently selected country filter (null means no filter). */
  selectedCountry: string | null;
  /** Called when the user clicks a country or deselects. */
  onSelectCountry: (cc: string | null) => void;
}

export function WorldMap({
  countries,
  selectedCountry,
  onSelectCountry,
}: WorldMapProps): React.JSX.Element {
  const styles = useStyles();
  const maxCount = Math.max(0, ...Object.values(countries));

  return (
    <div
      className={styles.mapWrapper}
      role="img"
      aria-label="World map showing banned IP counts by country. Click a country to filter the table below."
    >
      <ComposableMap
        projection="geoMercator"
        projectionConfig={{ scale: 130, center: [10, 20] }}
        width={800}
        height={400}
        style={{ width: "100%", height: "auto" }}
      >
        <GeoLayer
          countries={countries}
          maxCount={maxCount}
          selectedCountry={selectedCountry}
          onSelectCountry={onSelectCountry}
        />
      </ComposableMap>
    </div>
  );
}
