import { Application, filterSet } from 'dill-pixel';
import { gsap } from 'gsap';
import { Actor } from './Actor';
import { Entity } from './Entity';
import { System } from './System';

export class Solid<T = any, A extends Application = Application> extends Entity<T, A> {
  type = 'Solid';
  isSolid = true;
  riding: Set<Actor> = new Set();
  protected _animations: Set<gsap.core.Tween | gsap.core.Timeline> = new Set<gsap.core.Tween | gsap.core.Timeline>();

  protected _positionAnimation: {
    targetX: number;
    targetY: number;
    startX: number;
    startY: number;
    duration: number;
    elapsed: number;
    ease: gsap.EaseString;
    repeat: number;
    yoyo: boolean;
    repeatDelay: number;
    delayRemaining: number;
    iteration: number;
    isReversed: boolean;
  } | null = null;

  getCollideables<T extends Entity = Entity>(dx: number = 0, dy: number = 0): Set<T> {
    return System.getNearbyEntities<T>(this, 'actor', dx, dy) as Set<T>;
  }

  added() {
    System.addSolid(this);
    this.addSignalConnection(this.system.onSystemEnabledChanged.connect(this._handleSystemEnabledChanged));
  }

  private _handleSystemEnabledChanged(enabled: boolean) {
    if (enabled) {
      if (this._animations?.size > 0) {
        this._animations.forEach((animation) => animation?.resume());
      }
    } else {
      if (this._animations?.size > 0) {
        this._animations.forEach((animation) => animation?.pause());
      }
    }
  }

  removed() {
    System.removeSolid(this);
    if (this._animations?.size > 0) {
      this._animations.forEach((animation) => animation?.kill());
    }
  }

  getAllRiding(dx: number = 0, dy: number = 0) {
    return filterSet<Actor>(
      this.getCollideables(dx, dy),
      (entity: Actor) => entity.isActor && entity.isRiding(this),
    ) as Set<Actor>;
  }

  move(x: number, y: number): void {
    this.xRemainder += x;
    this.yRemainder += y;
    const moveX = Math.round(this.xRemainder);
    const moveY = Math.round(this.yRemainder);

    if (moveX !== 0 || moveY !== 0) {
      // Get all potential collisions before moving
      const ridingActors = this.getAllRiding(moveX, moveY);

      // For moving platforms, we need to check a larger area to catch fast-moving actors
      const sweepBox = this.boundingRect.clone();
      // Expand the sweep box in the direction of movement
      if (moveY < 0) {
        // Moving up - expand upward
        sweepBox.y += moveY;
        sweepBox.height -= moveY;
      } else {
        // Moving down - expand downward
        sweepBox.height += moveY;
      }
      if (moveX < 0) {
        // Moving left - expand left
        sweepBox.x += moveX;
        sweepBox.width -= moveX;
      } else {
        // Moving right - expand right
        sweepBox.width += moveX;
      }

      // Get potential collisions using the sweep box
      const potentialCollisions = new Set<Actor>();
      for (const actor of this.getCollideables<Actor>(moveX, moveY)) {
        if (actor.boundingRect.intersects(sweepBox)) {
          potentialCollisions.add(actor);
        }
      }

      // First move riding actors with the platform
      for (const actor of ridingActors) {
        if (actor.mostRiding === this) {
          // Move riding actors first to maintain contact
          actor.moveY(moveY);
          actor.moveX(moveX);
        }
      }

      // Then move the platform
      this.x += moveX;
      this.y += moveY;
      this.xRemainder -= moveX;
      this.yRemainder -= moveY;

      // Finally handle any collisions
      this.handleActorInteractions(moveX, moveY, ridingActors, potentialCollisions);
    }
    System.updateEntity(this);
  }

  animatePosition(x?: number | null, y?: number | null, vars: gsap.TweenVars = {}): gsap.core.Tween {
    const pos = this.position.clone();
    const tweenVars = Object.assign({ duration: 1, ease: 'linear.none' }, vars);

    const targetX = x === undefined || x === null ? pos.x : x;
    const targetY = y === undefined || y === null ? pos.y : y;

    // Store animation data for physics update
    this._positionAnimation = {
      targetX,
      targetY,
      startX: pos.x,
      startY: pos.y,
      duration: tweenVars.duration as number,
      elapsed: 0,
      ease: (tweenVars.ease?.toString() || 'linear.none') as gsap.EaseString,
      repeat: (tweenVars.repeat as number) || 0,
      yoyo: tweenVars.yoyo || false,
      repeatDelay: (tweenVars.repeatDelay as number) || 0,
      delayRemaining: 0,
      iteration: 0,
      isReversed: false,
    };

    // Create a dummy tween for compatibility
    const tween = gsap.to({}, tweenVars);
    this._animations.add(tween);
    return tween;
  }

  fixedUpdate(deltaTime: number) {
    super.fixedUpdate(deltaTime);

    // Update position animation in sync with physics
    if (this._positionAnimation) {
      // Handle repeat delay
      if (this._positionAnimation.delayRemaining > 0) {
        this._positionAnimation.delayRemaining -= deltaTime;
        return;
      }

      this._positionAnimation.elapsed += deltaTime;
      const progress = Math.min(this._positionAnimation.elapsed / this._positionAnimation.duration, 1);

      // Calculate the new position using GSAP's easing
      const easedProgress = gsap.parseEase(this._positionAnimation.ease)(
        this._positionAnimation.isReversed ? 1 - progress : progress,
      );

      const newX =
        this._positionAnimation.startX +
        (this._positionAnimation.targetX - this._positionAnimation.startX) * easedProgress;
      const newY =
        this._positionAnimation.startY +
        (this._positionAnimation.targetY - this._positionAnimation.startY) * easedProgress;

      // Apply movement through physics system
      this.move(newX - this.x, newY - this.y);

      // Handle completion of current iteration
      if (progress >= 1) {
        // Reset elapsed time
        this._positionAnimation.elapsed = 0;

        // Handle repeat logic
        if (
          this._positionAnimation.repeat === -1 ||
          this._positionAnimation.iteration < this._positionAnimation.repeat
        ) {
          this._positionAnimation.iteration++;

          // Handle yoyo
          if (this._positionAnimation.yoyo) {
            this._positionAnimation.isReversed = !this._positionAnimation.isReversed;
          }

          // Apply repeat delay
          if (this._positionAnimation.repeatDelay > 0) {
            this._positionAnimation.delayRemaining = this._positionAnimation.repeatDelay;
          }
        } else {
          // Animation complete
          this._positionAnimation = null;
        }
      }
    }
  }

  public handleActorInteractions(
    deltaX: number,
    deltaY: number,
    ridingActors = this.getAllRiding(),
    potentialCollisions = this.getCollideables<Actor>(deltaX, deltaY),
  ): void {
    for (const actor of potentialCollisions) {
      // Skip actors that are already riding (they were moved with the platform)
      if (ridingActors.has(actor)) continue;

      if (!actor.passThroughTypes.includes(this.type) && !actor.isPassingThrough(this)) {
        // For moving platforms, we need to do a more thorough collision check
        const isColliding = this.collidesWith(actor, deltaX, deltaY);
        const wasColliding = this.collidesWith(actor, 0, 0);

        // If either check detects a collision, handle it
        if (isColliding || wasColliding) {
          // Calculate overlaps
          const overlapX =
            deltaX !== 0
              ? deltaX > 0
                ? this.boundingRect.right - actor.boundingRect.left
                : this.boundingRect.left - actor.boundingRect.right
              : 0;

          const overlapY =
            deltaY !== 0
              ? deltaY > 0
                ? this.boundingRect.bottom - actor.boundingRect.top
                : this.boundingRect.top - actor.boundingRect.bottom
              : 0;

          // For fast-moving platforms, prioritize vertical resolution
          if (Math.abs(deltaY) > Math.abs(deltaX)) {
            if (overlapY !== 0) {
              actor.moveY(overlapY, actor.squish, null, this);
            }
            if (overlapX !== 0) {
              actor.moveX(overlapX, actor.squish, null, this);
            }
          } else {
            if (overlapX !== 0) {
              actor.moveX(overlapX, actor.squish, null, this);
            }
            if (overlapY !== 0) {
              actor.moveY(overlapY, actor.squish, null, this);
            }
          }
        }
      }
    }
  }

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  protected handleCollisionChange(_isColliding?: boolean) {}
}
