import { Injectable } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

import dayjs from 'dayjs/esm';
import { DATE_TIME_FORMAT } from 'app/config/input.constants';
import { IInputData, NewInputData } from '../input-data.model';

/**
 * A partial Type with required key is used as form input.
 */
type PartialWithRequiredKeyOf<T extends { id: unknown }> = Partial<Omit<T, 'id'>> & { id: T['id'] };

/**
 * Type for createFormGroup and resetForm argument.
 * It accepts IInputData for edit and NewInputDataFormGroupInput for create.
 */
type InputDataFormGroupInput = IInputData | PartialWithRequiredKeyOf<NewInputData>;

/**
 * Type that converts some properties for forms.
 */
type FormValueOf<T extends IInputData | NewInputData> = Omit<T, 'changeDate' | 'creationDate'> & {
  changeDate?: string | null;
  creationDate?: string | null;
};

type InputDataFormRawValue = FormValueOf<IInputData>;

type NewInputDataFormRawValue = FormValueOf<NewInputData>;

type InputDataFormDefaults = Pick<NewInputData, 'id' | 'changeDate' | 'creationDate'>;

type InputDataFormGroupContent = {
  id: FormControl<InputDataFormRawValue['id'] | NewInputData['id']>;
  sourceValue: FormControl<InputDataFormRawValue['sourceValue']>;
  variableValue: FormControl<InputDataFormRawValue['variableValue']>;
  imputedValue: FormControl<InputDataFormRawValue['imputedValue']>;
  stringValue: FormControl<InputDataFormRawValue['stringValue']>;
  sourceType: FormControl<InputDataFormRawValue['sourceType']>;
  dataDate: FormControl<InputDataFormRawValue['dataDate']>;
  changeDate: FormControl<InputDataFormRawValue['changeDate']>;
  changeUsername: FormControl<InputDataFormRawValue['changeUsername']>;
  dataSource: FormControl<InputDataFormRawValue['dataSource']>;
  dataUser: FormControl<InputDataFormRawValue['dataUser']>;
  dataComments: FormControl<InputDataFormRawValue['dataComments']>;
  creationDate: FormControl<InputDataFormRawValue['creationDate']>;
  creationUsername: FormControl<InputDataFormRawValue['creationUsername']>;
  variableClassCode: FormControl<InputDataFormRawValue['variableClassCode']>;
  variableClassName: FormControl<InputDataFormRawValue['variableClassName']>;
  version: FormControl<InputDataFormRawValue['version']>;
  variable: FormControl<InputDataFormRawValue['variable']>;
  period: FormControl<InputDataFormRawValue['period']>;
  sourceUnit: FormControl<InputDataFormRawValue['sourceUnit']>;
  unit: FormControl<InputDataFormRawValue['unit']>;
  owner: FormControl<InputDataFormRawValue['owner']>;
  sourceInputData: FormControl<InputDataFormRawValue['sourceInputData']>;
  sourceInputDataUpload: FormControl<InputDataFormRawValue['sourceInputDataUpload']>;
};

export type InputDataFormGroup = FormGroup<InputDataFormGroupContent>;

@Injectable({ providedIn: 'root' })
export class InputDataFormService {
  createInputDataFormGroup(inputData: InputDataFormGroupInput = { id: null }): InputDataFormGroup {
    const inputDataRawValue = this.convertInputDataToInputDataRawValue({
      ...this.getFormDefaults(),
      ...inputData,
    });
    return new FormGroup<InputDataFormGroupContent>({
      id: new FormControl(
        { value: inputDataRawValue.id, disabled: true },
        {
          nonNullable: true,
          validators: [Validators.required],
        },
      ),
      period: new FormControl(inputDataRawValue.period, {
        validators: [Validators.required],
      }),
      owner: new FormControl(inputDataRawValue.owner, {
        validators: [Validators.required],
      }),
      variable: new FormControl(inputDataRawValue.variable, {
        validators: [Validators.required],
      }),
      sourceUnit: new FormControl(inputDataRawValue.sourceUnit, {
        validators: [Validators.required],
      }),
      unit: new FormControl(inputDataRawValue.unit),
      sourceValue: new FormControl(inputDataRawValue.sourceValue, {
        validators: [Validators.required],
      }),
      variableValue: new FormControl(inputDataRawValue.variableValue, {
        validators: [Validators.required],
      }),
      imputedValue: new FormControl(inputDataRawValue.imputedValue, {
        validators: [Validators.required],
      }),
      sourceType: new FormControl(inputDataRawValue.sourceType, {
        validators: [Validators.required],
      }),
      stringValue: new FormControl(inputDataRawValue.stringValue),
      dataDate: new FormControl(inputDataRawValue.dataDate),
      changeDate: new FormControl(inputDataRawValue.changeDate, {
        validators: [Validators.required],
      }),
      changeUsername: new FormControl(inputDataRawValue.changeUsername, {
        validators: [Validators.required],
      }),
      dataSource: new FormControl(inputDataRawValue.dataSource),
      dataUser: new FormControl(inputDataRawValue.dataUser),
      dataComments: new FormControl(inputDataRawValue.dataComments),
      creationDate: new FormControl(inputDataRawValue.creationDate, {
        validators: [Validators.required],
      }),
      creationUsername: new FormControl(inputDataRawValue.creationUsername, {
        validators: [Validators.required],
      }),
      variableClassCode: new FormControl(inputDataRawValue.variableClassCode),
      variableClassName: new FormControl(inputDataRawValue.variableClassName),
      version: new FormControl(inputDataRawValue.version),
      sourceInputData: new FormControl(inputDataRawValue.sourceInputData),
      sourceInputDataUpload: new FormControl(inputDataRawValue.sourceInputDataUpload),
    });
  }

  getInputData(form: InputDataFormGroup): IInputData | NewInputData {
    return this.convertInputDataRawValueToInputData(form.getRawValue() as InputDataFormRawValue | NewInputDataFormRawValue);
  }

  resetForm(form: InputDataFormGroup, inputData: InputDataFormGroupInput): void {
    const inputDataRawValue = this.convertInputDataToInputDataRawValue({ ...this.getFormDefaults(), ...inputData });
    form.reset(
      {
        ...inputDataRawValue,
        id: { value: inputDataRawValue.id, disabled: true },
      } as any /* cast to workaround https://github.com/angular/angular/issues/46458 */,
    );
  }

  private getFormDefaults(): InputDataFormDefaults {
    const currentTime = dayjs();

    return {
      id: null,
      changeDate: currentTime,
      creationDate: currentTime,
    };
  }

  private convertInputDataRawValueToInputData(rawInputData: InputDataFormRawValue | NewInputDataFormRawValue): IInputData | NewInputData {
    return {
      ...rawInputData,
      changeDate: dayjs(rawInputData.changeDate, DATE_TIME_FORMAT),
      creationDate: dayjs(rawInputData.creationDate, DATE_TIME_FORMAT),
    };
  }

  private convertInputDataToInputDataRawValue(
    inputData: IInputData | (Partial<NewInputData> & InputDataFormDefaults),
  ): InputDataFormRawValue | PartialWithRequiredKeyOf<NewInputDataFormRawValue> {
    return {
      ...inputData,
      changeDate: inputData.changeDate ? inputData.changeDate.format(DATE_TIME_FORMAT) : undefined,
      creationDate: inputData.creationDate ? inputData.creationDate.format(DATE_TIME_FORMAT) : undefined,
    };
  }
}
