import { AfterViewInit, ChangeDetectorRef, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { UntypedFormBuilder, Validators } from '@angular/forms';
import { CustomFieldsHelper } from '../../ui-api/helpers/custom-fields-helper';
import { ICustomFieldModel } from '@fleio-api/custom-fields/model/custom-field.model';

@Component({
  selector: 'app-custom-fields',
  templateUrl: './custom-fields.component.html',
  styleUrls: ['./custom-fields.component.scss']
})
export class CustomFieldsComponent implements OnInit, AfterViewInit, OnChanges {
  @Input() customFieldsDefinition: { [name: string]: ICustomFieldModel } = {};
  @Input() expandCategory: string = null;
  @Input() customFieldsName = 'custom_fields';
  @Input() formModel = {};
  @Input() liveForm = {};
  customFieldsForm = this.formBuilder.group({
    // form used for custom fields controls
  });
  customFieldsByCategory = {};
  customFields = {};
  fieldCategories: Array<{
    name: string;
    collapsed: boolean;
    fields: ICustomFieldModel[];
  }>;
  customFieldsToSubmit = []; // array that will store the data that has to be submitted externally
  constructor(
    private formBuilder: UntypedFormBuilder,
    private changeDetector: ChangeDetectorRef,
  ) {
  }

  ngOnInit(): void {
    this.customFields = this.initCustomFieldsDefinition(
      this.customFields, this.customFieldsByCategory, this.customFieldsDefinition
    );
    this.fieldCategories = this.getCategories();
    for (const fieldName of Object.keys(this.customFieldsDefinition)) {
      if (this.customFieldsForm.controls.hasOwnProperty(fieldName)) {
        this.customFieldsForm.controls[fieldName].valueChanges.subscribe(value => {
          let fieldUpdated = false;
          for (const editedField of this.customFieldsToSubmit) {
            if (editedField.name === fieldName) {
              fieldUpdated = true;
              editedField.value = value;
              break;
            }
          }
          if (!fieldUpdated) {
            this.customFieldsToSubmit.push({
              name: fieldName,
              value
            });
          }
          this.updateCustomFieldsDefinition();
        });
      }
    }
  }

  ngAfterViewInit() {
    this.changeDetector.detectChanges();
  }

  checkCustomFieldsForm() {
    for (const fieldName of Object.keys(this.customFieldsDefinition)) {
      if (this.customFields.hasOwnProperty(fieldName)) {
        if (this.customFields[fieldName].required) {
          if (this.customFieldsForm.contains(fieldName)) {
            const control = this.customFieldsForm.controls[fieldName];
            if (control.invalid) {
              control.markAsTouched();
              return false;
            }
          }
        }
      }
    }
    return true;
  }

  setBackendErrors(errors, removeErrors: boolean = true, errorsKey: string = 'custom_fields') {
    if (typeof errors === 'string') {
      return;
    }

    if (!(errorsKey in errors)) {
      return;
    }

    const customFieldErrors = errors[errorsKey];
    if (removeErrors) {
      delete errors[errorsKey];
    }

    const backendErrors = {};
    for (const error of customFieldErrors) {
      Object.assign(backendErrors, error);
    }

    for (const fieldName of Object.keys(backendErrors)) {
      this.customFieldsForm.controls[fieldName]?.setErrors({
        backend: true,
        fleio_error: backendErrors[fieldName]
      });
    }
  }

  updateCustomFieldsDefinition() {
    if (typeof this.customFieldsDefinition === 'object' && this.customFieldsDefinition !== null) {
      let formModel = Object.assign({}, this.formModel)
      for (const customFieldName of Object.keys(this.customFieldsDefinition)) {
        for (const field of this.customFieldsToSubmit) {
          if (customFieldName === field.name) {
            formModel[field.name] = field.value;
            break;
          }
        }
      }
      for (const customFieldName of Object.keys(this.customFieldsDefinition)) {
        const fieldDefinition = this.customFieldsDefinition[customFieldName];
        if (this.customFields.hasOwnProperty(customFieldName)) {
          this.customFields[customFieldName].required = CustomFieldsHelper.checkRule(
            fieldDefinition.required,
            formModel
          );
          this.customFields[customFieldName].optional = CustomFieldsHelper.checkRule(
            fieldDefinition.optional,
            formModel
          );
          this.customFields[customFieldName].visible = (this.customFields[customFieldName].required ||
            this.customFields[customFieldName].optional);
        }
      }
      this.changeDetector.detectChanges();
    }
  }

  toggleCategory(index) {
    this.fieldCategories[index].collapsed = !this.fieldCategories[index].collapsed;
  };

  getModelFieldByName(name: string) {
    if (name && this.formModel && Array.isArray(this.formModel[this.customFieldsName])) {
      for (const field of this.formModel[this.customFieldsName]) {
        if (field.name === name) {
          return field;
        }
      }
    }
    return null;
  };

  getFieldValue(fieldName) {
    const modelField = this.getModelFieldByName(fieldName);
    if (modelField) {
      // init custom fields that are used externally
      this.customFieldsToSubmit.push({
        name: modelField.name,
        value: modelField.value
      });
    }
    return modelField ? modelField.value : undefined;
  }

  initCustomFieldsDefinition(customFields, customFieldsByCategory, definitions: { [name: string]: ICustomFieldModel }) {
    if (typeof definitions === 'object' && definitions !== null) {
      for (const fieldName of Object.keys(definitions)) {
        const fieldDefinition = definitions[fieldName];
        const field: ICustomFieldModel = {
          name: fieldName,
          required: CustomFieldsHelper.checkRule(fieldDefinition.required, this.formModel),
          optional: CustomFieldsHelper.checkRule(fieldDefinition.optional, this.formModel),
          label: fieldDefinition.label,
          choices: fieldDefinition.choices,
          type: fieldDefinition.type,
          value: this.getFieldValue(fieldName)
        };
        customFields[fieldName] = field;
        field.visible = (field.required || field.optional);
        // && !customFields[fieldName].defined
        if (['text', 'textarea', 'password', 'email', 'int', 'decimal'].indexOf(
          fieldDefinition.type
        ) !== -1) {
          field.fieldType = 'input';
        } else {
          field.fieldType = fieldDefinition.type;
        }

        const category = fieldDefinition.category;
        if (!customFieldsByCategory.hasOwnProperty(category)) {
          customFieldsByCategory[category] = [];
        }
        customFieldsByCategory[category].push(field);

        const validators = [];
        if (fieldDefinition.required) {
          validators.push(Validators.required);
        }
        this.customFieldsForm.addControl(
          fieldName,
          this.formBuilder.control([null, validators])
        );
        this.customFieldsForm.controls[fieldName].setValue(field.value);
      }
    }
    return customFields;
  };

  getCategories() {
    const categoryNames = Object.keys(this.customFieldsByCategory).sort();
    const categories = [];
    for (const categoryName of categoryNames) {
      let collapsed;
      if (!this.expandCategory) {
        collapsed = true;
      }
      collapsed = !categoryName.split(',').includes(this.expandCategory);
      if (collapsed) {
        categories.push({
          name: categoryName,
          collapsed,
          fields: this.customFieldsByCategory[categoryName]
        });
      } else {
        categories.unshift({
          name: categoryName,
          collapsed,
          fields: this.customFieldsByCategory[categoryName]
        });
      }
    }
    return categories;
  };

  ngOnChanges(changes: SimpleChanges) {
    if (changes.liveForm && changes.liveForm.currentValue) {
      Object.assign(this.formModel, changes.liveForm.currentValue);
    }
    this.updateCustomFieldsDefinition();
  }
}
