import { ScaleLinear, scaleLinear, ScaleTime, scaleTime } from 'd3-scale'
import * as d3Shape from 'd3-shape'
import { Interval } from 'date-fns'
import _ from 'lodash'
import React, { useState } from 'react'
import { View } from 'react-native'
import Svg, { G, Path, Text, Line, Rect } from 'react-native-svg'
import { formatCountShort } from '../util'
import { colorsV2 } from '../Colors'

/**
 * Vertical line that indicates the currently selected date, if any
 */
const SelectedDateIndicatorLine: React.FC<{
  selectedDate: Date | null
  scaleX: ScaleTime<number, number>
  height: number
}> = ({ selectedDate, scaleX, height }) => {
  return selectedDate != null ? (
    <Line
      x={scaleX(selectedDate)}
      y1={0}
      y2={height}
      stroke={colorsV2.white}
      strokeWidth={2}
    />
  ) : null
}

/**
 * y-axis lines and labels
 */
const YAxis: React.FC<{
  scaleY: ScaleLinear<number, number>
  graphWidth: number
  width: number
}> = ({ scaleY, graphWidth, width }) => (
  <G>
    {scaleY.ticks(3).map((tick, i) => (
      <G key={i} translateY={scaleY(tick)}>
        <Line
          x1={0}
          x2={graphWidth - width}
          stroke={colorsV2.metals.metal1}
          strokeWidth={1}
        />
        <Text
          translateX={graphWidth}
          fill={colorsV2.offWhite}
          fontWeight="bold"
          textAnchor="end"
        >
          {formatCountShort(tick)}
        </Text>
      </G>
    ))}
  </G>
)

export const StackedLineGraph: React.FC<{
  maxInterval: Interval
  selectedInterval: Interval
  selectedDate: Date | null
  data: { date: string; sources: Record<string, number | null> }[]
  sourceOrder: string[]
  colors: Record<string, string>
  yAxisLabelsWidth: number
}> = ({
  data,
  sourceOrder,
  colors,
  maxInterval,
  selectedInterval,
  selectedDate,
  yAxisLabelsWidth,
}) => {
  const [size, setSize] = useState({ width: 100, height: 10 })

  const padding = {
    left: 0,
    right: 0,
    top: 20,
    bottom: 0,
  }

  const contentWidth =
    size.width - yAxisLabelsWidth - padding.left - padding.right
  const contentHeight = size.height - padding.top - padding.bottom

  const maxSum = Math.max(...data.map((x) => _.sum(Object.values(x.sources))))

  const scaleX = scaleTime()
    .domain([maxInterval.start, maxInterval.end])
    .range([0, contentWidth])

  const scaleY = scaleLinear().domain([0, maxSum]).range([contentHeight, 0])

  const getArea = d3Shape
    .area<
      d3Shape.SeriesPoint<{
        date: string
        sources: Record<string, number | null>
      }>
    >()
    .x0((d) => scaleX(new Date(d.data.date)) ?? 0)
    .y0((d) => scaleY(d[0]) ?? 0)
    .y1((d) => scaleY(d[1]) ?? 0)
    .defined((d) => d.data != null)

  const sources = _.uniq(data.flatMap((d) => Object.keys(d.sources))).sort(
    (a, b) => sourceOrder.indexOf(a) - sourceOrder.indexOf(b)
  )
  const getStack = d3Shape
    .stack<{ date: string; sources: Record<string, number | null> }, string>()
    .keys(sources)
    .value((d, source) => d.sources[source] ?? 0)

  //The reciprocal of the selected interval
  const notSelectedInterval: Interval = {
    start: maxInterval.start,
    end: selectedInterval.start,
  }

  return (
    <View style={{ flex: 1 }} onLayout={(e) => setSize(e.nativeEvent.layout)}>
      <Svg viewBox={`0 0 ${size.width} ${size.height}`}>
        <G transform={`translate(${padding.left}, ${padding.top})`}>
          {getStack(data).map((x, i) => {
            const key = x.key
            const color = colors[key]
            return <Path d={getArea(x) ?? ''} fill={color} key={i} />
          })}
          <YAxis
            scaleY={scaleY}
            width={yAxisLabelsWidth}
            graphWidth={size.width}
          />
        </G>
        <Rect
          width={scaleX(notSelectedInterval.end)}
          height={size.height}
          fill="#0F1214"
          fillOpacity={0.85}
        />
        <SelectedDateIndicatorLine
          selectedDate={selectedDate}
          scaleX={scaleX}
          height={size.height}
        />
      </Svg>
    </View>
  )
}
