import clsx from 'clsx';
import { Formik, FormikValues } from 'formik';
import React, { PropsWithChildren, useContext } from 'react';
import { OptionalObjectSchema } from 'yup/lib/object';
import { Data } from '../../types/data';
import { BulkEditContext } from '../Explorer/BulkEdit/BulkEditContext';
import { QuickEditContext } from '../Explorer/QuickEdit/QuickEditContext';
import { PageHeaderJsActionProps } from '../PageHeader/PageHeaderAction';
import { StationMessage } from '../models';
import {
  FormActionData,
  InitialFormData,
  ObjectSchemaDefinition,
  SaveDataFunction,
} from './FormStation.models';
import classes from './FormStation.scss';
import { FormStationAction } from './FormStationActions';
import { FormStationContentWrapper } from './FormStationContentWrapper';
import { FormStationHeader } from './FormStationHeader';
import { SaveOnDemand } from './SaveOnDemand/SaveOnDemand';
import { SaveOnNavigate } from './SaveOnNavigate/SaveOnNavigate';
import { useDataProvider } from './helpers/useDataProvider';
import { useValidationError } from './helpers/useValidationError';

export interface FormStationProps<
  TValues extends Data = FormikValues,
  TSubmitResponse = unknown,
> {
  /** name of the property on the form that holds the title */
  titleProperty?: keyof TValues;
  /** fallback title if no value can be determined */
  defaultTitle?: string;
  /** Subtitle shown in page header */
  subtitle?: string;
  /** If set to true, the actions panel is shown, even if no actions are defined. */
  alwaysShowActionsPanel?: boolean;
  /** An array of the actions that should be executable on the page */
  actions?: FormActionData<TValues, TSubmitResponse>[];
  /** The width of the Actions container (as CSS width) */
  actionsWidth?: string;
  /** If set to true, the save header action is shown. (default: true) */
  showSaveHeaderAction?: boolean;
  /**
   * Optional configuration for the save header action button.
   * Allows customizing the label and icon.
   */
  saveHeaderActionConfig?: Pick<PageHeaderJsActionProps, 'label' | 'icon'>;
  /**
   * If set, this will override the default notification message shown
   * after a successful save.
   */
  saveNotificationMessage?: string;
  /**
   * An object containing the initial data of the form.
   */
  initialData: InitialFormData<TValues>;
  /** The Yup validation object (or any other Formik compatible validation schema) */
  validationSchema?: OptionalObjectSchema<ObjectSchemaDefinition<TValues>>;
  /** URL to navigate to when the form is reset (usually on a create station) */
  cancelNavigationUrl?: string;
  /** Component which contains additional information */
  infoPanel?: JSX.Element;
  /** Optional flag for removing the padding around child components. */
  edgeToEdgeContent?: boolean;
  /**
   * This will make sure that the submission is attempted even if the form is not dirty.
   * This is mainly useful for Create stations.
   */
  alwaysSubmitBeforeAction?: boolean;
  /**
   * If set, will display a message bar on the form station
   * @example stationMessage={{type: 'info', message: 'Informative message.'}}
   */
  stationMessage?: StationMessage;
  /** Update the tab title using the 'titleProperty' or 'defaultTitle' field. (default: true) */
  setTabTitle?: boolean;
  /**
   * Called whenever the form needs to be saved.
   * This method needs to throw an exception in case the saving did not succeed.
   */
  saveData: SaveDataFunction<TValues, TSubmitResponse>;
  /** CSS Class name for additional styles */
  className?: string;
}

export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
  titleProperty,
  defaultTitle,
  subtitle,
  children,
  alwaysShowActionsPanel = false,
  actions,
  actionsWidth,
  cancelNavigationUrl,
  initialData,
  saveData,
  validationSchema,
  infoPanel,
  edgeToEdgeContent,
  alwaysSubmitBeforeAction = false,
  stationMessage,
  className = '',
  setTabTitle = true,
  showSaveHeaderAction = true,
  saveHeaderActionConfig,
  saveNotificationMessage,
}: PropsWithChildren<
  FormStationProps<TValues, TSubmitResponse>
>): JSX.Element => {
  const quickEditContext = useContext(QuickEditContext);
  const bulkEditContext = useContext(BulkEditContext);

  const {
    onSubmit,
    stationError,
    setStationError,
    isFormSubmitting,
    lastSubmittedResponse,
  } = useDataProvider(initialData, saveData, saveNotificationMessage);

  const { setValidationError, validationWatcher } = useValidationError(
    stationError,
    setStationError,
  );

  return (
    <div
      className={clsx(
        classes.container,
        {
          [classes.hasError]: stationError,
        },
        'form-station-container',
        className,
      )}
    >
      <Formik
        initialValues={initialData.data ?? ({} as TValues)}
        validationSchema={validationSchema}
        onSubmit={onSubmit}
        enableReinitialize={true}
      >
        <>
          {validationWatcher}
          <FormStationHeader
            titleProperty={titleProperty as string}
            defaultTitle={defaultTitle}
            subtitle={subtitle}
            cancelNavigationUrl={cancelNavigationUrl}
            className={classes.header}
            setTabTitle={setTabTitle}
            showSaveHeaderAction={showSaveHeaderAction}
            saveHeaderActionConfig={saveHeaderActionConfig}
            setValidationError={setValidationError}
          />
          {!bulkEditContext && (
            <SaveOnNavigate
              isSubmitting={isFormSubmitting}
              onNavigationCancelled={setValidationError}
            />
          )}
          {quickEditContext && (
            <SaveOnDemand
              isSubmitting={isFormSubmitting}
              onFormInvalid={setValidationError}
              registerSaveCallback={quickEditContext.registerSaveCallback}
            />
          )}
          <FormStationContentWrapper
            stationMessage={stationMessage}
            edgeToEdgeContent={edgeToEdgeContent}
            infoPanel={!quickEditContext ? infoPanel : undefined}
            initialData={initialData}
            stationError={stationError}
            setStationError={setStationError}
          >
            {children}
          </FormStationContentWrapper>
          {(alwaysShowActionsPanel || (actions?.length ?? 0) > 0) &&
            !quickEditContext && (
              <FormStationAction<TValues, TSubmitResponse>
                actions={actions}
                width={actionsWidth}
                validationSchema={validationSchema}
                setStationError={setStationError}
                setValidationError={setValidationError}
                submitResponse={lastSubmittedResponse}
                alwaysSubmitBeforeAction={alwaysSubmitBeforeAction}
                className={classes.actionsPanel}
              />
            )}
        </>
      </Formik>
    </div>
  );
};
