import React, { useEffect, useRef, useState } from 'react'
import moment from 'moment'
import {
  Line,
  LineChart,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from 'recharts'

import Button from '../../Button/Button'
import { hasValue, sortByProperty } from '../../../services/HelperServiceTyped'
import HeaderMetric from '../../HeaderMetric/HeaderMetric'
import ListLoading from '../../Loaders/ListLoading'
import MdashCheck from '../../Mdash/MdashCheck'
import Popover from '../../Popover/Popover'
import { SideDrawer } from '../../SideDrawer/SideDrawer'
import SparkLine from './SparkLine'
import { useIsMobileView } from '../../../hooks/useIsMobileView/useIsMobileView'
import { abbreviateNumber } from './abbreviateNumber'
import type { HeaderMetricProps } from '../../HeaderMetric/HeaderMetric'
import styles from './_spark-line-with-tooltip.module.scss'
import { c } from '../../../translations/LibraryTranslationService'

type HeaderMetricType = {
  /** Display title for popover chart */
  title: string
  /** Main numerical display value at the top of the chart */
  value: number
  /** Is the chart loading */
  loading?: boolean
  /** The change value to be displayed (second displayed metric) */
  change?: number
  /** Currency object that includes currency code & symbol */
  currency?: {
    currencyCode: string
    currencySymbol: string
  }
  /** The change percentage (third displayed metric) */
  pctChange?: number
  /** Value type of display numbers (must be either percentage or number) */
  formatType?: 'percentage' | 'number'
} & HeaderMetricProps

export type SparklineProps = {
  // REQUIRED PARAMS
  /** Array of graph data object [{data_key: value, period_key: date }, ...] */
  graphData: Array<Record<string, unknown>>
  /** Main graph line color. Accepted values are 'green', 'red', 'blue', or 'purple' (default is 'blue'). The dark variant of the selected color will be applied. */
  graphColor: 'green' | 'red' | 'blue' | 'purple'

  // OPTIONAL PARAMS
  /** The string format for the display date (default: 'MMM Do'); for hours:minutes use 'h:mm' */
  customDateFormat?: string
  /** The change value to be displayed (second displayed metric) */
  changeValue?: number
  /** connects valid data points across null values (no data point is given to null values) */
  connectNulls?: boolean
  /** The key for the values of the data in the chartData object (default = 'data_point') */
  dataKey?: string
  /** Type of data (default: 'number') */
  dataType?: 'number' | 'currency' | 'percentage' | 'sales'
  /** Max domain value to display (must provide domainMin also) */
  domainMax?: number
  /** Min domain value to display (must provide domainMax also) */
  domainMin?: number
  /** All HeaderMetric props allowed */
  headerMetricProps: HeaderMetricType
  /** Optional HeaderMetric props allowed for secondary metric. The metric is not used for graph plotting but as an additional info metric */
  secondaryHeaderMetricProps?: HeaderMetricType
  /** Hide sparkline preview */
  hideSparklinePreview?: boolean
  /** Y-axis domain inverted */
  invertYAxis?: boolean

  /** Time period object key (default: 'date') */
  periodKey?: string
  /** Secondary display object */
  secondaryDisplay?: () => React.JSX.Element
  /** Show trophy icon in front of display value in header */
  showTrophy?: boolean
  /** Average/Threshold line color (default is the same as labelColor) */
  thresholdColor?: 'green' | 'red' | 'blue'
  /** The key for the data value representing the average/threshold value in the graph (default = 'threshold') */
  thresholdKey?: string
  /* Props to be passed for sparkline container height and width (number or percentage like '100%') */
  sparklineContainerDimension?: {
    height?: number | `${number}%`
    width?: number | `${number}%`
  }
  /** Optional prop to add a test id to the SparklineWithTooltip for QA testing */
  qaTestId?: string
  /** Optional prop to hide decimal values on the Y-axis */
  hideDecimalOnYAxis?: boolean
}

const DEFAULT_BLUE = 'var(--dark-blue)'
const DEFAULT_LIGHTBLUE = 'var(--chart-dark-2-blue)'

const Sparkline = ({
  customDateFormat = 'MMM Do',
  connectNulls = false,
  dataType = 'number',
  dataKey = 'data_point',
  domainMax,
  domainMin,
  graphColor = 'blue',
  graphData,
  headerMetricProps,
  secondaryHeaderMetricProps,
  hideSparklinePreview = false,
  invertYAxis,
  periodKey = 'date',
  secondaryDisplay,
  thresholdColor,
  thresholdKey = 'threshold',
  sparklineContainerDimension,
  qaTestId = 'sparkline-with-tooltip',
  hideDecimalOnYAxis,
}: SparklineProps): React.JSX.Element => {
  const isMobileView = useIsMobileView()
  const graphLineColor = `var(--dark-${graphColor})`
  const thresholdStrokeColor = `var(--chart-dark-2-${
    thresholdColor || graphColor
  })`
  const { title, currency } = headerMetricProps
  const [isDrawerOpen, setDrawerOpen] = useState(false)

  /////////////////////////////////////////////////////////////////////////////////////////////////
  // DYNAMIC CALCULATIONS FOR TOOLTIP HEIGHT WITH SECONDARY CONTAINER
  /////////////////////////////////////////////////////////////////////////////////////////////////
  const [secondaryContainerHeight, setSecondaryContainerHeight] = useState(0)
  const [secondaryContainerWidth, setSecondaryContainerWidth] = useState(0)
  const containerRef = useRef<HTMLDivElement>(null)
  const [isContainerOpened, setIsContainerOpened] = useState(false)
  const getSecondaryContainerHeight = () => {
    const elementRect = containerRef.current?.getBoundingClientRect()
    if (elementRect) {
      return { height: elementRect.height, width: elementRect.width }
    } else {
      return { height: 0, width: 0 }
    }
  }

  useEffect(() => {
    const { height, width } = getSecondaryContainerHeight()
    if (height > 0 && width > 0 && isContainerOpened) {
      setSecondaryContainerWidth(width)
      setSecondaryContainerHeight(height)
      setIsContainerOpened(false)
    }
  }, [isContainerOpened])

  // NOTE: 200 is the height of the main chart container;
  // 105 includes the chart legend and the top and bottom padding of the tooltip container;
  // 156 includes the 73 from the main chart plus 83 for the header height;
  // secondaryContainerHeight is the dynamic height of the secondary container;
  // if `isMobileView` then the chart and secondary container are stacked vertically and need
  // the combined height of both containers; otherwise the popover container needs to be
  // the height of the tallest container
  const tippyContainerHeight = isMobileView
    ? `${200 + secondaryContainerHeight + 156}px`
    : `${Math.max(200, secondaryContainerHeight) + 105}px`
  /////////////////////////////////////////////////////////////////////////////////////////////////
  // NOTE: 400 is the width of the main chart container
  // secondaryContainerWidth is the dynamic width of the secondary container;
  const tippyContainerWidth = isMobileView
    ? '100%'
    : `${400 + secondaryContainerWidth}px`

  const TooltipChart = () => (
    <div
      className={`${styles.chartSpacing} first-container`}
      data-testid={`${title}-sparkline-tooltip-popover-chart`}
      onClick={(e) => {
        e.stopPropagation()
      }}
    >
      <ResponsiveContainer width='100%' height={200}>
        <LineChart
          accessibilityLayer={false}
          data={graphData}
          // this onClick is needed for the tooltip to work on mobile (https://github.com/recharts/recharts/issues/444#issuecomment-335857687)
          onClick={undefined}
        >
          <Tooltip
            content={
              <LineTooltip
                customDateFormat={customDateFormat}
                decimalScale={headerMetricProps.decimalScale}
                prefix={dataType === 'currency' ? currency?.currencySymbol : ''}
                suffix={dataType === 'percentage' ? '%' : ''}
              />
            }
            cursor
          />
          <YAxis
            axisLine={false}
            tickLine={false}
            domain={[
              domainMin === 1 ? 1 : 'auto',
              domainMax === 1 ? 1 : 'auto',
            ]}
            tickCount={
              domainMax && domainMin && domainMax - domainMin <= 2 ? 3 : 5
            }
            tickMargin={5}
            width={44}
            style={{
              fontSize: '12px',
              fill: 'var(--purple)',
            }}
            type='number'
            reversed={invertYAxis}
            tickFormatter={(tick) =>
              `${
                dataType === 'sales' || dataType === 'currency'
                  ? currency?.currencySymbol
                  : ''
              }${abbreviateNumber(tick, undefined, hideDecimalOnYAxis)}${dataType === 'percentage' ? '%' : ''}`
            }
          />
          <XAxis
            dataKey={periodKey}
            type='category'
            interval='preserveStart'
            tickCount={8}
            tickLine={false}
            tickMargin={10}
            tick={
              // @ts-expect-error TS is expecting props here, but they are provided by the XAxis component (in XAxis context)
              <TwoLineDateLabel customDateFormat={customDateFormat} />
            }
          />
          <Line
            dataKey={dataKey}
            stroke={graphLineColor || DEFAULT_BLUE}
            strokeWidth={2}
            dot={{
              fill: graphLineColor || DEFAULT_BLUE,
              strokeWidth: 1,
              r: 2,
            }}
            fillOpacity={1}
            fill='var(--white)'
            activeDot={{
              stroke: graphLineColor || DEFAULT_BLUE,
              fill: 'var(--white)',
              strokeWidth: 2,
              r: 4.6,
            }}
            connectNulls={connectNulls}
          />
          <Line
            dataKey={thresholdKey}
            stroke={thresholdStrokeColor || DEFAULT_LIGHTBLUE}
            dot={false}
            strokeDasharray='5,5'
          />
        </LineChart>
      </ResponsiveContainer>
      <div className={styles.tooltipLabel}>
        <div className={styles.alignCenter}>
          <div className={`${styles.line} bg-dark-${graphColor}`} />
          <div className={styles.titleText}>{title}</div>
        </div>
      </div>
    </div>
  )

  const SecondaryDisplay = () => (
    <div
      ref={containerRef}
      style={{
        visibility: secondaryContainerHeight ? 'visible' : 'hidden',
        maxWidth: '500px', //to make sure secondary container content wont go beyond the main container
      }}
    >
      {secondaryDisplay && secondaryDisplay()}
    </div>
  )

  const PopoverOrSidedrawerContent = () => (
    <div
      className={styles.tooltipPopoverContainer}
      style={{
        ...(secondaryDisplay
          ? {
              height: tippyContainerHeight,
              minWidth: tippyContainerWidth,
            }
          : {}),
      }}
    >
      {/* TOOLTIP HEADER */}
      <div
        className={styles.tooltipPopoverHeader}
        data-testid={`${title}-sparkline-tooltip-popover-header`}
      >
        <HeaderMetric
          {...headerMetricProps}
          className={
            secondaryHeaderMetricProps ? styles.primaryHeaderMetricWrap : ''
          }
        />

        {secondaryHeaderMetricProps && !isMobileView ? (
          <div className={styles.divider} />
        ) : null}

        {secondaryHeaderMetricProps ? (
          <HeaderMetric {...secondaryHeaderMetricProps} />
        ) : null}
      </div>

      {/* TOOLTIP CHART ONLY */}
      {!secondaryDisplay && <TooltipChart />}

      {/* OR...TOOLTIP CHART && SECONDARY CONTAINER */}
      {secondaryDisplay && (
        <div className={styles.tooltipBodyContainer}>
          <TooltipChart />
          <SecondaryDisplay />
        </div>
      )}
    </div>
  )

  const PopoverOrSideDrawerChildren = ({
    openPopoverOrSidedrawer,
    isOpen,
  }: {
    openPopoverOrSidedrawer?: (value: boolean) => void
    isOpen?: boolean
  }) =>
    // SMALL SPARKLINE CHART - INITIAL DISPLAY CHART
    graphData?.length ? (
      <div
        onClick={() => {
          openPopoverOrSidedrawer?.(!isOpen)
          secondaryDisplay &&
            setTimeout(() => {
              setIsContainerOpened(true)
            }, 200) //  To ensure Popover is rendered after the animation, so containerRef is set correctly
        }}
        className={styles.miniChartContainer}
      >
        <SparkLine
          graphData={graphData}
          dataKey={dataKey}
          strokeColor={graphColor}
          thresholdColor={thresholdColor || graphColor}
          thresholdKey={thresholdKey}
          containerClassName={hideSparklinePreview ? styles.hide : ''}
          lineChartProps={{ style: { cursor: 'pointer' } }}
          yAxisProps={{ reversed: invertYAxis }}
          lineProps={{ connectNulls }}
          customDimension={sparklineContainerDimension}
        />
        <Button
          styleType='text-blue'
          className={graphData.length === 1 ? styles.viewButton : ''}
        >
          {c('view')}
        </Button>
      </div>
    ) : (
      <div style={{ width: '150px' }}>
        <ListLoading
          customGridGap='2px'
          customHeight='14px'
          numberOfRows={3}
          noSlideInUp
        />
      </div>
    )

  return isMobileView ? (
    <>
      {PopoverOrSideDrawerChildren({
        openPopoverOrSidedrawer: setDrawerOpen,
        isOpen: isDrawerOpen,
      })}
      <SideDrawer
        isOpen={isDrawerOpen}
        headerContent={headerMetricProps.title}
        closeCallout={() => setDrawerOpen(false)}
        qaTestId={`${qaTestId}-side-drawer`}
      >
        {PopoverOrSidedrawerContent()}
      </SideDrawer>
    </>
  ) : (
    <Popover
      position='top'
      popoverContent={PopoverOrSidedrawerContent()}
      maxWidth={secondaryDisplay ? tippyContainerWidth : '500'}
      noPadding
      className={secondaryDisplay ? styles.popoverSecondaryDisplay : ''}
      qaTestId={qaTestId}
    >
      {({ visible, setVisible }) =>
        PopoverOrSideDrawerChildren({
          openPopoverOrSidedrawer: setVisible,
          isOpen: visible,
        })
      }
    </Popover>
  )
}

export default Sparkline

type TwoLineDateLabelProps = {
  x: string | number
  y: string | number
  payload: {
    value: string | number
  }
  customDateFormat: string
}

const TwoLineDateLabel = ({
  x,
  y,
  payload,
  customDateFormat,
}: TwoLineDateLabelProps): React.JSX.Element => {
  const day = moment.utc(payload.value)
  const dateFormat = customDateFormat

  return (
    <g>
      <text className={styles.fs12} x={x} y={y}>
        <tspan
          fill='rgb(116, 121, 157)'
          textAnchor='middle'
          key='0'
          x={x}
          dy='1.1em'
        >
          {day.format(dateFormat)}
        </tspan>
      </text>
    </g>
  )
}

type LineTooltipTypes = {
  /** Required but provided through context by Recharts */
  payload?: {
    tooltipId: number
    dataKey: string
    value: number
    color: string
  }[]
  active?: boolean
  customDateFormat?: string
  decimalScale?: number
  keysToRemovePrefix?: string[]
  label?: string
  prefix?: string
  suffix?: string
  tooltipOrder?: string[]
  tooltipSecondDate?: string
}

const LineTooltip = ({
  payload,
  active,
  customDateFormat,
  decimalScale = 0,
  keysToRemovePrefix,
  label,
  prefix,
  suffix,
  tooltipOrder,
  tooltipSecondDate,
}: LineTooltipTypes) => {
  const locale = undefined // may eventually customize with user's locale
  const options = {
    minimumFractionDigits: decimalScale,
    maximumFractionDigits: decimalScale,
  }
  if (tooltipOrder) {
    payload?.map((e) => {
      const tooltipId = tooltipOrder.indexOf(e.dataKey) + 1
      e.tooltipId = tooltipId
      return e
    })
    if (payload) {
      payload = sortByProperty(payload, 'tooltipId')
    }
  }
  const day = moment.utc(label).format(customDateFormat)
  if (active) {
    return (
      <div className={styles.container}>
        {/* DISPLAY DATE */}
        <div className={styles.labelContainer}>
          <div>{day}</div>
          {tooltipSecondDate && (
            <span className={styles.titleText}>
              {`  -  ${moment(label).add(6, 'days').format(customDateFormat)}`}
            </span>
          )}
        </div>

        {/* DISPLAY DATA POINT DATA */}
        <div>
          {payload?.map((e) => {
            const check =
              typeof e.value === 'number' && !isNaN(e.value)
                ? hasValue(e.value)
                : !!e.value
            if (e.dataKey.includes('dontuse')) {
              return undefined
            }
            const usePrefix = prefix && !keysToRemovePrefix?.includes(e.dataKey)
            const useSuffix = suffix && !keysToRemovePrefix?.includes(e.dataKey)
            return (
              <div
                key={e.dataKey}
                className={styles.tooltipHoverTextContainer}
                data-testid='tooltip-hover-text-container'
              >
                {/* TOOLTIP HOVER TEXT */}
                <div className={styles.titleText}>
                  {e.dataKey
                    .toLocaleString()
                    .split('_')
                    .map((word) => {
                      if (word.toLowerCase() === 'vat') {
                        word = 'VAT'
                      }

                      return word.charAt(0).toUpperCase() + word.slice(1)
                    })
                    .join(' ')}
                </div>

                {/* TOOLTIP HOVER VALUE */}
                <div className={styles.tooltipStats} style={{ color: e.color }}>
                  <MdashCheck check={check}>
                    {usePrefix && prefix}
                    {e.value.toLocaleString(locale, options)}
                    {useSuffix && suffix}
                  </MdashCheck>
                </div>
              </div>
            )
          })}
        </div>
      </div>
    )
  }
  return null
}
