import React, {useState, ReactNode, useCallback, ReactElement} from 'react';
import {
  View,
  StyleSheet,
  ViewStyle,
  TouchableOpacityProps,
  GestureResponderEvent,
} from 'react-native';
import {
  CheckboxCategoryStatus,
  CheckboxIdentifier,
  CheckboxInfo,
} from '../../types';
import CheckboxItem from './CheckboxItem';
import CheckboxNested from './CheckboxNested';

export interface CheckboxIndeterminateProps {
  indeterminateCheckboxIcon?: JSX.Element;
  indeterminateCheckboxIconContainerStyle?: ViewStyle;
}

export interface CheckboxBaseProps extends TouchableOpacityProps {
  checkboxIconContainerStyle?: ViewStyle;
  checkboxComponentContainerStyle?: ViewStyle;
  selectedCheckboxStyle?: ViewStyle;
  selectedCheckboxIcon?: ReactElement;
  selectedCheckboxIconContainerStyle?: ViewStyle;
  selectedCheckboxComponentContainerStyle?: ViewStyle;
  selectedCheckboxTitleStyle?: ViewStyle;
}

export interface CheckboxProps
  extends CheckboxBaseProps,
    CheckboxIndeterminateProps {
  containerStyle?: ViewStyle;
  checkboxIds: CheckboxIdentifier[];
  checkboxComponent?(info: CheckboxInfo): string | JSX.Element;
  checkboxIndeterminateContainerStyle?: ViewStyle;
  defaultIds?: string[];
  onSelect(id: string, toggle: boolean, selected: string[]): void;
}

export default function Checkbox({
  containerStyle,
  checkboxIds,
  checkboxComponent,
  checkboxIndeterminateContainerStyle,
  defaultIds,
  onSelect,
  onPress,
  ...props
}: CheckboxProps) {
  const [selected, setSelected] = useState<string[]>(
    defaultIds !== undefined ? filterId(defaultIds) : [],
  );

  function checkId(
    id: string,
    checkboxIdenfitifer: CheckboxIdentifier[],
  ): boolean {
    for (const value of checkboxIdenfitifer) {
      if (typeof value === 'string') {
        if (value === id) {
          return true;
        }
      } else {
        return checkId(id, value.checkboxIds);
      }
    }

    return false;
  }

  function filterId(id: string | string[]) {
    const selection: string[] = [];

    if (Array.isArray(id)) {
      for (const check of id) {
        if (checkId(check, checkboxIds)) {
          selection.push(check);
        }
      }
    } else if (checkId(id, checkboxIds)) {
      selection.push(id);
    }

    return selection;
  }

  const isSelected = useCallback((id: string) => selected.indexOf(id) >= 0, [
    selected,
  ]);

  const checkIndeterminateStatus = useCallback(
    (
      checkboxIdenfitifer: CheckboxIdentifier[],
      checked: boolean,
      hasEmpty?: boolean,
    ): CheckboxCategoryStatus => {
      for (const indeterminate of checkboxIdenfitifer) {
        if (typeof indeterminate === 'string') {
          if (!hasEmpty && !isSelected(indeterminate)) {
            hasEmpty = true;
          } else if (!checked && isSelected(indeterminate)) {
            checked = true;
          }
        } else {
          return checkIndeterminateStatus(
            indeterminate.checkboxIds,
            checked,
            hasEmpty,
          );
        }
      }

      return checked
        ? hasEmpty
          ? 'indeterminate'
          : 'selected'
        : 'not-selected';
    },
    [isSelected],
  );

  const filterSelection = useCallback(
    (
      base: string[],
      checkboxIdentifier: CheckboxIdentifier[],
      toggle: boolean,
    ): string[] => {
      const selection = [...base];

      for (const identifier of checkboxIdentifier) {
        if (typeof identifier === 'string') {
          const select = selection.indexOf(identifier) >= 0;

          if (select && !toggle) {
            selection.splice(selection.indexOf(identifier), 1);
          }

          if (!select && toggle) {
            selection.push(identifier);
          }
        } else {
          return filterSelection(selection, identifier.checkboxIds, toggle);
        }
      }

      return selection;
    },
    [],
  );

  const handlePressCheckboxNested = useCallback(
    (
      status: CheckboxCategoryStatus,
      identifier: CheckboxIdentifier[],
      event: GestureResponderEvent,
    ) => {
      onPress && onPress(event);

      const selection = filterSelection(
        selected,
        identifier,
        status === 'not-selected' || status === 'indeterminate',
      );

      setSelected(selection);
    },
    [selected, filterSelection, onPress],
  );

  const handleRenderCheckboxNested = useCallback(
    (key: string, title: string, identifier: CheckboxIdentifier[]) => {
      const status = checkIndeterminateStatus(identifier, false);

      return (
        <CheckboxNested
          {...props}
          key={key}
          title={title}
          checkboxIds={identifier}
          status={status}
          onPress={event =>
            handlePressCheckboxNested(status, identifier, event)
          }
        />
      );
    },
    [props, checkIndeterminateStatus, handlePressCheckboxNested],
  );

  const handlePressCheckboxItem = useCallback(
    (id: string, event: GestureResponderEvent) => {
      onPress && onPress(event);
      const selection = [...selected];

      if (isSelected(id)) {
        selection.splice(selection.indexOf(id), 1);
        onSelect(id, false, selection);
      } else {
        selection.push(id);
        onSelect(id, true, selection);
      }

      setSelected(selection);
    },
    [selected, isSelected, onPress, onSelect],
  );

  const handleRenderCheckboxItem = useCallback(
    (id: string) => {
      const isIdSelected = isSelected(id);
      const component =
        checkboxComponent && checkboxComponent({id, isSelected: isIdSelected});
      const title =
        typeof component === 'string'
          ? component
          : component === undefined
          ? id
          : undefined;

      return (
        <CheckboxItem
          {...props}
          key={id}
          title={title}
          isSelected={isIdSelected}
          onPress={event => handlePressCheckboxItem(id, event)}>
          {component && typeof component !== 'string' && component}
        </CheckboxItem>
      );
    },
    [props, checkboxComponent, isSelected, handlePressCheckboxItem],
  );

  const handleRenderListCheckboxItem = useCallback(
    (checkboxIdenfitifer: CheckboxIdentifier[], category?: string) => {
      const list: ReactNode[] = [];

      for (const value of checkboxIdenfitifer) {
        if (typeof value === 'string') {
          const item = handleRenderCheckboxItem(value);

          list.push(item);
        } else {
          const title = value.title;
          const identifier = value.checkboxIds;
          const key = category !== undefined ? `${category}:${title}` : title;
          const checkboxNested = handleRenderCheckboxNested(
            key,
            title,
            identifier,
          );

          list.push(checkboxNested);
          list.push(handleRenderListCheckboxItem(value.checkboxIds, key));
        }
      }

      return category !== undefined ? (
        <View
          key={`category: ${category}`}
          style={StyleSheet.flatten([
            styles.checkboxIndeterminateContainer,
            checkboxIndeterminateContainerStyle,
          ])}>
          {list}
        </View>
      ) : (
        list
      );
    },
    [
      checkboxIndeterminateContainerStyle,
      handleRenderCheckboxItem,
      handleRenderCheckboxNested,
    ],
  );

  return (
    <View style={containerStyle}>
      {handleRenderListCheckboxItem(checkboxIds)}
    </View>
  );
}

const styles = StyleSheet.create({
  checkboxIndeterminateContainer: {
    marginLeft: 12,
  },
});