/**
 * Coordinate-generation for the /roadmap horizontal route component.
 *
 * Given an itemCount + viewport width, produces:
 *  - itemX / itemY: position of each milestone dot on the SVG canvas
 *  - cardSide: which side ('below' or 'above') the milestone's card hangs on
 *  - pathD: a smooth cubic-bezier SVG path string snaking through all dots
 *  - trackWidth: total scroll width of the track (≥ viewportWidth so the
 *      page always offers visible content + scroll affordance)
 *  - midY: vertical centreline of the track, returned for callers that want
 *      to place additional decoration relative to the centre
 *
 * No DOM access here — pure math. Tested directly in roadmap-layout.test.ts.
 */

export interface LayoutOpts {
  itemCount: number;
  viewportWidth: number;
  minSpacingX?: number;   // default 320
  trackHeight?: number;   // default 460
  amplitude?: number;     // default 120
  paddingX?: number;      // default 60 — symmetric leading + trailing padding
  paddingLeft?: number;   // overrides paddingX on the leading edge only
  paddingRight?: number;  // overrides paddingX on the trailing edge only
  tailLength?: number;    // px to extend the drawn path past the final
                          // milestone, easing back to the centreline — lets
                          // the line keep going as the last item scrolls in
}

export interface LayoutResult {
  trackWidth: number;
  pathD: string;
  itemX: number[];
  itemY: number[];
  cardSide: ('above' | 'below')[];
  midY: number;
}

export function computeRouteLayout(opts: LayoutOpts): LayoutResult {
  const minSpacing  = opts.minSpacingX  ?? 320;
  const trackHeight = opts.trackHeight  ?? 420;
  const amplitude   = opts.amplitude    ?? 120;
  const paddingDef  = opts.paddingX     ?? 60;
  const paddingL    = opts.paddingLeft  ?? paddingDef;
  const paddingR    = opts.paddingRight ?? paddingDef;
  const midY        = trackHeight / 2;

  const itemCount = Math.max(0, opts.itemCount);

  if (itemCount === 0) {
    return {
      trackWidth: opts.viewportWidth,
      pathD: '',
      itemX: [],
      itemY: [],
      cardSide: [],
      midY,
    };
  }

  // Aim for ~80% of viewport for low item counts; data-driven minimum
  // takes over once items × minSpacing exceeds that target (the carousel
  // case — track extends past viewport).
  const targetUsableWidth = opts.viewportWidth * 0.80;
  const dataDrivenWidth   = (itemCount - 1) * minSpacing;
  const usableWidth       = Math.max(targetUsableWidth, dataDrivenWidth);
  const trackWidth        = paddingL + usableWidth + paddingR;

  const itemX: number[] = Array.from({ length: itemCount }, (_, i) =>
    itemCount === 1
      ? paddingL + usableWidth / 2
      : paddingL + (i / (itemCount - 1)) * usableWidth,
  );

  // First item on the centreline; subsequent items alternate up/down with
  // a varying amplitude so the path feels hand-planned rather than purely
  // sinusoidal. Multiplier ramps 0.78 (first off-axis) → ~1.18 (last item)
  // — closer items swing less, further items swing more.
  const denom = Math.max(1, itemCount - 1);
  const itemY: number[] = itemX.map((_, i) => {
    if (i === 0) return midY;
    const direction = i % 2 === 1 ? -1 : 1;
    const multiplier = 0.78 + (i / denom) * 0.4;
    return midY + direction * amplitude * multiplier;
  });

  // Cards hang TOWARD the centreline rather than away from it. A dot above
  // centre (odd index) gets a card below; a dot below centre (even index >0)
  // gets a card above. This keeps every card growing into the track height
  // rather than out the top or bottom of the scroll container — which the
  // CSS spec clips regardless of overflow-y: visible (browser computes
  // overflow-y to auto whenever overflow-x is auto). i=0 sits on the
  // centreline, no clipping risk either way; defaulting to below.
  const cardSide: ('above' | 'below')[] = itemX.map((_, i) => {
    if (i === 0) return 'below';
    return i % 2 === 1 ? 'below' : 'above';
  });

  // Smooth cubic-bezier path. Control points use the midpoint x of each
  // segment, with control-y values matching the prior and next item
  // respectively — this keeps the curve tangent flat at each milestone
  // (the "river" feel rather than the "zigzag" feel).
  let 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]}`;
  }

  // Trailing tail: continue the path past the last milestone, easing it back
  // to the centreline so the line keeps going while the final item scrolls
  // toward the middle. Tangent stays flat at the last dot (control y = lastY).
  if (opts.tailLength && opts.tailLength > 0) {
    const lastX = itemX[itemCount - 1];
    const lastY = itemY[itemCount - 1];
    const tailEndX = lastX + opts.tailLength;
    const cx = (lastX + tailEndX) / 2;
    d += ` C ${cx} ${lastY}, ${cx} ${midY}, ${tailEndX} ${midY}`;
  }

  return { trackWidth, pathD: d, itemX, itemY, cardSide, midY };
}

/**
 * The travelled-portion stop position on the path stroke gradient.
 *  - No shipping items: 0 (path is entirely "ahead" tone)
 *  - Some shipping items: (lastShippingIndex + 0.5) / itemCount
 *  - Clamped to [0, 0.98] so the fade-to-ahead is always visible
 */
export function travelledStopFor(
  statuses: ReadonlyArray<'shipping' | 'in_beta' | 'planned' | 'exploring' | 'considering'>,
): number {
  if (statuses.length === 0) return 0;
  let last = -1;
  statuses.forEach((s, i) => { if (s === 'shipping') last = i; });
  if (last < 0) return 0;
  return Math.min(0.98, (last + 0.5) / statuses.length);
}
