import React from 'react';
import PropTypes from 'prop-types';
import { parse, resolver } from 'react-docgen';
import Markdown from 'terra-markdown';
import classNames from 'classnames/bind';
import styles from './PropsTable.module.scss';

const cx = classNames.bind(styles);

const propTypes = {
  /**
   * Title of component
   */
  componentName: PropTypes.string,
  /**
   * Markdown source file
   */
  src: PropTypes.string.isRequired,
  /**
   * Type of react-docgen resolver to use for prop-types resolution. Supported values are `default` or `findAllComponentDefinitions`
   */
  propsResolution: PropTypes.oneOf(['default', 'findAllComponentDefinitions']),
};

const defaultProps = {
  propsResolution: 'default',
};

function formatShape(shape) {
  return JSON.stringify(shape, null, 1);
}

function determineType(type) {
  let typeName = type.name;

  if (typeName === 'enum') {
    typeName = 'enum';
  } else if (typeName === 'arrayOf') {
    if (type.value.name === 'shape') {
      typeName = (
        <span>
          {' '}
          array of objects structured like:
          <pre className={cx('props-table-pre')}>
            {' '}
            {formatShape(type.value.value)}
            {' '}
          </pre>
        </span>
      );
    } else {
      typeName = `array of ${type.value.name}s`;
    }
  } else if (typeName === 'union') {
    const options = type.value.map((option) => {
      const name = option.name === 'shape' ? ((
        <span key={option.value}>
          {' '}
          an object structured like:
          <pre className={cx('props-table-pre')}>
            {' '}
            {formatShape(option.value)}
            {' '}
          </pre>
        </span>
      )) : (
        <span key={option.name}>
          {' '}
          {option.name}
        </span>
      );
      return name;
    });
    typeName = options.reduce((curr, next) => [curr, <span key={`${curr.value}-${next.value}`}> or </span>, next]);
  } else if (typeName === 'shape') {
    typeName = (
      <span>
        {' '}
        an object structured like:
        <pre className={cx('props-table-pre')}>
          {' '}
          {formatShape(type.value)}
          {' '}
        </pre>
      </span>
    );
  }

  return typeName;
}

/**
 * Renders a table view for the props metadata of a react component generated by react-docgen
 */
const PropsTable = ({
  componentName, propsResolution, src, ...customProps
}) => {
  /**
   * Runs component source code through react-docgen. Passing second argument to parse
   * function to account for multiple export.
   * @type {Object}
   */
  let componentMetaData;

  /**
   * Alias for props object from componentMetaData
   * @type {Object}
   */
  let componentProps;

  // Resolve using react-docgen's default resolver
  if (propsResolution === 'default') {
    componentMetaData = parse(src);
    componentProps = componentMetaData.props;
  }

  // Resolve using react-docgen's findAllComponentDefinitions resolver
  if (propsResolution === 'findAllComponentDefinitions') {
    componentMetaData = parse(src, resolver.findAllComponentDefinitions);
    componentProps = componentMetaData[0].props;
  }

  const tableRowClass = cx('prop-table-row');
  const tableClassNames = cx([
    'props-table',
    customProps.className,
  ]);

  return (
    <div dir="ltr" className={cx('props-table-markdown')}>
      <h2>
        {componentName}
        {' '}
        Props
      </h2>
      <table {...customProps} className={tableClassNames}>
        <thead>
          <tr>
            <th className={cx('prop-table-name')}>Prop Name</th>
            <th className={cx('prop-table-type')}>Type</th>
            <th className={cx('prop-table-required')}>Is Required</th>
            <th className={cx('prop-table-default')}>Default Value</th>
            <th className={cx('prop-table-description')}>Description</th>
          </tr>
        </thead>
        <tbody>
          {Object.keys(componentProps).map((key) => {
            const prop = componentProps[key];
            if (prop.description && prop.description.match(/@private/)) {
              return null;
            }

            const type = determineType(prop.type);

            /**
             * Check if the react-docgen parser returned a custom proptype.
             * If so, parse the raw value to see if the custom proptype has been marked as required.
            */
            const customRequired = (prop.type.name === 'custom' && prop.type.raw.includes('isRequired'));

            /* eslint-disable react/forbid-dom-props */
            return (
              <tr className={tableRowClass} key={key} style={{ fontSize: '90%' }}>
                <td style={{ fontWeight: 'bold' }}>{key}</td>
                <td>{(prop.type ? type : '')}</td>
                {(customRequired || prop.required
                  ? <td style={{ color: '#d53700' }}>required</td>
                  : <td style={{ color: '#444' }}>optional</td>)}
                {(prop.defaultValue
                  ? <td style={{ fontWeight: 'bold' }}>{prop.defaultValue.value}</td>
                  : <td style={{ color: '#444' }}>none</td>)}
                <td><Markdown src={prop.description} /></td>
              </tr>
            );
            /* eslint-enable react/forbid-dom-props */
          })}
        </tbody>
      </table>
    </div>
  );
};

PropsTable.propTypes = propTypes;
PropsTable.defaultProps = defaultProps;

export default PropsTable;
