import React from 'react'
import PropTypes from 'prop-types'
import Group from 'react-group'
import objectToString from 'javascript-stringify'
import Arguments from 'rsg-components/Arguments'
import Argument from 'rsg-components/Argument'
import Code from 'rsg-components/Code'
import JsDoc from 'rsg-components/JsDoc'
import Markdown from 'rsg-components/Markdown'
import Name from 'rsg-components/Name'
import Type from 'rsg-components/Type'
import Text from 'rsg-components/Text'
import Para from 'rsg-components/Para'
import Table from 'rsg-components/Table'
import { unquote, getType, showSpaces } from 'rsg-components/Props/util'

function renderType(type) {
  if (!type) {
    return 'unknown'
  }

  const { name } = type

  switch (name) {
    case 'arrayOf':
      return `${type.value.name}[]`
    case 'objectOf':
      return `{${renderType(type.value)}}`
    case 'instanceOf':
      return type.value
    default:
      return name
  }
}

function renderFlowType(type) {
  if (!type) {
    return 'unknown'
  }

  const { name, raw, value } = type

  switch (name) {
    case 'enum':
      return name
    case 'literal':
      return value
    case 'signature':
      return renderComplexType(type.type, raw)
    case 'union':
    case 'tuple':
      return renderComplexType(name, raw)
    default:
      return raw || name
  }
}

function renderComplexType(name, title) {
  return (
    <Text size='small' underlined title={title}>
      <Type>{name}</Type>
    </Text>
  )
}

function renderEnum(prop) {
  if (!Array.isArray(getType(prop).value)) {
    return <span>{getType(prop).value}</span>
  }

  const values = getType(prop).value.map(({ value }) => (
    <Code key={value}>{showSpaces(unquote(value))}</Code>
  ))
  return (
    <span>
      One of:{' '}
      <Group separator=', ' inline>
        {values}
      </Group>
    </span>
  )
}

function renderShape(props) {
  const rows = []
  for (const name in props) {
    const prop = props[name]
    const defaultValue = renderDefault(prop)
    const description = prop.description
    rows.push(
      <div key={name}>
        <Name>{name}</Name>
        {': '}
        <Type>{renderType(prop)}</Type>
        {defaultValue && ' — '}
        {defaultValue}
        {description && ' — '}
        {description && <Markdown text={description} inline />}
      </div>
    )
  }
  return rows
}

const defaultValueBlacklist = ['null', 'undefined']

function renderDefault(prop) {
  // Workaround for issue https://github.com/reactjs/react-docgen/issues/221
  // If prop has defaultValue it can not be required
  if (prop.defaultValue) {
    if (prop.type || prop.flowType) {
      const propName = prop.type ? prop.type.name : prop.flowType.type

      if (defaultValueBlacklist.indexOf(prop.defaultValue.value) > -1) {
        return <Code>{showSpaces(unquote(prop.defaultValue.value))}</Code>
      } else if (propName === 'func' || propName === 'function') {
        return (
          <Text
            size='small'
            color='light'
            underlined
            title={showSpaces(unquote(prop.defaultValue.value))}
          >
            Function
          </Text>
        )
      } else if (propName === 'shape' || propName === 'object') {
        try {
          // We eval source code to be able to format the defaultProp here. This
          // can be considered safe, as it is the source code that is evaled,
          // which is from a known source and safe by default
          // eslint-disable-next-line no-eval
          const object = eval(`(${prop.defaultValue.value})`)
          return (
            <Text
              size='small'
              color='light'
              underlined
              title={objectToString(object, null, 2)}
            >
              Shape
            </Text>
          )
        } catch (e) {
          // eval will throw if it contains a reference to a property not in the
          // local scope. To avoid any breakage we fall back to rendering the
          // prop without any formatting
          return (
            <Text
              size='small'
              color='light'
              underlined
              title={prop.defaultValue.value}
            >
              Shape
            </Text>
          )
        }
      }
    }

    return <Code>{showSpaces(unquote(prop.defaultValue.value))}</Code>
  } else if (prop.required) {
    return (
      <Text size='small' color='light'>
        Required
      </Text>
    )
  }
  return ''
}

function renderDescription(prop) {
  const { description, tags = {} } = prop
  const extra = renderExtra(prop)
  const args = [
    ...(tags.arg || []),
    ...(tags.argument || []),
    ...(tags.param || []),
  ]
  const returnDocumentation =
    (tags.return && tags.return[0]) || (tags.returns && tags.returns[0])

  return (
    <div>
      {description && <Markdown text={description} />}
      {extra && <Para>{extra}</Para>}
      <JsDoc {...tags} />
      {args.length > 0 && <Arguments args={args} heading />}
      {returnDocumentation && <Argument {...returnDocumentation} returns />}
    </div>
  )
}

function renderExtra(prop) {
  const type = getType(prop)
  if (!type) {
    return null
  }
  switch (type.name) {
    case 'enum':
      return renderEnum(prop)
    case 'union':
      return renderUnion(prop)
    case 'shape':
      return renderShape(prop.type.value)
    case 'arrayOf':
      if (type.value.name === 'shape') {
        return renderShape(prop.type.value.value)
      }
      return null
    case 'objectOf':
      if (type.value.name === 'shape') {
        return renderShape(prop.type.value.value)
      }
      return null
    default:
      return null
  }
}

function renderUnion(prop) {
  const type = getType(prop)
  if (!Array.isArray(type.value)) {
    return <span>{type.value}</span>
  }

  const values = type.value.map((value, index) => (
    <Type key={`${value.name}-${index}`}>{renderType(value)}</Type>
  ))
  return (
    <span>
      One of type:{' '}
      <Group separator=', ' inline>
        {values}
      </Group>
    </span>
  )
}

function renderName(prop) {
  const { name, tags = {} } = prop
  return <Name deprecated={!!tags.deprecated}>{name}</Name>
}

function renderTypeColumn(prop) {
  if (prop.flowType) {
    return <Type>{renderFlowType(getType(prop))}</Type>
  }
  return <Type>{renderType(getType(prop))}</Type>
}

export function getRowKey(row) {
  return row.name
}

export const columns = [
  {
    caption: '名称',
    render: renderName,
    className: 'name',
  },
  {
    caption: '说明',
    render: renderDescription,
    className: 'description',
  },
  {
    caption: '类型',
    render: renderTypeColumn,
    className: 'type',
  },
  {
    caption: '默认值',
    render: renderDefault,
    className: 'default',
  },
]

export default function PropsRenderer({ props }) {
  return <Table columns={columns} rows={props} getRowKey={getRowKey} />
}

PropsRenderer.propTypes = {
  props: PropTypes.array.isRequired,
}
