import React, { useRef, useEffect, useState } from 'react'
import styled from '@emotion/styled'
import { css, keyframes } from '@emotion/react'
import useInterval from '../hooks/useInterval'
import usePrevious from '../hooks/usePrevious'
import { isEqual, getDecimalCount } from '../utils/'
import {
  ArrowUpIcon,
  ArrowDownIcon,
  SwitchHorizontalIcon,
} from '@heroicons/react/solid'
import useMarkPrice from '../hooks/useMarkPrice'
import useOrderbook from '../hooks/useOrderbook'
import useMarket from '../hooks/useMarket'
import { ElementTitle } from './styles'
import useMangoStore from '../stores/useMangoStore'
import Tooltip from './Tooltip'
import FloatingElement from './FloatingElement'

const Line = styled.div<any>`
  text-align: ${(props) => (props.invert ? 'left' : 'right')};
  height: 100%;
  filter: opacity(40%);
  ${(props) => props['data-width'] && `width: ${props['data-width']};`}
`
const fadeIn = keyframes`
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
`

const FlipCard = styled.div`
  background-color: transparent;
  height: 100%;
  perspective: 1000px;
`

const FlipCardInner = styled.div<any>`
  position: relative;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: transform 0.8s ease-out;
  transform-style: preserve-3d;
  transform: ${({ flip }) => (flip ? 'rotateY(0deg)' : 'rotateY(180deg)')};
`

const FlipCardFront = styled.div`
  position: absolute;
  width: 100%;
  height: 100%;
`

const FlipCardBack = styled.div`
  position: absolute;
  width: 100%;
  height: 100%;
  transform: rotateY(180deg);
`

const StyledFloatingElement = styled(FloatingElement)`
  animation: ${css`
    ${fadeIn} 1s linear
  `};
`

const getCumulativeOrderbookSide = (
  orders,
  totalSize,
  depth,
  backwards = false
) => {
  let cumulative = orders
    .slice(0, depth)
    .reduce((cumulative, [price, size], i) => {
      const cumulativeSize = (cumulative[i - 1]?.cumulativeSize || 0) + size
      cumulative.push({
        price,
        size,
        cumulativeSize,
        sizePercent: Math.round((cumulativeSize / (totalSize || 1)) * 100),
      })
      return cumulative
    }, [])
  if (backwards) {
    cumulative = cumulative.reverse()
  }
  return cumulative
}

export default function Orderbook({ depth = 8 }) {
  const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
  const marketConfig = useMangoStore((s) => s.selectedMarket.config)
  const markPrice = useMarkPrice()
  const orderbook = useOrderbook()

  const currentOrderbookData = useRef(null)
  const lastOrderbookData = useRef(null)

  const [orderbookData, setOrderbookData] = useState(null)
  const [defaultLayout, setDefaultLayout] = useState(true)

  useInterval(() => {
    if (
      !currentOrderbookData.current ||
      JSON.stringify(currentOrderbookData.current) !==
        JSON.stringify(lastOrderbookData.current)
    ) {
      const bids = orderbook?.bids || []
      const asks = orderbook?.asks || []

      const sum = (total, [, size], index) =>
        index < depth ? total + size : total
      const totalSize = bids.reduce(sum, 0) + asks.reduce(sum, 0)

      const bidsToDisplay = getCumulativeOrderbookSide(
        bids,
        totalSize,
        depth,
        false
      )
      const asksToDisplay = defaultLayout
        ? getCumulativeOrderbookSide(asks, totalSize, depth, false)
        : getCumulativeOrderbookSide(asks, totalSize, depth, true)

      currentOrderbookData.current = {
        bids: orderbook?.bids,
        asks: orderbook?.asks,
      }
      if (bidsToDisplay[0] && asksToDisplay[0]) {
        const bid = bidsToDisplay[0].price
        const ask = defaultLayout
          ? asksToDisplay[0].price
          : asksToDisplay[asksToDisplay.length - 1].price
        const spread = ask - bid
        const spreadPercentage = (spread / ask) * 100

        setOrderbookData({
          bids: bidsToDisplay,
          asks: asksToDisplay,
          spread: spread,
          spreadPercentage: spreadPercentage,
        })
      } else {
        setOrderbookData(null)
      }
    }
  }, 250)

  useEffect(() => {
    lastOrderbookData.current = {
      bids: orderbook?.bids,
      asks: orderbook?.asks,
    }
  }, [orderbook])

  const handleLayoutChange = () => {
    setDefaultLayout(!defaultLayout)
    setOrderbookData((prevState) => ({
      ...orderbookData,
      asks: prevState.asks.reverse(),
    }))
  }

  return (
    <>
      <FlipCard>
        <FlipCardInner flip={defaultLayout}>
          {defaultLayout ? (
            <FlipCardFront>
              <StyledFloatingElement>
                <div className="flex items-center justify-between pb-2.5">
                  <div className="w-8 h-8" />
                  <ElementTitle noMarignBottom>Orderbook</ElementTitle>
                  <div className="flex relative">
                    <Tooltip content={'Switch Layout'} className="text-xs py-1">
                      <button
                        onClick={handleLayoutChange}
                        className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
                      >
                        <SwitchHorizontalIcon className="w-5 h-5" />
                      </button>
                    </Tooltip>
                  </div>
                </div>
                <MarkPriceComponent markPrice={markPrice} />

                <div
                  className={`text-th-fgd-4 flex justify-between mb-2 text-xs`}
                >
                  <div className={`text-left`}>
                    Size ({marketConfig.baseSymbol})
                  </div>
                  <div className={`text-center`}>
                    Price ({groupConfig.quoteSymbol})
                  </div>
                  <div className={`text-right`}>
                    Size ({marketConfig.baseSymbol})
                  </div>
                </div>
                <div className="flex">
                  <div className="w-1/2">
                    {orderbookData?.bids.map(({ price, size, sizePercent }) => (
                      <OrderbookRow
                        key={price + ''}
                        price={price}
                        size={size}
                        side="buy"
                        sizePercent={sizePercent}
                      />
                    ))}
                  </div>
                  <div className="w-1/2">
                    {orderbookData?.asks.map(({ price, size, sizePercent }) => (
                      <OrderbookRow
                        invert
                        key={price + ''}
                        price={price}
                        size={size}
                        side="sell"
                        sizePercent={sizePercent}
                      />
                    ))}
                  </div>
                </div>
                <div className="flex justify-between bg-th-bkg-1 p-2 mt-4 rounded-md text-xs">
                  <div className="text-th-fgd-3">Spread</div>
                  <div className="text-th-fgd-1">
                    {orderbookData?.spread.toFixed(2)}
                  </div>
                  <div className="text-th-fgd-1">
                    {orderbookData?.spreadPercentage.toFixed(2)}%
                  </div>
                </div>
              </StyledFloatingElement>
            </FlipCardFront>
          ) : (
            <FlipCardBack>
              <StyledFloatingElement>
                <div className="flex items-center justify-between pb-2.5">
                  <div className="w-8 h-8" />
                  <ElementTitle noMarignBottom>Orderbook</ElementTitle>
                  <div className="flex relative">
                    <Tooltip content={'Switch Layout'} className="text-xs py-1">
                      <button
                        onClick={handleLayoutChange}
                        className="flex items-center justify-center rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
                      >
                        <SwitchHorizontalIcon className="w-5 h-5" />
                      </button>
                    </Tooltip>
                  </div>
                </div>
                <MarkPriceComponent markPrice={markPrice} />
                <div className={`text-th-fgd-4 flex justify-between mb-2`}>
                  <div className={`text-left text-xs`}>
                    Size ({baseCurrency})
                  </div>
                  <div className={`text-right text-xs`}>
                    Price ({quoteCurrency})
                  </div>
                </div>
                {orderbookData?.asks.map(({ price, size, sizePercent }) => (
                  <OrderbookRow
                    key={price + ''}
                    price={price}
                    size={size}
                    side="sell"
                    sizePercent={sizePercent}
                  />
                ))}
                <div className="flex justify-between bg-th-bkg-1 p-2 my-2 rounded-md text-xs">
                  <div className="text-th-fgd-3">Spread</div>
                  <div className="text-th-fgd-1">
                    {orderbookData?.spread.toFixed(2)}
                  </div>
                  <div className="text-th-fgd-1">
                    {orderbookData?.spreadPercentage.toFixed(2)}%
                  </div>
                </div>
                {orderbookData?.bids.map(({ price, size, sizePercent }) => (
                  <OrderbookRow
                    key={price + ''}
                    price={price}
                    size={size}
                    side="buy"
                    sizePercent={sizePercent}
                  />
                ))}
              </StyledFloatingElement>
            </FlipCardBack>
          )}
        </FlipCardInner>
      </FlipCard>
    </>
  )
}

const OrderbookRow = React.memo<any>(
  ({ side, price, size, sizePercent, invert }) => {
    const element = useRef(null)
    const { market } = useMarket()
    const setMangoStore = useMangoStore((s) => s.set)

    useEffect(() => {
      !element.current?.classList.contains('flash') &&
        element.current?.classList.add('flash')
      const id = setTimeout(
        () =>
          element.current?.classList.contains('flash') &&
          element.current?.classList.remove('flash'),
        250
      )
      return () => clearTimeout(id)
    }, [price, size])

    const formattedSize =
      market?.minOrderSize && !isNaN(size)
        ? Number(size).toFixed(getDecimalCount(market.minOrderSize) + 1)
        : size

    const formattedPrice =
      market?.tickSize && !isNaN(price)
        ? Number(price).toFixed(getDecimalCount(market.tickSize) + 1)
        : price

    const handlePriceClick = () => {
      setMangoStore((state) => {
        state.tradeForm.price = price
      })
    }

    const handleSizeClick = () => {
      setMangoStore((state) => {
        state.tradeForm.baseSize = size
      })
    }

    return (
      <div className={`flex text-sm leading-7 justify-between`} ref={element}>
        {invert ? (
          <>
            <div className={`text-left relative flex-1`}>
              <Line
                invert
                data-width={sizePercent + '%'}
                side={side}
                className={`absolute inset-y-0 left-0 ${
                  side === 'buy' ? `bg-th-green` : `bg-th-red`
                }`}
              />
              <div
                onClick={handlePriceClick}
                className="z-30 relative text-th-fgd-1 px-1"
              >
                {formattedPrice}
              </div>
            </div>
            <div className={`text-right`} onClick={handleSizeClick}>
              {formattedSize}
            </div>
          </>
        ) : (
          <>
            <div
              className={`text-left flex-1 text-th-fgd-1`}
              onClick={handleSizeClick}
            >
              {formattedSize}
            </div>
            <div className={`text-right relative flex-1`}>
              <Line
                className={`absolute inset-y-0 right-0 ${
                  side === 'buy' ? `bg-th-green` : `bg-th-red`
                }`}
                data-width={sizePercent + '%'}
                side={side}
              />
              <div
                className={`z-30 relative text-th-fgd-1 px-1`}
                onClick={handlePriceClick}
              >
                {formattedPrice}
              </div>
            </div>
          </>
        )}
      </div>
    )
  },
  (prevProps, nextProps) =>
    isEqual(prevProps, nextProps, ['price', 'size', 'sizePercent'])
)

const MarkPriceComponent = React.memo<{ markPrice: number }>(
  ({ markPrice }) => {
    const { market } = useMarket()
    const previousMarkPrice: number = usePrevious(markPrice)

    const formattedMarkPrice =
      markPrice &&
      market?.tickSize &&
      markPrice.toFixed(getDecimalCount(market.tickSize))

    return (
      <div
        className={`flex justify-center items-center font-bold text-lg pb-4 ${
          markPrice > previousMarkPrice
            ? `text-th-green`
            : markPrice < previousMarkPrice
            ? `text-th-red`
            : `text-th-fgd-1`
        }`}
      >
        {markPrice > previousMarkPrice && (
          <ArrowUpIcon className={`h-5 w-5 mr-1 text-th-green`} />
        )}
        {markPrice < previousMarkPrice && (
          <ArrowDownIcon className={`h-5 w-5 mr-1 text-th-red`} />
        )}
        {formattedMarkPrice || '----'}
      </div>
    )
  },
  (prevProps, nextProps) => isEqual(prevProps, nextProps, ['markPrice'])
)
