import React, {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import {
  ChevronRightIcon,
  Cross1Icon,
  MagnifyingGlassIcon,
} from '@radix-ui/react-icons';
import { Popover } from '../atoms/popover';
import { Flex } from '../atoms/flex';
import { Button } from '../atoms/button';
import type { BadgeProps } from '../atoms/badge';
import { Badge } from '../atoms/badge';
import { CheckIcon } from '../icon';
import { ScrollArea } from '../atoms/scroll-area';
import { Grid } from '../atoms/grid';
import type { TypoProps } from '../atoms';
import { AutoSizingInput, Typo } from '../atoms';

type ID = string;
interface Item {
  name: string;
  id: ID;
}

const OPTION_HEIGHT = 32;

const stopDefaultEvents = (e: React.KeyboardEvent):void => {
  e.preventDefault();
  e.stopPropagation();
};

export interface TagSelectorProps<T extends Item> {
  options?: T[];
  selected?: ID[];
  placeholder?: string;
  disabled?: boolean;
  maxCount?: number;
  style?: React.CSSProperties;
  size?: 'large' | 'medium' | 'small';
  readOnly?: boolean;
  error?: boolean;

  onChange?: (selected: ID[]) => void;
  tagRender?: (item: T, onClickDelete?: () => void) => React.ReactNode;
  DropdownContainer?: DropdownContainer<T>;
  open?: boolean;
  dropdownItemRender?: (item: T) => React.ReactNode;
}
type DropdownContainer<T> = (props: {
  items: T[];
  children: React.ReactNode;
  currentItem?: T;
}) => React.ReactNode;

const DefaultDropdownContainer = (props: {
  children: React.ReactNode;
}): React.ReactNode => {
  return props.children;
};

export function TagSelector<T extends Item>(
  props: TagSelectorProps<T>
): React.ReactNode {
  const {
    options = [],
    tagRender,
    placeholder,
    maxCount = 0,
    DropdownContainer = DefaultDropdownContainer,
    size = 'medium',
    readOnly,
    error,
    open: propsOpen,
    dropdownItemRender,
  } = props;
  // eslint-disable-next-line react/hook-use-state -- props에 따라 미사용 상태가 될 수 있음
  const [_selected, _setSelected] = useState<string[]>(props.selected || []);
  const selected = props.selected || _selected;
  const setSelected: (v: ID[]) => void = props.onChange || _setSelected;
  const [value, setValue] = useState<string>('');
  const [focusIndex, setFocusIndex] = useState<number | null>(null);
  const [open, setOpen] = useState(false);
  const [focus, setFocus] = useState(false);
  const fieldRef = useRef<HTMLInputElement>(null);
  const scrollRef = useRef<HTMLDivElement>(null);

  const closeRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const onFocus = useCallback(() => {
    if (closeRef.current) {
      clearTimeout(closeRef.current);
    }
    setFocus(true);
    setOpen(true);
  }, []);

  const onBlur = useCallback(() => {
    closeRef.current = setTimeout(() => {
      setFocus(false);
      setOpen(false);
      setTimeout(() => {
        setValue('');
      }, 100);
    }, 300);
  }, []);

  const onChangeValue = useCallback<React.ChangeEventHandler<HTMLInputElement>>(
    (e) => {
      setValue(e.target.value);
    },
    []
  );

  const filteredOptions = useMemo<T[]>(() => {
    return options.filter((option) => {
      return option.name.includes(value);
    });
  }, [options, value]);

  const selectedItem = useMemo<T[]>(() => {
    const result = [] as T[];
    selected.forEach((id) => {
      const maybeItem = options.find((option) => option.id === id);
      if (maybeItem) result.push(maybeItem);
    });
    return result;
  }, [options, selected]);

  const onDelete = useCallback(
    (id: string) => { 
      setSelected(selected.filter((el) => el !== id));
    },
    [selected, setSelected]
  );

  const onSelect = useCallback(
    (id: string) => { 
      if (maxCount && selected.length + 1 > maxCount) return;
      setSelected([...selected, id]);
    },
    [maxCount, selected, setSelected]
  );

  const toggleItem = useCallback(
    (id: string) => { 
      if (selected.includes(id)) {
        onDelete(id);
      } else {
        onSelect(id);
      }
    },
    [onDelete, onSelect, selected]
  );

  const setScroll = useCallback((index: number): void => {
    if (scrollRef.current) {
      const scrollTop = scrollRef.current.scrollTop;
      const clientHeight = scrollRef.current.clientHeight;
      const focusTop = index * OPTION_HEIGHT;
      if (focusTop < scrollTop) {
        scrollRef.current.scrollTop = focusTop;
      } else if (focusTop + OPTION_HEIGHT > scrollTop + clientHeight) {
        scrollRef.current.scrollTop = focusTop + OPTION_HEIGHT - clientHeight;
      }
    }
  }, []);

  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      const key = e.key;

      switch (key) {
        case 'ArrowDown': {
          stopDefaultEvents(e);
          let newFocus = 0;
          if (typeof focusIndex === 'number') {
            newFocus = focusIndex + 1 >= options.length ? 0 : focusIndex + 1;
          }
          setFocusIndex(newFocus);
          setScroll(newFocus);
          break;
        }

        case 'ArrowUp': {
          stopDefaultEvents(e); 
          let newFocus = 0;
          if (typeof focusIndex === 'number') {
            newFocus = focusIndex - 1 < 0 ? options.length - 1 : focusIndex - 1;
          }
          setFocusIndex(newFocus);
          setScroll(newFocus);
          break;
        }

        case 'Enter':
          stopDefaultEvents(e);
          setValue('');
          if (focusIndex !== null) {
            toggleItem(filteredOptions[focusIndex]?.id);
          }
          break;

        case 'Backspace': 
          if (value.length === 0) {
            setSelected(selected.slice(0, -1));
          }
          break;

        case 'Escape':
          stopDefaultEvents(e);
          setOpen(false);
          break;
      }
    },
    [
      focusIndex,
      value.length,
      setScroll,
      options.length,
      toggleItem,
      filteredOptions,
      setSelected,
      selected,
    ]
  );

  const placeholderVisible = useMemo(() => {
    if (selectedItem.length) return false;
    if (value) return false;
    if (focus) return false;
    return true;
  }, [focus, selectedItem.length, value]);

  useEffect(() => {
    setFocusIndex(null);
  }, [open]);

  useEffect(() => {
    setFocusIndex(0);
  }, [filteredOptions]);

  const badgeSize = useMemo<BadgeProps['size']>(() => {
    switch (size) {
      case 'large':
        return 'large';
      case 'medium':
      case 'small':
      default:
        return 'small';
    }
  }, [size]);

  const placeholderVariant = useMemo<TypoProps['variant']>(() => {
    switch (size) {
      case 'large':
        return 'subtitle';
      case 'medium':
      default:
        return 'body';
    }
  }, [size]);

  const focusClassName = focus ? ' focused' : '';
  const readOnlyClassName = readOnly ? ' read-only' : '';
  const errorClassName = error ? ' error' : '';

  const readonlyContent = (
    <Flex gap="1">
      {selectedItem.map((item) => {
        return tagRender ? (
          tagRender(item)
        ) : (
          <Badge key={item.id} size={badgeSize}>
            {item.name}
          </Badge>
        );
      })}
    </Flex>
  );

  const editContent = (
    <>
      <Flex
        align="center"
        gap="1"
        maxWidth="100%"
        overflow="hidden"
        wrap="wrap"
      >
        {selectedItem.map((item) => {
          return tagRender ? (
            tagRender(item, () => {
              onDelete(item.id);
            })
          ) : (
            <Badge key={item.id} size={badgeSize}>
              {item.name}
              <Button
                onClick={() => {
                  onDelete(item.id);
                }}
                variant="transparent"
              >
                <Cross1Icon />
              </Button>
            </Badge>
          );
        })}
        {placeholderVisible ? (
          <Typo color="gray" variant={placeholderVariant}>
            {placeholder}
          </Typo>
        ) : null}
        <AutoSizingInput
          onBlur={onBlur}
          onChange={onChangeValue}
          onFocus={onFocus}
          onKeyDown={handleKeyDown}
          ref={fieldRef}
          value={value}
        />
      </Flex>
      {focus ? <MagnifyingGlassIcon /> : <ChevronRightIcon />}
    </>
  );
  return (
    <Popover.Root open={(Boolean(filteredOptions.length) && propsOpen) || open}>
      <Popover.Trigger style={{ position: 'relative' }}>
        <Grid
          align="center"
          className={`tag-selector ${size} ${focusClassName}${readOnlyClassName}${errorClassName}`}
          columns="1fr auto"
          gap="1"
          onClick={() => !readOnly && fieldRef.current?.focus()}
        >
          {readOnly ? readonlyContent : editContent}
        </Grid>
      </Popover.Trigger>

      <Popover.Content
        onOpenAutoFocus={preventDefault}
        onTouchMove={stopPropagationTouch}
        onWheel={stopPropagationWheel}
        sticky="always"
        style={{
          pointerEvents: 'fill',}}
      >
        <DropdownContainer
          currentItem={filteredOptions[focusIndex || 0]}
          items={filteredOptions}
        >
          <ScrollArea
            ref={scrollRef}
            scrollbars="vertical"
            style={{ height: OPTION_HEIGHT * 8 }}
            type="auto"
          >
            <Flex direction="column">
              {filteredOptions.map((item, i) => {
                const isSelected = Boolean(
                  selected.find((el) => el === item.id)
                );
                const isFocus = i === focusIndex;

                return (
                  <Button
                    className={`tag-selector-button ${isFocus ? 'focused' : ''}`}
                    key={item.id}
                    onClick={() => {
                      toggleItem(item.id);
                      fieldRef.current?.focus();
                    }}
                    onMouseEnter={() => {
                      setFocusIndex(i);
                    }}
                    variant={isSelected ? 'soft' : 'outline'}
                  >
                    <Flex justify="between" width="100%">
                      {dropdownItemRender
                        ? dropdownItemRender(item)
                        : item.name}
                      {isSelected ? <CheckIcon /> : null}
                    </Flex>
                  </Button>
                );
              })}
            </Flex>
          </ScrollArea>
        </DropdownContainer>
      </Popover.Content>
    </Popover.Root>
  );
}

const stopPropagationTouch: React.TouchEventHandler<HTMLDivElement> = (
  e
): void => {
  e.stopPropagation();
};

const stopPropagationWheel: React.WheelEventHandler<HTMLDivElement> = (
  e
): void => {
  e.stopPropagation();
};

const preventDefault = (e: Event): void => {
  e.preventDefault();
};
