import * as React from 'react'
import * as PropTypes from 'prop-types'
import styled from 'styled-components'
import PrismTheme from '../../utilities/prism-themes/dark'
import docgen from '../../docgen.json'
import unified from 'unified'
import parse from 'remark-parse'
import remark2react from 'remark-react'

const Base = styled('table')({
  display: 'table',
  margin: '0 -0.5rem',
  '@media only screen': {
    borderCollapse: 'collapse',
    border: 0,
    display: 'block',
    margin: 0,
    '@media (min-width: 720px)': {
      display: 'table',
      margin: '0 -0.5rem',
    },
  },
})

const Colgroup = styled('colgroup')({
  display: 'table-column-group',
  '@media only screen': {
    display: 'none',
    '@media (min-width: 720px)': {
      display: 'table-column-group',
    },
  },
})

const HeaderCellGroup = styled('thead')({
  display: 'table-header-group',
  '@media only screen': {
    display: 'none',
    '@media (min-width: 720px)': {
      display: 'table-header-group',
    },
  },
})

const HeaderRow = styled('tr')({
  display: 'table-row',
  margin: 0,
  '@media only screen': {
    display: 'block',
    margin: '2rem 0',
    '@media (min-width: 720px)': {
      display: 'table-row',
      margin: 0,
    },
  },
})

const HeaderCell = styled('th')({
  verticalAlign: 'top',
  textAlign: 'left',
  fontSize: '0.75rem',
  fontWeight: 'bolder',
  textTransform: 'uppercase',
  border: 0,
  display: 'table-cell',
  padding: '0 0.5rem',
  '@media only screen': {
    display: 'block',
    padding: 0,
    '@media (min-width: 720px)': {
      display: 'table-cell',
      padding: '0 0.5rem',
    },
  },
})

const MainBodyCell = styled('th')({
  display: 'table-cell',
  padding: '0.5rem',
  textAlign: 'left',
  '@media only screen': {
    verticalAlign: 'top',
    border: 0,
    lineHeight: 1,
    display: 'block',
    padding: 0,
    '@media (min-width: 720px)': {
      display: 'table-cell',
      padding: '0.5rem',
    },
  },
})

const BodyCellGroup = styled('tbody')({
  display: 'table-row-group',
  '@media only screen': {
    display: 'block',
    '@media (min-width: 720px)': {
      display: 'table-row-group',
    },
  },
})

const BodyCell = styled('td')({
  padding: '0.5rem 0.5rem',
  display: 'table-cell',
  lineHeight: 1.25,
  '& :first-child': {
    marginTop: 0,
  },
  '& :last-child': {
    marginBottom: 0,
  },
  '&[data-column-name]::before': {
    display: 'none',
  },
  '@media only screen': {
    verticalAlign: 'top',
    padding: 0,
    border: 0,
    lineHeight: 'inherit',
    display: 'block',
    '&[data-column-name]::before': {
      display: 'inline',
      fontSize: '0.75rem',
      fontWeight: 'bolder',
      textTransform: 'uppercase',
      content: "attr(data-column-name) ':'",
      marginRight: '1rem',
    },
    '@media (min-width: 720px)': {
      padding: '0.5rem 0.5rem',
      display: 'table-cell',
      lineHeight: 1.25,
      '&[data-column-name]::before': {
        display: 'none',
      },
    },
  },
})

const Code = styled('code')({
  display: 'inline-block',
})

const propTypes = {
  of: PropTypes.string,
}

type Props = PropTypes.InferProps<typeof propTypes>

type Docs = {
  displayName: string,
  description: string,
  methods: unknown[],
  props: {
    [name: string]: {
      required: boolean,
      type: {
        name: string,
        value: { value: string }[]
      },
      defaultValue: { value: string, },
      description: string,
    }
  }
}

const getPropName = (name, def) => def.required ? name : name + '?'

const getPropType = def => {
  switch (def.type.name) {
    case '(...args: any[]) => any':
      return 'Function'
    case 'enum':
      return def.type.value.map(v => v.value).join(' | ')
    default:
      break
  }
  return def.type.name
}

const getPropDefaultValue = def => {
  return def.defaultValue
    ? JSON.stringify(def.defaultValue.value)
    : undefined
}

const getPrismStyle = style => PrismTheme.styles.find(s => s.types.includes(style)) || PrismTheme.plain

const getPropNameHtml = (propName, propType) => {
  const variable = getPrismStyle('variable')
  const operator = getPrismStyle('operator')
  const fn = getPrismStyle('function')
  const isFunction = propType === 'Function'
  const trueStyle = isFunction ? fn : variable
  return (
    propName.endsWith('?')
      ? (
        <React.Fragment>
          <span
            style={trueStyle.style}
          >
            {propName.slice(0, -1)}
          </span>
          <span
            style={operator.style}
          >
            {propName.slice(-1)}
          </span>
        </React.Fragment>
      )
      : (
        <span
          style={trueStyle.style}
        >
          {propName}
        </span>
      )
  )
}

const getPropTypeHtml = propType => {
  if (!propType) {
    return undefined
  }
  const punctuation = getPrismStyle('punctuation')
  const string = getPrismStyle('string')
  const operator = getPrismStyle('operator')
  const keyword = getPrismStyle('keyword')
  const boolean = getPrismStyle('boolean')
  const number = getPrismStyle('number')
  const type = getPrismStyle('type')
  const tokens = propType.split('|')
  return tokens.reduce(
    (tt, t, i) => {
      let currentToken
      if (t.trim().startsWith('"') && t.trim().endsWith('"')) {
        currentToken = (
          <React.Fragment
            key={t}
          >
            <span
              style={punctuation.style}
            >
              {t.slice(0, t.indexOf('"') + 1)}
            </span>
            <span
              style={string.style}
            >
              {t.slice(t.indexOf('"') + 1, t.lastIndexOf('"'))}
            </span>
            <span
              style={punctuation.style}
            >
              {t.slice(t.lastIndexOf('"'))}
            </span>
          </React.Fragment>
        )
      } else if ('any string boolean number symbol unknown object bigint'.split(' ').includes(t)) {
        currentToken = (
          <span
            key={t}
            style={keyword.style}
          >
            {t}
          </span>
        )
      } else if ('null'.split(' ').includes(t)) {
        currentToken = (
          <span
            key={t}
            style={keyword.style}
          >
            {t}
          </span>
        )
      } else if ('true false'.split(' ').includes(t)) {
        currentToken = (
          <span
            key={t}
            style={boolean.style}
          >
            {t}
          </span>
        )
      } else if (!isNaN(Number(t))) {
        currentToken = (
          <span
            key={t}
            style={number.style}
          >
            {t}
          </span>
        )
      } else {
        currentToken = (
          <span
            key={t}
            style={type.style}
          >
            {t}
          </span>
        )
      }
      return [
        ...tt,
        i > 0 && <span
          key={`**separator:${t}**`}
          style={operator.style}
        >
          |
        </span>,
        currentToken,
      ]
    },
    []
  )
}

const Props: React.FC<Props> = ({ of: ofAttr }) => {
  const docs = (docgen as unknown as Docs[]).find(d => d.displayName === ofAttr)

  if (!(docs as Docs)) {
    return null
  }

  return (
    <Base>
      <Colgroup>
        <col width="20%" />
        <col width="25%" />
        <col width="25%" />
        <col width="*" />
      </Colgroup>
      <HeaderCellGroup>
        <HeaderRow>
          <HeaderCell>
            Name
          </HeaderCell>
          <HeaderCell>
            Type
          </HeaderCell>
          <HeaderCell>
            Default
          </HeaderCell>
          <HeaderCell>
            Description
          </HeaderCell>
        </HeaderRow>
      </HeaderCellGroup>
      <BodyCellGroup>
        {
          Object.entries((docs as Docs).props).map(([name, prop]) => {
            const propName = getPropName(name, prop)
            const propDefaultValue = getPropDefaultValue(prop)
            const propType = getPropType(prop)
            const propDescription = prop.description
              ? (
                (unified()
                  .use(parse)
                  .use(remark2react)
                  .processSync(prop.description).result.props.children[0] as any)
              )
              : undefined
            return (
              <HeaderRow
                key={name}
              >
                <MainBodyCell>
                  <Code>
                    {getPropNameHtml(propName, propType)}
                  </Code>
                </MainBodyCell>
                <BodyCell
                  data-column-name="Type"
                >
                  <Code>
                    {getPropTypeHtml(propType)}
                  </Code>
                </BodyCell>
                <BodyCell
                  data-column-name={propDefaultValue ? 'Default' : undefined}
                >
                  {
                    propDefaultValue
                    && (
                      <Code>
                        {getPropTypeHtml(propDefaultValue)}
                      </Code>
                    )
                  }
                </BodyCell>
                <BodyCell>
                  {propDescription}
                </BodyCell>
              </HeaderRow>
            )
          })
        }
      </BodyCellGroup>
    </Base>
  )
}

Props.propTypes = propTypes

export default Props
