
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import path from 'ramda/src/path';
import flatten from 'ramda/src/flatten';
import uniq from 'ramda/src/uniq';
// import mergeDeepRight from 'ramda/src/mergeDeepRight';
// import assocPath from 'ramda/src/assocPath';
import {
    FormGroup,
    FormControl,
    FormBuilder,
    FormArray,
    AbstractControl
 } from "react-reactive-form";

type ErrorType = 'required' | 'email' | 'min' | 'max';

export class BigFormService {
  bigForm: FormGroup;
  private _errorMessages = {};
  bigFormErrors = {};
  constructor(private store) {
    this.bigForm = new FormGroup({});
    this.initForm();
    // if (!environment.production) {
    //   this.bigForm.valueChanges.subscribe(form => console.log({ BIGFORM: form, FormModel: this.bigForm }));
    // }
  }

  initForm() {
    if (!this.bigForm) {
      this.bigForm = new FormGroup({});
    }
  }

  get errorMessages() {
    return this._errorMessages;
  }

  addErrorMessage(controlName: string, errorType: ErrorType, message: string) {
    this._errorMessages[`${controlName}:${errorType}`] = message;
  }

  addSimpleValueToFormModel(controlName: string, value: any) {
    if (!this.bigForm.get(controlName)) {
      this.bigForm.addControl(controlName, new FormControl(value));
    }
  }

  addObjectToFormModel(controlName: string, obj: any) {
    if (!this.bigForm.get(controlName)) {
      if (obj) {
        this.bigForm.addControl(controlName, FormBuilder.group(obj));
      }
    }
  }

  patchToForm(sourceObservable: Observable<any>, fieldMaps: { [key: string]: string }) {
    return sourceObservable.pipe(
      map(sourceObj => {
        Object.entries(fieldMaps).forEach(([key, value]) => {
          if (key && value) {
            let control = this.getControl(value);
            if (control) {
              control.setValue(this.getSourceValue(sourceObj, key));
            } else {
              this.setControl(value);
              control = this.getControl(value);
              control.setValue(this.getSourceValue(sourceObj, key));
            }
          }
        });
      })
    );
  }

  getSourceValue(source, fieldPath: string) {
    const thePath = fieldPath.split('.');
    return path(thePath, source) || '';
  }

  patchValues(formValues: { [key: string]: any }) {
    Object.entries(formValues).forEach(([key, value]) => {
      if (!this.bigForm.get(key)) {
        if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
          this.bigForm.addControl(key, FormBuilder.group(value));
        } else if (Array.isArray(value)) {
          if (value.some(val => typeof val === 'function' || Array.isArray(val))) {
            // Then it contains a validation function
            this.bigForm.addControl(key, FormBuilder.control(value[0], value[1]));
          } else {
            this.bigForm.addControl(key, FormBuilder.control(value[0]));
          }
        } else {
          this.bigForm.addControl(key, FormBuilder.control(value));
        }
      } else {
        this.bigForm.patchValue({ [key]: value });
      }
    });
  }

  addControls(formValues: { [key: string]: any }) {
    Object.entries(formValues).forEach(([key, value]) => {
      if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
        this.bigForm.addControl(key, FormBuilder.group(value));
      } else {
        this.bigForm.addControl(key, FormBuilder.control(value));
      }
    });
  }

  addControl(controlName: string, control: AbstractControl) {
    if (!this.bigForm.get(controlName)) {
      this.bigForm.addControl(controlName, control);
    }
  }

  getControl(controlPath: string) {
    this.initForm();
    const path = controlPath.split('.');
    let control = this.bigForm.controls[path[0]];

    if (!control) {
      this.setControl(controlPath);
      control = this.bigForm.controls[path[0]];
    }
    if (path.length > 1) {
      for (let i = 1; i < path.length; i++) {
        control = control.get(path[i]);
      }
    }
    return control;
  }

  getControlForValidation(controlPath: string) {
    this.initForm();
    const path = controlPath.split('.');
    let control = this.bigForm.controls[path[0]];

    // if (!control) {
    //   this.setControl(controlPath);
    //   control = this.bigForm.controls[path[0]];
    // }
    if (path.length > 1) {
      for (let i = 1; i < path.length; i++) {
        control = control.get(path[i]);
      }
    }
    return control;
  }

  setControl(controlPath: string) {
    const path = controlPath.split('.');
    if (path.length === 1) {
      this.bigForm.addControl(path[0], new FormControl(null));
    } else if (path.length === 2) {
      if (this.bigForm.get(path[0])) {
        (this.bigForm.get(path[0]) as FormGroup).addControl(path[1], new FormControl(null));
      } else {
        this.bigForm.addControl(path[0], new FormGroup({ [path[1]]: new FormControl(null) }));
      }
    }
  }

  // bigFormToStoreMapper(mapper: { [id: string]: string | any[] }) {
  //   return this.bigForm.valueChanges.pipe(
  //     debounceTime(800),
  //     map(formValues => {
  //       return Object.entries(mapper).reduce((acc, [formPath, storePath]) => {
  //         const valueExists =
  //           path(formPath.split('.'), formValues) !== undefined && path(formPath.split('.'), formValues) !== null;
  //         if (typeof storePath === 'string') {
  //           return valueExists
  //             ? mergeDeepRight(acc, assocPath(storePath.split('.'), path(formPath.split('.'), formValues), {}))
  //             : acc;
  //         } else if (Array.isArray(storePath) && typeof storePath[0] === 'string') {
  //           return storePath.reduce((innerAcc, innerStorePath) => {
  //             const innerObj = valueExists
  //               ? mergeDeepRight(
  //                   innerAcc,
  //                   assocPath(innerStorePath.split('.'), path(formPath.split('.'), formValues), {})
  //                 )
  //               : innerAcc;
  //             return mergeDeepRight(acc, innerObj);
  //           }, {});
  //         } else if (Array.isArray(storePath) && typeof storePath[0] === 'function') {
  //           const [func, pathToStore] = storePath;
  //           const transformed = func(path(formPath.split('.'), formValues));
  //           return valueExists ? mergeDeepRight(acc, assocPath(pathToStore.split('.'), transformed, {})) : acc;
  //         } else if (Array.isArray(storePath) && Array.isArray(storePath[0])) {
  //           return storePath.reduce((inner1, funcToPathArray) => {
  //             const [func, pathToStore] = funcToPathArray;
  //             const transformed = func(
  //               path(formPath.split('.'), formValues));
  //             const innerObj = valueExists
  //               ? mergeDeepRight(inner1, assocPath(pathToStore.split('.'), transformed, {}))
  //               : inner1;
  //             // const innerObj1 = valueExists
  //             //   ? R.mergeDeepRight(inner1, R.assocPath(pathToStore.split('.'), R.path(formPath.split('.'), formValues), {}))
  //             //   : inner1;
  //             return mergeDeepRight(acc, innerObj);
  //           }, {});
  //         }
  //       }, {});
  //     })
  //   );
  // }

  // allValid() {
  //   this.bigForm.updateValueAndValidity();
  //   return this.bigForm.valueChanges.pipe(map(val => this.bigForm.valid));
  // }

  // fieldsValid(fieldPaths: string[]) {
  //   const watchedControls = fieldPaths.map(field => this.getControl(field));

  //   return combineLatest(
  //     ...watchedControls.map(control => {
  //       control.updateValueAndValidity();
  //       return control.valueChanges.pipe(map(val => control.valid));
  //     })
  //   ).pipe(map(res => res.every(val => val === true)));
  // }

  getFormGroupErrors(form: FormGroup | FormArray) {
    if (Array.isArray(form.controls)) {
      return form.controls
        .map((ctrl, key) => {
          const control = form.get(`${key}`);
          if (control instanceof FormControl) {
            const controlErrors = control.errors;
            if (controlErrors) {
              return Object.keys(controlErrors).map(keyError => {
                return `${key}:${keyError}`;
              });
            }
          } else if (control instanceof FormGroup || control instanceof FormArray) {
            return this.getFormGroupErrors(control);
          }
        })
        .filter(val => !!val)
        .map(data => flatten(data));
    } else {
      return Object.keys(form.controls)
        .map(key => {
          const control = form.get(key);
          if (control instanceof FormControl) {
            const controlErrors = control.errors;
            if (controlErrors) {
              return Object.keys(controlErrors).map(keyError => {
                return `${key}:${keyError}`;
              });
            }
          } else if (control instanceof FormGroup || control instanceof FormArray) {
            return this.getFormGroupErrors(control);
          }
        })
        .filter(val => !!val)
        .map(data => flatten(data));
    }
  }

  getErrorMessagesForDisplay(errorMessages: { [key: string]: string }) {
    const formGroupErrors = flatten(this.getFormGroupErrors(this.bigForm));
    const errors = formGroupErrors.map(key => {
      return key && errorMessages && errorMessages[key];
    });
    return uniq(flatten(errors.filter(val => !!val)));
  }

  retrieveErrors$() {
    // return this.store.query('manifest.activeManifestItem').pipe(
    //   take(1),
    //   map(item => item && item.flowErrorMessages),
    //   map(errorMessages => {
    //     return this.getErrorMessagesForDisplay(errorMessages);
    //   })
    // );
  }

}
