import { action, computed, makeObservable, observable } from 'mobx';
import type { ToastConfig } from '@wix/bex-core';
import type { Field, SchemaConfig } from '../../types/SchemaConfig';
import {
  isPIISupportedByFieldType,
  isSelectableFieldType,
} from './fieldTypeUtils';
import type {
  FieldProperties,
  FieldPropertiesState,
  FieldRules,
  SelectableFieldTypes,
  ValidationRule,
} from './types';

export interface CmsFormStateParams {
  schemaConfig: SchemaConfig;
  getValidationRules: (t: SelectableFieldTypes) => ValidationRule[];
  mode?: 'new' | 'edit';
  editingFieldId?: string;
}

function defaultProperties(): FieldPropertiesState {
  return {
    type: undefined,
    name: '',
    isPii: false,
    helpText: '',
  };
}

// TODO: confirm vs cms-web
const NAME_MAX_LENGTH = 50;

export class CmsFormState {
  readonly schemaConfig: SchemaConfig;
  readonly getValidationRules: (t: SelectableFieldTypes) => ValidationRule[];

  properties: FieldPropertiesState = defaultProperties();
  fieldRules: FieldRules = { validationRules: {} };
  mode: 'new' | 'edit';
  editingFieldId: string | undefined;
  touchedFields: Set<keyof FieldProperties> = new Set();
  showValidationError: boolean = false;
  errorToastConfig: ToastConfig | undefined = undefined;

  constructor(params: CmsFormStateParams) {
    this.schemaConfig = params.schemaConfig;
    this.getValidationRules = params.getValidationRules;
    this.mode = params.mode ?? 'new';
    this.editingFieldId = params.editingFieldId;

    makeObservable(this, {
      properties: observable.ref,
      fieldRules: observable.ref,
      mode: observable.ref,
      editingFieldId: observable.ref,
      touchedFields: observable,
      showValidationError: observable.ref,
      errorToastConfig: observable.ref,

      fieldTypeError: computed,
      nameError: computed,
      hasInvalidFields: computed,
      visibleErrors: computed,
      supportedValidationRules: computed,
      validationErrorMessage: computed,

      setProperty: action,
      setValidationRule: action.bound,
      blurField: action,
      enableShowValidationError: action,
      setMode: action,
      setErrorToastConfig: action,
      loadField: action,
      reset: action,

      hideValidationError: action.bound,
      changeType: action.bound,
      changeName: action.bound,
      setIsPii: action.bound,
      setHelpText: action.bound,
    });
  }

  get fieldTypeError(): string | undefined {
    if (!this.properties.type) {
      return 'Field type is required';
    }
    return undefined;
  }

  get nameError(): string | undefined {
    const name = this.properties.name.trim();
    if (!name) {
      return 'Field name is required';
    }
    if (name.length > NAME_MAX_LENGTH) {
      return `Field name must be ${NAME_MAX_LENGTH} characters or fewer`;
    }
    return undefined;
  }

  get hasInvalidFields(): boolean {
    return !!(this.fieldTypeError || this.nameError);
  }

  // Spec §3 specifies booleans; returning the error string directly is more useful for rendering
  // (one property read instead of two). Deviation is intentional — update spec if accepted.
  get visibleErrors(): {
    type: string | undefined;
    name: string | undefined;
  } {
    const { showValidationError, touchedFields } = this;
    return {
      type: showValidationError ? this.fieldTypeError : undefined,
      name:
        showValidationError || touchedFields.has('name')
          ? this.nameError
          : undefined,
    };
  }

  get supportedValidationRules(): ValidationRule[] {
    return this.properties.type
      ? this.getValidationRules(this.properties.type)
      : [];
  }

  get validationErrorMessage(): string {
    return (
      this.fieldTypeError ||
      this.nameError ||
      'Please fix the errors before saving'
    );
  }

  setProperty<K extends keyof FieldPropertiesState>(
    key: K,
    value: FieldPropertiesState[K],
  ) {
    this.properties = { ...this.properties, [key]: value };
  }

  setValidationRule(rule: ValidationRule, value: unknown) {
    this.fieldRules = {
      ...this.fieldRules,
      validationRules: { ...this.fieldRules.validationRules, [rule]: value },
    };
  }

  blurField(field: keyof FieldProperties) {
    this.touchedFields.add(field);
  }

  enableShowValidationError() {
    this.showValidationError = true;
  }

  setMode(mode: 'new' | 'edit') {
    this.mode = mode;
  }

  setErrorToastConfig(cfg: ToastConfig | undefined) {
    this.errorToastConfig = cfg;
  }

  reset() {
    this.properties = defaultProperties();
    this.fieldRules = { validationRules: {} };
    this.editingFieldId = undefined;
    this.touchedFields.clear();
    this.showValidationError = false;
    this.errorToastConfig = undefined;
  }

  hideValidationError() {
    this.showValidationError = false;
  }

  changeType(v: SelectableFieldTypes) {
    this.setProperty('type', v);
    this._onTypeChange(v);
  }

  changeName(v: string) {
    this.setProperty('name', v);
  }

  setIsPii(v: boolean) {
    this.setProperty('isPii', v);
  }

  setHelpText(v: string) {
    this.setProperty('helpText', v);
  }

  loadField(field: Field) {
    const mappedType: SelectableFieldTypes | undefined = isSelectableFieldType(
      field.type,
    )
      ? field.type
      : undefined;
    this.properties = {
      type: mappedType,
      name: field.displayName,
      isPii: field.isPii ?? false,
      helpText: field.helpText ?? '',
    };
    this.fieldRules = {
      validationRules: {
        ...(field.validation?.required !== undefined && {
          required: field.validation.required,
        }),
        ...(field.validation?.stringLengthRange !== undefined && {
          charactersLimit: field.validation.stringLengthRange,
        }),
      },
    };
    this.editingFieldId = field.id;
    this.errorToastConfig = undefined;
  }

  private _onTypeChange(newType: SelectableFieldTypes) {
    const supported = new Set(this.getValidationRules(newType));
    this.fieldRules = {
      validationRules: Object.fromEntries(
        Object.entries(this.fieldRules.validationRules).filter(([k]) =>
          supported.has(k as ValidationRule),
        ),
      ),
    };
    if (!isPIISupportedByFieldType(newType)) {
      this.properties = { ...this.properties, isPii: false };
    }
    this.touchedFields.clear();
  }
}
