/* global document */
import React, {
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
  Fragment,
} from "react";
import { findNodeHandle, ViewStyle, Pressable } from "react-native";

import theme from "mazlo-theme";

import useScroll from "../../hooks/useScroll";
import { Checkbox } from "../Checkbox";
import { Icon } from "../Icon";
import { Input } from "../Input";
import { Portal as MenuPortal } from "../Portal";
import { Text } from "../Text";
import { View } from "../View";
import styles from "./styles";

export type SelectOption = {
  id?: string;
  name: string;
  value: string;
  content?: any;
};

type Offset = {
  x?: number;
  y?: number;
};

export type Props = {
  alignHorizontal?: "left" | "right";
  alignVertical?: "top" | "bottom";
  bordered?: boolean;
  contained?: boolean;
  headerComponent?: React.ReactNode;
  label?: string;
  menuStyle?: ViewStyle;
  multiple?: boolean;
  offset?: Offset;
  open?: boolean;
  options?: SelectOption[];
  search?: boolean;
  size?: string;
  toggleComponent?: React.ReactNode;
  value?: string | string[];
  emptyText?: string;
  fetchOptions?: () => Promise<SelectOption[]>;
  onSelect: (selected: string | string[]) => void;
  renderOption?: (item: SelectOption) => React.ReactNode;
  renderSelectedOption?: (option: SelectOption) => React.ReactNode;
  dark?: boolean;
  decorated?: boolean;
  disabled?: boolean;
  inline?: boolean;
};

type SelectRowProps = {
  multiple: boolean;
  option: SelectOption;
  selected: boolean;
  size?: string;
  onChange: (value: string, selected: boolean) => void;
  renderOption?: (option: SelectOption) => React.ReactNode;
};

const trueFn = () => true;

const SelectRow = ({
  multiple,
  option,
  selected,
  size,
  onChange,
  renderOption,
}: SelectRowProps) => {
  const toggle = useCallback(() => {
    onChange(option.value, !selected);
  }, [option.value, selected, onChange]);
  return (
    <Pressable
      // @ts-ignore
      delayPressIn={0}
      onPressIn={toggle}
      // @ts-ignore
      onStartShouldSetResponderCapture={trueFn}
      key={option.value}
      style={({ hovered }: any) => [
        styles.optionContainer,
        hovered && styles.hoveredOptionContainer,
        selected && styles.selectedOptionContainer,
      ]}
    >
      {multiple && (
        <View style={styles.checkboxContainer}>
          <Checkbox value={selected} />
        </View>
      )}
      <View style={styles.optionLabel}>
        {renderOption ? (
          renderOption(option)
        ) : (
          <Text
            selectable={false}
            style={[
              styles.optionText,
              {
                fontSize: theme.fontSizes[size],
                lineHeight: theme.fontSizes[size] * theme.lineHeights.normal,
              },
            ]}
          >
            {option.name}
          </Text>
        )}
      </View>
    </Pressable>
  );
};

const Select = ({
  alignHorizontal = "left",
  alignVertical = "bottom",
  bordered = true,
  contained = false,
  emptyText = "No options found",
  label,
  fetchOptions,
  headerComponent,
  menuStyle,
  multiple,
  onSelect,
  options: optionsProp = [],
  open,
  offset,
  renderOption,
  search,
  size = "sm",
  toggleComponent,
  value: valueProp,
  dark,
  decorated,
  renderSelectedOption,
  disabled,
  inline = false,
}: Props) => {
  const node = useRef();
  const menuNode = useRef();
  const { x: offsetLeft = 0, y: offsetTop = 0 } = offset || {};

  const [showOptions, setShowOptions] = useState(false);
  const menuOpen = open == null ? showOptions : open;

  const [layout, setLayout] = useState({ width: 0, height: 0, x: 0, y: 0 });
  const { scrollX, scrollY } = useScroll();
  useLayoutEffect(() => {
    const nodeHandle = findNodeHandle(node.current) as any;
    if (nodeHandle) {
      setLayout(nodeHandle.getBoundingClientRect().toJSON());
    }
  }, [menuOpen, node.current, scrollX, scrollY]);

  const [options, setOptions] = useState(optionsProp);
  useEffect(() => setOptions(optionsProp), [optionsProp]);

  const values = []
    .concat(valueProp)
    .filter((value) => options.find((option) => option.value === value));

  const selectedOptions = useMemo(
    () =>
      values
        .map((value) => options.find((option) => option.value === value))
        .filter(Boolean),
    [values, options]
  );

  const [searchText, setSearchText] = useState("");
  const filteredOptions = useMemo(() => {
    if (!searchText) return options;
    const text = searchText.toLowerCase();
    return options.filter((option) => option.name.toLowerCase().includes(text));
  }, [options, searchText]);

  const selectOption = useCallback(
    (value, selected) => {
      const newValues = selected
        ? [...values, value]
        : values.filter((item) => item !== value);

      onSelect(multiple ? newValues : newValues.pop());
      if (!multiple) {
        setShowOptions(false);
      }
    },
    [multiple, values, onSelect]
  );

  const handleMouseDown = useCallback(
    (e) => {
      const currentNode = findNodeHandle(node.current) as any;
      const currentMenu = findNodeHandle(menuNode.current) as any;
      if (
        currentNode &&
        !currentNode.contains(e.target) &&
        currentMenu &&
        !currentMenu.contains(e.target)
      ) {
        setShowOptions(false);
      }
    },
    [node.current, menuNode.current]
  );

  useEffect(() => {
    if (open == null) {
      if (menuOpen) {
        document.addEventListener("mousedown", handleMouseDown);
      } else {
        document.removeEventListener("mousedown", handleMouseDown);
      }
      return () => {
        document.removeEventListener("mousedown", handleMouseDown);
      };
    }
  }, [open, menuOpen]);

  useEffect(() => {
    if (fetchOptions) {
      fetchOptions().then(setOptions);
    }
  }, [fetchOptions]);

  const useLabel = label && !selectedOptions.length;

  const MenuContainer = useMemo(() => (inline ? Fragment : MenuPortal), [
    inline,
  ]);

  const toggleHandler = useCallback(
    () => setShowOptions((value) => !value),
    []
  );
  return (
    <View
      style={[styles.container, disabled && styles.containerDisabled]}
      ref={node}
    >
      {toggleComponent !== null && (
        <Pressable
          disabled={disabled}
          onPressIn={toggleHandler}
          onPress={(e) => {
            e?.preventDefault();
          }}
          // @ts-ignore
          onStartShouldSetResponderCapture={trueFn}
        >
          {toggleComponent || (
            <View
              style={[
                styles.toggleContainer,
                bordered && { borderWidth: 1 },
                contained && { paddingVertical: 0 },
              ]}
            >
              <View style={styles.selectedDisplay}>
                {!useLabel && (
                  <>
                    {multiple && (
                      <Text
                        style={[
                          styles.selectedCount,
                          { color: dark ? theme.colors.white : null },
                        ]}
                      >
                        {selectedOptions.length}
                      </Text>
                    )}
                    <Text
                      numberOfLines={1}
                      ellipsizeMode="tail"
                      style={{
                        fontSize: theme.fontSizes[size],
                        lineHeight:
                          theme.fontSizes[size] * theme.lineHeights.normal,
                        color: dark ? theme.colors.white : null,
                      }}
                    >
                      {multiple
                        ? "Selected"
                        : selectedOptions.length
                        ? renderSelectedOption?.(selectedOptions[0]) ||
                          selectedOptions[0]?.name ||
                          ""
                        : ""}
                    </Text>
                  </>
                )}
                {useLabel && (
                  <>
                    <Text
                      numberOfLines={1}
                      ellipsizeMode="tail"
                      style={{
                        fontSize: theme.fontSizes[size],
                        lineHeight:
                          theme.fontSizes[size] * theme.lineHeights.normal,
                        color: dark ? theme.colors.white : null,
                      }}
                    >
                      {label}
                    </Text>
                  </>
                )}
              </View>
              <View style={styles.toggleIconContainer}>
                <Icon
                  color={dark ? theme.colors.white : theme.colors.dark}
                  name="small-arrow-down"
                  style={styles.arrowIcon}
                />
              </View>
              {menuOpen && decorated && (
                <View style={styles.toggleContainerDecorator} />
              )}
            </View>
          )}
        </Pressable>
      )}
      <MenuContainer>
        <View
          ref={menuNode}
          style={[
            styles.optionsOrigin,
            !inline && {
              width: layout.width,
              top:
                layout.y +
                offsetTop +
                scrollY +
                (alignVertical === "bottom" ? layout.height : 0),
              left: layout.x + scrollX + offsetLeft,
            },
            inline && {
              position: "relative",
            },
          ]}
        >
          {menuOpen && (
            <View
              style={[
                styles.optionsContainer,
                inline && {
                  position: "relative",
                },
                dark && styles.optionContainerDark,
                decorated && styles.optionsContainerDecorated,
                alignHorizontal === "right" && styles.optionsContainerRight,
                alignVertical === "top" && styles.optionsContainerTop,
              ].concat([menuStyle])}
            >
              {search && (
                <Input
                  placeholder="Search"
                  value={searchText}
                  onChange={setSearchText}
                  style={styles.search}
                />
              )}
              {headerComponent}
              <View style={styles.filteredContainer}>
                {filteredOptions.length ? (
                  filteredOptions.map((option: SelectOption) => (
                    <SelectRow
                      size={size}
                      key={option.id || option.value}
                      multiple={multiple}
                      option={option}
                      selected={selectedOptions.includes(option)}
                      onChange={selectOption}
                      renderOption={renderOption}
                    />
                  ))
                ) : (
                  <Text style={styles.noOptions}>{emptyText}</Text>
                )}
              </View>
            </View>
          )}
        </View>
      </MenuContainer>
    </View>
  );
};

export default Select;
