<script>
  import { onMount } from 'svelte';
  import * as d3 from 'd3';
  import { feature } from 'topojson-client';
  import worldData from 'world-atlas/countries-50m.json';
  import { getSelected, toggle, setTotalCount, getHomeCountryCode } from '../layout/selection.svelte.js';

  const TERRITORY_PARENT = {
    '016': '840', // American Samoa -> United States
    '060': '826', // Bermuda -> United Kingdom
    '086': '826', // Br. Indian Ocean Ter. -> United Kingdom
    '092': '826', // British Virgin Is. -> United Kingdom
    '136': '826', // Cayman Is. -> United Kingdom
    '184': '554', // Cook Is. -> New Zealand
    '234': '208', // Faeroe Is. -> Denmark
    '238': '826', // Falkland Is. -> United Kingdom
    '239': '826', // S. Geo. and the Is. -> United Kingdom
    '248': '246', // Aland -> Finland
    '258': '250', // Fr. Polynesia -> France
    '260': '250', // Fr. S. Antarctic Lands -> France
    '304': '208', // Greenland -> Denmark
    '316': '840', // Guam -> United States
    '334': '036', // Heard I. and McDonald Is. -> Australia
    '446': '156', // Macao -> China
    '500': '826', // Montserrat -> United Kingdom
    '531': '528', // Curacao -> Netherlands
    '533': '528', // Aruba -> Netherlands
    '534': '528', // Sint Maarten -> Netherlands
    '540': '250', // New Caledonia -> France
    '570': '554', // Niue -> New Zealand
    '574': '036', // Norfolk Island -> Australia
    '580': '840', // N. Mariana Is. -> United States
    '612': '826', // Pitcairn Is. -> United Kingdom
    '630': '840', // Puerto Rico -> United States
    '652': '250', // St-Barthelemy -> France
    '654': '826', // Saint Helena -> United Kingdom
    '660': '826', // Anguilla -> United Kingdom
    '663': '250', // St-Martin -> France
    '666': '250', // St. Pierre and Miquelon -> France
    '796': '826', // Turks and Caicos Is. -> United Kingdom
    '831': '826', // Guernsey -> United Kingdom
    '832': '826', // Jersey -> United Kingdom
    '833': '826', // Isle of Man -> United Kingdom
    '850': '840', // U.S. Virgin Is. -> United States
    '876': '250', // Wallis and Futuna Is. -> France
  };

  function effId(d) {
    return TERRITORY_PARENT[d.id] || d.id;
  }

  const HOME_COLOR = '#8b5cf6';
  const HOME_COLOR_HOVER = '#7c3aed';
  const VISITED_COLOR = '#22c55e';
  const VISITED_COLOR_HOVER = '#16a34a';
  const UNVISITED_COLOR = '#ffffff';
  const UNVISITED_COLOR_HOVER = '#f0f6fa';

  function countryColor(d, sel, homeCode) {
    const id = effId(d);
    if (!sel.has(id)) return UNVISITED_COLOR;
    if (id === homeCode) return HOME_COLOR;
    return VISITED_COLOR;
  }

  function countryHoverColor(d, sel, homeCode) {
    const id = effId(d);
    if (!sel.has(id)) return UNVISITED_COLOR_HOVER;
    if (id === homeCode) return HOME_COLOR_HOVER;
    return VISITED_COLOR_HOVER;
  }

  let frameEl;
  let _paths = null;
  let _g = null;

  function fitProjection(proj, w, h) {
    proj.fitSize([w, h], { type: 'Sphere' });
    const s = proj.scale() * 1.5;
    proj.scale(s).translate([w / 2, h * 0.70]);
  }

  function updateAllFills() {
    if (!_paths || !_g) return;
    const sel = getSelected();
    const hc = getHomeCountryCode();
    _paths.attr('fill', d => countryColor(d, sel, hc));
    _g.selectAll('.micro-state').attr('fill', d => countryColor(d, sel, hc));
  }

  $effect(updateAllFills);

  onMount(() => {
    const width = frameEl.clientWidth;
    const height = frameEl.clientHeight;

    const projection = d3.geoMercator();
    fitProjection(projection, width, height);

    const path = d3.geoPath().projection(projection);

    const countries = feature(worldData, worldData.objects.countries)
      .features.filter(f => (f.id || f.properties.name === 'Kosovo') && f.id !== '010');

    countries.forEach(f => {
      if (!f.id) f.id = 'XK';
    });

    const sovereignIds = new Set(countries.map(f => effId(f)));
    setTotalCount(sovereignIds.size);

    const svg = d3.select(frameEl)
      .append('svg')
      .attr('width', width)
      .attr('height', height);

    _g = svg.append('g');

    const tooltip = d3.select(frameEl)
      .append('div')
      .attr('class', 'tooltip')
      .style('display', 'none');

    function updateFill(sel) {
      const s = getSelected();
      const hc = getHomeCountryCode();
      sel.attr('fill', d => countryColor(d, s, hc));
      _g.selectAll('.micro-state').attr('fill', d => countryColor(d, s, hc));
    }

    function attachEvents(sel) {
      sel
        .on('click', (event, d) => {
          toggle(effId(d));
          updateFill(d3.select(event.currentTarget));
        })
        .on('mouseenter', (event, d) => {
          const s = getSelected();
          const hc = getHomeCountryCode();
          d3.select(event.currentTarget).attr('fill', countryHoverColor(d, s, hc));
          tooltip.style('display', 'block').text(d.properties.name);
        })
        .on('mousemove', (event) => {
          const [x, y] = d3.pointer(event, frameEl);
          tooltip.style('left', (x + 10) + 'px').style('top', (y - 28) + 'px');
        })
        .on('mouseleave', (event, d) => {
          const s = getSelected();
          const hc = getHomeCountryCode();
          d3.select(event.currentTarget).attr('fill', countryColor(d, s, hc));
          tooltip.style('display', 'none');
        });
    }

    _paths = _g.selectAll('path')
      .data(countries)
      .join('path')
      .attr('d', path)
      .attr('fill', '#ffffff')
      .attr('stroke', '#d4d4d4')
      .attr('stroke-width', 0.5);
    attachEvents(_paths);

    function renderMicrostates() {
      _g.selectAll('.micro-state').remove();
      const threshold = Math.max(4, 16 / d3.zoomTransform(svg.node()).k);
      _paths.each(function (d) {
        if (effId(d) !== d.id) return;
        const { width, height } = this.getBBox();
        if (width < threshold && height < threshold) {
          const [cx, cy] = path.centroid(d);
          const hc = getHomeCountryCode();
          const c = _g.append('circle')
            .attr('class', 'micro-state')
            .datum(d)
            .attr('cx', cx)
            .attr('cy', cy)
            .attr('r', 2)
            .attr('fill', countryColor(d, getSelected(), hc))
            .attr('stroke', '#94a3b8')
            .attr('stroke-width', 0.5);
          attachEvents(c);
        }
      });
    }

    renderMicrostates();

    const zoom = d3.zoom()
      .scaleExtent([1, 32])
      .on('zoom', (event) => {
        _g.attr('transform', event.transform);
        renderMicrostates();
      });

    svg.call(zoom);

    svg.on('dblclick.zoom', null);
    svg.on('dblclick', (event) => {
      const [x, y] = d3.pointer(event);
      svg.transition().duration(300).call(zoom.scaleBy, 2, [x, y]);
    });

    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        svg.attr('width', width).attr('height', height);
        fitProjection(projection, width, height);
        const countryPaths = _g.selectAll('path');
        countryPaths.attr('d', path);
        updateFill(countryPaths);
        renderMicrostates();
      }
    });

    observer.observe(frameEl);

    return () => {
      observer.disconnect();
      svg.remove();
    };
  });
</script>

<div bind:this={frameEl} class="map-frame"></div>

<style>
  .map-frame {
    width: 100%;
    height: 100%;
    overflow: hidden;
    position: relative;
    background: #a4c8e0;
  }

  .map-frame :global(svg) {
    display: block;
    cursor: grab;
  }

  .map-frame :global(svg:active) {
    cursor: grabbing;
  }

  .map-frame :global(svg path) {
    cursor: pointer;
  }

  .map-frame :global(.tooltip) {
    position: absolute;
    padding: 4px 10px;
    background: #1f2937;
    color: #fff;
    font: 14px/1.4 sans-serif;
    border-radius: 4px;
    pointer-events: none;
    white-space: nowrap;
  }
</style>
