import { world, system, Player, EntityDamageCause, ItemStack, ItemLockMode, GameMode, DisplaySlotId } from "@minecraft/server";
import { ActionFormData, ModalFormData, MessageFormData } from "@minecraft/server-ui";

// --- ゲームバランス設定 ---
const VOID_Y_LIMIT = -10;
const JUMP_COOLDOWN = 60;       // ダブルジャンプCT (3秒)
const BACKSTEP_COOLDOWN = 30;   // バックステップCT (1.5秒)
const BACKSTEP_FORCE = 1.5;     
const DOUBLE_JUMP_FORCE = 1.6;

// 新アクション設定
const AIR_DASH_FORCE = 1.8;     // エアダッシュ突進力
const AIR_DASH_COOLDOWN = 30;   // エアダッシュCT (1.5秒)
const GRAPPLE_COOLDOWN = 80;    // フックショットCT (4秒)
const GRAPPLE_RANGE = 50;       // フック射程
const GRAPPLE_FORCE = 3.5;      // フックの牽引力

// ノックバック計算用係数
const KB_BASE = 0.3;            // 基礎値
const KB_RATE = 0.015;          // ダメージ%による倍率
const UP_BASE = 0.3;            // 浮き基礎
const UP_RATE = 0.003;          // 浮き倍率

// [V2.0] キル判定用定数
const ASSIST_TIMEOUT = 300;     // 15秒 (最後に攻撃を受けてから落下死判定が残る時間)

// BGMリスト
const BGM_LIST = [
    { id: "record.otherside", duration: 190 }, // ~3:10
    { id: "record.pigstep", duration: 148 },   // ~2:28
    { id: "record.wait", duration: 230 }       // ~3:50
];

// ステージごとのスポーンオフセット
const DEFAULT_SPAWNS = [
    { x: 0, y: 0, z: -9 }, // P1 (北側)
    { x: 0, y: 0, z: 9 },  // P2 (南側)
    { x: -9, y: 0, z: 0 }, // P3 (西側)
    { x: 9, y: 0, z: 0 }   // P4 (東側)
];

// The Twins用スポーンオフセット (中心 4000, 20, 0 からの相対座標)
const TWINS_SPAWNS = [
    { x: 0, y: 4, z: 14 },  
    { x: -14, y: 4, z: 0 }, 
    { x: 14, y: 4, z: 0 },  
    { x: 0, y: 4, z: -14 }  
];

// ステージ定義
const STAGES = [
    { name: "The Donut", x: 0, y: 20, z: 0, busy: false, spawns: DEFAULT_SPAWNS },
    { name: "The Cross", x: 1000, y: 20, z: 0, busy: false, spawns: DEFAULT_SPAWNS },
    { name: "The Pillars", x: 2000, y: 20, z: 0, busy: false, spawns: DEFAULT_SPAWNS },
    { name: "The Bowl", x: 3000, y: 23, z: 0, busy: false, spawns: DEFAULT_SPAWNS }, 
    { name: "The Twins", x: 4000, y: 20, z: 0, busy: false, spawns: TWINS_SPAWNS },
    { name: "The Desert", x: 5000, y: 20, z: 0, busy: false, spawns: DEFAULT_SPAWNS },
    { name: "The Islands", x: 6000, y: 20, z: 0, busy: false, spawns: DEFAULT_SPAWNS } 
];

// アイテム定義
const ITEM_MENU = "minecraft:book";
const ITEM_JUMP = "minecraft:feather";
const ITEM_BACKSTEP = "minecraft:sugar";
const ITEM_HOOK = "minecraft:tripwire_hook"; 

// スコアボード設定
const OBJ_DATA = "subow_wins";      

// --- グローバル変数 ---
let activeMatches = []; 
let pendingChallenges = []; 
const cooldowns = new Map();
const wasSneaking = new Map();
let animationTick = 0;

// [V2.0] 攻撃履歴管理: Map<VictimID, {attackerId: string, tick: number}>
const lastDamageMap = new Map();

// ==========================================
//           ヘルパー関数 (安全対策)
// ==========================================

function getPlayerById(id) {
    if (world.getPlayerByUserId && typeof world.getPlayerByUserId === 'function') {
        const p = world.getPlayerByUserId(id);
        if (p) return p;
    }
    return world.getAllPlayers().find(p => p.id === id);
}

// マッチ内ブロードキャスト (関係者のみにメッセージ)
function broadcastToMatch(match, message) {
    // プレイヤーへの送信
    match.players.forEach(pid => {
        const p = getPlayerById(pid);
        if (p) p.sendMessage(message);
    });
    // 観戦者への送信
    match.spectators.forEach(pid => {
        const p = getPlayerById(pid);
        if (p) p.sendMessage(message);
    });
}

// 虹色テキスト生成
function getRainbowText(text, tickOffset) {
    const colors = ["§c", "§6", "§e", "§a", "§b", "§d", "§5"];
    let result = "";
    const offset = Math.floor(tickOffset / 5);
    for(let i=0; i<text.length; i++) {
        const color = colors[(i + offset) % colors.length];
        if (text[i] === " ") {
            result += " ";
        } else {
            result += color + text[i];
        }
    }
    return result;
}

// ==========================================
//           スコアボード & ランキング
// ==========================================

// 初期化処理
function initScoreboard() {
    try {
        if (!world.scoreboard.getObjective(OBJ_DATA)) {
            world.scoreboard.addObjective(OBJ_DATA, "Wins");
        }
        
        const slot = DisplaySlotId ? DisplaySlotId.Sidebar : "sidebar";
        world.scoreboard.clearObjectiveAtDisplaySlot(slot);
        
    } catch (e) {
        console.warn("Scoreboard init error: " + e);
    }
}

function addWin(player) {
    const dataObj = world.scoreboard.getObjective(OBJ_DATA);
    if (!dataObj) return;
    
    try {
        let score = 0;
        try {
            score = dataObj.getScore(player) ?? 0;
        } catch(e) {}
        
        dataObj.setScore(player, score + 1);
    } catch (e) {}
}

function resetStats(sender) {
    const dataObj = world.scoreboard.getObjective(OBJ_DATA);
    if (dataObj) {
        world.scoreboard.removeObjective(OBJ_DATA);
        world.scoreboard.addObjective(OBJ_DATA, "Wins");
    }
    sender.sendMessage("§e[Debug] Leaderboard stats have been reset.");
}

function deleteScoreboard(sender) {
    try {
        if (world.scoreboard.getObjective(OBJ_DATA)) world.scoreboard.removeObjective(OBJ_DATA);
        const dispObj = world.scoreboard.getObjective("subow_disp");
        if (dispObj) world.scoreboard.removeObjective("subow_disp");

        sender.sendMessage("§e[Debug] Scoreboard objectives deleted.");
    } catch (e) {
        sender.sendMessage("§c[Error] Failed to delete scoreboards: " + e);
    }
}

function updateScoreboard() {
    animationTick++;
}

// 起動時にスコアボード初期化
system.run(() => {
    initScoreboard();
    try {
        world.getDimension("overworld").runCommand("gamerule falldamage false");
    } catch (e) {
        console.warn("Failed to set gamerule: " + e);
    }
});

// ==========================================
//           イベントリスナー
// ==========================================

world.afterEvents.playerSpawn.subscribe((ev) => {
    const player = ev.player;
    
    // 再参加時にマッチが既に終了している場合を考慮
    const match = getPlayerMatch(player);
    const specMatch = getSpectatingMatch(player);

    if (!match && !specMatch) {
        resetPlayerToLobby(player);
        
        player.onScreenDisplay.setTitle("§bWelcome to §l§6SuBow §e2.3", { 
            fadeInDuration: 10, 
            stayDuration: 70, 
            fadeOutDuration: 20 
        });
        player.onScreenDisplay.updateSubtitle("§7Now with Pre-Match Details!");
        
        player.playSound("random.levelup", { pitch: 0.8, volume: 0.5 });
        system.runTimeout(() => {
             player.playSound("firework.launch", { pitch: 1.0, volume: 1.0 });
        }, 10);
    }
    
    try {
        const dataObj = world.scoreboard.getObjective(OBJ_DATA);
        if (dataObj) {
            try {
                if (dataObj.getScore(player) === undefined) {
                    dataObj.setScore(player, 0);
                }
            } catch (e) {
                dataObj.setScore(player, 0);
            }
        }
    } catch(e) {}
});

world.beforeEvents.chatSend.subscribe((ev) => {
    const msg = ev.message.trim();

    if (msg === "!lobby" || msg === "!leave") {
        ev.cancel = true;
        system.run(() => {
            stopSpectating(ev.sender);
        });
        return;
    }

    if (msg === "!menu") {
        ev.cancel = true;
        system.run(() => {
            giveLobbyItem(ev.sender);
            ev.sender.sendMessage("§aMenu book given.");
        });
        return;
    }

    if (msg === "!1") {
        ev.cancel = true;
        system.run(() => {
            ev.sender.runCommand("gamemode creative");
            ev.sender.sendMessage("§eSet gamemode to Creative.");
        });
        return;
    }
    if (msg === "!0") {
        ev.cancel = true;
        system.run(() => {
            ev.sender.runCommand("gamemode adventure");
            ev.sender.sendMessage("§eSet gamemode to Adventure.");
        });
        return;
    }

    if (msg === "!debug_reset") {
        ev.cancel = true;
        system.run(() => forceResetGame(ev.sender));
        return;
    }

    if (msg === "!reset_stats") {
        ev.cancel = true;
        system.run(() => resetStats(ev.sender));
        return;
    }

    if (msg === "!delete_scoreboard") {
        ev.cancel = true;
        system.run(() => deleteScoreboard(ev.sender));
        return;
    }
});

world.afterEvents.itemUse.subscribe((ev) => {
    const player = ev.source;
    if (!(player instanceof Player)) return;

    const item = ev.itemStack;

    if (item.typeId === ITEM_MENU) {
        showMainMenu(player);
        return;
    }

    const match = getPlayerMatch(player);
    if (match && match.status === "active") {
        if (item.typeId === ITEM_BACKSTEP) performBackstep(player);
        if (item.typeId === ITEM_JUMP) performDoubleJump(player);
        if (item.typeId === ITEM_HOOK) performGrapple(player);
    }
});

// 矢の発射数カウントと直近履歴 (Entity Spawn検知)
world.afterEvents.entitySpawn.subscribe((ev) => {
    const entityId = ev.entity.id;
    if (ev.entity.typeId !== "minecraft:arrow") return;

    // owner情報は即座に入っていない可能性があるので次tickで確認
    system.run(() => {
        const entity = world.getEntity(entityId);
        if (!entity) return; 
        
        try {
            const proj = entity.getComponent("minecraft:projectile");
            const shooter = proj?.owner;
            
            if (shooter && shooter instanceof Player) {
                 const match = getPlayerMatch(shooter);
                 if (match && match.status === "active") {
                     // Firedカウント
                     const c = match.stats.fired.get(shooter.id) || 0;
                     match.stats.fired.set(shooter.id, c + 1);
                     
                     // 直近5発の履歴を「ミス(false)」として追加
                     if (match.settings.gameMode === "precision") {
                         let recent = match.stats.recentShots.get(shooter.id) || [];
                         recent.push({ id: entityId, hit: false });
                         // 5発を超えたら古いものを消す
                         if (recent.length > 5) recent.shift();
                         match.stats.recentShots.set(shooter.id, recent);
                     }
                 }
            }
        } catch(e) {}
    });
});

// 矢がブロックに当たった時 (何もしない＝当たってないので履歴はfalseのまま)
world.afterEvents.projectileHitBlock.subscribe((ev) => {
    try {
        if(ev.projectile && typeof ev.projectile.remove === 'function') {
            ev.projectile.remove();
        }
    } catch(e) {}
});

// ヒットイベント (自分撃ち防止 & ダメージ & 統計 & Precision履歴更新)
world.afterEvents.projectileHitEntity.subscribe((ev) => {
    const victim = ev.getEntityHit().entity;
    const projectile = ev.projectile;
    const hitVector = ev.location;

    // シューター特定ロジック
    let shooter = ev.source;
    if (!shooter) {
        try {
            const projComp = projectile?.getComponent("minecraft:projectile");
            shooter = projComp?.owner;
            if (!shooter && projectile?.owner) shooter = projectile.owner;
        } catch (e) {}
    }

    if (!victim || !(victim instanceof Player)) return;

    // 自分撃ち無効
    if (shooter && shooter.id === victim.id) {
         try {
            if (projectile && typeof projectile.remove === 'function') {
                projectile.remove();
            }
        } catch (e) {}
        return;
    }
    
    // 矢を消す
    try {
        if (projectile && typeof projectile.remove === 'function') {
            projectile.remove();
        }
    } catch (e) {}

    const match = getPlayerMatch(victim);
    if (!match || match.status !== "active") return;
    
    let damageToAdd = Math.floor(Math.random() * 9) + 14; 
    let shooterHitCount = 0; // Precision時の直近命中数

    // 攻撃履歴の記録
    if (shooter && shooter instanceof Player) {
        lastDamageMap.set(victim.id, {
            attackerId: shooter.id,
            tick: system.currentTick
        });

        // 命中数カウント (Hit)
        if (match.stats && match.stats.hit) {
            const h = match.stats.hit.get(shooter.id) || 0;
            match.stats.hit.set(shooter.id, h + 1);
        }

        // Precisionモード 命中履歴の更新とボーナス計算
        if (match.settings.gameMode === "precision") {
            let recent = match.stats.recentShots.get(shooter.id) || [];
            
            // 飛んできた矢のIDを履歴から探してフラグをtrueにする
            let found = false;
            if (projectile && projectile.id) {
                const idx = recent.findIndex(s => s.id === projectile.id);
                if (idx !== -1) {
                    recent[idx].hit = true;
                    found = true;
                }
            }
            
            // （ラグ等でIDが見つからなかった場合の救済措置として末尾をヒット扱いにする）
            if (!found) {
                recent.push({ id: projectile?.id || "unknown", hit: true });
                if (recent.length > 5) recent.shift();
            }
            
            match.stats.recentShots.set(shooter.id, recent);
            
            // 直近5発の命中数をカウント
            shooterHitCount = recent.filter(s => s.hit).length;
            
            // Precisionモードの基礎ダメージを低下 (5〜10)
            damageToAdd = Math.floor(Math.random() * 6) + 5;
            
            // 命中1発につきダメージ+24%加算 (最大+120%)
            const bonusDamage = shooterHitCount * 24;
            damageToAdd += bonusDamage;
            
            // アクションバー表示
            const showStats = shooter.getDynamicProperty("subow:show_stats") !== false;
            if(showStats){
                 const currentMultiplier = (0.4 + (shooterHitCount * 0.24)).toFixed(2);
                 shooter.onScreenDisplay.setActionBar(`§e🎯 Precision: ${shooterHitCount}/5 Hits! §c(+${bonusDamage}% DMG / ${currentMultiplier}x KB)`);
                 shooter.playSound("random.orb", { pitch: 1.0 + (shooterHitCount * 0.1), volume: 1.0 });
            }
        }
    }

    let currentPercent = getDamagePercent(victim);
    currentPercent += damageToAdd;
    setDamagePercent(victim, currentPercent);

    if (currentPercent >= 350) {
        victim.dimension.playSound("mob.wither.death", victim.location, { volume: 1.0, pitch: 1.0 });
        victim.dimension.spawnParticle("minecraft:huge_explosion_emitter", victim.location);
        
        // スマッシュKO時もキル処理ルートへ
        handleKillProcessing(victim, match);
        return; 
    }

    const vLoc = victim.location;
    let dx = vLoc.x - hitVector.x;
    let dz = vLoc.z - hitVector.z;
    let dist = Math.sqrt(dx * dx + dz * dz);

    if (dist < 0.05) {
        const angle = Math.random() * Math.PI * 2;
        dx = Math.cos(angle);
        dz = Math.sin(angle);
        dist = 1;
    }

    let knockback = KB_BASE + (currentPercent * KB_RATE);
    let upward = UP_BASE + (currentPercent * UP_RATE);

    // --- カスタム倍率の適用 ---
    let kbMultiplier = (match.settings && match.settings.kbMultiplier) ? match.settings.kbMultiplier : 1.0;
    
    // Precisionモードのノックバック倍率変動
    // 基礎を0.4倍とし、命中1発につき+0.24倍上昇 (5発命中で1.6倍)
    if (match.settings.gameMode === "precision") {
        const precisionMultiplier = 0.4 + (shooterHitCount * 0.24);
        kbMultiplier *= precisionMultiplier;
    }

    knockback *= kbMultiplier;
    upward *= kbMultiplier;

    if (currentPercent > 300) {
        knockback = 30.0; 
        upward = 0.2;     
        
        victim.dimension.playSound("mob.warden.sonic_boom", victim.location, { volume: 2.0, pitch: 1.0 });
        victim.dimension.spawnParticle("minecraft:sonic_explosion", victim.location);
    }

    try {
        victim.applyImpulse({ 
            x: (dx / dist) * knockback, 
            y: upward, 
            z: (dz / dist) * knockback 
        });
    } catch(e) {}

    victim.dimension.playSound("game.player.hurt", victim.location, { volume: 1.0, pitch: 1.0 });

    if (currentPercent > 100) {
        victim.dimension.playSound("random.explode", victim.location, { pitch: 1.2, volume: 2.0 });
        victim.dimension.playSound("mob.enderdragon.hit", victim.location, { pitch: 0.6, volume: 1.5 });
        victim.dimension.spawnParticle("minecraft:knockback_roar_particle", victim.location);
        victim.dimension.spawnParticle("minecraft:explosion_emitter", victim.location);
    } else {
        victim.dimension.playSound("note.pling", victim.location, { pitch: 1.5 + (currentPercent / 200), volume: 1.5 });
        victim.dimension.playSound("random.orb", victim.location, { pitch: 0.5 + (currentPercent / 200), volume: 0.8 });
    }
});

// --- プレイヤー退出時の後始末 ---
world.afterEvents.playerLeave.subscribe((ev) => {
    const leftPlayerId = ev.playerId;

    const challengesToRemove = pendingChallenges.filter(c => c.targetId === leftPlayerId || c.challengerId === leftPlayerId);

    if (challengesToRemove.length > 0) {
        challengesToRemove.forEach(c => {
            if (STAGES[c.stageIndex]) {
                STAGES[c.stageIndex].busy = false;
            }
        });
        pendingChallenges = pendingChallenges.filter(c => c.targetId !== leftPlayerId && c.challengerId !== leftPlayerId);
    }

    activeMatches = activeMatches.filter(m => {
        if (m.status === "waiting") {
            if (m.hostId === leftPlayerId) return false; 
            if (m.players.includes(leftPlayerId)) {
                m.players = m.players.filter(id => id !== leftPlayerId);
            }
        }
        return true;
    });

    activeMatches.forEach(m => {
        if (m.spectators.includes(leftPlayerId)) {
            m.spectators = m.spectators.filter(id => id !== leftPlayerId);
        }
    });

    for (const key of cooldowns.keys()) {
        if (key.startsWith(leftPlayerId)) {
            cooldowns.delete(key);
        }
    }
    wasSneaking.delete(leftPlayerId);
    lastDamageMap.delete(leftPlayerId);
});

// ==========================================
//           メインループ
// ==========================================

system.runInterval(() => {
    animationTick++;

    for (let i = activeMatches.length - 1; i >= 0; i--) {
        updateMatch(activeMatches[i], i);
    }

    for (const player of world.getAllPlayers()) {
        const match = getPlayerMatch(player);
        const spectatingMatch = getSpectatingMatch(player);

        if (match) {
            enforceGameInventory(player);
            if (match.status === "active") {
                handleSneakAction(player);
                
                // --- リスポーン待機中なら落下判定を行わない ---
                const isRespawning = player.getDynamicProperty("subow:respawn_timer");
                if (player.location.y < VOID_Y_LIMIT && player.getGameMode() !== GameMode.spectator && !isRespawning) {
                    handleKillProcessing(player, match);
                }
            }
        } else if (spectatingMatch) {
            // 観戦中: HUDのみ
        } else {
            enforceLobbyInventory(player);
        }

        updatePlayerActionBar(player, match, spectatingMatch);
    }
}, 1);

function updatePlayerActionBar(player, match, spectatingMatch) {
    let mainText = "";
    let creditText = "";

    const rawVal = player.getDynamicProperty("subow:show_credit");
    const showCredit = rawVal !== undefined ? rawVal : true;

    // 統計表示設定の取得
    const rawStats = player.getDynamicProperty("subow:show_stats");
    const showStats = rawStats !== false; // Default ON

    if (showCredit) {
        const animTitle = getRainbowText("SuBow 2.3", animationTick);
        const animAuthor = getRainbowText("by Aperion_HQ", animationTick + 2);
        creditText = `\n${animTitle} ${animAuthor}`;
    }

    if (match) {
        // ストック数はマップから取得する（ハンデ対応）
        let stocks = 3;
        if (match.stocks && match.stocks.has(player.id)) {
            stocks = match.stocks.get(player.id);
        }

        const percent = getDamagePercent(player);
        const respawnTimer = player.getDynamicProperty("subow:respawn_timer");
        
        // 統計表示
        const kills = match.stats.kills.get(player.id) || 0;
        const streak = match.stats.streaks.get(player.id) || 0;
        
        // 命中率表示・Precision精度表示
        let statsDetail = "";
        if (showStats) {
            const fired = match.stats.fired.get(player.id) || 0;
            const hit = match.stats.hit.get(player.id) || 0;
            const accuracy = fired > 0 ? Math.floor((hit / fired) * 100) : 0;
            statsDetail = ` §7| Hit: ${hit}/${fired} (${accuracy}%)`;
            
            if (match.settings.gameMode === "precision") {
                const recent = match.stats.recentShots.get(player.id) || [];
                const hitsIn5 = recent.filter(s => s.hit).length;
                statsDetail += ` §7| Prec: §e${hitsIn5}/5`;
            }
        }
        
        let modeInfo = "";
        if (match.settings.gameMode === "kills") modeInfo = `Target: ${match.settings.goalValue} Kills`;
        else if (match.settings.gameMode === "streak") modeInfo = `Target: ${match.settings.goalValue} Streak`;
        else if (match.settings.gameMode === "precision") modeInfo = `Lives: ${stocks} [Sniper]`;
        else modeInfo = `Lives: ${stocks}`; // Stock Mode

        // 残り時間 (Time Limitがある場合)
        let timeText = "";
        if (match.settings.timeLimit > 0) {
             const timeLeft = match.settings.timeLimit - Math.floor(match.elapsedTick / 20);
             const m = Math.floor(timeLeft / 60);
             const s = (timeLeft % 60).toString().padStart(2, '0');
             timeText = ` §7| Time: ${m}:${s}`;
        }
        
        const statsText = `\n§7Kills: §f${kills} §7| Streak: §6${streak}${statsDetail}${timeText}`;

        // リスポーン待ち中の表示
        if (respawnTimer && respawnTimer > 0) {
            const sec = Math.ceil(respawnTimer / 20);
            mainText = `§eRespawning in §l${sec}s§r`;
        } else {
            let color = percent > 100 ? "§c" : "§a";
            mainText = `${modeInfo} §r| Damage: ${color}${percent}%`;
        }
        mainText += statsText; // 統計を末尾に追加

    } else if (spectatingMatch) {
        const stage = STAGES[spectatingMatch.stageIndex];
        mainText = `§bSpectating: §f${stage.name}\n§eType §f!lobby §eto Leave`;

    } else {
        mainText = `§e[Right Click Book] to Open Menu`;
    }

    player.onScreenDisplay.setActionBar(mainText + creditText);
}

// ==========================================
//           アクション関数
// ==========================================

function handleSneakAction(player) {
    const isSneaking = player.isSneaking;
    const lastSneak = wasSneaking.get(player.id) || false;

    if (isSneaking && !lastSneak) {
        const vel = player.getVelocity();
        if (Math.abs(vel.y) > 0.05) {
            performAirDash(player);
        }
    }
    wasSneaking.set(player.id, isSneaking);
}

function performAirDash(player) {
    if (!canUseAction(player, "airdash", AIR_DASH_COOLDOWN)) return;

    const view = player.getViewDirection();
    player.applyImpulse({ 
        x: view.x * AIR_DASH_FORCE, 
        y: view.y * AIR_DASH_FORCE + 0.3, 
        z: view.z * AIR_DASH_FORCE 
    });
    
    player.playSound("mob.enderdragon.flap", { pitch: 1.8, volume: 1.0 });
    player.dimension.spawnParticle("minecraft:cloud_particle", player.location);
}

function performGrapple(player) {
    if (!canUseAction(player, "grapple", GRAPPLE_COOLDOWN)) return;

    const blockHit = player.getBlockFromViewDirection({ maxDistance: GRAPPLE_RANGE });
    
    if (blockHit) {
        const targetLoc = blockHit.block.location;
        const targetCenter = { x: targetLoc.x + 0.5, y: targetLoc.y + 0.5, z: targetLoc.z + 0.5 };
        const pLoc = player.location;
        
        let dx = targetCenter.x - pLoc.x;
        let dy = targetCenter.y - pLoc.y;
        let dz = targetCenter.z - pLoc.z;
        
        const dist = Math.sqrt(dx*dx + dy*dy + dz*dz);
        
        if (dist > 2) {
            player.applyImpulse({
                x: (dx / dist) * GRAPPLE_FORCE,
                y: (dy / dist) * GRAPPLE_FORCE + 0.5,
                z: (dz / dist) * GRAPPLE_FORCE
            });
            
            player.playSound("firework.launch", { pitch: 0.8, volume: 1.0 });
            player.dimension.spawnParticle("minecraft:basic_crit", targetCenter);
        }
    } else {
        player.playSound("note.bass", { pitch: 1.5, volume: 0.5 });
    }
}

function performDoubleJump(player) {
    if (!canUseAction(player, "jump", JUMP_COOLDOWN)) return;
    const view = player.getViewDirection();
    player.applyImpulse({ x: view.x * DOUBLE_JUMP_FORCE, y: 0.8, z: view.z * DOUBLE_JUMP_FORCE });
    player.playSound("mob.enderdragon.flap", { pitch: 1.5 });
    player.dimension.spawnParticle("minecraft:cloud_particle", player.location);
}

function performBackstep(player) {
    if (!canUseAction(player, "backstep", BACKSTEP_COOLDOWN)) return;
    const view = player.getViewDirection();
    player.applyImpulse({ x: -view.x * BACKSTEP_FORCE, y: 0.3, z: -view.z * BACKSTEP_FORCE });
    player.playSound("armor.equip_leather", { pitch: 1.5 });
    player.dimension.spawnParticle("minecraft:cloud_particle", player.location);
}

function canUseAction(player, actionName, cdTick) {
    const key = `${player.id}_${actionName}`;
    const lastUse = cooldowns.get(key) || 0;
    const current = system.currentTick;

    if (current - lastUse < cdTick) {
        player.playSound("note.bass", { pitch: 0.5, volume: 0.3 });
        return false;
    }
    cooldowns.set(key, current);
    return true;
}

// ==========================================
//           デバッグ・管理機能
// ==========================================

function forceResetGame(sender) {
    activeMatches = [];
    pendingChallenges = [];
    STAGES.forEach(s => s.busy = false);
    for (const p of world.getAllPlayers()) {
        resetPlayerToLobby(p);
        p.sendMessage("§cGame was forcefully reset by admin.");
    }
    sender.sendMessage("§e[Debug] All matches and players have been reset.");
}

// ==========================================
//           UI システム
// ==========================================

// --- Rules Text ---
const RULES_TEXT = {
    en: `§l=== SuBow PVP Rules ===§r\n\n` +
        `§b[Basic Concept]§r\n` +
        `Shoot enemies to increase their Damage. Higher damage means more knockback. Knock them into the void to score a kill!\n\n` +
        `§a[Custom Actions]§r\n` +
        `- §eDouble Jump§r: Use Feather in the air.\n` +
        `- §eBackstep§r: Use Sugar to dodge.\n` +
        `- §eGrappling Hook§r: Use Hook to pull yourself.\n` +
        `- §eAir Dash§r: Sneak while in mid-air.\n\n` +
        `§d[Game Modes]§r\n` +
        `- §cStock§r: Classic last man standing.\n` +
        `- §cScore§r: First to reach target kills wins.\n` +
        `- §cStreak§r: Get consecutive kills without dying.\n` +
        `- §cPrecision§r: Bonus DMG & KB based on last 5 shots' accuracy. Lower base damage, no buffs.`,
    
    ja: `§l=== SuBow PVP ルール ===§r\n\n` +
        `§b[基本ルール]§r\n` +
        `弓で敵を射抜いてダメージを溜めよう。ダメージが高いほどノックバックが大きくなります。敵を奈落に落とせばキル！\n\n` +
        `§a[専用アクション]§r\n` +
        `- §eダブルジャンプ§r: 空中で羽を使用\n` +
        `- §eバックステップ§r: 砂糖を使用して回避\n` +
        `- §eフックショット§r: フックを使用して移動\n` +
        `- §eエアダッシュ§r: 空中でスニーク\n\n` +
        `§d[ゲームモード]§r\n` +
        `- §cStock (ストック)§r: 残機がなくなるまで戦う\n` +
        `- §cScore (スコア)§r: 規定キル数に到達で勝利\n` +
        `- §cStreak (ストリーク)§r: 死なずに連続キルで勝利\n` +
        `- §cPrecision (精密射撃)§r: 直近5発の命中数でダメージ・吹っ飛び倍率上昇。基礎ダメージ低下・バフなし。`
};

function showRulesMenu(player, lang = "en") {
    const form = new ActionFormData()
        .title(lang === "en" ? "How to Play / Rules" : "ルールの説明")
        .body(RULES_TEXT[lang]);
    
    if (lang === "en") {
        form.button("日本語で読む (Read in Japanese)", "textures/ui/refresh");
    } else {
        form.button("Read in English (英語で読む)", "textures/ui/refresh");
    }
    form.button(lang === "en" ? "Back to Menu" : "メニューに戻る", "textures/ui/cancel");

    form.show(player).then(res => {
        if (res.canceled) return;
        if (res.selection === 0) {
            showRulesMenu(player, lang === "en" ? "ja" : "en");
        } else {
            showMainMenu(player);
        }
    });
}

function showMainMenu(player) {
    let challenge = null;
    if (pendingChallenges.length > 0) {
        challenge = pendingChallenges.find(c => c.targetId === player.id);
    }

    const form = new ActionFormData()
        .title("SuBow V2.3 Menu") 
        .body(`Player: ${player.name}\nStatus: ${isInMatch(player) ? "In Match" : "Lobby"}`);

    if (challenge) {
        const challenger = getPlayerById(challenge.challengerId);
        const challengerName = challenger ? challenger.name : "Unknown";
        form.button(`§a! Accept Duel (${challengerName})`, "textures/ui/check"); // Index 0
        form.button(`§c! Decline Duel`, "textures/ui/cancel"); // Index 1
    }

    form.button("Duel Challenge (1v1)", "textures/items/iron_sword");
    form.button("Create/Join FFA", "textures/items/diamond_sword");
    form.button("Spectate Match", "textures/items/ender_eye"); 
    form.button("Stage Status", "textures/items/map_filled");
    form.button("Player List / Status", "textures/items/book_portfolio");
    form.button("How to Play / Rules", "textures/items/paper"); 
    form.button("Settings", "textures/items/compass_item"); 

    form.show(player).then(res => {
        if (res.canceled) return;
        
        let selection = res.selection;

        if (challenge) {
            // Accept
            if (selection === 0) {
                acceptChallenge(player, challenge);
                return;
            }
            // Decline
            if (selection === 1) {
                declineChallenge(player, challenge);
                return;
            }
            selection -= 2; // チャレンジボタン分のインデックス調整
        }
        
        if (selection === 0) showDuelMenu(player);
        if (selection === 1) showFFAMenu(player);
        if (selection === 2) showSpectateMenu(player);
        if (selection === 3) showStageStatus(player);
        if (selection === 4) showPlayerList(player);
        if (selection === 5) showRulesMenu(player); 
        if (selection === 6) showSettingsMenu(player);
    });
}

// 観戦メニュー
function showSpectateMenu(player) {
    const ongoingMatches = activeMatches.filter(m => m.status === "active" || m.status === "countdown");
    
    if (ongoingMatches.length === 0) {
        player.sendMessage("§cNo active matches to spectate.");
        return;
    }

    const form = new ActionFormData()
        .title("Spectate Match")
        .body("Select a match to spectate:");

    ongoingMatches.forEach(m => {
        const stageName = STAGES[m.stageIndex].name;
        const typeStr = m.type.toUpperCase();
        
        let names = m.players.map(id => {
            const p = getPlayerById(id);
            return p ? p.name : "?";
        }).join(", ");
        if (names.length > 20) names = names.substring(0, 20) + "...";

        form.button(`§b${stageName}\n§7[${typeStr}] ${names}`);
    });

    form.show(player).then(res => {
        if (res.canceled) return;
        
        const selectedMatch = ongoingMatches[res.selection];
        startSpectating(player, selectedMatch);
    });
}

function showSettingsMenu(player) {
    // 既存設定
    const rawCredit = player.getDynamicProperty("subow:show_credit");
    const isCreditOn = rawCredit !== false; 
    
    // 統計表示設定
    const rawStats = player.getDynamicProperty("subow:show_stats");
    const isStatsOn = rawStats !== false; // Default ON

    // BGM設定
    const rawBgm = player.getDynamicProperty("subow:play_bgm");
    const isBgmOn = rawBgm !== false; // Default ON

    const form = new ActionFormData()
        .title("Settings")
        .body("Customize your SuBow Experience.")
        .button(`HUD Credits: ${isCreditOn ? "§aON" : "§cOFF"}`) // 0
        .button(`Show Accuracy Stats: ${isStatsOn ? "§aON" : "§cOFF"}`) // 1
        .button(`Match Music (BGM): ${isBgmOn ? "§aON" : "§cOFF"}`) // 2
        .button("Back"); // 3

    form.show(player).then(res => {
        if (res.canceled) return;
        
        if (res.selection === 0) {
            player.setDynamicProperty("subow:show_credit", !isCreditOn);
            showSettingsMenu(player);
        } else if (res.selection === 1) {
            player.setDynamicProperty("subow:show_stats", !isStatsOn);
            showSettingsMenu(player);
        } else if (res.selection === 2) {
            // BGM設定切り替え
            const newVal = !isBgmOn;
            player.setDynamicProperty("subow:play_bgm", newVal);
            // 即座に止める
            if (!newVal) {
                player.runCommand("stopsound @s record");
                player.runCommand("stopsound @s music");
            }
            showSettingsMenu(player);
        } else {
            showMainMenu(player);
        }
    });
}

function showPlayerList(player) {
    let text = "§l=== Player Status ===§r\n";
    
    for (const p of world.getAllPlayers()) {
        const match = getPlayerMatch(p);
        const specMatch = getSpectatingMatch(p);
        let status = "§7Lobby";
        let detail = "";

        if (match) {
            if (match.status === "active" || match.status === "countdown") {
                const stageName = STAGES[match.stageIndex].name;
                const stocks = match.stocks.get(p.id);
                const percent = Math.floor(getDamagePercent(p));
                status = "§cIn Match";
                
                const opponents = match.players
                    .filter(id => id !== p.id)
                    .map(id => {
                        const op = getPlayerById(id);
                        return op ? op.name : "Unknown";
                    });
                const opponentText = opponents.length > 0 ? `vs ${opponents.join(", ")}` : "Waiting...";
                detail = `\n  - Map: ${stageName}\n  - ${opponentText}\n  - Lives: ${stocks} | Dmg: ${percent}%`;
            } else {
                status = "§eWaiting FFA";
                const stageName = STAGES[match.stageIndex].name;
                detail = `\n  - Map: ${stageName}`;
            }
        } else if (specMatch) {
            status = "§bSpectating";
            detail = `\n  - Watching: ${STAGES[specMatch.stageIndex].name}`;
        }
        text += `\n§f${p.name}: ${status}${detail}\n`;
    }

    const form = new ActionFormData()
        .title("Player List")
        .body(text)
        .button("Close");

    form.show(player);
}

function showStageStatus(player) {
    let text = "§l=== Stage Status ===§r\n";
    STAGES.forEach(s => {
        text += `${s.name}: ${s.busy ? "§cIn Use" : "§aAvailable"}§r\n`;
    });
    const form = new ActionFormData().title("Status").body(text).button("Close");
    form.show(player);
}

// --- Duelメニュー ---
function showDuelMenu(player) {
    const targets = world.getAllPlayers().filter(p => p.id !== player.id && !isInMatch(p) && !isSpectating(p));
    const availableStages = STAGES.map((s, i) => ({ s, i })).filter(obj => !obj.s.busy);

    if (targets.length === 0) {
        player.sendMessage("§cNo available players.");
        return;
    }
    if (availableStages.length === 0) {
        player.sendMessage("§cNo available stages (All busy).");
        return;
    }

    const form = new ModalFormData()
        .title("Duel Challenge")
        .dropdown("Opponent", targets.map(p => p.name))
        .dropdown("Stage", availableStages.map(obj => obj.s.name))
        .dropdown("Game Mode", ["Stock (Classic)", "Streak (Consecutive)", "Precision (Sniper)"])
        // オプションオブジェクトを使用して初期値を設定
        .slider("Goal (Lives/Streak)", 1, 20, { defaultValue: 3 })
        .slider("Time Limit (Minutes)", 0, 10, { defaultValue: 0 })
        .toggle("§bAdvanced (Damage/KB/Handicap)"); // カスタム設定トグル

    form.show(player).then(res => {
        if (res.canceled) return;
        
        const target = targets[res.formValues[0]];
        const stageIndex = availableStages[res.formValues[1]].i;
        
        const modeIdx = res.formValues[2];
        const goalVal = res.formValues[3];
        const timeMin = res.formValues[4];
        const isAdvanced = res.formValues[5];

        if (STAGES[stageIndex].busy) {
             player.sendMessage("§cStage was taken by another player just now.");
             return;
        }

        const modes = ["stock", "streak", "precision"];
        const settings = {
            gameMode: modes[modeIdx],
            goalValue: goalVal,
            timeLimit: timeMin * 60,
            stocks: (modes[modeIdx] === "streak") ? 999 : goalVal, // PrecisionはStockベースで進行
            initialDamage: 0,
            kbMultiplier: 1.0,
            handicaps: {}
        };

        if (isAdvanced) {
            showAdvancedSettings(player, "duel", stageIndex, settings, target);
        } else {
            sendDuelRequest(player, target, stageIndex, settings);
        }
    });
}

// --- V2.3 統合されたAdvanced Settings ---
function showAdvancedSettings(player, type, stageIndex, settings, target) {
    const form = new ModalFormData()
        .title("Advanced Settings")
        // オプションオブジェクトを使用して初期値を設定
        .slider("Initial Damage %", 0, 300, { defaultValue: 0 })
        .slider("KB Multiplier (x10) [10 = 1.0x]", 5, 30, { defaultValue: 10 });
    
    // Duelの場合のみハンデ設定を表示（簡易化）
    if (type === "duel") {
        form.toggle(`Give Handicap to ${target.name}? (Less Health/Stocks)`);
    }

    form.show(player).then(res => {
        if (res.canceled) return;
        
        settings.initialDamage = res.formValues[0];
        settings.kbMultiplier = res.formValues[1] / 10.0;
        
        // 簡易ハンデ: 相手のストック-1 or ダメージ+50
        if (type === "duel" && res.formValues[2]) {
            const hStocks = Math.max(1, settings.stocks - 1);
            settings.handicaps[target.id] = { stocks: hStocks, initialDamage: settings.initialDamage + 50 };
            settings.handicaps[player.id] = { stocks: settings.stocks, initialDamage: settings.initialDamage };
        }

        if (type === "duel") {
            sendDuelRequest(player, target, stageIndex, settings);
        } else {
            createMatch("ffa", stageIndex, [player], settings);
        }
    });
}

// --- 共通: リクエスト送信処理 ---
function sendDuelRequest(player, target, stageIndex, settings) {
    if (STAGES[stageIndex].busy) {
         player.sendMessage("§cStage was taken by another player just now.");
         return;
    }
    STAGES[stageIndex].busy = true;

    pendingChallenges.push({
        targetId: target.id,
        challengerId: player.id,
        stageIndex: stageIndex,
        settings: settings
    });

    let modeText = settings.gameMode.toUpperCase();
    if(settings.timeLimit > 0) modeText += ` (${settings.timeLimit/60}m)`;
    
    const isCustom = settings.kbMultiplier !== 1.0 || Object.keys(settings.handicaps || {}).length > 0;
    const typeText = isCustom ? "§b[Custom Duel]" : "§6[Duel]";

    player.sendMessage(`§eSent duel request to ${target.name}. (Stage Reserved)`);
    target.sendMessage(`§c§l${typeText} Request! §r\n§e${player.name} wants to play §b${modeText}§e on ${STAGES[stageIndex].name}.\nCheck Menu to accept or decline.`);
    target.playSound("random.orb");
}

function acceptChallenge(target, challenge) {
    const challenger = getPlayerById(challenge.challengerId);
    
    if (!challenger) {
        target.sendMessage("§cOpponent not found (maybe left the game).");
        STAGES[challenge.stageIndex].busy = false; 
        pendingChallenges = pendingChallenges.filter(c => c !== challenge);
        return;
    }

    if (isInMatch(challenger)) {
        target.sendMessage("§cOpponent is already in a match.");
        STAGES[challenge.stageIndex].busy = false;
        pendingChallenges = pendingChallenges.filter(c => c !== challenge);
        return;
    }

    createMatch("duel", challenge.stageIndex, [challenger, target], challenge.settings);
    pendingChallenges = pendingChallenges.filter(c => c !== challenge);
}

function declineChallenge(target, challenge) {
    const challenger = getPlayerById(challenge.challengerId);
    
    if (challenger) {
        challenger.sendMessage(`§c${target.name} declined your duel request.`);
    }
    target.sendMessage(`§eDuel request declined.`);
    STAGES[challenge.stageIndex].busy = false;
    pendingChallenges = pendingChallenges.filter(c => c !== challenge);
}

function showFFAMenu(player) {
    const relevantRooms = activeMatches.filter(m => {
        if (m.type !== "ffa" || m.status !== "waiting") return false;
        
        const isHost = m.hostId === player.id;
        const isMember = m.players.includes(player.id);
        const isFull = m.players.length >= 4;

        if (isHost) return true;
        if (isMember) return true;
        return !isFull;
    });

    const form = new ActionFormData()
        .title("FFA Menu")
        .button("+ Create Room");

    relevantRooms.forEach(room => {
        const host = getPlayerById(room.hostId);
        const hostName = host ? host.name : "Unknown";
        let btnText = `Join: ${hostName} (${room.players.length}/4)`;
        
        if (room.hostId === player.id) {
             btnText = `§eManage My Room (${room.players.length}/4)`;
        } else if (room.players.includes(player.id)) {
             btnText = `§aBack to Room (${room.players.length}/4)`;
        }
        
        form.button(btnText);
    });

    form.show(player).then(res => {
        if (res.canceled) return;

        if (res.selection === 0) {
            showFFACreateSettings(player); 
        } else {
            const room = relevantRooms[res.selection - 1];
            
            // [V2.3] 自分がホストかメンバーならそのままLobbyUIへ、未参加なら詳細確認画面へ
            if (room.hostId === player.id || room.players.includes(player.id)) {
                openLobbyUI(player, room);
            } else {
                showRoomDetails(player, room);
            }
        }
    });
}

// [V2.3] ルーム詳細確認画面
function showRoomDetails(player, room) {
    const host = getPlayerById(room.hostId);
    const hostName = host ? host.name : "Unknown";
    
    let info = `§bHost: §f${hostName}\n`;
    info += `§bPlayers: §f${room.players.length}/4\n`;
    info += `§bMode: §f${room.settings.gameMode.toUpperCase()}\n`;
    info += `§bGoal: §f${room.settings.goalValue}\n`;
    
    if (room.settings.timeLimit > 0) {
        info += `§bTime Limit: §f${room.settings.timeLimit / 60} min\n`;
    } else {
        info += `§bTime Limit: §fNone\n`;
    }

    if (room.settings.kbMultiplier !== 1.0 || room.settings.initialDamage > 0) {
        info += `\n§c[Custom Settings Active]§r\n`;
        info += `KB Multiplier: ${room.settings.kbMultiplier}x\n`;
        info += `Initial Damage: ${room.settings.initialDamage}%\n`;
    }

    const form = new ActionFormData()
        .title("Room Details")
        .body(info)
        .button("§aJoin Room", "textures/ui/check")
        .button("§cBack to List", "textures/ui/cancel");

    form.show(player).then(res => {
        if (res.canceled || res.selection === 1) {
            showFFAMenu(player); // 戻る
        } else if (res.selection === 0) {
            joinMatch(room, player); // 参加
        }
    });
}

// --- FFA作成設定 ---
function showFFACreateSettings(player) {
    const form = new ModalFormData()
        .title("Create FFA Room (V2.3)")
        .dropdown("Game Mode", ["Stock (Classic)", "Score (Kills)", "Streak (Consecutive)", "Precision (Sniper)"])
        // オプションオブジェクトを使用して初期値を設定
        .slider("Goal Value (Lives/Kills/Streak)", 1, 50, { defaultValue: 3 }) 
        .slider("Time Limit (Minutes)", 0, 10, { defaultValue: 0 })
        .toggle("§bAdvanced (Damage/KB)");

    form.show(player).then(res => {
        if (res.canceled) return;
        const modeIdx = res.formValues[0];
        const goalVal = res.formValues[1];
        const timeMin = res.formValues[2];
        const isAdvanced = res.formValues[3];

        const modes = ["stock", "kills", "streak", "precision"];
        const settings = {
            gameMode: modes[modeIdx],
            goalValue: goalVal,
            timeLimit: timeMin * 60, // 秒換算
            stocks: (modes[modeIdx] === "stock" || modes[modeIdx] === "precision") ? goalVal : 999,
            initialDamage: 0,
            kbMultiplier: 1.0,
            handicaps: {}
        };

        if (isAdvanced) {
            showAdvancedSettings(player, "ffa", 0, settings, null);
        } else {
            createMatch("ffa", 0, [player], settings);
        }
    });
}

// ==========================================
//           マッチシステム
// ==========================================

function createMatch(type, stageIndex, players, settings) {
    const matchId = Date.now().toString();
    
    if (type === "duel") {
        STAGES[stageIndex].busy = true;
    }

    const match = {
        id: matchId,
        type: type,
        stageIndex: stageIndex,
        players: players.map(p => p.id),
        spectators: [],
        hostId: players[0].id,
        stocks: new Map(),
        status: "waiting", 
        countdownTimer: 0,
        settings: settings,
        currentBgm: null,
        bgmTick: 0,
        elapsedTick: 0,
        stats: {
            kills: new Map(),
            streaks: new Map(),
            maxStreaks: new Map(),
            fired: new Map(),
            hit: new Map(),
            recentShots: new Map()
        }
    };

    activeMatches.push(match);

    if (type === "duel") {
        startMatchSequence(match);
    } else {
        // --- FFA通知 ---
        let rules = [];
        rules.push(`Mode: ${settings.gameMode}`);
        if (settings.timeLimit > 0) rules.push(`Time: ${settings.timeLimit/60}m`);
        
        const ruleText = `\n§7[${rules.join(", ")}]`;

        world.sendMessage(`§b[SuBow] §e${players[0].name} created a new FFA Room!${ruleText} §7(Open Menu to Join)`);
        for (const p of world.getAllPlayers()) {
            p.playSound("random.orb", { volume: 0.3, pitch: 1.5 });
        }
        openLobbyUI(players[0], match);
    }
}

function joinMatch(match, player) {
    // 参加処理本体
    if (!match.players.includes(player.id) && match.players.length >= 4) {
        player.sendMessage("§cRoom is full.");
        showFFAMenu(player); 
        return;
    }

    if (!match.players.includes(player.id)) {
        match.players.push(player.id);
        broadcastToMatch(match, `§e${player.name} joined (${match.players.length}/4)`);
    }

    // ホスト以外なら待機、ホストなら管理画面
    if (player.id === match.hostId) {
        openLobbyUI(player, match);
    } else {
        player.sendMessage("§aJoined the room. Waiting for host to start...");
        player.playSound("random.orb");
    }
}

function openLobbyUI(player, match) {
    if (player.id !== match.hostId) return;

    const form = new ActionFormData()
        .title("FFA Room Lobby")
        .body(`Players: ${match.players.length}/4\nMode: ${match.settings.gameMode}\nTarget: ${match.settings.goalValue}\nTime: ${match.settings.timeLimit/60}m`)
        .button("START GAME", "textures/items/diamond_sword")
        .button("Disband Room", "textures/ui/cancel"); 
    
    form.show(player).then(res => {
        if (!res.canceled && res.selection === 0) {
            showFFAStageSelection(player, match);
        } else if (res.selection === 1) {
            // 解散処理
            activeMatches = activeMatches.filter(m => m.id !== match.id);
            match.players.forEach(pid => {
                const p = getPlayerById(pid);
                if (p) {
                    p.sendMessage("§cThe room has been disbanded by the host.");
                    p.playSound("note.bass");
                }
            });
        }
    });
}

function showFFAStageSelection(player, match) {
    if (match.players.length < 2) {
        player.sendMessage("§cCannot start with 1 player. Invite friends!");
        openLobbyUI(player, match);
        return;
    }

    const availableStages = STAGES.map((s, i) => ({ s, i })).filter(obj => !obj.s.busy);
    if (availableStages.length === 0) {
        player.sendMessage("§cNo available stages.");
        return;
    }

    const form = new ModalFormData()
        .title("Select Stage")
        .dropdown("Stage", availableStages.map(obj => obj.s.name));

    form.show(player).then(res => {
        if (res.canceled) {
            openLobbyUI(player, match);
            return;
        }
        
        const selected = availableStages[res.formValues[0]];
        match.stageIndex = selected.i;
        STAGES[selected.i].busy = true;
        
        startMatchSequence(match);
    });
}

function playMatchBGM(match) {
    const track = BGM_LIST[Math.floor(Math.random() * BGM_LIST.length)];
    match.currentBgm = track;
    match.bgmTick = 0; 

    // API経由で再生
    const playSoundCmd = (pid) => {
        const p = getPlayerById(pid);
        if(p) {
            const rawBgm = p.getDynamicProperty("subow:play_bgm");
            const playBgm = rawBgm !== false; // Default ON

            p.runCommand(`stopsound @s music`);
            p.runCommand(`stopsound @s record`);
            
            if (playBgm) {
                p.playSound(track.id, { volume: 0.5, pitch: 1.0 }); 
            }
        }
    };

    match.players.forEach(pid => playSoundCmd(pid));
    match.spectators.forEach(pid => playSoundCmd(pid));
}

function startMatchSequence(match) {
    match.status = "countdown";
    match.countdownTimer = 100; 
    
    const stage = STAGES[match.stageIndex];
    
    match.players.forEach((pid, index) => {
        const p = getPlayerById(pid);
        if (!p) return;

        p.setDynamicProperty("subow:matchId", match.id);
        p.runCommand("gamemode adventure");
        
        const spawnOffset = stage.spawns[index % stage.spawns.length];
        const spawnX = stage.x + spawnOffset.x;
        const spawnY = stage.y + spawnOffset.y;
        const spawnZ = stage.z + spawnOffset.z;
        const faceX = spawnX + spawnOffset.x;
        const faceZ = spawnZ + spawnOffset.z;

        if (spawnOffset.x === 0 && spawnOffset.z === 0) {
            p.runCommand(`tp @s ${spawnX} ${spawnY} ${spawnZ}`); 
        } else {
            p.runCommand(`tp @s ${spawnX} ${spawnY} ${spawnZ} facing ${faceX} ${spawnY} ${faceZ}`);
        }

        p.runCommand("tag @s remove spectator");
        p.runCommand("clear @s");
        
        p.runCommand("effect @s clear");
        p.addEffect("health_boost", 1000000, { amplifier: 4, showParticles: false }); 
        p.addEffect("instant_health", 1, { amplifier: 255, showParticles: false });
        p.addEffect("regeneration", 1000000, { amplifier: 255, showParticles: false }); 
        p.addEffect("saturation", 1000000, { amplifier: 255, showParticles: false });
        
        if (match.settings.gameMode !== "precision") {
            p.addEffect("speed", 1000000, { amplifier: 1, showParticles: false });
            p.addEffect("jump_boost", 1000000, { amplifier: 1, showParticles: false });
        }

        let pStocks = match.settings.stocks;
        let pInitDmg = match.settings.initialDamage || 0;

        if (match.settings.handicaps && match.settings.handicaps[pid]) {
            const h = match.settings.handicaps[pid];
            pStocks = h.stocks;
            pInitDmg = h.initialDamage;
        }

        match.stocks.set(pid, pStocks); 
        setDamagePercent(p, pInitDmg);
        
        match.stats.kills.set(pid, 0);
        match.stats.streaks.set(pid, 0);
        match.stats.maxStreaks.set(pid, 0);
        match.stats.fired.set(pid, 0);
        match.stats.hit.set(pid, 0);
        match.stats.recentShots.set(pid, []); 
        
        p.sendMessage(`§l§e=== MATCH STARTING ===\n§bStage: ${stage.name}`);
        p.playSound("random.levelup");
    });
}

function handleKillProcessing(victim, match) {
    let stocks = match.stocks.get(victim.id) - 1;
    match.stocks.set(victim.id, stocks);
    
    let killerId = null;
    const lastHit = lastDamageMap.get(victim.id);
    const currentTick = system.currentTick;
    
    if (lastHit && (currentTick - lastHit.tick < ASSIST_TIMEOUT) && match.players.includes(lastHit.attackerId)) {
        killerId = lastHit.attackerId;
    }

    match.stats.streaks.set(victim.id, 0);
    if (match.settings.gameMode === "precision") {
        match.stats.recentShots.set(victim.id, []);
    }
    lastDamageMap.delete(victim.id);

    if (killerId && killerId !== victim.id) {
        const killer = getPlayerById(killerId);
        const kName = killer ? killer.name : "Unknown";
        
        const newKills = (match.stats.kills.get(killerId) || 0) + 1;
        match.stats.kills.set(killerId, newKills);
        
        const newStreak = (match.stats.streaks.get(killerId) || 0) + 1;
        match.stats.streaks.set(killerId, newStreak);
        
        const maxS = match.stats.maxStreaks.get(killerId) || 0;
        if (newStreak > maxS) match.stats.maxStreaks.set(killerId, newStreak);
        
        broadcastToMatch(match, `§c${victim.name} §ewas knocked into the void by §a${kName}§e!`);
        
        if (newStreak >= 2) {
             const title = newStreak === 2 ? "§eDouble Kill!" : 
                           newStreak === 3 ? "§6Triple Kill!" :
                           newStreak >= 5 ? "§c§lRAMPAGE!" : "§bMulti Kill!";
             
             if(killer) {
                 killer.onScreenDisplay.setTitle(title, { fadeInDuration: 0, stayDuration: 20, fadeOutDuration: 10 });
                 killer.playSound("random.totem", { pitch: 0.5 + (newStreak * 0.1) });
             }
             broadcastToMatch(match, `§6§l${kName} is on a ${newStreak} kill streak!`);
        } else {
             if(killer) killer.playSound("random.orb", { pitch: 1.5 });
        }
        
    } else {
        broadcastToMatch(match, `§c${victim.name} §efell! (Self-Destruct)`);
    }

    if (checkWinConditionImmediate(match)) return;

    if (stocks > 0) {
        let respawnDamage = match.settings.initialDamage || 0;
        if (match.settings.handicaps && match.settings.handicaps[victim.id]) {
            respawnDamage = match.settings.handicaps[victim.id].initialDamage;
        }
        setDamagePercent(victim, respawnDamage);
        
        victim.runCommand("gamemode spectator");
        const stage = STAGES[match.stageIndex];
        victim.runCommand(`tp @s ${stage.x} ${stage.y + 15} ${stage.z}`);

        victim.setDynamicProperty("subow:respawn_timer", 100); 
        
        victim.sendMessage("§eYou will respawn in 5 seconds. Move to a safe spot!");
        victim.playSound("mob.endermen.teleport", { pitch: 0.5 });
    } else {
        victim.runCommand("gamemode spectator");
        const stage = STAGES[match.stageIndex];
        victim.runCommand(`tp @s ${stage.x} ${stage.y + 10} ${stage.z}`);
        
        broadcastToMatch(match, `§c§l${victim.name} eliminated!`);
        victim.playSound("mob.wither.death");
    }
}

function checkWinConditionImmediate(match) {
    const goal = match.settings.goalValue;
    let winnerId = null;

    if (match.settings.gameMode === "kills") {
        for (const pid of match.players) {
            if ((match.stats.kills.get(pid) || 0) >= goal) {
                winnerId = pid;
                break;
            }
        }
    } else if (match.settings.gameMode === "streak") {
        for (const pid of match.players) {
             if ((match.stats.streaks.get(pid) || 0) >= goal) {
                 winnerId = pid;
                 break;
             }
        }
    }

    if (winnerId) {
        endMatch(match, getPlayerById(winnerId));
        return true;
    }
    return false;
}

function updateMatch(match, index) {
    if (match.status === "waiting") return;

    if (match.status === "countdown") {
        handleMatchCountdown(match);
        return;
    }

    if (match.status === "active") {
        match.elapsedTick++;
        
        if (match.currentBgm) {
            match.bgmTick++;
            if (match.bgmTick >= match.currentBgm.duration * 20) {
                playMatchBGM(match); 
            }
        }

        match.players.forEach(pid => {
            const p = getPlayerById(pid);
            if (p) {
                const respawnTimer = p.getDynamicProperty("subow:respawn_timer");
                if (respawnTimer !== undefined && respawnTimer > 0) {
                    handleRespawnCountdown(p, match, respawnTimer);
                }
            }
        });

        if (match.settings.timeLimit > 0 && match.elapsedTick >= match.settings.timeLimit * 20) {
            handleTimeUp(match);
            return;
        }

        if (match.settings.gameMode === "stock" || match.settings.gameMode === "precision") {
            const activeOrRespawning = [];
            match.players.forEach(pid => {
                 const p = getPlayerById(pid);
                 const timer = p ? p.getDynamicProperty("subow:respawn_timer") : 0;
                 if (match.stocks.get(pid) > 0 || (timer && timer > 0)) {
                     activeOrRespawning.push(pid);
                 }
            });

            if (activeOrRespawning.length <= 1) {
                 const winnerId = activeOrRespawning[0];
                 const winner = winnerId ? getPlayerById(winnerId) : null;
                 endMatch(match, winner);
            }
        }
    }
}

function handleTimeUp(match) {
    broadcastToMatch(match, "§c§lTIME UP!");
    let winnerId = null;
    let maxVal = -1;

    if (match.settings.gameMode === "streak") {
        match.players.forEach(pid => {
            const val = match.stats.maxStreaks.get(pid) || 0;
            if (val > maxVal) { maxVal = val; winnerId = pid; }
            else if (val === maxVal) winnerId = null; 
        });
    } else {
        match.players.forEach(pid => {
            const val = match.stats.kills.get(pid) || 0;
            if (val > maxVal) { maxVal = val; winnerId = pid; }
            else if (val === maxVal) winnerId = null; 
        });
    }
    endMatch(match, winnerId ? getPlayerById(winnerId) : null);
}

function handleRespawnCountdown(player, match, currentTimer) {
    const nextTimer = currentTimer - 1;
    player.setDynamicProperty("subow:respawn_timer", nextTimer);

    if (nextTimer % 20 === 0 && nextTimer > 0) {
        const sec = nextTimer / 20;
        player.onScreenDisplay.setTitle(`§eRespawn in §c${sec}`, { fadeInDuration: 0, stayDuration: 20, fadeOutDuration: 0 });
        player.playSound("note.hat", { pitch: 1.5 });
    }

    if (nextTimer <= 0) {
        performRespawn(player, match);
    }
}

function performRespawn(player, match) {
    player.setDynamicProperty("subow:respawn_timer", undefined);
    const stage = STAGES[match.stageIndex];
    
    const nearestSpawn = getNearestSpawn(player, stage);
    
    const sX = stage.x + nearestSpawn.x;
    const sY = stage.y + nearestSpawn.y;
    const sZ = stage.z + nearestSpawn.z;
    
    player.runCommand(`tp @s ${sX} ${sY} ${sZ}`);
    
    player.runCommand("gamemode adventure");
    player.runCommand("effect @s clear");

    player.addEffect("health_boost", 1000000, { amplifier: 4, showParticles: false });
    player.addEffect("instant_health", 1, { amplifier: 255, showParticles: false });
    player.addEffect("regeneration", 1000000, { amplifier: 255, showParticles: false });
    player.addEffect("saturation", 1000000, { amplifier: 255, showParticles: false });
    
    if (match.settings.gameMode !== "precision") {
        player.addEffect("speed", 1000000, { amplifier: 1, showParticles: false });
        player.addEffect("jump_boost", 1000000, { amplifier: 1, showParticles: false });
    }
    
    player.addEffect("resistance", 60, { amplifier: 255, showParticles: true }); 
    
    player.playSound("random.totem");
    player.onScreenDisplay.setTitle(""); 
}

function getNearestSpawn(player, stage) {
    const pLoc = player.location;
    let nearest = stage.spawns[0];
    let minDistSq = Infinity;

    for (const spawn of stage.spawns) {
        const sX = stage.x + spawn.x;
        const sY = stage.y + spawn.y;
        const sZ = stage.z + spawn.z;
        
        const dx = pLoc.x - sX;
        const dy = pLoc.y - sY;
        const dz = pLoc.z - sZ;
        const distSq = dx*dx + dy*dy + dz*dz;

        if (distSq < minDistSq) {
            minDistSq = distSq;
            nearest = spawn;
        }
    }
    return nearest;
}

function handleMatchCountdown(match) {
    match.countdownTimer--;
    const stage = STAGES[match.stageIndex];

    const remainingSeconds = Math.ceil(match.countdownTimer / 20);
    
    if (match.countdownTimer % 20 === 0 && remainingSeconds > 0 && remainingSeconds <= 5) {
        match.players.forEach(pid => {
            const p = getPlayerById(pid);
            if (p) {
                let color = "§e";
                if (remainingSeconds <= 3) color = "§6";
                if (remainingSeconds === 1) color = "§c";
                p.onScreenDisplay.setTitle(`${color}${remainingSeconds}`);
                p.playSound("note.hat");
            }
        });
        match.spectators.forEach(pid => {
            const p = getPlayerById(pid);
            if (p) p.playSound("note.hat");
        });
    }

    match.players.forEach((pid, index) => {
        const p = getPlayerById(pid);
        if (p) {
            enforceGameInventory(p);
            
            const spawnOffset = stage.spawns[index % stage.spawns.length];
            const spawnX = stage.x + spawnOffset.x;
            const spawnY = stage.y + spawnOffset.y;
            const spawnZ = stage.z + spawnOffset.z;
            const faceX = spawnX + spawnOffset.x;
            const faceZ = spawnZ + spawnOffset.z;

             if (spawnOffset.x === 0 && spawnOffset.z === 0) {
                 p.runCommand(`tp @s ${spawnX} ${spawnY} ${spawnZ}`);
             } else {
                 p.runCommand(`tp @s ${spawnX} ${spawnY} ${spawnZ} facing ${faceX} ${spawnY} ${faceZ}`);
             }
        }
    });

    if (match.countdownTimer <= 0) {
        match.status = "active";
        playMatchBGM(match);
        match.players.forEach(pid => {
            const p = getPlayerById(pid);
            if (p) {
                p.onScreenDisplay.setTitle("§a§lGO!");
                p.playSound("mob.wither.spawn");
            }
        });
        match.spectators.forEach(pid => {
            const p = getPlayerById(pid);
            if (p) p.onScreenDisplay.setTitle("§a§lGO!");
        });
    }
}

function stopMatchBGM(match) {
    const stopSoundCmd = (pid) => {
        const p = getPlayerById(pid);
        if (p) {
            p.runCommand(`stopsound @s record`);
        }
    };
    match.players.forEach(pid => stopSoundCmd(pid));
    match.spectators.forEach(pid => stopSoundCmd(pid));
}

function endMatch(match, winner) {
    const msg = winner ? `§l§6VICTORY! §r§e${winner.name} won!` : "§7DRAW GAME";
    
    if (winner) {
        addWin(winner); 
    }
    
    stopMatchBGM(match); 

    broadcastToMatch(match, msg);

    let details = "\n§7=== Match Results ===";
    match.players.forEach(pid => {
        const p = getPlayerById(pid);
        const k = match.stats.kills.get(pid) || 0;
        const s = match.stats.maxStreaks.get(pid) || 0;
        const fired = match.stats.fired.get(pid) || 0;
        const hit = match.stats.hit.get(pid) || 0;
        const acc = fired > 0 ? Math.floor((hit/fired)*100) : 0;
        
        if (p) details += `\n§f${p.name}: §c${k} Kills §7(Acc: ${acc}%)`;
    });
    broadcastToMatch(match, details);

    match.players.forEach(pid => {
        try {
            const p = getPlayerById(pid);
            if (p) {
                if (winner && p.id === winner.id) p.playSound("ui.toast.challenge_complete");
                resetPlayerToLobby(p);
            }
        } catch (e) {
            console.warn(`Error resetting player ${pid}: ${e}`);
        }
    });

    match.spectators.forEach(pid => {
        try {
            const p = getPlayerById(pid);
            if (p) {
                stopSpectating(p);
            }
        } catch(e) {}
    });

    STAGES[match.stageIndex].busy = false;
    activeMatches = activeMatches.filter(m => m.id !== match.id);
}

// ==========================================
//           観戦システム
// ==========================================

function startSpectating(player, match) {
    if (!match) return;
    
    match.spectators.push(player.id);
    player.setDynamicProperty("subow:spectatingMatchId", match.id);
    
    const stage = STAGES[match.stageIndex];
    player.runCommand("gamemode spectator");
    player.runCommand(`tp @s ${stage.x} ${stage.y + 15} ${stage.z}`);
    
    player.sendMessage(`§aStarted spectating ${stage.name}.`);
    player.sendMessage(`§eType §l!lobby§r §eor §l!leave§r §eto return to lobby.`);
    player.playSound("random.orb");
}

function stopSpectating(player) {
    player.setDynamicProperty("subow:spectatingMatchId", undefined);
    
    activeMatches.forEach(m => {
        if (m.spectators.includes(player.id)) {
            m.spectators = m.spectators.filter(id => id !== player.id);
        }
    });

    resetPlayerToLobby(player);
    player.sendMessage("§eReturned to Lobby.");
}

function isSpectating(player) {
    return !!player.getDynamicProperty("subow:spectatingMatchId");
}

function getSpectatingMatch(player) {
    const matchId = player.getDynamicProperty("subow:spectatingMatchId");
    if (!matchId) return null;
    return activeMatches.find(m => m.id === matchId);
}

// ==========================================
//           アクション & アイテム (Inventory)
// ==========================================

function enforceLobbyInventory(player) {
    const inv = player.getComponent("inventory").container;
    const slot0 = inv.getItem(0);
    if (!slot0 || slot0.typeId !== ITEM_MENU) {
        const book = new ItemStack(ITEM_MENU);
        book.nameTag = "§r§bGame Menu §7(Right Click)";
        inv.setItem(0, book);
    }
}

function enforceGameInventory(player) {
    const inv = player.getComponent("inventory").container;
    const invSize = inv.size;

    const slot0 = inv.getItem(0);
    if (!slot0 || slot0.typeId !== "minecraft:bow") {
        const bow = new ItemStack("minecraft:bow");
        
        const enchantComponent = bow.getComponent("minecraft:enchantments");
        if (enchantComponent && enchantComponent.enchantments) {
            try {
                const enchants = enchantComponent.enchantments;
                enchants.addEnchantment({ type: { id: "infinity" }, level: 1 });
                enchantComponent.enchantments = enchants;
            } catch (e) {}
        }
        
        inv.setItem(0, bow);
    } else {
        const durability = slot0.getComponent("durability");
        if (durability && durability.damage > durability.maxDurability - 5) {
             slot0.getComponent("durability").damage = 0;
             inv.setItem(0, slot0);
        }
    }

    let hasArrow = false;
    for(let i=0; i<invSize; i++) {
        if(inv.getItem(i)?.typeId === "minecraft:arrow") hasArrow = true;
    }
    if (!hasArrow) {
        inv.setItem(9, new ItemStack("minecraft:arrow", 1));
    }

    // Slot 6: Hook
    const slot6 = inv.getItem(6);
    if (!slot6 || slot6.typeId !== ITEM_HOOK) {
        const hook = new ItemStack(ITEM_HOOK);
        hook.nameTag = "§eGrappling Hook §7(Right Click)";
        hook.setLore(["§7Cooldown: 4s", "§7Pull to block"]);
        inv.setItem(6, hook);
    }

    // Slot 7: Jump
    const slot7 = inv.getItem(7);
    if (!slot7 || slot7.typeId !== ITEM_JUMP) {
        const feather = new ItemStack(ITEM_JUMP);
        feather.nameTag = "§aDouble Jump §7(Right Click)";
        feather.setLore(["§7Cooldown: 3s", "§7Use in Air"]);
        inv.setItem(7, feather);
    }

    // Slot 8: Backstep
    const slot8 = inv.getItem(8);
    if (!slot8 || slot8.typeId !== ITEM_BACKSTEP) {
        const sugar = new ItemStack(ITEM_BACKSTEP);
        sugar.nameTag = "§bBackstep §7(Right Click)";
        sugar.setLore(["§7Cooldown: 1.5s", "§7Use to dodge"]);
        inv.setItem(8, sugar);
    }
}

// ==========================================
//           ユーティリティ
// ==========================================

function isInMatch(player) {
    return !!player.getDynamicProperty("subow:matchId");
}

function getPlayerMatch(player) {
    const matchId = player.getDynamicProperty("subow:matchId");
    if (!matchId) return null;
    return activeMatches.find(m => m.id === matchId);
}

function resetPlayerToLobby(player) {
    player.setDynamicProperty("subow:matchId", undefined);
    player.setDynamicProperty("subow:spectatingMatchId", undefined);
    // リスポーンタイマーも消す
    player.setDynamicProperty("subow:respawn_timer", undefined);

    player.runCommand("gamemode adventure");
    player.runCommand("clear @s");
    player.runCommand("tag @s remove spectator");
    player.runCommand("effect @s clear");
    player.runCommand("stopsound @s record"); 
    player.runCommand("tp @s 0 100 0");
    player.onScreenDisplay.setActionBar("");
    player.nameTag = player.name;
    giveLobbyItem(player);
}

function giveLobbyItem(player) {
    const inv = player.getComponent("inventory").container;
    const book = new ItemStack(ITEM_MENU);
    book.nameTag = "§r§bGame Menu §7(Right Click)";
    inv.setItem(0, book);
}

function getDamagePercent(entity) {
    return entity.getDynamicProperty("subow:damage_percent") || 0;
}

function setDamagePercent(entity, amount) {
    entity.setDynamicProperty("subow:damage_percent", amount);
    const matchId = entity.getDynamicProperty("subow:matchId");
    if(matchId) {
        const match = activeMatches.find(m => m.id === matchId);
        let stocks = 3;
        if (match) {
             if (match.stocks && match.stocks.has(entity.id)) {
                 stocks = match.stocks.get(entity.id);
             } else if (match.settings && match.settings.stocks) {
                 stocks = match.settings.stocks;
             }
        }
        
        const color = amount > 100 ? "§c" : "§a";
        let suffix = "";
        
        // モードに応じたネームタグ表示
        if (match && match.settings.gameMode === "kills") {
             const kills = match.stats.kills.get(entity.id) || 0;
             suffix = `\n§eKills: ${kills}`;
        } else if (match && match.settings.gameMode === "streak") {
             const streak = match.stats.streaks.get(entity.id) || 0;
             suffix = `\n§6Streak: ${streak}`;
        } else {
             // Stock or Precision
             suffix = `\n§cLives: ${stocks}`;
        }
        
        entity.nameTag = `${entity.name}${suffix} §f| ${color}${Math.floor(amount)}%`;
    }
}