import type * as monaco from "monaco-editor"
import type { Range } from "monaco-editor"
import styled from "@emotion/styled"
import { css } from "@emotion/css"
import { Popover } from "@headlessui/react"
import { memo, useMemo } from "react"
import { useReactiveVar } from "@apollo/client"
import { uniqBy } from "lodash"
import { token, vars } from "../../theme"
import { getActiveFile, useSelector } from "../../store"
import { cursorPosition, topLevelSymbols } from "../Monaco"
import { editorNavigation } from "../../utils/emitter"
import { CHEVRON, ItemKind } from "./ItemIcon"

const NavContainer = styled.div`
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 1px 12px;
  font-size: 0.95em;
  border-bottom: 1px solid var(--separator-border);
  min-height: 30px;
`
const Segment = styled(Popover.Button)`
  align-items: center;
  border: none;
  color: ${token.fg.muted};
  display: flex;
  flex-shrink: 0;
  padding: 3px 1px;
  gap: 3px;
  &:hover {
    color: ${token.fg.default};
  }
  &:focus-visible {
    border-radius: 2px;
    outline: 1px solid ${token.accent.fg};
  }
`

const PopoverContainer = styled.form`
  background-color: ${token.canvas.overlay};
  border: 1px solid ${token.border.default};
  border-radius: 5px;
  box-shadow: ${token.shadow.large};
  margin-top: 1px;
  max-height: 510px;
  overflow: auto;
  position: absolute;
  padding: 5px;
  z-index: 1;
`

const NavItem = styled.button`
  align-items: center;
  border-radius: 4px;
  border: none;
  cursor: pointer;
  display: flex;
  flex-shrink: 0;
  font-size: 1.05em;
  gap: 5px;
  padding: 5px;
  white-space: nowrap;
  width: 100%;
  min-width: 80px;

  &:hover {
    background-color: ${vars.backgroundActive};
  }
`

const NavItemText = styled.span`
  max-width: 185px;
  overflow: hidden;
  text-overflow: ellipsis;
`

export const NavView = memo(() => {
  const symbols = useReactiveVar(topLevelSymbols)
  const cursor = useReactiveVar(cursorPosition)
  const { childrenMap, parentMap } = useMemo(() => group(symbols), [symbols])

  const nav = useMemo(
    () =>
      uniqBy(
        symbols.filter(item => (item.range as Range).containsPosition(cursor!)),
        item => JSON.stringify(item.range),
      ),
    [symbols, cursor],
  )

  const hasStyle = useSelector(state => getActiveFile(state) != null)

  if (!hasStyle) {
    return null
  }

  return (
    <NavContainer>
      {CHEVRON}
      {nav.map((item, i, { length }) => {
        const siblings = childrenMap.get(parentMap.get(item)!)

        return (
          <Popover
            className={css`
              position: relative;
            `}
            key={i}
          >
            <Segment disabled={!siblings?.length}>
              <ItemKind kind={item.kind} />
              {item.name}
              {i < length - 1 && CHEVRON}
            </Segment>
            <PopoverContainer as={Popover.Panel}>
              {siblings?.map((child, i) => (
                <NavItem
                  key={i}
                  onClick={() => {
                    editorNavigation.emit({
                      position: (child.range as Range).getStartPosition(),
                    })
                  }}
                >
                  <ItemKind
                    kind={child.kind}
                    className={css`
                      font-size: 1.1em !important;
                    `}
                  />
                  <NavItemText title={child.name}>{child.name}</NavItemText>
                </NavItem>
              ))}
            </PopoverContainer>
          </Popover>
        )
      })}
      {!nav.length && <div>...</div>}
    </NavContainer>
  )
})

type DocumentSymbol = monaco.languages.DocumentSymbol

function group(symbols: readonly DocumentSymbol[]) {
  const parentMap = new WeakMap<DocumentSymbol, DocumentSymbol>()
  const childrenMap = new WeakMap<DocumentSymbol, DocumentSymbol[]>()

  const dummyNode = {} as DocumentSymbol

  for (let i = 0; i < symbols.length; i++) {
    const symbol = symbols[i]
    for (let j = i + 1; j < symbols.length; j++) {
      const other = symbols[j]
      if ((symbol.range as monaco.Range).containsRange(other.range)) {
        parentMap.set(other, symbol)
        if (!childrenMap.has(symbol)) {
          childrenMap.set(symbol, [other])
        } else {
          childrenMap.get(symbol)!.push(other)
        }
      }
    }
  }

  const roots = symbols.filter(item => !parentMap.has(item))
  roots.forEach(item => {
    parentMap.set(item, dummyNode)
  })
  childrenMap.set(dummyNode, roots)

  return { parentMap, childrenMap }
}
