/** 2D polygon helpers for the cut. Pure math, no engine — the honest core of
 *  the cutting sim (a convex food polygon split by the blade's line). */
export interface V {
  x: number;
  y: number;
}

export const sub = (a: V, b: V): V => ({ x: a.x - b.x, y: a.y - b.y });
export const add = (a: V, b: V): V => ({ x: a.x + b.x, y: a.y + b.y });
export const cross = (a: V, b: V): number => a.x * b.y - a.y * b.x;
export const len = (a: V): number => Math.hypot(a.x, a.y);
export const scale = (a: V, s: number): V => ({ x: a.x * s, y: a.y * s });
export const norm = (a: V): V => {
  const l = len(a) || 1;
  return { x: a.x / l, y: a.y / l };
};
export const perp = (a: V): V => ({ x: -a.y, y: a.x });

/** Shoelace area (always positive). */
export function polyArea(vs: V[]): number {
  let a = 0;
  for (let i = 0; i < vs.length; i++) {
    const j = (i + 1) % vs.length;
    a += cross(vs[i], vs[j]);
  }
  return Math.abs(a) / 2;
}

/** Area-weighted centroid. */
export function centroid(vs: V[]): V {
  let a = 0, cx = 0, cy = 0;
  for (let i = 0; i < vs.length; i++) {
    const j = (i + 1) % vs.length;
    const c = cross(vs[i], vs[j]);
    a += c;
    cx += (vs[i].x + vs[j].x) * c;
    cy += (vs[i].y + vs[j].y) * c;
  }
  a *= 0.5;
  if (Math.abs(a) < 1e-6) return { ...vs[0] };
  return { x: cx / (6 * a), y: cy / (6 * a) };
}

/** Coefficient of variation of piece areas — toastsim's pieceCV, straight port.
 *  0 = identical pieces, higher = a lottery. */
export function pieceCV(areas: number[]): number {
  if (areas.length < 2) return 0;
  const mean = areas.reduce((a, b) => a + b, 0) / areas.length;
  if (mean <= 0) return 0;
  const v = areas.reduce((a, b) => a + (b - mean) ** 2, 0) / areas.length;
  return Math.sqrt(v) / mean;
}

export function cvWord(cv: number): string {
  return cv < 0.1 ? 'even' : cv < 0.22 ? 'respectable' : cv < 0.4 ? 'uneven' : 'a lottery';
}

export function pointInPoly(p: V, vs: V[]): boolean {
  let inside = false;
  for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
    const a = vs[i], b = vs[j];
    if ((a.y > p.y) !== (b.y > p.y) && p.x < ((b.x - a.x) * (p.y - a.y)) / (b.y - a.y) + a.x) {
      inside = !inside;
    }
  }
  return inside;
}

/** Longest span across the polygon (diameter). */
export function polyDiameter(vs: V[]): number {
  let d = 0;
  for (let i = 0; i < vs.length; i++)
    for (let j = i + 1; j < vs.length; j++) d = Math.max(d, len(sub(vs[i], vs[j])));
  return d;
}

/** Where segment a→b crosses the polygon boundary, sorted along a→b. */
export function segPolyCrossings(a: V, b: V, vs: V[]): V[] {
  const dir = sub(b, a);
  const hits: { p: V; t: number }[] = [];
  for (let i = 0; i < vs.length; i++) {
    const c = vs[i], d = vs[(i + 1) % vs.length];
    const e = sub(d, c);
    const denom = cross(dir, e);
    if (Math.abs(denom) < 1e-9) continue;
    const t = cross(sub(c, a), e) / denom; // along a→b
    const u = cross(sub(c, a), dir) / denom; // along c→d
    if (t >= -1e-6 && t <= 1 + 1e-6 && u >= -1e-6 && u <= 1 + 1e-6) {
      hits.push({ p: { x: a.x + dir.x * t, y: a.y + dir.y * t }, t });
    }
  }
  hits.sort((x, y) => x.t - y.t);
  return hits.map((h) => h.p);
}

/**
 * Split a convex polygon by the infinite line through A and B. Returns the two
 * halves (each ≥3 verts), or null if the line misses the polygon.
 */
export function splitConvexByLine(vs: V[], A: V, B: V): [V[], V[]] | null {
  const dir = sub(B, A);
  const EPS = 1e-6;
  const sgn = (p: V): number => {
    const s = cross(dir, sub(p, A));
    return s > EPS ? 1 : s < -EPS ? -1 : 0; // 0 = on the cut line → shared by both
  };
  const left: V[] = [], right: V[] = [];
  for (let i = 0; i < vs.length; i++) {
    const cur = vs[i], nxt = vs[(i + 1) % vs.length];
    const sc = sgn(cur), sn = sgn(nxt);
    if (sc >= 0) left.push(cur);
    if (sc <= 0) right.push(cur);
    if (sc * sn < 0) {
      // strictly opposite sides: split the edge at the crossing, add to both
      const e = sub(nxt, cur);
      const t = cross(sub(A, cur), dir) / cross(e, dir);
      const p = { x: cur.x + e.x * t, y: cur.y + e.y * t };
      left.push(p);
      right.push(p);
    }
  }
  if (left.length < 3 || right.length < 3) return null;
  return [left, right];
}
