import { FormPageState, ReportBI, WixPatternsContainer } from '@wix/bex-core';
import { action, computed, makeObservable, observable } from 'mobx';
import { FieldValues, UseFormReturn } from 'react-hook-form';
import { SettingsPageStateBIReporter } from './SettingsPageStateBIReporter';
import { EventEmitter, TypedEmitter } from '@wix/bex-core/events';

export interface SettingsPageStateParams<
  T,
  V extends FieldValues = FieldValues,
> {
  /**
   * A function that fetches the settings data.
   * @returns a promise that resolves to an object containing the settings.
   */
  fetch: () => Promise<{ settings: T | undefined }>;

  /**
   * A function to handle the save action.
   */
  onSave: () => Promise<void>;

  /**
   * A function to handle the cancel action.
   */
  onCancel: () => Promise<void>;

  /**
   * An optional object with an optional message, or it can directly get a string message.
   */
  saveSuccessToast?: { message?: string } | string;

  /**
   * An optional function to display an error toast message when a save operation fails.
   * @returns An object with a `message` and an `action` object. Alternatively, it can return an object with an optional `message` and a `tryAgain` flag, or a string message.
   * The previous return types are still supported but are now deprecated.
   */
  saveErrorToast?: (
    err: unknown,
    params: { retry: () => void },
  ) =>
    | {
        message?: string;
        /**
         * @deprecated use `action` instead
         */
        tryAgain?: boolean;
        action?: { text: string; onClick: () => void };
      }
    | string;

  form: UseFormReturn<V>;

  /**
   * An object representing the container, possibly for dependency injection or component management.
   */
  container: WixPatternsContainer;
}

export interface SettingsPageStateInterface {}

export class SettingsPageState<T, V extends FieldValues = FieldValues>
  implements SettingsPageStateInterface
{
  readonly fetch: SettingsPageStateParams<T>['fetch'];
  readonly _onSave: SettingsPageStateParams<T, V>['onSave'];
  readonly _onCancel: SettingsPageStateParams<T, V>['onCancel'];
  readonly saveSuccessToast?: SettingsPageStateParams<T>['saveSuccessToast'];
  readonly saveErrorToast?: SettingsPageStateParams<T>['saveErrorToast'];

  _settings: T | null = null;

  readonly container: WixPatternsContainer;
  readonly reportBI: ReportBI;
  readonly bi: SettingsPageStateBIReporter<T, V>;

  readonly formPage: FormPageState<V>;

  readonly events = new EventEmitter() as TypedEmitter<{
    save: () => void;
    saveError: (err: unknown) => void;
  }>;

  constructor(params: SettingsPageStateParams<T, V>) {
    this.fetch = params.fetch;
    this._onSave = params.onSave;
    this._onCancel = params.onCancel;
    this.saveSuccessToast = params.saveSuccessToast;
    this.saveErrorToast = params.saveErrorToast;
    this.formPage = new FormPageState<V>({ form: params.form });

    this.container = params.container;

    this.reportBI = this.container.createBILogger({
      componentType: 'Settings page',
      pageType: 'Settings',
      route: window.location.pathname,
    });

    this.bi = new SettingsPageStateBIReporter({
      reportBI: this.reportBI,
      settingsPageState: this,
    });

    this.bi.init();

    makeObservable(this, {
      handleSubmit: action,
      _settings: observable.ref,
      setSettings: action,
      isDirty: computed,
    });
  }

  init() {
    const {
      formPage: { initTask },
      container,
    } = this;
    const { onBeforeUnload } = container;

    const onBeforeUnloadSubscription = onBeforeUnload?.(
      action((event: Event) => {
        if (!this.isDirty) {
          return;
        }
        event.preventDefault();
        this.bi.reportOnBeforeUnload();
      }),
    );

    initTask.runOnce(async () => {
      const [data] = await Promise.all([
        this.fetch(),
        container.initTask.status.promise,
      ]);

      this.setSettings(data.settings ?? null);
      this.bi.appLoaded?.();
    });

    return () => {
      onBeforeUnloadSubscription?.remove();
    };
  }

  retryFetch() {
    this.formPage.retryFetch();
  }

  setSettings(newSettings: T | null) {
    this._settings = newSettings;
  }

  get settings(): T | null {
    return this._settings;
  }

  get isFetching() {
    return this.formPage.isFetching;
  }

  get form() {
    return this.formPage.form;
  }

  get isDirty() {
    return this.formPage.isDirty;
  }

  onCancel() {
    this.bi.reportButtonClick({ ctaName: 'Cancel' });
    this._onCancel();
  }

  _showErrorToast(err: any, e?: React.BaseSyntheticEvent) {
    const errorToast = this.saveErrorToast?.(err, {
      retry: () => this.handleSubmit(e),
    });
    const message =
      typeof errorToast === 'string' ? errorToast : errorToast?.message;
    const showTryAgain =
      typeof errorToast === 'string' ? true : errorToast?.tryAgain !== false;
    const action =
      typeof errorToast === 'string' ? undefined : errorToast?.action;

    this.container.showToast?.({
      type: 'ERROR',
      biName: 'cairo-settings-page-save-error-toast',
      message:
        message ??
        this.container.translate(
          err?.code === 'ERR_NETWORK'
            ? 'cairo.entityPage.saveError-offline.toast.description'
            : 'cairo.entityPage.saveError-technical.toast.description',
        ),
      action:
        action || showTryAgain
          ? {
              uiType: 'LINK',
              text:
                action?.text ?? this.container.translate('cairo.toast.retry'),
              onClick: () => {
                if (action?.onClick) {
                  action.onClick();
                } else {
                  this.bi.reportSaveTryAgain();
                  this.handleSubmit(e);
                }
              },
              removeToastOnClick: true,
            }
          : undefined,
    });
  }

  _showSuccessToast() {
    const message =
      typeof this.saveSuccessToast === 'string'
        ? this.saveSuccessToast
        : this.saveSuccessToast?.message;

    this.container.showToast?.({
      type: 'SUCCESS',
      biName: 'cairo-settings-page-save-success-toast',
      message:
        message ?? this.container.translate('cairo.entityPage.success.toast'),
    });
  }

  async handleSubmit(e?: React.BaseSyntheticEvent) {
    if (this.formPage.submitTask.status.isLoading) {
      return;
    }

    this.events.emit('save');

    const {
      form,
      formPage: { submitTask },
      bi,
    } = this;

    submitTask.run(async () => {
      await form.handleSubmit(
        async () => {
          bi.reportButtonClick({ ctaName: 'Save', isValid: true });
          try {
            if (!this.isFetching) {
              await this._onSave();
              form.reset(form.getValues());
            }
            this._showSuccessToast();
          } catch (err) {
            this._showErrorToast(err, e);
            this.container.errorMonitor.captureException(err);
            this.events.emit('saveError', err);
            form.setError('root.serverError', {}); // https://react-hook-form.com/docs/useform/seterror
          }
        },
        () => {
          bi.reportButtonClick({ ctaName: 'Save', isValid: false });
        }, // can be used to report validation errors
      )(e);
    });

    await this.formPage.submitTask.status.promise;
  }

  get initTask() {
    return this.formPage.initTask;
  }

  get submitTask() {
    return this.formPage.submitTask;
  }
}
