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

type OnSaveParams = {
  widgetsFormData: { [key: string]: any };
};

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

  /**
   * A function to handle the save action.
   * @returns promise that resolves to an object that contains a 'updatedEntity' object.
   */
  onSave: (params: OnSaveParams) => Promise<{ updatedEntity: T }>;

  /**
   * 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;

  /**
   * An optional function to transform the updated entity data before it is passed to the collection page.
   * @param entity the updated entity.
   * @returns an object representing the entity in the collection page.
   */
  transformEntityToCollectionItem?: (entity: T) => any;

  form: UseFormReturn<V>;
  /**
   * An object representing the container, possibly for dependency injection or component management.
   */
  container: WixPatternsContainer;

  /**
   * A string representing the ID of the parent page.
   * Must be passed if not using Patterns Router
   */
  parentPageId?: string;

  /**
   * A string representing the path of the parent page.
   * Must be passed if using Patterns Router
   */
  parentPath?: string;

  /**
   * An object representing the router state
   */
  routerState?: WixPatternsRouterState | null;

  /**
   * An optional object to override the actions bar.
   */
  actionsBarConfig?: ActionsBarConfig;
}

export type EntityPageStateParams<
  T,
  V extends FieldValues = FieldValues,
> = EntityPageStateParamsBase<T, V>;

export interface EntityPageStateInterface {}

export interface EntityPageWidgetState {
  validate: () => Promise<{ isValid: boolean; values?: any }>;
  isDirty: boolean;
}

export class EntityPageState<T, V extends FieldValues = FieldValues>
  implements EntityPageStateInterface
{
  readonly fetch: EntityPageStateParams<T>['fetch'];
  readonly onSave: EntityPageStateParams<T, V>['onSave'];
  readonly saveSuccessToast?: EntityPageStateParams<T>['saveSuccessToast'];
  readonly saveErrorToast?: EntityPageStateParams<T>['saveErrorToast'];
  readonly transformEntityToCollectionItem: (entity: T) => any;

  _entity: T | null = null;
  private readonly _parentPageId?: string;
  private readonly _parentPath?: string;

  actionsBarConfig?: EntityPageStateParams<T>['actionsBarConfig'];

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

  _routerState: WixPatternsRouterState | null = null;

  readonly formPage: FormPageState<V>;

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

  constructor(params: EntityPageStateParams<T, V>) {
    this.fetch = params.fetch;
    this.onSave = params.onSave;
    this.saveSuccessToast = params.saveSuccessToast;
    this.saveErrorToast = params.saveErrorToast;
    this.formPage = new FormPageState<V>({ form: params.form });
    this._parentPageId = params.parentPageId;
    this.transformEntityToCollectionItem =
      params.transformEntityToCollectionItem || ((anEntity: T) => anEntity);
    this._parentPath = params.parentPath;

    this.container = params.container;

    this._routerState = params.routerState ?? null;

    if (this._routerState) {
      this._entity = this._routerState.currentState?._chosenEntity?.entity;
    }

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

    this.bi = new EntityPageStateBIReporter({
      reportBI: this.reportBI,
      entityPageState: this,
    });
    this.bi.init();

    makeObservable(this, {
      handleSubmit: action,
      _entity: observable.ref,
      setEntity: action,
      isFormDirty: computed,
      _withinRouter: computed,
    });
  }

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

    const onBeforeUnloadSubscription = onBeforeUnload?.(
      action((event: Event) => {
        if (isMobileDevice()) {
          return;
        }

        const { form } = this;
        // Check both react-hook-form's state AND the explicitly set flag
        if (
          form.formState.isSubmitSuccessful ||
          this.formPage._isSubmitSuccessful ||
          !this.isFormDirty
        ) {
          return;
        }
        event.preventDefault();
        this.bi.reportOnBeforeUnload();
      }),
    );

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

      this.setEntity(data.entity ?? null);
      this.bi.appLoaded?.();
    });

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

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

  setEntity(newEntity: T | null) {
    this._entity = newEntity;
  }

  get entity(): T | null {
    return this._entity;
  }

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

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

  set isSubmitSuccessful(flag: boolean) {
    this.formPage.isSubmitSuccessful = flag;
  }

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

  // backwards compatibility
  set _isFormDirty(val: boolean) {
    this.formPage._isFormDirty = val;
  }

  get _withinRouter() {
    return !!this._routerState;
  }

  onCancel() {
    this.bi.reportButtonClick({
      ctaName: 'Cancel',
      additionalInfo: this.actionsBarConfig?.cancelCta?.biAdditionalInfo,
    });
    this.navigateToParent();
  }

  navigateToParent(updatedEntity?: any) {
    const entityStringKey = this._entity ? '_updatedEntity' : '_createdEntity';
    const entityObject = { [entityStringKey]: updatedEntity };

    if (this._withinRouter) {
      this._routerState!.navigate(this._parentPath!, { state: entityObject });
    } else {
      this.container.navigateTo!({
        pageId: this._parentPageId!,
      });
    }
  }

  _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-entity-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-entity-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 { isValid, data: widgetsFormData } = await this.formPage.validate();

    if (!isValid) {
      this.bi.reportButtonClick({
        ctaName: 'Save',
        isValid: false,
        additionalInfo: this.actionsBarConfig?.saveCta?.biAdditionalInfo,
      });
      return;
    }

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

    submitTask.run(async () => {
      let savedEntity: T | undefined;
      let submitSuccessful = false;

      await form.handleSubmit(
        async () => {
          bi.reportButtonClick({
            ctaName: 'Save',
            isValid: true,
            additionalInfo: this.actionsBarConfig?.saveCta?.biAdditionalInfo,
          });
          try {
            if (!this.isFetching) {
              const onSaveResult = await this.onSave({
                widgetsFormData,
              });
              savedEntity = onSaveResult?.updatedEntity;
            }
            this._showSuccessToast();
            submitSuccessful = true; // Mark as successful locally
            // Explicitly set isSubmitSuccessful to prevent onBeforeUnload from triggering
            this.isSubmitSuccessful = true;
          } 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,
            additionalInfo: this.actionsBarConfig?.saveCta?.biAdditionalInfo,
          });
        }, // can be used to report validation errors
      )(e);

      if (!submitSuccessful) {
        return;
      }
      const transformedItem =
        savedEntity && this.transformEntityToCollectionItem
          ? this.transformEntityToCollectionItem(savedEntity)
          : savedEntity;
      this.navigateToParent(transformedItem);
    });
    await this.formPage.submitTask.status.promise;
  }

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

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

  private _checkPageParams() {
    if (this._withinRouter) {
      if (this._parentPath === undefined) {
        throw new Error('parentPath was not defined in useEntityPage');
      }
    } else {
      if (this._parentPageId === undefined) {
        throw new Error('parentPageId was not defined in useEntityPage');
      }

      if (!this.container.navigateTo) {
        throw new Error('navigateTo was not defined in Patterns Provider');
      }
    }
  }
}
