import {
    _decorator, Component, Node, Vec3, UITransform, director, Enum, find,
    instantiate, CCInteger, Sprite,
} from 'cc';
import { EDITOR, PREVIEW } from 'cc/env';
import { CELL_PIXEL } from './core/GridConstants';
import {
    cellToWorld as isoCellToWorld,
    cellToWorldCenter,
    worldCenterToCell,
} from './core/GridCoords';
import {
    CommonDefine, Direction, GameState, GridType, Skin, addDirection, themeToSkin,
} from './core/Define';
import { EventManager, EventType } from './core/EventManager';
import { ExternalLevelInfo, JsBridge } from './bridge/JsBridge';
import { ensureRuntimeAssetsForLevel } from './core/RuntimePack';
import {
    hasLevel, getMaxLevelId, getMinLevelId, LEVEL_ID_BASE, resolveLevelConfig,
    nextLevelId, prevLevelId,
} from './level/LevelRegistry';
import { LevelConfig, SpawnConfig, SpawnKind } from './level/LevelTypes';
import {
    PropPlacement, propWorldYOffset, resolvePropPlacement,
    entityWorldPositionForRole, worldToMoverCellForRole,
} from './level/EntitySpawnPlacement';
import { EntityVisualOptions } from './visual/EntityTextureResolver';
import { getLevelPrefabResourcePath } from './level/LevelPrefabPaths';
import { loadLevelPrefab as loadLevelPrefabImpl } from './level/LevelPrefabLoader';
import { PlayerController } from './controller/PlayerController';
import { VehicleController } from './controller/VehicleController';
import { PropController } from './controller/PropController';
import { VisualAssets } from './visual/VisualAssets';
import { resolveThemeId } from './theme/ThemeDatabase';
import { LevelDisplay } from './level/LevelDisplay';
import { mergeLevelConfigWithMapData } from './level/LevelConfigMerge';
import { ensureEntityUILayer, findLevelChildByName, forEachLevelEntityNode, sortIsoTiles } from './level/TileLayout';
import { Movement } from './gameplay/Movement';
import { tryLinkPlayerVehicle, tryLinkVehicleRider } from './controller/RideLink';
import { GridSnapHelper } from './level/GridSnapHelper';
import { LineGridRenderer } from './gameplay/LineGridRenderer';
import { ThemeBackground } from './theme/ThemeBackground';
import { GameAudio } from './audio/GameAudio';
import { reloadThemeDatabase } from './theme/ThemeRegistry';
import { reloadTileDisplayMeta } from './visual/TileDisplayMeta';
import { UIMain } from './ui/UIMain';
import { clearUIIconCache } from './ui/UIStyleAssets';

const { ccclass, property } = _decorator;

interface GridEntry {
    type: GridType;
    node: Node;
}

/** Web 主站：优先走 loader 注入的 loadLevelPrefab（按关下载 + 内存 bundle） */
function loadLevelPrefabForRuntime(path: string) {
    const hook = (globalThis as { __tfrhLoadLevelPrefab?: typeof loadLevelPrefabImpl }).__tfrhLoadLevelPrefab;
    if (typeof hook === 'function') return hook(path);
    return loadLevelPrefabImpl(path);
}

/**
 * 主站唯一入口组件（原 GameManager + GameController 合并）。
 * Inspector：填写 inputLevel，预览 ▶ 后点 SwitchLevel（对齐 Unity TestGame2）。
 */
@ccclass('GameController')
export class GameController extends Component {
    static instance: GameController | null = null;

    @property({ group: { name: '关卡切换', id: '1' }, displayName: 'inputLevel' })
    inputLevel = '1';

    @property({ group: { name: '关卡切换', id: '1' }, type: CCInteger, displayName: '当前关卡', readonly: true })
    curLevelID = 1;

    @property({ group: { name: '关卡切换', id: '1' }, displayName: '关卡范围', readonly: true })
    levelRangeHint = '1–80';

    @property({ group: { name: '关卡切换', id: '1' }, type: CCInteger, displayName: '启动关卡 Initial Level ID' })
    initialLevelID = LEVEL_ID_BASE;

    @property({ group: { name: '场景', id: '2' }, displayName: 'Main Level Entrance', type: Node })
    mainLevelEntrance: Node | null = null;

    @property({ group: { name: '场景', id: '2' }, displayName: 'Cur Level', type: Node, readonly: true })
    curLevel: Node | null = null;

    @property({ group: { name: '高级', id: '3' }, type: Enum(Skin), displayName: 'Player Skin' })
    playerSkin: Skin = Skin.Silu;

    @property({ group: { name: '高级', id: '3' }, displayName: 'UI Style' })
    uiStyle = 'default';

    @property({ group: { name: '高级', id: '3' }, displayName: 'inputStyle' })
    inputStyle = 'default';

    @property({ group: { name: '高级', id: '3' }, displayName: '调试-跳过角色与可拾取物' })
    skipPlayerAndProps = false;

    gameState: GameState = GameState.Run;
    isInputEnd = false;
    stepNum = 0;
    ready = false;
    private levelLoadSeq: Promise<void> = Promise.resolve();
    /** 倍速 1/2/4，作用于移动与角色动画 */
    gameSpeed = 1;

    private creating = false;
    private curConfig: LevelConfig | null = null;
    private gridTypes = new Map<string, GridEntry>();
    private gridTypesForProps = new Map<string, GridEntry>();
    private groundCells = new Map<string, string>();
    private borderCells = new Set<string>();

    onLoad() {
        if (GameController.instance && GameController.instance !== this) {
            this.destroy();
            return;
        }
        GameController.instance = this;
        this.levelRangeHint = `${getMinLevelId()}–${getMaxLevelId()}`;
        this.registerWebApi();
    }

    start() {
        const raw = typeof this.inputLevel === 'string' ? this.inputLevel : String(this.inputLevel ?? '');
        if (!raw.trim()) {
            this.inputLevel = String(this.curLevelID || this.initialLevelID);
        }
    }

    update() {
        if (EDITOR && !PREVIEW) return;
        this.resolveMainLevelEntrance();
        this.syncCurLevelRef();
    }

    onDestroy() {
        if (GameController.instance === this) GameController.instance = null;
    }

    // --- Inspector / 扩展按钮 ---

    /** Inspector 扩展 SwitchLevel 按钮（Unity TestGame2 同款） */
    clickSwitchLevel() {
        this.switchToInputLevel();
    }

    /** Inspector：上一关（按关卡库顺序） */
    clickPrevLevel() {
        this.prevLevel();
    }

    /** Inspector：下一关 */
    clickNextLevel() {
        this.nextLevel();
    }

    // --- 预览 / Inspector 调试（对齐 Unity TestPlayer）---

    debugMove(n: number) {
        this.sendMessage('Player', 'CallMove', n);
    }

    debugJump() {
        this.sendMessage('Player', 'CallJump');
    }

    debugRotateLeft(n = 1) {
        this.sendMessage('Player', 'CallRotateLeft', n);
    }

    debugRotateRight(n = 1) {
        this.sendMessage('Player', 'CallRotateRight', n);
    }

    debugPlayerInfo() {
        this.sendMessage('Player', 'CallPlayerInfo');
    }

    debugInputEnd() {
        this.callSetIsInputEnd(1);
    }

    debugVehicleMove(n: number) {
        this.sendMessage('Vehicle', 'CallMove', n);
    }

    debugVehicleInfo() {
        this.sendMessage('Vehicle', 'CallVehicleInfo');
    }

    debugResetLevel() {
        this.resetLevel();
    }

    /** 读取 inputLevel 并切换 */
    switchToInputLevel() {
        if (!this.resolveMainLevelEntrance()) {
            console.warn('[GameController] mainLevelEntrance 未绑定，请等 AppBootstrap 完成');
            return;
        }
        this.applyOptionalParams();
        this.confirmSwitchLevel();
    }

    /** Unity: SendMessage('GameController', 'SwitchLevel', levelId) */
    SwitchLevel(levelID: number) {
        this.confirmSwitchLevel(levelID);
    }

    switchLevel(levelID: number) {
        this.confirmSwitchLevel(levelID);
    }

    prevLevel() {
        if (!this.resolveMainLevelEntrance()) return;
        this.confirmSwitchLevel(prevLevelId(this.curLevelID));
    }

    nextLevel() {
        if (!this.resolveMainLevelEntrance()) return;
        this.confirmSwitchLevel(nextLevelId(this.curLevelID));
    }

    markReady() {
        this.ready = true;
        this.levelRangeHint = `${getMinLevelId()}–${getMaxLevelId()}`;
        if (typeof window !== 'undefined') {
            (window as unknown as { __tfrhCocosReady?: boolean }).__tfrhCocosReady = true;
        }
    }

    onBootstrapReady() {
        this.levelRangeHint = `${getMinLevelId()}–${getMaxLevelId()}`;
    }

    private applyOptionalParams() {
        const style = (typeof this.inputStyle === 'string' ? this.inputStyle : String(this.inputStyle ?? '')).trim()
            || (typeof this.uiStyle === 'string' ? this.uiStyle : String(this.uiStyle ?? '')).trim();
        if (style) {
            this.uiStyle = style;
            this.changeUIStyle(style);
        }
        const player = this.findNodeByName('Player');
        player?.getComponent(PlayerController)?.callChangeSkin?.(this.playerSkin);
    }

    resolveMainLevelEntrance(): boolean {
        if (this.mainLevelEntrance?.isValid) return true;
        const scene = director.getScene();
        if (!scene) return false;
        for (const p of ['GameRoot/MainLevelEntrance', 'Canvas/GameRoot/MainLevelEntrance', 'MainLevelEntrance']) {
            const n = find(p, scene);
            if (n?.isValid) {
                this.mainLevelEntrance = n;
                return true;
            }
        }
        return false;
    }

    private syncCurLevelRef() {
        if (!this.mainLevelEntrance?.isValid) return;
        const isLevelRoot = (n: Node) => /^Level_?\d+$/.test(n.name);
        const found = this.mainLevelEntrance.children.find((c) => c?.isValid && isLevelRoot(c));
        if (found) {
            this.curLevel = found;
            return;
        }
        if (this.curLevel?.isValid && this.curLevel.parent === this.mainLevelEntrance) return;
        this.curLevel = null;
    }

    // --- Web 对接 ---

    private registerWebApi() {
        const api = {
            SendMessage: (objectName: string, methodName: string, param?: string | number) => {
                this.sendMessage(objectName, methodName, param);
            },
        };
        if (typeof window !== 'undefined') {
            (window as unknown as { cocosIns?: typeof api }).cocosIns = api;
            (window as unknown as { unityInstance?: typeof api }).unityInstance = api;
        }
    }

    sendMessage(objectName: string, methodName: string, param?: string | number) {
        if (objectName === 'GameController' || objectName === 'GameManager') {
            if (methodName === 'SwitchLevel' || methodName === 'switchLevel') {
                const id = typeof param === 'number' ? param : parseInt(String(param ?? ''), 10);
                if (!Number.isNaN(id)) this.SwitchLevel(id);
                return;
            }
            this.invoke(this, methodName, param);
            return;
        }
        if (objectName === 'UIMain') {
            const c = this.findNodeByName('UIMain')?.getComponent(UIMain);
            if (c) this.invoke(c, methodName, param);
            return;
        }
        const node = this.findNodeByName(objectName);
        if (!node) {
            if (objectName === 'Player' && this.creating) return;
            console.warn(`SendMessage: 未找到 ${objectName}`);
            return;
        }
        for (const comp of node.getComponents(Component)) {
            if (this.resolveMethod(comp, methodName)) {
                this.invoke(comp, methodName, param);
                return;
            }
        }
        console.warn(`SendMessage: ${objectName} 无方法 ${methodName}`);
    }

    /** Unity SendMessage 使用 PascalCase；Cocos 组件多为 camelCase */
    private resolveMethod(target: object, methodName: string): string | null {
        const rec = target as Record<string, unknown>;
        if (typeof rec[methodName] === 'function') return methodName;
        if (methodName.length > 0) {
            const camel = methodName[0].toLowerCase() + methodName.slice(1);
            if (typeof rec[camel] === 'function') return camel;
        }
        return null;
    }

    private invoke(target: object, methodName: string, param?: string | number) {
        const resolved = this.resolveMethod(target, methodName);
        if (!resolved) return;
        const fn = (target as Record<string, unknown>)[resolved];
        if (typeof fn !== 'function') return;
        if (param === undefined) (fn as () => void).call(target);
        else (fn as (p: string | number) => void).call(target, param);
    }

    callSetIsInputEnd(v: number) {
        this.isInputEnd = v !== 0;
        if (this.isInputEnd) EventManager.dispatch(EventType.InputEnd, this.gameState);
    }

    changeUIStyle(style: string) {
        this.uiStyle = style;
        VisualAssets.setTheme(style);
        clearUIIconCache();
        void VisualAssets.preload(style);
        UIMain.findInstance()?.refreshStyle(style);
    }

    /** Unity GameManager.ResetLevel */
    resetLevel() {
        void this.enqueueLoadLevel(this.curLevelID, true);
    }

    getGameSpeed(): number {
        return this.gameSpeed;
    }

    setGameSpeed(speed: number) {
        const s = speed > 0 ? speed : 1;
        this.gameSpeed = s;
        director.globalGameTimeScale = s;
        Movement.setSpeedMultiplier(s);
    }

    callMute() {
        UIMain.findInstance()?.setMuted(true);
    }
    callUnmute() {
        UIMain.findInstance()?.setMuted(false);
    }

    // --- 游戏逻辑（原 GameManager）---

    setGameState(s: GameState) { this.gameState = s; }

    initData() {
        this.setGameState(GameState.Run);
        this.stepNum = 0;
        this.callSetIsInputEnd(0);
    }

    /** Unity MultMode：关卡 id >= 999000 */
    isMultMode(): boolean {
        return this.curLevelID >= 999000;
    }

    jsCallCheck(n: number): boolean {
        if (this.isInputEnd || this.gameState !== GameState.Run) return false;
        this.stepNum += Math.abs(n);
        return true;
    }

    getRelativePosition(given: Direction, input: Direction): string {
        const rel = (((input - given) % 4) + 4) % 4;
        return ['front', 'right', 'back', 'left'][rel];
    }

    cellKey(x: number, y: number) { return `${x},${y}`; }
    cellToWorld(cell: Vec3): Vec3 { return isoCellToWorld(cell); }
    /** 实体与砖块均对齐格子中心（tileAnchor 0.5,0.5） */
    worldToCell(world: Vec3): Vec3 { return worldCenterToCell(world); }

    nextGridPosition(pos: Vec3, dir: Direction): Vec3 {
        const c = this.worldToCell(pos);
        switch (dir) {
            case Direction.North: c.x += 1; break;
            case Direction.South: c.x -= 1; break;
            case Direction.East: c.y -= 1; break;
            case Direction.West: c.y += 1; break;
        }
        return cellToWorldCenter(c);
    }

    calculateGridTypeAtCell(cell: Vec3): GridType {
        return this.calculateGridType(cellToWorldCenter(cell));
    }

    /** 仅瓦片层格型（不含动态 Ride 覆盖），用于下车 / 载具跟随判定 */
    calculateTileGridAtCell(cell: Vec3): GridType {
        const key = this.cellKey(cell.x, cell.y);
        if (this.borderCells.has(key)) return GridType.Block;
        const g = this.groundCells.get(key);
        if (g === CommonDefine.BlockBase) return GridType.Across;
        if (g === CommonDefine.BlockJump) return GridType.Jump;
        const b = this.curConfig?.boundary;
        if (b && (Math.abs(cell.x) >= b.x || Math.abs(cell.y) >= b.y)) return GridType.Boundary;
        return GridType.None;
    }

    calculateNextTileGridAtCell(cell: Vec3, dir: Direction): GridType {
        return this.calculateTileGridAtCell(this.offsetCell(cell, dir));
    }

    calculateLastTileGridAtCell(cell: Vec3, dir: Direction): GridType {
        return this.calculateTileGridAtCell(this.offsetCell(cell, addDirection(dir, 2)));
    }

    private offsetCell(cell: Vec3, dir: Direction): Vec3 {
        const c = cell.clone();
        switch (dir) {
            case Direction.North: c.x += 1; break;
            case Direction.South: c.x -= 1; break;
            case Direction.East: c.y -= 1; break;
            case Direction.West: c.y += 1; break;
        }
        return c;
    }

    calculateNextGridTypeAtCell(cell: Vec3, dir: Direction): GridType {
        return this.calculateGridTypeAtCell(this.offsetCell(cell, dir));
    }

    calculateLastGridTypeAtCell(cell: Vec3, dir: Direction): GridType {
        return this.calculateGridTypeAtCell(this.offsetCell(cell, addDirection(dir, 2)));
    }

    calculateGridType(pos: Vec3): GridType {
        const cell = this.worldToCell(pos);
        const key = this.cellKey(cell.x, cell.y);
        const dyn = this.gridTypes.get(key);
        if (dyn) return dyn.type;
        if (this.borderCells.has(key)) return GridType.Block;
        const g = this.groundCells.get(key);
        if (g === CommonDefine.BlockBase) return GridType.Across;
        if (g === CommonDefine.BlockJump) return GridType.Jump;
        const b = this.curConfig?.boundary;
        if (b && (Math.abs(cell.x) >= b.x || Math.abs(cell.y) >= b.y)) return GridType.Boundary;
        return GridType.None;
    }

    calculateNextGridType(pos: Vec3, dir: Direction): GridType {
        return this.calculateGridType(this.nextGridPosition(pos, dir));
    }

    calculateLastGridType(pos: Vec3, dir: Direction): GridType {
        return this.calculateGridType(this.nextGridPosition(pos, addDirection(dir, 2)));
    }

    getGameObject(pos: Vec3): Node | null {
        const c = this.worldToCell(pos);
        return this.getGameObjectAtCell(c);
    }

    getGameObjectAtCell(cell: Vec3): Node | null {
        return this.gridTypes.get(this.cellKey(cell.x, cell.y))?.node ?? null;
    }

    addObj(pos: Vec3, type: GridType, node: Node) {
        this.addObjAtCell(this.worldToCell(pos), type, node);
    }

    addObjAtCell(cell: Vec3, type: GridType, node: Node) {
        this.gridTypes.set(this.cellKey(cell.x, cell.y), { type, node });
    }

    removeObj(pos: Vec3) {
        this.removeObjAtCell(this.worldToCell(pos));
    }

    removeObjAtCell(cell: Vec3) {
        this.gridTypes.delete(this.cellKey(cell.x, cell.y));
    }

    removeProp(pos: Vec3) {
        this.removePropAtCell(this.worldToCell(pos));
    }

    removePropAtCell(cell: Vec3) {
        this.gridTypesForProps.delete(this.cellKey(cell.x, cell.y));
    }

    countProp(): number {
        if (!this.curLevel) return 0;
        let n = 0;
        this.forEachPropInLevel(this.curLevel, (c) => {
            if (c?.isValid && c.active) n++;
        });
        return n;
    }

    /** 关卡内是否已拾取全部可收集物（金币等） */
    allPropsCollected(): boolean {
        return this.countProp() === 0;
    }

    private forEachPropInLevel(levelRoot: Node, fn: (node: Node) => void): void {
        if (!levelRoot?.isValid) return;
        const walk = (node: Node) => {
            if (!node?.isValid) return;
            if (node !== levelRoot) {
                const prop = node.getComponent(PropController);
                if (prop) {
                    fn(node);
                    return;
                }
            }
            for (const ch of node.children) walk(ch);
        };
        walk(levelRoot);
    }

    getCurLevel() { return this.curConfig; }

    getEntityVisualOptions(spawnTexture?: string): EntityVisualOptions {
        return {
            theme: this.curConfig?.theme ?? this.uiStyle,
            entityTextures: this.curConfig?.entityTextures,
            spawnTexture,
        };
    }

    findNodeByName(name: string): Node | null {
        const scene = director.getScene();
        if (!scene) return null;
        return this.findInTree(scene, name);
    }

    private findInTree(root: Node, name: string): Node | null {
        if (root.name === name) return root;
        for (const ch of root.children) {
            const f = this.findInTree(ch, name);
            if (f) return f;
        }
        return null;
    }

    destroyCurLevel() {
        if (this.mainLevelEntrance?.isValid) {
            const grid = this.findInTree(this.mainLevelEntrance, 'LineGrid');
            if (grid?.isValid) {
                grid.parent = this.mainLevelEntrance;
                grid.setPosition(0, 0, 0);
            }
            for (const ch of [...this.mainLevelEntrance.children]) {
                if (!ch || ch.name === 'LineGrid') continue;
                if (ch.isValid) ch.destroy();
            }
        } else if (this.curLevel?.isValid) {
            this.curLevel.destroy();
        }
        this.curLevel = null;
        this.syncCurLevelRef();
        this.gridTypes.clear();
        this.gridTypesForProps.clear();
        this.groundCells.clear();
        this.borderCells.clear();
    }

    confirmSwitchLevel(levelID?: number) {
        const raw = levelID !== undefined
            ? String(levelID)
            : (typeof this.inputLevel === 'string' ? this.inputLevel : String(this.inputLevel ?? '')).trim();
        if (!raw) {
            console.warn('[GameController] 关卡 ID 为空');
            return;
        }
        const id = parseInt(raw, 10);
        if (Number.isNaN(id) || id <= 0) {
            console.warn('[GameController] 关卡 ID 无效:', raw);
            return;
        }
        if (!hasLevel(id)) {
            console.warn(
                `[GameController] 关卡 ${id} 不在 Cocos 关卡库 (${getMinLevelId()}–${getMaxLevelId()})，`
                + `仍按 Level${id}.prefab 加载`,
            );
        }
        this.inputLevel = String(id);
        void this.loadLevel(id);
    }

    loadLevel(levelID: number) {
        if (!this.ready && !this.mainLevelEntrance) {
            console.warn('[GameController] 尚未初始化');
            return;
        }
        if (!this.mainLevelEntrance) {
            console.error('[GameController] mainLevelEntrance 未绑定');
            return;
        }
        void this.enqueueLoadLevel(levelID, false);
    }

    private enqueueLoadLevel(levelID: number, forceRestart: boolean) {
        this.levelLoadSeq = this.levelLoadSeq
            .then(() => this.loadLevelNow(levelID, forceRestart))
            .catch((e) => console.error('[GameController] 关卡加载失败', e));
        return this.levelLoadSeq;
    }

    private async loadLevelNow(levelID: number, forceRestart: boolean) {
        if (levelID === this.curLevelID && this.curLevel?.isValid && this.curConfig) {
            await this.restartCurrentLevel();
            return;
        }
        this.destroyCurLevel();
        await this.createNewLevel(levelID);
    }

    /** 同关 SwitchLevel：只补刷贴图，避免无预加载的软重置导致资源错乱 */
    private async refreshLevelPresentation() {
        const config = this.curConfig;
        const levelRoot = this.curLevel;
        if (!config || !levelRoot?.isValid) return;
        await reloadThemeDatabase();
        await reloadTileDisplayMeta();
        const tileNames = LevelDisplay.collectTileNames(config);
        await VisualAssets.ensureLevelAssetsReady(config, tileNames);
        await LevelDisplay.refreshTiles(levelRoot, config);
        await VisualAssets.refreshLevelEntityVisuals(levelRoot, config);
        this.reapplyThemeEntityPositions(levelRoot, config);
        this.syncPlayerAppearanceToLevel(config);
        console.log(`[GameController] 已刷新关卡 ${this.curLevelID} 贴图`);
    }

    /** 重置玩家/道具，保留关卡砖块与背景，避免闪屏 */
    private async restartCurrentLevel() {
        if (!this.curLevel?.isValid || !this.curConfig || !this.mainLevelEntrance) {
            await this.createNewLevel(this.curLevelID);
            return;
        }
        const config = this.curConfig;
        const levelRoot = this.curLevel;
        const tileNames = LevelDisplay.collectTileNames(config);
        await VisualAssets.ensureLevelAssetsReady(config, tileNames);
        await this.purgeDynamicEntities(levelRoot);
        const spawned = await this.spawnAllEntities(levelRoot, config);
        await this.finalizeEntityPresentation(levelRoot, config);
        this.initGridTypes();
        this.initData();
        EventManager.dispatch(EventType.LevelInit);
        this.refreshAllVehicleIcons(levelRoot);
        this.externalCallLevelInfo(spawned);
        this.syncPlayerAppearanceToLevel(config);
        console.log(`[GameController] 已重置关卡 ${this.curLevelID}（无重载）`);
    }

    /** 主题 entityDisplay 中 Y 偏移/站立补偿变更后，重算实体世界坐标（缩放只改贴图尺寸） */
    private reapplyThemeEntityPositions(levelRoot: Node, config: LevelConfig) {
        const theme = config.theme || 'silu';
        for (const s of config.spawns ?? []) {
            if (s.kind === 'player') {
                const pc = findLevelChildByName(levelRoot, 'Player')?.getComponent(PlayerController);
                pc?.reapplyCellStandPosition();
                continue;
            }
            if (s.kind === 'vehicle') {
                const vc = findLevelChildByName(levelRoot, 'Vehicle')?.getComponent(VehicleController);
                vc?.reapplyCellStandPosition();
                continue;
            }
            if (s.kind === 'prop') {
                const node = findLevelChildByName(levelRoot, `Prop_${s.x}_${s.y}`);
                if (!node?.isValid) continue;
                const cell = new Vec3(s.x, s.y, 0);
                const placement = resolvePropPlacement(s, config);
                const pos = cellToWorldCenter(cell);
                pos.y += propWorldYOffset(placement, theme);
                node.setPosition(pos);
            }
        }
    }

    private isDynamicEntity(node: Node): boolean {
        const n = node.name;
        if (n === 'Player' || /^Player[AB]\d$/.test(n)) return true;
        if (n === 'Vehicle' || /^Vehicle[AB]\d$/.test(n)) return true;
        if (n === 'PropDecor' || n.startsWith('PropDecor_')) return true;
        if (n === 'Prop' || n.startsWith('Prop_')) return true;
        return !!(
            node.getComponent(PlayerController)
            || node.getComponent(VehicleController)
            || node.getComponent(PropController)
        );
    }

    private collectDynamicEntities(levelRoot: Node): Node[] {
        const found: Node[] = [];
        const walk = (node: Node) => {
            if (!node?.isValid) return;
            if (node !== levelRoot && this.isDynamicEntity(node)) {
                found.push(node);
                return;
            }
            for (const ch of [...node.children]) walk(ch);
        };
        walk(levelRoot);
        return found;
    }

    /** destroy() 延迟到帧末；先移出场景并等一帧，避免刷新时旧道具与新道具叠在一起 */
    private purgeDynamicEntities(levelRoot: Node): Promise<void> {
        const toRemove = this.collectDynamicEntities(levelRoot);
        for (const ch of toRemove) {
            ch.removeFromParent();
            ch.destroy();
        }
        return new Promise((resolve) => {
            this.scheduleOnce(() => resolve(), 0);
        });
    }

    private async spawnAllEntities(levelRoot: Node, config: LevelConfig): Promise<Node[]> {
        const spawned: Node[] = [];
        for (const s of config.spawns ?? []) {
            if (this.skipPlayerAndProps && this.isSkippedSpawn(s)) continue;
            const node = await this.spawnEntityAsync(levelRoot, s, config);
            if (node) spawned.push(node);
        }
        await this.repairMissingProps(levelRoot, config);
        return spawned;
    }

    /** 刷新后偶发贴图未挂上：补刷缺失或无形的可拾取物 */
    private async repairMissingProps(levelRoot: Node, config: LevelConfig): Promise<void> {
        const propSpawns = (config.spawns ?? []).filter((s) => s.kind === 'prop' || s.kind === 'prop_decor');
        if (!propSpawns.length) return;
        for (const s of propSpawns) {
            const nodeName = s.kind === 'prop' ? `Prop_${s.x}_${s.y}` : 'PropDecor';
            let node = findLevelChildByName(levelRoot, nodeName);
            if (!node?.isValid) {
                node = await this.spawnEntityAsync(levelRoot, s, config);
            }
            if (!node?.isValid) continue;
            const spr = node.getComponent(Sprite);
            if (!spr?.spriteFrame) {
                let propPlacement = s.propPlacement;
                if (s.kind === 'prop' && !propPlacement) {
                    propPlacement = resolvePropPlacement(s, config);
                }
                await VisualAssets.setupEntityVisualAsync(node, s.kind, undefined, {
                    theme: config.theme || 'silu',
                    entityTextures: config.entityTextures,
                    spawnTexture: s.texture,
                    propPlacement,
                }, s.scale);
            }
        }
    }

    private async finalizeEntityPresentation(levelRoot: Node, config: LevelConfig): Promise<void> {
        await VisualAssets.refreshLevelEntityVisuals(levelRoot, config);
        // 先按各自 spawn 对齐格子，再绑乘：避免「先同格绑乘 → 再写回各自 spawn」把骑乘格拆散
        forEachLevelEntityNode(levelRoot, (node) => {
            const mov = node.getComponent(Movement);
            const spawn = mov?.getSpawnCell?.();
            if (spawn) mov.shareCommittedCell(spawn);
        });
        const player = findLevelChildByName(levelRoot, 'Player')?.getComponent(PlayerController);
        if (player) tryLinkPlayerVehicle(player);
        forEachLevelEntityNode(levelRoot, (ch) => {
            const vc = ch.getComponent(VehicleController);
            if (vc) tryLinkVehicleRider(vc);
        });
        sortIsoTiles(levelRoot, true);
        // 延迟一帧再强制全量：等 LevelDisplay.scheduleOnce 对齐完砖，开局金币/遮挡一次算准
        this.scheduleOnce(() => {
            if (levelRoot?.isValid) sortIsoTiles(levelRoot, true);
        }, 0);
    }

    async createNewLevel(levelID: number) {
        if (this.creating) return;
        this.creating = true;
        try {
            await ensureRuntimeAssetsForLevel(levelID);
        } catch (e) {
            console.error('[GameController] 运行时资源加载失败', e);
            this.creating = false;
            return;
        }
        const config = resolveLevelConfig(levelID);
        if (!config || !this.mainLevelEntrance) {
            this.creating = false;
            return;
        }
        this.curLevelID = levelID;
        this.curConfig = config;
        this.inputLevel = String(levelID);

        await reloadThemeDatabase();
        await reloadTileDisplayMeta();
        VisualAssets.clearNamedTileCache();
        VisualAssets.clearPathFrameCache();

        const path = getLevelPrefabResourcePath(levelID, config);
        try {
            const prefab = await loadLevelPrefabForRuntime(path);
            await VisualAssets.preload(this.uiStyle);
            // 在 instantiate 前无法拦截；先禁用 prefab 内 GridSnapHelper 的 showGrid
            GridSnapHelper.stripBeforePlayFromPrefab(prefab);
            const levelRoot = instantiate(prefab);
            GridSnapHelper.purgeRuntimeGrids(levelRoot);
            levelRoot.parent = this.mainLevelEntrance;
            levelRoot.setPosition(0, 0, 0);
            this.curLevel = levelRoot;
            // 预制体 onEnable 可能已画格，挂载后再清一次
            GridSnapHelper.purgeRuntimeGrids(this.mainLevelEntrance!);

            const runtimeConfig = mergeLevelConfigWithMapData(config, levelRoot);
            this.curConfig = runtimeConfig;
            this.applyMapDataFromConfig(runtimeConfig);
            await LevelDisplay.prepare(levelRoot, runtimeConfig);

            const mapTheme = runtimeConfig.theme || 'silu';
            await ThemeBackground.apply(this.mainLevelEntrance, mapTheme);
            await VisualAssets.ensureLevelAssetsReady(
                runtimeConfig,
                LevelDisplay.collectTileNames(runtimeConfig),
            );

            const spawned = await this.spawnAllEntities(levelRoot, runtimeConfig);
            await this.finalizeEntityPresentation(levelRoot, runtimeConfig);
            this.syncPlayerAppearanceToLevel(runtimeConfig);

            await GameAudio.playBackground(levelRoot);

            this.initGridTypes();
            this.initData();
            EventManager.dispatch(EventType.LevelInit);
            this.refreshAllVehicleIcons(levelRoot);
            if (this.mainLevelEntrance?.isValid) {
                LineGridRenderer.ensure(this.mainLevelEntrance);
            }
            this.externalCallLevelInfo(spawned);
            GridSnapHelper.purgeRuntimeGrids(levelRoot);
            this.scheduleOnce(() => {
                const scene = this.mainLevelEntrance?.scene;
                if (scene?.isValid) GridSnapHelper.purgeScene(scene);
            }, 0);
            console.log(`[GameController] 已加载关卡 ${levelID} (${path})`);
        } catch (e) {
            console.error(`[GameController] 预制体加载失败 (${path})`, e);
            console.error('[GameController] 可运行: python3 tools/bake_cocos_level_prefabs.py');
        } finally {
            this.creating = false;
            this.syncCurLevelRef();
        }
    }

    private applyMapDataFromConfig(config: LevelConfig) {
        this.groundCells.clear();
        this.borderCells.clear();
        if (config.ground) {
            for (const [k, v] of Object.entries(config.ground)) this.groundCells.set(k, v);
        }
        if (config.border) {
            for (const k of Object.keys(config.border)) this.borderCells.add(k);
        }
    }

    private initGridTypes() {
        this.gridTypes.clear();
        this.gridTypesForProps.clear();
        if (!this.curLevel?.isValid) return;
        const config = this.curConfig ?? undefined;
        const theme = config?.theme ?? this.uiStyle;
        const vehicles: Node[] = [];
        forEachLevelEntityNode(this.curLevel, (c) => {
            if (c.name.includes('Vehicle')) vehicles.push(c);
        });
        for (const v of vehicles) {
            const vc = v.getComponent(VehicleController);
            const cell = vc?.getCommittedCell()
                ?? worldToMoverCellForRole(v.position, config, theme, 'vehicle');
            this.addObjAtCell(cell, GridType.Ride, v);
        }
        for (const s of config?.spawns ?? []) {
            if (s.kind !== 'vehicle') continue;
            const key = this.cellKey(s.x, s.y);
            if (this.gridTypes.has(key)) continue;
            const node = vehicles.find((v) => {
                const c = v.getComponent(VehicleController)?.getCommittedCell();
                return c && c.x === s.x && c.y === s.y;
            });
            if (node) {
                this.addObjAtCell(new Vec3(s.x, s.y, 0), GridType.Ride, node);
            }
        }
        forEachLevelEntityNode(this.curLevel, (p) => {
            const prop = p.getComponent(PropController);
            if (!prop) return;
            const spawn = prop.getSpawnCell();
            const c = spawn ?? this.worldToCell(p.position);
            this.gridTypesForProps.set(this.cellKey(c.x, c.y), {
                type: this.calculateGridTypeAtCell(c),
                node: p,
            });
        });
    }

    private isSkippedSpawn(s: SpawnConfig): boolean {
        return s.kind === 'player' || s.kind === 'prop' || s.kind === 'prop_decor' || s.kind === 'vehicle';
    }

    /** LevelInit 后再刷一次载具贴图，覆盖 component.start / 异步预载的 spawn 方向 */
    private refreshAllVehicleIcons(levelRoot: Node) {
        if (!levelRoot?.isValid) return;
        const refresh = () => {
            forEachLevelEntityNode(levelRoot, (ch) => {
                ch.getComponent(VehicleController)?.refreshIcon();
            });
        };
        refresh();
        this.scheduleOnce(refresh, 0);
    }

    /** 地图主题优先于主站 CallChangeSkin / uiStyle */
    private syncPlayerAppearanceToLevel(config: LevelConfig) {
        const theme = resolveThemeId(config.theme || 'silu');
        this.playerSkin = themeToSkin(theme);
        const player = this.findNodeByName('Player');
        player?.getComponent(PlayerController)?.applyLevelTheme(theme);
        const vehicle = this.findNodeByName('Vehicle')
            ?? (() => {
                let found: Node | null = null;
                if (this.curLevel?.isValid) {
                    forEachLevelEntityNode(this.curLevel, (c) => {
                        if (!found && c.getComponent(VehicleController)) found = c;
                    });
                }
                return found;
            })();
        vehicle?.getComponent(VehicleController)?.refreshIcon();
        if (this.curLevel?.isValid) {
            this.reapplyThemeEntityPositions(this.curLevel, config);
        }
        UIMain.findInstance()?.refreshStyle(theme);
    }

    private resolveDirection(raw?: Direction | string): Direction | undefined {
        if (raw === undefined || raw === null) return undefined;
        if (typeof raw === 'number') return raw as Direction;
        const name = String(raw).replace(/^Direction\./, '');
        const key = name as keyof typeof Direction;
        if (Object.prototype.hasOwnProperty.call(Direction, key)) {
            const v = Direction[key];
            if (typeof v === 'number') return v as Direction;
        }
        return undefined;
    }

    private async spawnEntityAsync(parent: Node, s: SpawnConfig, config: LevelConfig): Promise<Node | null> {
        this.removeExistingSpawn(parent, s);
        const built = this.buildSpawnEntity(s, config);
        if (!built) return null;
        const { node, pos, visual } = built;
        ensureEntityUILayer(node);
        node.setPosition(pos);
        node.parent = parent;
        await VisualAssets.setupEntityVisualAsync(
            node,
            s.kind,
            visual.direction,
            visual.options,
            visual.scaleMul,
        );
        if (s.kind === 'vehicle') {
            node.getComponent(VehicleController)?.refreshIcon();
        }
        return node;
    }

    private removeExistingSpawn(levelRoot: Node, s: SpawnConfig): void {
        for (const n of this.collectDynamicEntities(levelRoot)) {
            if (s.kind === 'player' && (n.name.includes('Player') || n.getComponent(PlayerController))) {
                n.removeFromParent();
                n.destroy();
            } else if (s.kind === 'vehicle' && (n.name.includes('Vehicle') || n.getComponent(VehicleController))) {
                n.removeFromParent();
                n.destroy();
            } else if (s.kind === 'prop' && n.name === `Prop_${s.x}_${s.y}`) {
                n.removeFromParent();
                n.destroy();
            } else if (s.kind === 'prop_decor' && (n.name === 'PropDecor' || n.name.startsWith('PropDecor_'))) {
                n.removeFromParent();
                n.destroy();
            }
        }
    }

    private buildSpawnEntity(s: SpawnConfig, config: LevelConfig): {
        node: Node;
        pos: Vec3;
        visual: {
            direction?: Direction;
            options: EntityVisualOptions;
            scaleMul: number;
        };
    } | null {
        const mapTheme = config.theme || 'silu';
        const cell = new Vec3(s.x, s.y, 0);
        let propPlacement: PropPlacement | undefined;
        let pos: Vec3;
        if (s.kind === 'player') {
            pos = entityWorldPositionForRole(cell, config, mapTheme, 'player');
        } else if (s.kind === 'vehicle') {
            pos = entityWorldPositionForRole(cell, config, mapTheme, 'vehicle');
        } else {
            pos = cellToWorldCenter(cell);
            if (s.kind === 'prop') {
                propPlacement = resolvePropPlacement(s, config);
                pos.y += propWorldYOffset(propPlacement, mapTheme);
            }
        }
        let node: Node;
        let direction: Direction | undefined;
        if (s.kind === 'player') {
            node = new Node('Player');
            node.addComponent(PlayerController);
            const pc = node.getComponent(PlayerController)!;
            direction = this.resolveDirection(s.playerDirection) ?? Direction.South;
            pc.direction = direction;
            pc.setSpawnCell(cell);
        } else if (s.kind === 'vehicle') {
            node = new Node('Vehicle');
            const vc = node.addComponent(VehicleController);
            direction = this.resolveDirection(s.vehicleDirection) ?? Direction.North;
            vc.direction = direction;
            vc.setSpawnCell(cell);
        } else if (s.kind === 'prop') {
            node = new Node(`Prop_${s.x}_${s.y}`);
            const propCtrl = node.addComponent(PropController);
            propCtrl.setSpawnCell(cell);
        } else if (s.kind === 'prop_decor') {
            node = new Node('PropDecor');
        } else {
            return null;
        }
        return {
            node,
            pos,
            visual: {
                direction,
                options: {
                    theme: mapTheme,
                    entityTextures: config.entityTextures,
                    spawnTexture: s.texture,
                    propPlacement,
                },
                scaleMul: s.scale ?? 1,
            },
        };
    }

    /**
     * 关卡加载完成时通知主站（对齐 Unity GameManager.ExternalCallLevelInfo）。
     * 网页侧实现全局函数 externalLevelInfo(json) 接收 JSON：
     * { LevelID, PlayerName, VehicleName }
     */
    externalCallLevelInfo(objects: Node[]) {
        const info: ExternalLevelInfo = {
            LevelID: this.curLevelID,
            PlayerName: '',
            VehicleName: '',
        };
        for (const obj of objects) {
            if (obj.name.includes('Player')) {
                info.PlayerName = obj.name;
            } else if (obj.name.includes('Vehicle')) {
                info.VehicleName = obj.name;
            }
        }
        console.log(`[GameController] externalLevelInfo LevelID=${info.LevelID}`);
        JsBridge.notifyExternalLevelInfo(info);
    }
}

/** 兼容旧代码 `GameManager.instance` */
export { GameController as GameManager };
