/**
 * 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, useState } from "react";
import { ComposableMap, ZoomableGroup, Geography, useGeographies } from "react-simple-maps";
import { Button, makeStyles, tokens } from "@fluentui/react-components";
import type { GeoPermissibleObjects } from "d3-geo";
import { ISO_NUMERIC_TO_ALPHA2 } from "../data/isoNumericToAlpha2";
import { getBanCountColor } from "../utils/mapColors";

// ---------------------------------------------------------------------------
// 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%",
    position: "relative",
    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",
  },
  zoomControls: {
    position: "absolute",
    top: tokens.spacingVerticalM,
    right: tokens.spacingHorizontalM,
    display: "flex",
    flexDirection: "column",
    gap: tokens.spacingVerticalXS,
    zIndex: 10,
  },
});

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

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

function GeoLayer({
  countries,
  selectedCountry,
  onSelectCountry,
  thresholdLow,
  thresholdMedium,
  thresholdHigh,
}: 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],
  );

  if (geographies.length === 0) return <></>;

  // react-simple-maps types declare path as always defined, but it can be null
  // during initial render before MapProvider context initializes. Cast to reflect
  // the true runtime type and allow safe null checking.
  const safePath = path as unknown as typeof path | null;

  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;
          
          // Compute the fill color based on ban count
          const fillColor = getBanCountColor(
            count,
            thresholdLow,
            thresholdMedium,
            thresholdHigh,
          );
          
          // Only calculate centroid if path is available
          let cx: number | undefined;
          let cy: number | undefined;
          if (safePath != null) {
            const centroid = safePath.centroid(geo as unknown as GeoPermissibleObjects);
            [cx, cy] = centroid;
          }

          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: isSelected ? tokens.colorBrandBackground : fillColor,
                    stroke: tokens.colorNeutralStroke2,
                    strokeWidth: 0.75,
                    outline: "none",
                  },
                  hover: {
                    fill: isSelected
                      ? tokens.colorBrandBackgroundHover
                      : cc && count > 0
                        ? tokens.colorNeutralBackground3
                        : fillColor,
                    stroke: tokens.colorNeutralStroke1,
                    strokeWidth: 1,
                    outline: "none",
                  },
                  pressed: {
                    fill: cc ? tokens.colorBrandBackgroundPressed : fillColor,
                    stroke: tokens.colorBrandStroke1,
                    strokeWidth: 1,
                    outline: "none",
                  },
                }}
              />
              {count > 0 && cx !== undefined && cy !== undefined && 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;
  /** Ban count threshold for green coloring (default: 20). */
  thresholdLow?: number;
  /** Ban count threshold for yellow coloring (default: 50). */
  thresholdMedium?: number;
  /** Ban count threshold for red coloring (default: 100). */
  thresholdHigh?: number;
}

export function WorldMap({
  countries,
  selectedCountry,
  onSelectCountry,
  thresholdLow = 20,
  thresholdMedium = 50,
  thresholdHigh = 100,
}: WorldMapProps): React.JSX.Element {
  const styles = useStyles();
  const [zoom, setZoom] = useState<number>(1);
  const [center, setCenter] = useState<[number, number]>([0, 0]);

  const handleZoomIn = (): void => {
    setZoom((z) => Math.min(z + 0.5, 8));
  };

  const handleZoomOut = (): void => {
    setZoom((z) => Math.max(z - 0.5, 1));
  };

  const handleResetView = (): void => {
    setZoom(1);
    setCenter([0, 0]);
  };

  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."
    >
      {/* Zoom controls */}
      <div className={styles.zoomControls}>
        <Button
          appearance="secondary"
          size="small"
          onClick={handleZoomIn}
          disabled={zoom >= 8}
          title="Zoom in"
          aria-label="Zoom in"
        >
          +
        </Button>
        <Button
          appearance="secondary"
          size="small"
          onClick={handleZoomOut}
          disabled={zoom <= 1}
          title="Zoom out"
          aria-label="Zoom out"
        >
          −
        </Button>
        <Button
          appearance="secondary"
          size="small"
          onClick={handleResetView}
          disabled={zoom === 1 && center[0] === 0 && center[1] === 0}
          title="Reset view"
          aria-label="Reset view"
        >
          ⟲
        </Button>
      </div>

      <ComposableMap
        projection="geoMercator"
        projectionConfig={{ scale: 130, center: [10, 20] }}
        width={800}
        height={400}
        style={{ width: "100%", height: "auto" }}
      >
        <ZoomableGroup
          zoom={zoom}
          center={center}
          onMoveEnd={({ zoom: newZoom, coordinates }): void => {
            setZoom(newZoom);
            setCenter(coordinates);
          }}
          minZoom={1}
          maxZoom={8}
        >
          <GeoLayer
            countries={countries}
            selectedCountry={selectedCountry}
            onSelectCountry={onSelectCountry}
            thresholdLow={thresholdLow}
            thresholdMedium={thresholdMedium}
            thresholdHigh={thresholdHigh}
          />
        </ZoomableGroup>
      </ComposableMap>
    </div>
  );
}
