;(() => {
  const ROT_LABEL = ['北', '东', '南', '西']
  const ROT_KEY = ['north', 'east', 'south', 'west']

  const KEY = {
    active: 'barn_plan_active',
    planKey: 'barn_plan_struct_key',
    x: 'barn_plan_x',
    y: 'barn_plan_y',
    z: 'barn_plan_z',
    rot: 'barn_plan_rot',

    scanActive: 'barn_scan_active',
    scanPlanKey: 'barn_scan_plan_key',
    scanOx: 'barn_scan_ox',
    scanOy: 'barn_scan_oy',
    scanOz: 'barn_scan_oz',
    scanRot: 'barn_scan_rot',
    scanMinX: 'barn_scan_min_x',
    scanMinY: 'barn_scan_min_y',
    scanMinZ: 'barn_scan_min_z',
    scanMaxX: 'barn_scan_max_x',
    scanMaxY: 'barn_scan_max_y',
    scanMaxZ: 'barn_scan_max_z',
    scanX: 'barn_scan_x',
    scanY: 'barn_scan_y',
    scanZ: 'barn_scan_z',
    scanChecked: 'barn_scan_checked',
  }

  function getCfgRoot() {
    return global.cloudIslandStructurePlanner || {}
  }

  function getDefaultScanPad() {
    return getCfgRoot().defaultScanPad || {}
  }

  function getScanBatchPerTick() {
    return Number(getCfgRoot().scanBatchPerTick || 300)
  }

  function getStructureMaps() {
    const byKey = {}
    const byItem = {}
    const structures = Array.isArray(getCfgRoot().structures) ? getCfgRoot().structures : []
    for (const s of structures) {
      if (!s || !s.key || !s.itemId || !s.size || !s.templates) continue
      byKey[s.key] = s
      byItem[s.itemId] = s
    }
    return { byKey: byKey, byItem: byItem }
  }

  function idxToLabel(idx) {
    return ROT_LABEL[((idx % 4) + 4) % 4]
  }

  function rotToKey(idx) {
    return ROT_KEY[((idx % 4) + 4) % 4]
  }

  function getConfigByItem(itemId) {
    return getStructureMaps().byItem[itemId] || null
  }

  function getConfigByKey(key) {
    return getStructureMaps().byKey[key] || null
  }

  function getActivePlanConfig(data) {
    return getConfigByKey(String(data.getString(KEY.planKey) || ''))
  }

  function getScanConfig(data) {
    return getConfigByKey(String(data.getString(KEY.scanPlanKey) || ''))
  }

  function getTaskIds(cfg) {
    return Array.isArray(cfg.taskIds) ? cfg.taskIds : []
  }

  function getFootprintByIndex(cfg, idx) {
    const i = ((idx % 4) + 4) % 4
    const sx = Number(cfg.size.x || 1)
    const sy = Number(cfg.size.y || 1)
    const sz = Number(cfg.size.z || 1)
    const swap = i === 1 || i === 3
    return {
      x: swap ? sz : sx,
      y: sy,
      z: swap ? sx : sz,
    }
  }

  function getTemplateByIndex(cfg, idx) {
    const key = rotToKey(idx)
    return cfg.templates[key] || ''
  }

  function getPlacementOrigin(cfg, ox, oy, oz, rotIdx) {
    const i = ((rotIdx % 4) + 4) % 4
    const size = getFootprintByIndex(cfg, i)
    const halfX = Math.floor((size.x - 1) / 2)
    const halfZ = Math.floor((size.z - 1) / 2)

    if (i === 0) return { x: ox - halfX, y: oy, z: oz }
    if (i === 1) return { x: ox - (size.x - 1), y: oy, z: oz - halfZ }
    if (i === 2) return { x: ox - halfX, y: oy, z: oz - (size.z - 1) }
    return { x: ox, y: oy, z: oz - halfZ }
  }

  function getPad(cfg, key, fallback) {
    const pad = cfg.scanPad || {}
    const defaultPad = getDefaultScanPad()
    const v = pad[key]
    if (v === undefined || v === null) {
      const dv = defaultPad[key]
      return Number(dv === undefined || dv === null ? fallback : dv)
    }
    return Number(v)
  }

  function showBoundaryParticles(server, player, cfg, ox, oy, oz, rotIdx) {
    const origin = getPlacementOrigin(cfg, ox, oy, oz, rotIdx)
    const size = getFootprintByIndex(cfg, rotIdx)
    const x1 = origin.x
    const z1 = origin.z
    const x2 = origin.x + size.x - 1
    const z2 = origin.z + size.z - 1
    const yBottom = origin.y + 0.15
    const yTop = origin.y + size.y - 1 + 0.85
    const stepX = Math.max(1, Math.floor(size.x / 12))
    const stepZ = Math.max(1, Math.floor(size.z / 12))
    const stepY = Math.max(1, Math.floor(size.y / 6))

    for (let x = x1; x <= x2; x += stepX) {
      server.runCommandSilent(`particle minecraft:happy_villager ${x + 0.5} ${yBottom} ${z1 + 0.5} 0 0 0 0 1 force ${player.username}`)
      server.runCommandSilent(`particle minecraft:happy_villager ${x + 0.5} ${yBottom} ${z2 + 0.5} 0 0 0 0 1 force ${player.username}`)
      server.runCommandSilent(`particle minecraft:happy_villager ${x + 0.5} ${yTop} ${z1 + 0.5} 0 0 0 0 1 force ${player.username}`)
      server.runCommandSilent(`particle minecraft:happy_villager ${x + 0.5} ${yTop} ${z2 + 0.5} 0 0 0 0 1 force ${player.username}`)
    }
    for (let z = z1; z <= z2; z += stepZ) {
      server.runCommandSilent(`particle minecraft:happy_villager ${x1 + 0.5} ${yBottom} ${z + 0.5} 0 0 0 0 1 force ${player.username}`)
      server.runCommandSilent(`particle minecraft:happy_villager ${x2 + 0.5} ${yBottom} ${z + 0.5} 0 0 0 0 1 force ${player.username}`)
      server.runCommandSilent(`particle minecraft:happy_villager ${x1 + 0.5} ${yTop} ${z + 0.5} 0 0 0 0 1 force ${player.username}`)
      server.runCommandSilent(`particle minecraft:happy_villager ${x2 + 0.5} ${yTop} ${z + 0.5} 0 0 0 0 1 force ${player.username}`)
    }
    for (let y = origin.y + 0.15; y <= yTop; y += stepY) {
      server.runCommandSilent(`particle minecraft:end_rod ${x1 + 0.5} ${y} ${z1 + 0.5} 0 0 0 0 1 force ${player.username}`)
      server.runCommandSilent(`particle minecraft:end_rod ${x2 + 0.5} ${y} ${z1 + 0.5} 0 0 0 0 1 force ${player.username}`)
      server.runCommandSilent(`particle minecraft:end_rod ${x1 + 0.5} ${y} ${z2 + 0.5} 0 0 0 0 1 force ${player.username}`)
      server.runCommandSilent(`particle minecraft:end_rod ${x2 + 0.5} ${y} ${z2 + 0.5} 0 0 0 0 1 force ${player.username}`)
    }
  }

  function getScanBounds(cfg, ox, oy, oz, rotIdx) {
    const origin = getPlacementOrigin(cfg, ox, oy, oz, rotIdx)
    const size = getFootprintByIndex(cfg, rotIdx)
    const padE = getPad(cfg, 'east', 1)
    const padW = getPad(cfg, 'west', 1)
    const padS = getPad(cfg, 'south', 1)
    const padN = getPad(cfg, 'north', 1)
    const padDown = getPad(cfg, 'yDown', 0)
    const padUp = getPad(cfg, 'yUp', 0)
    return {
      minX: origin.x - padW,
      minY: origin.y - padDown,
      minZ: origin.z - padN,
      maxX: origin.x + size.x - 1 + padE,
      maxY: origin.y + size.y - 1 + padUp,
      maxZ: origin.z + size.z - 1 + padS,
    }
  }

  function isAirAtByCommand(server, x, y, z) {
    if (server.runCommandSilent(`execute if block ${x} ${y} ${z} minecraft:air`) === 1) return true
    if (server.runCommandSilent(`execute if block ${x} ${y} ${z} minecraft:cave_air`) === 1) return true
    if (server.runCommandSilent(`execute if block ${x} ${y} ${z} minecraft:void_air`) === 1) return true
    return false
  }

  function unlockTasks(server, player, cfg) {
    const taskIds = getTaskIds(cfg)
    if (!taskIds.length) return
    for (const id of taskIds) {
      if (!id) continue
      server.runCommandSilent(`ftbquests change_progress ${player.username} complete ${id}`)
    }
  }

  function consumeBlueprintOnce(server, player, cfg) {
    if (!cfg || !cfg.itemId) return
    server.runCommandSilent(`clear ${player.username} ${cfg.itemId} 1`)
  }

  function writeBuildLog(player, cfg, x, y, z, rotIdx) {
    const name = cfg.displayName || cfg.key
    const msg = [
      `§6[云岛建设局] ${name}落成日志§r`,
      `§7- 施工人：§f${player.username}§r`,
      `§7- 坐标：§f${x}, ${y}, ${z}§r`,
      `§7- 朝向：§f${idxToLabel(rotIdx)}§r`,
      '§7- 记录：§f主体落成，已登记入册。§r',
      '§a- 状态：相关任务节点已解锁§r',
    ]
    for (const line of msg) player.tell(line)
  }

  function clearPlan(player) {
    const d = player.persistentData
    d.putBoolean(KEY.active, false)
    d.remove(KEY.planKey)
    d.remove(KEY.x)
    d.remove(KEY.y)
    d.remove(KEY.z)
    d.remove(KEY.rot)
  }

  function clearScan(player) {
    const d = player.persistentData
    d.putBoolean(KEY.scanActive, false)
    d.remove(KEY.scanPlanKey)
    d.remove(KEY.scanOx)
    d.remove(KEY.scanOy)
    d.remove(KEY.scanOz)
    d.remove(KEY.scanRot)
    d.remove(KEY.scanMinX)
    d.remove(KEY.scanMinY)
    d.remove(KEY.scanMinZ)
    d.remove(KEY.scanMaxX)
    d.remove(KEY.scanMaxY)
    d.remove(KEY.scanMaxZ)
    d.remove(KEY.scanX)
    d.remove(KEY.scanY)
    d.remove(KEY.scanZ)
    d.remove(KEY.scanChecked)
  }

  function placeFromScanSnapshot(server, player) {
    const data = player.persistentData
    const cfg = getScanConfig(data)
    if (!cfg) {
      player.tell('§c扫描配置丢失，已取消本次放置。§r')
      clearScan(player)
      return
    }

    const ox = data.getInt(KEY.scanOx)
    const oy = data.getInt(KEY.scanOy)
    const oz = data.getInt(KEY.scanOz)
    const rot = ((data.getInt(KEY.scanRot) % 4) + 4) % 4
    const origin = getPlacementOrigin(cfg, ox, oy, oz, rot)
    const templateId = getTemplateByIndex(cfg, rot)
    if (!templateId) {
      player.tell(`§c配置缺少朝向模板：${cfg.key}/${rotToKey(rot)}§r`)
      clearScan(player)
      return
    }

    const cmd = `place template ${templateId} ${origin.x} ${origin.y} ${origin.z} none`
    const ok = server.runCommandSilent(cmd)
    if (ok === 1 || ok === true) {
      consumeBlueprintOnce(server, player, cfg)
      writeBuildLog(player, cfg, ox, oy, oz, rot)
      unlockTasks(server, player, cfg)
      clearPlan(player)
      clearScan(player)
      return
    }

    player.tell(`§c模板放置失败，请检查模板ID与命令语法（当前模板：${templateId}）。§r`)
    showBoundaryParticles(server, player, cfg, ox, oy, oz, rot)
    clearScan(player)
  }

  function startPlacementScan(player, cfg) {
    const data = player.persistentData
    if (!data.getBoolean(KEY.active)) {
      player.tell('§e还没有选址。请先右键地面进行选址。§r')
      return
    }
    if (data.getBoolean(KEY.scanActive)) {
      player.tell('§e正在扫描中，请稍候。§r')
      return
    }

    const ox = data.getInt(KEY.x)
    const oy = data.getInt(KEY.y)
    const oz = data.getInt(KEY.z)
    const rot = ((data.getInt(KEY.rot) % 4) + 4) % 4
    const b = getScanBounds(cfg, ox, oy, oz, rot)

    data.putBoolean(KEY.scanActive, true)
    data.putString(KEY.scanPlanKey, cfg.key)
    data.putInt(KEY.scanOx, ox)
    data.putInt(KEY.scanOy, oy)
    data.putInt(KEY.scanOz, oz)
    data.putInt(KEY.scanRot, rot)
    data.putInt(KEY.scanMinX, b.minX)
    data.putInt(KEY.scanMinY, b.minY)
    data.putInt(KEY.scanMinZ, b.minZ)
    data.putInt(KEY.scanMaxX, b.maxX)
    data.putInt(KEY.scanMaxY, b.maxY)
    data.putInt(KEY.scanMaxZ, b.maxZ)
    data.putInt(KEY.scanX, b.minX)
    data.putInt(KEY.scanY, b.minY)
    data.putInt(KEY.scanZ, b.minZ)
    data.putInt(KEY.scanChecked, 0)
    player.tell(`§7开始扫描边界...（每tick约${getScanBatchPerTick()}格）§r`)
  }

  BlockEvents.rightClicked(event => {
    if (!event.server) return
    if (event.hand && String(event.hand) !== 'MAIN_HAND') return

    const player = event.player
    const held = player.mainHandItem
    if (!held || held.isEmpty()) return

    const heldCfg = getConfigByItem(held.id)
    const server = event.server
    const data = player.persistentData
    const hasPlan = data.getBoolean(KEY.active)
    const planCfg = hasPlan ? getActivePlanConfig(data) : null

    if (!hasPlan && !heldCfg) return

    if (data.getBoolean(KEY.scanActive)) {
      clearScan(player)
      player.tell('§7已取消正在进行的扫描。§r')
    }

    if (hasPlan && !planCfg) {
      clearPlan(player)
      player.tell('§c当前选址对应配置不存在，已清除选址。§r')
      return
    }

    if (hasPlan && (!heldCfg || heldCfg.key !== planCfg.key)) {
      return
    }

    const activeCfg = hasPlan ? planCfg : heldCfg
    const oldRot = data.getInt(KEY.rot)

    if (hasPlan && player.isCrouching()) {
      const ox = data.getInt(KEY.x)
      const oy = data.getInt(KEY.y)
      const oz = data.getInt(KEY.z)
      const rot = (oldRot + 1) % 4
      data.putInt(KEY.rot, rot)
      showBoundaryParticles(server, player, activeCfg, ox, oy, oz, rot)
      player.tell(`§b已切换朝向：${idxToLabel(rot)}。§r`)
      return
    }

    const ox = Math.floor(event.block.x)
    const oy = Math.floor(event.block.y) + 1
    const oz = Math.floor(event.block.z)

    if (!hasPlan) {
      data.putBoolean(KEY.active, true)
      data.putString(KEY.planKey, activeCfg.key)
      data.putInt(KEY.x, ox)
      data.putInt(KEY.y, oy)
      data.putInt(KEY.z, oz)
      data.putInt(KEY.rot, 0)
      showBoundaryParticles(server, player, activeCfg, ox, oy, oz, 0)
      player.tell(`§e已选址：${ox}, ${oy}, ${oz}；朝向：${idxToLabel(0)}。左键确认放置。§r`)
      return
    }

    data.putInt(KEY.x, ox)
    data.putInt(KEY.y, oy)
    data.putInt(KEY.z, oz)
    const rot = ((oldRot % 4) + 4) % 4
    showBoundaryParticles(server, player, activeCfg, ox, oy, oz, rot)
    player.tell(`§e已改址：${ox}, ${oy}, ${oz}；朝向：${idxToLabel(rot)}。左键确认放置。§r`)
  })

  BlockEvents.leftClicked(event => {
    if (!event.server) return
    if (event.hand && String(event.hand) !== 'MAIN_HAND') return

    const player = event.player
    const held = player.mainHandItem
    if (!held || held.isEmpty()) return

    const data = player.persistentData
    if (!data.getBoolean(KEY.active)) return
    const planCfg = getActivePlanConfig(data)
    if (!planCfg) {
      clearPlan(player)
      player.tell('§c当前选址对应配置不存在，已清除选址。§r')
      return
    }

    const heldCfg = getConfigByItem(held.id)
    if (!heldCfg || heldCfg.key !== planCfg.key) return
    startPlacementScan(player, planCfg)
  })

  ServerEvents.tick(event => {
    const server = event.server
    server.players.forEach(player => {
      const data = player.persistentData

      if (data.getBoolean(KEY.active)) {
        const planCfg = getActivePlanConfig(data)
        if (!planCfg) {
          clearScan(player)
          clearPlan(player)
          player.tell('§7当前选址配置已失效，已自动取消选址。§r')
          return
        }
        const held = player.mainHandItem
        const heldId = held && !held.isEmpty() ? held.id : ''
        if (heldId !== planCfg.itemId) {
          clearScan(player)
          clearPlan(player)
          player.tell('§7已切换到其他物品，当前选址已自动取消。§r')
          return
        }
      }

      if (!data.getBoolean(KEY.scanActive)) return

      const cfg = getScanConfig(data)
      if (!cfg) {
        player.tell('§c扫描配置不存在，已取消。§r')
        clearScan(player)
        return
      }

      const minY = data.getInt(KEY.scanMinY)
      const minZ = data.getInt(KEY.scanMinZ)
      const maxX = data.getInt(KEY.scanMaxX)
      const maxY = data.getInt(KEY.scanMaxY)
      const maxZ = data.getInt(KEY.scanMaxZ)
      let x = data.getInt(KEY.scanX)
      let y = data.getInt(KEY.scanY)
      let z = data.getInt(KEY.scanZ)
      let checked = data.getInt(KEY.scanChecked)
      let budget = getScanBatchPerTick()
      let blocked = false
      let bx = 0
      let by = 0
      let bz = 0

      while (budget > 0 && x <= maxX) {
        checked++
        if (!isAirAtByCommand(server, x, y, z)) {
          blocked = true
          bx = x
          by = y
          bz = z
          break
        }
        budget--
        z++
        if (z > maxZ) {
          z = minZ
          y++
          if (y > maxY) {
            y = minY
            x++
          }
        }
      }

      if (blocked) {
        const ox = data.getInt(KEY.scanOx)
        const oy = data.getInt(KEY.scanOy)
        const oz = data.getInt(KEY.scanOz)
        const rot = ((data.getInt(KEY.scanRot) % 4) + 4) % 4
        player.tell(`§c边界碰撞：non_air_by_command @ ${bx},${by},${bz}。请右键改位置或Shift+右键改朝向。§r`)
        showBoundaryParticles(server, player, cfg, ox, oy, oz, rot)
        clearScan(player)
        return
      }

      if (x > maxX) {
        placeFromScanSnapshot(server, player)
        return
      }

      data.putInt(KEY.scanX, x)
      data.putInt(KEY.scanY, y)
      data.putInt(KEY.scanZ, z)
      data.putInt(KEY.scanChecked, checked)
    })
  })
})()
