import "./style.pcss";
import { bubble } from "@web3r/flowerkit/evt";
import { getDebouncedFn } from "@web3r/flowerkit/fn";
import { isObjEmpty } from "@web3r/flowerkit/obj";
import type {
  FC,
  FormEvent, FormEventHandler,
  Ref,
  ReactElement,
} from "react";
import {
  useCallback,
  useMemo,
  useRef,
  useState,
  useImperativeHandle,
} from "react";
import { FormRow } from "./components/formRow";
import type {
  IForm, IFormRef, IFormCfg, TFormValues, 
} from "./config/types";
import { FormContext } from "./context";
import { isStateIncludesOtherState, getFormInitialValues, getCustomisationForCols } from "./lib";
import type { IComponentBaseProps } from "../../types/components";
import { getUserControlledFields } from "../../utils/getUserControlledFields";
import { useCN } from "../../utils/react/useCN";
import { Input } from "../input";

/**
 * Компонент формы
 * @returns {ReactElement}
 */
const Form: FC<IForm & IComponentBaseProps> = ({
  id = null,
  rows = [],
  cfg = {},
  onSubmit = (e: FormEvent) => e.preventDefault(),
  onReset = null,
  baseClass = "form",
  children = null,
  setCustomComponent = null,
  setInputProps = null,
  setVisibility = null,
  extraClasses = {},
  extraAttrs = {},
  isCustomWrapper = false,
  isUseValuesCompare = false,
  initialValues = {},
  auxParams = {},
  isHidden = false,
  headerSlot = null,
  footerSlot = null,
  ref = null,
}): ReactElement => {

  const { getCN } = useCN(baseClass);

  const formRef = useRef<HTMLFormElement | null>(null);

  const [ formValues, setFormValues ] = useState<TFormValues>(
    () => (rows?.length ? getFormInitialValues({ formRows: rows }) : initialValues || {})
  );

  const stateActions = useMemo(() => ({ setFormValues }), []);

  const onChange = useCallback(({
    target,
    detail,
  }: {
    target: { name: string; value: unknown; };
    detail?: { name?: string; value?: unknown; } | unknown;
  }) => {
    const hasDetailName = typeof detail === "object" && detail !== null && "name" in detail;
    const source = hasDetailName ? (detail as { name?: string; value?: unknown; }) : target;
    const { name, value } = source;
    name && setFormValues((state) => ({ ...state, [name]: value as TFormValues[string] }));
  }, []);

  const handleReset = useCallback((event: FormEvent) => {
    if (typeof onReset === "function") {
      onReset({ event, data: { formValues }, stateActions });
    } else {
      getUserControlledFields(formRef.current)?.forEach((node) => bubble(node, "reset", { bubbles: false }));
    }
  }, [ onReset, formValues, stateActions ]);

  const isFormElementVisible = useMemo(() => {
    return isUseValuesCompare
      ? ({ isShowForValues }: { isShowForValues?: Record<string, unknown>; }) =>
        isShowForValues ? isStateIncludesOtherState({
          firstState: isShowForValues,
          secondState: formValues,
        }) : true
      : setVisibility;
  }, [ formValues, isUseValuesCompare, setVisibility ]);

  const formRows = useMemo(() => {
    if (!setCustomComponent && !setInputProps && !isUseValuesCompare) {
      return rows;
    } else {
      return rows?.map((row) => {
        const nextRow = {
          ...row,
          cols: row.cols?.map((col) => ({
            ...col,
            field: col.field ? {
              ...col.field,
              input: col.field.input ? { ...col.field.input } : col.field.input,
            } : col.field,
          })),
        };

        if (typeof isFormElementVisible === "function") {
          nextRow.isVisible = isFormElementVisible(nextRow);
        }

        getCustomisationForCols({
          cols: nextRow.cols,
          setCustomComponent,
          setInputProps,
          isFormElementVisible,
        });

        return nextRow;
      });
    }
  }, [ rows, setCustomComponent, setInputProps, isFormElementVisible, isUseValuesCompare ]);

  const visibleRows = useMemo(() => {
    const keyCounter = new Map<string, number>();
    return formRows?.filter(({ isVisible = true }) => isVisible).map((row) => {
      const keyBase = JSON.stringify({
        columns: row.columns,
        colsCount: row.cols?.length ?? 0,
        isShowForValues: row.isShowForValues,
      });
      const keyOrder = (keyCounter.get(keyBase) ?? 0) + 1;
      keyCounter.set(keyBase, keyOrder);
      return <FormRow key={`${keyBase}_${keyOrder}`} {...row} />;
    });
  }, [ formRows ]);

  const formBody = useMemo(() => (
    <div className={getCN("body")}>
      {children || visibleRows}
    </div>
  ), [ getCN, children, visibleRows ]);

  const formHeader = useMemo(() => (
    (!!headerSlot || (!!auxParams && !isObjEmpty(auxParams))) && (
      <div className={getCN("header", { isHidden: !headerSlot })}>
        {Object.entries(auxParams || {}).map(([ name, value ]) => (
          <Input key={`auxParam_${name}`} type={"hidden"} name={name} value={value?.toString()} />
        ))}
        {headerSlot}
      </div>
    )
  ), [ headerSlot, auxParams, getCN ]);

  const formFooter = useMemo(() => (
    !!footerSlot && <div className={getCN("footer")}>{footerSlot}</div>
  ), [ footerSlot, getCN ]);

  const formExtraClasses = { ...extraClasses, isHidden };

  useImperativeHandle(ref, (): IFormRef => ({
    el: formRef.current,
    ref: null,
    utils: null,
    getData: () => ({ value: formValues }),
    stateActions,
  }), [ formValues, stateActions ]);

  const debouncedChangeHandler: Function = getDebouncedFn(onChange);
  const debouncedResetHandler: Function = getDebouncedFn(handleReset, 300, true);

  const ctx = useMemo(() => ({
    getCN, baseClass, formValues, setFormValues,
  }), [ getCN, baseClass, formValues, setFormValues ]);

  return (
    <FormContext value={ctx}>
      {isCustomWrapper ? (
        <div role={"form"} id={id || undefined} ref={formRef as Ref<HTMLDivElement>} className={getCN("", formExtraClasses)} {...extraAttrs}>
          {formHeader}
          {formBody}
          {formFooter}
        </div>
      ) : (
        <form
          onChange={debouncedChangeHandler as FormEventHandler<HTMLFormElement>}
          hidden={isHidden}
          id={id || undefined}
          ref={formRef}
          onReset={debouncedResetHandler as FormEventHandler<HTMLFormElement>}
          onSubmit={onSubmit}
          autoComplete={"off"}
          className={getCN("", formExtraClasses)}
          data-js-form={JSON.stringify(cfg)}
          noValidate
          {...extraAttrs}
        >
          {formHeader}
          {formBody}
          {formFooter}
        </form>
      )}
    </FormContext>
  );
};

export type { IForm, IFormCfg, IFormRef };

export { Form };
