/** 두 벡터의 외적 (2D) — Graham scan에서 왼쪽 회전 여부 판별 */
function cross(o: [number, number], a: [number, number], b: [number, number]): number {
  return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
}

/** Graham scan 기반 볼록 껍질 (반시계 방향) */
export function convexHull(points: [number, number][]): [number, number][] {
  const n = points.length;
  if (n < 2) return points.slice();
  const sorted = points.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);
  const lower: [number, number][] = [];
  for (const p of sorted) {
    while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
      lower.pop();
    }
    lower.push(p);
  }
  const upper: [number, number][] = [];
  for (let i = sorted.length - 1; i >= 0; i--) {
    const p = sorted[i];
    while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
      upper.pop();
    }
    upper.push(p);
  }
  lower.pop();
  upper.pop();
  return lower.concat(upper);
}

/** 중심에서 각 꼭짓점 방향으로 padding 확장 */
export function padPolygon(hull: [number, number][], padding: number): [number, number][] {
  if (hull.length === 0) return hull;
  const cx = hull.reduce((s, p) => s + p[0], 0) / hull.length;
  const cy = hull.reduce((s, p) => s + p[1], 0) / hull.length;
  return hull.map(([x, y]) => {
    const dx = x - cx;
    const dy = y - cy;
    const len = Math.sqrt(dx * dx + dy * dy);
    if (len === 0) return [x + padding, y + padding] as [number, number];
    const scale = (len + padding) / len;
    return [cx + dx * scale, cy + dy * scale] as [number, number];
  });
}

/** cluster_id 해시 → HSL 색상 */
export function clusterColor(id: number): string {
  const h = (id * 137) % 360;
  return `hsl(${h}, 80%, 55%)`;
}
