import { _decorator, Component, director, Node, Label, Button, UITransform, Color, Vec3, Graphics, Sprite, SpriteFrame, Texture2D, resources, Rect, Size, ImageAsset } from 'cc';
import { UIFlowMgr, ISceneEnter } from '../ui/UIFlowMgr';
import { DESIGN_WIDTH, DESIGN_HEIGHT } from '../common/Constants';

const { ccclass, property } = _decorator;

/** Maps abstract SceneId → physical Cocos Creator scene name. */
const SCENE_MAP: Record<string, string> = {
    boot: 'Boot',
    story_intro: 'StoryIntro',
    main_menu: 'MainMenu',
    // level_select currently reuses MainMenu until a dedicated LevelSelect scene exists.
    level_select: 'MainMenu',
    // `gameplay` is dispatched by levelId — resolved at runtime.
    gameplay: 'Level_1_1',
    settlement: 'Settlement',
    // Settings panel is overlayed on MainMenu for the MVP.
    settings: 'MainMenu',
};

/** levelId → physical scene name mapping (chapter 1 only). */
const LEVEL_SCENE_MAP: Record<string, string> = {
    '1-1': 'Level_1_1',
    '1-2': 'Level_1_2',
    '1-3': 'Level_1_3',
    '1-4': 'Level_1_4',
    '1-5': 'Level_1_5',
    '1-5-boss': 'Boss_ShuangHuanFang',
};

/** Level definitions for the level-select screen (chapter 1 MVP). */
const LEVEL_DEFS: ReadonlyArray<{ id: string; label: string }> = [
    { id: '1-1', label: '1-1  森林入口' },
    { id: '1-2', label: '1-2  竹林深处' },
    { id: '1-3', label: '1-3  城墙之下' },
    { id: '1-4', label: '1-4  密道暗行' },
    { id: '1-5', label: '1-5  魔城前庭' },
    { id: '1-5-boss', label: 'Boss  双幻坊' },
];

/**
 * MainMenu scene entry (task 9.2 hookup).
 *
 * Owns a `UIFlowMgr` instance and translates each abstract `onSceneEnter`
 * callback into a concrete `director.loadScene` call. Attach this component
 * to the root node of `MainMenu.scene`.
 *
 * Supports two display modes reusing the same scene:
 *   - **main_menu**: title + Start / Settings buttons
 *   - **level_select**: chapter heading + level buttons + Back button
 *
 * When `autoBuildUI` is enabled (default), all UI is created
 * programmatically so the scene is usable out of the box even before
 * any art pass.
 */
@ccclass('MainMenuEntry')
export class MainMenuEntry extends Component {
    @property({ tooltip: '是否自动生成 Start / Settings 按钮 (方便没美术时就能跑通)' })
    public autoBuildUI: boolean = true;

    private flow: UIFlowMgr | undefined;
    /** Nodes created in main-menu mode, so we can remove them when switching. */
    private menuNodes: Node[] = [];
    /** Nodes created in level-select mode, so we can remove them when switching. */
    private levelSelectNodes: Node[] = [];

    protected onLoad(): void {
        this.flow = new UIFlowMgr(undefined, {
            onSceneEnter: (ev) => this.handleSceneEnter(ev),
        });
        if (this.autoBuildUI) this.buildMainMenuUI();
    }

    /** Bind this to the "Start" button's click event in the Inspector. */
    public onPressStart(): void {
        this.flow?.onPressStartGame();
    }

    /** Bind this to the "Settings" button's click event. */
    public onPressSettings(): void {
        this.flow?.onOpenSettings();
    }

    /** Pick a level from the level-select screen. */
    public onPressLevel(levelId: string): void {
        this.flow?.onPickLevel(levelId);
    }

    /** Go back from level-select to main-menu. */
    public onPressBackToMenu(): void {
        this.showMainMenu();
    }

    private handleSceneEnter(ev: ISceneEnter): void {
        const payload = ev.payload ?? {};

        // gameplay: always load a different scene.
        if (ev.scene === 'gameplay' && typeof payload.levelId === 'string') {
            const physical = LEVEL_SCENE_MAP[payload.levelId];
            if (physical) {
                director.loadScene(physical);
                return;
            }
        }

        // level_select: we reuse this scene — just swap UI panels.
        if (ev.scene === 'level_select') {
            this.showLevelSelect();
            return;
        }

        // settings: for MVP, overlayed on MainMenu — just swap to menu mode.
        if (ev.scene === 'settings') {
            this.showMainMenu();
            return;
        }

        // For any other scene (boot / story_intro / settlement / main_menu),
        // load the physical scene normally.
        const physical = SCENE_MAP[ev.scene];
        if (physical && ev.scene !== 'main_menu') {
            director.loadScene(physical);
        } else if (ev.scene === 'main_menu') {
            this.showMainMenu();
        }
    }

    // ------------------------------------------------------------------
    // UI mode switching
    // ------------------------------------------------------------------

    private showMainMenu(): void {
        this.clearNodes(this.levelSelectNodes);
        if (this.menuNodes.length === 0 && this.autoBuildUI) {
            this.buildMainMenuUI();
        }
    }

    private showLevelSelect(): void {
        this.clearNodes(this.menuNodes);
        if (this.levelSelectNodes.length === 0 && this.autoBuildUI) {
            this.buildLevelSelectUI();
        }
    }

    private clearNodes(arr: Node[]): void {
        for (const n of arr) {
            if (n.isValid) {
                n.removeFromParent();
                n.destroy();
            }
        }
        arr.length = 0;
    }

    // ------------------------------------------------------------------
    // Auto-built UI (development affordance; art pass will replace it)
    // ------------------------------------------------------------------
    private buildMainMenuUI(): void {
        // Ensure the host node has a UITransform matching the design resolution.
        ensureCanvasSize(this.node);

        // Title.
        this.menuNodes.push(createLabel(this.node, '影 之 传 说', 0, 140, 44, Color.WHITE));

        // Start button (centered, 40 above origin).
        this.menuNodes.push(createButton(this.node, 'Start', 0, 40, 220, 60, () => this.onPressStart()));

        // Settings button (centered, 40 below origin).
        this.menuNodes.push(createButton(this.node, 'Settings', 0, -40, 220, 60, () => this.onPressSettings()));

        // Hint line at the bottom.
        this.menuNodes.push(createLabel(this.node, 'Chapter 1 · MVP', 0, -200, 20, new Color(180, 180, 180, 255)));
    }

    private buildLevelSelectUI(): void {
        ensureCanvasSize(this.node);

        // Chapter heading.
        this.levelSelectNodes.push(
            createLabel(this.node, '第一章  青叶之刃', 0, 220, 36, Color.WHITE),
        );

        // Level buttons — laid out vertically with even spacing.
        const startY = 140;
        const gapY = 55;
        for (let i = 0; i < LEVEL_DEFS.length; i++) {
            const def = LEVEL_DEFS[i];
            const y = startY - i * gapY;
            this.levelSelectNodes.push(
                createButton(this.node, def.label, 0, y, 280, 44, () => this.onPressLevel(def.id)),
            );
        }

        // Back button at the bottom.
        this.levelSelectNodes.push(
            createButton(this.node, 'Back', 0, -200, 160, 44, () => this.onPressBackToMenu()),
        );
    }
}

// ======================================================================
// Shared UI helpers — intentionally kept inline (no external module) so
// each Scene Entry stays self-contained and easy to remove once real UI is
// authored in the editor.
// ======================================================================

function ensureCanvasSize(host: Node): void {
    let ut = host.getComponent(UITransform);
    if (!ut) ut = host.addComponent(UITransform);
    ut.setContentSize(DESIGN_WIDTH, DESIGN_HEIGHT);
    host.setPosition(0, 0, 0);
}

function createLabel(parent: Node, text: string, x: number, y: number, fontSize: number, color: Color): Node {
    const n = new Node('AutoLabel');
    n.layer = parent.layer;
    parent.addChild(n);
    const ut = n.addComponent(UITransform);
    ut.setContentSize(DESIGN_WIDTH, fontSize * 1.6);
    const lb = n.addComponent(Label);
    lb.useSystemFont = true;
    lb.string = text;
    lb.fontSize = fontSize;
    lb.lineHeight = Math.floor(fontSize * 1.2);
    lb.color = color;
    lb.horizontalAlign = 1; // CENTER
    lb.verticalAlign = 1;   // CENTER
    n.setPosition(new Vec3(x, y, 0));
    return n;
}

function createButton(
    parent: Node,
    text: string,
    x: number,
    y: number,
    w: number,
    h: number,
    onClick: () => void
): Node {
    const n = new Node(`Btn_${text}`);
    n.layer = parent.layer;
    parent.addChild(n);
    const ut = n.addComponent(UITransform);
    ut.setContentSize(w, h);

    // Background drawn via Graphics — avoids needing any texture asset.
    const g = n.addComponent(Graphics);
    g.fillColor = new Color(40, 40, 60, 230);
    g.rect(-w / 2, -h / 2, w, h);
    g.fill();
    g.strokeColor = new Color(200, 200, 220, 255);
    g.lineWidth = 2;
    g.rect(-w / 2, -h / 2, w, h);
    g.stroke();

    // Label child for the text.
    const labelNode = new Node('Label');
    labelNode.layer = parent.layer;
    n.addChild(labelNode);
    const lut = labelNode.addComponent(UITransform);
    lut.setContentSize(w, h);
    const lb = labelNode.addComponent(Label);
    lb.useSystemFont = true;
    lb.string = text;
    lb.fontSize = 24;
    lb.lineHeight = 28;
    lb.color = Color.WHITE;
    lb.horizontalAlign = 1;
    lb.verticalAlign = 1;

    const btn = n.addComponent(Button);
    btn.transition = Button.Transition.SCALE;
    btn.target = n;
    btn.zoomScale = 0.95;
    n.on(Node.EventType.TOUCH_END, onClick, n);

    n.setPosition(new Vec3(x, y, 0));
    return n;
}

// Re-export helpers so sibling Scene Entries can reuse them.
export { ensureCanvasSize, createLabel, createButton, createSprite, createSpriteSheetFrame, createSolidRect };

// ======================================================================
// Sprite helpers (task 10.1 hookup) — load a PNG from `resources/` and
// attach it to a fresh child Node. Returned synchronously; the Sprite's
// `spriteFrame` is assigned once `resources.load` resolves.
//
// Paths passed in are **relative to `assets/resources`** and **without
// extension**. Example: `textures/characters/kage_red` — the helper will
// append `/spriteFrame` automatically.
// ======================================================================

// ======================================================================
// Internal: Load a PNG from `resources/` as a SpriteFrame.
//
// Strategy — try two paths so it works regardless of .meta type:
//   1. `resources.load(path, SpriteFrame)` — works when the .meta has
//      `type: "sprite-frame"` (Creator auto-generates the sub-asset).
//   2. Fallback: `resources.load(path, ImageAsset)` → Texture2D →
//      SpriteFrame — works when .meta has `type: "texture"`.
// ======================================================================

function _loadSpriteFrameFromImage(
    resPath: string,
    onReady: (sf: SpriteFrame) => void,
): void {
    // Try the direct SpriteFrame path first (works with sprite-frame meta).
    resources.load(resPath, SpriteFrame, (err, sf) => {
        if (!err && sf) {
            onReady(sf as SpriteFrame);
            return;
        }
        // Fallback: load raw image and build SpriteFrame manually.
        resources.load(resPath, ImageAsset, (err2, imgAsset) => {
            if (err2 || !imgAsset) {
                console.warn(`[_loadSpriteFrameFromImage] Failed to load '${resPath}' (both paths):`, err, err2);
                return;
            }
            const tex = new Texture2D();
            tex.image = imgAsset as ImageAsset;
            const frame = new SpriteFrame();
            frame.texture = tex;
            frame.rect = new Rect(0, 0, tex.width, tex.height);
            frame.originalSize = new Size(tex.width, tex.height);
            onReady(frame);
        });
    });
}

/**
 * Create a child Node with a Sprite that displays the entire PNG at
 * `resPath`. If `w` / `h` are omitted the node will be resized to the
 * native image size once the texture finishes loading.
 *
 * NOTE: Loads via `ImageAsset` → `Texture2D` → `SpriteFrame` pipeline
 * instead of relying on a `/spriteFrame` sub-asset, because PNG .meta
 * files may not have `type: "sprite-frame"`.
 */
function createSprite(
    parent: Node,
    resPath: string,
    x: number,
    y: number,
    w?: number,
    h?: number,
    opts?: { name?: string; color?: Color; flipX?: boolean }
): Node {
    const n = new Node(opts?.name ?? `Sprite_${resPath.split('/').pop()}`);
    n.layer = parent.layer;
    parent.addChild(n);
    const ut = n.addComponent(UITransform);
    if (w != null && h != null) ut.setContentSize(w, h);
    const sp = n.addComponent(Sprite);
    sp.sizeMode = Sprite.SizeMode.CUSTOM;
    if (opts?.color) sp.color = opts.color;
    n.setPosition(new Vec3(x, y, 0));
    if (opts?.flipX) n.setScale(-Math.abs(n.scale.x), n.scale.y, n.scale.z);

    _loadSpriteFrameFromImage(resPath, (sf) => {
        if (!n.isValid) return;
        sp.spriteFrame = sf;
        if (w == null || h == null) {
            ut.setContentSize(sf.rect.width, sf.rect.height);
        }
    });
    return n;
}

/**
 * Create a child Node that renders a single frame out of a horizontal
 * sprite-sheet PNG. `frameIndex` is 0-based; `frameW`/`frameH` describe
 * each frame's pixel size; the sheet is expected to lay frames left-to-
 * right in one row (matches `gen_pixel_art_assets.py`).
 *
 * The returned Node is shown at `displayW`×`displayH` (defaults to the
 * native frame size — i.e. 1 CSS px per source px).
 */
function createSpriteSheetFrame(
    parent: Node,
    resPath: string,
    frameIndex: number,
    frameW: number,
    frameH: number,
    x: number,
    y: number,
    displayW?: number,
    displayH?: number,
    opts?: { name?: string; flipX?: boolean }
): Node {
    const n = new Node(opts?.name ?? `Frame_${resPath.split('/').pop()}_${frameIndex}`);
    n.layer = parent.layer;
    parent.addChild(n);
    const ut = n.addComponent(UITransform);
    ut.setContentSize(displayW ?? frameW, displayH ?? frameH);
    const sp = n.addComponent(Sprite);
    sp.sizeMode = Sprite.SizeMode.CUSTOM;
    n.setPosition(new Vec3(x, y, 0));
    if (opts?.flipX) n.setScale(-Math.abs(n.scale.x), n.scale.y, n.scale.z);

    _loadSpriteFrameFromImage(resPath, (sf) => {
        if (!n.isValid) return;
        // Slice a single frame out of the full sheet.
        const sliced = new SpriteFrame();
        sliced.texture = sf.texture;
        sliced.rect = new Rect(frameIndex * frameW, 0, frameW, frameH);
        sliced.originalSize = new Size(frameW, frameH);
        sp.spriteFrame = sliced;
    });
    return n;
}

/**
 * Utility for semi-transparent overlays (e.g. story-page text backdrop).
 */
function createSolidRect(parent: Node, x: number, y: number, w: number, h: number, color: Color): Node {
    const n = new Node('SolidRect');
    n.layer = parent.layer;
    parent.addChild(n);
    const ut = n.addComponent(UITransform);
    ut.setContentSize(w, h);
    const g = n.addComponent(Graphics);
    g.fillColor = color;
    g.rect(-w / 2, -h / 2, w, h);
    g.fill();
    n.setPosition(new Vec3(x, y, 0));
    return n;
}
