//ノード種別
export enum NodeType{
  Root = 'root',
  LogicGroup ='logicgroup',
  Condition ='condition'
}

//ロジックオペレーター
export enum LogicalOperator{
  AND = 'AND',
  OR = 'OR'
}

//条件オペレーター
export enum Operator{
  Equal = '=',
  NotEqual='!=',
  Greater = '>',
  GreaterOrEqual = '>=',
  Less = '<',
  LessOrEqual = '<=',
  Contains = 'contains',
  NotContains = 'not contains',
  StartWith = 'start With',
  EndWith = 'end with',
  NotStartWith = 'not start with',
  NotEndWith = 'not end with'
}


// INode
export interface INode {
  index:number;
  type: NodeType;
  header:string;
  parent: INode | null;
  logicalOperator:LogicalOperator
}

// ロジックノード
export class GroupNode implements INode {
  index:number;
  type: NodeType;
  children: INode[];
  parent: INode | null;
  logicalOperator: LogicalOperator;
  get label():string{
    return this.logicalOperator;
  }
  get header():string{
    return this.type===NodeType.Root?'root':'generic';
  }
  get expanded():boolean{
    return this.children.length>0;
  }
  constructor(logicOp:LogicalOperator, parent: INode | null) {
    this.index=0;
    this.type = parent==null?NodeType.Root: NodeType.LogicGroup;
    this.logicalOperator = logicOp;
    this.parent=parent;
    this.children=[];
  }

  static fromJSON(json: any, parent: INode | null = null): GroupNode {
    const node = new GroupNode(json.logicalOperator, parent);
    node.index=json.index;
    node.children = json.children.map((childJson: any) => {
      return childJson.type === NodeType.LogicGroup
        ? GroupNode.fromJSON(childJson, node)
        : ConditionNode.fromJSON(childJson, node);
    });
    return node;
  }

}

export type OperatorListItem = {
  label: string;
  value: string;
}

// 条件式ノード
export class ConditionNode implements INode {
  index: number;
  type: NodeType;
  parent:INode;
  get logicalOperator(): LogicalOperator{
    return this.parent.logicalOperator;
  };
  object: any; // 比較元
  operator: Operator | OperatorListItem; // 比較子
  value: any;
  get header():string{
    return 'generic';
  }

  constructor(object: any, operator: Operator | OperatorListItem, value: any, parent: GroupNode) {
    this.index=0;
    this.type = NodeType.Condition;
    this.object = object;
    this.operator = operator;
    this.value = value;
    this.parent=parent;
  }

  static fromJSON(json: any, parent: GroupNode): ConditionNode {
    const node= new ConditionNode(
      json.object,
      json.operator,
      json.value,
      parent
    );
    node.index=json.index;
    return node;
  }
}
// 条件式の管理クラス
export class ConditionTree {
  root: GroupNode;
  maxIndex:number;
  queryString:string;

  constructor() {
    this.maxIndex=0;
    this.root = new GroupNode(LogicalOperator.AND, null);
    this.queryString='';
  }

  // ノード追加
  addNode(parent: GroupNode, node: INode): void {
    this.maxIndex++;
    node.index=this.maxIndex;
    parent.children.push(node);
  }

  // ノード削除
  removeNode(node: INode): void {
    if (node.parent === null) {
      throw new Error('ルートノード削除できません');
    } else {
      const parent = node.parent as GroupNode;
      const index = parent.children.indexOf(node);
      if (index > -1) {
        parent.children.splice(index, 1);
      }
    }
  }

  findByIndex(index:number):INode|undefined{
    return this.findChildren(this.root,index);
  }

  findChildren(parent: GroupNode, index: number): INode | undefined {
    if (parent.index === index) {
      return parent;
    }
    for (const node of parent.children) {
      if (node.index === index) {
        return node;
      }
      if (node.type !== NodeType.Condition) {
        const foundNode = this.findChildren(node as GroupNode, index);
        if (foundNode) {
          return foundNode;
        }
      }
    }
    return undefined;
  }

  getMaxIndex(node:INode):number{
    let maxIndex:number=node.index;
    if(node.type!==NodeType.Condition){
      const groupNode = node as GroupNode;
      groupNode.children.forEach((child)=>{
          const childMax = this.getMaxIndex(child);
          if(childMax>maxIndex){
            maxIndex=childMax;
          }
      });
    }
    return maxIndex;
}

  //条件式を表示する
  buildConditionString(node:INode){
    if (node.type !== NodeType.Condition) {
      let conditionString = '(';
      const groupNode = node as GroupNode;
      for (let i = 0; i < groupNode.children.length; i++) {
        const childConditionString = this.buildConditionString(groupNode.children[i]);
        if (childConditionString !== '') {
          conditionString += childConditionString;
          if (i < groupNode.children.length - 1) {
            conditionString += ` ${groupNode.logicalOperator} `;
          }
        }
      }
      conditionString += ')';
      return conditionString;
    } else {
      const condNode=node as ConditionNode;
      if (condNode.object && condNode.object.sharedText && condNode.operator ) {
        // let value=condNode.value;
        // if(value && typeof value ==='object' && ('label' in value)){
        //     value =condNode.value.label;
        // }
        const rightVal = condNode.value.sharedText || '""';
        return `${condNode.object.sharedText} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} ${rightVal}`;
        // return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
      } else {
        return '';
      }
    }
  }

  buildConditionQueryString(node:INode){
    if (node.type !== NodeType.Condition) {
      let conditionString = '';
      if(node.type !== NodeType.Root){
        conditionString = '(';
      }

      const groupNode = node as GroupNode;
      for (let i = 0; i < groupNode.children.length; i++) {
        const childConditionString = this.buildConditionQueryString(groupNode.children[i]);
        if (childConditionString !== '') {
          conditionString += childConditionString;
          if (i < groupNode.children.length - 1) {
            conditionString += ` ${groupNode.logicalOperator.toLowerCase()} `;
          }
        }
      }

      if(node.type !== NodeType.Root){
        conditionString += ')';
      }
      return conditionString;
    } else {
      const condNode=node as ConditionNode;
      if (condNode.object && condNode.operator ) {
        if (!condNode.object.code || !condNode.value.sharedText){
          return '';
        }
        return `${condNode.object.code} ${typeof condNode.operator === 'object' ? condNode.operator.value : condNode.operator} "${condNode.value.sharedText}"`;
      } else {
        return '';
      }
    }
  }

  /**
   *
   * @param node ノード移動
   * @param direction
   * @returns
   */
  moveNode(node:INode, direction: 'up' | 'down'): void {
    if (!node || !node.parent) {
      return;
    }

    const parent = node.parent as GroupNode;
    const currentIndex = parent.children.findIndex(child => child === node);
    if (currentIndex === -1) {
      return;
    }

    const newIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;

    // 範囲外のインデックスの処理
    if (newIndex >= 0 && newIndex < parent.children.length) {
      // ノードの位置を入れ替える
      [parent.children[currentIndex], parent.children[newIndex]] = [parent.children[newIndex], parent.children[currentIndex]];
      return; // 範囲外なら移動しない
    }else if(newIndex<0 && parent.parent){
      this.removeNode(node);
      const parentIndex = (parent.parent as GroupNode).children.findIndex(child => child === parent);
      (parent.parent as GroupNode).children.splice(parentIndex, 0, node);
      node.parent = parent.parent;
    }
  }

  getGroups(parent:GroupNode):number[]{
    const groups:number[]=[];
    groups.push(parent.index);
    parent.children.forEach((node)=>{
      if(node.type!==NodeType.Condition){
        groups.push(...this.getGroups(node as GroupNode));
      }
    });
    return groups;
  }

  /**
   * 条件ノードをグループ化
   * @param nodes 結合ノードを選択する
   * @param logicOp
   * @returns
   */
  createGroupNode(firstNode:INode,nodes: INode[], logicOp: LogicalOperator): GroupNode | null {
    if (nodes.length === 0) {
      return null;
    }

    // 最初のノードの親ノードを取得
    const parent = firstNode.parent as GroupNode;
    if (!parent) {
      throw new Error('ルートノードをグループ化できません');
    }

    // 親ノードを取得
    const filteredNodes = nodes.filter(node => node.parent === parent);

    // 新しいグループノードを作成
    const newGroup = new GroupNode(logicOp, parent);
    this.maxIndex++;
    newGroup.index = this.maxIndex;

    // 新しいグループノードの挿入位置を取得
    let firstNodeIndex = parent.children.length;
    if (filteredNodes.length > 0) {
      firstNodeIndex = parent.children.indexOf(filteredNodes[0]);
    }

    filteredNodes.forEach(node => {
      // 元の親ノードから削除する
      const nodeIndex = parent.children.indexOf(node);
      parent.children.splice(nodeIndex, 1);

      // 新しいグループに追加
      node.parent = newGroup;
      newGroup.children.push(node);
    });

    // 新しいGroupNodeを挿入する
    parent.children.splice(firstNodeIndex, 0, newGroup);
    return newGroup;
  }

   /**
    * GroupNodeを解散する
    * @param groupNode 対象グループノード
    */
   dissolveGroupNode(groupNode: GroupNode): void {
    if (groupNode.parent === null || groupNode.type !== NodeType.LogicGroup) {
      throw new Error('ルートノードと非グループノードを解散することができません');
    }

    // 親ノードを取得
    const parent = groupNode.parent as GroupNode;
    const groupIndex = parent.children.indexOf(groupNode);

    // 子ノードをリセットする
    groupNode.children.forEach(child => {
      child.parent = parent;
      parent.children.splice(groupIndex, 0, child);
    });

    //グループノードを削除する
    parent.children.splice(groupIndex + groupNode.children.length, 1);
  }

  // Jsonから復元
  fromJson(jsonString: string): INode {
    const json = JSON.parse(jsonString);
    this.root = GroupNode.fromJSON(json) as GroupNode;
    this.maxIndex=this.getMaxIndex(this.root);
    return this.root;
  }

  toJson():string{
    return JSON.stringify({queryString :this.queryString, ...this.root}, (key, value) => {
      if (key === 'parent') {
        return value ? value.type : null;
      }
      return value;
    });
  }

  setQuery(queryString:string){
    this.queryString=queryString;
  }
}
