---
import type { RoadmapItemWithAttribution, RoadmapStatus } from '../lib/db';
import { computeRouteLayout, travelledStopFor } from '../lib/roadmap-layout';

interface Props {
  items: RoadmapItemWithAttribution[];
  viewportWidth?: number;   // SSR fallback for the layout math
}

const { items, viewportWidth = 1100 } = Astro.props;

const layout = computeRouteLayout({ itemCount: items.length, viewportWidth });
const travelledStop = travelledStopFor(items.map(i => i.status));

const STATUS_LABEL: Record<RoadmapStatus, string> = {
  shipping:    'SHIPPING',
  in_beta:     'IN BETA',
  exploring:   'EXPLORING',
  considering: 'CONSIDERING',
};
const STATUS_LABEL_COLOR: Record<RoadmapStatus, string> = {
  shipping:    '#6d8c7c',
  in_beta:     '#b96b58',
  exploring:   '#b4b2a9',
  considering: '#b4b2a9',
};
const STATUS_DOT_COLOR: Record<RoadmapStatus, string> = {
  shipping:    '#6d8c7c',
  in_beta:     '#b96b58',
  exploring:   '#b4b2a9',
  considering: '#d4d2c8',
};

// "You are here" — the most recent shipping item. -1 if nothing has shipped yet.
let lastShippingIndex = -1;
items.forEach((it, i) => { if (it.status === 'shipping') lastShippingIndex = i; });

function trailingLine(item: RoadmapItemWithAttribution): string | null {
  if (item.metadata_text && item.metadata_text.trim().length > 0) return item.metadata_text;
  if (item.attributed.length > 0) {
    const names = item.attributed.map(a => a.name.split(' ')[0]);
    if (names.length === 1) return `Shaped by ${names[0]}`;
    if (names.length === 2) return `Shaped by ${names[0]} and ${names[1]}`;
    return `Shaped by ${names.slice(0, -1).join(', ')} and ${names.at(-1)}`;
  }
  return null;
}

// Stringified x position of the 'you are here' milestone for the
// initial-scroll logic in the nav script. -1 → 0 (no scroll offset).
const initialShippingX = lastShippingIndex >= 0 ? layout.itemX[lastShippingIndex] : 0;
---
<section class="route" aria-label="Roadmap route" data-initial-x={initialShippingX}>

  <!-- The route — desktop horizontal. .rr-fullbleed escapes the parent
       .page max-width so the route can span the actual viewport while
       the header above and legend below stay centred in the content
       column. -->
  <div class="rr-wrap rr-fullbleed rr-desktop" data-item-count={items.length}>
    <div class="rr-scroll" id="rr-scroll">
      <div class="rr-scroll-inner">
        <div class="rr-track" id="rr-track" style={`width: ${layout.trackWidth}px; height: 420px;`}>
        <svg class="rr-path" id="rr-path-svg" width={layout.trackWidth} height="420" aria-hidden="true">
          <defs>
            <linearGradient id="rr-path-gradient" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0" stop-color="#2a2520" stop-opacity="0.55"/>
              <stop offset={String(travelledStop)} stop-color="#2a2520" stop-opacity="0.55"/>
              <stop offset={String(Math.min(1, travelledStop + 0.06))} stop-color="#2a2520" stop-opacity="0.15"/>
              <stop offset="1" stop-color="#2a2520" stop-opacity="0.15"/>
            </linearGradient>
          </defs>
          {layout.pathD && (
            <path id="rr-path-d" d={layout.pathD} fill="none" stroke="url(#rr-path-gradient)" stroke-width="1.25" stroke-linecap="round"/>
          )}
        </svg>

        {items.map((item, i) => (
          <div
            class="rr-milestone"
            data-y={layout.itemY[i]}
            style={`left: ${layout.itemX[i]}px; top: ${layout.itemY[i]}px;`}
          >
            <div
              class:list={['rr-dot', { 'rr-current': i === lastShippingIndex }]}
              style={`background:${STATUS_DOT_COLOR[item.status]};`}
              aria-hidden="true"
            ></div>

            <div class:list={['rr-attach', `rr-attach-${layout.cardSide[i]}`]}>
              <div class="rr-connector" aria-hidden="true"></div>
              <a class="rr-card" tabindex="0" href={`#item-${item.id}`} id={`item-${item.id}`}>
                <p class="rr-eyebrow" style={`color:${STATUS_LABEL_COLOR[item.status]};`}>
                  {item.target ? `${item.target.toUpperCase()} · ` : ''}{STATUS_LABEL[item.status]}
                </p>
                <p class="rr-card-title">{item.title}</p>
                <div class="rr-more">
                  {item.description && <p class="rr-desc">{item.description}</p>}
                  {trailingLine(item) && <p class="rr-trail">{trailingLine(item)}</p>}
                </div>
              </a>
            </div>
          </div>
        ))}
        </div>
      </div>
    </div>

    <div class="rr-fade-left" id="rr-fade-l" aria-hidden="true"></div>
    <div class="rr-fade-right" id="rr-fade-r" aria-hidden="true"></div>
  </div>

  <!-- Legend lives in /roadmap.astro now so it returns to centred
       content-column width below the full-bleed route. -->

  <!-- Mobile vertical timeline -->
  <ol class="rr-mobile" aria-label="Roadmap timeline">
    {items.map((item, i) => (
      <li class="rrm-row">
        <div class="rrm-track-col" aria-hidden="true">
          <span class="rrm-dot" style={`background:${STATUS_DOT_COLOR[item.status]};`}></span>
          {i < items.length - 1 && <span class="rrm-line"></span>}
        </div>
        <div class="rrm-body">
          <p class="rrm-eyebrow" style={`color:${STATUS_LABEL_COLOR[item.status]};`}>
            {item.target ? `${item.target.toUpperCase()} · ` : ''}{STATUS_LABEL[item.status]}
          </p>
          <p class="rrm-title">{item.title}</p>
          {item.description && <p class="rrm-desc">{item.description}</p>}
          {trailingLine(item) && <p class="rrm-trail">{trailingLine(item)}</p>}
        </div>
      </li>
    ))}
  </ol>
</section>

<script>
  // Vanilla route runtime. Two concerns:
  //  1. Nav: arrow buttons, edge fades, initial-scroll into shipping
  //  2. Viewport-aware layout — SSR uses a 1100px fallback for the math;
  //     on the client we know the real viewport, so we recompute itemX
  //     positions + SVG path d + track width on mount and on (debounced)
  //     resize. itemY values come from data-y on each milestone (path
  //     amplitude doesn't change with viewport, only the horizontal spread).
  const MIN_SPACING = 320;
  const PADDING_X   = 60;

  document.querySelectorAll<HTMLElement>('.route').forEach((section) => {
    const scroll = section.querySelector<HTMLElement>('#rr-scroll');
    const wrap   = section.querySelector<HTMLElement>('.rr-wrap');
    const track  = section.querySelector<HTMLElement>('#rr-track');
    const svg    = section.querySelector<SVGSVGElement>('#rr-path-svg');
    const pathD  = section.querySelector<SVGPathElement>('#rr-path-d');
    const milestones = Array.from(section.querySelectorAll<HTMLElement>('.rr-milestone'));
    const fadeL  = section.querySelector<HTMLElement>('#rr-fade-l');
    const fadeR  = section.querySelector<HTMLElement>('#rr-fade-r');
    if (!scroll || !track || !svg) return;

    const itemCount = milestones.length;
    const itemY: number[] = milestones.map(m => Number(m.dataset.y ?? 0));

    /** Recompute trackWidth + itemX[] + pathD using the live viewport. */
    function recompute() {
      const vw = window.innerWidth;
      const targetUsableWidth = vw * 0.80;
      const dataDrivenWidth   = (itemCount - 1) * MIN_SPACING;
      const usableWidth       = Math.max(targetUsableWidth, dataDrivenWidth);
      const trackWidth        = usableWidth + PADDING_X * 2;

      const itemX: number[] = [];
      for (let i = 0; i < itemCount; i += 1) {
        itemX.push(
          itemCount === 1
            ? PADDING_X + usableWidth / 2
            : PADDING_X + (i / (itemCount - 1)) * usableWidth,
        );
      }

      // Bezier path: control points at the segment midpoint x with control
      // y values matching the prior and next milestone (keeps the tangent
      // flat at each dot — the "river" feel from the layout helper).
      let d = '';
      if (itemCount > 0) {
        d = `M ${itemX[0]} ${itemY[0]}`;
        for (let i = 1; i < itemCount; i += 1) {
          const cx = (itemX[i - 1] + itemX[i]) / 2;
          d += ` C ${cx} ${itemY[i - 1]}, ${cx} ${itemY[i]}, ${itemX[i]} ${itemY[i]}`;
        }
      }

      // Apply.
      track!.style.width = `${trackWidth}px`;
      svg!.setAttribute('width', String(trackWidth));
      if (pathD && d) pathD.setAttribute('d', d);
      milestones.forEach((m, i) => { m.style.left = `${itemX[i]}px`; });
    }

    /* Arrow handler + edge-state updates are rewritten in the next commit
       (drag + wheel + glide). For now only the fades update on scroll. */
    function updateNav() {
      const max = scroll!.scrollWidth - scroll!.clientWidth;
      const atStart = scroll!.scrollLeft <= 2;
      const atEnd   = scroll!.scrollLeft >= max - 2;
      if (fadeL) fadeL.style.opacity = atStart ? '0' : '1';
      if (fadeR) fadeR.style.opacity = atEnd ? '0' : '1';
    }

    scroll.addEventListener('scroll', updateNav, { passive: true });

    // Debounced resize → recompute layout + refresh nav state. 120ms is
    // long enough to coalesce drag-resize events without feeling laggy.
    let resizeTimer: number | undefined;
    window.addEventListener('resize', () => {
      if (resizeTimer !== undefined) window.clearTimeout(resizeTimer);
      resizeTimer = window.setTimeout(() => { recompute(); updateNav(); }, 120);
    });

    // Initial mount: recompute with the real viewport, then scroll the
    // 'you are here' milestone roughly 25% from the left.
    recompute();
    const initialX = milestones.find(m => m.querySelector('.rr-dot.rr-current'));
    if (initialX) {
      const x = parseFloat(initialX.style.left) || 0;
      const max = scroll.scrollWidth - scroll.clientWidth;
      const target = Math.max(0, Math.min(max, x - scroll.clientWidth * 0.25));
      scroll.scrollLeft = target;
    }
    setTimeout(updateNav, 50);
    updateNav();
  });
</script>

<style>
  /* ── Desktop route ──────────────────────────────────────────────── */
  .rr-wrap { position: relative; }

  /* Escape the parent .page max-width so the route can use the actual
     viewport width. The headline, dispatch banner, section header, and
     legend all stay centred at content width — only the route widens. */
  .rr-fullbleed {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
  }
  .rr-scroll {
    /* overflow-x: auto + overflow-y: visible is the only thing that lets
       hovered cards expand above/below the track without being clipped.
       The previous fix bandaged it with extra trackHeight; this is the
       real fix. The .rr-scroll-inner wrapper is the spec-recommended
       belt-and-braces in case a browser misbehaves on this combination. */
    overflow-x: auto;
    overflow-y: visible;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    scrollbar-width: none;
    /* Top/bottom give cards room to grow above/below the track. The 80px
       sides give the first/last cards room when fully scrolled inside
       the now full-bleed container — small but visible breathing room
       between the route and the absolute viewport edges. */
    padding: 60px 80px 80px;
    scroll-padding-left: 80px;
    scroll-padding-right: 80px;
  }
  .rr-scroll::-webkit-scrollbar { display: none; }
  .rr-scroll-inner { /* structural — keeps the track on its own layer */ }
  .rr-track { position: relative; }
  .rr-path { position: absolute; top: 0; left: 0; pointer-events: none; }

  .rr-milestone {
    position: absolute;
    transform: translate(-50%, -50%);
  }

  .rr-dot {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    box-shadow: 0 0 0 5px var(--background);   /* halo cuts the path under the dot */
    transition: transform .25s ease, box-shadow .25s ease;
    scroll-snap-align: center;
  }
  .rr-dot.rr-current {
    transform: scale(1.3);
    box-shadow:
      0 0 0 5px var(--background),              /* cream halo */
      0 0 0 6px rgba(185, 107, 88, 0.45);       /* terracotta ring outside */
  }

  /* Hover-on-card animates the sibling dot too. :has() is fine on every
     evergreen browser we target; older Firefox just doesn't grow the dot. */
  .rr-milestone:has(.rr-card:hover) .rr-dot,
  .rr-milestone:has(.rr-card:focus-visible) .rr-dot {
    transform: scale(1.15);
  }
  .rr-milestone:has(.rr-card:hover) .rr-dot.rr-current,
  .rr-milestone:has(.rr-card:focus-visible) .rr-dot.rr-current {
    transform: scale(1.4);
  }

  .rr-attach {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .rr-attach-below { top: 7px; }    /* hangs down from the dot */
  .rr-attach-above { bottom: 7px; flex-direction: column-reverse; }

  .rr-connector {
    width: 1px;
    height: 30px;
    background: rgba(0, 0, 0, 0.18);
  }

  .rr-card {
    display: block;
    width: 220px;
    padding: 12px 14px;
    border-radius: 10px;
    background: transparent;
    color: inherit;
    text-decoration: none;
    border-bottom: none;
    transition:
      transform .35s cubic-bezier(.2,.7,.3,1),
      box-shadow .35s ease,
      background .25s ease;
    cursor: pointer;
  }
  .rr-card:hover,
  .rr-card:focus-visible {
    background: var(--surface-card);
    box-shadow:
      0 12px 32px -16px rgba(42, 37, 32, 0.25),
      0 0 0 0.5px var(--surface-card-border);
    transform: translateY(-2px);
    z-index: 10;
    border-bottom: none;
    outline: none;
  }

  .rr-eyebrow {
    font-family: var(--font-sans);
    font-size: 9px;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    margin: 0 0 6px;
    font-weight: 600;
  }
  .rr-card-title {
    font-family: var(--font-serif);
    font-size: 16px;
    line-height: 1.2;
    color: var(--on-surface);
    margin: 0;
  }
  .rr-more {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    transition:
      max-height .35s ease,
      opacity .25s ease,
      margin-top .35s ease;
    margin-top: 0;
  }
  .rr-card:hover .rr-more,
  .rr-card:focus-visible .rr-more {
    max-height: 280px;
    opacity: 1;
    margin-top: 10px;
  }
  .rr-desc {
    font-family: var(--font-sans);
    font-size: 12px;
    line-height: 1.55;
    color: var(--on-surface-variant);
    margin: 0 0 10px;
  }
  .rr-trail {
    font-family: var(--font-sans);
    font-size: 9px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--on-surface-muted);
    margin: 0;
  }

  /* Edge fades cover only the track itself — the top/bottom padding
     zones (60/80) on .rr-scroll exist so hover cards can overflow there
     without clipping, so the fades shouldn't paint over them. */
  .rr-fade-left, .rr-fade-right {
    position: absolute;
    top: 60px;
    bottom: 80px;
    pointer-events: none;
    transition: opacity .25s ease;
  }
  .rr-fade-left {
    left: 0; width: 60px;
    background: linear-gradient(to left, transparent, var(--background));
    opacity: 0;
  }
  .rr-fade-right {
    right: 0; width: 90px;
    background: linear-gradient(to right, transparent, var(--background));
  }

  /* ── Mobile vertical timeline ──────────────────────────────────── */
  .rr-mobile { display: none; }

  @media (max-width: 767px) {
    .rr-desktop { display: none; }
    .rr-mobile {
      display: block;
      list-style: none;
      padding: 0;
      margin: 0;
    }

    .rrm-row {
      display: grid;
      grid-template-columns: 32px 1fr;
      gap: 16px;
      align-items: start;
    }
    .rrm-track-col {
      position: relative;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 0;
      min-height: 100%;
      padding-top: 6px;
    }
    .rrm-dot {
      width: 12px;
      height: 12px;
      border-radius: 50%;
      flex-shrink: 0;
    }
    .rrm-line {
      width: 1px;
      flex: 1;
      min-height: 28px;
      background: rgba(0, 0, 0, 0.18);
      margin-top: 4px;
    }
    .rrm-body {
      display: flex;
      flex-direction: column;
      gap: 6px;
      padding-bottom: 28px;
    }
    .rrm-eyebrow {
      font-family: var(--font-sans);
      font-size: 9px;
      letter-spacing: 1.4px;
      text-transform: uppercase;
      margin: 0;
      font-weight: 600;
    }
    .rrm-title {
      font-family: var(--font-serif);
      font-size: 18px;
      line-height: 1.2;
      color: var(--on-surface);
      margin: 0;
    }
    .rrm-desc {
      font-family: var(--font-sans);
      font-size: 13px;
      line-height: 1.55;
      color: var(--on-surface-variant);
      margin: 0;
    }
    .rrm-trail {
      font-family: var(--font-sans);
      font-size: 9px;
      letter-spacing: 1px;
      text-transform: uppercase;
      color: var(--on-surface-muted);
      margin: 0;
    }
  }
</style>
