import { _decorator, Component, Node, UITransform, Vec3, Layers, Sprite } from 'cc';
import { cellToWorldCenter, parseTileNodeName } from '../core/GridCoords';
import { CommonDefine, MoverRole, GameState } from '../core/Define';
import { GameManager } from '../manager/GameManager';
import { getTilePivot } from '../visual/TilePivots';
import { getTileDrawSize, resolveTilePixelSize } from '../visual/TileSizes';
import { LevelConfig } from './LevelTypes';
import { getLevelRuntimeContext } from './LevelRuntimeContext';
import { worldToMoverCellForRole, entityWorldPositionForRole } from './EntitySpawnPlacement';
import { Movement } from '../gameplay/Movement';
import { DEFAULT_PLAYER_ANCHOR_Y, DEFAULT_VEHICLE_ANCHOR_Y, getEntityDisplaySizes } from '../visual/EntityDisplayRefs';
import { cellHasTile } from './EntitySpawnPlacement';
import {
    compareIsoDrawOrder,
    UNITY_SORTING_ORDER,
    tileDrawFrontKey,
    entityDrawFrontKeyWithWalls,
    type UnitySortableEntity,
    type UnitySortableTile,
} from './UnityDrawSort';
import { resolveTileNameAtCell } from './TileKinds';

const { ccclass } = _decorator;

const UI_LAYER = Layers.Enum.UI_2D;
/**
 * Cocos 用 siblingIndex 模拟 Unity SortingOrder + CustomAxis 深度。
 * 逻辑见 UnityDrawSort.ts（对齐 GraphicsSettings + TilemapRenderer Individual）。
 */
export const DRAW_SORT_Y_SCALE = 100;
type DrawKind = 'walkable' | 'wall' | 'actor' | 'pickable' | 'scenery';

interface TileDrawEntry {
    node: Node;
    x: number;
    y: number;
    kind: DrawKind;
    tileName: string;
}

interface EntityDrawEntry {
    node: Node;
    x: number;
    y: number;
    kind: DrawKind;
}

type DrawEntry = TileDrawEntry | EntityDrawEntry;

function safeNodeName(node: Node | null | undefined): string {
    return (node?.isValid ? node.name : '') || '';
}

function isPlayerEntityName(name: string | undefined | null): boolean {
    if (!name) return false;
    return name === 'Player' || /^Player[AB]\d$/.test(name);
}

function isVehicleEntityName(name: string | undefined | null): boolean {
    if (!name) return false;
    return name === 'Vehicle' || /^Vehicle[AB]\d$/.test(name);
}

function isCoinEntityName(name: string | undefined | null): boolean {
    if (!name) return false;
    return name === 'Prop' || name.startsWith('Prop_');
}

function isActorEntity(node: Node): boolean {
    return isPlayerEntityName(safeNodeName(node))
        || isVehicleEntityName(safeNodeName(node));
}

/** 可拾取金币 / 道具（PropController） */
function isPickableProp(node: Node): boolean {
    if (!node?.isValid) return false;
    if (node.getComponent('PropController')) return true;
    return isCoinEntityName(safeNodeName(node));
}

function isEntityDrawNode(node: Node): boolean {
    const n = safeNodeName(node);
    if (!n) return false;
    return isActorEntity(node) || isPickableProp(node)
        || n === 'PropDecor' || n.startsWith('PropDecor_');
}

function isWalkablePathTile(tileName: string): boolean {
    return tileName === CommonDefine.BlockBase || tileName === CommonDefine.BlockJump;
}

function isWallTileName(tileName: string): boolean {
    return tileName === CommonDefine.BlockWall;
}

function resolveTileName(
    parsed: { layer: 'ground' | 'border'; x: number; y: number },
    config?: LevelConfig,
): string {
    const key = `${parsed.x},${parsed.y}`;
    return resolveTileNameAtCell(parsed.layer, key, config?.ground, config?.border);
}

/** 分类基于规范化后的瓦片名（贴图名已按层级锁定，避免墙/地属性互串） */
function classifyTileKind(
    parsed: { layer: 'ground' | 'border'; x: number; y: number },
    config?: LevelConfig,
): DrawKind {
    const name = resolveTileName(parsed, config);
    if (isWallTileName(name)) return 'wall';
    if (isWalkablePathTile(name)) return 'walkable';
    return 'scenery';
}

function classifyEntityKind(node: Node): DrawKind {
    if (isPickableProp(node)) return 'pickable';
    if (isActorEntity(node)) return 'actor';
    return 'scenery';
}

export interface EntityDrawOrderOptions {
    /** @deprecated 保留字段避免旧调用报错 */
    preferVehicleOverPlayer?: boolean;
}

function unityEntitySortingOrder(node: Node): number {
    const n = safeNodeName(node);
    if (isPlayerEntityName(n)) return UNITY_SORTING_ORDER.player;
    if (isVehicleEntityName(n)) return UNITY_SORTING_ORDER.vehicle;
    if (isPickableProp(node)) {
        const prop = node.getComponent('PropController') as { getSpawnCell?: () => Vec3 | null } | null;
        const cell = prop?.getSpawnCell?.();
        const config = resolveLevelConfig();
        if (cell && !cellHasTile(cell.x, cell.y, config)) {
            return UNITY_SORTING_ORDER.nProp;
        }
        return UNITY_SORTING_ORDER.prop;
    }
    if (n === 'PropDecor' || n.startsWith('PropDecor_')) return UNITY_SORTING_ORDER.prop;
    return UNITY_SORTING_ORDER.nProp;
}

function toUnitySortableTile(tile: TileDrawEntry): UnitySortableTile {
    const centerY = tile.node.position.y;
    let depthY = getNodeBottomY(tile.node);
    if (isWallTileName(tile.tileName)) {
        depthY = getWallSortFrontY(tile);
    } else if (isWalkablePathTile(tile.tileName)) {
        depthY = Math.max(depthY, centerY);
    } else {
        depthY = centerY;
    }
    return {
        cellX: tile.x,
        cellY: tile.y,
        tileName: tile.tileName,
        centerY,
        depthY,
    };
}

function toUnitySortableEntity(entity: EntityDrawEntry): UnitySortableEntity {
    const node = entity.node;
    const cell = entity.kind === 'actor'
        ? getActorWallSampleCell(entity, node)
        : { x: entity.x, y: entity.y };
    const depthY = entity.kind === 'actor'
        ? getEntitySortBottomY(node)
        : getNodeBottomY(node);
    return {
        sortingOrder: unityEntitySortingOrder(node),
        cellX: cell.x,
        cellY: cell.y,
        centerY: node.position.y,
        depthY,
        isPlayer: isPlayerEntityName(safeNodeName(node)),
        isPickable: entity.kind === 'pickable',
    };
}

/** 等距深度：返回值 > 0 表示 a 应排在 b 之后（更靠前） */
export { compareIsoDrawOrder };

function buildSortedDrawOrder(tiles: TileDrawEntry[], entities: EntityDrawEntry[]): {
    entries: DrawEntry[];
    keys: number[];
} {
    const wallTiles = tiles
        .filter((t) => isWallTileName(t.tileName))
        .map((t) => toUnitySortableTile(t));

    const keyed: { entry: DrawEntry; key: number; name: string; isPlayer: boolean }[] = [];
    for (const t of tiles) {
        keyed.push({
            entry: t,
            key: tileDrawFrontKey(toUnitySortableTile(t)),
            name: t.node.name,
            isPlayer: false,
        });
    }
    for (const e of entities) {
        const ent = toUnitySortableEntity(e);
        keyed.push({
            entry: e,
            key: entityDrawFrontKeyWithWalls(ent, wallTiles),
            name: e.node.name,
            isPlayer: ent.isPlayer,
        });
    }
    // 正在骑乘：角色 frontKey 高于其载具（同格 typeRank 通常已够；此处兜住贴墙抬升后并列/短暂格子差）
    for (const p of keyed) {
        if (!p.isPlayer || p.entry.kind !== 'actor') continue;
        const ride = (p.entry.node.getComponent('PlayerController') as {
            getRideVehicle?: () => { node?: Node } | null;
        } | null)?.getRideVehicle?.();
        const vehicleNode = ride?.node;
        if (!vehicleNode) continue;
        const v = keyed.find((k) => k.entry.node === vehicleNode);
        if (v && p.key <= v.key) p.key = v.key + 1;
    }
    keyed.sort((a, b) => {
        if (a.key !== b.key) return a.key - b.key;
        // 同 key：角色永远盖过载具（避免 Player 名字排在 Vehicle 前反而画在下）
        if (a.isPlayer !== b.isPlayer) return a.isPlayer ? 1 : -1;
        return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
    });
    return {
        entries: keyed.map((k) => k.entry),
        keys: keyed.map((k) => k.key),
    };
}

/** 精灵顶边 Y（Tiles 本地） */
function getNodeTopY(node: Node): number {
    if (!node?.isValid) return 0;
    const ui = resolveSortTransform(node);
    const pos = node.position;
    if (!ui || ui.contentSize.height <= 0) return pos.y;
    const h = ui.contentSize.height * Math.abs(node.scale.y);
    return pos.y + (1 - ui.anchorPoint.y) * h;
}

/** 墙砖立面顶边 Y（WallBlock pivot 偏低，取格心与顶边较大值） */
function getWallSortFrontY(tile: TileDrawEntry): number {
    const centerY = cellToWorldCenter(new Vec3(tile.x, tile.y, 0)).y;
    return Math.max(centerY, getNodeTopY(tile.node));
}

function resolveActorSortAnchorY(node: Node): number {
    return isVehicleEntityName(safeNodeName(node)) ? DEFAULT_VEHICLE_ANCHOR_Y : DEFAULT_PLAYER_ANCHOR_Y;
}

function resolveActorSortDisplayHeight(node: Node, role: MoverRole, theme?: string): number {
    const ui = resolveSortTransform(node);
    if (ui && ui.contentSize.height > 0) {
        return ui.contentSize.height * Math.abs(node.scale.y);
    }
    const sizes = getEntityDisplaySizes(theme);
    return role === 'vehicle' ? sizes.vehicle.height : sizes.player.height;
}

/** 逻辑格站立点 + 固定参考锚点 → 排序底边 Y */
function getActorStandBottomY(
    cell: Vec3,
    node: Node,
    role: MoverRole,
    config?: LevelConfig,
    theme?: string,
): number {
    const stand = entityWorldPositionForRole(cell, config, theme, role);
    const h = resolveActorSortDisplayHeight(node, role, theme);
    return stand.y - resolveActorSortAnchorY(node) * h;
}

/** 移动中反推逻辑格 */
function getEntitySortCell(node: Node): { x: number; y: number } {
    const gm = GameManager.instance;
    if (!gm) return { x: 0, y: 0 };
    const config = resolveLevelConfig();
    const theme = config?.theme ?? gm.uiStyle;
    const n = safeNodeName(node);
    let role: MoverRole = 'player';
    if (isVehicleEntityName(n)) role = 'vehicle';
    const cell = worldToMoverCellForRole(node.position, config, theme, role);
    return { x: cell.x, y: cell.y };
}

/** 关卡内所有砖块/实体并入 Tiles（避免 Ground/Border 在 BFS 中整块早于 Tiles 绘制） */
function pullDrawablesIntoTiles(levelRoot: Node, tilesRoot: Node): boolean {
    const worldPos = new Vec3();
    const pending: Node[] = [];
    const walk = (parent: Node) => {
        for (const ch of parent.children) {
            if (!ch?.isValid || !ch.active) continue;
            if (parseTileNodeName(ch.name) || isEntityDrawNode(ch)) {
                pending.push(ch);
                continue;
            }
            if (ch.name === 'Ground' || ch.name === 'Border' || ch.name === 'Tiles') {
                walk(ch);
            }
        }
    };
    walk(levelRoot);
    let moved = false;
    for (const node of pending) {
        if (node.parent === tilesRoot) continue;
        node.getWorldPosition(worldPos);
        node.parent = tilesRoot;
        node.setWorldPosition(worldPos);
        moved = true;
    }
    return moved;
}
function dedupeLayerTileDuplicates(levelRoot: Node, tilesRoot: Node) {
    for (const layerName of ['Ground', 'Border'] as const) {
        const layer = levelRoot.getChildByName(layerName);
        if (!layer) continue;
        for (const ch of [...layer.children]) {
            if (!ch?.isValid) continue;
            if (!parseTileNodeName(ch.name)) continue;
            const canon = tilesRoot.getChildByName(ch.name);
            if (canon?.isValid && canon !== ch) {
                ch.destroy();
            }
        }
    }
}

/**
 * 移动中采样格（图层用）——人物 / 载具拆开：
 * - 角色朝屏幕前（落点 x+y 更小）：用落点，避免裁腿；朝后用起点
 * - 载具朝屏幕前（下/南、右/东）：用起点，避免提早浮到前侧地砖上
 * - 载具朝屏幕后（上/北、左/西）：用落点，否则钉在起点会比落点更靠前，
 *   途中会短暂「不被下方砖挡住」，落地写回后又正常
 * - 骑乘跟驱动方取格，再按本节点人/车分叉
 */
function resolveDrawSampleMovement(node: Node): Movement | null {
    const self = node.getComponent(Movement);
    const rider = (node.getComponent('VehicleController') as {
        getPlayer?: () => Movement | null;
    } | null)?.getPlayer?.() ?? null;
    if (rider?.isMoving()) return rider;
    const vehicle = (node.getComponent('PlayerController') as {
        getRideVehicle?: () => Movement | null;
    } | null)?.getRideVehicle?.() ?? null;
    if (vehicle?.isMoving()) return vehicle;
    return self;
}

function isVehicleSortNode(node: Node): boolean {
    return isVehicleEntityName(safeNodeName(node)) || !!node.getComponent('VehicleController');
}

function sampleCellFromMovement(
    mov: Movement,
    node: Node,
    fallback: { x: number; y: number },
): { x: number; y: number } {
    const committed = mov.getCommittedCell();
    if (mov.isMoving()) {
        const landing = mov.getLandingCell();
        if (committed && landing) {
            const fromSum = committed.x + committed.y;
            const toSum = landing.x + landing.y;
            const goingFront = toSum < fromSum;
            if (isVehicleSortNode(node)) {
                // 载具：朝前钉起点；朝后改落点（与落地静止同层，继续被下方砖挡）
                if (goingFront) {
                    return { x: committed.x, y: committed.y };
                }
                return { x: landing.x, y: landing.y };
            }
            // 角色：朝前用落点，朝后用起点
            if (goingFront) {
                return { x: landing.x, y: landing.y };
            }
            return { x: committed.x, y: committed.y };
        }
        if (committed) return { x: committed.x, y: committed.y };
        return getEntitySortCell(node);
    }
    if (committed) return { x: committed.x, y: committed.y };
    const spawn = mov.getSpawnCell();
    if (spawn) return { x: spawn.x, y: spawn.y };
    return fallback;
}

function getActorWallSampleCell(entity: EntityDrawEntry, node: Node): { x: number; y: number } {
    const mov = resolveDrawSampleMovement(node);
    if (!mov) return { x: entity.x, y: entity.y };
    return sampleCellFromMovement(mov, node, { x: entity.x, y: entity.y });
}

function resolveSortTransform(node: Node): UITransform | null {
    const rootUi = node.getComponent(UITransform);
    if (rootUi && rootUi.contentSize.height > 0) return rootUi;
    for (const ch of node.children) {
        if (!ch?.isValid) continue;
        const ui = ch.getComponent(UITransform);
        if (ui && ui.contentSize.height > 0) return ui;
    }
    return rootUi;
}

/** 足底中心 Y（Tiles 本地）；排序依据底边而非锚点/中心 */
export function getNodeBottomY(node: Node): number {
    if (!node?.isValid) return 0;
    const ui = resolveSortTransform(node);
    const pos = node.position;
    if (!ui || ui.contentSize.height <= 0) return pos.y;
    const h = ui.contentSize.height * Math.abs(node.scale.y);
    return pos.y - ui.anchorPoint.y * h;
}

/** 画家算法 Order：round(底边Y × DRAW_SORT_Y_SCALE) */
export function getNodeSortOrder(node: Node): number {
    return Math.round(getNodeBottomY(node) * DRAW_SORT_Y_SCALE);
}

/**
 * 人物/载具排序底边 Y：始终按逻辑格站立点 + 固定参考锚点；
 * 移动中在起止格底边 Y 之间插值（骑乘时跟驱动方 Movement 同步）。
 */
function getEntitySortBottomY(node: Node): number {
    if (!isActorEntity(node)) return getNodeBottomY(node);
    const mov = resolveDrawSampleMovement(node) ?? node.getComponent(Movement);
    const gm = GameManager.instance;
    const config = resolveLevelConfig();
    const theme = config?.theme ?? gm?.uiStyle;
    const role: MoverRole = isVehicleEntityName(safeNodeName(node)) ? 'vehicle' : 'player';

    if (mov?.isMoving()) {
        const from = mov.getCommittedCell();
        const to = mov.getLandingCell();
        if (from && to) {
            const t = mov.getMoveStepProgress();
            const fromY = getActorStandBottomY(from, node, role, config, theme);
            const toY = getActorStandBottomY(to, node, role, config, theme);
            return fromY + (toY - fromY) * t;
        }
    }

    const cell = mov?.getCommittedCell() ?? mov?.getSpawnCell();
    if (!cell) return getNodeBottomY(node);
    return getActorStandBottomY(cell, node, role, config, theme);
}

/** 实体所在关卡根（Ground/Border/Tiles 的父节点，不是 Tiles 容器本身） */
export function resolveLevelDrawRoot(from: Node): Node | null {
    let cur: Node | null = from;
    while (cur?.isValid) {
        if (cur.getChildByName('Ground') || cur.getChildByName('Border') || cur.getChildByName('Tiles')) {
            return cur;
        }
        if (/^Level_\d+/.test(cur.name)) return cur;
        cur = cur.parent;
    }
    return null;
}

/** 实体逻辑格（spawn / committed；实体互排序用，与砖块比较用底边 Y） */
function getEntityLogicCell(node: Node): { x: number; y: number } {
    const propCtrl = node.getComponent('PropController') as { getSpawnCell?: () => Vec3 | null } | null;
    const propCell = propCtrl?.getSpawnCell?.();
    if (propCell) return { x: propCell.x, y: propCell.y };

    const m = /^Prop_(-?\d+)_(-?\d+)$/.exec(safeNodeName(node));
    if (m) return { x: Number(m[1]), y: Number(m[2]) };

    const vehicle = node.getComponent('VehicleController') as {
        getPlayer?: () => { getCommittedCell?: () => Vec3 | null; getSpawnCell?: () => Vec3 | null } | null;
    } | null;
    const rider = vehicle?.getPlayer?.() ?? null;
    if (rider) {
        const riderCell = rider.getCommittedCell?.() ?? rider.getSpawnCell?.();
        if (riderCell) return { x: riderCell.x, y: riderCell.y };
    }

    const mov = node.getComponent(Movement);
    if (mov) {
        const cell = mov.getCommittedCell?.() ?? mov.getSpawnCell?.();
        if (cell) return { x: cell.x, y: cell.y };
    }

    const gm = GameManager.instance;
    if (gm) {
        const c = gm.worldToCell(node.position);
        return { x: c.x, y: c.y };
    }
    return { x: 0, y: 0 };
}

/** 关卡内实体（可能在 levelRoot 或 Tiles 下） */
export function forEachLevelEntityNode(levelRoot: Node, fn: (node: Node) => void) {
    if (!levelRoot?.isValid) return;
    for (const ch of levelRoot.children) {
        if (ch?.isValid && isEntityDrawNode(ch)) fn(ch);
    }
    const tiles = levelRoot.getChildByName('Tiles');
    if (!tiles?.isValid) return;
    for (const ch of tiles.children) {
        if (ch?.isValid && isEntityDrawNode(ch)) fn(ch);
    }
}

export function findLevelChildByName(levelRoot: Node, name: string): Node | null {
    if (!levelRoot?.isValid) return null;
    const direct = levelRoot.getChildByName(name);
    if (direct) return direct;
    return levelRoot.getChildByName('Tiles')?.getChildByName(name) ?? null;
}

function resolveLevelConfig(): LevelConfig | undefined {
    return getLevelRuntimeContext()?.getCurLevel()
        ?? GameManager.instance?.getCurLevel?.()
        ?? undefined;
}

function collectTileEntries(levelRoot: Node, tilesRoot: Node, seen: Set<Node>): TileDrawEntry[] {
    const config = resolveLevelConfig();
    const entries: TileDrawEntry[] = [];
    const pushTile = (node: Node, parsed: { layer: 'ground' | 'border'; x: number; y: number }) => {
        if (seen.has(node)) return;
        seen.add(node);
        const tileName = resolveTileName(parsed, config);
        const kind = classifyTileKind(parsed, config);
        entries.push({
            node,
            x: parsed.x,
            y: parsed.y,
            kind,
            tileName,
        });
    };

    forEachTileChild(levelRoot, pushTile);
    for (const ch of tilesRoot.children) {
        if (!ch?.isValid || seen.has(ch)) continue;
        const parsed = parseTileNodeName(ch.name);
        if (parsed) pushTile(ch, parsed);
    }
    return entries;
}

function collectEntityEntries(levelRoot: Node, tilesRoot: Node, seen: Set<Node>): EntityDrawEntry[] {
    const entries: EntityDrawEntry[] = [];
    const pushEntity = (node: Node | null | undefined) => {
        if (!node?.isValid || seen.has(node) || !isEntityDrawNode(node)) return;
        seen.add(node);
        const cell = getEntityLogicCell(node);
        entries.push({
            node,
            x: cell.x,
            y: cell.y,
            kind: classifyEntityKind(node),
        });
    };
    const walkEntities = (parent: Node) => {
        for (const ch of parent.children) {
            if (!ch?.isValid) continue;
            if (isEntityDrawNode(ch)) {
                pushEntity(ch);
                continue;
            }
            if (ch.name === 'Ground' || ch.name === 'Border' || ch.name === 'Tiles') {
                walkEntities(ch);
            }
        }
    };
    walkEntities(levelRoot);
    return entries;
}

function applyDrawOrder(tilesRoot: Node, entries: DrawEntry[]) {
    const inSort = new Set<Node>();
    const worldPos = new Vec3();

    for (const { node } of entries) {
        if (!node.isValid) continue;
        inSort.add(node);
        if (isEntityDrawNode(node)) ensureEntityUILayer(node);
        if (node.parent !== tilesRoot) {
            node.getWorldPosition(worldPos);
            node.parent = tilesRoot;
            node.setWorldPosition(worldPos);
        }
    }

    let head = 0;
    for (const ch of [...tilesRoot.children]) {
        if (!ch.isValid || inSort.has(ch)) continue;
        if (ch.getSiblingIndex() !== head) ch.setSiblingIndex(head);
        head++;
    }

    // 只调 sibling，不写 z（UI_2D 改 z 会抖）
    for (let i = entries.length - 1; i >= 0; i--) {
        const node = entries[i].node;
        if (!node.isValid) continue;
        const idx = head + i;
        if (node.getSiblingIndex() !== idx) node.setSiblingIndex(idx);
    }
}

function drawOrderSignature(entries: DrawEntry[], keys: number[]): string {
    let s = '';
    for (let i = 0; i < entries.length; i++) {
        if (i) s += '\n';
        s += `${entries[i].node.uuid}:${keys[i]}`;
    }
    return s;
}

function finalizeLevelRootOrder(levelRoot: Node, tilesRoot: Node) {
    for (const name of ['Ground', 'Border'] as const) {
        const layer = levelRoot.getChildByName(name);
        if (!layer) continue;
        layer.active = layer.children.length > 0;
        if (layer.children.length === 0) {
            layer.setSiblingIndex(0);
        }
    }
    tilesRoot.setSiblingIndex(levelRoot.children.length - 1);
    tilesRoot.active = true;
}

/**
 * 等距绘制排序：开局 force 全量；起步/落地 dirty 时若顺序未变则跳过。
 */
export function sortIsoTiles(levelRoot: Node, force = false) {
    if (!levelRoot?.isValid) return;
    ensureDrawOrderUpdater(levelRoot);
    const tilesRoot = ensureTilesRoot(levelRoot);
    pullDrawablesIntoTiles(levelRoot, tilesRoot);
    dedupeLayerTileDuplicates(levelRoot, tilesRoot);
    const seen = new Set<Node>();
    const tileEntries = collectTileEntries(levelRoot, tilesRoot, seen);
    const entityEntries = collectEntityEntries(levelRoot, tilesRoot, seen);
    const sorted = buildSortedDrawOrder(tileEntries, entityEntries);
    const updater = levelRoot.getComponent(IsoDrawOrderUpdater);
    if (force) updater?.resetOrderCache();
    const sig = drawOrderSignature(sorted.entries, sorted.keys);
    const changed = !updater || updater.noteOrderChanged(sig);
    if (!force && !changed) {
        finalizeLevelRootOrder(levelRoot, tilesRoot);
        return;
    }
    applyDrawOrder(tilesRoot, sorted.entries);
    finalizeLevelRootOrder(levelRoot, tilesRoot);
}

/** @deprecated 与 sortIsoTiles 相同 */
export function refreshIsoEntityDrawOrder(levelRoot: Node) {
    sortIsoTiles(levelRoot, true);
}

/** @deprecated 与 sortIsoTiles 相同 */
export function refreshIsoDrawOrder(levelRoot: Node) {
    sortIsoTiles(levelRoot, true);
}

/** @deprecated 与 sortIsoTiles 相同 */
export function refreshIsoDrawOrderImmediate(levelRoot: Node) {
    sortIsoTiles(levelRoot, true);
}

export function bringEntityNodesToFront(levelRoot: Node, _opts?: EntityDrawOrderOptions) {
    sortIsoTiles(levelRoot, true);
}

export function ensureEntityUILayer(node: Node) {
    const walk = (n: Node) => {
        n.layer = UI_LAYER;
        for (const ch of n.children) walk(ch);
    };
    walk(node);
}

function ensureTilesRoot(levelRoot: Node): Node {
    let tilesRoot = levelRoot.getChildByName('Tiles');
    if (!tilesRoot) {
        tilesRoot = new Node('Tiles');
        tilesRoot.layer = UI_LAYER;
        tilesRoot.parent = levelRoot;
        setupLayerContainer(tilesRoot);
    }
    tilesRoot.setSiblingIndex(levelRoot.children.length - 1);
    return tilesRoot;
}

const TILE_LAYER_NAMES = ['Tiles', 'Ground', 'Border'] as const;

function forEachTileChild(levelRoot: Node, fn: (node: Node, parsed: { layer: 'ground' | 'border'; x: number; y: number }) => void) {
    for (const layerName of TILE_LAYER_NAMES) {
        const layer = levelRoot.getChildByName(layerName);
        if (!layer) continue;
        for (const ch of layer.children) {
            if (!ch?.isValid || !ch.active) continue;
            const parsed = parseTileNodeName(ch.name);
            if (!parsed) continue;
            fn(ch, parsed);
        }
    }
}

export function alignTileNode(node: Node, cellX: number, cellY: number, tileName: string, theme?: string) {
    const w = cellToWorldCenter(new Vec3(cellX, cellY, 0));
    let ui = node.getComponent(UITransform);
    if (!ui) ui = node.addComponent(UITransform);
    const spr = node.getComponent(Sprite);
    const source = resolveTilePixelSize(tileName, spr?.spriteFrame ?? null, theme);
    const draw = getTileDrawSize(tileName, source.width, source.height, theme);
    const pivot = getTilePivot(tileName, theme);
    ui.setContentSize(draw.width, draw.height);
    ui.setAnchorPoint(pivot.x, pivot.y);
    node.setPosition(w.x, w.y, 0);
    node.setScale(1, 1, 1);
}

export function setupLayerContainer(layer: Node) {
    let ui = layer.getComponent(UITransform);
    if (!ui) ui = layer.addComponent(UITransform);
    ui.setAnchorPoint(0, 0);
    ui.setContentSize(1, 1);
    layer.setPosition(0, 0, 0);
}

function tileNameFromConfig(
    layer: 'ground' | 'border',
    key: string,
    config: LevelConfig,
): string {
    return resolveTileNameAtCell(layer, key, config.ground, config.border);
}

export function layoutLevelTiles(levelRoot: Node, config: LevelConfig) {
    if (!levelRoot?.isValid) return;
    const theme = config.theme;
    const ground = levelRoot.getChildByName('Ground');
    const border = levelRoot.getChildByName('Border');
    if (ground) {
        setupLayerContainer(ground);
        for (const ch of ground.children) {
            const parsed = parseTileNodeName(ch.name);
            if (!parsed || parsed.layer !== 'ground') continue;
            const key = `${parsed.x},${parsed.y}`;
            alignTileNode(ch, parsed.x, parsed.y, tileNameFromConfig('ground', key, config), theme);
        }
    }
    if (border) {
        setupLayerContainer(border);
        for (const ch of border.children) {
            const parsed = parseTileNodeName(ch.name);
            if (!parsed || parsed.layer !== 'border') continue;
            const key = `${parsed.x},${parsed.y}`;
            alignTileNode(ch, parsed.x, parsed.y, tileNameFromConfig('border', key, config), theme);
        }
    }
    const tilesRoot = levelRoot.getChildByName('Tiles');
    if (tilesRoot) {
        setupLayerContainer(tilesRoot);
        for (const ch of tilesRoot.children) {
            const parsed = parseTileNodeName(ch.name);
            if (!parsed) continue;
            const key = `${parsed.x},${parsed.y}`;
            const layer = parsed.layer;
            alignTileNode(ch, parsed.x, parsed.y, tileNameFromConfig(layer, key, config), theme);
        }
    }
    sortIsoTiles(levelRoot, true);
}

@ccclass('IsoDrawOrderUpdater')
class IsoDrawOrderUpdater extends Component {
    private _dirty = true;
    private _lastOrderSig = '';

    markDirty() {
        this._dirty = true;
    }

    resetOrderCache() {
        this._lastOrderSig = '';
    }

    /** @returns true 表示顺序相对缓存有变化 */
    noteOrderChanged(sig: string): boolean {
        if (sig === this._lastOrderSig) return false;
        this._lastOrderSig = sig;
        return true;
    }

    lateUpdate() {
        if (!this.node?.isValid) return;
        const st = GameManager.instance?.gameState;
        if (st === GameState.ResultWin || st === GameState.ResultFail) return;
        if (!this._dirty) return;
        this._dirty = false;
        sortIsoTiles(this.node, false);
    }
}

function ensureDrawOrderUpdater(levelRoot: Node) {
    if (!levelRoot.getComponent(IsoDrawOrderUpdater)) {
        levelRoot.addComponent(IsoDrawOrderUpdater);
    }
}

export function markIsoDrawOrderDirty(levelRoot: Node) {
    if (!levelRoot?.isValid) return;
    ensureDrawOrderUpdater(levelRoot);
    levelRoot.getComponent(IsoDrawOrderUpdater)?.markDirty();
}
