import { world, ItemStack } from "@minecraft/server";

// ======= Global State =======
// Stores per player { baseLoc, loc, origType }
const playerPrevLightState = new Map();
// Track which players have toggled this press
const toggledThisUse = new Set();

// Global queue for failed resets
// Each entry: { dimensionId, loc: {x,y,z}, origType }
// const pendingReplacements = [];

const PENDING_KEY = "flashlight.pending";
let pendingReplacements = [];

function loadPending() {
  const saved = world.getDynamicProperty(PENDING_KEY);
  if (saved) {
    try {
      pendingReplacements = JSON.parse(saved);
    } catch {
      pendingReplacements = [];
    }
  }
}

loadPending();

const MAX_DIST_WITH_RAYCAST_HIT = 18;
const MAX_DIST_NO_RAYCAST_HIT = 10;

// Offset per face
const faceOffsets = {
  Up: { x: 0, y: 1, z: 0 },
  Down: { x: 0, y: -1, z: 0 },
  North: { x: 0, y: 0, z: -1 },
  South: { x: 0, y: 0, z: 1 },
  West: { x: -1, y: 0, z: 0 },
  East: { x: 1, y: 0, z: 0 },
};

function positionsEqual(a, b) {
  return a.x === b.x && a.y === b.y && a.z === b.z;
}

export function flashlightSwitch() {
  world.afterEvents.itemStartUse.subscribe((ev) => {
    const player = ev.source;
    // Already toggled this press?
    if (toggledThisUse.has(player.id)) return;

    const item = ev.itemStack;
    if (!item) return;
    if (item.typeId === "hb_hr:flashlight_on" || item.typeId === "hb_hr:flashlight_off") {
      toggleFlashlightInMainhand(player);
      toggledThisUse.add(player.id);
    }
  });

  world.afterEvents.itemStopUse.subscribe((ev) => {
    // On release, clear so next press can toggle again
    toggledThisUse.delete(ev.source.id);
  });
}

// ======= Main Flashlight Logic =======
export function flashlight(player) {
  const saved = world.getDynamicProperty(PENDING_KEY);
  if (saved) {
    try {
      pendingReplacements = JSON.parse(saved);
    } catch {}
  }
  // On each new player join:
  world.afterEvents.playerSpawn.subscribe((ev) => {
    processPendingReplacementsForPlayer(ev.player);
  });

  const dimension = world.getDimension(player.dimension.id);

  // 1) Get raycast
  const hit = player.getBlockFromViewDirection({
    maxDistance: MAX_DIST_WITH_RAYCAST_HIT,
    includePassableBlocks: false,
    includeLiquidBlocks: false,
  });

  // 2) Determine baseLoc
  let baseLoc;
  if (hit) {
    const off = faceOffsets[hit.face] || faceOffsets.Up;
    const b = hit.block.location;
    baseLoc = { x: b.x + off.x, y: b.y + off.y, z: b.z + off.z };
  } else {
    const { x: pitch, y: yaw } = player.getRotation();
    const radYaw = ((yaw + 90) * Math.PI) / 180;
    const radPitch = -(pitch * Math.PI) / 180;
    const dx = Math.cos(radYaw) * Math.cos(radPitch);
    const dy = Math.sin(radPitch);
    const dz = Math.sin(radYaw) * Math.cos(radPitch);
    baseLoc = {
      x: Math.floor(player.location.x + dx * MAX_DIST_NO_RAYCAST_HIT),
      y: Math.floor(player.location.y + dy * MAX_DIST_NO_RAYCAST_HIT),
      z: Math.floor(player.location.z + dz * MAX_DIST_NO_RAYCAST_HIT),
    };
  }

  // 3) Check previous state
  const prevState = playerPrevLightState.get(player.id);
  let candidateLoc, origType;
  if (prevState && positionsEqual(prevState.baseLoc, baseLoc)) {
    // same baseLoc as before → reuse
    candidateLoc = prevState.loc;
    origType = prevState.origType;
  } else {
    // 4) Search for new candidateLoc
    const checkPoints = generateCheckPoints();
    let found = false;
    for (const off of checkPoints) {
      const loc = { x: baseLoc.x + off.x, y: baseLoc.y + off.y, z: baseLoc.z + off.z };
      const blk = dimension.getBlock(loc);
      if (!blk) {
        // Chunk not loaded → attempt reset and queue
        break;
      }
      if (blk.typeId === "minecraft:air") {
        candidateLoc = loc;
        origType = "minecraft:air";
        found = true;
        break;
      }
    }
    if (!found) {
      // Abort: reset old light block (or queue)
      if (prevState) resetOrQueue(prevState, player.id, dimension.id);
      return;
    }
  }

  // 5) Reset old light block if position has changed
  if (prevState && !positionsEqual(prevState.loc, candidateLoc)) {
    resetOrQueue(prevState, player.id, dimension.id);
  }

  // 6) Set new light block
  const target = dimension.getBlock(candidateLoc);
  if (target && target.typeId !== "minecraft:light_block_15") {
    target.setType("minecraft:light_block_15");

    // — Debug: current queue before removal
    // if (prevState) {
    // world.sendMessage(`Before removal, pending (${pendingReplacements.length}): ` + pendingReplacements.map((p) => `(${p.loc.x},${p.loc.y},${p.loc.z})`).join(" | "));
    // }

    // — Remove old entry via splice
    if (prevState) {
      for (let i = pendingReplacements.length - 1; i >= 0; i--) {
        const p = pendingReplacements[i];
        if (p.dimensionId === player.dimension.id && sameLoc(p.loc, prevState.loc)) {
          pendingReplacements.splice(i, 1);
          // world.sendMessage(`Removed old pending at ${prevState.loc.x},${prevState.loc.y},${prevState.loc.z}`);
        }
      }
    }

    // — Create new entry
    pendingReplacements.push({
      playerId: player.id, // ← new
      dimensionId: player.dimension.id,
      loc: candidateLoc,
      origType: origType,
    });

    // — Debug: queue after push
    // world.sendMessage(`After push, pending (${pendingReplacements.length}): ` + pendingReplacements.map((p) => `(${p.loc.x},${p.loc.y},${p.loc.z})`).join(" | "));

    // — Save
    savePending();
  }

  // 7) Save state
  playerPrevLightState.set(player.id, {
    baseLoc,
    loc: candidateLoc,
    origType,
  });
}
// Helper function: generates your 3×3 area (+ vertical etc.)
function generateCheckPoints() {
  return [
    { x: 0, y: 0, z: 0 },
    { x: 0, y: 1, z: 0 },
    { x: 0, y: 2, z: 0 },
    { x: 0, y: -1, z: 0 },
    { x: 0, y: -2, z: 0 },
    { x: 1, y: 0, z: 0 },
    { x: -1, y: 0, z: 0 },
    { x: 0, y: 0, z: 1 },
    { x: 0, y: 0, z: -1 },
    { x: 0, y: 1, z: 1 },
    { x: 0, y: 1, z: -1 },
    { x: 1, y: 1, z: 0 },
    { x: -1, y: 1, z: 0 },
    { x: 1, y: 1, z: 1 },
    { x: -1, y: 1, z: 1 },
    { x: 1, y: 1, z: -1 },
    { x: -1, y: 1, z: -1 },
    { x: 2, y: 0, z: 0 },
    { x: -2, y: 0, z: 0 },
    { x: 0, y: 0, z: 2 },
    { x: 0, y: 0, z: -2 },
    { x: 2, y: 1, z: 0 },
    { x: -2, y: 1, z: 0 },
    { x: 0, y: 1, z: 2 },
    { x: 0, y: 1, z: -2 },
    { x: 1, y: 2, z: 1 },
    { x: -1, y: 2, z: 1 },
    { x: 1, y: 2, z: -1 },
    { x: -1, y: 2, z: -1 },
    { x: 2, y: 0, z: 2 },
    { x: -2, y: 0, z: 2 },
    { x: 2, y: 0, z: -2 },
    { x: -2, y: 0, z: -2 },
    { x: 2, y: 2, z: 0 },
    { x: -2, y: 2, z: 0 },
    { x: 0, y: 2, z: 2 },
    { x: 0, y: 2, z: -2 },
    { x: 2, y: 2, z: 2 },
    { x: -2, y: 2, z: 2 },
    { x: 2, y: 2, z: -2 },
    { x: -2, y: 2, z: -2 },
  ];
}

// Helper function: try to reset, otherwise queue + remove prevState
function resetOrQueue(state, playerId, dimensionId) {
  const dim = world.getDimension(dimensionId);
  try {
    const block = dim.getBlock(state.loc);
    if (!block) throw new Error("Chunk/Block not loaded");
    block.setType(state.origType);
  } catch (e) {
    pendingReplacements.push({
      playerId: playerId, // ← new
      dimensionId,
      loc: state.loc,
      origType: state.origType,
    });
    savePending();
  }
  playerPrevLightState.delete(playerId);
}

// ======= Player-specific queue processing =======
function processPendingReplacementsForPlayer(player) {
  const dim = world.getDimension(player.dimension.id);
  const onlineIds = new Set(world.getPlayers().map((p) => p.id));

  // ── 1) Handle anyone who is now offline ──
  for (let i = pendingReplacements.length - 1; i >= 0; i--) {
    const p = pendingReplacements[i];
    if (!onlineIds.has(p.playerId)) {
      // That player has disconnected—attempt to reset their light block now:
      let didReset = false;
      try {
        const deadDim = world.getDimension(p.dimensionId);
        const blk = deadDim.getBlock(p.loc);
        if (blk && blk.typeId === "minecraft:light_block_15") {
          blk.setType(p.origType);
        }
        // If we reached this line without throwing, the chunk was loaded,
        // so we consider the reset “successful” (even if the block type
        // wasn’t exactly what we expected).
        didReset = true;
      } catch {
        // Chunk was unloaded or something else went wrong. Do NOT splice here;
        // leave the entry in pendingReplacements so we can retry later.
      }

      if (didReset) {
        pendingReplacements.splice(i, 1);
      }
    }
  }

  // ── 2) Now handle any remaining entries that belong to _this_ player in this dimension ──
  for (let i = pendingReplacements.length - 1; i >= 0; i--) {
    const p = pendingReplacements[i];
    if (p.playerId !== player.id || p.dimensionId !== player.dimension.id) continue;

    const blk = dim.getBlock(p.loc);
    if (blk && blk.typeId === "minecraft:light_block_15") {
      try {
        blk.setType(p.origType);
      } catch {
        // Chunk got unloaded mid‐reset. In that case, skip splicing and let it retry next time.
        continue;
      }
      pendingReplacements.splice(i, 1);
    } else if (blk && blk.typeId !== "minecraft:light_block_15") {
      // Someone else changed that block already—just drop our pending entry.
      pendingReplacements.splice(i, 1);
    }
  }

  savePending();
}

// (2) The standalone “offline” function from above:
function processPendingReplacementsForOfflinePlayers() {
  const onlineIds = new Set(world.getPlayers().map((p) => p.id));

  for (let i = pendingReplacements.length - 1; i >= 0; i--) {
    const entry = pendingReplacements[i];
    if (!onlineIds.has(entry.playerId)) {
      let didReset = false;
      try {
        const deadDim = world.getDimension(entry.dimensionId);
        const blk = deadDim.getBlock(entry.loc);
        if (blk && blk.typeId === "minecraft:light_block_15") {
          blk.setType(entry.origType);
        }
        didReset = true;
      } catch {
        // Chunk unloaded → don’t splice yet
      }
      if (didReset) {
        pendingReplacements.splice(i, 1);
      }
    }
  }

  savePending();
}

export function flashlightHandler(player) {
  // 1) Get the equippable component
  const equip = player.getComponent("equippable");
  if (!equip) return;
  const mainhandItem = equip.getEquipment("Mainhand");
  // 3) If the correct item is in hand → toggle light on/off
  if (mainhandItem?.typeId === "hb_hr:flashlight_on") {
    processPendingReplacementsForOfflinePlayers();
    flashlight(player);
  } else {
    // 4) Item put away: reset old state / queue
    const prevState = playerPrevLightState.get(player.id);
    if (prevState) {
      resetOrQueue(prevState, player.id, player.dimension.id);
      playerPrevLightState.delete(player.id);
    }
    // 5) Process all remaining pending resets
    processPendingReplacementsForPlayer(player);
  }
}

function toggleFlashlightInMainhand(player) {
  // 1) Get the equippable component
  const equip = player.getComponent("equippable");
  if (!equip) return;
  const current = equip.getEquipment("Mainhand");

  if (!current) return;

  player.runCommand("/replaceitem entity @s slot.weapon.mainhand 0 air");
  let newId = null;
  if (current.typeId === "hb_hr:flashlight_on") {
    newId = "hb_hr:flashlight_off";
  } else if (current.typeId === "hb_hr:flashlight_off") {
    newId = "hb_hr:flashlight_on";
  }
  if (!newId) return;

  // Preserve count and any custom data
  const replacement = new ItemStack(newId);
  equip.getEquipmentSlot("Mainhand").setItem(replacement);
}

function savePending() {
  world.setDynamicProperty(PENDING_KEY, JSON.stringify(pendingReplacements));
}

function sameLoc(a, b) {
  return a.x === b.x && a.y === b.y && a.z === b.z;
}
