interface SpecBuilder {
  readonly build: (ptyName: string, params: any) => string | null;
}

class DefaultBuilder implements SpecBuilder {
  readonly build = (ptyName: string, params: any) => {
    const val = params[ptyName];
    if (val) {
      return `${ptyName}: {_v: eq,_val: "${val}"}`;
    }
    return null;
  };
}

class TextBuilder implements SpecBuilder {
  readonly build = (ptyName: string, params: any) => {
    const val = params[ptyName];
    if (val) {
      return `${ptyName}: {_v: like,_val: "${val}%"}`;
    }
    return null;
  };
}

class TextIdBuilder implements SpecBuilder {
  readonly build = (ptyName: string, params: any) => {
    const val = params[ptyName];
    if (val) {
      return `${ptyName}: {_v: eq,_val: ${val}}`;
    }
    return null;
  };
}

class BooleanSelectBuilder implements SpecBuilder {
  readonly build = (ptyName: string, params: any) => {
    const val = params[ptyName];
    if (val) {
      return `${ptyName}: {_v: eq,_val: ${val}}`;
    }
    return null;
  };
}

class DateRangeBuilder implements SpecBuilder {
  readonly build = (ptyName: string, params: any) => {
    const val = params[ptyName];
    if (val) {
      return `${ptyName}: {_v: btw, _val: ${JSON.stringify(val[0])}, _to: ${JSON.stringify(
        val[1],
      )}}`;
    }
    return null;
  };
}

class DateTimeRangeBuilder implements SpecBuilder {
  readonly build = (ptyName: string, params: any) => {
    const val = params[ptyName];
    if (val) {
      return `${ptyName}: {_v: btw, _val: ${JSON.stringify(val[0])}, _to: ${JSON.stringify(
        val[1],
      )}}`;
    }
    return null;
  };
}

class DigitBuilder implements SpecBuilder {
  readonly build = (ptyName: string, params: any) => {
    const val = params[ptyName];
    if (val) {
      return `${ptyName}: {_v: eq,_val: ${val}}`;
    }
    return null;
  };
}

class DecimalDigitBuilder implements SpecBuilder {
  readonly build = (ptyName: string, params: any) => {
    const val = params[ptyName];
    if (val) {
      return `${ptyName}: {_v: eq,_val: "${val}"}`;
    }
    return null;
  };
}

class EntityLevel {
  constructor(path: string) {
    this.path = path;
  }

  readonly path: string;
  readonly standardFields: Map<string, Api.SingleTableField> = new Map();
  readonly refFields: Map<string, EntityLevel> = new Map();

  private getRealLevel(level: EntityLevel | undefined) {
    return level || this;
  }

  add(field: Api.SingleTableField) {
    const fullPath = field.code.split('.');
    const etyPath = fullPath.pop();
    if (!!!etyPath) {
      return;
    }
    let currentLevel: EntityLevel | undefined;
    fullPath.forEach((p) => {
      let levelVar = this.getRealLevel(currentLevel)?.refFields.get(p);
      if (!!!levelVar) {
        levelVar = new EntityLevel(p);
        this.getRealLevel(currentLevel).refFields.set(p, levelVar);
      }
      currentLevel = levelVar;
    });
    this.getRealLevel(currentLevel).standardFields.set(etyPath, field);
  }
}

class SpecificationBuilderManager {
  readonly map: Map<string, SpecBuilder> = new Map([
    ['default', new DefaultBuilder()],
    ['text.string', new TextBuilder()],
    ['text.id', new TextIdBuilder()],
    ['select.boolean', new BooleanSelectBuilder()],
    ['dateRange.date', new DateRangeBuilder()],
    ['dateTimeRange.datetime', new DateTimeRangeBuilder()],
    ['digit.integer', new DigitBuilder()],
    ['digit.long', new DigitBuilder()],
    ['digit.decimal', new DecimalDigitBuilder()],
  ]);
  readonly defaultBuilder: SpecBuilder = new DefaultBuilder();

  getBuilder(field: Api.SingleTableField) {
    const builder = this.map.get(field.valueType);
    return builder || this.defaultBuilder;
  }

  build(
    params: any,
    singleTable: Api.SingleTable,
    extCondition: string | undefined = undefined,
    rootPath = 'specification',
  ) {
    const rootLevel = this.toEntityLevel(singleTable, rootPath);
    return this.buildEntityLevel(params, rootLevel, extCondition);
  }

  private toEntityLevel(singleTable: Api.SingleTable, rootPath = 'specification') {
    const entityLevel = new EntityLevel(rootPath);
    singleTable.fields.filter((f) => !!!f.hideInSearch).forEach((f) => entityLevel.add(f));
    return entityLevel;
  }

  private buildEntityLevel(
    params: any,
    entityLevel: EntityLevel,
    extCondition: string | undefined = undefined,
  ) {
    const specification: string[] = [];
    if (extCondition) {
      specification.push(extCondition);
    }
    entityLevel.standardFields.forEach((field, ptyName) => {
      const spec = this.getBuilder(field).build(ptyName, params);
      if (spec) {
        specification.push(spec);
      }
    });
    entityLevel.refFields.forEach((el, path) => {
      const elParams = params[path];
      if (elParams) {
        specification.push(this.buildEntityLevel(elParams, el));
      }
    });
    return `${entityLevel.path}:{${specification.join(',')}}`;
  }
}

const specificationBuilderManager = new SpecificationBuilderManager();
export default specificationBuilderManager;
