import { clsx } from 'clsx';
import { Component } from 'react';

import { Status, Size } from '../common';

const radius = { xxs: 6, xs: 11, sm: 22, xl: 61 };

const ANIMATION_DURATION_IN_MS = 1500;

export type ProcessIndicatorStatus =
  `${Status.PROCESSING | Status.FAILED | Status.SUCCEEDED | Status.HIDDEN}`;

export interface ProcessIndicatorProps {
  /** @default 'processing' */
  status?: ProcessIndicatorStatus;
  /** @default 'sm' */
  size?: 'xxs' | `${Size.EXTRA_SMALL | Size.SMALL | Size.EXTRA_LARGE}`;
  className?: string;
  'data-testid'?: string;
  onAnimationCompleted?: (status: ProcessIndicatorStatus) => void;
}

type ProcessIndicatorState = Required<Pick<ProcessIndicatorProps, 'status' | 'size'>>;

export default class ProcessIndicator extends Component<
  ProcessIndicatorProps,
  ProcessIndicatorState
> {
  declare props: ProcessIndicatorProps &
    Required<Pick<ProcessIndicatorProps, keyof typeof ProcessIndicator.defaultProps>>;

  static defaultProps = {
    status: 'processing',
    size: 'sm',
  } satisfies Partial<ProcessIndicatorProps>;

  interval = 0;
  timeout = 0;

  constructor(props: ProcessIndicator['props']) {
    super(props);
    this.state = {
      status: props.status,
      size: props.size,
    };
  }

  /**
   * Create interval for animation duration (1500ms)
   * Update state only at the end of every interval
   * (end of animation loop) if props changed before end of animation
   */
  componentDidMount() {
    this.interval = window.setInterval(() => {
      const { status: targetStatus, size: targetSize, onAnimationCompleted } = this.props;
      const { status: currentStatus, size: currentSize } = this.state;

      if (currentStatus !== targetStatus) {
        this.setState({ status: targetStatus }, () => {
          if (onAnimationCompleted) {
            this.timeout = window.setTimeout(() => {
              onAnimationCompleted(targetStatus);
            }, ANIMATION_DURATION_IN_MS);
          }
        });
      }

      if (currentSize !== targetSize) {
        this.setState({ size: targetSize });
      }
    }, ANIMATION_DURATION_IN_MS);
  }

  /**
   * Only trigger render if comopnent's state got
   * updated from interval callback
   */
  shouldComponentUpdate(nextProps: ProcessIndicator['props'], nextState: ProcessIndicatorState) {
    const isSameStatus = nextProps.status === nextState.status;
    const isSameSize = nextProps.size === nextState.size;

    return isSameStatus && isSameSize;
  }

  // Clear interval before destroying component
  componentWillUnmount() {
    window.clearInterval(this.interval);
    window.clearTimeout(this.timeout);
  }

  render() {
    const { className, 'data-testid': dataTestId } = this.props;
    const { size, status } = this.state;
    const classes = clsx(`process process-${size}`, className, {
      [`process-danger`]: status === Status.FAILED,
      [`process-stopped`]: status === Status.HIDDEN,
      [`process-success`]: status === Status.SUCCEEDED,
    });

    return (
      <span className={classes} data-testid={dataTestId}>
        <span className="process-icon-container">
          <span className="process-icon-horizontal" />
          <span className="process-icon-vertical" />
        </span>
        <svg xmlns="http://www.w3.org/2000/svg">
          <circle className="process-circle" cx="50%" cy="50%" r={radius[size]} fillOpacity="0.0" />
        </svg>
      </span>
    );
  }
}
