/**
 * Утилиты для анимации
 * Основано на логике анимации из HTML файла (a.html)
 */

/**
 * Линейная интерполяция между двумя значениями
 * @param from - начальное значение
 * @param to - конечное значение
 * @param t - коэффициент интерполяции (0-1)
 * @returns интерполированное значение
 */
export const lerp = (from: number, to: number, t: number): number => {
  return from + (to - from) * t;
};

/**
 * Интерполяция угла с учетом кратчайшего пути
 * @param from - начальный угол в радианах
 * @param to - конечный угол в радианах
 * @param t - коэффициент интерполяции (0-1)
 * @returns интерполированный угол в радианах
 */
export const lerpAngle = (from: number, to: number, t: number): number => {
  // Нормализуем углы к диапазону 0-2π
  from = ((from % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
  to = ((to % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);

  // Найдем кратчайший путь между углами
  let diff = to - from;
  if (diff > Math.PI) {
    diff -= Math.PI * 2;
  } else if (diff < -Math.PI) {
    diff += Math.PI * 2;
  }

  return from + diff * t;
};

/**
 * Класс для анимации позиции
 * Основано на логике анимации из HTML файла (a.html)
 */
export class PositionAnimator {
  private currentX: number;
  private currentY: number;
  private targetX: number;
  private targetY: number;
  private isAnimating: boolean = false;
  private animationId: number | null = null;
  private onUpdate: (position: { x: number; y: number }) => void;
  private onComplete: () => void;
  private duration: number;
  private startTime: number | null = null;

  constructor(
    onUpdate: (position: { x: number; y: number }) => void,
    onComplete: () => void = () => {},
    duration: number = 500
  ) {
    this.currentX = 0;
    this.currentY = 0;
    this.targetX = 0;
    this.targetY = 0;
    this.onUpdate = onUpdate;
    this.onComplete = onComplete;
    this.duration = duration;
  }

  /**
   * Анимировать к новой позиции
   */
  animateTo(x: number, y: number, duration?: number): void {
    this.targetX = x;
    this.targetY = y;
    this.duration = duration || this.duration;
    this.startTime = null;
    this.isAnimating = true;

    if (this.animationId) {
      cancelAnimationFrame(this.animationId);
    }

    this.animationId = requestAnimationFrame(this.animate);
  }

  /**
   * Установить позицию мгновенно
   */
  setPosition(x: number, y: number): void {
    this.currentX = x;
    this.currentY = y;
    this.targetX = x;
    this.targetY = y;
    this.isAnimating = false;
    this.onUpdate({ x, y });
  }

  /**
   * Остановить анимацию
   */
  stop(): void {
    this.isAnimating = false;
    if (this.animationId) {
      cancelAnimationFrame(this.animationId);
      this.animationId = null;
    }
  }

  private animate = (timestamp: number): void => {
    if (!this.isAnimating) return;

    if (!this.startTime) {
      this.startTime = timestamp;
    }

    const elapsed = timestamp - this.startTime;
    const progress = Math.min(elapsed / this.duration, 1);

    // Используем easeOutCubic для плавной анимации
    const easedProgress = 1 - Math.pow(1 - progress, 3);

    this.currentX = lerp(this.currentX, this.targetX, easedProgress);
    this.currentY = lerp(this.currentY, this.targetY, easedProgress);

    this.onUpdate({ x: this.currentX, y: this.currentY });

    if (progress < 1) {
      this.animationId = requestAnimationFrame(this.animate);
    } else {
      this.isAnimating = false;
      this.onComplete();
    }
  };
}

/**
 * Передискретизация пути для обеспечения равномерного расстояния между точками
 * @param path - массив [lat, lon] или [x, y]
 * @param segmentLength - желаемое расстояние между точками (в единицах координат)
 * @returns новый массив точек
 */
export const resamplePath = <T extends number[]>(path: T[], segmentLength: number): T[] => {
  if (path.length < 2) return path;

  const newPath: T[] = [path[0]];
  let leftover = 0;

  for (let i = 0; i < path.length - 1; i++) {
    const p1 = path[i];
    const p2 = path[i + 1];
    const dx = p2[0] - p1[0];
    const dy = p2[1] - p1[1];
    const dist = Math.sqrt(dx * dx + dy * dy);

    if (dist === 0) continue;

    let currentDist = segmentLength - leftover;
    while (currentDist <= dist) {
      const t = currentDist / dist;
      const point = new Array(p1.length) as T;
      for (let j = 0; j < p1.length; j++) {
        point[j] = p1[j] + (p2[j] - p1[j]) * t;
      }
      newPath.push(point);
      currentDist += segmentLength;
    }
    leftover = dist - (currentDist - segmentLength);
  }

  // Добавляем последнюю точку, если она существенно отличается от последней добавленной
  const lastP = path[path.length - 1];
  const lastNewP = newPath[newPath.length - 1];
  let isDifferent = false;
  for (let j = 0; j < lastP.length; j++) {
    if (Math.abs(lastP[j] - lastNewP[j]) > 0.0000001) {
      isDifferent = true;
      break;
    }
  }

  if (isDifferent) {
    newPath.push(lastP);
  }

  return newPath;
};

/**
 * Класс для анимации по полярным координатам
 * Основано на логике анимации из HTML файла (a.html)
 */
export class PolarAnimator {
  private currentAngle: number;
  private currentDistance: number;
  private targetAngle: number;
  private targetDistance: number;
  private centerX: number;
  private centerY: number;
  private isAnimating: boolean = false;
  private animationId: number | null = null;
  private onUpdate: (position: { x: number; y: number }) => void;
  private onComplete: () => void;
  private duration: number;
  private startTime: number | null = null;

  constructor(
    onUpdate: (position: { x: number; y: number }) => void,
    onComplete: () => void = () => {},
    duration: number = 500
  ) {
    this.currentAngle = 0;
    this.currentDistance = 0;
    this.targetAngle = 0;
    this.targetDistance = 0;
    this.centerX = 0;
    this.centerY = 0;
    this.onUpdate = onUpdate;
    this.onComplete = onComplete;
    this.duration = duration;
  }

  /**
   * Анимировать к новой позиции по полярным координатам
   */
  animateToPolar(
    centerX: number,
    centerY: number,
    targetAngle: number,
    targetDistance: number,
    duration?: number
  ): void {
    this.centerX = centerX;
    this.centerY = centerY;
    this.targetAngle = targetAngle;
    this.targetDistance = targetDistance;
    this.duration = duration || this.duration;
    this.startTime = null;
    this.isAnimating = true;

    if (this.animationId) {
      cancelAnimationFrame(this.animationId);
    }

    this.animationId = requestAnimationFrame(this.animate);
  }

  /**
   * Установить позицию мгновенно
   */
  setPosition(angle: number, distance: number): void {
    this.currentAngle = angle;
    this.currentDistance = distance;
    this.targetAngle = angle;
    this.targetDistance = distance;
    this.isAnimating = false;

    const x = this.centerX + distance * Math.cos(angle);
    const y = this.centerY - distance * Math.sin(angle);
    this.onUpdate({ x, y });
  }

  /**
   * Остановить анимацию
   */
  stop(): void {
    this.isAnimating = false;
    if (this.animationId) {
      cancelAnimationFrame(this.animationId);
      this.animationId = null;
    }
  }

  private animate = (timestamp: number): void => {
    if (!this.isAnimating) return;

    if (!this.startTime) {
      this.startTime = timestamp;
    }

    const elapsed = timestamp - this.startTime;
    const progress = Math.min(elapsed / this.duration, 1);

    // Используем easeOutCubic для плавной анимации
    const easedProgress = 1 - Math.pow(1 - progress, 3);

    this.currentAngle = lerpAngle(this.currentAngle, this.targetAngle, easedProgress);
    this.currentDistance = lerp(this.currentDistance, this.targetDistance, easedProgress);

    const x = this.centerX + this.currentDistance * Math.cos(this.currentAngle);
    const y = this.centerY - this.currentDistance * Math.sin(this.currentAngle);

    this.onUpdate({ x, y });

    if (progress < 1) {
      this.animationId = requestAnimationFrame(this.animate);
    } else {
      this.isAnimating = false;
      this.onComplete();
    }
  };
}
