import React from 'react'
import { Text, View } from 'react-native'
import { ScaleLinear } from 'd3-scale'
import { TextStylesV2 } from '../../Styles'
import { colorsV2 } from '../../Colors'
import { formatCountShort } from '../../util'

interface AxisRange {
  min?: number
  max?: number
}

export interface AxisConfig {
  // The range over which the axis renders labels. This does not have to fully
  // contain "scale", but instead can be used to pad or clip the axis if
  // necessary
  bounds?: AxisRange
  // Instead of using generated labels based on "scale", use provided optional
  // min or max values, e.g. if scale was [0, 100], yielding ticks [0, 50, 100],
  // providing {min: 1} would instead yield ticks [1, 50, 100].
  fixedLabels?: AxisRange
}

export const YAxis: React.FC<{
  scale: ScaleLinear<number, number>
  side: 'left' | 'right'
  color: string
  // If config is not provided, label defaults will be inferred from "scale"
  config?: AxisConfig
}> = (props) => {
  const textStyle = {
    ...TextStylesV2.tinyBold,
    ...{ color: colorsV2.offWhite },
  }

  // The font we use is not vertically centered, so this calculation just shifts
  // the axis proportionally up by 25% so labels can align with dividers.
  const fontAlignment = textStyle.fontSize * 0.25
  const marginVertical = -textStyle.fontSize / 2

  const domain = props.scale.domain()
  // Domain _may_ be more than two items, so we just play it safe and use
  // max/min to find the delta instead of deconstructing into [a, b]
  const domainMin = Math.min(...domain)
  const domainMax = Math.max(...domain)
  const delta = Math.abs(domainMax - domainMin)
  // In case the domain is less than the number of ticks, we reduce the number
  // of ticks to avoid situation with fractional ticks, e.g. domain [1, 2] with
  // 5 ticks would yield [1, 1, 2, 2, 2], with the clamp, we get ticks [1, 2]
  const clampedTicks = Math.min(delta, 4)
  const tickValues = props.scale
    .ticks(clampedTicks)
    .reverse()
    // First tick may be overridden. E.g. when zero is not a valid label for
    // playlist positions, we will use provided minimum tick label instead.
    // Other ticks are still computed for readability by D3.
    .map((tickValue) => {
      if (props?.config?.fixedLabels?.min && tickValue === domainMin) {
        return props?.config?.fixedLabels?.min ?? tickValue
      } else if (props?.config?.fixedLabels?.max && tickValue === domainMax) {
        return props?.config?.fixedLabels?.max ?? tickValue
      } else {
        return tickValue
      }
    })

  return (
    <View style={{ display: 'flex', width: 36 }}>
      {tickValues.map((value, index) => {
        // Label at the bottom of the figure is flush with the container, other
        // labels are centered with the point position
        const isLastTick = index === tickValues.length - 1
        const alignLastLabel =
          isLastTick && !props?.config?.fixedLabels?.min
            ? { bottom: 0 }
            : {
                top: (props.scale(value) ?? 0) + marginVertical - fontAlignment,
              }
        return (
          <Text
            key={value}
            style={{
              ...textStyle,
              color: props.color,
              position: 'absolute',
              ...(props.side === 'left' ? { right: 3 } : { left: 3 }),
              ...alignLastLabel,
            }}
          >
            {formatCountShort(value)}
          </Text>
        )
      })}
    </View>
  )
}
