/**
 * Wire hit detection utilities.
 * Used by SimulatorCanvas to detect wire clicks/hover without relying on SVG pointer-events.
 */

import type { Wire } from '../types/wire';

export interface RenderedSegment {
  x1: number;
  y1: number;
  x2: number;
  y2: number;
  axis: 'horizontal' | 'vertical';
  /** Index j: this segment was generated from stored[j] → stored[j+1] */
  storedPairIndex: number;
}

/**
 * Expand stored waypoints into the actual orthogonal (L-shape) rendered points.
 * Between each consecutive stored pair, a corner point is inserted if they are not axis-aligned.
 */
export function getRenderedPoints(wire: Wire): { x: number; y: number }[] {
  const stored = [
    { x: wire.start.x, y: wire.start.y },
    ...(wire.waypoints ?? []),
    { x: wire.end.x, y: wire.end.y },
  ];

  if (stored.length < 2) return stored;

  const result: { x: number; y: number }[] = [stored[0]];
  for (let i = 1; i < stored.length; i++) {
    const prev = stored[i - 1];
    const curr = stored[i];
    if (prev.x !== curr.x && prev.y !== curr.y) {
      // L-shape: horizontal-first corner
      result.push({ x: curr.x, y: prev.y });
    }
    result.push(curr);
  }
  return result;
}

/**
 * Get all rendered segments with their metadata (axis, storedPairIndex).
 */
export function getRenderedSegments(wire: Wire): RenderedSegment[] {
  const stored = [
    { x: wire.start.x, y: wire.start.y },
    ...(wire.waypoints ?? []),
    { x: wire.end.x, y: wire.end.y },
  ];

  const segments: RenderedSegment[] = [];
  let ri = 0;
  const rendered = getRenderedPoints(wire);

  for (let j = 0; j < stored.length - 1; j++) {
    const prev = stored[j];
    const curr = stored[j + 1];
    const hasCorner = prev.x !== curr.x && prev.y !== curr.y;
    const numSubs = hasCorner ? 2 : 1;

    for (let s = 0; s < numSubs; s++) {
      const p1 = rendered[ri + s];
      const p2 = rendered[ri + s + 1];
      if (!p1 || !p2) continue;
      segments.push({
        x1: p1.x,
        y1: p1.y,
        x2: p2.x,
        y2: p2.y,
        axis: p1.y === p2.y ? 'horizontal' : 'vertical',
        storedPairIndex: j,
      });
    }
    ri += numSubs;
  }
  return segments;
}

/** Distance from point (px, py) to line segment (x1,y1)-(x2,y2). */
export function distToSegment(
  px: number, py: number,
  x1: number, y1: number,
  x2: number, y2: number,
): number {
  const dx = x2 - x1;
  const dy = y2 - y1;
  const lenSq = dx * dx + dy * dy;
  if (lenSq === 0) return Math.hypot(px - x1, py - y1);
  const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
  return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
}

/** Find the topmost wire within `threshold` world-units of (wx, wy). */
export function findWireNearPoint(
  wires: Wire[],
  wx: number,
  wy: number,
  threshold: number,
): Wire | null {
  for (let i = wires.length - 1; i >= 0; i--) {
    const wire = wires[i];
    const segments = getRenderedSegments(wire);
    for (const seg of segments) {
      if (distToSegment(wx, wy, seg.x1, seg.y1, seg.x2, seg.y2) <= threshold) {
        return wire;
      }
    }
  }
  return null;
}

/** Find the segment of a wire closest to (wx, wy) within threshold. */
export function findSegmentNearPoint(
  wire: Wire,
  wx: number,
  wy: number,
  threshold: number,
): RenderedSegment | null {
  const segments = getRenderedSegments(wire);
  for (const seg of segments) {
    if (distToSegment(wx, wy, seg.x1, seg.y1, seg.x2, seg.y2) <= threshold) {
      return seg;
    }
  }
  return null;
}

/**
 * Compute new waypoints array when dragging a segment.
 * Inserts a new waypoint between stored[j] and stored[j+1] at the drag position.
 */
export function computeDragWaypoints(
  originalWaypoints: { x: number; y: number }[],
  storedPairIndex: number,
  dragX: number,
  dragY: number,
): { x: number; y: number }[] {
  const newWp = { x: dragX, y: dragY };
  return [
    ...originalWaypoints.slice(0, storedPairIndex),
    newWp,
    ...originalWaypoints.slice(storedPairIndex),
  ];
}

/**
 * Move an entire rendered segment perpendicularly.
 * - horizontal segment → moves up/down (change Y of both endpoints)
 * - vertical segment → moves left/right (change X of both endpoints)
 * If the segment is the first or last, inserts connector points to keep
 * the wire connected to its fixed start/end.
 */
export function moveSegment(
  renderedPts: { x: number; y: number }[],
  segIndex: number,
  axis: 'horizontal' | 'vertical',
  newValue: number,
): { x: number; y: number }[] {
  const n = renderedPts.length;
  const numSegs = n - 1;
  const pts = renderedPts.map((p) => ({ ...p }));

  if (axis === 'horizontal') {
    if (segIndex === 0 && numSegs > 0) {
      // First segment: keep start fixed, insert connector
      pts.splice(1, 0, { x: pts[0].x, y: newValue }, { x: pts[1].x, y: newValue });
      pts.splice(3, 1); // remove original pts[1] copy
    } else if (segIndex === numSegs - 1 && numSegs > 0) {
      // Last segment: keep end fixed, insert connector
      const last = pts[n - 1];
      pts.splice(n - 1, 0, { x: pts[n - 2].x, y: newValue }, { x: last.x, y: newValue });
    } else {
      pts[segIndex].y = newValue;
      pts[segIndex + 1].y = newValue;
    }
  } else {
    // vertical
    if (segIndex === 0 && numSegs > 0) {
      pts.splice(1, 0, { x: newValue, y: pts[0].y }, { x: newValue, y: pts[1].y });
      pts.splice(3, 1);
    } else if (segIndex === numSegs - 1 && numSegs > 0) {
      const last = pts[n - 1];
      pts.splice(n - 1, 0, { x: newValue, y: pts[n - 2].y }, { x: newValue, y: last.y });
    } else {
      pts[segIndex].x = newValue;
      pts[segIndex + 1].x = newValue;
    }
  }

  return pts;
}

/**
 * Convert a list of rendered (expanded) points back to wire waypoints.
 * Waypoints are the interior corner/bend points (excludes start and end).
 * Consecutive collinear points are collapsed so only actual corners remain.
 */
export function renderedToWaypoints(
  renderedPts: { x: number; y: number }[],
): { x: number; y: number }[] {
  if (renderedPts.length <= 2) return [];

  const waypoints: { x: number; y: number }[] = [];
  for (let i = 1; i < renderedPts.length - 1; i++) {
    const prev = renderedPts[i - 1];
    const curr = renderedPts[i];
    const next = renderedPts[i + 1];
    const d1x = Math.sign(curr.x - prev.x);
    const d1y = Math.sign(curr.y - prev.y);
    const d2x = Math.sign(next.x - curr.x);
    const d2y = Math.sign(next.y - curr.y);
    // Keep only direction-change points (actual corners)
    if (d1x !== d2x || d1y !== d2y) {
      waypoints.push({ x: curr.x, y: curr.y });
    }
  }
  return waypoints;
}

/**
 * Build an SVG path string from an ordered list of rendered points (straight segments).
 */
export function renderedPointsToPath(pts: { x: number; y: number }[]): string {
  if (pts.length < 2) return '';
  return (
    `M ${pts[0].x} ${pts[0].y}` +
    pts.slice(1).map((p) => ` L ${p.x} ${p.y}`).join('')
  );
}
