import { makeAutoObservable, runInAction } from "mobx";

interface CameraPosition {
  x: number;
  y: number;
}

interface AnimationConfig {
  duration: number;
  easing: (t: number) => number;
}

interface Station {
  longitude: number;
  latitude: number;
  id?: number;
}

class CameraAnimationStore {
  public maxZoom: number = 8;
  public minZoom: number = 1;

  // Актуальные, анимируемые значения, которые используются для рендеринга
  public animatedPosition: CameraPosition = { x: 0, y: 0 };
  public animatedZoom: number = 1;

  // Цель, к которой мы стремимся
  private targetPosition: CameraPosition | null = null;
  private targetZoom: number | null = null;

  // Начальные значения для текущей интерполяции
  private startPosition: CameraPosition = { x: 0, y: 0 };
  private startZoom: number = 1;

  private isAnimating: boolean = false;
  private animationStartTime: number = 0;
  private animationFrameId: number | null = null;

  public animationConfig: AnimationConfig = {
    duration: 3000, // 3 секунды для максимально плавной анимации
    easing: this.easeInOutCubic,
  };

  // Единый колбэк для обновления камеры в Pixi
  private onUpdate: ((pos: CameraPosition, zoom: number) => void) | null = null;

  constructor() {
    makeAutoObservable(this);
  }

  private easeInOutCubic(t: number): number {
    return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
  }

  public setUpdateCallback(
    callback: ((pos: CameraPosition, zoom: number) => void) | null
  ) {
    this.onUpdate = callback;
  }

  public setMaxZoom(maxZoom: number) {
    this.maxZoom = maxZoom;
  }
  public setMinZoom(minZoom: number) {
    this.minZoom = minZoom;
  }

  // Метод для ручной синхронизации (когда пользователь управляет камерой)
  public syncState(pos: CameraPosition, zoom: number) {
    this.stopAnimation();
    runInAction(() => {
      // Сохраняем точные значения без округления для плавной анимации
      this.animatedPosition = {
        x: pos.x,
        y: pos.y,
      };
      this.animatedZoom = zoom;
    });
  }

  // Метод для обновления целевой позиции без перезапуска анимации
  public updateTarget(targetPos: CameraPosition, targetZoom: number) {
    if (this.isAnimating) {
      // Если анимация уже идет, обновляем стартовую позицию на текущую и целевую на новую
      // Это даст плавную корректировку курса без рывков
      runInAction(() => {
        this.startPosition = { ...this.animatedPosition };
        this.startZoom = this.animatedZoom;
        this.targetPosition = targetPos;
        this.targetZoom = targetZoom;
        this.animationStartTime = performance.now();
      });
    } else {
      // Если анимация не идет, запускаем новую
      this.animateTo(targetPos, targetZoom);
    }
  }

  // Главный метод для запуска анимации
  public animateTo(targetPos: CameraPosition, targetZoom: number) {
    this.stopAnimation(); // Прерываем текущую анимацию
    runInAction(() => {
      this.startPosition = { ...this.animatedPosition };
      this.startZoom = this.animatedZoom;
      this.targetPosition = targetPos;
      this.targetZoom = targetZoom;
      this.animationStartTime = performance.now();
      this.isAnimating = true;
      this.animationFrameId = requestAnimationFrame(this.animationLoop);
    });
  }

  public followTram(
    tramMapPos: CameraPosition,
    screenCenter: CameraPosition,
    _stations: Station[] = []
  ) {
    // Анимация начинается с текущего зума и плавно переходит к максимальному зуму
    // для плавного приближения к желтой точке при слежении
    const targetZoom = this.maxZoom;

    // Правильная формула для позиционирования камеры: центр экрана минус позиция точки умноженная на зум
    // Это гарантирует, что точка tramMapPos будет в центре экрана
    const targetCameraPosition = {
      x: screenCenter.x - tramMapPos.x * targetZoom,
      y: screenCenter.y - tramMapPos.y * targetZoom,
    };

    // Используем updateTarget чтобы не останавливать анимацию при обновлении координат
    this.updateTarget(targetCameraPosition, targetZoom);
  }

  private animationLoop = (timestamp: number) => {
    if (!this.isAnimating || !this.targetPosition || this.targetZoom === null)
      return;

    const elapsed = timestamp - this.animationStartTime;
    const progress = Math.min(elapsed / this.animationConfig.duration, 1);
    const easedProgress = this.animationConfig.easing(progress);

    runInAction(() => {
      // Плавная интерполяция без округления для максимально плавной анимации
      const newPos = {
        x:
          this.startPosition.x +
          (this.targetPosition!.x - this.startPosition.x) * easedProgress,
        y:
          this.startPosition.y +
          (this.targetPosition!.y - this.startPosition.y) * easedProgress,
      };
      const newZoom =
        this.startZoom + (this.targetZoom! - this.startZoom) * easedProgress;

      this.animatedPosition = newPos;
      this.animatedZoom = newZoom;

      this.onUpdate?.(this.animatedPosition, this.animatedZoom);
    });

    if (progress < 1) {
      this.animationFrameId = requestAnimationFrame(this.animationLoop);
    } else {
      this.stopAnimation();
    }
  };

  public stopAnimation = () => {
    if (this.animationFrameId) {
      cancelAnimationFrame(this.animationFrameId);
    }
    runInAction(() => {
      this.isAnimating = false;
      this.animationFrameId = null;
      this.targetPosition = null;
      this.targetZoom = null;
    });
  };

  public get isActivelyAnimating(): boolean {
    return this.isAnimating;
  }
}

export const cameraAnimationStore = new CameraAnimationStore();
export default CameraAnimationStore;
