import { MouseEventHandler, ReactNode, useCallback, useLayoutEffect, useRef, useState } from 'react';
import { CSSTransition } from 'react-transition-group';

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 { IconGlyph } from '../Icon/constants';

import {
  StyledContent,
  StyledPanel,
  StyledTrigger,
  TunedIcon,
  animationDuration,
  heightVariableName,
} from './styled';

/** Props for {@link ExtendablePanel} */
export interface ExtendablePanelProps extends CommonProps {
  /**
   * The icon when the panel is opened
   *
   * @default {@link IconGlyph.ChevronUp}
   */
  openedIcon?: IconGlyph;
  /**
   * The icon when the panel is closed
   *
   * @default {@link IconGlyph.ChevronDown}
   */
  closedIcon?: IconGlyph;
  /** The trigger text when the panel is opened */
  openedLabel: ReactNode;
  /** The trigger text when the panel is closed */
  closedLabel: ReactNode;
  /** The panel content */
  children: ReactNode;
  /**
   * Displays the trigger in the end when panel is opened
   *
   * @default false
   */
  reversed?: boolean;
  /**
   * Opens the panel by default
   *
   * @default false
   */
  opened?: boolean;
}

/**
 * The component allows the user to show and hide content on a page.
 *
 * ```tsx
 * <ExtendablePanel closedLabel="Trigger closed" openedLabel="Trigger opened">
 *   Lorem ipsum
 * </ExtendablePanel>
 * ```
 *
 * Closed by default
 *
 * <Story id="components-extendablepanel--default" />
 *
 * Can be opened by passing the {@link ExtendablePanelProps.opened} prop
 *
 * <Story id="components-extendablepanel--opened" />
 *
 * Can be reversed - displays the trigger in the end when opened
 *
 * <Story id="components-extendablepanel--reversed" />
 *
 * Can have custom icons
 *
 * <Story id="components-extendablepanel--icons" />
 */
export function ExtendablePanel(props: ExtendablePanelProps) {
  const {
    reversed = false,
    opened = false,
    closedLabel,
    closedIcon = IconGlyph.ChevronDown,
    openedIcon = IconGlyph.ChevronUp,
    openedLabel,
    children,
    testId,
    className,
    ariaDescribedBy,
    ...restProps
  } = props;
  assertEmptyObject(restProps);

  const testIdAttribute = useTestIdAttribute();
  const triggerId = useId();
  const panelId = useId();

  const [visible, setVisible] = useState(opened);

  const handleClick = useCallback<MouseEventHandler<HTMLButtonElement>>(() => {
    setVisible((value) => !value);
  }, []);

  const transitionRef = useRef<HTMLDivElement | null>(null);

  useLayoutEffect(() => {
    const node = transitionRef.current;
    if (visible && node) {
      node.style.setProperty(heightVariableName, node.scrollHeight + 'px');
    }
  }, [visible]);

  return (
    <StyledPanel aria-describedby={ariaDescribedBy} className={className} {...{ [testIdAttribute]: testId }}>
      <StyledTrigger
        $reversed={reversed}
        aria-controls={panelId}
        aria-expanded={visible}
        id={triggerId}
        onClick={handleClick}
        {...{ [testIdAttribute]: makeTestId(testId, 'trigger') }}
      >
        <TunedIcon glyph={visible ? openedIcon : closedIcon} />
        {visible ? openedLabel : closedLabel}
      </StyledTrigger>
      <div
        aria-labelledby={triggerId}
        id={panelId}
        role="region"
        {...{ [testIdAttribute]: makeTestId(testId, 'content') }}
      >
        <CSSTransition
          in={visible}
          mountOnEnter
          nodeRef={transitionRef}
          timeout={animationDuration}
          unmountOnExit
        >
          <StyledContent ref={transitionRef}>{children}</StyledContent>
        </CSSTransition>
      </div>
    </StyledPanel>
  );
}
