import EventEmitter from 'eventemitter3';
import { backDir, toDir } from './utils';
import { loading } from '@user/data-base';
import type { RenderAdapter } from '@motajs/render';
import type {
    FloorLayer,
    FloorViewport,
    HeroKeyMover,
    HeroRenderer,
    Layer,
    LayerFloorBinder,
    LayerMovingRenderable
} from '@user/client-modules';
import { sleep } from '@motajs/common';

// todo: 转身功能

interface MoveStepDir {
    type: 'dir';
    value: Move2;
}

interface MoveStepSpeed {
    type: 'speed';
    value: number;
}

export type MoveStep = MoveStepDir | MoveStepSpeed;

export interface IMoveController {
    /** 本次移动是否完成 */
    done: boolean;
    /** 当前移动队列 */
    queue: MoveStep[];
    /** 当本次移动完成时兑现 */
    onEnd: Promise<void>;

    /**
     * 停止本次移动，停止后将不再能继续移动
     */
    stop(): Promise<void>;

    /**
     * 向队列末尾添加移动信息
     * @param step 移动信息
     */
    push(...step: MoveStep[]): void;
}

interface EObjectMovingEvent {
    stepEnd: [step: MoveStep];
    moveEnd: [];
    moveStart: [queue: MoveStep[]];
}

export abstract class ObjectMoverBase extends EventEmitter<EObjectMovingEvent> {
    /** 移动队列 */
    private moveQueue: MoveStep[] = [];

    /** 当前的移动速度 */
    moveSpeed: number = 100;
    /** 当前的移动方向 */
    moveDir: Dir2 = 'down';
    /** 当前是否正在移动 */
    moving: boolean = false;
    /** 面朝方向 */
    faceDir: Dir2 = 'down';

    /** 当前的控制对象 */
    controller?: IMoveController;

    /**
     * 当本次移动开始时执行的函数
     * @param controller 本次移动的控制对象
     */
    protected abstract onMoveStart(controller: IMoveController): Promise<void>;

    /**
     * 当本次移动结束时执行的函数
     * @param controller 本次移动的控制对象
     */
    protected abstract onMoveEnd(controller: IMoveController): Promise<void>;

    /**
     * 当这一步开始移动时执行的函数，可以用来播放移动动画等
     * @param step 这一步的移动信息
     * @param controller 本次移动的控制对象
     * @returns 一个Promise，其值表示本次移动的代码，并将其值传入这一步移动后的操作，即{@link onStepEnd}
     */
    protected abstract onStepStart(
        step: MoveStepDir,
        controller: IMoveController
    ): Promise<number>;

    /**
     * 当这一步移动结束后执行的函数（即{@link onStepStart}成功兑现后），可以用于设置物体坐标等
     * @param step 这一步的移动信息
     * @param code 本步移动的代码，即onMoveStart的返回值
     * @param controller 本次移动的控制对象
     * @returns 一个Promise，当其兑现时，本步移动全部完成，将会开始下一步的移动
     */
    protected abstract onStepEnd(
        step: MoveStepDir,
        code: number,
        controller: IMoveController
    ): Promise<void>;

    /**
     * 当移动速度被设置时执行的函数
     * @param speed 设置为的移动速度
     * @param controller 本次移动的控制对象
     */
    protected abstract onSetMoveSpeed(
        speed: number,
        controller: IMoveController
    ): void;

    private getMoveDir(move: Move2): Dir2 {
        if (move === 'forward') return this.moveDir;
        if (move === 'backward') return backDir(this.faceDir);
        return move;
    }

    private getFaceDir(move: Move2): Dir2 {
        if (move === 'forward' || move === 'backward') return this.faceDir;
        return move;
    }

    /**
     * 设置当前面朝方向，移动中设置无效
     * @param dir 面朝方向
     */
    setFaceDir(dir: Dir2) {
        if (!this.moving) this.faceDir = dir;
    }

    /**
     * 设置当前移动方向，移动中设置无效
     * @param dir 移动方向
     */
    setMoveDir(dir: Dir2) {
        if (!this.moving) this.moveDir = dir;
    }

    /**
     * 开始移动，这个操作后，本次移动将不能通过{@link insertMove}添加移动步骤，
     * 只能通过{@link IMoveController.push}添加，而通过前者添加的移动步骤会在下次移动生效
     */
    startMove(): IMoveController | null {
        if (this.moving) return null;
        const queue = this.moveQueue.slice();
        this.clearMoveQueue();

        this.moving = true;
        let stopMove = false;

        const start = async () => {
            // 等待宏任务执行完成，不然controller会在首次调用中未定义
            await new Promise<void>(res => res());
            await this.onMoveStart(controller);
            this.emit('moveStart', queue);
            while (queue.length > 0) {
                if (stopMove || !this.moving) break;
                const step = queue.shift();
                if (!step) break;
                if (step.type === 'dir') {
                    this.moveDir = this.getMoveDir(step.value);
                    this.faceDir = this.getFaceDir(step.value);
                    const code = await this.onStepStart(step, controller);
                    await this.onStepEnd(step, code, controller);
                } else {
                    const speed = step.value;
                    this.moveSpeed = speed;
                    this.onSetMoveSpeed(speed, controller);
                }
                this.emit('stepEnd', step);
            }
            this.moving = false;
            this.emit('moveEnd');

            await this.onMoveEnd(controller);
        };

        const onEnd = start().then(() => {
            this.controller = void 0;
            controller.done = true;
        });
        const controller: IMoveController = {
            done: false,
            onEnd,
            queue,
            push(...step) {
                queue.push(...step);
            },
            stop() {
                stopMove = true;
                return onEnd;
            }
        };
        this.controller = controller;

        return controller;
    }

    /**
     * 向移动队列末尾插入移动实例
     * @param move 移动实例
     */
    insertMove(...move: MoveStep[]) {
        this.moveQueue.push(...move);
    }

    /**
     * 清空移动队列
     */
    clearMoveQueue() {
        this.moveQueue = [];
    }

    /**
     * 向移动队列末尾插入一个移动操作
     * @param step 移动方向
     */
    oneStep(step: Move2) {
        this.moveQueue.push({ type: 'dir', value: step });
    }

    /**
     * 按照传入的数组移动物体，插入至移动队列末尾
     * @param steps 移动步骤
     */
    moveAs(steps: MoveStep[]) {
        this.moveQueue.push(...steps);
    }
}

const enum BlockMoveCode {
    Step
}

export class BlockMover extends ObjectMoverBase {
    /** 楼层渲染适配器，用于显示动画 */
    static adapter?: RenderAdapter<Layer>;

    x: number;
    y: number;
    floorId: FloorIds;
    layer: FloorLayer;

    /** 本次移动中需要进行动画移动的楼层渲染组件 */
    private layerItems: Layer[] = [];
    /** 本次移动过程中的移动renderable实例 */
    private renderable?: LayerMovingRenderable;
    /** 本次移动的图块id */
    private blockNum: number = 0;

    constructor(
        x: number,
        y: number,
        floorId: FloorIds,
        layer: FloorLayer,
        dir: Dir = 'down'
    ) {
        super();

        this.x = x;
        this.y = y;
        this.floorId = floorId;
        this.moveDir = dir;
        this.layer = layer;
    }

    /**
     * 绑定移动点
     * @param x 绑定点横坐标
     * @param y 绑定点纵坐标
     * @param floorId 绑定点楼层
     * @returns 是否绑定成功，例如如果当前绑定点正在移动，那么就会绑定失败
     */
    bind(
        x: number,
        y: number,
        floorId: FloorIds,
        layer: FloorLayer,
        dir: Dir = 'down'
    ) {
        if (this.moving) return false;
        this.x = x;
        this.y = y;
        this.floorId = floorId;
        this.moveDir = dir;
        this.layer = layer;
        return true;
    }

    protected async onMoveStart(_controller: IMoveController): Promise<void> {
        const adapter = BlockMover.adapter;
        if (adapter) {
            const list = adapter.items;
            const items = [...list].filter(v => {
                if (v.layer !== this.layer) return false;
                const ex = v.getExtends('floor-binder') as LayerFloorBinder;
                if (!ex) return false;
                return ex.getFloor() === core.status.floorId;
            });
            this.layerItems = items;
        }

        let blockNum: number = 0;
        if (this.layer === 'event') {
            blockNum = core.status.maps[this.floorId].map[this.y][this.x];
        } else {
            const array = core.maps._getBgFgMapArray(this.layer, this.floorId);
            blockNum = array[this.y][this.x];
        }
        this.blockNum = blockNum;

        Mota.r(() => {
            const { Layer } = Mota.require('@user/client-modules');
            const r = Layer.getMovingRenderable(blockNum, this.x, this.y);

            if (r) {
                this.renderable = r;
                this.layerItems.forEach(v => {
                    v.moving.add(r);
                });
            }
        });

        if (this.layer === 'event') {
            core.removeBlock(this.x, this.y, this.floorId);
        }
    }

    protected async onMoveEnd(_controller: IMoveController): Promise<void> {
        if (this.renderable) {
            this.layerItems.forEach(v => {
                v.moving.delete(this.renderable!);
            });
        }

        this.layerItems = [];
        this.renderable = void 0;

        if (this.layer === 'event') {
            core.setBlock(this.blockNum as AllNumbers, this.x, this.y);
        }
    }

    protected async onStepStart(
        step: MoveStepDir,
        _controller: IMoveController
    ): Promise<BlockMoveCode> {
        await this.moveAnimate(step);
        const { x: dx, y: dy } = core.utils.scan2[this.moveDir];
        this.x += dx;
        this.y += dy;

        return BlockMoveCode.Step;
    }

    protected async onStepEnd(
        _step: MoveStepDir,
        _code: BlockMoveCode,
        _controller: IMoveController
    ): Promise<void> {}

    protected onSetMoveSpeed(
        _speed: number,
        _controller: IMoveController
    ): void {}

    private moveAnimate(_step: MoveStepDir) {
        const layer = this.layerItems[0];
        if (!layer) return;
        if (!this.renderable) return;
        const data = this.renderable;
        const fx = this.x;
        const fy = this.y;
        const { x: dx, y: dy } = core.utils.scan2[this.moveDir];
        const start = Date.now();
        const replay = core.status.replay.speed ?? 1;
        const time = replay === 24 ? 1 : this.moveSpeed / replay;

        return new Promise<void>(res => {
            layer.delegateTicker(
                () => {
                    const now = Date.now() - start;
                    const progress = now / time;
                    data.x = fx + dx * progress;
                    data.y = fy + dy * progress;
                    this.layerItems.forEach(v => {
                        v.update(v);
                    });
                },
                this.moveSpeed,
                () => {
                    data.x = fx + dx;
                    data.y = fy + dy;
                    data.zIndex = fy + dy;
                    res();
                }
            );
        });
    }
}

interface CanMoveStatus {
    /** 由CannotIn和CannotOut计算出的信息，不可移动时不会触发触发器 */
    canMove: boolean;
    /** 由图块的noPass计算出的信息，不可移动时会触发触发器 */
    noPass: boolean;
}

const enum HeroMoveCode {
    Step,
    Stop,
    /** 不能移动，并撞击前面一格的图块，触发其触发器 */
    Hit,
    /** 不能移动，同时当前格有CannotOut，或目标格有CannotIn，不会触发前面一格的触发器 */
    CannotMove
}

export class HeroMover extends ObjectMoverBase {
    /** 勇士渲染适配器，用于等待动画等操作 */
    static adapter?: RenderAdapter<HeroRenderer>;
    /** 视角适配器 */
    static viewport?: RenderAdapter<FloorViewport>;

    /** 当前移动是否忽略地形 */
    private ignoreTerrain: boolean = false;
    /** 当前移动是否不计入录像 */
    private noRoute: boolean = false;
    /** 当前移动是否是在lockControl条件下开始的 */
    private inLockControl: boolean = false;
    /** 是否会在特殊时刻进行自动存档 */
    private autoSave: boolean = false;

    /** 本次移动开始时的移动速度 */
    private beforeMoveSpeed: number = 100;

    override startMove(
        ignoreTerrain: boolean = false,
        noRoute: boolean = false,
        inLockControl: boolean = false,
        autoSave: boolean = false
    ): IMoveController | null {
        if (this.moving) return null;
        this.ignoreTerrain = ignoreTerrain;
        this.noRoute = noRoute;
        this.inLockControl = inLockControl;
        this.autoSave = autoSave;
        return super.startMove();
    }

    private checkAutoSave(x: number, y: number, nx: number, ny: number) {
        const index = `${x},${y}`;
        const nIndex = `${nx},${ny}`;
        const map = core.status.thisMap.enemy.mapDamage;
        const dam = map[index];
        const nextDam = map[nIndex];
        if (!dam || !nextDam) return;
        // 可以在这里判断地图伤害，并进行自动存档，例如在进入或离开地图伤害时存档
        // if (dam.damage > 0 || nextDam.damage > 0) {
        // core.autosave()
        // }
    }

    protected async onMoveStart(controller: IMoveController): Promise<void> {
        this.beforeMoveSpeed = this.moveSpeed;
        const adapter = HeroMover.adapter;
        const viewport = HeroMover.viewport;
        if (!adapter || !viewport) return;
        if (!core.isReplaying() || core.status.replay.speed <= 3) {
            adapter.sync('startAnimate');
            await adapter.all('readyMove');
        }
        // 这里要检查前面那一格能不能走，不能走则不触发平滑视角，以避免撞墙上视角卡住
        if (!this.ignoreTerrain) {
            const { x, y } = core.status.hero.loc;
            const firstDir = controller.queue.find(
                v => v.type === 'dir'
            )?.value;
            if (firstDir && firstDir !== 'backward' && firstDir !== 'forward') {
                const data = this.checkCanMove(x, y, toDir(firstDir as Dir));
                if (data.canMove && !data.noPass) {
                    viewport.sync('startMove');
                }
            }
        } else {
            viewport.sync('startMove');
        }
    }

    protected async onMoveEnd(controller: IMoveController): Promise<void> {
        this.moveSpeed = this.beforeMoveSpeed;
        this.onSetMoveSpeed(this.moveSpeed, controller);
        const adapter = HeroMover.adapter;
        const viewport = HeroMover.viewport;
        if (!adapter || !viewport) return;
        adapter.sync('endAnimate');
        await adapter.all('endMove');
        viewport.sync('endMove');
        core.clearContinueAutomaticRoute();
        core.stopAutomaticRoute();
    }

    protected async onStepStart(
        _step: MoveStepDir,
        controller: IMoveController
    ): Promise<HeroMoveCode> {
        const showDir = toDir(this.faceDir);
        const dir4Move = toDir(this.moveDir);
        core.setHeroLoc('direction', showDir);

        const { x, y } = core.status.hero.loc;
        const { x: nx, y: ny } = this.nextLoc(x, y, this.moveDir);

        if (this.autoSave && !this.ignoreTerrain) {
            this.checkAutoSave(x, y, nx, ny);
        }

        if (!this.inLockControl && core.status.lockControl) {
            controller.stop();
            return HeroMoveCode.Stop;
        }

        if (!this.ignoreTerrain || !this.noRoute) {
            this.moveDir = dir4Move;
        }

        const dir = this.moveDir;
        if (!this.ignoreTerrain) {
            const { noPass, canMove } = this.checkCanMove(x, y, dir4Move);

            if (!canMove) {
                return HeroMoveCode.CannotMove;
            }
            // 不能移动
            if (noPass) {
                return HeroMoveCode.Hit;
            }
        }

        // 可以移动，显示移动动画

        await this.moveAnimate(nx, ny, showDir, dir);

        return HeroMoveCode.Step;
    }

    protected async onStepEnd(
        _step: MoveStepDir,
        code: HeroMoveCode,
        controller: IMoveController
    ): Promise<void> {
        const { x, y } = core.status.hero.loc;
        const { x: nx, y: ny } = this.nextLoc(x, y, this.moveDir);
        const showDir = toDir(this.faceDir);

        // 前方不能移动
        if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Hit) {
            controller.stop();
            this.onCannotMove(showDir);
            if (code === HeroMoveCode.Hit) {
                core.trigger(nx, ny);
            }
            core.checkRouteFolding();
            return;
        }

        // 本次移动停止
        if (code === HeroMoveCode.Stop) {
            core.clearContinueAutomaticRoute();
            core.stopAutomaticRoute();
            controller.stop();
            return;
        }

        // 本次移动正常完成
        if (code === HeroMoveCode.Step) {
            core.setHeroLoc('x', nx, true);
            core.setHeroLoc('y', ny, true);

            if (!this.ignoreTerrain) {
                const direction = core.getHeroLoc('direction');
                core.control._moveAction_popAutomaticRoute();
                if (!this.noRoute) core.status.route.push(direction);

                // 中毒处理
                if (core.hasFlag('poison')) {
                    core.status.hero.hp -= core.values.poisonDamage;
                    if (core.status.hero.hp <= 0) {
                        core.status.hero.hp = 0;
                        core.events.lose();
                    }
                    core.updateStatusBar();
                }

                core.moveOneStep();
                core.checkRouteFolding();
            }
        }
    }

    protected onSetMoveSpeed(
        speed: number,
        _controller: IMoveController
    ): void {
        const adapter = HeroMover.adapter;
        if (!adapter) return;
        adapter.sync('setMoveSpeed', speed);
    }

    /**
     * 移动动画
     * @param x 目标横坐标
     * @param y 目标纵坐标
     * @param showDir 显示方向
     * @param moveDir 移动方向
     */
    private async moveAnimate(
        x: number,
        y: number,
        showDir: Dir,
        moveDir: Dir2
    ) {
        const adapter = HeroMover.adapter;
        const viewport = HeroMover.viewport;
        if (!adapter || !viewport) return;
        const replay = core.status.replay.speed;
        const speed = replay === 24 ? 1 : this.moveSpeed / replay;
        viewport.all('moveTo', x, y, speed * 1.6);
        adapter.sync('setAnimateDir', showDir);
        if (core.isReplaying() && core.status.replay.speed > 3) {
            adapter.sync('endAnimate');
            await sleep(speed);
            await adapter.all('setHeroLoc', x, y);
        } else {
            if (core.isReplaying()) {
                adapter.sync('startAnimate');
            }
            await adapter.all('move', moveDir);
        }
    }

    /**
     * 当前方一格不能走时，执行的函数
     * @param dir 移动方向
     */
    private onCannotMove(dir: Dir) {
        if (!this.noRoute) core.status.route.push(dir);
        core.status.automaticRoute.moveStepBeforeStop = [];
        core.status.automaticRoute.lastDirection = dir;

        if (core.status.automaticRoute.moveStepBeforeStop.length === 0) {
            core.clearContinueAutomaticRoute();
            core.stopAutomaticRoute();
        }
    }

    /**
     * 检查前方是否可以移动
     * @param x 当前横坐标
     * @param y 当前纵坐标
     * @param dir 移动方向
     */
    private checkCanMove(x: number, y: number, dir: Dir): CanMoveStatus {
        // 如果是循环式地图
        const { x: nx, y: ny } = this.nextLoc(x, y, dir);
        const noPass = core.noPass(nx, ny);
        const canMove = core.canMoveHero(x, y, dir);
        return { noPass, canMove };
    }

    /**
     * 获取前方一格的坐标
     * @param x 当前横坐标
     * @param y 当前纵坐标
     * @param dir 移动方向
     */
    private nextLoc(x: number, y: number, dir: Dir2): Loc {
        const { x: dx, y: dy } = core.utils.scan2[dir];
        const nx = x + dx;
        const ny = y + dy;
        return { x: nx, y: ny };
    }
}

interface HeroMoveCollection {
    mover: HeroMover;
    keyMover?: HeroKeyMover;
}

// 初始化基础勇士移动实例
const heroMover = new HeroMover();
export const heroMoveCollection: HeroMoveCollection = {
    mover: heroMover
};
loading.once('coreInit', () => {
    // 注册按键操作
    Mota.r(() => {
        const { HeroKeyMover } = Mota.require('@user/client-modules');
        const { gameKey } = Mota.require('@motajs/system-action');
        const keyMover = new HeroKeyMover(gameKey, heroMover);
        heroMoveCollection.keyMover = keyMover;
    });
});

// Adapter初始化
loading.once('coreInit', () => {
    if (main.replayChecking || main.mode === 'editor') return;
    const Adapter = Mota.require('@motajs/render').RenderAdapter;
    const adapter = Adapter.get<HeroRenderer>('hero-adapter');
    const viewport = Adapter.get<FloorViewport>('viewport');
    const layerAdapter = Adapter.get<Layer>('layer');
    HeroMover.adapter = adapter;
    HeroMover.viewport = viewport;
    BlockMover.adapter = layerAdapter;
});
