import { Combobox, ScrollArea, useVirtualizedCombobox } from '@mantine/core';
import { useVirtualizer } from '@tanstack/react-virtual';
import React, { useCallback, useEffect, useImperativeHandle, useRef } from 'react';

import type { BoxerItem, BoxerProps, BoxerRef } from './Boxer.types';

import { BoxerProvider, useBoxerStore } from './Boxer.store';
import BoxerTarget from './BoxerTarget';
import useBoxerOptions from './hooks/useBoxerOptions';

const BoxerInner = React.forwardRef<BoxerRef>((_, ref) => {
  // Component Refs
  const parentRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const valueRef = useRef<any>(null);
  const bufferRef = useRef<any>(null);

  // Component store State
  const {
    boxerData,
    clearable,
    comboBoxProps,
    dropDownProps,
    error,
    fetchData,
    fetchMoreOnBottomReached,
    input,
    isFetching,
    label,
    mah,
    multiSelect,
    onBufferChange,
    onChange,
    opened,
    openOnClear,
    placeholder,
    scrollAreaProps,
    search,
    selectedOptionIndex,
    selectFirst,
    setInput,
    setOpened,
    setSearch,
    setSelectedOptionIndex,
    showAll,
    value,
  } = useBoxerStore((state) => ({
    boxerData: state.boxerData,
    clearable: state.clearable,
    comboBoxProps: state.comboBoxProps,
    dropDownProps: state.dropDownProps,
    error: state.error,
    fetchData: state.fetchData,
    fetchMoreOnBottomReached: state.fetchMoreOnBottomReached,
    input: state.input,
    isFetching: state.isFetching,
    label: state.label,
    mah: state.mah,
    multiSelect: state.multiSelect,
    onBufferChange: state.onBufferChange,
    onChange: state.onChange,
    opened: state.opened,
    openOnClear: state.openOnClear,
    placeholder: state.placeholder,
    scrollAreaProps: state.scrollAreaProps,
    search: state.search,
    selectedOptionIndex: state.selectedOptionIndex,
    selectFirst: state.selectFirst,
    setInput: state.setInput,
    setOpened: state.setOpened,
    setSearch: state.setSearch,
    setSelectedOptionIndex: state.setSelectedOptionIndex,
    showAll: state.showAll,
    value: state.value,
  }));

  // Virtualization setup
  const count = boxerData.length;
  const virtualizer = useVirtualizer({
    count,
    estimateSize: () => 36,
    getScrollElement: () => parentRef.current,
  });
  const virtualItems = virtualizer.getVirtualItems();

  // Component Callback Functions
  const onOptionSubmit = useCallback(
    (indexOrId: number | string) => {
      const index = typeof indexOrId === 'string' ? parseInt(indexOrId, 10) : indexOrId;
      const option = boxerData[index];

      if (!option) {
        return;
      }

      if (multiSelect) {
        // Handle multi-select
        const currentValues = Array.isArray(value) ? value : [];
        const isSelected = currentValues.includes(option.value);

        const newValues = isSelected
          ? currentValues.filter((v: any) => v !== option.value)
          : [...currentValues, option.value];

        onChange?.(newValues);

        // Update buffer for multi-select
        const newBuffer = boxerData.filter((item) => newValues.includes(item.value));
        onBufferChange?.(newBuffer);
      } else {
        // Handle single select
        onChange?.(option.value);
        setSearch('');
        setInput(option.label);
        valueRef.current = option.value;
        setOpened(false);
      }
    },
    [boxerData, multiSelect, value, onChange, onBufferChange, setSearch, setInput, setOpened]
  );

  const onClear = useCallback(() => {
    if (showAll && selectFirst) {
      onOptionSubmit(0);
    } else {
      if (multiSelect) {
        onChange?.([] as any);
      } else {
        onChange?.(null as any);
      }
      setSearch('');
      setInput('');
      inputRef.current?.focus();
    }

    if (openOnClear) {
      setOpened(true);
    }
  }, [showAll, selectFirst, multiSelect, onChange, setSearch, setInput, openOnClear, setOpened, onOptionSubmit]);

  // Component Hooks
  const combobox = useVirtualizedCombobox({
    getOptionId: (index) => String(index),
    onDropdownClose: () => {
      setOpened(false);
    },
    onDropdownOpen: () => {
      if (!value || (multiSelect && (!Array.isArray(value) || value.length === 0))) {
        setSearch('');
        setInput('');
      }
      combobox.selectFirstOption();
    },
    onSelectedOptionSubmit: onOptionSubmit,
    opened,
    selectedOptionIndex,
    setSelectedOptionIndex: (index) => {
      setSelectedOptionIndex(index);
      if (index !== -1) {
        virtualizer.scrollToIndex(index);
      }
    },
    totalOptionsCount: boxerData.length,
  });

  // Component variables
  const { options } = useBoxerOptions({
    boxerData,
    multiSelect,
    onOptionSubmit,
    value,
  });

  // Component useEffects
  useEffect(() => {
    // Fetch initial data
    fetchData('', true);
  }, []);

  useEffect(() => {
    // Handle search changes
    const delayDebounceFn = setTimeout(() => {
      if (search !== undefined && opened) {
        fetchData(search, true);
      }
    }, 300);

    return () => clearTimeout(delayDebounceFn);
  }, [search, opened]);

  useEffect(() => {
    // Sync input with value
    if (multiSelect) {
      const labels = boxerData
        .filter((item) => Array.isArray(value) && value.includes(item.value))
        .map((item) => item.label)
        .join(', ');

      // When dropdown is closed, show selected labels. When open, allow searching
      if (!opened && input !== labels) {
        setInput(labels);
        setSearch('');
      }
    } else {
      const label = boxerData.find((item) => item.value === value)?.label;

      // Only sync if we need to update the input to match the value
      if (input !== label && (search ?? '') === '' && valueRef.current !== value && value) {
        setInput(label ?? '');
      } else if (!value && !valueRef.current && (search ?? '') === '') {
        setSearch('');
        setInput('');
      }
    }

    // Handle buffer change
    if (multiSelect) {
      const buffer =
        boxerData.filter((item: BoxerItem) => Array.isArray(value) && value.includes(item.value)) ??
        [];

      if (JSON.stringify(bufferRef.current) !== JSON.stringify(buffer)) {
        onBufferChange?.(buffer);
        bufferRef.current = buffer;
      }
    } else {
      const buffer = boxerData?.find((item: BoxerItem) => item.value === value) ?? null;

      if (bufferRef.current?.value !== buffer?.value) {
        onBufferChange?.(buffer);
        bufferRef.current = buffer;
      }
    }
  }, [value, boxerData, input, search, multiSelect, opened, onBufferChange, setInput, setSearch]);

  useEffect(() => {
    // Select first option automatically
    if (selectFirst && (boxerData?.length ?? 0) > 0 && !multiSelect) {
      if (!value) {
        onOptionSubmit?.(0);
      }
    }
  }, [selectFirst, boxerData, multiSelect]);

  // Expose ref methods
  useImperativeHandle(ref, () => ({
    clear: () => {
      onClear();
    },
    close: () => {
      setOpened(false);
      combobox.closeDropdown();
    },
    focus: () => {
      inputRef.current?.focus();
    },
    getValue: () => {
      return value;
    },
    open: () => {
      setOpened(true);
      combobox.openDropdown();
    },
    setValue: (newValue: any) => {
      onChange?.(newValue);
    },
  }));

  return (
    <Combobox
      {...comboBoxProps}
      resetSelectionOnOptionHover={false}
      store={combobox}
      withinPortal={true}
    >
      <Combobox.Target>
        <Combobox.EventsTarget>
          <BoxerTarget
            clearable={clearable}
            combobox={combobox}
            error={error}
            isFetching={isFetching}
            label={label}
            onBlur={() => {
              if (!value && !multiSelect) {
                setSearch('');
                setInput('');
                combobox.closeDropdown();
                setOpened(false);
              }
            }}
            onClear={onClear}
            onSearch={(event) => {
              setSearch(event.currentTarget.value);
              setInput(event.currentTarget.value);
              setOpened(true);
            }}
            placeholder={placeholder}
            ref={inputRef}
            search={input}
          />
        </Combobox.EventsTarget>
      </Combobox.Target>

      <Combobox.Dropdown
        onKeyDown={() => {
          if (inputRef.current) {
            inputRef.current.value = '';
            inputRef.current?.focus();
          }
        }}
        p={2}
        {...dropDownProps}
      >
        {opened && options.length > 0 ? (
          <Combobox.Options>
            <ScrollArea.Autosize
              {...scrollAreaProps}
              mah={mah ?? 200}
              viewportProps={{
                ...scrollAreaProps?.viewportProps,
                onScroll: (event) => {
                  fetchMoreOnBottomReached(event.currentTarget as HTMLDivElement);
                },
                style: { border: '1px solid gray', borderRadius: 4 },
              }}
              viewportRef={parentRef}
            >
              <div
                style={{
                  height: `${virtualizer.getTotalSize()}px`,
                  position: 'relative',
                  width: '100%',
                }}
              >
                <div
                  style={{
                    left: 0,
                    position: 'absolute',
                    top: 0,
                    transform: `translateY(${virtualItems[0]?.start ?? 0}px)`,
                    width: '100%',
                  }}
                >
                  {virtualItems.map((virtualRow) => (
                    <div
                      data-index={virtualRow.index}
                      key={virtualRow.key}
                      ref={virtualizer.measureElement}
                    >
                      {options[virtualRow.index]}
                    </div>
                  ))}
                </div>
              </div>
            </ScrollArea.Autosize>
          </Combobox.Options>
        ) : (
          <Combobox.Empty>Nothing found</Combobox.Empty>
        )}
      </Combobox.Dropdown>
    </Combobox>
  );
});

BoxerInner.displayName = 'BoxerInner';

const Boxer = React.forwardRef<BoxerRef, BoxerProps>((props, ref) => {
  return (
    <BoxerProvider {...props}>
      <BoxerInner ref={ref} />
    </BoxerProvider>
  );
});

Boxer.displayName = 'Boxer';

export { Boxer };
export default Boxer;
