import { v4 as uuidv4 } from 'uuid';

/**
 * アプリ情報
 */
export interface AppInfo {
  appId: string;
  code?: string;
  name: string;
  description?: string;
}

/**
 * 属性項目情報
 */
export interface IProp{
    //プロパティ名
    name: string;
    //プロパティ表示名
    displayName: string;
    placeholder: string;
    //入力提示・説明
    hint:string;
    //関連属性リスト
    connectProps:[{key:string,propName:string}]|undefined;
    //プロパティ設定値
    modelValue: any;
}

/**
* アクションのプロパティ定義
 */
export interface IActionProperty {
  component: string;
  props: IProp;
}
/**
 * 変数オブジェクト
 */
export interface IActionVariable{
  actionName:string;
  displayName:string;
  name: {
    name:string;
    actionName:string;
    displayName:string;
    vars : {
      vName:string;
      logicalOperator:string;
      field: object;
    }[]
  };
}
/**
 * アクションタイプ定義
 */
export interface IActionNode {
  id: string;
  //アクション名
  name: string;
  title: string;
  varName:IProp|undefined;
  subTitle: string;
  inputPoint: string;
  //出力ポイント（条件分岐以外未使用）
  outputPoints: Array<string>;
  //ルートアクション（Kintone event）
  isRoot: boolean;
  //アクションのプロパティ定義
  actionProps: Array<IActionProperty>;
  //アクションのプロパティ設定値抽出
  ActionValue: object
  prevNodeId?: string;
  nextNodeIds: Map<string, string>;
}
/**
 * アクションフローの定義
 */
export interface IActionFlow {
  id: string;
  actionNodes: IActionNode[];
  addNode(newNode: IActionNode, prevNode?: IActionNode, inputPoint?: string): IActionNode;
  removeNode(targetNode: IActionNode): boolean;
  removeAllNext(targetNodeId: string): void;
  getVarNames(currentNode:IActionNode):IActionVariable[];
  findNodeById(id: string): IActionNode | undefined;
  toJSON(): any;
  getRoot(): IActionNode | undefined;
  createNewId(): string;
  getColumns(node:IActionNode):number;
}

/**
 * アクションのプロパティ定義に基づいたクラス
 */
class ActionProperty implements IActionProperty {
  component: string;
  props: IProp;

  static defaultProperty(): IActionProperty {
    return new ActionProperty('InputText', 'displayName', '表示名', '表示を入力してください', '','');
  };

  constructor(
    component: string,
    name: string,
    displayName: string,
    placeholder: string,
    hint:string,
    modelValue: any
  ) {
    this.component = component;
    this.props = {
      name: name,
      displayName: displayName,
      placeholder: placeholder,
      hint:hint,
      connectProps:undefined,
      modelValue: modelValue
    };
  }
}


/**
 * IActionNodeの実装、RootActionNode以外のアクション定義
 */
export class ActionNode implements IActionNode {
  id: string;
  name: string;
  get title(): string {
    const prop = this.actionProps.find((prop) => prop.props.name === 'displayName');
    return prop?.props.modelValue;
  };
  get subTitle(): string {
    return this.name;
  };

  //変数名
  get varName():IProp|undefined{
      const prop = this.actionProps.find((prop) => prop.props.name === 'verName');
      return prop?.props;
  }

  inputPoint: string;
  //出力ポイント（条件分岐以外未使用）
  outputPoints: Array<string>;
  actionProps: Array<IActionProperty>;
  get isRoot(): boolean {
    return false;
  };
  get ActionValue(): object {
    const propValue: any = {};
    this.actionProps.forEach((value) => {
      propValue[value.props.name] = value.props.modelValue
    });
    return propValue;
  };
  prevNodeId?: string;
  nextNodeIds: Map<string, string>;
  constructor(
    name: string,
    title: string,
    inputPoint: string,
    outputPoint: Array<string> = [],
    actionProps: Array<IActionProperty> = [ActionProperty.defaultProperty()],
  ) {
    this.id = uuidv4();
    this.name = name;
    this.inputPoint = inputPoint;
    this.outputPoints = outputPoint;
    const defProp = ActionProperty.defaultProperty();
    defProp.props.modelValue = title;
    this.actionProps = actionProps;
    const prop = this.actionProps.find((prop) => prop.props.name === defProp.props.name);
    if (prop === undefined) {
      this.actionProps.unshift(defProp);
    }
    this.nextNodeIds = new Map<string, string>();
  }

}
/**
 * ルートアクション定義
 */
export class RootAction implements IActionNode {
  id: string;
  name: string;
  title: string;
  subTitle: string;
  inputPoint: string;
  varName: IProp | undefined=undefined;
  //出力ポイント（条件分岐以外未使用）
  outputPoints: Array<string>;
  isRoot: boolean;
  actionProps: Array<IActionProperty>;
  ActionValue: object;
  prevNodeId?: string = undefined;
  nextNodeIds: Map<string, string>;
  constructor(
    name: string,
    title: string,
    subTitle: string,
  ) {
    this.id = uuidv4();
    this.name = name;
    this.title = title;
    this.subTitle = subTitle;
    this.inputPoint = '';
    this.outputPoints = [];
    this.isRoot = true;
    this.actionProps = [];
    this.ActionValue = {};
    this.nextNodeIds = new Map<string, string>();
  }
}

/**
 * アクションフローの定義
 */
export class ActionFlow implements IActionFlow {
  id: string;
  actionNodes: Array<IActionNode>;
  constructor(actionNodes: Array<IActionNode> | RootAction) {
    if (actionNodes instanceof Array) {
      this.actionNodes = actionNodes;
    } else {
      this.actionNodes = [actionNodes];
    }
    this.id = '';
    //this.id = uuidv4();
  }
  /**
   * ノードを追加する
   * 1.ID採番する
   * 2.前のノードを関連付け
   * @param newNode 新規追加するノード
   * @param prevNode 前のノード
   * @param inputPoint 入力ポイント
   * @returns 追加されたノード
   */
  addNode(
    newNode: IActionNode,
    prevNode?: IActionNode,
    inputPoint?: string): IActionNode {
    if (inputPoint !== undefined) {
      newNode.inputPoint = inputPoint;
    }
    if (prevNode !== undefined) {
      this.resetNodeRelation(prevNode, newNode, inputPoint || '');
    } else {
      prevNode = this.actionNodes[this.actionNodes.length - 1];
      this.connectNodes(prevNode, newNode, inputPoint || '');
    }
    const index = this.actionNodes.findIndex(node => node.id === prevNode?.id)
    if (index >= 0) {
      this.actionNodes.splice(index + 1, 0, newNode);
    } else {
      this.actionNodes.push(newNode);
    }
    return newNode;
  }
  /**
   * ノードを削除する
   * @param delNode
   */
  removeNode(targetNode: IActionNode): boolean {
    if (!targetNode) {
      return false;
    }
    if (targetNode.isRoot) {
      return false;
    }
    this.disconnectFromPrevNode(targetNode);
    this.reconnectOrRemoveNextNodes(targetNode);
    this.removeFromActionNodes(targetNode.id);
    return true;
  }
  /***
   * 目標ノードの次のノードを全部削除する
   */
  removeAllNext(targetNodeId: string) {
    if (!targetNodeId || targetNodeId === '') {
      return false;
    }
    const targetNode = this.findNodeById(targetNodeId);
    if (!targetNode) {
      return false;
    }
    if(targetNode.isRoot){
      this.actionNodes=[targetNode];
      targetNode.nextNodeIds.clear();
      return;
    }
    if (targetNode.nextNodeIds.size == 0) {
      return false;
    }
    for (const [, id] of targetNode.nextNodeIds) {
      this.removeAll(id);
    }
    targetNode.nextNodeIds.clear();
  }

  /***
   * 目標ノードの次のノードを全部削除する
   */
  removeAll(targetNodeId: string) {
    if (!targetNodeId || targetNodeId === '') {
      return;
    }
    const targetNode = this.findNodeById(targetNodeId);
    if (!targetNode) {
      return
    }
    // if (targetNode.nextNodeIds.size == 0) {
    //   return
    // }
    for (const [, id] of targetNode.nextNodeIds) {
      this.removeAll(id);
    }
    this.removeNode(targetNode);
  }

  // 断开与前一个节点的连接
  disconnectFromPrevNode(targetNode: IActionNode): void {
    const prevNodeId = targetNode.prevNodeId;
    if (prevNodeId) {
      const prevNode = this.findNodeById(prevNodeId);
      if (prevNode) {
        for (const [key, value] of prevNode.nextNodeIds) {
          if (value === targetNode.id) {
            prevNode.nextNodeIds.delete(key);
          }
        }
      }
    }
  }

  // actionNodes からノードを削除する
  private removeFromActionNodes(targetNodeId: string): void {
    const index = this.actionNodes.findIndex(node => node.id === targetNodeId);
    if (index > -1) {
      this.actionNodes.splice(index, 1);
    }
  }

  /**
   * ノード削除時、前のノードと次のノードを接続する
   * @param targetNode
   */
  reconnectOrRemoveNextNodes(targetNode: IActionNode): void {
    if (!targetNode || !targetNode.prevNodeId) {
      return;
    }
    //前のノードを取得
    const prevNode = this.findNodeById(targetNode.prevNodeId);
    if (!prevNode) return;
    //次のノード取得
    const nextNodeIds = targetNode.nextNodeIds;
    if (nextNodeIds.size == 0) {
      return;
    }
    //次のノード一つの場合
    if (nextNodeIds.size == 1) {
      const nextNodeId = nextNodeIds.get('');
      if (!nextNodeId) return;
      const nextNode = this.findNodeById(nextNodeId);
      if (!nextNode) return;
      this.connectNodes(prevNode,nextNode,targetNode.inputPoint || '');
      return;
    }
    //二つ以上の場合
    for (const [point, nextid] of nextNodeIds) {
      const nextNode = this.findNodeById(nextid);
      if (!nextNode) return;
      if (!this.connectNodes(prevNode, nextNode, point)) {
        this.removeAllNext(nextid);
        this.removeFromActionNodes(nextid);
      }
    }
  }

  /**
   * 二つノードを接続する
   * @param prevNode
   * @param nextNodeId
   * @param point
   * @returns
   */
  connectNodes(prevNode: IActionNode, nextNode: IActionNode, point: string): boolean {
    if (!prevNode || !nextNode) {
      return false;
    }
    if (!nextNode) return false;
    prevNode.nextNodeIds.set(point, nextNode.id);
    nextNode.prevNodeId = prevNode.id;
    nextNode.inputPoint = point;
    return true;
  }

  /**
   *
   * @param prevNode ノード挿入時の接続をリセットする
   * @param newNode
   * @param inputPoint
   */
  resetNodeRelation(prevNode: IActionNode, newNode: IActionNode, inputPoint?: string) {
    //元の次のノードを取得
    const originalNextNodeId = prevNode.nextNodeIds.get(inputPoint || '');
    prevNode.nextNodeIds.set(inputPoint || '', newNode.id);
    newNode.prevNodeId = prevNode.id;
    newNode.inputPoint=inputPoint||'';
    this.setNewNodeNextId(newNode, originalNextNodeId, inputPoint);
  }

  /**
   * 後ノードと新ノードの関連付け
   * @param newNode
   * @param originalNextNodeId
   * @param inputPoint
   */
  private setNewNodeNextId(newNode: IActionNode, originalNextNodeId: string | undefined, inputPoint?: string) {
    // 元の接続ノードが存在する
    if (!originalNextNodeId) {return;}
    const originNextNode = this.findNodeById(originalNextNodeId);
    if (!originNextNode) {return;}
    // 新しいノードの outputPoints に該当 inputPointが存在するか場合をチェックする
    if (newNode.outputPoints.includes(inputPoint || '')) {
      newNode.nextNodeIds.set(inputPoint || '', originalNextNodeId);
      originNextNode.prevNodeId=newNode.id;
    } else {
      //  inputPointが存在しない場合、outputPointのポイントの任意ポートを選択する
      const alternativeOutputPoint = newNode.outputPoints.length > 0 ? newNode.outputPoints[0] : '';
      newNode.nextNodeIds.set(alternativeOutputPoint, originalNextNodeId);
      originNextNode.prevNodeId=newNode.id;
    }
  }

  /***
   * IDでActionNodeを取得する
   */
  findNodeById(id: string): IActionNode | undefined {
    return this.actionNodes.find((node) => node.id === id);
  }

  getVarNames(currentNode:IActionNode):IActionVariable[]{
    let varNames:IActionVariable[]=[];
    if(currentNode.prevNodeId!==undefined){
      const prevNode=this.findNodeById(currentNode.prevNodeId);
      if(prevNode!==undefined){
        varNames = this.getPrevVarNames(prevNode);
      }
    }
    return varNames;
  }

  getPrevVarNames(prevNode:IActionNode):IActionVariable[]{
    let varNames:IActionVariable[]=[];
    if(prevNode.varName!==undefined && prevNode.varName.modelValue){

      if(prevNode.varName.modelValue ==='object'){
        console.log(prevNode);

      }

      varNames.unshift({
        actionName:prevNode.name,
        displayName:prevNode.varName.displayName,
        name:prevNode.varName.modelValue
      });
    }
    if(prevNode.prevNodeId!==undefined){
      const prevPrevNode=this.findNodeById(prevNode.prevNodeId);
      if(prevPrevNode!==undefined){
        const prevVars = this.getPrevVarNames(prevPrevNode);
        varNames=[...prevVars,...varNames];
      }
    }
    return varNames;
  }

  toJSON() {
    return {
      id: this.id,
      actionNodes: this.actionNodes.map(node => {
        const { nextNodeIds, ...rest } = node;
        return {
          ...rest,
          nextNodeIds: Object.fromEntries(nextNodeIds)
        };
      })
    };
  }

  getColumns(node:IActionNode):number{
    let result= 1;
    if(node.outputPoints && node.outputPoints.length>1){
      result += node.outputPoints.length -1;
    }
    let nextNode;
    for (const [key, id] of node.nextNodeIds) {
      nextNode=this.findNodeById(id);
      if(nextNode){
        result +=this.getColumns(nextNode)-1;
      }
    }
    return result;
  }

  getRoot(): IActionNode | undefined {
    return this.actionNodes.find(node => node.isRoot)
  }

  createNewId():string{
    this.id=uuidv4();
    return this.id;
  }

  static fromJSON(json: string): ActionFlow {
    const parsedObject = JSON.parse(json);

    const actionNodes = parsedObject.actionNodes.map((node: any) => {
      const nodeClass = !node.isRoot ? new ActionNode(node.name, node.title, node.inputPoint, node.outputPoints, node.actionProps)
        : new RootAction(node.name, node.title, node.subTitle);
      nodeClass.nextNodeIds = new Map<string,string>(Object.entries(node.nextNodeIds));
      nodeClass.prevNodeId = node.prevNodeId;
      nodeClass.id = node.id;
      return nodeClass;
    });
    const actionFlow = new ActionFlow(actionNodes);
    actionFlow.id = parsedObject.id;
    return actionFlow;
  }
}

