import { useState, useEffect, useCallback, useContext } from 'react';
import { useWixPatternsContainer } from '@wix/bex-core/react';
import { NumberRangeFilterProps } from './NumberRangeFilter';
import { NumberInputProps } from '@wix/design-system';
import type { Translate } from '@wix/bex-core';
import { ToolbarCollectionContext } from '../ToolbarCollectionContext';

function getValidateAndFormatNumber({
  decimal,
  percision,
  min,
  max,
  translate,
}: {
  decimal: boolean;
  percision: number;
  min?: number;
  max?: number;
  translate: Translate;
}) {
  const validateAndFormatNumber = (
    value?: number | null,
  ): { value: number | null; error: string | null } => {
    if (!value && value !== 0) {
      return { value: null, error: null };
    }

    let error = null;
    const num = decimal ? parseFloat(value.toString()) : value;

    if (isNaN(num)) {
      return { value: null, error };
    }

    if (min !== undefined && num < min) {
      error = translate('cairo.filtersPanel.numRange.minRange.consumer.error', {
        minAmount: min,
      });
      return { value: min, error };
    }

    if (max !== undefined && num > max) {
      error = translate('cairo.filtersPanel.numRange.maxRange.consumer.error', {
        maxAmount: max,
      });
      return { value: max, error };
    }

    return decimal
      ? { value: Number(num.toFixed(percision)), error }
      : { value: num, error };
  };

  return { validateAndFormatNumber };
}

function useInputState(initialValue?: number | null) {
  const [value, setValue] = useState<number | null>(initialValue ?? null);
  const [error, setError] = useState<string | null>(null);

  return { value, setValue, error, setError };
}

function useDebounce<T extends (...args: any[]) => void>(
  callback: T,
  delay: number,
) {
  const container = useWixPatternsContainer();
  const debouncedCallback = useCallback(
    container.lodash.debounce(callback, delay),
    [callback, delay],
  );

  return debouncedCallback;
}

export function useNumberRangeFilter({
  filter,
  decimal = false,
  min,
  max,
}: NumberRangeFilterProps) {
  const { translate: t } = useWixPatternsContainer();

  const percision = decimal ? 2 : 0;

  const { collection } = useContext(ToolbarCollectionContext) ?? {};
  const {
    value: fromValue,
    setValue: setFromValue,
    error: fromError,
    setError: setFromError,
  } = useInputState(filter.value.from);

  const {
    value: toValue,
    setValue: setToValue,
    error: toError,
    setError: setToError,
  } = useInputState(filter.value.to);

  const step = decimal ? 0.1 : 1;

  const handleInputChange = ({
    from,
    to,
    isFrom = true,
  }: {
    from?: number | null;
    to?: number | null;
    isFrom: boolean;
  }) => {
    if (isFrom) {
      setFromValue(from ?? null);
    } else {
      setToValue(to ?? null);
    }
  };

  const updateFilterValue = ({
    from,
    to,
  }: {
    from?: number | null;
    to?: number | null;
  }) => {
    const { validateAndFormatNumber } = getValidateAndFormatNumber({
      decimal,
      percision,
      min,
      max,
      translate: t,
    });
    const { value: validatedFrom, error: fromError } =
      validateAndFormatNumber(from);
    const { value: validatedTo, error: toError } = validateAndFormatNumber(to);

    setFromError(null);
    setToError(null);

    if (fromError) {
      setFromError(fromError);
      return;
    }

    if (toError) {
      setToError(toError);
      return;
    }

    if (
      validatedFrom !== null &&
      validatedTo !== null &&
      validatedFrom > validatedTo
    ) {
      // Only set error for the most recently changed value
      if (fromValue !== filter.value?.from) {
        setFromError(t('cairo.filtersPanel.numRange.minRange.error'));
      } else {
        setToError(t('cairo.filtersPanel.numRange.maxRange.error'));
      }
      return;
    }

    if (validatedFrom !== fromValue) {
      setFromValue(validatedFrom);
    }
    if (validatedTo !== toValue) {
      setToValue(validatedTo);
    }

    filter.changeValue(
      { from: validatedFrom, to: validatedTo },
      {
        emitEvents: ['beforeRefresh', 'refresh'],
        clickedValueKey: filter.itemKey({
          from: validatedFrom,
          to: validatedTo,
        }),
      },
    );
  };

  const debouncedUpdateFilters = useDebounce(updateFilterValue, 500);

  useEffect(() => {
    const from =
      typeof fromValue === 'number'
        ? parseFloat(fromValue.toFixed(percision))
        : null;
    const to =
      typeof toValue === 'number'
        ? parseFloat(toValue.toFixed(percision))
        : null;

    debouncedUpdateFilters({ from, to });
    return () => {
      debouncedUpdateFilters.cancel();
    };
  }, [fromValue, toValue]);

  const onBeforeClearFilters = useCallback(() => {
    setFromValue(null);
    setToValue(null);
  }, [setFromValue, setToValue]);

  useEffect(() => {
    if (filter.value.from === null && filter.value.to === null) {
      onBeforeClearFilters();
    }
  }, [filter.value.from, filter.value.to, onBeforeClearFilters]);

  useEffect(() => {
    if (!collection) {
      return;
    }
    // This is needed because we manage the filter value manually in order to have debounced filter updates
    collection.emitter.on('beforeClearFilters', onBeforeClearFilters);
    return () => {
      collection.emitter.off('beforeClearFilters', onBeforeClearFilters);
    };
  }, [collection?.emitter, onBeforeClearFilters]);

  const commonInputProps: Partial<NumberInputProps> = {
    step,
    min,
    max,
  };

  return {
    fromValue,
    toValue,
    handleInputChange,
    placeholder: {
      min: t('cairo.filtersPanel.numRange.minRange-from.placeholder'),
      max: t('cairo.filtersPanel.numRange.maxRange-to.placeholder'),
    },
    commonInputProps,
    fromError,
    toError,
    t,
  };
}
