import {
  AriaAttributes,
  Children,
  MouseEventHandler,
  ReactElement,
  useCallback,
  useEffect,
  useState,
} from 'react';

import { useId } from '../../hooks/useId';
import { useTestIdAttribute } from '../../hooks/useTestIdAttribute';
import { CommonProps } from '../../types';
import { assertEmptyObject } from '../../utils/assertEmptyObject';
import { makeTestId } from '../../utils/makeTestId';

import { AccordionItemProps } from './components/AccordionItem/AccordionItem';
import { AccordionPanel } from './components/AccordionPanel/AccordionPanel';
import { AccordionTrigger } from './components/AccordionTrigger/AccordionTrigger';
import { StyledItem } from './styled';

/** Props for {@link Accordion} */
export type AccordionProps = CommonProps & {
  /** Array of {@link AccordionItem}   */
  children: ReactElement<AccordionItemProps> | ReactElement<AccordionItemProps>[];
  /** aria-label for the root accordion element */
  ariaLabel?: AriaAttributes['aria-label'];
  /** Allows to have only one opened item at the time */
  singleselect?: boolean;
} & (
    | {
        /** Expand all accordion children */
        expandAll: true;
        expandByIndexes?: undefined;
      }
    | {
        expandAll?: false;
        /** Expand accordion children by indexes */
        expandByIndexes?: number[];
      }
  );

/**
 * The accordion component allows the user to show and hide sections of related content on a page.
 *
 * ```tsx
 * <Accordion>
 *   <AccordionItem label="Orange">Orange description</AccordionItem>
 *   <AccordionItem label="Apple">Apple description</AccordionItem>
 * </Accordion>
 * ```
 *
 * <Story id="components-accordion--default" />
 *
 * Accordion items can have icons
 *
 * <Story id="components-accordion--with-icon" />
 *
 * Accordion has a special mode to show only one opened item at the time
 *
 * <Story id="components-accordion--single-select" />
 */
export function Accordion(props: AccordionProps) {
  const {
    children,
    ariaLabel,
    ariaDescribedBy,
    testId,
    className,
    singleselect,
    expandAll,
    expandByIndexes,
    ...restProps
  } = props;
  assertEmptyObject(restProps);

  const testIdAttribute = useTestIdAttribute();
  const id = useId();

  const [expanded, setExpanded] = useState<number[]>([]);

  useEffect(() => {
    if (expandAll && Array.isArray(children)) {
      setExpanded(Array.from(Array(children.length).keys()));
    }
  }, [children, expandAll]);

  useEffect(() => {
    if (Array.isArray(expandByIndexes)) {
      setExpanded(expandByIndexes);
    }
  }, [children, expandByIndexes]);

  const handleClick = useCallback<MouseEventHandler<HTMLButtonElement>>(
    (event) => {
      const index = Number(event.currentTarget.dataset.index);
      const ariaExpanded = event.currentTarget.getAttribute('aria-expanded');
      setExpanded((value) => {
        if (ariaExpanded === 'true') {
          return value.filter((item) => item !== index);
        }
        return singleselect ? [index] : value.concat(index);
      });
    },
    [singleselect],
  );

  return (
    <div
      aria-describedby={ariaDescribedBy}
      aria-label={ariaLabel}
      className={className}
      {...{ [testIdAttribute]: testId }}
    >
      {Children.map(children, (child, index) => {
        const { label, icon, children: content, testId: itemTestId, className: itemClassName } = child.props;
        const triggerId = `${id}-trigger-${index}`;
        const panelId = `${id}-panel-${index}`;
        const isExpanded = expanded.includes(index);

        return (
          <StyledItem className={itemClassName} {...{ [testIdAttribute]: itemTestId }}>
            <AccordionTrigger
              expanded={isExpanded}
              icon={icon}
              index={index}
              label={label}
              onClick={handleClick}
              panelId={panelId}
              testId={makeTestId(itemTestId, 'trigger')}
              triggerId={triggerId}
            />
            <AccordionPanel
              content={content}
              expanded={isExpanded}
              hasIcon={!!icon}
              panelId={panelId}
              testId={makeTestId(itemTestId, 'panel')}
              triggerId={triggerId}
            />
          </StyledItem>
        );
      })}
    </div>
  );
}
