/**
 * @file FieldsTab.tsx - Tab component for viewing and editing metadata fields.
 * When a folder node is selected, displays editable folder fields with type-aware validation (text, number, date, option).
 * When a document node is selected, shows a document template selector and the corresponding template fields.
 * Tracks which fields have changed, validates the form inline, and submits updates via the folder field or document field API endpoints.
 * Supports auto-save via the saveWorkItem signal and refreshes template fields on reauthentication token changes.
 */

import { useEffect, useRef, useState, type ChangeEvent, type FC } from 'react';
import { useNamedAnchorRemainingHeight } from '../../../../hooks/useNamedAnchorRemainingHeight.ts';
import type {
  DocumentNode,
  FolderNode,
} from '../../DetailsFolderPanel/sections/FolderSection/FolderTree.tsx';
import { Alert, Button, FloatingLabel, Form } from 'react-bootstrap';
import {
  type FolderField,
  updateFolderFields,
} from '../../../../api/folderField.ts';
import { FieldInstanceTypes } from '../../../../types/fieldInstance.ts';
import { toast } from 'react-toastify';
import {
  type DocumentFieldType,
  type DocumentFieldTypeName,
  DocumentFieldTypes,
} from '../../../../types/documentField.ts';
import {
  type DocumentTemplate,
  type DocumentTemplateField,
  listTemplateFields,
} from '../../../../api/documentTemplate.ts';
import { updateDocumentFields } from '../../../../api/document.ts';
import { useWorkItem } from '../../../../contexts/WorkItemContext.tsx';
import { useProcessingUI } from '../../../../contexts/ProcessingUIContext.tsx';
import { useReauth } from '../../../../contexts/ReauthContext.tsx';
import { type Document } from '../../../../api/document.ts';
import {
  FieldInputControl,
  toDatetimeLocalString,
} from '../../../shared/FieldInputControl.tsx';
import { Icon } from '../../../Icon.tsx';
import {
  MAX_DOCUMENT_TEMPLATE_FIELD_LENGTH,
  MAX_FOLDER_FIELD_LENGTH,
} from '../../../../constants.ts';
import { useTranslation } from 'react-i18next';
import type { FetchError } from '../../../../utils/fetchHelper.ts';

/**
 * Props for the FieldsTab component.
 */
interface FieldsTabProps {
  /** Whether the tab content is currently visible (controls d-none CSS class) */
  show: boolean;
}

/**
 * Fields tab within the Fields/Notes/Tracking panel that displays folder or document template fields depending on the selected FolderTree node, with inline validation and API update support.
 */
export const FieldsTab: FC<FieldsTabProps> = ({ show }) => {
  // Used to trigger data refresh
  const { reauthToken, oldReauthToken, setOldReauthToken } = useReauth();

  const { registerAnchor, maxHeight } = useNamedAnchorRemainingHeight('fields');

  // Required contexts for work item processing
  const {
    workItem,
    folderFields,
    setFolderFields,
    documentTemplates,
    documents,
    setDocuments,
  } = useWorkItem();
  const {
    selectedNode,
    setSelectedNodeFieldsHaveChanged,
    isMobile,
    saveWorkItem,
  } = useProcessingUI();

  // Dynamic refs for inputs used when editing folder fields
  const folderFieldInputRefs = useRef<
    Map<number, HTMLInputElement | HTMLSelectElement>
  >(new Map());

  const folderFieldsForm = useRef<HTMLFormElement | null>(null);
  const [folderFieldsValidated, setFolderFieldsValidated] = useState(false);
  const [folderFieldsValid, setFolderFieldsValid] = useState(false);
  // Track which folder field IDs have been modified by the user
  const [changedFolderFieldIds, setChangedFolderFieldIds] = useState<
    Set<number>
  >(new Set());
  // Sync the "fields have changed" flag and validate the form whenever changed fields update
  useEffect(() => {
    setSelectedNodeFieldsHaveChanged(changedFolderFieldIds.size > 0);

    // Check form validity
    if (changedFolderFieldIds.size > 0) {
      setFolderFieldsValid(
        folderFieldsForm.current?.checkValidity() as boolean
      );
      setFolderFieldsValidated(true);
    } else {
      setFolderFieldsValid(true);
      setFolderFieldsValidated(false);
    }
  }, [changedFolderFieldIds]);
  useEffect(() => {
    if ((selectedNode as DocumentNode)?.documentId !== undefined) {
      setChangedFolderFieldIds(new Set());
    }
  }, [selectedNode]);

  /**
   * Assembles changed folder field values from input refs and submits them via the API.
   */
  const saveFolderFields = () => {
    const updatedFields: FolderField[] = [];
    // Assemble folder field objects with new values
    folderFieldInputRefs.current.forEach((element, key) => {
      const found = folderFields.find((field) => field.fieldId === key);
      if (found) {
        let hasChanged = false;
        switch (found.instanceType) {
          case FieldInstanceTypes.Date: {
            hasChanged =
              new Date(found.fieldValue).getTime() !==
              new Date(element.value).getTime();
            break;
          }
          case FieldInstanceTypes.Number: {
            hasChanged =
              found.fieldValue === null ||
              Number(found.fieldValue) !== Number(element.value);
            break;
          }
          default: {
            hasChanged = String(found.fieldValue) !== String(element.value);
            if (found.instanceType === FieldInstanceTypes.Option) {
              hasChanged = hasChanged && String(element.value) !== '';
            }
            break;
          }
        }

        if (hasChanged) {
          updatedFields.push({
            ...found,
            fieldValue: element.value,
          });
        }
      }
    });

    updateFolderFields(workItem?.workItemId as number, updatedFields)
      .then(() => {
        setFolderFields(
          Object.values(
            Object.fromEntries(
              [...folderFields, ...updatedFields].map((field) => [
                field.fieldId,
                { ...field },
              ])
            )
          )
        );

        toast.success(t('functions.save_folder_fields.success'));

        setChangedFolderFieldIds(new Set());
      })
      .catch(() => {
        toast.error(t('functions.save_folder_fields.errors.failed_to_update'));
      });
  };

  // Tracks the currently selected document template ID (0 = no template selected)
  const [selectedTemplateId, setSelectedTemplateId] = useState(0);
  const updateSelectedTemplate = (event: ChangeEvent<HTMLSelectElement>) => {
    setSelectedTemplateId(Number(event.target.value));
    setChangedTemplateFieldIds(new Set());
  };
  useEffect(() => {
    if ((selectedNode as DocumentNode)?.documentId !== undefined) {
      const document = documents.find(
        (_document) =>
          _document.documentId === (selectedNode as DocumentNode).documentId
      );

      const template = documentTemplates.find(
        (_template) => _template.templateId === document?.documentTemplateId
      );
      if (template) {
        setSelectedTemplateId(document?.documentTemplateId as number);
      } else {
        setSelectedTemplateId(0);
      }

      setChangedTemplateFieldIds(new Set());
    }
  }, [selectedNode]);

  // Retrieve document template fields and store in state
  const [templateFields, setTemplateFields] = useState<DocumentTemplateField[]>(
    []
  );
  const retrieveTemplateFields = async () => {
    // Reset input refs
    templateFieldInputRefs.current = new Map();

    if (
      (selectedNode as DocumentNode).documentId !== undefined &&
      selectedTemplateId
    ) {
      // Retrieve the template fields
      listTemplateFields(selectedTemplateId)
        .then((result) => {
          setTemplateFields(result);
        })
        .catch((error: FetchError) => {
          toast.error(error.message);
          setTemplateFields([]);
        });
    }
  };
  useEffect(() => {
    const template = documentTemplates.find(
      (_template) => _template.templateId === selectedTemplateId
    );
    if (template) {
      retrieveTemplateFields();
    }
  }, [selectedTemplateId]);

  // Dynamic refs for inputs used when editing document template fields
  const templateFieldInputRefs = useRef<
    Map<number, HTMLInputElement | HTMLSelectElement>
  >(new Map());

  const documentTemplateFieldsForm = useRef<HTMLFormElement | null>(null);
  const [documentTemplateFieldsValidated, setDocumentTemplateFieldsValidated] =
    useState(false);
  const [documentTemplateFieldsValid, setDocumentTemplateFieldsValid] =
    useState(false);
  // Track which template field IDs have been modified by the user
  const [changedTemplateFieldIds, setChangedTemplateFieldIds] = useState<
    Set<number>
  >(new Set());
  // Sync the "fields have changed" flag and validate the document template fields form
  useEffect(() => {
    if ((selectedNode as DocumentNode)?.documentId !== undefined) {
      const document = documents.find(
        (_document) =>
          _document.documentId === (selectedNode as DocumentNode).documentId
      );
      const template = documentTemplates.find(
        (_template) => _template.templateId === selectedTemplateId
      );

      // Add empty boolean fields (no match in existing document fields) to changedTemplateFieldIds so validation works correctly
      const emptyBooleanFieldIds = templateFields
        .filter(
          (field) =>
            field.isEditable && field.fieldType === DocumentFieldTypes.Boolean
        )
        .map((field) => {
          const matchingDocumentField = document?.documentTemplateFields?.find(
            (_field) => _field.fieldId === field.fieldId
          );
          return !matchingDocumentField ||
            matchingDocumentField.fieldValue === ''
            ? field.fieldId
            : null;
        })
        .filter((id): id is number => id !== null);
      if (
        document?.documentTemplateId !== selectedTemplateId &&
        emptyBooleanFieldIds.some((id) => !changedTemplateFieldIds.has(id))
      ) {
        setChangedTemplateFieldIds((prev) => {
          const next = new Set(prev);
          emptyBooleanFieldIds.forEach((id) => next.add(id));
          return next;
        });
      }

      // Handle template change case
      if (
        template &&
        document?.documentTemplateId !== selectedTemplateId &&
        templateFields.filter((field) => field.isEditable).length
      ) {
        setSelectedNodeFieldsHaveChanged(true);

        // Check form validity (when template selection has changed)
        setDocumentTemplateFieldsValid(
          documentTemplateFieldsForm.current?.checkValidity() as boolean
        );
        setDocumentTemplateFieldsValidated(true);

        return;
      }
    }

    setSelectedNodeFieldsHaveChanged(changedTemplateFieldIds.size > 0);

    // Check form validity (when template selection has not changed)
    if (changedTemplateFieldIds.size > 0) {
      setDocumentTemplateFieldsValid(
        documentTemplateFieldsForm.current?.checkValidity() as boolean
      );
      setDocumentTemplateFieldsValidated(true);
    } else {
      setDocumentTemplateFieldsValid(true);
      setDocumentTemplateFieldsValidated(false);
    }
  }, [changedTemplateFieldIds, templateFields]);

  /**
   * Assembles changed template field values from input refs and submits them via the API.
   * Also handles creating new field definitions when a field exists in the template but not yet on the document.
   *
   * @param document Document for fields to be saved for
   */
  const saveDocumentTemplateFields = (document: Document) => {
    let updatedFields: DocumentTemplateField[] = [];
    // Assemble template field objects with new values
    templateFieldInputRefs.current.forEach((element, key) => {
      let found = document?.documentTemplateFields?.find(
        (field) => field.fieldId === key
      );

      if (found) {
        // If document already includes field definition
        if (found.fieldValue !== element.value) {
          updatedFields.push({
            ...found,
            fieldValue: element.value,
          });
        }
      } else {
        // Must create field definition
        found = templateFields.find((field) => field.fieldId === key);
        if (found) {
          updatedFields.push({
            fieldId: found.fieldId as number,
            fieldName: found.fieldName as string,
            fieldType: found.fieldType as DocumentFieldType,
            fieldTypeName: found.fieldTypeName as DocumentFieldTypeName,
            fieldSize: found.fieldSize as number,
            fieldValue: element.value,
          });
        }
      }
    });

    // Strip empty values
    updatedFields = updatedFields.filter(
      (field) =>
        field.fieldType === DocumentFieldTypes.String || field.fieldValue !== ''
    );

    const template = documentTemplates.find(
      (_template) => _template.templateId === selectedTemplateId
    ) as DocumentTemplate;

    updateDocumentFields(
      document?.documentId as number,
      template,
      updatedFields
    )
      .then(() => {
        setDocuments(
          documents.map((_document) => {
            if (_document.documentId === document?.documentId) {
              _document.documentTemplateId = template.templateId as number;
              _document.documentTemplateName = template.templateName as string;

              _document.documentTemplateFields = Object.values(
                Object.fromEntries(
                  [
                    ...(
                      _document.documentTemplateFields as DocumentTemplateField[]
                    ).filter((field) =>
                      templateFields
                        ?.map((_field) => _field.fieldId)
                        .includes(field.fieldId)
                    ),
                    ...updatedFields,
                  ].map((field) => [field.fieldId, { ...field }])
                )
              );
            }
            return _document;
          })
        );

        toast.success(t('functions.save_document_template_fields.success'));

        setChangedTemplateFieldIds(new Set());
      })
      .catch(() => {
        toast.error(
          t('functions.save_document_template_fields.errors.failed_to_update')
        );
      });
  };

  // Listen for saveWorkItem trigger and save folder/document fields so work item can close
  useEffect(() => {
    if (saveWorkItem) {
      if (changedFolderFieldIds.size > 0) {
        saveFolderFields();
      } else {
        const document = documents.find(
          (_document) =>
            _document.documentId === (selectedNode as DocumentNode).documentId
        );
        if (
          selectedTemplateId !== 0 &&
          selectedTemplateId !== document?.documentTemplateId
        ) {
          if (changedTemplateFieldIds.size === 0) {
            setSelectedNodeFieldsHaveChanged(false);
          } else {
            saveDocumentTemplateFields(document as Document);
          }
        } else if (changedTemplateFieldIds.size > 0) {
          saveDocumentTemplateFields(document as Document);
        }
      }
    }
  }, [saveWorkItem]);

  // Trigger refresh of page data
  useEffect(() => {
    const tokenChanged = reauthToken !== oldReauthToken;

    if (tokenChanged) {
      retrieveTemplateFields().finally(() => {
        if (tokenChanged) {
          setOldReauthToken(reauthToken);
        }
      });
    }
  }, [reauthToken, oldReauthToken, setOldReauthToken]);

  const { t } = useTranslation(undefined, {
    keyPrefix: 'components.processing.fields_notes_tracking_panel.tabs.fields',
  });

  return (
    <div className={show ? '' : 'd-none'}>
      <div ref={registerAnchor('fields')} />
      <div
        className={'overflow-y-auto p-1'}
        style={{ maxHeight: isMobile ? undefined : maxHeight }}
      >
        {/* Folder fields */}
        {(selectedNode as FolderNode)?.folderId !== undefined && (
          <>
            {folderFields.length ? (
              <Form
                ref={folderFieldsForm}
                noValidate
                validated={folderFieldsValidated}
              >
                {folderFields
                  .sort((a, b) => a.fieldId - b.fieldId)
                  .map((field) => (
                    <Form.Group key={field.fieldId} className={'mb-2'}>
                      <FloatingLabel
                        controlId={field.fieldId + '-field-input'}
                        label={<span>{field.alias || field.fieldName}</span>}
                      >
                        <FieldInputControl
                          type={{
                            folder: true,
                            instanceType: field.instanceType,
                          }}
                          typeSpecificProps={
                            field.instanceType === FieldInstanceTypes.Option
                              ? {
                                  folderFieldOptions:
                                    field.fieldOptions as string[],
                                }
                              : undefined
                          }
                          ref={(element) => {
                            if (element) {
                              folderFieldInputRefs.current.set(
                                field.fieldId,
                                element
                              );
                            } else {
                              folderFieldInputRefs.current.delete(
                                field.fieldId
                              );
                            }
                          }}
                          placeholder={field.fieldValue ?? field.initialValue}
                          defaultValue={field.fieldValue ?? field.initialValue}
                          disabled={field.editAllowed === false}
                          onChange={(event) => {
                            const oldValue =
                              field.fieldValue ?? field.initialValue;
                            const newValue = event.target.value;

                            if (
                              newValue !==
                              (field.instanceType === FieldInstanceTypes.Date
                                ? toDatetimeLocalString(oldValue)
                                : oldValue)
                            ) {
                              setChangedFolderFieldIds((prev) =>
                                new Set(prev).add(field.fieldId)
                              );
                            } else {
                              setChangedFolderFieldIds((prev) => {
                                const next = new Set(prev);
                                next.delete(field.fieldId);
                                return next;
                              });
                            }
                          }}
                          maxLength={Number(MAX_FOLDER_FIELD_LENGTH)}
                        />
                        <Form.Control.Feedback type={'invalid'}>
                          {t(
                            'inputs.folder_fields.field_control.invalid_text',
                            {
                              type:
                                field.instanceType === FieldInstanceTypes.Date
                                  ? 'date (with time)'
                                  : field.instanceType ===
                                      FieldInstanceTypes.Number
                                    ? 'number'
                                    : 'value',
                            }
                          )}
                        </Form.Control.Feedback>
                      </FloatingLabel>
                    </Form.Group>
                  ))}
                <Button
                  variant={'primary'}
                  type={'submit'}
                  className={'w-100 mt-2'}
                  onClick={(event) => {
                    event.preventDefault();
                    saveFolderFields();
                  }}
                  disabled={!changedFolderFieldIds.size || !folderFieldsValid}
                >
                  <div
                    className={
                      'd-flex justify-content-center align-items-center'
                    }
                  >
                    <Icon iconName={'ClipboardCheck'} size={20} />
                    <span className={'ms-2'}>
                      {t('buttons.folder_fields.save.button_text')}
                    </span>
                  </div>
                </Button>
              </Form>
            ) : (
              <Alert variant={'light'} className={'text-center mb-0'}>
                {t('alerts.folder_fields.none')}
              </Alert>
            )}
          </>
        )}

        {/* Document fields */}
        {(selectedNode as DocumentNode)?.documentId !== undefined &&
          (() => {
            const document = documents.find(
              (_document) =>
                _document.documentId ===
                (selectedNode as DocumentNode).documentId
            );

            return (
              <>
                {documentTemplates.length ? (
                  <>
                    <Form.Group className={'pb-1'}>
                      <FloatingLabel
                        controlId={'template-select'}
                        label={t(
                          'inputs.template_fields.template_select.label'
                        )}
                      >
                        <Form.Select
                          value={selectedTemplateId}
                          onChange={updateSelectedTemplate}
                        >
                          <option value={0} disabled>
                            {t(
                              'inputs.template_fields.template_select.options.default'
                            )}
                          </option>
                          {documentTemplates.map((template) => (
                            <option
                              key={template.templateId}
                              value={template.templateId}
                            >
                              {template.templateName}
                            </option>
                          ))}
                        </Form.Select>
                      </FloatingLabel>
                    </Form.Group>
                    {selectedTemplateId !== 0 && (
                      <>
                        <hr />
                        <Form
                          ref={documentTemplateFieldsForm}
                          noValidate
                          validated={documentTemplateFieldsValidated}
                        >
                          {templateFields.length ? (
                            <>
                              {templateFields
                                .sort(
                                  (a, b) =>
                                    (a.fieldId as number) -
                                    (b.fieldId as number)
                                )
                                .map((field) => {
                                  // Must find the matching field instance from the document
                                  let matchingDocumentField =
                                    document?.documentTemplateFields?.find(
                                      (_field) =>
                                        _field.fieldId === field.fieldId
                                    );
                                  // Set matchingDocumentField manually if no match found
                                  if (!matchingDocumentField) {
                                    matchingDocumentField = {
                                      fieldId: field.fieldId as number,
                                      fieldName: field.fieldName as string,
                                      fieldType:
                                        field.fieldType as DocumentFieldType,
                                      fieldTypeName:
                                        field.fieldTypeName as DocumentFieldTypeName,
                                      fieldSize: field.fieldSize as number,
                                      fieldValue: field.fieldValue ?? '',
                                      isEditable: field.isEditable as boolean,
                                    };
                                  }

                                  const key =
                                    document?.documentId +
                                    '-' +
                                    matchingDocumentField.fieldId;

                                  return (
                                    <Form.Group
                                      // Unique key forces re-render when selected document changes
                                      // Ensures value changes
                                      key={key}
                                      className={'mb-2'}
                                    >
                                      <FloatingLabel
                                        controlId={key + '-field-input'}
                                        label={<span>{field.fieldName}</span>}
                                      >
                                        <FieldInputControl
                                          type={{
                                            document: true,
                                            instanceType:
                                              matchingDocumentField.fieldType as DocumentFieldType,
                                          }}
                                          ref={(element) => {
                                            if (element) {
                                              templateFieldInputRefs.current.set(
                                                matchingDocumentField.fieldId as number,
                                                element
                                              );
                                            } else {
                                              templateFieldInputRefs.current.delete(
                                                matchingDocumentField.fieldId as number
                                              );
                                            }
                                          }}
                                          placeholder={
                                            matchingDocumentField.fieldValue
                                          }
                                          defaultValue={
                                            matchingDocumentField.fieldValue
                                          }
                                          maxLength={
                                            field.fieldSize ||
                                            Number(
                                              MAX_DOCUMENT_TEMPLATE_FIELD_LENGTH
                                            ) ||
                                            undefined
                                          }
                                          disabled={field.isEditable === false}
                                          onChange={(event) => {
                                            const newValue = event.target.value;

                                            if (
                                              newValue !==
                                              (matchingDocumentField.fieldValue &&
                                              matchingDocumentField.fieldType ===
                                                DocumentFieldTypes.Date
                                                ? toDatetimeLocalString(
                                                    matchingDocumentField.fieldValue
                                                  )
                                                : matchingDocumentField.fieldValue)
                                            ) {
                                              setChangedTemplateFieldIds(
                                                (prev) =>
                                                  new Set(prev).add(
                                                    matchingDocumentField.fieldId as number
                                                  )
                                              );
                                            } else {
                                              setChangedTemplateFieldIds(
                                                (prev) => {
                                                  const next = new Set(prev);
                                                  next.delete(
                                                    matchingDocumentField.fieldId as number
                                                  );
                                                  return next;
                                                }
                                              );
                                            }
                                          }}
                                          required={
                                            (selectedTemplateId !==
                                              document?.documentTemplateId &&
                                              !changedTemplateFieldIds.size) ||
                                            changedTemplateFieldIds.has(
                                              matchingDocumentField.fieldId as number
                                            )
                                          }
                                        />
                                        <Form.Control.Feedback type={'invalid'}>
                                          {selectedTemplateId !==
                                            document?.documentTemplateId &&
                                          !changedTemplateFieldIds.size ? (
                                            <span>
                                              {t(
                                                'inputs.template_fields.field_control.invalid_text.missing_one'
                                              )}
                                            </span>
                                          ) : (
                                            <span>
                                              {t(
                                                'inputs.template_fields.field_control.invalid_text.default',
                                                {
                                                  type:
                                                    matchingDocumentField.fieldType ===
                                                    DocumentFieldTypes.Date
                                                      ? 'date (with time)'
                                                      : matchingDocumentField.fieldType ===
                                                          DocumentFieldTypes.Number
                                                        ? 'number'
                                                        : matchingDocumentField.fieldType ===
                                                            DocumentFieldTypes.Integer
                                                          ? 'integer'
                                                          : 'value',
                                                }
                                              )}
                                            </span>
                                          )}
                                        </Form.Control.Feedback>
                                      </FloatingLabel>
                                    </Form.Group>
                                  );
                                })}
                            </>
                          ) : (
                            <Alert
                              variant={'light'}
                              className={'text-center mb-2'}
                            >
                              {t('alerts.template_fields.no_fields')}
                            </Alert>
                          )}
                          <Button
                            variant={'primary'}
                            type={'submit'}
                            className={'w-100 mt-2'}
                            disabled={
                              !templateFields.filter(
                                (field) => field.isEditable
                              ).length ||
                              (!changedTemplateFieldIds.size &&
                                selectedTemplateId ===
                                  document?.documentTemplateId) ||
                              !documentTemplateFieldsValid
                            }
                            onClick={(event) => {
                              event.preventDefault();
                              saveDocumentTemplateFields(document as Document);
                            }}
                          >
                            <div
                              className={
                                'd-flex justify-content-center align-items-center'
                              }
                            >
                              <Icon iconName={'ClipboardCheck'} size={20} />
                              <span className={'ms-2'}>
                                {t('buttons.template_fields.save.button_text')}
                              </span>
                            </div>
                          </Button>
                        </Form>
                      </>
                    )}
                  </>
                ) : (
                  <Alert variant={'light'} className={'text-center mb-0'}>
                    {t('alerts.template_fields.no_templates')}
                  </Alert>
                )}
              </>
            );
          })()}
      </div>
    </div>
  );
};
