import React, {
  useCallback,
  useEffect,
  useImperativeHandle,
  useRef,
  forwardRef,
} from "react";
import { GeneralIdRef, MakeGeneralIdRef } from "../../utils/GeneralIdRef";
import formsService, {
  CreateFormInstance,
  EditFormInstance,
} from "../../modules/manager/forms/services/formsService";
import parse, { HTMLReactParserOptions, domToReact } from "html-react-parser";
import { CustomField } from "../../modules/manager/customfields/services/customFieldsService";
import { toast } from "react-toastify";
import { renderCustomField } from "./formHelpers";
import { CustomFieldValue } from "../../modules/manager/glossary/services/glossaryService";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../i18n/i18n";
import { useFormWithGuard } from "./useFormRouter";

interface TemplateFillerProps {
  templateId?: GeneralIdRef;
  formInstanceId?: GeneralIdRef;
  onValidationChanged?: () => void;
}

export interface TemplateFillerHandle {
  hasValidationErrors: () => boolean;
  Save: () => Promise<any>;
}

interface TemplateState {
  name?: string;
  templateId?: GeneralIdRef;
  version?: bigint;
  definition?: string;
}

const TemplateFiller = forwardRef<TemplateFillerHandle, TemplateFillerProps>(
  ({ templateId, formInstanceId, onValidationChanged }, ref) => {
    const form = useFormWithGuard({
      loaded: false,
      data: {},
      errors: {},
      redirect: "",
    });

    const [template, setTemplate] = React.useState<TemplateState>({
      name: undefined,
      templateId: undefined,
      version: undefined,
      definition: undefined,
    });

    const prevHasErrorsRef = useRef<boolean>(false);

    form.schema = {};

    const loadTemplate = useCallback(async () => {
      let loadedData: any;

      if (templateId !== undefined) {
        loadedData = await formsService.getForm(
          templateId?.id,
          templateId?.guid,
        );
        loadedData.templateId = undefined;
        loadedData.customFieldValues = undefined;
        loadedData.updatedVersion = undefined;
      } else if (formInstanceId !== undefined) {
        loadedData = await formsService.getFormInstance(
          formInstanceId?.id,
          formInstanceId?.guid,
        );
      } else {
        loadedData = {
          name: undefined,
          id: undefined,
          guid: undefined,
          version: undefined,
          definition: undefined,
          customFieldDefinitions: undefined,
          templateId: undefined,
          customFieldValues: undefined,
          updatedVersion: undefined,
        };
      }

      const newTemplate: TemplateState = {
        name: loadedData.name,
        templateId: MakeGeneralIdRef(loadedData.id, loadedData.guid),
        version: loadedData.version,
        definition: loadedData.definition,
      };

      const newCustomFields = loadedData.customFieldDefinitions as
        | CustomField[]
        | undefined;
      const newData = { ...form.state.data } as Record<string, any>;

      form.setCustomFieldValues(
        newData,
        loadedData.customFieldValues,
        newCustomFields ?? [],
      );

      form.setState({
        loaded: true,
        data: newData,
        customFields: newCustomFields,
      });
      setTemplate(newTemplate);
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [templateId, formInstanceId]);

    useEffect(() => {
      void loadTemplate();
    }, [loadTemplate]);

    useEffect(() => {
      if (!onValidationChanged) return;

      const hasErrors = Object.keys(form.state.errors).length > 0;
      if (prevHasErrorsRef.current !== hasErrors) {
        prevHasErrorsRef.current = hasErrors;
        onValidationChanged();
      }
    }, [form.state.errors, onValidationChanged]);

    const handleCustomFieldChange = useCallback(
      (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
        if (e.target instanceof HTMLTextAreaElement) {
          form.handleTextAreaChange(
            e as React.ChangeEvent<HTMLTextAreaElement>,
          );
        } else {
          form.handleCustomFieldChange(
            e as React.ChangeEvent<HTMLInputElement>,
          );
        }
      },
      [form],
    );

    const handleCustomFieldPickerChange = useCallback(
      (name: string, value: GeneralIdRef | CustomFieldValue[]) => {
        if (Array.isArray(value)) {
          form.handleGlossaryPickerChange(name, value);
        } else {
          form.handlePickerChange(name, value as GeneralIdRef);
        }
      },
      [form],
    );

    const getCustomFieldType = useCallback(
      (field: CustomFieldValue, customFields: CustomField[]) => {
        return form.getCustomFieldType(field as any, customFields);
      },
      [form],
    );

    const parseDefinition = useCallback(
      (definition: string, customFieldDefinitions: CustomField[]) => {
        const options: HTMLReactParserOptions = {
          replace: (domNode) => {
            const domNodeAsAny: any = domNode;
            if (domNodeAsAny.name === "span") {
              if (domNodeAsAny.attribs.fieldtype === "CustomField") {
                const customField = customFieldDefinitions.filter(
                  (x) => x.guid === domNodeAsAny.attribs.guid,
                )[0];
                return renderCustomField(
                  customField,
                  false,
                  form.state.data,
                  form.state.errors,
                  handleCustomFieldChange,
                  handleCustomFieldPickerChange,
                  getCustomFieldType,
                );
              }
            } else if (domNodeAsAny.name === "p") {
              return (
                <div className="p">
                  {domToReact(domNodeAsAny.children, options)}
                </div>
              );
            }
          },
        };

        return parse(definition, options);
      },
      [
        form.state.data,
        form.state.errors,
        handleCustomFieldChange,
        handleCustomFieldPickerChange,
        getCustomFieldType,
      ],
    );

    const hasValidationErrors = useCallback((): boolean => {
      return Object.keys(form.state.errors).length > 0;
    }, [form.state.errors]);

    const { t } = useTranslation(Namespaces.Common);

    const Save = useCallback(async () => {
      const { errors } = form.state;

      if (Object.keys(errors).length > 0) {
        toast.error(t("ThereAreErrorsOnTheForm"));
        throw new Error(t("ThereAreErrorsOnTheForm") as string);
      }

      const customFieldValues = form.CustomFieldValues();
      if (formInstanceId !== undefined) {
        if (template.templateId === undefined)
          throw Error(t("TemplateIdCannotBeNull") as string);
        if (template.version === undefined)
          throw Error(t("VersionCannotBeNull") as string);

        const editFormInstance: EditFormInstance = {
          formInstanceId,
          templateId: template.templateId,
          version: template.version,
          customFieldValues,
        };
        await formsService.putFormInstance(editFormInstance);
      } else {
        if (
          template.templateId !== undefined &&
          template.version !== undefined
        ) {
          const formInstance: CreateFormInstance = {
            templateId: template.templateId,
            version: template.version,
            customFieldValues,
          };
          return await formsService.postFormInstance(formInstance);
        }
        throw new Error(t("TemplateUnknown") as string);
      }
    }, [form, template, formInstanceId, t]);

    useImperativeHandle(ref, () => ({
      hasValidationErrors,
      Save,
    }));

    const { loaded, customFields } = form.state;

    let parsedDefinition: any;
    if (template.definition && customFields)
      parsedDefinition = parseDefinition(template.definition, customFields);
    else parsedDefinition = <></>;

    return <div className="ck-content form-editor">{parsedDefinition}</div>;
  },
);

TemplateFiller.displayName = "TemplateFiller";

export default TemplateFiller;
