/**
 * @file NewWorkItemModal.tsx - Modal dialog for creating a new work item within a selected process.
 * Provides form inputs for the work item name, folder field values, optional file uploads with validation for accepted types and size limits, and an optional initial note.
 * On submission, all assembled data is sent to the IWS5 API via {@link createWorkItem}.
 */

import type { Process } from '../../../api/process.ts';
import { type FC, useEffect, useRef, useState } from 'react';
import type { FolderField } from '../../../api/folderField.ts';
import {
  Button,
  Col,
  FloatingLabel,
  Form,
  Modal,
  Row,
  Spinner,
} from 'react-bootstrap';
import { FieldInputControl } from '../../shared/FieldInputControl.tsx';
import {
  type FieldInstanceType,
  FieldInstanceTypes,
} from '../../../types/fieldInstance.ts';
import { createWorkItem } from '../../../api/workItem.ts';
import { toast } from 'react-toastify';
import {
  DEFAULT_DOCUMENT_TEMPLATE_ID,
  DEFAULT_DOCUMENT_TEMPLATE_NAME,
  MAX_FILE_NAME_LENGTH,
  MAX_FILE_SIZE,
  MAX_FOLDER_FIELD_LENGTH,
  MAX_NOTE_LENGTH,
  MAX_WORK_ITEM_NAME_LENGTH,
  UPLOAD_ACCEPTED_FILE_EXTENSIONS,
} from '../../../constants.ts';
import { type Document } from '../../../api/document.ts';
import type { DocumentTemplateField } from '../../../api/documentTemplate.ts';
import { Key } from 'ts-key-enum';
import { useReauthModalSuspension } from '../../../hooks/modals/useReauthModalSuspension.ts';
import { useProcessPermissions } from '../../../contexts/ProcessPermissionsContext.tsx';
import { AtAllowRights } from '../../../types/atAllowRights.ts';
import { Trans, useTranslation } from 'react-i18next';
import { CustomFileInput } from '../../CustomFileInput/CustomFileInput.tsx';

/**
 * Props for the NewWorkItemModal component.
 */
interface NewWorkItemModalProps {
  /** Whether the modal is currently visible */
  show: boolean;

  /** Callback invoked when the modal is dismissed */
  onHide: () => void;

  /** The currently selected process the work item will belong to */
  process: Process | undefined;

  /** Folder field definitions for the selected process */
  folderFields: FolderField[];
}

/**
 * Modal for creating a new work item within the selected process.
 *
 * The form is split into two columns:
 * - Left: work item name (required), file upload for documents (PDF only, validated), and an optional initial note textarea
 * - Right: dynamically rendered folder field inputs
 *
 * Pressing Enter anywhere in the form (except the file picker and note textarea) triggers submission.
 * The modal is suspended when a reauthentication prompt is active via useReauthModalSuspension.
 */
export const NewWorkItemModal: FC<NewWorkItemModalProps> = ({
  show,
  onHide,
  process,
  folderFields,
}) => {
  // Work item name validation
  const workItemNameRef = useRef<HTMLInputElement | null>(null);
  const [workItemNameValid, setWorkItemNameValid] = useState(false);

  // Reference to the hidden file input used for document uploads
  const fileControlRef = useRef<HTMLInputElement | null>(null);

  /**
   * Reads a single uploaded file via the FileReader API and returns a partial {@link Document} payload suitable for the API call to create the work item.
   * Validates file name length and file size before reading. If either is exceeded, a toast error is shown and the promise never resolves (the file is silently skipped).
   *
   * @param file The file selected by the user
   * @returns A promise resolving to a document payload containing the base64-encoded file content, default template identifiers, and an empty fields array
   */
  const readUploadedDocument = async (
    file: File
  ): Promise<
    Partial<Document> & {
      documentFile: string;
      documentFields: DocumentTemplateField[];
    }
  > => {
    const reader = new FileReader();

    // Wrap FileReader operations in Promise so that function can be awaited
    return new Promise((resolve, reject) => {
      reader.onload = () => {
        const result = reader.result as string;
        // Strip the data-URL prefix (e.g. "data:application/pdf;base64,") to get raw base64
        const base64 = result.split(',')[1];

        resolve({
          documentName: file.name,
          documentTemplateId: DEFAULT_DOCUMENT_TEMPLATE_ID,
          documentTemplateName: DEFAULT_DOCUMENT_TEMPLATE_NAME,
          documentFile: base64,
          documentFields: [],
        });
      };

      reader.onerror = () => {
        toast.error(
          `${t('functions.read_uploaded_document.errors.failed_to_read')}: ` +
            file.name
        );
        reject();
      };

      // Guard against large file names and file sizes
      if (file.name.length > MAX_FILE_NAME_LENGTH) {
        toast.error(
          t('functions.read_uploaded_document.errors.file_name_too_long', {
            file_name: file.name,
            max_length: MAX_FILE_NAME_LENGTH,
          })
        );
      } else if (file.size > MAX_FILE_SIZE) {
        toast.error(
          t('functions.read_uploaded_document.errors.file_size_too_large', {
            file_name: file.name,
            max_size: MAX_FILE_SIZE / 1024 / 1024,
          })
        );
      } else {
        reader.readAsDataURL(file);
      }
    });
  };

  // Folder fields validation
  const folderFieldsForm = useRef<HTMLFormElement | null>(null);
  const [folderFieldsValid, setFolderFieldsValid] = useState(true);

  // Reference to the optional initial note textarea
  const noteRef = useRef<HTMLTextAreaElement | null>(null);

  /**
   * Assembles the full work item payload (name, folder fields, documents, notes) and submits it to the IWS5 API.
   */
  const createNewWorkItem = async () => {
    // Read uploaded documents
    const documents: (Partial<Document> & {
      documentFile: string;
      documentFields: DocumentTemplateField[];
    })[] = [];
    const files = Array.from(fileControlRef.current?.files as FileList);
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      // Extract the file extension (e.g. ".pdf") for allow-list validation
      const extension = '.' + file.name.split('.').slice(-1).join('');

      const extensionMatch = UPLOAD_ACCEPTED_FILE_EXTENSIONS.find((ext) =>
        ext.extensions.includes(extension)
      );
      if (extensionMatch) {
        documents.push(await readUploadedDocument(file));
      } else {
        toast.warn(
          t('functions.create_new_work_item.errors.disallowed_extension', {
            file_name: file.name,
          })
        );
      }
    }

    setProcessing(true);

    // Submit work item creation request to the IWS5 API
    createWorkItem(
      process?.id as number,
      workItemNameRef.current?.value as string,
      // Map DOM form inputs back to FolderField partials, including only editable fields that have a non-empty value
      folderFieldsForm.current
        ? Array.from(folderFieldsForm.current as HTMLFormElement)
            .map((input): Partial<FolderField> => {
              const matchingField = folderFields.find(
                (field) =>
                  field.fieldId === Number((input as HTMLInputElement).name)
              );
              return {
                fieldId: matchingField?.fieldId as number,
                fieldName: matchingField?.fieldName as string,
                dataType: matchingField?.dataType as FieldInstanceType,
                fieldValue: (input as HTMLInputElement).value,
                editAllowed: matchingField?.editAllowed,
              };
            })
            .filter((field) => field.fieldValue && field.editAllowed)
        : [],
      documents,
      noteRef.current?.value ? [{ note: noteRef.current?.value as string }] : []
    )
      .then(() => {
        toast.success(t('functions.create_new_work_item.success'));
        onHide();
      })
      .catch(() => {
        toast.error(
          t('functions.create_new_work_item.errors.failed_to_create')
        );
      })
      .finally(() => {
        setProcessing(false);
      });
  };

  // Ref to the submit button so the Enter key handler can programmatically click it
  const createWorkItemButton = useRef<HTMLButtonElement | null>(null);
  // Tracks whether an API submission is in-flight (disables button and shows spinner)
  const [processing, setProcessing] = useState(false);

  // Suspend this modal when a reauthentication dialog is active
  const modalSuspensionState = useReauthModalSuspension(show);

  useEffect(() => {
    if (!show || modalSuspensionState === 'suspended') {
      return;
    }

    // Enter key triggers save confirmation
    // Except when file picker or note textarea are active element
    const handleKeyDown = (event: KeyboardEvent) => {
      if (
        event.key === Key.Enter &&
        fileControlRef.current !== document.activeElement &&
        noteRef.current !== document.activeElement
      ) {
        event.preventDefault();
        createWorkItemButton.current?.click();
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [show, modalSuspensionState]);

  // Process-level permission checks used to conditionally disable inputs
  const { hasPermission } = useProcessPermissions();

  const { t } = useTranslation(undefined, {
    keyPrefix: 'components.home.modals.new_work_item',
  });

  // Guard: render nothing until process and folder field data are available
  if (!process || !folderFields) {
    return;
  }

  return (
    <Modal
      centered
      show={modalSuspensionState !== 'hidden'}
      onHide={onHide}
      className={modalSuspensionState === 'suspended' ? 'suspended' : ''}
      dialogClassName={'modal-w50'}
    >
      <Modal.Header closeButton>
        <Modal.Title>{t('modal.header.title')}</Modal.Title>
      </Modal.Header>
      <Modal.Body>
        <div>
          <p className={'mb-0'}>
            <Trans
              t={t}
              i18nKey={'modal.body.paragraph_text'}
              values={{
                process_name: process.name,
                process_version: process.version,
              }}
            />
          </p>
          <small>
            <i>{t('modal.body.small_text')}</i>
          </small>
        </div>
        <hr />
        <Row className={'gap-2 gap-lg-0'}>
          <Col xs={12} lg={folderFields.length > 0 ? 6 : 12}>
            <Form noValidate validated={true}>
              <Form.Label>
                {t('modal.body.inputs.work_item_name.label')}:
              </Form.Label>
              <Form.Group>
                <FloatingLabel
                  controlId={'work-item-name-input'}
                  label={
                    <span>
                      {t('modal.body.inputs.work_item_name.label')}{' '}
                      <span className={'text-danger'}>*</span>
                    </span>
                  }
                >
                  <Form.Control
                    ref={workItemNameRef}
                    type={'text'}
                    placeholder={t('modal.body.inputs.work_item_name.label')}
                    onChange={(event) => {
                      setWorkItemNameValid(Boolean(event.target.value));
                    }}
                    required
                    autoFocus
                    maxLength={Number(MAX_WORK_ITEM_NAME_LENGTH)}
                  />
                  <Form.Control.Feedback type={'invalid'}>
                    {t('modal.body.inputs.work_item_name.invalid_text')}
                  </Form.Control.Feedback>
                </FloatingLabel>
              </Form.Group>
            </Form>
            <hr />
            <Form.Group>
              <Form.Label>
                {t('modal.body.inputs.documents_to_upload.label')}:
                <br />
                <small>
                  {t('modal.body.inputs.documents_to_upload.small_text')}
                </small>
              </Form.Label>
              <CustomFileInput
                ref={fileControlRef}
                multiple
                accept={'.pdf'}
                disabled={!hasPermission(AtAllowRights.AttachDocument)}
              />
            </Form.Group>
            <hr />
            <Form.Group>
              <Form.Label>
                {t('modal.body.inputs.initial_note.label.top')}:
              </Form.Label>
              <FloatingLabel
                controlId={'initial-note-textarea'}
                label={
                  <span>
                    {t('modal.body.inputs.initial_note.label.floating')}
                  </span>
                }
              >
                <Form.Control
                  ref={noteRef}
                  as={'textarea'}
                  placeholder={t(
                    'modal.body.inputs.initial_note.label.floating'
                  )}
                  style={{ height: '6.25rem' }}
                  disabled={!hasPermission(AtAllowRights.AddNote)}
                  maxLength={Number(MAX_NOTE_LENGTH)}
                />
              </FloatingLabel>
            </Form.Group>
          </Col>
          {folderFields.length > 0 && (
            <Col xs={12} lg={6}>
              <Form ref={folderFieldsForm} noValidate validated={true}>
                <Form.Label>
                  {t('modal.body.inputs.folder_fields.label')}:
                </Form.Label>
                {folderFields
                  .sort((a, b) => a.fieldId - b.fieldId)
                  .map((field, index, array) => (
                    <Form.Group
                      key={field.fieldId}
                      className={`${index + 1 !== array.length ? 'mb-2' : ''}`}
                    >
                      <FloatingLabel
                        controlId={field.fieldId + '-field-input'}
                        label={<span>{field.alias || field.fieldName}</span>}
                      >
                        <FieldInputControl
                          name={field.fieldId.toString()}
                          type={{
                            folder: true,
                            instanceType: field.instanceType,
                          }}
                          typeSpecificProps={
                            field.instanceType === FieldInstanceTypes.Option
                              ? {
                                  folderFieldOptions:
                                    field.fieldOptions as string[],
                                }
                              : undefined
                          }
                          placeholder={
                            field.fieldValue || (field.initialValue as string)
                          }
                          defaultValue={
                            field.fieldValue || (field.initialValue as string)
                          }
                          disabled={field.editAllowed === false}
                          onInput={() => {
                            setFolderFieldsValid(
                              folderFieldsForm.current?.checkValidity() as boolean
                            );
                          }}
                          maxLength={Number(MAX_FOLDER_FIELD_LENGTH)}
                        />
                        <Form.Control.Feedback type={'invalid'}>
                          {t('modal.body.inputs.folder_fields.invalid_text', {
                            type:
                              field.instanceType === FieldInstanceTypes.Date
                                ? 'date (with time)'
                                : field.instanceType ===
                                    FieldInstanceTypes.Number
                                  ? 'number'
                                  : 'value',
                          })}
                        </Form.Control.Feedback>
                      </FloatingLabel>
                    </Form.Group>
                  ))}
              </Form>
            </Col>
          )}
        </Row>
      </Modal.Body>
      <Modal.Footer>
        <Button variant={'secondary'} onClick={onHide}>
          {t('modal.footer.buttons.cancel.button_text')}
        </Button>
        <Button
          ref={createWorkItemButton}
          variant={'primary'}
          disabled={!workItemNameValid || !folderFieldsValid || processing}
          onClick={createNewWorkItem}
        >
          {processing ? (
            <div className={'d-flex justify-content-center align-items-center'}>
              <Spinner
                as={'span'}
                size={'sm'}
                animation={'border'}
                role={'status'}
                aria-hidden={'true'}
                className={'me-2'}
              />
              {t('modal.footer.buttons.create_work_item.creating_text')}...
            </div>
          ) : (
            <>{t('modal.footer.buttons.create_work_item.default')}</>
          )}
        </Button>
      </Modal.Footer>
    </Modal>
  );
};
