import { forwardRef, useMemo } from 'react'
import PropTypes from 'prop-types'
import { theme } from 'antd'
import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled'
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled'

const validProgress = (progress) => {
  if (!progress || progress < 0) return 0
  if (progress > 100) return 100
  return progress
}

const getSuccessPercent = ({ success, successPercent }) => {
  if (success && 'progress' in success) return success.progress
  if (success && 'percent' in success) return success.percent
  return successPercent
}

const sortGradient = (gradients) => {
  const entries = Object.keys(gradients)
    .map((key) => ({
      key: Number.parseFloat(key.replace(/%/g, '')),
      value: gradients[key]
    }))
    .filter(({ key }) => !Number.isNaN(key))
    .sort((a, b) => a.key - b.key)

  return entries.map(({ key, value }) => `${value} ${key}%`).join(', ')
}

const resolveStrokeBackground = (strokeColor, direction = 'to right') => {
  if (!strokeColor) return undefined

  if (typeof strokeColor === 'string') {
    return strokeColor
  }

  if (Array.isArray(strokeColor)) {
    return strokeColor[0]
  }

  const { from, to, direction: gradientDirection, ...rest } = strokeColor

  if (Object.keys(rest).length > 0) {
    return `linear-gradient(${gradientDirection || direction}, ${sortGradient(rest)})`
  }

  return `linear-gradient(${gradientDirection || direction}, ${from}, ${to})`
}

const getBarHeight = ({ size, strokeWidth }) => {
  if (typeof strokeWidth === 'number') return strokeWidth

  if (size === 'small') return 6
  if (typeof size === 'number') return size
  if (Array.isArray(size)) return size[1] ?? 8
  if (typeof size === 'object' && size?.height != null) return size.height

  return 8
}

const getOuterWidth = ({ size, strokeWidth }) => {
  if (typeof size === 'number') return size
  if (Array.isArray(size)) return size[0] ?? -1
  if (typeof size === 'object' && size?.width != null) return size.width
  if (strokeWidth) return -1
  return -1
}

const HProgress = forwardRef(function HProgress(
  {
    className,
    rootClassName,
    style,
    percent = 0,
    success,
    successPercent,
    status,
    showInfo = true,
    strokeColor,
    trailColor,
    strokeLinecap = 'round',
    strokeWidth,
    size = 'default',
    format,
    percentPosition = {}
  },
  ref
) {
  const { token } = theme.useToken()

  const { align: infoAlign = 'end', type: infoPosition = 'outer' } =
    percentPosition

  const resolvedPercent = validProgress(percent)
  const resolvedSuccessPercent = getSuccessPercent({ success, successPercent })
  const successValue =
    resolvedSuccessPercent == null
      ? undefined
      : validProgress(resolvedSuccessPercent)

  const percentNumber = Number.parseInt(
    String(successValue ?? resolvedPercent),
    10
  )

  const progressStatus = useMemo(() => {
    if (status === 'exception' || status === 'success' || status === 'active') {
      return status
    }
    if (percentNumber >= 100) return 'success'
    return status || 'normal'
  }, [status, percentNumber])

  const barHeight = getBarHeight({ size, strokeWidth })
  const outerWidth = getOuterWidth({ size, strokeWidth })
  const borderRadius =
    strokeLinecap === 'square' || strokeLinecap === 'butt' ? 0 : barHeight

  const trailBackground = trailColor || token.colorFillSecondary

  const defaultFillColor =
    progressStatus === 'exception'
      ? token.colorError
      : progressStatus === 'success'
        ? token.colorSuccess
        : token.colorPrimary

  const fillBackground =
    resolveStrokeBackground(strokeColor) || defaultFillColor

  const successBackground =
    success?.strokeColor || token.colorSuccess

  const textFormatter = format || ((value) => `${value}%`)
  const progressTextValue = textFormatter(
    resolvedPercent,
    successValue ?? undefined
  )

  const progressInfo = !showInfo ? null : (
    <span
      className={[
        'h-progress-text',
        infoPosition === 'outer' && infoAlign === 'start'
          ? 'h-progress-text-start'
          : null
      ]
        .filter(Boolean)
        .join(' ')}
      title={typeof progressTextValue === 'string' ? progressTextValue : undefined}
      style={{ color: token.colorText }}
    >
      {progressStatus === 'exception' ? (
        <CloseCircleFilled style={{ color: token.colorError }} />
      ) : progressStatus === 'success' ? (
        <CheckCircleFilled style={{ color: token.colorSuccess }} />
      ) : (
        progressTextValue
      )}
    </span>
  )

  const innerInfo =
    infoPosition === 'inner' && showInfo ? (
      <span className='h-progress-text-inner'>{progressTextValue}</span>
    ) : null

  const lineInner = (
    <div
      className='h-progress-inner'
      style={{
        height: barHeight,
        backgroundColor: trailBackground,
        borderRadius
      }}
    >
      <div
        className='h-progress-bg'
        style={{
          width: `${resolvedPercent}%`,
          height: barHeight,
          borderRadius,
          background: fillBackground,
          overflow: 'hidden'
        }}
      >
        {innerInfo}
      </div>
      {progressStatus === 'active' && (
        <div
          className='h-progress-bg-mask'
          style={{ width: `${resolvedPercent}%`, borderRadius }}
        >
          <div className='h-progress-bg-wave' />
        </div>
      )}
      {successValue != null && (
        <div
          className='h-progress-success-bg'
          style={{
            width: `${successValue}%`,
            height: barHeight,
            borderRadius,
            background: successBackground
          }}
        />
      )}
    </div>
  )

  const isOuterStart = infoPosition === 'outer' && infoAlign === 'start'
  const isOuterEnd = infoPosition === 'outer' && infoAlign === 'end'
  const isLayoutBottom =
    infoPosition === 'outer' && infoAlign === 'center'

  const content = isLayoutBottom ? (
    <div className='h-progress-layout-bottom'>
      {lineInner}
      {progressInfo}
    </div>
  ) : (
    <div
      className='h-progress-outer'
      style={{ width: outerWidth < 0 ? '100%' : outerWidth }}
    >
      {isOuterStart && progressInfo}
      {lineInner}
      {isOuterEnd && progressInfo}
    </div>
  )

  return (
    <div
      ref={ref}
      className={['h-progress', className, rootClassName]
        .filter(Boolean)
        .join(' ')}
      style={style}
      role='progressbar'
      aria-valuemin={0}
      aria-valuemax={100}
      aria-valuenow={resolvedPercent}
    >
      {content}
    </div>
  )
})

HProgress.propTypes = {
  className: PropTypes.string,
  rootClassName: PropTypes.string,
  style: PropTypes.object,
  percent: PropTypes.number,
  success: PropTypes.shape({
    percent: PropTypes.number,
    progress: PropTypes.number,
    strokeColor: PropTypes.string
  }),
  successPercent: PropTypes.number,
  status: PropTypes.oneOf(['normal', 'exception', 'active', 'success']),
  showInfo: PropTypes.bool,
  strokeColor: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.arrayOf(PropTypes.string),
    PropTypes.object
  ]),
  trailColor: PropTypes.string,
  strokeLinecap: PropTypes.oneOf(['butt', 'square', 'round']),
  strokeWidth: PropTypes.number,
  size: PropTypes.oneOfType([
    PropTypes.oneOf(['default', 'small']),
    PropTypes.number,
    PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
    PropTypes.shape({
      width: PropTypes.number,
      height: PropTypes.number
    })
  ]),
  format: PropTypes.func,
  percentPosition: PropTypes.shape({
    align: PropTypes.oneOf(['start', 'center', 'end']),
    type: PropTypes.oneOf(['inner', 'outer'])
  })
}

export default HProgress
