import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { CustomField, ExtendedFieldsDataMap, Translate } from '@wix/bex-core';
import type { OnCustomFieldChange } from '../../components/CustomFieldsWidget/OnCustomFieldChange';
import { BaseInputRef } from '../../fields';
import { CustomFieldsWidgetState } from './CustomFieldsWidgetState';

export interface CustomFieldsFormStateParams {
  namespace: string;
  customFieldsWidgetState: CustomFieldsWidgetState;
  translate: Translate;
}

export class CustomFieldsFormState {
  readonly _namespace;
  readonly _customFieldsWidgetState;
  _fields;
  _extendedFieldsDataMap;
  _initialValues;
  _translate: Translate;
  _inputRefs: { [fieldId: string]: BaseInputRef };

  constructor(params: CustomFieldsFormStateParams) {
    const { namespace, customFieldsWidgetState, translate } = params;

    this._namespace = namespace;
    this._customFieldsWidgetState = customFieldsWidgetState;

    const { fields, extendedFieldsDataMap } =
      customFieldsWidgetState.getNamespaceFormSchemaAndData(namespace);

    this._fields = fields ?? [];
    this._extendedFieldsDataMap = observable.map<string, any>(
      extendedFieldsDataMap,
    );
    this._initialValues = { ...extendedFieldsDataMap };
    this._inputRefs = {};
    this._translate = translate;

    makeObservable(this, {
      setValue: action,
      setNewFields: action,
      values: computed,
      fields: computed,
      isDirty: computed,
      _fields: observable.ref,
      writeableFields: computed,
      _isUserFieldsNamespace: computed,
      _inputRefs: observable.ref,
      appName: computed,
    });
  }

  _getFullyQualifiedFieldId(originalId: string) {
    return `extendedFields.namespaces.${this._namespace}.${originalId}`;
  }

  get fields() {
    return this._fields;
  }

  get writeableFields() {
    const { _isUserFieldsNamespace, _fields } = this;
    if (_isUserFieldsNamespace) {
      return _fields;
    }
    return _fields.filter((field) =>
      field.permissions?.write?.includes('users'),
    );
  }

  get _isUserFieldsNamespace() {
    const { _customFieldsWidgetState, _namespace } = this;
    const { _userFieldsNamespace } = _customFieldsWidgetState;

    return _namespace === _userFieldsNamespace;
  }

  init() {
    const { _customFieldsWidgetState } = this;

    const disposers = [
      reaction(
        () =>
          _customFieldsWidgetState.getNamespaceFormSchemaAndData(
            this._namespace,
          ),
        ({ fields }) => {
          if (!fields) {
            return;
          }
          this.setNewFields(fields);
        },
        {
          fireImmediately: true,
        },
      ),
    ];

    return () => {
      disposers.forEach((d) => d?.());
    };
  }

  setValue<T>({
    id,
    value,
    onChange,
  }: {
    id: string;
    value: T;
    onChange?: OnCustomFieldChange;
  }) {
    this._extendedFieldsDataMap.set(id, value);
    onChange?.({
      id: `extendedFields.namespaces.${this._namespace}.${id}`,
      value,
      allFields: Object.keys(this.values).reduce((acc, fieldId) => {
        acc[fieldId] = {
          id: this._getFullyQualifiedFieldId(fieldId),
          value: this.values[fieldId],
        };
        return acc;
      }, {} as Record<string, { id: string; value: any }>),
      namespace: this._namespace,
    });
  }

  getInitialValue(id: string) {
    return this._initialValues[id];
  }

  setInputRef(id: string, input: BaseInputRef) {
    this._inputRefs[id] = input;
  }

  setNewFields(fields: CustomField[]) {
    this._fields = fields;
    this._adjustExtendedFields();
  }

  _adjustExtendedFields() {
    const existingFields = this._fields.map((field) => field.id);
    this._extendedFieldsDataMap.forEach((_, key) => {
      if (!existingFields.includes(key)) {
        this._extendedFieldsDataMap.delete(key);
      }
    });
  }

  validate(): { isValid: boolean; extendedFields?: ExtendedFieldsDataMap } {
    const invalid = Object.keys(this._inputRefs).find((fieldId) => {
      const ref = this._inputRefs[fieldId];
      return ref.invalid;
    });

    if (invalid) {
      this._inputRefs[invalid].focus?.();
      return { isValid: false };
    }

    return { isValid: true, extendedFields: this.values };
  }

  get isDirty() {
    return !!Array.from(this._extendedFieldsDataMap.entries()).find(
      ([key, value]) => {
        // Files field is not considered dirty since it cannot be changed
        const customField = this._fields.find((field) => field.id === key);
        if (customField?.type === 'files') {
          return false;
        }
        if (this._initialValues.hasOwnProperty(key)) {
          return this._initialValues[key] !== value;
        } else {
          // includes both any string value and boolean types (all existing field types values)
          return !!value;
        }
      },
    );
  }

  get appName() {
    const { _customFieldsWidgetState, _namespace } = this;
    const { apps, dataExtension } = _customFieldsWidgetState;
    const {
      dataExtensionService: { userFieldsNamespace },
    } = dataExtension;

    if (_namespace === userFieldsNamespace) {
      return this._translate('cairo.customFields.entityPage.sectionTitle');
    }

    const appSchema = dataExtension.appSchemas?.find(
      (schema) => schema.namespace === _namespace,
    );

    return apps.appNames[appSchema?.appDefId || ''];
  }

  get values() {
    return Object.fromEntries(this._extendedFieldsDataMap);
  }
}
