<script>
  import { onMount, onDestroy } from 'svelte';
  import * as d3 from 'd3';
  import { feature } from 'topojson-client';
  import worldData from 'world-atlas/countries-50m.json';

  let { onclose, onprogress } = $props();

  const HOME_CODE = '203';

  const MOCK_TRIPS = [
    { countryName: 'Japan', countryCode: '392', date: '2024-03-15', city: 'Tokyo' },
    { countryName: 'France', countryCode: '191', date: '2024-06-20', city: 'Paris' },
    { countryName: 'Spain', countryCode: '724', date: '2024-09-10', city: 'Barcelona' },
    { countryName: 'United States', countryCode: '840', date: '2025-01-05', city: 'New York' },
    { countryName: 'Thailand', countryCode: '764', date: '2025-04-18', city: 'Bangkok' },
    { countryName: 'Australia', countryCode: '036', date: '2025-08-22', city: 'Sydney' },
  ];

  const HOME_COLOR = '#8b5cf6';
  const VISITED_COLOR = '#22c55e';
  const ARC_COLOR = '#000000';
  const PLANE_COLOR = '#7c3aed';
  const PLANE_PATH = 'M14,0 L4,-3 L0,-7 L-3,-5 L0,-2 L-5,-1 L-9,-5 L-11,-4 L-7,0 L-11,4 L-9,5 L-5,1 L0,2 L-3,5 L0,7 L4,3 Z';
  const UNVISITED = '#ffffff';

  const TERRITORY_PARENT = {
    '016': '840', '060': '826', '086': '826', '092': '826', '136': '826',
    '184': '554', '234': '208', '238': '826', '239': '826', '248': '246',
    '258': '250', '260': '250', '304': '208', '316': '840', '334': '036',
    '446': '156', '500': '826', '531': '528', '533': '528', '534': '528',
    '540': '250', '570': '554', '574': '036', '580': '840', '612': '826',
    '630': '840', '652': '250', '654': '826', '660': '826', '663': '250',
    '666': '250', '796': '826', '831': '826', '832': '826', '833': '826',
    '850': '840', '876': '250',
  };

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

  let frameEl;
  let svg, g, pathFn, projection;
  let countryPaths;
  let homeFeature;
  let featuresById = {};
  let isCancelled = false;
  let isPlaying = $state(false);
  let isFinished = $state(false);

  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 computeArc(p1, p2) {
    const interp = d3.geoInterpolate(p1, p2);
    const steps = 80;
    const raw = [];

    for (let i = 0; i <= steps; i++) {
      const t = i / steps;
      const geo = interp(t);
      const pt = projection(geo);
      if (!pt) continue;
      raw.push({ t, x: pt[0], y: pt[1] });
    }

    if (raw.length < 2) return [];

    const first = raw[0];
    const last = raw[raw.length - 1];
    const dx = last.x - first.x;
    const dy = last.y - first.y;
    const dist = Math.sqrt(dx * dx + dy * dy);
    const arcH = Math.max(40, Math.min(200, dist * 0.22));

    return raw.map(p => [p.x, p.y - arcH * Math.sin(Math.PI * p.t)]);
  }

  function getAngleAtLength(node, len) {
    const d = 0.5;
    const total = node.getTotalLength();
    const p1 = node.getPointAtLength(Math.max(0, len - d));
    const p2 = node.getPointAtLength(Math.min(total, len + d));
    return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
  }

  function animateStroke(pathEl, tipEl, startOffset, endOffset, duration) {
    return new Promise((resolve) => {
      const node = pathEl.node();
      if (!node) { resolve(); return; }
      const totalLength = node.getTotalLength();

      if (totalLength === 0) { resolve(); return; }

      d3.timer(elapsed => {
        if (isCancelled) { resolve(); return true; }

        const t = Math.min(elapsed / duration, 1);
        const offset = startOffset + (endOffset - startOffset) * t;

        pathEl.attr('stroke-dashoffset', offset);

        const drawn = totalLength - offset;
        const clamped = Math.max(0, Math.min(drawn, totalLength));
        try {
          const pt = node.getPointAtLength(clamped);
          const angle = getAngleAtLength(node, clamped);
          tipEl.attr('transform', `translate(${pt.x}, ${pt.y}) rotate(${angle}) scale(1.4)`).attr('opacity', 1);
        } catch (e) {
          // ignore SVG errors
        }

        if (t >= 1) {
          resolve();
          return true;
        }
      });
    });
  }

  function delay(ms) {
    return new Promise(resolve => {
      if (isCancelled) { resolve(); return; }
      const id = setTimeout(resolve, ms);
    });
  }

  async function animateTrip(destCode, destFeature) {
    if (!homeFeature || !destFeature) return;

    const homeCentroid = d3.geoCentroid(homeFeature);
    const destCentroid = d3.geoCentroid(destFeature);

    const pts = computeArc(homeCentroid, destCentroid);
    if (pts.length < 2) return;

    const lineGen = d3.line().curve(d3.curveBasis);
    const pathData = lineGen(pts);

    if (!pathData) return;

    function createArc(pathData) {
      const el = g.append('path')
        .attr('d', pathData)
        .attr('fill', 'none')
        .attr('stroke', ARC_COLOR)
        .attr('stroke-width', 2.5)
        .attr('stroke-opacity', 0.8)
        .attr('stroke-linecap', 'round');
      const tip = g.append('path')
        .attr('d', PLANE_PATH)
        .attr('fill', PLANE_COLOR)
        .attr('opacity', 0);
      return { el, tip };
    }

    // --- Outbound: home -> dest ---
    let { el: outEl, tip: outTip } = createArc(pathData);
    const outLen = outEl.node().getTotalLength();
    outEl.attr('stroke-dasharray', outLen).attr('stroke-dashoffset', outLen);

    const homeDot = g.append('circle')
      .attr('r', 4)
      .attr('fill', PLANE_COLOR)
      .attr('cx', pts[0][0])
      .attr('cy', pts[0][1])
      .attr('opacity', 1);

    await animateStroke(outEl, outTip, outLen, 0, 2500);
    if (isCancelled) return;

    outEl.remove();
    outTip.remove();
    homeDot.remove();

    // Color the destination country
    const targetPath = countryPaths.filter(d => effId(d) === destCode);
    targetPath.transition().duration(500).attr('fill', VISITED_COLOR);
    g.selectAll('.micro-state-j')
      .filter(d => effId(d) === destCode)
      .transition().duration(500)
      .attr('fill', VISITED_COLOR);

    await delay(800);
    if (isCancelled) return;

    // --- Return: dest -> home ---
    const revPts = [...pts].reverse();
    const revData = d3.line().curve(d3.curveBasis)(revPts);
    let { el: retEl, tip: retTip } = createArc(revData);
    const retLen = retEl.node().getTotalLength();
    retEl.attr('stroke-dasharray', retLen).attr('stroke-dashoffset', retLen);

    const destDot = g.append('circle')
      .attr('r', 4)
      .attr('fill', PLANE_COLOR)
      .attr('cx', revPts[0][0])
      .attr('cy', revPts[0][1])
      .attr('opacity', 1);

    await animateStroke(retEl, retTip, retLen, 0, 2200);
    if (isCancelled) return;

    retEl.remove();
    retTip.remove();
    destDot.remove();

    await delay(300);
  }

  async function startJourney() {
    isPlaying = true;
    isFinished = false;
    isCancelled = false;

    const trips = MOCK_TRIPS;
    const total = trips.length;

    for (let i = 0; i < total; i++) {
      if (isCancelled) break;

      const trip = trips[i];
      const destFeature = featuresById[trip.countryCode];
      if (!destFeature) continue;

      const label = `${trip.city}, ${trip.countryName}`;
      if (onprogress) onprogress({ index: i + 1, total, label });

      await animateTrip(trip.countryCode, destFeature);
    }

    if (!isCancelled) {
      isFinished = true;
      isPlaying = false;
      if (onprogress) onprogress({ index: trips.length, total: trips.length, label: 'Journey complete!' });
    } else {
      isPlaying = false;
    }
  }

  function stopJourney() {
    isCancelled = true;
    isPlaying = false;
  }

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

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

    pathFn = 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';
    });

    for (const f of countries) {
      featuresById[effId(f)] = f;
    }

    homeFeature = featuresById[HOME_CODE];

    svg = d3.select(frameEl)
      .append('svg')
      .attr('width', width)
      .attr('height', height)
      .style('cursor', 'default');

    g = svg.append('g');

    countryPaths = g.selectAll('path')
      .data(countries)
      .join('path')
      .attr('d', pathFn)
      .attr('fill', d => effId(d) === HOME_CODE ? HOME_COLOR : UNVISITED)
      .attr('stroke', '#d4d4d4')
      .attr('stroke-width', 0.5);

    function renderMicrostates() {
      g.selectAll('.micro-state-j').remove();
      const threshold = 4;
      countryPaths.each(function (d) {
        if (effId(d) !== d.id) return;
        const { width, height } = this.getBBox();
        if (width < threshold && height < threshold) {
          const [cx, cy] = pathFn.centroid(d);
          g.append('circle')
            .attr('class', 'micro-state-j')
            .datum(d)
            .attr('cx', cx)
            .attr('cy', cy)
            .attr('r', 2)
            .attr('fill', effId(d) === HOME_CODE ? HOME_COLOR : UNVISITED)
            .attr('stroke', '#94a3b8')
            .attr('stroke-width', 0.5);
        }
      });
    }

    renderMicrostates();

    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);
        countryPaths.attr('d', pathFn);
        renderMicrostates();
      }
    });

    observer.observe(frameEl);

    startJourney();

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

<div bind:this={frameEl} class="journey-frame">
  <button class="close-btn" onclick={() => { stopJourney(); onclose?.(); }}>✕</button>
  {#if isFinished}
    <div class="done-badge">Journey complete!</div>
  {/if}
</div>

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

  .journey-frame :global(svg) {
    display: block;
  }

  .close-btn {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 10;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: none;
    background: rgba(0,0,0,0.55);
    color: #fff;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s ease;
  }

  .close-btn:hover {
    background: rgba(0,0,0,0.75);
  }

  .done-badge {
    position: absolute;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    background: rgba(0,0,0,0.65);
    color: #fff;
    font-family: var(--heading, sans-serif);
    font-size: 16px;
    padding: 10px 24px;
    border-radius: 24px;
    white-space: nowrap;
    letter-spacing: 0.04em;
  }
</style>
