import type { EffectInstance } from './types/effects';

function distSqPointToSegment(
  px: number,
  py: number,
  x1: number,
  y1: number,
  x2: number,
  y2: number,
): number {
  const dx = x2 - x1;
  const dy = y2 - y1;
  const len2 = dx * dx + dy * dy;
  if (len2 < 1e-18) {
    const ex = px - x1;
    const ey = py - y1;
    return ex * ex + ey * ey;
  }
  let t = ((px - x1) * dx + (py - y1) * dy) / len2;
  t = Math.max(0, Math.min(1, t));
  const qx = x1 + t * dx;
  const qy = y1 + t * dy;
  const ex = px - qx;
  const ey = py - qy;
  return ex * ex + ey * ey;
}

/** Минимальная квадрат дистанции от точки (норм. координаты) до «тела» эффекта — для ластика. */
export function minDistSqEffectToPoint(inst: EffectInstance, p: { x: number; y: number }): number {
  switch (inst.type) {
    case 'fog':
    case 'fire':
    case 'rain':
    case 'water': {
      let best = Number.POSITIVE_INFINITY;
      for (const q of inst.points) {
        const dx = q.x - p.x;
        const dy = q.y - p.y;
        best = Math.min(best, dx * dx + dy * dy);
      }
      return best;
    }
    case 'lightning':
    case 'sunbeam':
      return distSqPointToSegment(p.x, p.y, inst.start.x, inst.start.y, inst.end.x, inst.end.y);
    case 'freeze':
    case 'darkness': {
      const dx = inst.at.x - p.x;
      const dy = inst.at.y - p.y;
      return dx * dx + dy * dy;
    }
    case 'scorch':
    case 'ice':
    case 'shadow':
    case 'poisonCloud':
    case 'explosion': {
      const dx = inst.at.x - p.x;
      const dy = inst.at.y - p.y;
      return dx * dx + dy * dy;
    }
    default:
      return Number.POSITIVE_INFINITY;
  }
}

function eraseHitThresholdSq(inst: EffectInstance, toolRadiusN: number): number {
  if (
    inst.type === 'scorch' ||
    inst.type === 'ice' ||
    inst.type === 'shadow' ||
    inst.type === 'poisonCloud' ||
    inst.type === 'explosion'
  ) {
    const r = toolRadiusN + inst.radiusN;
    return r * r;
  }
  return toolRadiusN * toolRadiusN;
}

/** Ближайший эффект действия в пределах радиуса ластика, иначе `null`. Эффекты поля стираются кистью отдельно. */
export function pickEraseTargetId(
  instances: readonly EffectInstance[],
  p: { x: number; y: number },
  toolRadiusN: number,
): string | null {
  let best: { id: string; dd: number } | null = null;
  for (const inst of instances) {
    if (inst.type === 'fog' || inst.type === 'fire' || inst.type === 'rain' || inst.type === 'water') {
      continue;
    }
    const dd = minDistSqEffectToPoint(inst, p);
    const th = eraseHitThresholdSq(inst, toolRadiusN);
    if (dd <= th && (!best || dd < best.dd)) {
      best = { id: inst.id, dd };
    }
  }
  return best?.id ?? null;
}
