import { IContext,ICodeValue } from "./ActionTypes";

//ノード種別
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 class ConditionNode implements INode {
  index: number;
  type: NodeType;
  parent:INode;
  get logicalOperator(): LogicalOperator{
    return this.parent.logicalOperator;
  };
  object: any; // 比較元
  operator: Operator; // 比較子
  value: any;
  get header():string{
    return 'generic';
  }

  constructor(object: any, operator: Operator, 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;

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

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

  /**
   *  ノード削除
   * @param 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);
      }
    }
  }

/**
 * 条件ツリーからインディクス値で条件ノードを検索する
 * @param index 
 * @returns 
 */
  findByIndex(index:number):INode|undefined{
    return this.findChildren(this.root,index);
  }

  /**
   * 指定のノードグループからインディクス値で条件ノードを検索する
   * @param parent 
   * @param index 
   * @returns 
   */
  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;
  }

  /**
   * 
   * @param node 最大のインディクス値を取得する
   * @returns 
   */
  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;
}

  /**
   * 条件式を表示する
   * @param node 条件ノード
   * @returns 
   */
  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.operator ) {
        let value=condNode.value;
        if(value && typeof value ==='object' && ('label' in value)){
            value =condNode.value.label;
        }
        return `${condNode.object.name} ${condNode.operator} '${value}'`;
      } else {
        return '';
      }
    }
  }
  /**
   * すべて条件式を取得する
   */
  getConditions(parent:GroupNode):ConditionNode[]{
    const condiNodes:ConditionNode[]=[];
    parent.children.forEach((node)=>{
      if(node.type===NodeType.Condition){
        condiNodes.push(node as ConditionNode);
      }else{
        condiNodes.push(...this.getConditions(node as GroupNode));
      }
    });
    return condiNodes;
  }

  /**
   * 
   * @param parent すべて条件グループを取得する
   * @returns 
   */
  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;
  }

  /**
   * Jsonから復元
   * @param jsonString 
   * @returns 
   */
  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;
  }
  /**
   * JSON文字列に変換する
   * @returns 
   */
  toJson():string{
    return JSON.stringify(this.root, (key, value) => {
      if (key === 'parent') {
        return value ? value.type : null;
      }
      return value;
    });
  }

  /**
   * 条件式を計算する
   * @param node 
   * @param context 
   * @returns 
   */
  evaluate(node: INode, context: any): boolean {
    if (node.type === NodeType.Condition) {
      return this.evaluateCondition(node as ConditionNode, context);
    } else if (node.type === NodeType.LogicGroup || node.type === NodeType.Root) {
      const groupNode = node as GroupNode;
      const results = groupNode.children.map(child => this.evaluate(child, context));
  
      if (groupNode.logicalOperator === LogicalOperator.AND) {
        return results.every(result => result);
      } else if (groupNode.logicalOperator === LogicalOperator.OR) {
        return results.some(result => result);
      } else {
        throw new Error('Unsupported logical operator');
      }
    } else {
      throw new Error('Unsupported node type');
    }
  }


  getCodeValue(value: any): any {
    if (value && typeof value === 'object' && 'code' in value) {
      return value.code;
    } else {
      return value;
    }
  }

  /**
   * ICodeValue型のcodeに変換する
   * @param value 
   * @returns 
   */
  convertCodeValue(value:any):any{
    if (Array.isArray(value)) {
      return value.map(v => this.getCodeValue(v));
    } else {
      return this.getCodeValue(value);
    }
  }
  /**
   * 
   * @param vars 変数値を取得する
   * @param path 
   * @returns 
   */
  getContextVarByPath (vars: {[key:string]:any}, path: string){
    return path.split(".").reduce((o, k) => (o || {})[k], vars);
  };

  /**
   * Condition objectの値を取得する
   * @param object 
   * @param context 
   * @returns 
   */
  getObjectValue(object:any,context:IContext){
    if(!object || typeof object!=="object" || !("objectType" in object)){
        return undefined;
    }
    if(object.objectType==='field'){
      const fieldValue = context.record[object.code];
      if(fieldValue.type==='NUMBER' && fieldValue.value!==undefined){
        return Number(fieldValue.value);
      }else{
        return this.convertCodeValue(fieldValue.value);
      }      
    }else if(object.objectType==='variable'){
      const varValue = this.getContextVarByPath(context.variables,object.name.name);
      return this.convertCodeValue(varValue);
    }else if(object.objectType==='text'){
      return object.sharedText;
    }
  }

  /**
   * 比較オブジェクトの値を取得
   * @param object 
   * @returns 
   */
  getConditionValue(object:any,context:IContext){
    if(!object || typeof object!=="object"){
      return object;
    }
    if("objectType" in object){
      return this.getObjectValue(object,context);
    }
    if("sharedText" in object){
      return object.sharedText;
    }
    if("label" in object){
      return object.label;
    }
    if("value" in object){
      return object.value;
    }
    if("name" in object){
      return object.name;
    }
  }

  /**
   * 条件式を計算する
   * @param condition 
   * @param context 
   * @returns 
   */
  evaluateCondition(condition: ConditionNode, context: IContext): boolean {
    const { object, operator, value } = condition;
    const targetObject = this.getObjectValue(object,context);
    const conditionObject = this.getConditionValue(value,context);
    const isArrayTarget = Array.isArray(targetObject);
    const isArrayValue = Array.isArray(conditionObject);
    //配列の比較
    if((isArrayTarget && targetObject.length > 1)||(isArrayValue && conditionObject.length>1)){
      return this.compareArray(operator,targetObject,conditionObject)
    }
    const targetValue = isArrayTarget?targetObject[0]:targetObject;
    const conditionValue = isArrayValue?conditionObject[0]:conditionObject;
    switch (operator) {
      case Operator.Equal:
      case Operator.NotEqual:
      case Operator.Greater:
      case Operator.GreaterOrEqual:
      case Operator.Less:
      case Operator.LessOrEqual:
        return this.compare(operator,targetValue, conditionValue);
      case Operator.Contains:
        return this.contains(targetValue,conditionValue);
      case Operator.NotContains:
        return !this.contains(targetValue,conditionValue);
      case Operator.StartWith:
        return this.startWith(targetValue,conditionValue);
      case Operator.NotStartWith:
        return !this.startWith(targetValue,conditionValue);
      case Operator.EndWith:
        return this.endsWith(targetValue,conditionValue);
      case Operator.NotEndWith:
        return this.endsWith(targetValue,conditionValue);
      default:
        throw new Error('Unsupported operator');
    }
  }
  
  /**
   * 比較を実行する
   * @param operator 
   * @param targetValue 
   * @param value 
   * @returns 
   */
  compare(operator: Operator, targetValue: any, value: any): boolean {
    // targetValue は日期时，value も日期に変換して比較する
    if(targetValue===undefined || targetValue===null||targetValue===''){
      if(value===undefined || value===null||value===''){
        targetValue='';
        value='';
      }
    }
    if (targetValue instanceof Date) {
      const dateValue = new Date(value);
      if (!isNaN(dateValue.getTime())) {
        value = dateValue;
      }
    }
    //targetValueは数値時，value を数値に変換する
    else if (typeof targetValue === 'number') {
      const numberValue = Number(value);
      if (!isNaN(numberValue)) {
        value = numberValue;
      }
    }
    else if (typeof targetValue === 'string') {
      value = String(value);
    }
    switch (operator) {
      case Operator.Equal:
        return targetValue === value;
      case Operator.NotEqual:
        return targetValue !== value;
      case Operator.Greater:
        return targetValue > value;
      case Operator.GreaterOrEqual:
        return targetValue >= value;
      case Operator.Less:
        return targetValue < value;
      case Operator.LessOrEqual:
        return targetValue <= value;
      default:
        throw new Error('Unsupported operator for comparison');
    }
  }
  
  /**
   * 配列値の比較
   * @param operator 
   * @param targetValue 
   * @param value 
   */
 /**
 * 配列値の比較
 * @param operator 
 * @param targetValue 
 * @param value 
 */
 compareArray(operator: Operator, targetValue: any, value: any): boolean {
  const isArrayTarget = Array.isArray(targetValue);
  const isArrayValue = Array.isArray(value);
  const sortArray = (arr: any[]) => arr.slice().sort();
  targetValue = isArrayTarget?targetValue:[targetValue];
  value= isArrayValue?value:[value];
  const targetArray=sortArray(targetValue);
  const valueArray=sortArray(value);
  switch (operator) {
    case Operator.Equal:
      return  targetArray.length===valueArray.length && targetArray.every((v, i) => v === valueArray[i]);
    case Operator.NotEqual:
      return targetArray.length!==valueArray.length || !targetArray.every((v, i) => v === valueArray[i]);
    case Operator.Contains:
      return valueArray.length>0 && valueArray.every(v => targetArray.includes(v));
    case Operator.NotContains:
      return valueArray.length===0 || !valueArray.every(v => targetArray.includes(v));
    default:
     return false;
  }
}

  /**
   * 含む計算
   * @param targetValue 
   * @param value 
   * @returns 
   */
  contains(targetValue: any, value: any): boolean {
    if (typeof targetValue === 'string' && typeof value === 'string') {
      return targetValue.includes(value);
    }
    return false;
  }
   /**
   * StartWith計算
   * @param targetValue 
   * @param value 
   * @returns 
   */
  startWith(targetValue: any, value: any): boolean {
    if (typeof targetValue === 'string' && typeof value === 'string') {
      return targetValue.startsWith(value);
    }
    return false;
  }
  /**
   * EndsWith計算
   * @param targetValue 
   * @param value 
   * @returns 
   */
  endsWith(targetValue: any, value: any): boolean {
    if (typeof targetValue === 'string' && typeof value === 'string') {
      return targetValue.endsWith(value);
    }
    return false;
  }
}
