import { Component, OnInit } from '@angular/core';
import { DetailsFormBase } from '@objects-view/details-form-base';
import {
  FormArray,
  FormControl, FormGroup,
  UntypedFormArray,
  UntypedFormBuilder,
  UntypedFormControl,
  UntypedFormGroup,
  Validators
} from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ConfigService } from '@shared/config/config.service';
import { Observable, of } from 'rxjs';
import { IActionResult } from '@objects-view/interfaces/actions/action-result';
import { IPricingRuleModel } from '@fleio-api/openstack/model/pricing-rule.model';
import { IPricingRuleCreateOptions } from '@fleio-api/openstack/model/pricing-rule-create-options';
import { PricingRulesApiService } from '@fleio-api/openstack/pricing-rule/pricing-rules-api.service';
import { IPricingRuleAttribute } from '@fleio-api/openstack/model/pricing-rule-attribute.model';
import { IPricingRuleResource } from '@fleio-api/openstack/model/pricing-rule-resource.model';
import { IPricingPlanModel } from '@fleio-api/openstack/model/pricing-plan.model';
import { PricingPlansApiService } from '@fleio-api/openstack/pricing-plan/pricing-plans-api.service';
import { map, startWith } from 'rxjs/operators';
import * as _moment from 'moment';
import { IPricingRuleConditionModel } from '@fleio-api/openstack/model/pricing-rule-condition.model';
import { IPricingRuleModifierModel } from '@fleio-api/openstack/model/pricing-rule-modifier.model';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { AutocompleteHelper } from '@shared/ui-api/helpers/autocomplete-helper';
import { ValidatorsHelper } from '@shared/ui-api/helpers/validators-helper';
import { IIcon } from '@shared/ui/common/interfaces/icon';

@Component({
  selector: 'app-pricing-rule-edit-form',
  templateUrl: './pricing-rule-edit-form.component.html',
  styleUrls: ['./pricing-rule-edit-form.component.scss']
})
export class PricingRuleEditFormComponent extends DetailsFormBase<IPricingRuleModel> implements OnInit {
  LIST_OP = ['in', 'ni'];
  separatorKeysCodes: number[] = [ENTER, COMMA];

  pricingRuleForm = this.initForm();

  attributes = [] as IPricingRuleAttribute[];
  createOptions: IPricingRuleCreateOptions;
  pricingPlan: IPricingPlanModel;
  selectedResource = null as null | IPricingRuleResource;
  objectKeys = Object.keys;
  rateType = 'flat';
  conditions = this.pricingRuleForm.controls.conditions as FormArray;
  modifiers = this.pricingRuleForm.controls.modifiers as FormArray;
  removeTierTooltip = $localize`Remove tier`;
  addTierTooltip = $localize`Add tier`;
  removeFilterTooltip = $localize`Remove filter`;
  addFilterTooltip = $localize`Add filter`;
  removeModifierTooltip = $localize`Remove modifier`;
  percentPlaceholder = $localize`Add percent to rule price (%)`;
  pricePlaceholder = $localize`Add to rule price`;
  addPriceModifierTooltip = $localize`Add price modifier`;
  conditionsChipsControls = {};
  conditionsChipsObservables = {};
  modifiersChipsControls = {};
  modifiersChipsObservables = {};
  billedValueIncrementInfo: IIcon = {
    name: 'info',
    verticalAlign: 'middle',
    fontSize: '20px',
    marginLeft: '15px',
    marginTop: '15px',
    tooltipDirection: 'right',
    tooltip: $localize`Price is per every "x" defined unit(s).`
  };

  initForm() {
    return this.formBuilder.group({
      display_name: ['', Validators.required],
      resource: ['', Validators.required],
      attribute: [''],
      attribute_unit: [{value: '', disabled: true}],
      time_unit: [''],
      pricing: this.initPricing(),
      conditions: this.formBuilder.array([]),
      modifiers: this.formBuilder.array([]),
      start_dt: [null],
      end_dt: [null],
      calculate_metrics_pricing_per_time_unit: [false],
    });
  }

  initPricing() {
    return this.formBuilder.group({
      prices: this.formBuilder.array([this.formBuilder.group({
        f: 0,
        p: [0, [ValidatorsHelper.requiredNumericValueValidator, Validators.required]],
        t: [{value: '∞', disabled: true}]
      })]),
    });
  }

  constructor(
    private formBuilder: UntypedFormBuilder, private activatedRoute: ActivatedRoute, private config: ConfigService,
    private pricingRulesApiService: PricingRulesApiService, private router: Router,
    private pricingPlanApi: PricingPlansApiService
  ) {
    super();
  }

  disableWheel() {
    return false;
  }

  createTier(): UntypedFormGroup {
    return this.formBuilder.group({
      f: 0,
      p: [0, [ValidatorsHelper.requiredNumericValueValidator, Validators.required]],
      t: [{value: '∞', disabled: true}]
    });
  }

  createFilter(): UntypedFormGroup {
    return this.formBuilder.group({
      attribute: ['', Validators.required],
      operator: ['', Validators.required],
      value: ['', Validators.required],
      attribute_unit: [null, Validators.required],
    });
  }

  createModifier(): UntypedFormGroup {
    return this.formBuilder.group({
      attribute: ['', Validators.required],
      name: ['', Validators.required],
      operator: ['', Validators.required],
      value: ['', Validators.required],
      price: ['', [Validators.required, ValidatorsHelper.requiredNumericValueValidator]],
      price_is_percent: [false, Validators.required],
      time_unit: ['', Validators.required],
      attribute_unit: [null, Validators.required],
    });
  }

  ngOnInit() {
    super.ngOnInit();
    this.createOptions = this.activatedRoute.snapshot.data.createOptions;
    this.pricingPlan = this.activatedRoute.snapshot.data.pricingPlan;
    if (this.pricingPlan) {
      this.updatePricePlaceholder();
    }
    if (this.objectController) {
      this.objectController.actionCallback = () => this.savePriceRule();
    }
    // parse some fields in the json sent to backend, backend should be modified to accept params like they
    // are built in this form
    if (this.object && this.objectKeys(this.object).length) {
      this.pricingRuleForm.controls.resource.disable();
      const oldAttribute = this.object.pricing.attribute;
      const oldAttributeUnit = this.object.pricing.attribute_unit;
      const oldTimeUnit = this.object.pricing.time_unit;
      delete this.object.pricing.attribute;
      delete this.object.pricing.attribute_unit;
      delete this.object.pricing.time_unit;
      this.object.attribute = oldAttribute;
      this.object.attribute_unit = oldAttributeUnit;
      this.object.time_unit = oldTimeUnit;
      this.pricingRuleForm.patchValue(this.object);
      if (this.object && this.object.attribute === '' || this.object.attribute === null) {
        this.pricingRuleForm.controls.attribute.setValue(' ');
      }
      const formPrices = this.pricingRuleForm.get('pricing').get('prices') as UntypedFormArray;
      formPrices.removeAt(0); // remove the initialization of prices from create page
      if (this.object.pricing.prices.length > 1) {
        this.rateType = 'tiered';
      }
      const validators = [];
      if (this.object.billed_value_increment > 1) {
        validators.push(ValidatorsHelper.stepValueValidator(this.object.billed_value_increment));
      }
      for (const price of this.object.pricing.prices) {
        formPrices.push(this.formBuilder.group({
          f: [price.f, validators],
          t: [price.t, validators],
          p: [price.p, [ValidatorsHelper.requiredNumericValueValidator, Validators.required]],
        }));
      }
      for (const resource of this.createOptions.resources) {
        if (resource.id === this.object?.resource) {
          if (resource?.metrics?.length) {
            if (!this.pricingRuleForm.contains('billed_value_increment')) {
              this.pricingRuleForm.addControl(
                'billed_value_increment',
                this.formBuilder.control(this.object.billed_value_increment, ValidatorsHelper.requiredIntegerValidator)
              );
              this.pricingRuleForm.controls.billed_value_increment.valueChanges.subscribe(value => {
                if (this.rateType === 'tiered') {
                  this.reCalculateTiers();
                }
              });
            }
          }
        }
      }
      if (this.rateType === 'tiered') {
        this.reCalculateTiers();
      }
      for (const resource of this.createOptions.resources) {
        if (resource.id === this.object.resource) {
          this.selectedResource = resource;
          this.updatePricingAttributeValidators();
          if (this.selectedResource.internal_name === 'instance_ip') {
            if (!this.pricingRuleForm.contains('free_resources_for_related_resource')) {
              this.pricingRuleForm.addControl(
                'free_resources_for_related_resource', this.formBuilder.control(
                  this.object.free_resources_for_related_resource
                )
              );
            }
          }
          this.attributes = this.selectedResource.attributes;
          this.canSelectUnitForAttribute();
        }
      }

      // add conditions (filters)
      const formFilters = this.pricingRuleForm.get('conditions') as UntypedFormArray;
      for (const filter of this.object.conditions) {
        this.parseListValue(filter);
        const filterGroup = this.formBuilder.group(filter);
        filterGroup.controls.value.setValidators(Validators.required);
        formFilters.push(filterGroup);
      }

      // add modifiers
      const formModifiers = this.pricingRuleForm.get('modifiers') as UntypedFormArray;
      for (const modifier of this.object.modifiers as any) {
        this.parseListValue(modifier);
        const modifierGroup = this.formBuilder.group(modifier);
        modifierGroup.controls.name.setValidators(Validators.required);
        modifierGroup.controls.value.setValidators(Validators.required);
        modifierGroup.controls.price.setValidators([Validators.required, ValidatorsHelper.requiredNumericValueValidator]);
        if (modifier.price_is_percent) {
          modifierGroup.removeControl('time_unit');
        }
        formModifiers.push(modifierGroup);
      }

      this.pricingPlanApi.get(this.object.plan).pipe(map(response => {
        this.pricingPlan = response;
        this.updatePricePlaceholder();
      })).subscribe();
    }
  }

  private parseListValue(record: IPricingRuleConditionModel | IPricingRuleModifierModel) {
    // overrides value so that mat-select[multiple] would work
    if (this.isListOperator(record.operator) && !Array.isArray(record.value)) {
      record.value = [
        record.value.split(',').map((value) => value.trim())
      ];
    }
  }

  private dumpListValue(record: IPricingRuleConditionModel | IPricingRuleModifierModel) {
    // overrides mat-select[multiple] value from array to a string representing list of values
    if (this.isListOperator(record.operator)) {
      if (Array.isArray(record.value)) {
        record.value = record.value.join(',');
      }
    }
  }

  updatePricePlaceholder() {
    this.pricePlaceholder = $localize`Add to rule price (${this.pricingPlan.currency})`;
  }

  isListOperator(operator) {
    return this.LIST_OP.includes(operator);
  }

  private savePriceRule(): Observable<IActionResult> {
    const value = this.pricingRuleForm.getRawValue();
    // parse some fields in the json sent to backend, backend should be modified to accept params like they
    // are built in this form
    const attribute = value.attribute;
    const attributeUnit = value.attribute_unit || null;
    const timeUnit = value.time_unit;
    delete value.attribute;
    delete value.attribute_unit;
    delete value.time_unit;
    value.pricing.attribute = attribute;
    value.pricing.attribute_unit = attributeUnit;
    if (timeUnit) {
      value.pricing.time_unit = timeUnit;
    }
    value.plan = this.pricingPlan.id;
    if (this.selectedResource && this.selectedResource.type === 'internal') {
      value.start_dt = null;
      value.end_dt = null;
    }

    if (value.start_dt) {
      value.start_dt = _moment(value.start_dt).format();
    }
    if (value.end_dt) {
      value.end_dt = _moment(value.end_dt).format();
    }

    for (const filter of value.conditions) {
      this.dumpListValue(filter);
    }
    for (const modifier of value.modifiers) {
      this.dumpListValue(modifier);
    }

    this.createOrUpdate(this.pricingRulesApiService, value).subscribe(() => {
      this.router.navigateByUrl(
        this.config.getPanelUrl('settings/openstack-plans') + '/' + this.pricingPlan.id
      ).catch(() => {
      });
    });

    return of(null);
  }

  public resetPrices() {
    const prices = this.pricingRuleForm.get('pricing').get('prices') as UntypedFormArray;
    while (prices.length) {
      prices.removeAt(0);
    }
    prices.push(
      this.createTier()
    );
  }

  public changedCalculateMetrics(event) {
    if (event.checked) {
      if (!this.pricingRuleForm.controls.time_unit) {
        // add control if missing for metric based rules once calculation method is changed by switching checkbox
        this.pricingRuleForm.addControl('time_unit', new UntypedFormControl('', Validators.required));
      }
    } else {
      // remove the time_unit control if use no longer uses time units for metric based rules
      this.pricingRuleForm.removeControl('time_unit');
    }
  }

  changedModifierPricePercent(value, index) {
    const modifiers = this.pricingRuleForm.controls.modifiers as UntypedFormGroup;
    const changedModifier = modifiers.controls[index] as UntypedFormGroup;
    if (value.checked) {
      changedModifier.removeControl('time_unit');
    } else {
      changedModifier.addControl('time_unit', new UntypedFormControl('', Validators.required));
    }
  }

  private updatePricingAttributeValidators() {
    if (this.hasPricingAttribute()) {
      this.pricingRuleForm.controls.attribute.setValidators(Validators.required);
      this.pricingRuleForm.controls.attribute_unit.setValidators(Validators.required);
    } else {
      this.pricingRuleForm.controls.attribute.clearValidators();
      this.pricingRuleForm.controls.attribute_unit.clearValidators();
      this.pricingRuleForm.controls.attribute.updateValueAndValidity();
      this.pricingRuleForm.controls.attribute_unit.updateValueAndValidity();
    }
  }

  public selectedResourceChanged() {
    const oldSelectedResource = this.selectedResource;
    this.selectedResource = this.createOptions.resources.find(
      x => x.id === this.pricingRuleForm.controls.resource.value
    );
    this.updatePricingAttributeValidators();
    this.attributes = this.selectedResource.attributes;
    this.rateType = 'flat';
    this.resetPrices();
    const newSelectedResource = this.selectedResource;
    const newResourceIsMetricBased = (
      newSelectedResource.metric_display === true && newSelectedResource.attribute_display === false
    );
    if (!newResourceIsMetricBased) {
      this.pricingRuleForm.removeControl('billed_value_increment');
    } else {
      if (!this.pricingRuleForm.controls.billed_value_increment) {
        this.pricingRuleForm.addControl(
          'billed_value_increment',
          this.formBuilder.control(1, ValidatorsHelper.requiredIntegerValidator)
        );
        this.pricingRuleForm.controls.billed_value_increment.valueChanges.subscribe(value => {
          if (this.rateType === 'tiered') {
            this.reCalculateTiers();
          }
        });
      }
    }
    if (oldSelectedResource && oldSelectedResource.type !== this.selectedResource.type) {
      this.pricingRuleForm.reset();
      this.pricingRuleForm.controls.calculate_metrics_pricing_per_time_unit.setValue(false);
      this.pricingRuleForm.controls.pricing = this.initPricing();
      this.pricingRuleForm.controls.resource.setValue(newSelectedResource.id);
      if (newResourceIsMetricBased) {
        // It is certain that we'll not use time unit for metric based rules. However, user can activate per time unit
        // and when doing so, the control is re-added in changedCalculateMetrics method
        this.pricingRuleForm.removeControl('time_unit');
      } else {
        if (!this.pricingRuleForm.controls.time_unit) {
          this.pricingRuleForm.addControl('time_unit', new UntypedFormControl('', Validators.required));
        }
      }
    }
    if (newSelectedResource.internal_name === 'instance_ip') {
      this.pricingRuleForm.controls.attribute.setValue(' '); // instance IPs has only the "existence" attribute
      if (!this.pricingRuleForm.contains('free_resources_for_related_resource')) {
        this.pricingRuleForm.addControl('free_resources_for_related_resource', this.formBuilder.control(0));
      }
    } else {
      if (this.pricingRuleForm.contains('free_resources_for_related_resource')) {
        this.pricingRuleForm.removeControl('free_resources_for_related_resource');
      }
    }
  }

  public filterAttributeChanged(index) {
    // reset the operator and value after attribute changed in filter
    const filters = this.pricingRuleForm.get('conditions') as UntypedFormArray;
    const selectedFilter = filters.at(index);
    const oldAttribute = selectedFilter.value.attribute;
    filters.removeAt(index);
    filters.insert(index, this.formBuilder.group({
      attribute: [oldAttribute, Validators.required],
      operator: ['', Validators.required],
      value: ['', Validators.required],
      attribute_unit: [null],
    }));
    this.conditionsChipsObservables[index] = null;
  }

  public modifierAttributeChanged(index) {
    // reset the operator and value after attribute changed in modifier
    const modifiers = this.pricingRuleForm.get('modifiers') as UntypedFormArray;
    const selectedFilter = modifiers.at(index);
    const oldAttribute = selectedFilter.value.attribute;
    const oldName = selectedFilter.value.name;
    const oldPrice = selectedFilter.value.price;
    const oldPriceAsPercent = selectedFilter.value.price_is_percent;
    const oldTimeUnit = selectedFilter.value.time_unit;
    modifiers.removeAt(index);
    const controlsConfig = {
      attribute: [oldAttribute, Validators.required],
      name: [oldName, Validators.required],
      operator: ['', Validators.required],
      value: ['', Validators.required],
      attribute_unit: [null],
      price: [oldPrice, [Validators.required, ValidatorsHelper.requiredNumericValueValidator]],
      price_is_percent: [oldPriceAsPercent, Validators.required],
    } as any;
    if (!oldPriceAsPercent) {
      controlsConfig.time_unit = [oldTimeUnit, Validators.required];
    }
    modifiers.insert(index, this.formBuilder.group(controlsConfig));
    this.modifiersChipsObservables[index] = null;
  }

  public hasPricingAttribute() {
    if (this.selectedResource && this.selectedResource.type === 'service') {
      for (const attribute of this.selectedResource.attributes) {
        if (attribute.type === 'integer') {
          return true;
        }
      }
      return false;
    }
    return false;
  }

  public canSelectUnitForAttribute() {
    if (this.selectedResource) {
      for (const attribute of this.selectedResource.attributes) {
        if (attribute.value_size && attribute.name === this.pricingRuleForm.controls.attribute.value) {
          if (['k', 'm', 'g', 't', 'b', 'p', 'e'].indexOf(attribute.value_size)) {
            return this.pricingRuleForm.controls.attribute_unit.enable();
          }
        }
      }
      this.pricingRuleForm.controls.attribute_unit.setValue('');
      return this.pricingRuleForm.controls.attribute_unit.disable();
    }
    this.pricingRuleForm.controls.attribute_unit.setValue('');
    return this.pricingRuleForm.controls.attribute_unit.disable();
  }

  public getMetricHelpText(): string | null {
    if (this.selectedResource && this.selectedResource.metrics) {
      for (const metric of this.selectedResource.metrics) {
        if (metric.name === this.pricingRuleForm.controls.attribute.value) {
          return metric.help_text;
        }
      }
      return null;
    }
    return null;
  }

  public getMetricPerSecondBillingAvailability(compareWithSelectedMetric = true): boolean {
    let isSelectedMetric = false;
    if (this.selectedResource && this.selectedResource.metrics) {
      for (const metric of this.selectedResource.metrics) {
        if (compareWithSelectedMetric) {
          if (metric.name === this.pricingRuleForm.controls.attribute.value) {
            isSelectedMetric = true;
          }
        }
        if ((!compareWithSelectedMetric || isSelectedMetric) && (metric.reaggregation &&
          (['mean', 'max', 'min'].indexOf(metric.reaggregation) > -1 || metric.billing_based_on_existence))) {
          return true;
        }
      }
      return false;
    }
    return false;
  }

  public addTier(event: PointerEvent) {
    event.preventDefault();
    const prices = this.pricingRuleForm.get('pricing').get('prices') as UntypedFormArray;
    prices.push(this.createTier());
    this.reCalculateTiers();
  }

  public addFilter() {
    const filters = this.pricingRuleForm.get('conditions') as UntypedFormArray;
    return filters.push(this.createFilter());
  }

  public addModifier() {
    const modifiers = this.pricingRuleForm.get('modifiers') as UntypedFormArray;
    return modifiers.push(this.createModifier());
  }

  public removeTier(index) {
    const prices = this.pricingRuleForm.get('pricing').get('prices') as UntypedFormArray;
    prices.removeAt(index);
    this.reCalculateTiers();
  }

  public removeFilter(index) {
    const filters = this.pricingRuleForm.get('conditions') as UntypedFormArray;
    return filters.removeAt(index);
  }

  public removeModifier(index) {
    const modifiers = this.pricingRuleForm.get('modifiers') as UntypedFormArray;
    return modifiers.removeAt(index);
  }

  public reCalculateTiers() {
    // if a from field changed, re-calculate all tiers to be consecutive and have only one infinite
    const prices = this.pricingRuleForm.get('pricing').get('prices') as UntypedFormArray;
    // eslint-disable-next-line @typescript-eslint/prefer-for-of
    for (let tierToManipulateIndex = 0; tierToManipulateIndex < prices.length; tierToManipulateIndex++) {
      // eslint-disable-next-line @typescript-eslint/prefer-for-of
      const tierChanged = prices.at(tierToManipulateIndex).value as { t: any; p: any; f: any };
      let nextMaxValue = 0;
      let toField = null;
      for (let tierToCompareWithIndex = 0; tierToCompareWithIndex < prices.length; tierToCompareWithIndex++) {
        if (prices.at(tierToCompareWithIndex).value.f > tierChanged.f &&
          (nextMaxValue > prices.at(tierToCompareWithIndex).value.f || nextMaxValue === 0)) {
          nextMaxValue = prices.at(tierToCompareWithIndex).value.f;
        }
      }
      if (nextMaxValue === 0) {
        toField = '∞';
      } else {
        toField = nextMaxValue;
      }

      const tierToUpdate = prices.at(tierToManipulateIndex) as FormGroup;
      tierToUpdate.controls.t.setValue(toField);
      const validators = [];
      if (this.pricingRuleForm.controls.billed_value_increment.value > 1) {
        validators.push(ValidatorsHelper.stepValueValidator(this.pricingRuleForm.controls.billed_value_increment.value));
      }
      tierToUpdate.controls.f.setValidators(validators);
      tierToUpdate.controls.f.updateValueAndValidity();
      tierToUpdate.controls.t.setValidators(validators);
      tierToUpdate.controls.t.updateValueAndValidity();
    }
  }

  public attrByName(attributeName) {
    for (const attribute of this.selectedResource.attributes) {
      if (attribute.name === attributeName) {
        return attribute;
      }
    }
    return null;
  }

  public getFilterValueInputType(attributeName): string | null {
    const attribute = this.attrByName(attributeName);
    if (attribute) {
      if (attribute.choices_dict) {
        return 'choices';
      } else if (attribute.type === 'string') {
        return 'textarea';
      } else if (attribute.type === 'integer') {
        return 'number';
      } else if (attribute.type === 'datetime') {
        return 'datetime';
      }
      return null;
    }
    return null;
  }

  public showAttributeUnitForFiltersModifiers(attributeName): boolean {
    const attribute = this.attrByName(attributeName);
    if (attribute) {
      return (!attribute.choices && attribute.type === 'integer' &&
        ['k', 'm', 'g', 't', 'b', 'p', 'e'].indexOf(attribute.value_size) > -1);
    }
    return false;
  }

  public getOperatorsForAttribute(selectedAttributeName): Record<any, any> {
    const attribute = this.attrByName(selectedAttributeName);
    if (attribute) {
      if (attribute.choices) {
        return this.createOptions.string_operators;
      } else if (attribute.type === 'string') {
        return this.createOptions.string_operators;
      } else if (attribute.type === 'integer' || attribute.type === 'datetime') {
        return this.createOptions.number_operators;
      } else {
        return {};
      }
    }
    return {};
  }

  getConditionChipsFormControl(index): FormControl {
    if (this.conditionsChipsControls.hasOwnProperty(index)) {
      return this.conditionsChipsControls[index];
    } else {
      const newControl = new FormControl();
      this.conditionsChipsControls[index] = newControl;
      return newControl;
    }
  }

  getModifierChipsFormControl(index): FormControl {
    if (this.modifiersChipsControls.hasOwnProperty(index)) {
      return this.modifiersChipsControls[index];
    } else {
      const newControl = new FormControl();
      this.modifiersChipsControls[index] = newControl;
      return newControl;
    }
  }

  getObjKeys(dict) {
    return Object.keys(dict);
  }

  filterOperatorChanged(event, index) {
    const condition = this.conditions.controls[index] as UntypedFormGroup;
    if (this.isListOperator(event.value)) {
      condition.controls.value.setValue([]);
    } else {
      condition.controls.value.setValue('');
    }
  }

  modifierOperatorChanged(event, index) {
    const modifier = this.modifiers.controls[index] as UntypedFormGroup;
    if (this.isListOperator(event.value)) {
      modifier.controls.value.setValue([]);
    } else {
      modifier.controls.value.setValue('');
    }
  }

  private _filter(value: string, values, index, conditionsOrModifiers): string[] {
    if (!value) {
      return values.filter((choice) => Array.isArray(conditionsOrModifiers.controls[index].value?.value) && !conditionsOrModifiers.controls[index].value.value.includes(choice));
    }
    const filterValue = value.toLowerCase();
    return values.filter((choice) => (
      choice.toLowerCase().includes(filterValue) ||
      this.attrByName(
        conditionsOrModifiers.controls[index].value.attribute
      ).choices_dict[choice]?.display?.toLowerCase()?.includes(filterValue)
    ) && (Array.isArray(conditionsOrModifiers.controls[index].value?.value) && !conditionsOrModifiers.controls[index].value.value.includes(choice)));
  }

  getConditionChoices(index) {
    const values = this.getObjKeys(this.attrByName(this.conditions.controls[index].value.attribute).choices_dict);
    if (this.conditionsChipsObservables[index]) {
      return this.conditionsChipsObservables[index];
    } else {
      const obs = this.getConditionChipsFormControl(index).valueChanges.pipe(
        startWith(null),
        map((val: string | null) => this._filter(val, values, index, this.conditions)),
      );
      this.conditionsChipsObservables[index] = obs;
      return obs;
    }
  }

  getModifierChoices(index) {
    const values = this.getObjKeys(this.attrByName(this.modifiers.controls[index].value.attribute).choices_dict);
    if (this.modifiersChipsObservables[index]) {
      return this.modifiersChipsObservables[index];
    } else {
      const obs = this.getModifierChipsFormControl(index).valueChanges.pipe(
        startWith(null),
        map((val: string | null) => this._filter(val, values, index, this.modifiers)),
      );
      this.modifiersChipsObservables[index] = obs;
      return obs;
    }
  }

  getFilterChipTooltip(condition, index) {
    let tooltip = '';
    const choices = this.attrByName(
      this.conditions['controls'][index].value.attribute
    ).choices_dict;
    if (choices) {
      tooltip = choices[condition]?.display || '';
    } else {
      tooltip = condition;
    }
    if (choices[condition]?.sub) {
      tooltip += `\n${choices[condition]?.sub}`;
    }
    return tooltip;
  }

  getModifierChipTooltip(modifier, index) {
    let tooltip = '';
    const choices = this.attrByName(
      this.modifiers['controls'][index].value.attribute
    ).choices_dict;
    if (choices) {
      tooltip = choices[modifier]?.display || '';
    } else {
      tooltip = modifier;
    }
    if (choices[modifier]?.sub) {
      tooltip += `\n${choices[modifier]?.sub}`;
    }
    return tooltip;
  }

  selectedFilterChip(event, index, chipInput) {
    const condition = this.conditions.controls[index] as UntypedFormGroup;
    const conditionVal = condition.value;
    conditionVal.value.push(event.option.value);
    condition.controls.value.setValue(conditionVal.value);
    if (chipInput) {
      chipInput.value = '';
    }
    this.getConditionChipsFormControl(index).setValue(null);
  }

  selectedModifierChip(event, index, chipInput) {
    const modifier = this.modifiers.controls[index] as UntypedFormGroup;
    const modifierVal = modifier.value;
    modifierVal.value.push(event.option.value);
    modifier.controls.value.setValue(modifierVal.value);
    if (chipInput) {
      chipInput.value = '';
    }
    this.getModifierChipsFormControl(index).setValue(null);
  }

  addFilterChip(event, index) {
    const condition = this.conditions.controls[index] as UntypedFormGroup;
    const value = (event.value || '').trim();

    const conditionVal = condition.value;
    if (value) {
      conditionVal.value.push(value);
      condition.controls.value.setValue(conditionVal.value);
    }

    if (event.chipInput) {
      event.chipInput.clear();
    }
    this.getConditionChipsFormControl(index).setValue(null);
  }

  addFilterChipOnFocusOut(chipInput, index) {
    const choices = this.attrByName(this.conditions.controls[index].value.attribute).choices_dict;
    if (choices) {
      return;
    }
    const condition = this.conditions.controls[index] as UntypedFormGroup;
    const value = (chipInput.value || '').trim();

    const conditionVal = condition.value;
    if (value) {
      conditionVal.value.push(value);
      condition.controls.value.setValue(conditionVal.value);
    }

    chipInput.value = null;
    this.getConditionChipsFormControl(index).setValue(null);
  }

  addModifierChip(event, index) {
    const modifier = this.modifiers.controls[index] as UntypedFormGroup;
    const value = (event.value || '').trim();

    const modifierVal = modifier.value;
    if (value) {
      modifierVal.value.push(value);
      modifier.controls.value.setValue(modifierVal.value);
    }

    if (event.chipInput) {
      event.chipInput.clear();
    }
    this.getModifierChipsFormControl(index).setValue(null);
  }

  addModifierChipOnFocusOut(chipInput, index) {
    const choices = this.attrByName(this.modifiers.controls[index].value.attribute).choices_dict;
    if (choices) {
      return;
    }
    const modifier = this.modifiers.controls[index] as UntypedFormGroup;
    const value = (chipInput.value || '').trim();

    const modifierVal = modifier.value;
    if (value) {
      modifierVal.value.push(value);
      modifier.controls.value.setValue(modifierVal.value);
    }

    chipInput.value = null;
    this.getModifierChipsFormControl(index).setValue(null);
  }

  removeFilterChip(value: string, index) {
    const condition = this.conditions.controls[index];
    const conditionVal = condition.value;
    const valIndex = conditionVal.value.indexOf(value);

    if (valIndex >= 0) {
      conditionVal.value.splice(valIndex, 1);
    }
    AutocompleteHelper.refreshAutocompleteOptions(this.getConditionChipsFormControl(index));
  }

  removeModifierChip(value: string, index) {
    const modifier = this.modifiers.controls[index];
    const modifierVal = modifier.value;
    const valIndex = modifierVal.value.indexOf(value);

    if (valIndex >= 0) {
      modifierVal.value.splice(valIndex, 1);
    }
    AutocompleteHelper.refreshAutocompleteOptions(this.getModifierChipsFormControl(index));
  }

}
