import { set } from "lodash";
import { StateRoot } from "queenjs";

type RecordOptions = { combine?: boolean };
class State extends StateRoot {
  currLen = 0; //操作栈的长度
  maxLen = 100; //操作栈总长度

  opIndex = -1; //操作栈的指针

  canUndo = this.computed((state) => {
    return state.opIndex >= 0;
  });
  canRedo = this.computed((state) => {
    return state.opIndex < state.currLen - 1;
  });
}

export class HistoryController {
  state = new State().reactive();
  queue: GroupAction[] = [];
  cacheGroupAction = new GroupAction();

  // 添加缓存记录
  record(action: Action, options?: RecordOptions) {
    this.cacheGroupAction.record(action, options);
  }

  // 保存缓存记录到历史栈中
  submit(action?: Action) {
    const { state, queue, cacheGroupAction } = this;
    if (action) this.record(action);
    if (!cacheGroupAction.actions.length) return;

    // 将缓存操作记录保存到当前指针的下一栈中
    queue[++state.opIndex] = cacheGroupAction;
    // 设置栈的长度为指针的长度，舍弃后面的记录
    queue.length = state.opIndex + 1;
    // 若栈长度超过上限, 舍弃之前的记录
    if (queue.length > state.maxLen) {
      queue.splice(0, queue.length - state.maxLen);
      state.opIndex = state.maxLen - 1;
    }
    // 更新当前长度状态
    state.currLen = queue.length;
    // 更新当前缓存GroupAction
    this.cacheGroupAction = new GroupAction();
  }

  undo() {
    if (!this.state.canUndo) return;
    this.submit();
    this.queue[this.state.opIndex--].undo();
  }

  redo() {
    if (!this.state.canRedo) return;
    this.cacheGroupAction.undo();
    this.cacheGroupAction.actions.length = 0;
    this.queue[++this.state.opIndex].redo();
  }

  //清除操作
  clear() {
    this.queue = [];
    this.state.currLen = 0;
    this.state.opIndex = -1;
    this.cacheGroupAction = new GroupAction();
  }
}

export class Action {
  constructor(
    public type: "set" | "delete",
    public root: any,
    public path: string,
    public value?: any,
    public oldValue?: any
  ) {}
  undo() {
    set(this.root, this.path, this.oldValue);
  }
  redo() {
    set(this.root, this.path, this.value);
  }
}

export class GroupAction {
  actions: Action[] = [];
  record(action: Action, options?: RecordOptions) {
    const lastAction = this.actions.at(-1);
    if (
      options?.combine &&
      lastAction?.root === action.root &&
      lastAction?.path === action.path &&
      lastAction?.type === action.type
    ) {
      action.oldValue = lastAction.oldValue;
      this.actions[this.actions.length - 1] = action;
    } else {
      this.actions.push(action);
    }
  }
  undo() {
    [...this.actions].reverse().forEach((act) => act.undo());
  }
  redo() {
    this.actions.forEach((d) => d.redo());
  }
}
