import { world, system } from "@minecraft/server";
import vehiclesValues from "./vehicleValues.js";

const planeSpeeds = new Map(); // Map for storing speed tied to player
const lastEnvironment = new Map(); // Track the last environment for each player
const lastGroundTime = new Map(); // Track when players were last on ground

const barrelRollCooldown = 3000; // Cooldown for barrel roll
const doubleTapInterval = 80; // Time window for detecting a double tap
const maxHoldTime = 200; // Maximum time to hold jump before resetting
let lastJumpReleaseTime = 0;
let lastJumpPressTime = 0;
let lastBarrelRollTime = 0;
let jumpReleased = true; // Starts as true to allow detecting the first press
let jumpTapCount = 0;

// Check if the player is riding a plane and return vehicle data
function getVehicleData(player) {
  const vehicle = player.getComponent("minecraft:riding")?.entityRidingOn;
  if (!vehicle) return null; // If no vehicle, return null

  const vehicleData = Object.values(vehiclesValues).find(
    (v) => v.typeId === vehicle.typeId && v.type === "plane"
  );

  if (!vehicleData) return null;

  // Get the list of passengers
  const passengers = vehicle.getComponent("minecraft:rideable")?.getRiders();
  if (!passengers || passengers[0] !== player) {
    return null; // Player is NOT the first rider (driver), ignore input
  }

  return { vehicle, vehicleData };
}

// Determine the environment based on block below the vehicle
function getEnvironment(player, vehicle, currentTime) {
  const x = Math.floor(vehicle.location.x);
  const y = Math.floor(vehicle.location.y - 0.5);
  const z = Math.floor(vehicle.location.z);

  // Avoid error by checking Y height bounds
  if (y < -64 || y > 319) {
    return {
      currentEnvironment: "envAir",
      previousEnvironment: lastEnvironment.get(player.name) || "envAir",
    };
  }

  const blockBelow = vehicle.dimension.getBlock({ x, y, z });
  const blockBelowTypeId = blockBelow?.type?.id;

  const environmentMapping = {
    "minecraft:soul_sand": "envSlow",
    "minecraft:slime": "envSlow",
    "minecraft:honey_block": "envSlow",
    "minecraft:cobweb": "envSlow",
    "minecraft:ice": "envIce",
    "minecraft:packed_ice": "envIce",
    "minecraft:blue_ice": "envIce",
  };

  let currentEnvironment = "envAir";
  if (environmentMapping[blockBelowTypeId]) {
    lastGroundTime.set(player.name, currentTime);
    currentEnvironment = environmentMapping[blockBelowTypeId];
  } else if (vehicle.isInWater) {
    lastGroundTime.set(player.name, currentTime);
    currentEnvironment = "envWater";
  } else if (vehicle.isOnGround) {
    lastGroundTime.set(player.name, currentTime);
    currentEnvironment = "envGround";
  }

  const previousEnvironment = lastEnvironment.get(player.name) || "envAir";
  lastEnvironment.set(player.name, currentEnvironment);

  return { currentEnvironment, previousEnvironment };
}

function calculateImpulse(
  player,
  vehicle,
  environment,
  vehicleData,
  speed,
  impulse,
  isJumping,
  hasFuel,
  isFlying
) {
  let {
    groundAcceleration,
    maxGroundSpeed,
    airAcceleration,
    maxAirSpeed,
    waterVehicle,
  } = vehicleData;
  let viewAngle = player?.getViewDirection().y.toFixed(2);
  if (isJumping && hasFuel) {
    if (
      isFlying &&
      viewAngle < -0.25 &&
      viewAngle > -0.75 &&
      speed < maxAirSpeed * 1.1
    ) {
      let swoopBoost = Math.abs(viewAngle / 15);
      impulse += swoopBoost;
      if (speed > maxAirSpeed) {
        impulse -= airAcceleration * 2;
      }
      planeSpeeds.set(player.name, impulse);
    }
    if (
      (environment === "envGround" && !waterVehicle) ||
      (environment === "envWater" && waterVehicle)
    ) {
      if (speed < maxGroundSpeed) impulse += groundAcceleration;
      else if (speed > maxGroundSpeed) impulse -= groundAcceleration;
    } else if (environment === "envSlow") {
      impulse +=
        speed < maxGroundSpeed / 3 ? groundAcceleration / 3 : -impulse * 0.3;
    } else if (environment === "envIce") {
      impulse +=
        speed < maxGroundSpeed ? groundAcceleration / 10 : -impulse * 0.3;
    } else if (environment === "envAir") {
      if (speed < maxAirSpeed) impulse += airAcceleration;
      else if (speed > maxAirSpeed * 2.5) impulse -= airAcceleration * 10;
      else if (speed > maxAirSpeed * 1.5) impulse -= airAcceleration * 5;
      else if (speed > maxAirSpeed) impulse -= airAcceleration * 2;
    }

    if (speed < 0.1 && impulse > 0.1) {
      impulse = 0;
    }
  } else {
    impulse = reduceImpulse(
      maxAirSpeed,
      environment,
      impulse,
      groundAcceleration,
      airAcceleration,
      speed
    );
  }

  return Math.max(impulse, 0);
}

function reduceImpulse(
  maxAirSpeed,
  environment,
  impulse,
  groundAcceleration,
  airAcceleration,
  speed
) {
  const decelerationFactor = {
    envGround: groundAcceleration / 2,
    envWater: groundAcceleration / 2,
    envSlow: groundAcceleration * 3,
    envIce: groundAcceleration * 10,
    envAir: airAcceleration / 5,
  }[environment];

  if (speed > maxAirSpeed) {
    impulse -= airAcceleration * 2;
  }

  return speed > 0.1 ? impulse - decelerationFactor : 0;
}

function applyImpulse(player, vehicle, impulse) {
  const viewDirection = player.getViewDirection();
  vehicle.applyImpulse({
    x: viewDirection.x * impulse,
    y: 0,
    z: viewDirection.z * impulse,
  });

  planeSpeeds.set(player.name, impulse);
}

system.runInterval(() => {
  const currentTime = Date.now();

  for (const player of world.getPlayers()) {
    const vehicleCheck = getVehicleData(player);
    if (vehicleCheck) {
      const { vehicle, vehicleData } = vehicleCheck;

      const { currentEnvironment, previousEnvironment } = getEnvironment(
        player,
        vehicle,
        currentTime
      );
      const hasFuel = vehicle?.getProperty("pb_plane:fuel");
      if (player.isJumping && !hasFuel) {
        player.onScreenDisplay.setActionBar(
          "Please Insert Fuel Into Planes Top Left Equipment Slot To Fly"
        );
      }
      const speed = Math.sqrt(
        vehicle.getVelocity().x ** 2 + vehicle.getVelocity().z ** 2
      );
      let impulse = planeSpeeds.get(player.name) || 0;

      const isFlying =
        currentEnvironment === "envAir" &&
        (currentTime - lastGroundTime.get(player.name)) / 1000 > 2;

      if (
        previousEnvironment === "envGround" &&
        currentEnvironment === "envAir"
      ) {
        impulse *= 0.75; // Reduce impulse on transition to air
      }

      const currentFlyingState = vehicle.getProperty("pb_plane:flying");
      if (isFlying && !currentFlyingState) {
        vehicle.setProperty("pb_plane:flying", true); // Flying state updated to true
      }
      if (!isFlying && currentFlyingState) {
        vehicle.setProperty("pb_plane:flying", false); // Flying state updated to false
      }
      impulse = calculateImpulse(
        player,
        vehicle,
        currentEnvironment,
        vehicleData,
        speed,
        impulse,
        player.isJumping,
        hasFuel,
        isFlying
      );
      let trickBoost = vehicleData.trickBoost;
      let canTrick = vehicleData.canTrick;

      if (isFlying) {
        if (player.isJumping) {
          // Record the first time the jump button is pressed
          if (jumpReleased) {
            lastJumpPressTime = Date.now();
            jumpReleased = false; // Indicate the button is now held
          }

          // Check if the jump is being held for too long
          if (Date.now() - lastJumpPressTime > maxHoldTime) {
            jumpTapCount = 0; // Reset the jump tap count if held too long
          }
        } else {
          // When the jump button is released
          if (!jumpReleased) {
            jumpReleased = true; // Mark the button as released
            lastJumpReleaseTime = Date.now();

            // Handle double-tap detection
            if (Date.now() - lastJumpPressTime <= maxHoldTime) {
              if (Date.now() - lastJumpReleaseTime <= doubleTapInterval) {
                jumpTapCount++;
              } else {
                jumpTapCount = 1; // Start a new tap count
              }

              // Perform barrel roll on double-tap
              if (jumpTapCount === 1 && canTrick) {
                if (Date.now() - lastBarrelRollTime >= barrelRollCooldown) {
                  vehicle.playAnimation(
                    "animation.pb_plane.plane.barrel_roll",
                    {
                      blendOutTime: 1,
                      stopExpression: "query.any_animation_finished",
                    }
                  );

                  impulse += trickBoost;
                  lastBarrelRollTime = Date.now(); // Update cooldown
                }

                // Reset tap count after performing the barrel roll
                jumpTapCount = 0;
              }
            }
          }
        }
      }

      if (takeoffSpeed(player, vehicle, hasFuel, speed, vehicleData)) {
        takeOffFlight(player, vehicle, hasFuel);
      }
      if (player.hasTag("pb_plane_dev_info")) {
        console.warn(
          jumpTapCount,
          isFlying,
          currentEnvironment,
          parseFloat(speed.toFixed(2)),
          vehicleData.take_off,
          parseFloat(impulse.toFixed(2)),
          player?.getViewDirection().y.toFixed(2)
        );
      }

      applyImpulse(player, vehicle, impulse);
    } else {
      planeSpeeds.set(player.name, 0); // Reset speed if player is not in a vehicle
    }
  }
});

function takeoffSpeed(player, vehicle, hasFuel, speed, vehicleData) {
  if (!hasFuel) {
    player.setDynamicProperty("canTakeOff", false);
    return;
  }

  let canTakeOff = player.getDynamicProperty("canTakeOff");
  const waterVehicle = vehicleData.waterVehicle;

  if (waterVehicle) {
    if (vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar(
        "This Plane Needs To Be On Water To Take Off"
      );
      player.setDynamicProperty("canTakeOff", false);
    }
    if (speed === 0 && vehicle.isInWater) {
      player.onScreenDisplay.setActionBar("Hold Jump To Accelerate");
      player.setDynamicProperty("canTakeOff", false);
    }
    if (speed > 0 && speed < vehicleData.take_off && vehicle.isInWater) {
      player.onScreenDisplay.setActionBar("Increase Speed To Take-Off");
      player.setDynamicProperty("canTakeOff", false);
    }
    if (speed >= vehicleData.take_off && vehicle.isInWater) {
      player.onScreenDisplay.setActionBar("Look Up To Take-Off");
      player.setDynamicProperty("canTakeOff", true);
    }
    if (speed >= vehicleData.take_off && !vehicle.isInWater) {
      player.setDynamicProperty("canTakeOff", true);
    }
  } else {
    if (speed === 0 && vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Hold Jump To Accelerate");
      player.setDynamicProperty("canTakeOff", false);
    }
    if (speed > 0 && speed < vehicleData.take_off && vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Increase Speed To Take-Off");
      player.setDynamicProperty("canTakeOff", false);
    }
    if (speed >= vehicleData.take_off && vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Look Up To Take-Off");
      player.setDynamicProperty("canTakeOff", true);
    }
    if (speed >= vehicleData.take_off && !vehicle.isOnGround) {
      player.setDynamicProperty("canTakeOff", true);
    }
  }

  return canTakeOff;
}

function takeOffFlight(player, vehicle, hasFuel) {
  const viewDirection = parseFloat(player?.getViewDirection().y.toFixed(2));
  const events = [
    {
      condition: hasFuel && viewDirection >= 0.5 && player.isJumping, // player is jumping and moving upwards fast
      effect: "ascend_fast",
      amplifier: Math.floor(viewDirection * 0.35 * 100),
    },
    {
      condition:
        hasFuel &&
        viewDirection >= 0.2 &&
        viewDirection < 0.5 &&
        player.isJumping, // player is jumping and moving upwards slowly
      effect: "ascend_slow",
      amplifier: viewDirection,
    },
    {
      condition:
        hasFuel &&
        viewDirection >= -0.2 &&
        viewDirection < 0.2 &&
        player.isJumping, // player is jumping and holding altitude
      effect: "maintain",
    },
    {
      condition:
        hasFuel &&
        viewDirection >= -0.5 &&
        viewDirection < -0.2 &&
        player.isJumping, // player is jumping and flying downwards slowly
      effect: "descend_slow",
      amplifier: 1,
    },
    {
      condition:
        hasFuel &&
        viewDirection < -0.5 &&
        viewDirection >= -0.75 &&
        player.isJumping, // player is jumping and flying downwards fast
      effect: "descend_fast",
      amplifier: 0,
    },
    {
      condition: viewDirection >= -0.75 && (!player.isJumping || !hasFuel),
      effect: "gliding",
      amplifier: 1, // Player is not jumping, or out of fuel - glide
    },
    { condition: viewDirection < -0.75, effect: "descend_fall", amplifier: 0 }, // If player is looking straight down
  ];
  vehicle?.removeEffect("levitation");
  vehicle?.removeEffect("slow_falling");

  const { effect, amplifier } = events.find(({ condition }) => condition) || {};

  if (effect === "ascend_fast") {
    if (!vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Plane Ascending Fast");
    }
    vehicle?.addEffect("levitation", 20, {
      amplifier: amplifier,
      showParticles: false,
    });
  }
  if (effect === "ascend_slow") {
    if (!vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Plane Ascending");
    }
    const ascendValue = Math.max(0.08, Math.min(viewDirection / 2, 0.12));
    vehicle?.applyImpulse({ x: 0, y: ascendValue, z: 0 });
  }
  if (effect === "maintain") {
    vehicle?.applyImpulse({ x: 0, y: 0.0784, z: 0 });
    if (!vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Holding Altitude");
    }
  }
  if (effect == "descend_slow") {
    if (!vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Plane Descending");
    }
    vehicle?.addEffect("slow_falling", 20, {
      amplifier: amplifier,
      showParticles: false,
    });
  }
  if (effect == "descend_fast") {
    if (!vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Plane Descending Fast");
    }
    vehicle?.addEffect("slow_falling", 20, {
      amplifier: amplifier,
      showParticles: false,
    });
  }
  if (effect == "descend_fall") {
    if (!vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Plane Plummeting");
    }
    vehicle?.removeEffect("levitation");
    vehicle?.removeEffect("slow_falling");
  }
  if (effect == "gliding") {
    if (!vehicle.isOnGround) {
      player.onScreenDisplay.setActionBar("Plane Gliding");
    }
    vehicle?.addEffect("slow_falling", 20, {
      amplifier: amplifier,
      showParticles: false,
    });
  }
}
