import { _decorator, AudioSource, Vec3 } from 'cc';
import { Direction, GameState, GridType, MoveState, MoverRole, Skin, skinToTheme, themeToSkin } from '../core/Define';
import { EventManager, EventType } from '../core/EventManager';
import { JsBridge } from '../bridge/JsBridge';
import { GameAudio } from '../audio/GameAudio';
import { GameManager } from '../manager/GameManager';
import { Movement } from '../gameplay/Movement';
import { vehicleFollowsLandingTile, canStayMountedAfterLanding, moverRoleForLandingCell, shouldDismountOnTile } from '../gameplay/MoveRules';
import { VehicleController } from './VehicleController';
import { PropController } from './PropController';
import { findVehicleAtCell, tryLinkPlayerVehicle } from './RideLink';
import { PlayerAction } from '../visual/PlayerAnimPaths';
import { PlayerActionAnimator } from '../visual/PlayerActionAnimator';
import { scaledJumpArcOffset, scaledMoveSpeed, UNITY_PLAYER_MOVE_SPEED, CELL_PIXEL, PROP_COLLECT_MOVE_PROGRESS, PROP_COLLECT_TOUCH_RADIUS } from '../core/GridConstants';
import { cellToWorldCenter } from '../core/GridCoords';
import { getThemePlayerRideYOffset } from '../theme/ThemeDatabase';
import {
    entityWorldPositionForRole,
    moverLogicalGridPositionForRole,
    worldToMoverCellForRole,
} from '../level/EntitySpawnPlacement';
import { forEachLevelEntityNode, resolveLevelDrawRoot, sortIsoTiles } from '../level/TileLayout';

const { ccclass } = _decorator;

export interface ExternalData {
    position: { x: number; y: number; z: number };
    gridType: number;
    direction: string;
}

export interface ExternalDataList {
    direction: number;
    externalDatas: ExternalData[];
}

export interface ExternalResult {
    isWin: boolean;
    stepNum: number;
    direction: number;
    isInputEnd: boolean;
}

/**
 * 玩家移动（对齐 Unity PlayerController）
 * - 玩家节点为位移主体
 * - 骑乘：仅目标空地联动载具；落砖下车；视觉 Y 抬高
 * - 跳跃：只能落到 Jump 砖（MoveRules + moveCondition）
 */
@ccclass('PlayerController')
export class PlayerController extends Movement {
    coins = 0;
    private vehicle: VehicleController | null = null;
    private sendFinally = false;
    private animator: PlayerActionAnimator | null = null;
    private sfxSource: AudioSource | null = null;

    onLoad() {
        this.moverRole = 'player';
        this.moveSpeed = scaledMoveSpeed(UNITY_PLAYER_MOVE_SPEED);
        this.animator = this.node.getComponent(PlayerActionAnimator)
            ?? this.node.addComponent(PlayerActionAnimator);
        this.sfxSource = this.node.getComponent(AudioSource) ?? this.node.addComponent(AudioSource);
        EventManager.register(EventType.LevelInit, this.onLevelInit);
        EventManager.register(EventType.InputEnd, this.onInputEnd);
        this.coins = 0;
    }

    onDestroy() {
        EventManager.remove(EventType.LevelInit, this.onLevelInit);
        EventManager.remove(EventType.InputEnd, this.onInputEnd);
    }

    start() {
        super.start();
        if (this.node.name === 'Player' && GameManager.instance) {
            this.callChangeSkin(GameManager.instance.playerSkin);
        } else {
            this.animator?.setAction(this.animator?.getAction() ?? PlayerAction.Idle);
        }
    }

    // —— 骑乘视觉（逻辑坐标在载具甲板，显示抬高） ——

    private rideYOffset(): number {
        const gm = GameManager.instance;
        const theme = gm?.getCurLevel()?.theme ?? gm?.uiStyle;
        return getThemePlayerRideYOffset(theme);
    }

    private rideDeckAtCell(cell: Vec3): Vec3 {
        const gm = GameManager.instance!;
        const config = gm.getCurLevel();
        const theme = config?.theme ?? gm.uiStyle;
        return entityWorldPositionForRole(cell, config ?? undefined, theme, 'vehicle');
    }

    /** 步行上载具时的落点（甲板 + 骑乘视觉 Y，避免到格后再闪一下） */
    private rideMountWorldPosition(cell: Vec3): Vec3 {
        const deck = this.rideDeckAtCell(cell);
        return new Vec3(deck.x, deck.y + this.rideYOffset(), deck.z);
    }

    private applyMountVisualAtDeck(deck: Vec3) {
        this.node.setPosition(deck.x, deck.y + this.rideYOffset(), deck.z);
    }

    /** 骑乘时逻辑甲板世界坐标（无视觉抬高） */
    getRideDeckWorldPosition(): Vec3 {
        if (this.vehicle) return this.vehicle.node.position.clone();
        const p = this.node.position;
        return new Vec3(p.x, p.y, p.z);
    }

    /** 是否处于「载具跟随」状态（仅 Idle 时锁定到甲板；移动中由 OnMoving 驱动载具） */
    private isVehicleLinked(): boolean {
        return !!this.vehicle && this.moveState !== MoveState.Moving;
    }

    snapToRideBase() {
        if (!this.vehicle) return;
        this.applyMountVisualAtDeck(this.vehicle.node.position);
    }

    applyRideVisual() {
        if (!this.vehicle) return;
        this.applyMountVisualAtDeck(this.vehicle.node.position);
    }

    /** 载具换向/flip 后重对齐骑乘高度 */
    syncRideMountVisual() {
        this.applyRideVisual();
    }

    setRideBaseFromVehicle(base: Vec3) {
        this.node.setPosition(base);
        this.applyRideVisual();
    }

    private syncVehicleToPlayerBase() {
        if (!this.vehicle) return;
        const p = this.node.position;
        const off = this.rideYOffset();
        this.vehicle.node.setPosition(p.x, p.y - off, p.z);
    }

    // —— 格子采样：骑乘期间始终以载具甲板为逻辑位置 ——

    protected override getGridSamplePosition(): Vec3 {
        // 优先 committedCell：空地格 Y 补偿会让 worldToMoverCell 误判邻格（如 (-1,-1)→(0,0)）
        if (this.committedCell) {
            return cellToWorldCenter(this.committedCell);
        }
        const gm = GameManager.instance!;
        const config = gm.getCurLevel();
        const theme = config?.theme ?? gm.uiStyle;
        if (this.vehicle) {
            return moverLogicalGridPositionForRole(
                this.vehicle.node.position,
                config ?? undefined,
                theme,
                'vehicle',
            );
        }
        return moverLogicalGridPositionForRole(
            this.node.position,
            config ?? undefined,
            theme,
            'player',
        );
    }

    update(dt: number) {
        if (this.isVehicleLinked()) {
            this.applyMountVisualAtDeck(this.vehicle!.node.position);
        }
        super.update(dt);
    }

    protected override shouldApplyPlayerStandOffset(): boolean {
        if (!this.vehicle) return true;
        if (this.moveState !== MoveState.Moving) return false;
        return !vehicleFollowsLandingTile(this.targetGridType);
    }

    setRideVehicle(v: VehicleController | null) {
        if (this.vehicle && this.vehicle !== v) {
            this.vehicle.setPlayer(null);
        }
        this.vehicle = v;
        if (v) {
            v.setPlayer(this);
            Movement.callEach = true;
            v.setDirection(this.direction);
            Movement.callEach = false;
        }
    }

    getRideVehicle(): VehicleController | null {
        return this.vehicle;
    }

    // —— 上下车 ——

    private unbindVehicle() {
        if (!this.vehicle) return;
        this.vehicle.setPlayer(null);
        this.vehicle = null;
    }

    private alignWithVehicleBase() {
        if (!this.vehicle || !this.committedCell) return;
        const deck = this.rideDeckAtCell(this.committedCell);
        this.vehicle.node.setPosition(deck);
        this.vehicle.shareCommittedCell(this.committedCell);
        this.node.setPosition(deck);
        this.applyRideVisual();
    }

    /** Unity CheckIfCurIsRide：仅脚下为 Ride 动态格时绑定载具 */
    private checkIfCurIsRide() {
        if (this.curGrid !== GridType.Ride || !this.committedCell) return;
        const gm = GameManager.instance!;
        const vc = findVehicleAtCell(this.committedCell)
            ?? gm.getGameObjectAtCell(this.committedCell)?.getComponent(VehicleController);
        if (!vc) return;
        this.setRideVehicle(vc);
        vc.setPlayer(this);
        vc.setDirection(this.direction);
        this.alignWithVehicleBase();
    }

    private onLevelInit = () => {
        this.sendFinally = false;
        this.coins = 0;
        this.resetMoveRuntime();
        this.unbindVehicle();
        this.refreshVisual(PlayerAction.Idle);
        this.syncCommittedCellFromPosition();
        this.snapMoverToCellStand();
        this.checkIfCurIsRide();
        tryLinkPlayerVehicle(this);
    };

    private onInputEnd = () => {
        const gm = GameManager.instance!;
        this.externalCallResult(gm.allPropsCollected());
    };

    protected override snapMoverToCellStand() {
        const gm = GameManager.instance;
        if (!gm || !this.committedCell) return;
        const config = gm.getCurLevel();
        const theme = config?.theme ?? gm.uiStyle;
        const landingLive = gm.calculateGridTypeAtCell(this.committedCell);

        if (canStayMountedAfterLanding(landingLive)) {
            const deck = this.rideDeckAtCell(this.committedCell);
            if (this.vehicle) {
                this.vehicle.node.setPosition(deck);
                this.vehicle.shareCommittedCell(this.committedCell);
                const mount = this.rideMountWorldPosition(this.committedCell);
                this.node.setPosition(mount);
                this.targetPosition.set(mount);
            } else {
                const mount = this.rideMountWorldPosition(this.committedCell);
                this.node.setPosition(mount);
                this.targetPosition.set(mount);
            }
            return;
        }

        const follow = !!this.vehicle && vehicleFollowsLandingTile(landingLive);
        const deck = entityWorldPositionForRole(this.committedCell, config ?? undefined, theme, 'vehicle');
        if (follow) {
            this.vehicle!.node.setPosition(deck);
            this.vehicle!.shareCommittedCell(this.committedCell);
            const mount = this.rideMountWorldPosition(this.committedCell);
            this.node.setPosition(mount);
            this.targetPosition.set(mount);
            return;
        }
        const pos = entityWorldPositionForRole(this.committedCell, config ?? undefined, theme, 'player');
        this.node.setPosition(pos);
        this.targetPosition.set(pos);
    }

    protected override resolveMoveStepOrigin(_targetCell: Vec3, landingGrid: GridType): Vec3 {
        if (this.vehicle && vehicleFollowsLandingTile(landingGrid) && this.committedCell) {
            return this.rideMountWorldPosition(this.committedCell);
        }
        // 下车：从骑乘视觉高度平滑移到砖面，勿先 snap 到甲板
        if (this.vehicle && shouldDismountOnTile(landingGrid) && this.committedCell) {
            return this.rideMountWorldPosition(this.committedCell);
        }
        return this.node.position.clone();
    }

    protected override resolveTargetWorldPosition(targetCell: Vec3, landingGrid: GridType): Vec3 {
        if (this.vehicle && vehicleFollowsLandingTile(landingGrid)) {
            return this.rideMountWorldPosition(targetCell);
        }
        const gm = GameManager.instance!;
        const config = gm.getCurLevel();
        const theme = config?.theme ?? gm.uiStyle;
        const role: MoverRole = moverRoleForLandingCell('player', landingGrid, !!this.vehicle);
        if (!this.vehicle && landingGrid === GridType.Ride) {
            return this.rideMountWorldPosition(targetCell);
        }
        return entityWorldPositionForRole(targetCell, config ?? undefined, theme, role);
    }

    protected override onRotateComplete() {
        if (this.vehicle) {
            this.vehicle.setDirection(this.direction);
            if (this.committedCell) this.alignWithVehicleBase();
        }
    }

    /** 是否应拾取该道具（移动中需进入格子且接近触碰，避免起步即消失） */
    canCollectProp(prop: PropController): boolean {
        const propCell = prop.getSpawnCell();
        if (!propCell) return false;

        const committed = this.getCommittedCell();
        if (!this.isMoving()) {
            return !!committed && prop.matchesCell(committed);
        }

        const landing = this.getLandingCell();
        if (!landing || !prop.matchesCell(landing)) return false;

        const gm = GameManager.instance!;
        const config = gm.getCurLevel();
        const theme = config?.theme ?? gm.uiStyle;
        const sample = worldToMoverCellForRole(this.node.position, config ?? undefined, theme, 'player');
        if (!prop.matchesCell(sample)) return false;

        if (this.getMoveStepProgress() < PROP_COLLECT_MOVE_PROGRESS) return false;

        const touchR = CELL_PIXEL * PROP_COLLECT_TOUCH_RADIUS;
        return Vec3.distance(this.node.position, prop.node.position) <= touchR;
    }

    private collectPropsOnApproach() {
        const gm = GameManager.instance;
        const level = gm?.curLevel;
        if (!level) return;
        forEachLevelEntityNode(level, (ch) => {
            const prop = ch.getComponent(PropController);
            if (!prop || !this.canCollectProp(prop)) return;
            prop.collect(this);
        });
    }

    protected onMoveNextSet(isJump: boolean) {
        if (isJump && this.targetGridType === GridType.Jump && this.vehicle) {
            this.unbindVehicle();
        }
        this.animator?.setAction(isJump ? PlayerAction.Jump : PlayerAction.Move);
        if (isJump && this.targetGridType === GridType.Jump) {
            this.targetPosition.y += scaledJumpArcOffset();
        }
    }

    protected override playMoveAnim() {
        this.animator?.setAction(PlayerAction.Move);
    }

    protected override syncRideAfterMoveStep() {
        if (this.vehicle && vehicleFollowsLandingTile(this.targetGridType)) {
            this.syncVehicleToPlayerBase();
        }
    }

    onMoving() {
        if (this.vehicle && vehicleFollowsLandingTile(this.targetGridType) && !Movement.callEach) {
            this.syncVehicleToPlayerBase();
        }
        this.collectPropsOnApproach();
        if (!this.sfxSource) return;
        const action = this.animator?.getAction() ?? PlayerAction.Idle;
        if (action === PlayerAction.Move) {
            const key = vehicleFollowsLandingTile(this.targetGridType) && this.vehicle
                ? 'vehicleMove' : 'move';
            void GameAudio.playSfxOnSource(this.sfxSource, key);
        } else if (action === PlayerAction.Jump) {
            void GameAudio.playSfxOnSource(this.sfxSource, 'jump');
        }
    }

    protected onMoveFail(isJump: boolean) {
        this.externalCallResult(false);
        if (isJump) {
            console.log(
                `${this.node.name} 无法跳跃：跳跃只能落到 Jump 砖块（当前=${GridType[this.curGrid]} 前方=${GridType[this.nextGrid]}）`,
            );
        } else if (this.curGrid === GridType.Jump) {
            console.log(`${this.node.name} 在跳跃砖块上只能跳跃或转向，不能普通前后移动`);
        } else {
            console.log(
                `${this.node.name} 无法移动（当前=${GridType[this.curGrid]} 前方=${GridType[this.nextGrid]} 后方=${GridType[this.lastGrid]}）`,
            );
        }
    }

    protected onMoveToTarget() {
        this.commitLandingCell();
        this.finishMoveToTarget(true);
    }

    /** Unity OnMoveToTarget(bool) — 上下车 / 载具 Ride 注册 */
    finishMoveToTarget(sendMsgOnStep0 = true) {
        const gm = GameManager.instance!;
        this.applyResultAction();

        const cell = this.committedCell
            ?? worldToMoverCellForRole(
                this.node.position,
                gm.getCurLevel() ?? undefined,
                gm.getCurLevel()?.theme ?? gm.uiStyle,
                'player',
            );

        if (this.curGrid === GridType.Ride) {
            const vc = findVehicleAtCell(cell)
                ?? gm.getGameObjectAtCell(cell)?.getComponent(VehicleController);
            if (vc) {
                this.setRideVehicle(vc);
                vc.setPlayer(this);
                vc.setDirection(this.direction);
            }
        } else if (this.curGrid !== GridType.None && this.vehicle) {
            this.unbindVehicle();
        }

        PropController.tryCollectAtCell(cell, this);

        if (this.vehicle) {
            this.alignWithVehicleBase();
            gm.removeObj(this.lastPosition);
            const rideCell = this.committedCell ?? cell;
            gm.addObjAtCell(rideCell, GridType.Ride, this.vehicle.node);
        }

        if (this.committedCell) {
            this.targetGridType = gm.calculateGridTypeAtCell(this.committedCell);
        }

        if (this.step === 0 && sendMsgOnStep0) {
            this.externalCall();
        }
    }

    // —— 外观 / 外部接口 ——

    private activeVisualTheme(): string {
        const gm = GameManager.instance;
        const levelTheme = gm?.getCurLevel()?.theme?.trim();
        if (levelTheme) return levelTheme;
        if (gm) return skinToTheme(gm.playerSkin);
        return 'silu';
    }

    private visualOpts() {
        const gm = GameManager.instance;
        const theme = this.activeVisualTheme();
        return { ...(gm?.getEntityVisualOptions() ?? {}), theme };
    }

    private refreshVisual(action = PlayerAction.Idle) {
        this.animator?.configure(this.activeVisualTheme(), this.direction);
        this.animator?.setAction(action);
    }

    /** 关卡加载后强制用地图主题（主站 CallChangeSkin 不得覆盖） */
    applyLevelTheme(theme: string) {
        const t = theme?.trim();
        if (!t) return;
        const gm = GameManager.instance;
        if (gm) gm.playerSkin = themeToSkin(t);
        this.animator?.configure(t, this.direction);
        this.animator?.setAction(this.animator?.getAction() ?? PlayerAction.Idle, true);
        this.reapplyCellStandPosition();
        this.vehicle?.refreshIcon();
    }

    override setDirection(dir: Direction) {
        super.setDirection(dir);
        this.animator?.setDirection(dir, this.visualOpts());
        if (Movement.callEach) return;
        Movement.callEach = true;
        this.vehicle?.setDirection(dir);
        Movement.callEach = false;
    }

    callChangeSkin(n: number) {
        if (n < 0 || n > Skin.sanxing) return;
        if (GameManager.instance) GameManager.instance.playerSkin = n as Skin;
        const levelTheme = GameManager.instance?.getCurLevel()?.theme?.trim();
        if (levelTheme) {
            this.applyLevelTheme(levelTheme);
            return;
        }
        this.refreshVisual(this.animator?.getAction() ?? PlayerAction.Idle);
    }

    playCoinsAudio() {
        GameAudio.playSfx('coins', this.node);
    }

    private applyResultAction() {
        const gm = GameManager.instance;
        if (!gm) return;
        if (gm.gameState === GameState.ResultWin) {
            this.animator?.setAction(PlayerAction.Win);
        } else if (gm.gameState === GameState.ResultFail) {
            this.animator?.setAction(PlayerAction.Fail);
        } else {
            this.animator?.setAction(PlayerAction.Idle);
        }
    }

    callPlayerInfo() {
        this.externalCall();
    }

    setName(name: string) {
        this.vehicle?.setName(name);
    }

    getCoinsNum() {
        JsBridge.call('coinsData', JSON.stringify({ coinsNum: this.coins, gameObjectName: this.node.name }));
    }

    addCoins() {
        this.coins++;
        this.playCoinsAudio();
    }

    setPosition(pos: Vec3) {
        if (this.vehicle) {
            this.setRideBaseFromVehicle(pos);
        } else {
            this.node.setPosition(pos);
        }
    }

    setTargetGridType(t: GridType) {
        this.targetGridType = t;
    }

    syncFromVehicle(targetGridType: GridType, isJump: boolean) {
        this.targetGridType = targetGridType;
        this.onMoveNextSet(isJump);
    }

    syncMoveToTargetFromVehicle() {
        this.finishMoveToTarget(false);
    }

    externalCall() {
        const gm = GameManager.instance!;
        const list: ExternalDataList = { direction: this.direction, externalDatas: [] };
        const sample = this.getGridSamplePosition();
        const self = gm.worldToCell(sample);
        list.externalDatas.push({
            position: { x: self.x, y: self.y, z: 0 },
            gridType: this.curGrid,
            direction: 'self',
        });
        for (let d = Direction.North; d <= Direction.West; d++) {
            const wp = gm.nextGridPosition(sample, d);
            const cell = gm.worldToCell(wp);
            list.externalDatas.push({
                position: { x: cell.x, y: cell.y, z: 0 },
                gridType: gm.calculateNextGridType(sample, d),
                direction: gm.getRelativePosition(this.direction, d),
            });
        }
        JsBridge.call('processData', JSON.stringify(list));
    }

    externalCallResult(isWin: boolean) {
        const gm = GameManager.instance!;
        if (gm.gameState === GameState.Run) {
            gm.setGameState(isWin ? GameState.ResultWin : GameState.ResultFail);
        }
        if (this.node.name === 'Player') {
            if (isWin) {
                GameAudio.playSfx('success', this.node);
                this.animator?.setAction(PlayerAction.Win);
            } else {
                GameAudio.playSfx('fail', this.node);
                this.animator?.setAction(PlayerAction.Fail);
            }
        }
        if (this.node.name === 'Player' && !this.sendFinally) {
            JsBridge.call('externalResult', JSON.stringify({
                isWin,
                stepNum: gm.stepNum,
                direction: this.direction,
                isInputEnd: gm.isInputEnd,
            } satisfies ExternalResult));
            this.sendFinally = true;
        }
        const root = resolveLevelDrawRoot(this.node);
        if (root) sortIsoTiles(root);
    }
}
