import { makeAutoObservable, runInAction, toJS } from 'mobx';
import { InputProps } from '../../components/input/input.interface';
import { validateField } from './utils/validateField';
import { FieldType, FormFieldOptions, FormFieldType } from './Field.interface';
import { getValidationFun } from './utils/getValidationFunction';

/**
 * A model to represent a field in a form.
 */
export class Field {
  /**
   * The id of the field. It must be unique for the form.
   */
  id: string;
  /**
   * The value of the field.
   */
  value: string | undefined;
  /**
   * The type of the field.
   * @default 'text'
   */
  type: FormFieldType;
  /**
   * Options for the field if the field is of type `option`.
   */
  options?: FormFieldOptions;
  /**
   * The prop options for the input component.
   */
  input?: Partial<InputProps>;
  /**
   * A function to validate the value of the field.
   * @optional Defaults will be used if not provided.
   */
  validation?: (value?: string) => boolean;

  constructor(props: FieldType) {
    this.id = props.id;
    this.value = props.value;
    this.type = props.type || 'text';
    this.options = props.options;
    this.input = props.input;
    this.validation = props.validation;
    makeAutoObservable(this);
  }

  /**
   * Whether the field is valid or not.
   */
  get valid() {
    const required: boolean = this.input?.required || false;
    const validationFun = getValidationFun(this.type, this.validation);
    return validateField(this.value, required, validationFun);
  }

  /**
   * Sets the value of the field.
   * @param value the value to set the field to
   */
  set = (value?: string) => {
    runInAction(() => {
      this.value = value;
    });
  };

  /**
   * Sets the input to be disabled
   * @param disabled
   */
  setDisabled = (disabled: boolean) => {
    runInAction(() => {
      if (this.input) {
        this.input.disabled = disabled;
      } else {
        this.input = { disabled };
      }
    });
  };

  /**
   * Sets the input to be required
   * @param required
   */
  setRequired = (required: boolean) => {
    runInAction(() => {
      if (this.input) {
        this.input.required = required;
      } else {
        this.input = { required };
      }
    });
  };

  /**
   * Sets the min value of input
   * *Only applies to number, date, input types*
   * @param min
   */
  setMin = (min: string) => {
    if (this.type !== 'number' && this.type !== 'date') {
      throw new Error(
        'setMin can only be used with number and date input types'
      );
    }
    runInAction(() => {
      if (this.input) {
        this.input.min = min;
      } else {
        this.input = { min };
      }
    });
  };
  /**
   * Sets the max value of input
   * *Only applies to number, date, input types*
   * @param min
   */
  setMax = (max: string) => {
    runInAction(() => {
      if (this.input) {
        this.input.max = max;
      } else {
        this.input = { max };
      }
    });
  };

  /**
   * Clears the value of the field.
   */
  clear = () => {
    this.set(undefined);
  };

  /**
   * Get the field as a plain object.
   * @returns the field as a plain object
   */
  obj = (): FieldType => {
    const field = toJS(this);
    return {
      id: field.id,
      value: field.value || '',
      type: field.type,
      options: field.options,
      input: field.input,
      validation: field.validation
    };
  };
}
