import { system, world, EquipmentSlot, EasingType } from "@minecraft/server";
import { ConstantAnimation } from "./animations";
import { getWeaponType, dashPlayer, isHoldingWeapon, isRangedWeapon, shootMoveRayAndHit} from "./combat_mechanic";
import { parryStates, activateParry, parryCooldowns} from "./parry";
import { usingSpell } from "./items";
import { usingSigns } from "./signs";
import { ENABLE_NON_COMBAT_WEAKNESS, NON_COMBAT_WEAKNESS, ENABLE_PARRY, ENABLE_DASHING, DASH_COOLDOWN, CombatAnimation, PARRY_WINDOW_TICKS } from "./combat_config";

// track last played animation per player
export const currentAnimations = new Map();
export const animationCooldowns = new Map();
// store attack states
export const attackStates = new Map();
export const rollState = new Map();
export const comboStates = new Map();  
export const combatPlayed = new Map();
export const comboResetTasks = new Map(); // track scheduled resets per player

system.runInterval(() => {
  for (const player of world.getPlayers()) {
    // if (player.getGameMode() === "creative") continue;

    if (player.getDynamicProperty("combat_state") && player.getDynamicProperty("camera_preset") === "cinematic") {
      setFreeCamera(player);
    }
    const now = system.currentTick;
    if (!player.getDynamicProperty("combat_state")) {
        if (NON_COMBAT_WEAKNESS) {
          player.addEffect("weakness", 60, {
              amplifier: NON_COMBAT_WEAKNESS,
              showParticles: false
          });
        }
        continue;
    };

    if (!player.isInWater && player.isJumping && !player.isOnGround && !player.hasTag("dash")) {
      if (!player.isOnGround && player.hasTag("dash") || !ENABLE_DASHING) return;
      const moveDir = getMoveDirection(player);
      if (shootMoveRayAndHit(player, 3)) return;
      if (moveDir === "forward") {
        dashPlayer(player, DASH_COOLDOWN);
        rollState.set(player.id, DASH_COOLDOWN, "step_front");
        playAnimationWithClock(player, AnimationHelper("animation.combat.step_front", player), DASH_COOLDOWN);
        system.runTimeout(() => {
          rollState.delete(player.id);
        }, DASH_COOLDOWN); // Tick delay of 5 seconds
      } else if (moveDir === "backward") {
        dashPlayer(player, DASH_COOLDOWN, "step_back");
        rollState.set(player.id, DASH_COOLDOWN);
        playAnimationWithClock(player, AnimationHelper("animation.combat.step_back", player), DASH_COOLDOWN);
        system.runTimeout(() => {
          rollState.delete(player.id);
        }, DASH_COOLDOWN); // Tick delay of 5 seconds
      } else if (moveDir === "left") {
        dashPlayer(player, DASH_COOLDOWN, "left");
        rollState.set(player.id, DASH_COOLDOWN);
        playAnimationWithClock(player, AnimationHelper("animation.combat.step_right", player), DASH_COOLDOWN);
        system.runTimeout(() => {
          rollState.delete(player.id);
        }, DASH_COOLDOWN); // Tick delay of 5 seconds
      } else if (moveDir === "right") {
        dashPlayer(player, DASH_COOLDOWN, "right");
        rollState.set(player.id, DASH_COOLDOWN);
        playAnimationWithClock(player, AnimationHelper("animation.combat.step_left", player), DASH_COOLDOWN);
        system.runTimeout(() => {
          rollState.delete(player.id);
        }, DASH_COOLDOWN); // Tick delay of 5 seconds
      }
    }

    if (player.isSneaking && isHoldingWeapon(player)) {
      if (!ENABLE_PARRY) continue;
      if ((parryCooldowns.get(player.id) ?? 0) > now) {
        system.runTimeout(() => {
          player.removeTag("ae_fpm.disable_animation");
        }, PARRY_WINDOW_TICKS - 5);
        continue;
      } else {
        player.addTag("ae_fpm.disable_animation");
      }
      // Activate parry if not already active
      if (!parryStates.has(player.id)) {
        activateParry(player);
      }
    }

    // --- decide animation ---
    const expiry = attackStates.get(player.id);
    const rolling = rollState.get(player.id);
    const parry = parryStates.get(player.id);
    const useSpell = usingSpell.get(player.id);
    const useSign = usingSigns.get(player.id);
    const isHoldingRangeWeapon = isRangedWeapon(player);

    if (player.getDynamicProperty("camera_preset") === "dynamic_df"){
      if (isHoldingRangeWeapon || player.isInWater){
        player.removeTag("ae_fpm.disable_animation");
      } else {
        player.addTag("ae_fpm.disable_animation");
      }
    }

    if (rolling || parry || useSpell || useSign || isHoldingRangeWeapon || player.isInWater) continue;
    let animResult;

    if (expiry && expiry > system.currentTick) {
      // Only play combat animation once
      if (!combatPlayed.get(player.id)) {
        animResult = resolveNextCombatAnimation(player);
        combatPlayed.set(player.id, true); // mark as played
      } else {
        // combat already played, skip
        continue;
      }
    } else {
      animResult = resolveNextAnimation(player);
      combatPlayed.delete(player.id); // reset for next attack
    }
    if (animResult.nextAnim === null) return;
    playAnimationWithClock(player, animResult.nextAnim, animResult.duration);
  }
});
function setFreeCamera(player) {
    const offsetBack = player.getDynamicProperty("camera_forward");   // how far behind
    const offsetRight = player.getDynamicProperty("camera_horizontal");  // how far to the right
    const offsetUp = player.getDynamicProperty("camera_vertical");     // how far above
    const offsetPitch = player.getDynamicProperty("camera_pitch") || 15;  // x rotation
    const offsetYaw = player.getDynamicProperty("camera_yaw") || 0;       // y rotation

    const head = player.getHeadLocation();
    const rot = player.getRotation();
    const yawRad = (rot.y * Math.PI) / 180;

    // Player forward vector (backward is positive offsetBack)
    const forwardX = -Math.sin(yawRad);
    const forwardZ = Math.cos(yawRad);

    // Player right vector (flip horizontal)
    const rightX = -Math.cos(yawRad);  // flip sign here
    const rightZ = -Math.sin(yawRad);  // flip sign here

    // Combine vectors
    const offsetX = forwardX * offsetBack + rightX * offsetRight;
    const offsetZ = forwardZ * offsetBack + rightZ * offsetRight;

    player.camera.setCamera("minecraft:free", {
        location: { 
            x: head.x + offsetX, 
            y: head.y + offsetUp,  // vertical moves camera up
            z: head.z + offsetZ 
        },
        rotation: { 
            x: rot.x + offsetPitch,  // dynamic pitch
            y: rot.y + offsetYaw      // dynamic yaw
        },
        easeOptions: {
            easeTime: 0.1,
            easeType: EasingType.sine,
        },
    });
}



export function resolveNextCombatAnimation(player) {
  const expiry = attackStates.get(player.id);
  const comboState = comboStates.get(player.id);
  if (!comboState) {
    // no combo state yet → just idle fallback
    return { 
      nextAnim: null, 
      duration: 20 
    };
  }
  const comboInfo = CombatAnimation[comboState.weaponType];
  let nextAnim = CombatAnimation.hand.base + "1"; // fallback
  let duration = CombatAnimation.hand.cooldown;

  if (expiry && expiry > system.currentTick && comboState) {
    nextAnim = `${comboInfo.base}${comboState.index}`;
    duration = comboInfo.cooldown;
  } else {
    // expired → reset
    comboStates.delete(player.id);
  }

  // append "_have_weapon" if holding weapon
  if (isHoldingWeapon(player)) {
    nextAnim = `${nextAnim}_weapon`;
  }

  if (player.getDynamicProperty("camera_preset") === "dynamic_df") {
    nextAnim =  nextAnim + "_dnf";
  }
  return { nextAnim, duration };
}


export function resolveNextAnimation(player) {
  const walking = isWalking(player);
  let nextAnim = ConstantAnimation.idle;
  let duration = 10;

  if (player.isSprinting) {
    nextAnim = ConstantAnimation.sprint;
  } else if (walking) {
    nextAnim = ConstantAnimation.walk;
  }

  // safely read held item typeId
  const item = player.getComponent("minecraft:equippable")
                    ?.getEquipment(EquipmentSlot.Mainhand);
  const typeId = item?.typeId ?? "";

  const weaponType = getWeaponType(typeId);

  if (weaponType && weaponType !== "hand") {
    // e.g. idle_sword, walk_axe, sprint_pickaxe
    nextAnim = `${nextAnim}_${weaponType}`;
  }

  if (player.getDynamicProperty("camera_preset") === "dynamic_df") {
    nextAnim =  nextAnim + "_dnf";
  }
  return { nextAnim, duration };
}

/**
 * Play an animation with cooldown and blending,
 * but if the state changes, switch immediately.
 */
export function playAnimationWithClock(player, animation, duration, options = {}) {
  const lastAnim = currentAnimations.get(player.id);
  const key = player.id + ":" + animation;

  // if same animation is already playing and still cooling down → do nothing
  if (lastAnim === animation && animationCooldowns.has(key)) return;

  // play immediately if different state or cooldown expired
  player.playAnimation(animation, {
    blendOutTime: 0.1,
    ...options,
  });

  // update trackers
  currentAnimations.set(player.id, animation);
  animationCooldowns.set(key, true);

  // clear cooldown after duration
  system.runTimeout(() => {
    animationCooldowns.delete(key);
  }, duration);
}

export function isWalking(player) {
  const velocity = player.getVelocity();
  const isRiding = player.getComponent("minecraft:riding");

  const horizontalSpeed = Math.sqrt(velocity.x ** 2 + velocity.z ** 2);

  return (
    horizontalSpeed > 0.05 && // movement threshold
    !isRiding &&
    !player.isSneaking &&
    player.isOnGround
  );
}

export function getMoveDirection(player) {
    const velocity = player.getVelocity();
    const dir = player.getViewDirection();

    const vx = velocity.x;
    const vz = velocity.z;
    const dx = dir.x;
    const dz = dir.z;

    const speed = Math.sqrt(vx * vx + vz * vz);
    if (speed < 0.15) return;

    // Normalize velocity
    const len = Math.sqrt(vx * vx + vz * vz);
    const nvx = vx / len;
    const nvz = vz / len;

    // Normalize look direction
    const dlen = Math.sqrt(dx * dx + dz * dz);
    const ndx = dx / dlen;
    const ndz = dz / dlen;

    // Dot product → forward/backward
    const dot = nvx * ndx + nvz * ndz;

    // Cross product (2D) → left/right
    // >0 = right, <0 = left (relative to look direction)
    const cross = nvx * ndz - nvz * ndx;

    if (dot > 0.5) return "forward";
    if (dot < -0.5) return "backward";
    if (cross > 0) return "right";
    if (cross < 0) return "left";

    return "backward"; // fallback (should rarely hit)
}


export function AnimationHelper (animationString, player) {
  let animation = "";
  if (player.getDynamicProperty("camera_preset") === "dynamic_df") {
    animation =  animationString + "_dnf";
  } else {
    animation = animationString;
  }

  return animation;
}