import React from 'react'
import { useMemo, useState } from 'react'
import { linearScale } from '../../utils/charts.js'
import { colors } from '../../utils/colors.js'
import { Text } from '../Text/index.js'
import { getChartColorForString } from '../../utils/misc.js'
import {
  autoUpdate,
  FloatingPortal,
  offset,
  shift,
  useClientPoint,
  useFloating,
  useInteractions,
} from '@floating-ui/react'
import { radius } from '../../utils/radius.js'
import { shadows } from '../../utils/shadows.js'
import { Legend } from '../Legend/index.js'

type BarChartProps = {
  /** The data to be visualized in the bar chart. Each record represents a data point with keys corresponding to xKey and yKeys. */
  data: Record<string, number>[]
  /** The key in the data objects that represents the x-axis values. */
  xKey: string
  /** The keys in the data objects that represent the y-axis values. */
  yKeys: string[]
  /** Optional formatter for x-axis values. */
  xFormat?: (x: number) => string
  /** Optional formatter for y-axis values. */
  yFormat?: (y: number) => string
  /** The layout variant for the bars, either 'stacked' or 'separate'. Default is 'separate'. */
  variant?: 'stacked' | 'separate'
  /** The orientation of the chart, either 'horizontal' or 'vertical'. Default is 'vertical'. */
  orientation?: 'horizontal' | 'vertical'
  /** Whether to display a legend for the chart. Default is true. */
  showLegend?: boolean
  /** Whether to display values for each bar. Default is true. */
  showValues?: boolean
}

/**
 * The BarChart component renders a customizable bar chart to visualize data, supporting stacked or separate layouts
 * and both horizontal and vertical orientations. It provides features such as value formatting, legends, and hover tooltips
 * for enhanced data interpretation.
 *
 * @param {BarChartProps} props - The properties for configuring the BarChart.
 */
function BarChart({
  data,
  xKey,
  yKeys,
  xFormat,
  yFormat,
  variant = 'separate',
  orientation = 'vertical',
  showLegend = true,
  showValues = true,
}: BarChartProps) {
  const [minY, maxY] = useMemo(() => {
    let minY = Number.POSITIVE_INFINITY
    let maxY = Number.NEGATIVE_INFINITY

    for (const item of data) {
      if (variant === 'separate') {
        for (const yKey of yKeys) {
          if (item[yKey] < minY) minY = item[yKey]
          if (item[yKey] > maxY) maxY = item[yKey]
        }
      }

      if (variant === 'stacked') {
        let itemSum = 0

        for (const yKey of yKeys) {
          itemSum += item[yKey]
        }

        if (itemSum < minY) minY = itemSum
        if (itemSum > maxY) maxY = itemSum
      }
    }

    return [minY, maxY]
  }, [xKey, yKeys, data])
  const yScale = useMemo(
    () => linearScale([minY, maxY], [5, 100]),
    [minY, maxY],
  )
  const [hover, setHover] = useState<number | null>(null)
  const { refs, floatingStyles, context } = useFloating({
    open: hover !== null,
    strategy: 'fixed',
    placement: 'bottom',
    middleware: [offset(16), shift({ padding: 8 })],
    whileElementsMounted: autoUpdate,
  })

  const clientPoint = useClientPoint(context)

  const { getReferenceProps, getFloatingProps } = useInteractions([clientPoint])

  return (
    <div
      style={{
        width: '100%',
        height: '100%',
        display: 'flex',
        flexDirection: 'column',
        gap: 16,
      }}
    >
      {orientation === 'horizontal' && (
        <div
          style={{
            position: 'relative',
            display: 'grid',
            width: '100%',
            height: '100%',
            gridTemplateRows: `repeat(${data.length}, minmax(${yKeys.length > 1 && variant === 'separate' ? yKeys.length * 12 : 12}px,1fr))`,
            gridTemplateColumns: showValues
              ? 'minmax(48px, max-content) 1fr max-content'
              : 'minmax(48px, max-content) 1fr',
            overflowY: 'auto',
          }}
        >
          {data.map((item, itemIndex) => (
            <React.Fragment key={itemIndex}>
              <div
                style={{
                  display: 'flex',
                  alignItems: 'center',
                  justifyContent: 'flex-end',
                  maxWidth: '96px',
                }}
              >
                <Text singleLine variant="subtext-regular" color="neutral60">
                  {item[xKey]}
                </Text>
              </div>
              <div
                style={{
                  display: 'flex',
                  gap: 8,
                  position: 'relative',
                  paddingLeft: 8,
                  paddingBottom:
                    yKeys.length > 1 && variant === 'separate' ? 8 : 4,
                  paddingTop:
                    yKeys.length > 1 && variant === 'separate' ? 8 : 4,
                }}
                ref={hover === itemIndex ? refs.setReference : undefined}
                {...(hover === itemIndex ? getReferenceProps() : {})}
                onMouseEnter={() => {
                  setHover(itemIndex)
                }}
                onMouseLeave={() => {
                  setHover(null)
                }}
              >
                <div
                  style={{
                    position: 'absolute',
                    width: '1px',
                    background: colors.neutral10,
                    left: 8,
                    top: 0,
                    bottom: 0,
                  }}
                />
                {[0.25, 0.5, 0.75].map((percentage, index) => (
                  <div
                    key={index}
                    style={{
                      position: 'absolute',
                      width: '1px',
                      background: colors.neutral10,
                      left: `calc(8px + (100% - 8px) * ${percentage})`,
                      top: 0,
                      bottom: 0,
                    }}
                  />
                ))}
                <div
                  style={{
                    position: 'absolute',
                    width: '1px',
                    background: colors.neutral10,
                    right: 0,
                    top: 0,
                    bottom: 0,
                  }}
                />
                <div
                  style={{
                    width: '100%',
                    display: 'flex',
                    flexDirection: 'column',
                    gap: 4,
                  }}
                >
                  {variant === 'stacked' ? (
                    <div
                      style={{
                        position: 'relative',
                        height: '100%',
                        width: `${yScale(yKeys.reduce((acc, curr) => (acc += item[curr]), 0))}%`,
                        display: 'flex',
                        gap: 1,
                      }}
                    >
                      {yKeys.map((yKey, yKeyIndex) => (
                        <div
                          key={yKeyIndex}
                          style={{
                            height: '100%',
                            width: `${(item[yKey] / yKeys.reduce((acc, curr) => (acc += item[curr]), 0)) * 100}%`,
                            background: colors.neutralInverted100,
                            ...(yKeyIndex === 0 && {
                              borderTopLeftRadius: 8,
                              borderBottomLeftRadius: 8,
                            }),
                            ...(yKeyIndex === yKeys.length - 1 && {
                              borderTopRightRadius: 8,
                              borderBottomRightRadius: 8,
                            }),
                          }}
                        >
                          <div
                            style={{
                              height: '100%',
                              width: '100%',
                              borderTop: `1px solid ${colors.white20}`,
                              background:
                                colors[getChartColorForString(yKey, false)],
                              ...(yKeyIndex === 0 && {
                                borderTopLeftRadius: 8,
                                borderBottomLeftRadius: 8,
                              }),
                              ...(yKeyIndex === yKeys.length - 1 && {
                                borderTopRightRadius: 8,
                                borderBottomRightRadius: 8,
                              }),
                            }}
                          />
                        </div>
                      ))}
                      {showValues && (
                        <div
                          style={{
                            position: 'absolute',
                            right: 0,
                            top: '50%',
                            maxWidth: '96px',
                            transform: 'translate(calc(100% + 4px), -50%)',
                          }}
                        >
                          <Text
                            singleLine
                            variant="subtext-medium"
                            color="neutral80"
                          >
                            {yFormat(
                              yKeys.reduce(
                                (acc, curr) => (acc += item[curr]),
                                0,
                              ),
                            )}
                          </Text>
                        </div>
                      )}
                    </div>
                  ) : (
                    yKeys.map((yKey) => (
                      <div
                        key={yKey}
                        style={{
                          position: 'relative',
                          height: '100%',
                          width: `${yScale(item[yKey])}%`,
                          background: colors.neutralInverted100,
                          borderRadius: 8,
                        }}
                      >
                        <div
                          style={{
                            width: '100%',
                            height: '100%',
                            borderTop: `1px solid ${colors.white20}`,
                            borderRadius: 8,
                            background:
                              colors[getChartColorForString(yKey, false)],
                          }}
                        />
                        {showValues && (
                          <div
                            style={{
                              position: 'absolute',
                              right: 0,
                              top: '50%',
                              maxWidth: '96px',
                              transform: 'translate(calc(100% + 4px), -50%)',
                            }}
                          >
                            <Text
                              singleLine
                              variant="subtext-medium"
                              color="neutral80"
                            >
                              {yFormat(item[yKey])}
                            </Text>
                          </div>
                        )}
                      </div>
                    ))
                  )}
                </div>
              </div>
              {showValues && (
                <div
                  style={{
                    display: 'flex',
                    flexDirection: 'column',
                    maxWidth: 'calc(96px + 4px)',
                    pointerEvents: 'none',
                    userSelect: 'none',
                    opacity: 0,
                  }}
                >
                  {variant === 'stacked' ? (
                    <div style={{ paddingLeft: 4, overflow: 'clip' }}>
                      <Text
                        singleLine
                        variant="subtext-medium"
                        color="neutral80"
                      >
                        {yFormat(
                          yKeys.reduce((acc, curr) => (acc += item[curr]), 0),
                        )}
                      </Text>
                    </div>
                  ) : (
                    yKeys.map((yKey) => (
                      <div key={yKey} style={{ paddingLeft: 4, overflow: 'clip' }}>
                        <Text
                          singleLine
                          variant="subtext-medium"
                          color="neutral80"
                        >
                          {yFormat(item[yKey])}
                        </Text>
                      </div>
                    ))
                  )}
                </div>
              )}
            </React.Fragment>
          ))}
        </div>
      )}
      {orientation === 'vertical' && (
        <div
          style={{
            position: 'relative',
            display: 'grid',
            width: '100%',
            height: '100%',
            gridTemplateColumns: `repeat(${data.length}, minmax(${yKeys.length > 1 && variant === 'separate' ? yKeys.length * 12 : 12}px,1fr))`,
            gridTemplateRows: showValues ? '20px 1fr 16px' : '1fr 16px',
            overflowX: 'auto',
          }}
        >
          {showValues && data.map((item) => <div key={'label' + item[xKey]} />)}
          {data.map((item, itemIndex) => (
            <React.Fragment key={itemIndex}>
              <div
                style={{
                  display: 'flex',
                  gap: 8,
                  position: 'relative',
                  paddingBottom: 8,
                  paddingRight:
                    yKeys.length > 1 && variant === 'separate' ? 8 : 4,
                  paddingLeft:
                    yKeys.length > 1 && variant === 'separate' ? 8 : 4,
                }}
                ref={hover === itemIndex ? refs.setReference : undefined}
                {...(hover === itemIndex ? getReferenceProps() : {})}
                onMouseEnter={() => {
                  setHover(itemIndex)
                }}
                onMouseLeave={() => {
                  setHover(null)
                }}
              >
                <div
                  style={{
                    position: 'absolute',
                    height: 1,
                    background: colors.neutral10,
                    left: 0,
                    right: 0,
                    top: 0,
                  }}
                />
                {[0.25, 0.5, 0.75].map((percentage) => (
                  <div
                    key={percentage}
                    style={{
                      position: 'absolute',
                      height: 1,
                      background: colors.neutral10,
                      left: 0,
                      right: 0,
                      top: `calc(8px + (100% - 8px) * ${percentage})`,
                    }}
                  />
                ))}
                <div
                  style={{
                    position: 'absolute',
                    height: 1,
                    background: colors.neutral10,
                    left: 0,
                    right: 0,
                    bottom: 8,
                  }}
                />
                <div
                  style={{
                    width: '100%',
                    display: 'flex',
                    gap: 4,
                    alignItems: 'flex-end',
                  }}
                >
                  {variant === 'stacked' ? (
                    <div
                      style={{
                        position: 'relative',
                        width: '100%',
                        height: `${yScale(yKeys.reduce((acc, curr) => (acc += item[curr]), 0))}%`,
                        display: 'flex',
                        flexDirection: 'column-reverse',
                        gap: 1,
                      }}
                    >
                      {yKeys.map((yKey, yKeyIndex) => (
                        <div
                          key={yKey}
                          style={{
                            width: '100%',
                            height: `${(item[yKey] / yKeys.reduce((acc, curr) => (acc += item[curr]), 0)) * 100}%`,
                            background: colors.neutralInverted100,
                            ...(yKeyIndex === 0 && {
                              borderBottomLeftRadius: 8,
                              borderBottomRightRadius: 8,
                            }),
                            ...(yKeyIndex === yKeys.length - 1 && {
                              borderTopLeftRadius: 8,
                              borderTopRightRadius: 8,
                            }),
                          }}
                        >
                          <div
                            style={{
                              height: '100%',
                              width: '100%',
                              background:
                                colors[getChartColorForString(yKey, false)],
                              ...(yKeyIndex === 0 && {
                                borderBottomLeftRadius: 8,
                                borderBottomRightRadius: 8,
                              }),
                              ...(yKeyIndex === yKeys.length - 1 && {
                                borderTopLeftRadius: 8,
                                borderTopRightRadius: 8,
                                borderTop: `1px solid ${colors.white20}`,
                              }),
                            }}
                          />
                        </div>
                      ))}
                      {showValues && (
                        <div
                          style={{
                            position: 'absolute',
                            top: -4,
                            left: '50%',
                            transform: 'translate(-50%, -100%)',
                            maxWidth: '100%',
                          }}
                        >
                          <Text
                            singleLine
                            variant="subtext-medium"
                            color="neutral80"
                          >
                            {yFormat(
                              yKeys.reduce(
                                (acc, curr) => (acc += item[curr]),
                                0,
                              ),
                            )}
                          </Text>
                        </div>
                      )}
                    </div>
                  ) : (
                    yKeys.map((yKey) => (
                      <div
                        key={yKey}
                        style={{
                          position: 'relative',
                          width: '100%',
                          height: `${yScale(item[yKey])}%`,
                          background: colors.neutralInverted100,
                          borderRadius: 8,
                        }}
                      >
                        <div
                          style={{
                            width: '100%',
                            height: '100%',
                            background:
                              colors[getChartColorForString(yKey, false)],
                            borderRadius: 8,
                            borderTop: `1px solid ${colors.white20}`,
                          }}
                        />
                        {showValues && (
                          <div
                            style={{
                              position: 'absolute',
                              top: -4,
                              left: '50%',
                              transform: 'translate(-50%, -100%)',
                              maxWidth: '100%',
                            }}
                          >
                            <Text
                              singleLine
                              variant="subtext-medium"
                              color="neutral80"
                            >
                              {yFormat(item[yKey])}
                            </Text>
                          </div>
                        )}
                      </div>
                    ))
                  )}
                </div>
              </div>
            </React.Fragment>
          ))}
          {data.map((item, index) => (
            <div
              key={index}
              style={{
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
              }}
            >
              <Text singleLine variant="subtext-regular" color="neutral60">
                {item[xKey]}
              </Text>
            </div>
          ))}
        </div>
      )}
      {hover !== null && (
        <FloatingPortal>
          <div
            ref={refs.setFloating}
            {...getFloatingProps()}
            style={{
              width: 160,
              pointerEvents: 'none',
              background: colors.neutralInverted100,
              border: `1px solid  ${colors.neutral20Adjusted}`,
              boxShadow: shadows.popoverSmall,
              display: 'flex',
              flexDirection: 'column',
              borderRadius: radius[8],
              padding: '6px 10px',
              gap: 4,
              ...floatingStyles,
            }}
          >
            <Text singleLine variant="subtext-medium" color="neutral60">
              {xFormat(data[hover][xKey])}
            </Text>
            {yKeys.map((yKey) => (
              <div
                key={yKey}
                style={{
                  display: 'flex',
                  justifyContent: 'space-between',
                  width: '100%',
                }}
              >
                <Legend
                  label={yKey}
                  color={getChartColorForString(yKey, false)}
                />
                <Text singleLine variant="subtext-mono" color="neutral100">
                  {yFormat(data[hover][yKey])}
                </Text>
              </div>
            ))}
          </div>
        </FloatingPortal>
      )}
      {showLegend && (
        <div
          style={{
            display: 'flex',
            gap: 16,
            justifyContent: 'center',
            alignItems: 'center',
          }}
        >
          {yKeys.map((yKey) => (
            <Legend
              key={yKey}
              label={yKey}
              color={getChartColorForString(yKey, false)}
            />
          ))}
        </div>
      )}
    </div>
  )
}

export { BarChart }
export type { BarChartProps }
