import { PointInt2D } from "../../Utils/Math.utils";
// import { findPathOnNavigationGridIfExists } from "../../World/NavigationGrid/NavigationGrid";
import { NPCController } from "../NPCController/NPCController";
// import { NPCProto } from "../NPCProto/NPCProto";

async function NPCJobFunctor(props, action) {
    return new NPCActionCallbackResult();
}

/**
 *
 * @param {NPCActionCallbackResult} lastActionResult
 */
function NPCActionCallback(lastActionResult) {}

/**
 *
 * @param {NPCTaskCallbackResult} taskResult
 */
function NPCTaskCallback(taskResult) {}

export class NPCActionCallbackResult {
    /**
     * status of this action. -1 - error, 0 - waiting for execution, 1 - completed, 2 - can not be completed
     */
    status = -1;
    statusText = "";
    /**
     * @type {NPCAction}
     */
    action = undefined;

    /**
     * statuses: -1 - error, 0 - waiting for execution, 1 - completed, 2 - can not be completed
     * @param {Number} status
     * @param {String} text
     * @param {NPCAction} action
     */
    constructor(status, text = "", action = undefined) {
        this.status = status;
        this.statusText = text;
        this.action = action;
    }

    clone() {
        return new NPCActionCallbackResult(this.status, this.statusText, this.action);
    }
}

export class NPCTaskCallbackResult {
    /**
     * status of this task. -1 - error, 0 - completed, 1 - can not be completed, 2 - reset
     */
    status = -1;
    statusText = "";
    /**
     * @type {NPCActionCallbackResult}
     */
    lastActionCallbackResult;
    /**
     * @type {NPCTask}
     */
    task = undefined;

    /**
     *
     * @param {Number} status
     * @param {String} statusText
     * @param {NPCActionCallbackResult} lastActionCallbackResult
     * @param {NPCTask} task
     */
    constructor(status, statusText, lastActionCallbackResult, task) {
        this.status = status;
        this.statusText = statusText;
        this.lastActionCallbackResult = lastActionCallbackResult;
        this.task = task;
    }

    clone()
    {
        return new NPCTaskCallbackResult(this.status, this.statusText, this.lastActionCallbackResult, this.task);
    }
}

/**
 * NPC Action is the smallest part of NPC's behavior.
 * There are 2 types of actions:
 * 0: move to tile
 * 1: do something
 */
export class NPCAction {
    //invalid action type by default
    type = -1;
    //target position
    targetPosition = new PointInt2D();
    /**
     * target job function
     * @type {NPCJobFunctor}
     */
    targetJob;
    /**
     * these props will passed to job function on it's execution
     */
    targetJobProps;
    /**
     * status of this action. -1 - error, 0 - waiting for execution, 1 - completed, 2 - can not be completed
     */
    status = -1;
    /**
     * @type {NPCProto}
     */
    owner = null;
    /**
     * will this task block chain of execution
     */
    skipToNextIgnoringStatus = false;

    usePause = false;
    pauseTime = 0;

    /**
     * NPC Action is the smallest part of NPC's behavior.
     * There are 2 types of actions:
     * 0: move to tile
     * 1: do something
     * @param {Number} type
     * @param {PointInt2D} pos
     * @param {NPCJobFunctor} job
     * @param {any} jobProps
     * @param {NPCController} owner
     * @param {Boolean} skipToNext
     * @param {Boolean} usePause
     * @param {Number} pauseTime
     */
    constructor(type, pos, job, jobProps = {}, owner = null, skipToNext = false, usePause = false, pauseTime = 0) {
        this.type = type;
        this.targetPosition = pos;
        this.targetJob = job;
        this.targetJobProps = jobProps;
        this.status = 0;
        this.owner = owner;
        this.skipToNextIgnoringStatus = skipToNext;
        this.usePause = usePause;
        this.pauseTime = pauseTime;
    }
}

/**
 * NPC Task is a container with NPC Actions. It is like a timeline with frames (frames are NPC Actions)
 */
export class NPCTask {
    /**
     * @type {Array<NPCAction>}
     */
    actionsContainer = [];
    _currentIndex = 0;
    _startIndex = 0;
    /**
     * @type {NPCController}
     */
    owner = null;
    //is task active and will execute actions
    active = false;
    //will task start again after completion
    loop = false;
    onPause = false;

    _lastActionCompleted = false;
    /**
     * @type {NPCActionCallbackResult}
     */
    _lastActionCallbackResult;

    /**
     * @type {NPCActionCallback}
     */
    actionCallback;
    /**
     * @type {NPCTaskCallback}
     */
    taskCallback;

    _pauseTimeout;

    /**
     * @param {Array<NPCAction>} actions
     * @param {NPCController} owner
     * @param {boolean} loop
     * @param {NPCActionCallback} actionCallback
     * @param {NPCTaskCallback} taskCallback
     */
    constructor(actions, owner = null, loop = false, actionCallback = undefined, taskCallback = undefined) {
        this.actionsContainer = actions;
        this.owner = owner;
        this.loop = loop;
        this.actionCallback = actionCallback;
        this.taskCallback = taskCallback;
    }

    startTask() {
        if (!this.owner || this._lastActionCompleted) return false;
        this._currentIndex = this._startIndex;
        this.active = true;
        return this._pushNewAction();
    }

    resetTask() {
        this.active = false;
        this.onPause = false;
        this._resetAllActions();
        this._currentIndex = this._startIndex;
        clearTimeout(this._pauseTimeout);
        this.taskCallback(new NPCTaskCallbackResult(2, "reset", this._lastActionCallbackResult?.clone(), this));
        return true;
    }

    /**
     * set owner for all actions and this task
     * @param {NPCController} owner
     */
    setOwnerToAll(owner) {
        this.owner = owner;
        for (const action of this.actionsContainer) {
            action.owner = owner;
        }
    }

    handleTaskTick(ticker) {
        if (!this.active || this.onPause) return;
        if (this._lastActionCompleted) {
            this._lastActionCompleted = false;
            this.actionCallback(this._lastActionCallbackResult.clone());
            if (!this.active) return;
            if (!this.actionsContainer[this._currentIndex].skipToNextIgnoringStatus) {
                if (this._lastActionCallbackResult.status !== 1) {
                    this.taskCallback(new NPCTaskCallbackResult(1, "chain blocked by action status", this._lastActionCallbackResult.clone(), this));
                    this.resetTask();
                    return;
                }
            }
            //pay attention in timeout logic
            if (this.actionsContainer[this._currentIndex].usePause) {
                this.onPause = true;
                clearTimeout(this._pauseTimeout);
                let thisCopy = this;
                this._pauseTimeout = setTimeout(() => {
                    thisCopy.onPause = false;
                    thisCopy._currentIndex++;
                    if (thisCopy._currentIndex >= thisCopy.actionsContainer.length) {
                        if (thisCopy.loop) {
                            thisCopy._currentIndex = thisCopy._startIndex;
                            thisCopy._resetAllActions();
                            thisCopy._pushNewAction();
                        } else thisCopy.active = false;
                    } else {
                        thisCopy._pushNewAction();
                    }
                }, this.actionsContainer[this._currentIndex].pauseTime);
                return;
            }
            //
            this._currentIndex++;
            if (this._currentIndex >= this.actionsContainer.length) {
                if (this.loop) {
                    this._currentIndex = this._startIndex;
                    this._resetAllActions();
                    this._pushNewAction();
                } else {
                    this.active = false;
                    this.taskCallback(new NPCTaskCallbackResult(0, "finished", this._lastActionCallbackResult.clone(), this));
                }
            } else {
                this._pushNewAction();
            }
        }
    }

    _pushNewAction() {
        switch (this.actionsContainer[this._currentIndex].type) {
            case 0:
                this.owner.moveTo(this.actionsContainer[this._currentIndex].targetPosition, (r) => {
                    this._handleMovementResult(r);
                });
                break;
            case 1:
                this.actionsContainer[this._currentIndex].targetJob(this.actionsContainer[this._currentIndex].targetJobProps, this.actionsContainer[this._currentIndex]).then((r) => {
                    this._handleJobResult(r);
                });
                break;
            default:
                return false;
        }
        return true;
    }

    _resetAllActions() {
        for (const action of this.actionsContainer) {
            action.status = 0;
        }
    }

    /**
     * @param {NPCActionCallbackResult} resultFromMoving 
     */
    _handleMovementResult(resultFromMoving) {
        this.actionsContainer[this._currentIndex].status = resultFromMoving === 0 ? 1 : resultFromMoving === -1 ? -1 : 2;
        this._lastActionCompleted = true;
        this._lastActionCallbackResult = new NPCActionCallbackResult(resultFromMoving === 0 ? 1 : resultFromMoving === -1 ? -1 : 2, "", this.actionsContainer[this._currentIndex]);
    }

    /**
     * @param {NPCActionCallbackResult} resultFromJob 
     */
    _handleJobResult(resultFromJob) {
        this._lastActionCallbackResult = resultFromJob;
        this._lastActionCompleted = true;
    }
}

/**
 * @param {NPCTask} task
 */
function TaskChanger(task) {}

export class NPCBehaviorCallbackResult {
    /**
     * 0 - action callback
     * 1 - task callback
     * @type {Number}
     */
    type;
    /**
     * status of the callback (see more in taskCallback or actionCallback)
     * @type {Number}
     */
    status;
    /**
     * @type {String}
     */
    statusText;
    /**
     * @type {NPCTaskCallbackResult}
     */
    taskCallbackResult;
    /**
     * @type {NPCActionCallbackResult}
     */
    actionCallbackResult;

    /**
     *
     * @param {Number} type
     * @param {Number} status
     * @param {String} statusText
     * @param {NPCTaskCallbackResult} taskCallbackResult
     * @param {NPCActionCallbackResult} actionCallbackResult
     */
    constructor(type, status, statusText, taskCallbackResult, actionCallbackResult) {
        this.type = type;
        this.status = status;
        this.statusText = statusText;
        this.taskCallbackResult = taskCallbackResult;
        this.actionCallbackResult = actionCallbackResult;
    }
}

export class NPCBehavior {
    /**
     * @type {Map<String, NPCTask>}
     */
    taskMap = new Map();
    owner = undefined;
    isTaskInProgress = false;
    currentTask = "";
    behaviorCallback = undefined;

    /**
     * @param {NPCController} owner
     */
    constructor(owner, behaviorCallback) {
        this.owner = owner;
        this.behaviorCallback = behaviorCallback;
    }

    /**
     * @param {String} key
     * @param {NPCTask} task
     */
    addTask(key, task) {
        task.setOwnerToAll(this.owner);
        task.actionCallback = this._handleActionCallback.bind(this);
        task.taskCallback = this._handleTaskCallback.bind(this);
        this.taskMap.set(key, task);
    }

    /**
     * @param {String} key
     */
    startTask(key) {
        if (this.isTaskInProgress) return false;
        if (this.taskMap.get(key).startTask()) {
            this.currentTask = key;
            this.isTaskInProgress = true;
            return true;
        }
        return false;
    }

    abortCurrentTask() {
        if (this.taskMap.get(this.currentTask)?.resetTask()) {
            this.isTaskInProgress = false;
            this.currentTask = "";
        }
    }

    /**
     *
     * @param {String} key
     * @param {TaskChanger} changer
     */
    changeTask(key, changer) {
        changer(this.taskMap.get(key));
    }

    handleNPCBehaviorTick(ticker) {
        this.taskMap.get(this.currentTask)?.handleTaskTick(ticker);
    }

    /**
     *
     * @param {NPCTaskCallbackResult} result
     */
    _handleTaskCallback(result) {
        this.isTaskInProgress = false;
        this.behaviorCallback(new NPCBehaviorCallbackResult(1, result.status, result.statusText, result.clone(), undefined));
        console.log(result);
    }

    /**
     *
     * @param {NPCActionCallbackResult} result
     */
    _handleActionCallback(result) {
        this.behaviorCallback(new NPCBehaviorCallbackResult(0, result.status, result.statusText, undefined, result.clone()));
        console.log(result);
    }
}
