import React from 'react'
import match from 'match-sorter'
import { queryCache as cache, useQueryCache } from 'react-query'
import useLocalStorage from './useLocalStorage'
import { useSafeState, isStale } from './utils'

import {
  Panel,
  QueryKeys,
  QueryKey,
  Button,
  Code,
  Input,
  Select,
  QueryCountStyles,
  ActiveQueryPanel,
} from './styledComponents'
import { ThemeProvider } from './theme'
import {
  getQueryStatusLabel,
  getQueryStatusColor,
  getQueryOpacity,
} from './utils'
import Explorer from './Explorer'
import Logo from './Logo'

const isServer = typeof window === 'undefined'

const theme = {
  background: '#0b1521',
  backgroundAlt: '#132337',
  foreground: 'white',
  gray: '#3f4e60',
  grayAlt: '#222e3e',
  inputBackgroundColor: '#fff',
  inputTextColor: '#000',
  success: '#00ab52',
  danger: '#ff0085',
  active: '#006bff',
  warning: '#ffb200',
}

export function ReactQueryDevtools({
  initialIsOpen,
  panelProps = {},
  closeButtonProps = {},
  toggleButtonProps = {},
  position = 'bottom-left',
  containerElement: Container = 'footer',
}) {
  const rootRef = React.useRef()
  const panelRef = React.useRef()
  const [isOpen, setIsOpen] = useLocalStorage(
    'reactQueryDevtoolsOpen',
    initialIsOpen
  )
  const [isResolvedOpen, setIsResolvedOpen] = useSafeState(false)

  React.useEffect(() => {
    setIsResolvedOpen(isOpen)
  }, [isOpen, isResolvedOpen, setIsResolvedOpen])

  React[isServer ? 'useEffect' : 'useLayoutEffect'](() => {
    if (isResolvedOpen) {
      const previousValue = rootRef.current?.parentElement.style.paddingBottom

      const run = () => {
        const containerHeight = panelRef.current?.getBoundingClientRect().height
        rootRef.current.parentElement.style.paddingBottom = `${containerHeight}px`
      }

      run()

      window.addEventListener('resize', run)

      return () => {
        window.removeEventListener('resize', run)
        rootRef.current.parentElement.style.paddingBottom = previousValue
      }
    }
  }, [isResolvedOpen])

  const { style: panelStyle = {}, ...otherPanelProps } = panelProps

  const {
    style: closeButtonStyle = {},
    onClick: onCloseClick,
    ...otherCloseButtonProps
  } = closeButtonProps

  const {
    style: toggleButtonStyle = {},
    onClick: onToggleClick,
    ...otherToggleButtonProps
  } = toggleButtonProps

  return (
    <Container ref={rootRef} className="ReactQueryDevtools">
      {isResolvedOpen ? (
        <ThemeProvider theme={theme}>
          <ReactQueryDevtoolsPanel
            ref={panelRef}
            {...otherPanelProps}
            style={{
              position: 'fixed',
              bottom: '0',
              right: '0',
              zIndex: '99999',
              width: '100%',
              height: '500px',
              maxHeight: '90%',
              boxShadow: '0 0 20px rgba(0,0,0,.3)',
              borderTop: `1px solid ${theme.gray}`,
              ...panelStyle,
            }}
            setIsOpen={setIsOpen}
          />
          <Button
            {...otherCloseButtonProps}
            onClick={() => {
              setIsOpen(false)
              onCloseClick && onCloseClick()
            }}
            style={{
              position: 'fixed',
              zIndex: '99999',
              margin: '.5rem',
              bottom: 0,
              ...(position === 'top-right'
                ? {
                    right: '0',
                  }
                : position === 'top-left'
                ? {
                    left: '0',
                  }
                : position === 'bottom-right'
                ? {
                    right: '0',
                  }
                : {
                    left: '0',
                  }),
              ...closeButtonStyle,
            }}
          >
            Close
          </Button>
        </ThemeProvider>
      ) : (
        <button
          {...otherToggleButtonProps}
          aria-label="Open React Query Devtools"
          onClick={() => {
            setIsOpen(true)
            onToggleClick && onToggleClick()
          }}
          style={{
            background: 'none',
            border: 0,
            padding: 0,
            position: 'fixed',
            bottom: '0',
            right: '0',
            zIndex: '99999',
            display: 'inline-flex',
            fontSize: '1.5rem',
            margin: '.5rem',
            cursor: 'pointer',
            width: 'fit-content',
            ...(position === 'top-right'
              ? {
                  top: '0',
                  right: '0',
                }
              : position === 'top-left'
              ? {
                  top: '0',
                  left: '0',
                }
              : position === 'bottom-right'
              ? {
                  bottom: '0',
                  right: '0',
                }
              : {
                  bottom: '0',
                  left: '0',
                }),
            ...toggleButtonStyle,
          }}
        >
          <Logo aria-hidden />
        </button>
      )}
    </Container>
  )
}

const getStatusRank = q =>
  q.state.isFetching ? 0 : !q.observers.length ? 3 : isStale(q) ? 2 : 1

const sortFns = {
  'Status > Last Updated': (a, b) =>
    getStatusRank(a) === getStatusRank(b)
      ? sortFns['Last Updated'](a, b)
      : getStatusRank(a) > getStatusRank(b)
      ? 1
      : -1,
  'Query Hash': (a, b) => (a.queryHash > b.queryHash ? 1 : -1),
  'Last Updated': (a, b) => (a.state.updatedAt < b.state.updatedAt ? 1 : -1),
}

export const ReactQueryDevtoolsPanel = React.forwardRef(
  function ReactQueryDevtoolsPanel(props, ref) {
    const { setIsOpen, ...panelProps } = props

    const queryCache = useQueryCache ? useQueryCache() : cache

    const [sort, setSort] = useLocalStorage(
      'reactQueryDevtoolsSortFn',
      Object.keys(sortFns)[0]
    )

    const [filter, setFilter] = useLocalStorage('reactQueryDevtoolsFilter', '')

    const [sortDesc, setSortDesc] = useLocalStorage(
      'reactQueryDevtoolsSortDesc',
      false
    )

    const [isDragging, setIsDragging] = useSafeState(false)

    const sortFn = React.useMemo(() => sortFns[sort], [sort])

    React[isServer ? 'useEffect' : 'useLayoutEffect'](() => {
      if (!sortFn) {
        setSort(Object.keys(sortFns)[0])
      }
    }, [setSort, sortFn])

    React[isServer ? 'useEffect' : 'useLayoutEffect'](() => {
      if (isDragging) {
        const run = e => {
          const containerHeight = window.innerHeight - e.pageY

          if (containerHeight < 70) {
            setIsOpen(false)
          } else {
            ref.current.style.height = `${containerHeight}px`
          }
        }
        document.addEventListener('mousemove', run)
        document.addEventListener('mouseup', handleDragEnd)

        return () => {
          document.removeEventListener('mousemove', run)
          document.removeEventListener('mouseup', handleDragEnd)
        }
      }
    }, [isDragging])

    const handleDragStart = e => {
      if (e.button !== 0) return // Only allow left click for drag
      setIsDragging(true)
    }

    const handleDragEnd = e => {
      setIsDragging(false)
    }

    const [unsortedQueries, setUnsortedQueries] = useSafeState(
      Object.values(queryCache.queries)
    )

    const [activeQueryHash, setActiveQueryHash] = useLocalStorage(
      'reactQueryDevtoolsActiveQueryHash',
      ''
    )

    const queries = React.useMemo(() => {
      const sorted = [...unsortedQueries].sort(sortFn)

      if (sortDesc) {
        sorted.reverse()
      }

      return match(sorted, filter, { keys: ['queryHash'] }).filter(
        d => d.queryHash
      )
    }, [sortDesc, sortFn, unsortedQueries, filter])

    const activeQuery = React.useMemo(() => {
      return queries.find(query => query.queryHash === activeQueryHash)
    }, [activeQueryHash, queries])

    const hasFresh = queries.filter(q => getQueryStatusLabel(q) === 'fresh')
      .length
    const hasFetching = queries.filter(
      q => getQueryStatusLabel(q) === 'fetching'
    ).length
    const hasStale = queries.filter(q => getQueryStatusLabel(q) === 'stale')
      .length
    const hasInactive = queries.filter(
      q => getQueryStatusLabel(q) === 'inactive'
    ).length

    React.useEffect(() => {
      return queryCache.subscribe(queryCache => {
        setUnsortedQueries(Object.values(queryCache.queries))
      })
    }, [sort, sortFn, sortDesc, queryCache, setUnsortedQueries])

    return (
      <ThemeProvider theme={theme}>
        <Panel ref={ref} className="ReactQueryDevtoolsPanel" {...panelProps}>
          <div
            style={{
              position: 'absolute',
              left: 0,
              top: 0,
              width: '100%',
              height: '4px',
              marginBottom: '-4px',
              cursor: 'row-resize',
              zIndex: 100000,
            }}
            onMouseDown={handleDragStart}
            onMouseUp={handleDragEnd}
          ></div>
          <div
            style={{
              flex: '1 1 500px',
              minHeight: '40%',
              maxHeight: '100%',
              overflow: 'auto',
              borderRight: `1px solid ${theme.grayAlt}`,
              display: 'flex',
              flexDirection: 'column',
            }}
          >
            <div
              style={{
                padding: '.5rem',
                background: theme.backgroundAlt,
                display: 'flex',
                justifyContent: 'space-between',
                alignItems: 'center',
              }}
            >
              <QueryCountStyles>
                <div
                  style={{
                    fontWeight: 'bold',
                  }}
                >
                  Queries ({queries.length})
                </div>
              </QueryCountStyles>
              <div
                style={{
                  display: 'flex',
                  flexDirection: 'column',
                }}
              >
                <QueryKeys style={{ marginBottom: '.5rem' }}>
                  <QueryKey
                    style={{
                      background: theme.success,
                      opacity: hasFresh ? 1 : 0.3,
                    }}
                  >
                    fresh <Code>({hasFresh})</Code>
                  </QueryKey>{' '}
                  <QueryKey
                    style={{
                      background: theme.active,
                      opacity: hasFetching ? 1 : 0.3,
                    }}
                  >
                    fetching <Code>({hasFetching})</Code>
                  </QueryKey>{' '}
                  <QueryKey
                    style={{
                      background: theme.warning,
                      color: 'black',
                      textShadow: '0',
                      opacity: hasStale ? 1 : 0.3,
                    }}
                  >
                    stale <Code>({hasStale})</Code>
                  </QueryKey>{' '}
                  <QueryKey
                    style={{
                      background: theme.gray,
                      opacity: hasInactive ? 1 : 0.3,
                    }}
                  >
                    inactive <Code>({hasInactive})</Code>
                  </QueryKey>
                </QueryKeys>
                <div
                  style={{
                    display: 'flex',
                    alignItems: 'center',
                  }}
                >
                  <Input
                    placeholder="Filter"
                    value={filter}
                    onChange={e => setFilter(e.target.value)}
                    onKeyDown={e => {
                      if (e.key === 'Escape') setFilter('')
                    }}
                    style={{
                      flex: '1',
                      marginRight: '.5rem',
                    }}
                  />
                  <Select
                    value={sort}
                    onChange={e => setSort(e.target.value)}
                    style={{
                      flex: '1',
                      minWidth: 75,
                      marginRight: '.5rem',
                    }}
                  >
                    {Object.keys(sortFns).map(key => (
                      <option key={key} value={key}>
                        Sort by {key}
                      </option>
                    ))}
                  </Select>
                  <Button
                    onClick={() => setSortDesc(old => !old)}
                    style={{
                      padding: '.2rem .4rem',
                    }}
                  >
                    {sortDesc ? '⬇ Desc' : '⬆ Asc'}
                  </Button>
                </div>
              </div>
            </div>
            <div
              style={{
                overflow: 'auto scroll',
              }}
            >
              {queries.map((query, i) => (
                <div
                  key={query.queryHash || i}
                  onClick={() =>
                    setActiveQueryHash(
                      activeQueryHash === query.queryHash ? '' : query.queryHash
                    )
                  }
                  style={{
                    display: 'flex',
                    borderBottom: `solid 1px ${theme.grayAlt}`,
                    cursor: 'pointer',
                    background:
                      query === activeQuery
                        ? 'rgba(255,255,255,.1)'
                        : undefined,
                  }}
                >
                  <div
                    style={{
                      flex: '0 0 auto',
                      width: '2rem',
                      height: '2rem',
                      background: getQueryStatusColor(query, theme),
                      opacity: getQueryOpacity(query),
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center',
                      fontWeight: 'bold',
                      textShadow:
                        getQueryStatusLabel(query) === 'stale'
                          ? '0'
                          : '0 0 10px black',
                      color:
                        getQueryStatusLabel(query) === 'stale'
                          ? 'black'
                          : 'white',
                    }}
                  >
                    {query.observers.length}
                  </div>
                  <Code
                    style={{
                      padding: '.5rem',
                    }}
                  >
                    {`${query.queryHash}`}
                  </Code>
                </div>
              ))}
            </div>
          </div>
          {activeQuery ? (
            <ActiveQueryPanel>
              <div
                style={{
                  padding: '.5rem',
                  background: theme.backgroundAlt,
                  position: 'sticky',
                  top: 0,
                  zIndex: 1,
                }}
              >
                Query Details
              </div>
              <div
                style={{
                  padding: '.5rem 0',
                }}
              >
                <div
                  style={{
                    padding: '.5rem 1rem',
                    display: 'flex',
                    alignItems: 'stretch',
                    justifyContent: 'space-between',
                  }}
                >
                  <Code
                    style={{
                      lineHeight: '1.8rem',
                    }}
                  >
                    <pre
                      style={{
                        margin: 0,
                        padding: 0,
                      }}
                    >
                      {JSON.stringify(activeQuery.queryKey, null, 2)}
                    </pre>
                  </Code>
                  <span
                    style={{
                      padding: '0.3rem .6rem',
                      borderRadius: '0.4rem',
                      fontWeight: 'bold',
                      textShadow: '0 2px 10px black',
                      background: getQueryStatusColor(activeQuery, theme),
                      flexShrink: 0,
                      opacity: getQueryOpacity(activeQuery),
                    }}
                  >
                    {getQueryStatusLabel(activeQuery)}
                  </span>
                </div>
                <div
                  style={{
                    padding: '.5rem 1rem',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'space-between',
                  }}
                >
                  Last Updated:{' '}
                  <Code>
                    {new Date(activeQuery.state.updatedAt).toLocaleTimeString()}
                  </Code>
                </div>
              </div>
              <div
                style={{
                  background: theme.backgroundAlt,
                  padding: '.5rem',
                  position: 'sticky',
                  top: 0,
                  zIndex: 1,
                }}
              >
                Actions
              </div>
              <div
                style={{
                  padding: '1rem',
                }}
              >
                <Button
                  onClick={() => activeQuery.fetch()}
                  disabled={activeQuery.state.isFetching}
                  style={{
                    background: theme.active,
                  }}
                >
                  Refetch
                </Button>{' '}
                <Button
                  onClick={() =>
                    queryCache.removeQueries(q => q === activeQuery)
                  }
                  style={{
                    background: theme.danger,
                  }}
                >
                  Remove
                </Button>{' '}
              </div>
              <div
                style={{
                  background: theme.backgroundAlt,
                  padding: '.5rem',
                  position: 'sticky',
                  top: 0,
                  zIndex: 1,
                }}
              >
                Data Explorer
              </div>
              <div
                style={{
                  padding: '.5rem',
                }}
              >
                <Explorer
                  label="Data"
                  value={activeQuery?.state?.data}
                  defaultExpanded={{}}
                />
              </div>
              <div
                style={{
                  background: theme.backgroundAlt,
                  padding: '.5rem',
                  position: 'sticky',
                  top: 0,
                  zIndex: 1,
                }}
              >
                Query Explorer
              </div>
              <div
                style={{
                  padding: '.5rem',
                }}
              >
                <Explorer
                  label="Query"
                  value={activeQuery}
                  defaultExpanded={{
                    queryKey: true,
                  }}
                />
              </div>
            </ActiveQueryPanel>
          ) : null}
        </Panel>
      </ThemeProvider>
    )
  }
)