import clsx from 'clsx';
import React, {
  ReactElement,
  useEffect,
  useLayoutEffect,
  useState,
} from 'react';
import { useExpand } from '../../hooks/useExpand/useExpand';
import { Button, ButtonContext } from '../Buttons';
import { IconName } from '../Icons';
import classes from './Accordion.scss';
import {
  AccordionItem,
  AccordionItemProps,
} from './AccordionItem/AccordionItem';

interface ExpandedState {
  [key: string]: boolean;
}

export interface AccordionProps {
  /** React element which will be displayed as the Accordion Header */
  header?: JSX.Element;

  /** Whether the Accordion should be expanded or collapsed during the first render */
  expandedByDefault?: boolean;

  /** Determines if spacing will be added to align with the Accordion item with the header (default: true) */
  alignAccordionItem?: boolean;

  /** Whether the rows of the accordion should stick to the top */
  stickyRows?: boolean;

  /** CSS Class name for additional styles */
  className?: string;
}

/**
 * Renders a component in which each child component of the class AccordionItem can be expanded.
 *
 * @example
 *  <Accordion header={<b>Header</b>}>
 *    <AccordionItem header={<b>Item 1</b>}>
 *      <p>Content 1</p>
 *    </AccordionItem>
 *    <AccordionItem header={<b>Item 2</b>}>
 *      <p>Content 2</p>
 *    </AccordionItem>
 *  </Accordion>
 */
export const Accordion: React.FC<AccordionProps> = ({
  header,
  expandedByDefault = false,
  children,
  stickyRows,
  alignAccordionItem,
  className = '',
}) => {
  const expandAll = useExpand();
  const [expanded, setExpanded] = useState<ExpandedState>({});
  const disabled = !React.Children.count(children);

  useEffect(() => {
    setExpanded((oldState) => {
      const updatedState: ExpandedState = {};

      React.Children.forEach(children, (child, i) => {
        if (React.isValidElement(child)) {
          if (child.type === AccordionItem) {
            const keyString = String(child.key ?? i);

            if (!Object.keys(oldState).includes(keyString)) {
              updatedState[keyString] = expandedByDefault;
            } else {
              updatedState[keyString] = oldState[keyString];
            }
          }
        }
      });

      return updatedState;
    });
  }, [children, expandedByDefault]);

  useLayoutEffect(() => {
    expandAll.collapse();
    Object.keys(expanded).forEach((e) => {
      if (expanded[e]) {
        expandAll.expand();
      }
    });
  });

  return (
    <div
      className={clsx(classes.container, className)}
      data-test-id="accordion"
    >
      <div className={clsx(classes.wrapper)}>
        {header && (
          <div className={clsx(classes.header)} data-test-id="accordion-header">
            <Button
              icon={IconName.ChevronRight}
              onButtonClicked={() => {
                const updatedState = { ...expanded };

                Object.keys(updatedState).forEach((e) => {
                  updatedState[e] = !expandAll.isExpanded;
                });

                setExpanded(updatedState);
                expandAll.toggleExpanded();
              }}
              className={clsx(classes.button, {
                [classes.rotated]: expandAll.isExpanded,
                [classes.disabled]: disabled,
              })}
              buttonContext={ButtonContext.None}
              disabled={disabled}
              dataTestId="accordion-toggle-all"
            ></Button>
            {header}
          </div>
        )}
        {React.Children.map(children, (child, i) => {
          if (React.isValidElement(child)) {
            if (child.type === AccordionItem) {
              const keyString = String(child.key ?? i);
              return React.cloneElement(
                child as ReactElement<AccordionItemProps>,
                {
                  sticky: (child.props.sticky ?? stickyRows) && header,
                  key: keyString,
                  allowLeftSpacing: alignAccordionItem,
                  isExpanded: expanded[keyString] ?? expandedByDefault,
                  toggleExpanded: () => {
                    const updatedState = { ...expanded };
                    if (!Object.keys(updatedState).includes(keyString)) {
                      updatedState[keyString] = !expandedByDefault;
                    } else {
                      updatedState[keyString] = !updatedState[keyString];
                    }
                    setExpanded(updatedState);
                  },
                },
              );
            } else {
              return child;
            }
          }
        })}
      </div>
    </div>
  );
};
