import React, { useEffect, useMemo, useState } from 'react';
import type { AutocompleteChangeReason } from '@mui/material';

import { TagType } from '../types';

import { getIAB } from '@/modules/@common/iab/helpers';
import { createFlatTreeMap, getAggregateSelectedIds, NodeType, TreeMapperType } from '@/mui/helpers/treeView';

import StateSelect from '../components/StateSelect/StateSelect';
import TagList from '../components/TagList/TagList';
import { Autocomplete, Stack, TextField, TreeView } from '@/mui/components';

import styles from './TagIabSelector.module.scss';

const treeMapper = {
  label: 'name',
  children: 'categories',
};

type SizeType = 'small' | 'medium';

type Props = {
  size?: SizeType;
  value?: TagType[];
  include?: boolean;
  placeholder?: string;
  disabled?: boolean;
  selectProps: { [key: string]: unknown };
  getTagLabel?: (tag: TagType) => void;
  onChange: (
    event: React.SyntheticEvent,
    option: unknown | unknown[] | object[],
    reason: AutocompleteChangeReason | null,
  ) => void;
  onInputChange?: (event: React.SyntheticEvent, query: string) => void;
  onOpen?: () => void;
  onClose?: () => void;
};

function TagIabSelector({
  size = 'medium',
  value = [],
  include = true,
  placeholder,
  disabled,
  getTagLabel,
  onChange,
  onInputChange,
  onOpen,
  onClose,
  selectProps = {},
  ...restProps
}: Props) {
  const [stateInclude, toggleInclude] = useState(include);
  const [searchText, setSearchText] = useState('');
  const [flatTreeInclude, setFlatTreeInclude] = useState(
    createFlatTreeMap(
      getIAB() as NodeType[],
      treeMapper as TreeMapperType,
      value.filter((tag: TagType) => tag.include).map((tag: TagType) => tag.value) as string[],
    ),
  );

  const [flatTreeExclude, setFlatTreeExclude] = useState(
    createFlatTreeMap(
      getIAB() as NodeType[],
      treeMapper as TreeMapperType,
      value.filter((tag: TagType) => !tag.include).map((tag: TagType) => tag.value) as string[],
    ),
  );

  useEffect(() => {
    toggleInclude(include ?? true);
  }, [include]);

  useEffect(() => {
    setFlatTreeInclude(
      createFlatTreeMap(
        getIAB() as NodeType[],
        treeMapper as TreeMapperType,
        value.filter((tag: TagType) => tag.include).map((tag: TagType) => tag.value) as string[],
      ),
    );
    setFlatTreeExclude(
      createFlatTreeMap(
        getIAB() as NodeType[],
        treeMapper as TreeMapperType,
        value.filter((tag: TagType) => !tag.include).map((tag: TagType) => tag.value) as string[],
      ),
    );
  }, [value]);

  const tagList = useMemo(() => {
    const tags: TagType[] = [];

    getAggregateSelectedIds(flatTreeInclude).forEach((nodeId) => {
      const node = flatTreeInclude.get(nodeId);

      tags.push({
        label: node.label,
        value: node.id,
        id: node.id,
        include: true,
      });
    });

    getAggregateSelectedIds(flatTreeExclude).forEach((nodeId) => {
      const node = flatTreeExclude.get(nodeId);

      tags.push({
        label: node.label,
        value: node.id,
        id: node.id,
        include: false,
      });
    });

    return tags.sort((a, b) => (a.id as number) - (b.id as number));
  }, [flatTreeInclude, flatTreeExclude]);

  const options = useMemo(() => [], []);

  return (
    <Stack spacing={1} fullWidth>
      <Stack fullWidth spacing={2} direction="row" alignItems="flex-start">
        <div>
          <StateSelect
            disabled={!!disabled}
            {...selectProps}
            size={size}
            value={stateInclude}
            onChange={(state) => toggleInclude(state)}
          />
        </div>
        <Autocomplete
          disabled={disabled}
          onOpen={onOpen}
          onClose={onClose}
          {...restProps}
          multiple
          renderTags={() => null}
          size={size}
          value={tagList}
          inputValue={searchText}
          options={options}
          optionLabelKey="label"
          optionValueKey="value"
          disableClearable={false}
          classes={{
            noOptions: styles.NoOptions,
          }}
          onChange={(event, tags$, reason) => {
            if (reason === 'clear') {
              setFlatTreeInclude((prevState) => {
                let hasChanges = false;
                const copiedMap = new Map();

                prevState.forEach((node) => {
                  if (node.selected) {
                    hasChanges = true;
                  }

                  copiedMap.set(node.id, {
                    ...node,
                    selected: false,
                  });
                });

                return hasChanges ? copiedMap : prevState;
              });
              setFlatTreeExclude((prevState) => {
                let hasChanges = false;
                const copiedMap = new Map();

                prevState.forEach((node) => {
                  if (node.selected) {
                    hasChanges = true;
                  }

                  copiedMap.set(node.id, {
                    ...node,
                    selected: false,
                  });
                });

                return hasChanges ? copiedMap : prevState;
              });

              setSearchText('');

              onChange(event, [], reason);
            }
          }}
          onInputChange={(event, query) => {
            setSearchText(query);

            if (onInputChange) {
              onInputChange(event, query);
            }
          }}
          renderInput={(params) => (
            <TextField
              {...params}
              size={size}
              placeholder={placeholder}
              onKeyDown={(event) => {
                if (!searchText && event.key === 'Backspace') {
                  event.stopPropagation();
                }
              }}
            />
          )}
          noOptionsText={
            <TreeView
              flatTree={stateInclude ? flatTreeInclude : flatTreeExclude}
              onNodeSelect={(event, nodeIds) => {
                const primaryTree = stateInclude ? flatTreeInclude : flatTreeExclude;
                const secondaryTree = !stateInclude ? flatTreeInclude : flatTreeExclude;

                const copiedMap = new Map();

                primaryTree.forEach((node) => {
                  copiedMap.set(node.id, {
                    ...node,
                    selected: nodeIds.includes(node.id),
                  });
                });

                const aggrigatedSelectedIds = getAggregateSelectedIds(copiedMap);

                const included = stateInclude ? copiedMap : new Map(secondaryTree);
                const excluded = stateInclude ? new Map(secondaryTree) : copiedMap;
                let hasChanges = false;

                aggrigatedSelectedIds.forEach((nodeId) => {
                  const node = secondaryTree.get(nodeId);

                  if (node.selected) {
                    hasChanges = true;

                    (stateInclude ? excluded : included).set(node.id, {
                      ...node,
                      selected: false,
                    });

                    node.childrenIds.forEach((childId: string) => {
                      const child = secondaryTree.get(childId);

                      (stateInclude ? excluded : included).set(child.id, {
                        ...child,
                        selected: false,
                      });
                    });
                  }
                });

                setFlatTreeInclude(stateInclude ? copiedMap : (prevState) => (hasChanges ? included : prevState));
                setFlatTreeExclude(!stateInclude ? copiedMap : (prevState) => (hasChanges ? excluded : prevState));

                onChange(
                  event,
                  [
                    ...getAggregateSelectedIds(included).map((nodeId) => {
                      const node = included.get(nodeId);

                      return {
                        ...node,
                        include: true,
                      };
                    }),
                    ...getAggregateSelectedIds(excluded).map((nodeId) => {
                      const node = excluded.get(nodeId);

                      return {
                        ...node,
                        include: false,
                      };
                    }),
                  ],
                  null,
                );
              }}
              searchText={searchText}
            />
          }
        />
      </Stack>
      <TagList
        disabled={!!disabled}
        size={size}
        getTagLabel={getTagLabel}
        tags={tagList}
        onDelete={(event, tag) => {
          const primaryTree = tag.include ? flatTreeInclude : flatTreeExclude;
          const secondaryTree = !tag.include ? flatTreeInclude : flatTreeExclude;

          const copiedMap = new Map(primaryTree);

          const node = copiedMap.get(tag.id);

          copiedMap.set(node.id, {
            ...node,
            selected: false,
          });

          node.childrenIds.forEach((childId: string) => {
            const child = copiedMap.get(childId);

            copiedMap.set(childId, {
              ...child,
              selected: false,
            });
          });

          onChange(
            event,
            [
              ...getAggregateSelectedIds(copiedMap).map((nodeId) => {
                const node$ = primaryTree.get(nodeId);

                return {
                  ...node$,
                  include: tag.include,
                };
              }),
              ...getAggregateSelectedIds(secondaryTree).map((nodeId) => {
                const node$ = secondaryTree.get(nodeId);

                return {
                  ...node$,
                  include: !tag.include,
                };
              }),
            ],
            null,
          );
        }}
      />
    </Stack>
  );
}

export default TagIabSelector;
