import {
    _decorator, Component, Vec3, Graphics, Color, Node, UITransform, Layers, Prefab,
} from 'cc';
import { EDITOR, PREVIEW, BUILD } from 'cc/env';
import {
    cellToWorldCenter, tileNodeName, parseTileNodeName, getHalfCellSize,
} from '../core/GridCoords';
import { LevelMapData } from './LevelMapData';
import { alignTileNode, setupLayerContainer, sortIsoTiles } from './TileLayout';
import { resolveTileNameAtCell } from './TileKinds';

const { ccclass, property, executeInEditMode, executionOrder } = _decorator;

const UI_LAYER = Layers.Enum.UI_2D;

/** 与关卡编辑器 canvas 一致（仅网格线，无填充） */
const GRID_STROKE = new Color(85, 85, 85, 255);
const CELL_STROKE = new Color(74, 158, 255, 255);

const RUNTIME_GRID_NODE_NAMES = new Set(['_GridSnapGizmo']);

/**
 * 挂在关卡预制体根节点（LevelN）。编辑器中：
 * - 显示与关卡地图编辑面板一致的等距菱形参考格（无灰底，不遮挡主题背景）
 * - 拖动 Ground/Border/Tiles 下瓦片时吸附到格子中心
 */
@ccclass('GridSnapHelper')
@executeInEditMode(true)
@executionOrder(-200)
export class GridSnapHelper extends Component {
    @property({ displayName: '吸附格子', tooltip: '场景内移动瓦片时自动对齐格子中心' })
    snapEnabled = true;

    @property({ displayName: '显示参考网格' })
    showGrid = false;

    @property({ displayName: '网格半径', min: 4, max: 24, step: 1, tooltip: '无地图数据时的备用半径' })
    gridRadius = 12;

    @property({ displayName: '地图周围额外格数', min: 0, max: 6, step: 1 })
    gridPadding = 2;

    @property({ displayName: '高亮已有砖块的格子' })
    highlightOccupied = true;

    @property({ displayName: '吸附时同步节点名' })
    syncNodeNames = true;

    private _gridHost: Node | null = null;
    private _graphics: Graphics | null = null;
    private _alignedOnce = false;

    /** 仅「编辑器内编辑预制体/场景、未点预览」时为 true */
    static isEditingInEditor(): boolean {
        return EDITOR && !PREVIEW && !BUILD;
    }

    /** 预览、构建、真机运行 */
    static isRuntime(): boolean {
        return !GridSnapHelper.isEditingInEditor();
    }

    /**
     * 实例化前关闭 prefab 内 showGrid（仅当 prefab.data 可安全访问时）。
     * 编辑器预览若脚本未编译完成，访问 prefab.data 可能抛错；失败时由实例化后 stripBeforePlay 兜底。
     */
    static stripBeforePlayFromPrefab(prefab: Prefab) {
        try {
            const root = prefab.data as Node | null;
            if (!root?.isValid) return;
            for (const snap of root.getComponentsInChildren(GridSnapHelper)) {
                if (!snap?.isValid) continue;
                snap.showGrid = false;
                snap.enabled = false;
            }
        } catch (e) {
            console.warn('[GridSnapHelper] stripBeforePlayFromPrefab 跳过（将在实例化后清理）', e);
        }
    }

    /** 关卡实例化后立即剥离编辑器网格 */
    static stripBeforePlay(levelRoot: Node) {
        if (!levelRoot?.isValid) return;
        for (const snap of levelRoot.getComponents(GridSnapHelper)) {
            if (!snap.isValid) continue;
            snap.showGrid = false;
            snap.enabled = false;
            snap.removeGridGizmo();
        }
        GridSnapHelper.destroyGridGizmoNodes(levelRoot);
    }

    /** 预览/构建/真机运行：彻底移除一切网格节点与组件（无条件，由游戏逻辑调用） */
    static purgeRuntimeGrids(root: Node) {
        if (!root?.isValid) return;
        try {
            for (const snap of [...root.getComponentsInChildren(GridSnapHelper)]) {
                if (!snap?.isValid) continue;
                snap.showGrid = false;
                snap.enabled = false;
                snap.removeGridGizmo();
                snap.destroy();
            }
            GridSnapHelper.destroyGridGizmoNodes(root);
        } catch (e) {
            console.warn('[GridSnapHelper] purgeRuntimeGrids 部分失败', e);
        }
    }

    /** 递归销毁编辑器参考格节点 */
    static destroyGridGizmoNodes(root: Node) {
        const walk = (node: Node) => {
            for (let i = node.children.length - 1; i >= 0; i--) {
                const ch = node.children[i];
                if (RUNTIME_GRID_NODE_NAMES.has(ch.name)) {
                    ch.getComponent(Graphics)?.clear();
                    ch.destroy();
                    continue;
                }
                walk(ch);
            }
        };
        walk(root);
    }

    static purgeScene(scene: Node) {
        GridSnapHelper.purgeRuntimeGrids(scene);
    }

    /** @deprecated 使用 purgeRuntimeGrids */
    static stripRuntimeGrid(levelRoot: Node) {
        GridSnapHelper.purgeRuntimeGrids(levelRoot);
    }

    onLoad() {
        if (!GridSnapHelper.isEditingInEditor()) {
            this.showGrid = false;
            this.enabled = false;
            this.removeGridGizmo();
            this.destroy();
            return;
        }
        this.ensureGridGraphics();
        this.alignAllTiles();
        this._alignedOnce = true;
    }

    onEnable() {
        if (!GridSnapHelper.isEditingInEditor()) {
            this.showGrid = false;
            this.removeGridGizmo();
            return;
        }
        if (!this.showGrid) return;
        this.ensureGridGraphics();
        if (!this._alignedOnce) this.alignAllTiles();
        this.drawReferenceGrid();
    }

    start() {
        if (!GridSnapHelper.isEditingInEditor()) this.removeGridGizmo();
    }

    removeGridGizmo() {
        this.showGrid = false;
        const host = this.node.getChildByName('_GridSnapGizmo');
        if (host) {
            host.getComponent(Graphics)?.clear();
            host.destroy();
        }
        this._gridHost = null;
        this._graphics = null;
    }

    lateUpdate() {
        if (!GridSnapHelper.isEditingInEditor()) {
            this.removeGridGizmo();
            return;
        }
        if (!this.showGrid) return;
        this.drawReferenceGrid();
    }

    /** 供扩展场景脚本或外部调用 */
    snapNode(node: Node | null) {
        if (!node || !node.isValid) return;
        const layer = this.resolveLayer(node);
        if (!layer) return;
        const { ground, border } = this.readMapJson();
        this.applySnap(node, layer, ground, border);
        sortIsoTiles(this.node);
    }

    private resolveLayer(node: Node): 'ground' | 'border' | null {
        let cur: Node | null = node;
        while (cur) {
            if (cur.name === 'Ground') return 'ground';
            if (cur.name === 'Border') return 'border';
            if (cur === this.node) break;
            cur = cur.parent;
        }
        const parsed = parseTileNodeName(node.name);
        return parsed?.layer ?? null;
    }

    private readMapJson(): {
        ground: Record<string, string>;
        border: Record<string, boolean | string>;
    } {
        const md = this.node.getComponent(LevelMapData);
        if (!md) return { ground: {}, border: {} };
        try {
            return {
                ground: JSON.parse(md.groundJson || '{}'),
                border: JSON.parse(md.borderJson || '{}'),
            };
        } catch {
            return { ground: {}, border: {} };
        }
    }

    private occupiedKeys(ground: Record<string, string>, border: Record<string, boolean | string>): Set<string> {
        return new Set([...Object.keys(ground), ...Object.keys(border)]);
    }

    /** 编辑器打开预制体时对齐全部瓦片并合并深度排序 */
    alignAllTiles() {
        const { ground, border } = this.readMapJson();
        if (this.snapEnabled) {
            this.snapLayerChildren('Ground', 'ground', ground, border);
            this.snapLayerChildren('Border', 'border', ground, border);
            this.snapLayerChildren('Tiles', null, ground, border);
        }
        sortIsoTiles(this.node);
    }

    private snapLayerChildren(
        layerName: string,
        layer: 'ground' | 'border' | null,
        groundMap: Record<string, string>,
        borderMap: Record<string, boolean | string>,
    ) {
        const layerNode = this.node.getChildByName(layerName);
        if (!layerNode) return;
        setupLayerContainer(layerNode);
        for (const ch of layerNode.children) {
            const parsed = parseTileNodeName(ch.name);
            if (!parsed) continue;
            const useLayer = layer ?? parsed.layer;
            this.applySnap(ch, useLayer, groundMap, borderMap);
        }
    }

    private inferTileName(
        layer: 'ground' | 'border',
        key: string,
        groundMap: Record<string, string>,
        borderMap: Record<string, boolean | string>,
    ): string {
        return resolveTileNameAtCell(layer, key, groundMap, borderMap);
    }

    private applySnap(
        node: Node,
        layer: 'ground' | 'border',
        groundMap: Record<string, string>,
        borderMap: Record<string, boolean | string>,
    ) {
        const parsed = parseTileNodeName(node.name);
        if (!parsed) return;
        const key = `${parsed.x},${parsed.y}`;
        const tileName = this.inferTileName(layer, key, groundMap, borderMap);
        alignTileNode(node, parsed.x, parsed.y, tileName);
        if (this.syncNodeNames) {
            const expected = tileNodeName(layer, parsed.x, parsed.y);
            if (node.name !== expected) node.name = expected;
        }
    }

    private ensureGridGraphics() {
        if (!GridSnapHelper.isEditingInEditor()) return;
        let host = this.node.getChildByName('_GridSnapGizmo');
        if (!host) {
            host = new Node('_GridSnapGizmo');
            host.parent = this.node;
        }
        host.layer = UI_LAYER;
        host.setSiblingIndex(0);
        let ui = host.getComponent(UITransform);
        if (!ui) ui = host.addComponent(UITransform);
        ui.setAnchorPoint(0, 0);
        ui.setContentSize(1, 1);
        let g = host.getComponent(Graphics);
        if (!g) g = host.addComponent(Graphics);
        this._gridHost = host;
        this._graphics = g;
    }

    private iterGridCells(): { x: number; y: number }[] {
        const { ground, border } = this.readMapJson();
        const keys = [...Object.keys(ground), ...Object.keys(border)];
        if (!keys.length) {
            const out: { x: number; y: number }[] = [];
            const r = this.gridRadius;
            for (let x = -r; x <= r; x++) {
                for (let y = -r; y <= r; y++) out.push({ x, y });
            }
            return out;
        }
        let minX = Infinity;
        let maxX = -Infinity;
        let minY = Infinity;
        let maxY = -Infinity;
        for (const k of keys) {
            const [xs, ys] = k.split(',');
            const x = parseInt(xs, 10);
            const y = parseInt(ys, 10);
            if (Number.isNaN(x) || Number.isNaN(y)) continue;
            minX = Math.min(minX, x);
            maxX = Math.max(maxX, x);
            minY = Math.min(minY, y);
            maxY = Math.max(maxY, y);
        }
        const pad = this.gridPadding;
        const out: { x: number; y: number }[] = [];
        for (let x = minX - pad; x <= maxX + pad; x++) {
            for (let y = minY - pad; y <= maxY + pad; y++) out.push({ x, y });
        }
        return out;
    }

    private traceDiamond(g: Graphics, cx: number, cy: number, halfW: number, halfH: number) {
        g.moveTo(cx, cy + halfH);
        g.lineTo(cx + halfW, cy);
        g.lineTo(cx, cy - halfH);
        g.lineTo(cx - halfW, cy);
        g.close();
    }

    private drawReferenceGrid() {
        if (!GridSnapHelper.isEditingInEditor() || !this.showGrid) return;
        this.ensureGridGraphics();
        const g = this._graphics;
        if (!g) return;

        const { halfW, halfH } = getHalfCellSize();
        const cells = this.iterGridCells();
        const { ground, border } = this.readMapJson();
        const occupied = this.highlightOccupied ? this.occupiedKeys(ground, border) : new Set<string>();

        g.clear();

        for (const { x, y } of cells) {
            const w = cellToWorldCenter(new Vec3(x, y, 0));
            const key = `${x},${y}`;
            const isOcc = occupied.has(key);
            g.lineWidth = isOcc ? 1.5 : 1;
            g.strokeColor = isOcc ? CELL_STROKE : GRID_STROKE;
            this.traceDiamond(g, w.x, w.y, halfW, halfH);
            g.stroke();
        }
    }
}
