import {
  EditorFileTab,
  TAB_TRANSFER_TYPE,
} from '@/features/ide-react/context/tabs-context'
import FileTreeIcon from '@/features/file-tree/components/file-tree-icon'
import MaterialIcon from '@/shared/components/material-icon'
import { debugConsole } from '@/utils/debugging'
import classNames from 'classnames'
import { throttle } from 'lodash'
import {
  memo,
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
  TAB_USER_EDIT_EVENT,
  tabsEvents,
} from '@/features/source-editor/extensions/tabs-listener'

type TabProps = {
  tab: EditorFileTab
  openTab: (id: string) => void
  closeTab: (id: string) => void
  canCloseTab: boolean
  makeTabPermanent: (id: string) => void
  openContextMenu: (
    coords: { top: number; left: number },
    tabId: string
  ) => void
  closeContextMenu: () => void
  isSelected: boolean
  onTabDrop: (
    sourceTabId: string,
    targetTabId: string,
    position: 'left' | 'right'
  ) => void
}

function getSideOfTargetFromEvent(
  element: Element,
  clientX: number
): 'left' | 'right' {
  const rect = element.getBoundingClientRect()
  if (rect.left + rect.width / 2 < clientX) {
    return 'right'
  } else {
    return 'left'
  }
}

export const Tab = memo(function Tab({
  tab,
  openTab,
  closeTab,
  canCloseTab,
  makeTabPermanent,
  openContextMenu,
  closeContextMenu,
  isSelected,
  onTabDrop,
}: TabProps) {
  const { t } = useTranslation()
  const tabRef = useRef<HTMLDivElement>(null)

  const [dropTargetPosition, setDropTargetPosition] = useState<
    'left' | 'right' | null
  >(null)
  const onDragStart = useCallback(
    (e: React.DragEvent) => {
      e.stopPropagation()
      e.dataTransfer.setData(TAB_TRANSFER_TYPE, tab.id)
      e.dataTransfer.effectAllowed = 'move'
    },
    [tab]
  )

  const throttledOnDragOver = useMemo(
    () =>
      throttle((element: Element, clientX: number) => {
        setDropTargetPosition(getSideOfTargetFromEvent(element, clientX))
      }, 50),
    []
  )

  const onDragOver = useCallback(
    (e: React.DragEvent) => {
      e.preventDefault()
      e.stopPropagation()
      e.dataTransfer.dropEffect = 'move'
      throttledOnDragOver(e.currentTarget, e.clientX)
    },
    [throttledOnDragOver]
  )

  const onDragLeave = useCallback(
    (e: React.DragEvent) => {
      e.stopPropagation()
      if (e.currentTarget.contains(e.relatedTarget as Node | null)) {
        return
      }
      throttledOnDragOver.cancel()
      setDropTargetPosition(null)
    },
    [throttledOnDragOver]
  )

  const onDrop = useCallback(
    (e: React.DragEvent) => {
      throttledOnDragOver.cancel()
      setDropTargetPosition(null)
      e.preventDefault()
      e.stopPropagation()
      const draggedTabId = e.dataTransfer.getData(TAB_TRANSFER_TYPE)
      if (!draggedTabId) {
        debugConsole.warn('No dragged tab id found in dataTransfer')
        return
      }
      onTabDrop(
        draggedTabId,
        tab.id,
        getSideOfTargetFromEvent(e.currentTarget, e.clientX)
      )
    },
    [onTabDrop, tab, throttledOnDragOver]
  )

  const onDoubleClick = useCallback(() => {
    makeTabPermanent(tab.id)
  }, [makeTabPermanent, tab])

  const onClick = useCallback(
    (e: React.MouseEvent) => {
      if (e.button === 0) {
        openTab(tab.id)
      }
    },
    [openTab, tab]
  )

  const onKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      if (e.code === 'Enter') {
        openTab(tab.id)
      }
    },
    [openTab, tab]
  )

  const onCloseClick = useCallback(
    (e: React.MouseEvent) => {
      e.stopPropagation()
      closeTab(tab.id)
    },
    [closeTab, tab]
  )

  const onMouseUp = useCallback(
    (e: React.MouseEvent) => {
      if (e.button === 1) {
        e.stopPropagation()
        closeTab(tab.id)
      }
    },
    [closeTab, tab]
  )

  const onContextMenu = useCallback(
    (e: React.MouseEvent) => {
      if (e.shiftKey) {
        closeContextMenu()
        return
      }
      e.preventDefault()
      openContextMenu({ top: e.pageY, left: e.pageX }, tab.id)
    },
    [openContextMenu, closeContextMenu, tab]
  )

  useLayoutEffect(() => {
    if (isSelected && tabRef.current) {
      tabRef.current.scrollIntoView({
        block: 'nearest',
        inline: 'nearest',
      })
    }
  }, [isSelected])

  useEffect(() => {
    if (isSelected && tab.lifetime === 'temporary') {
      const handler = () => {
        makeTabPermanent(tab.id)
      }
      tabsEvents.addEventListener(TAB_USER_EDIT_EVENT, handler)
      return () => {
        tabsEvents.removeEventListener(TAB_USER_EDIT_EVENT, handler)
      }
    }
  }, [isSelected, makeTabPermanent, tab])

  return (
    <div
      ref={tabRef}
      onDragStart={onDragStart}
      onDragOver={onDragOver}
      onDragLeave={onDragLeave}
      draggable
      onDrop={onDrop}
      onDoubleClick={onDoubleClick}
      role="tab"
      aria-selected={isSelected}
      key={tab.id}
      onClick={onClick}
      onMouseUp={onMouseUp}
      onKeyDown={onKeyDown}
      onContextMenu={onContextMenu}
      data-tab-id={tab.id}
      tabIndex={0}
      className={classNames('editor-file-tab', {
        'tab-selected': isSelected,
        'tab-drop-left': dropTargetPosition === 'left',
        'tab-drop-right': dropTargetPosition === 'right',
        'tab-temporary': tab.lifetime === 'temporary',
      })}
    >
      <div className="editor-file-tab-content">
        <span className="editor-file-tab-icon">
          <FileTreeIcon isLinkedFile={tab.isLinkedFile} name={tab.name} />
        </span>
        <div className="editor-file-tab-path">&lrm;{tab.displayPath}</div>
        <div className="editor-file-tab-action">
          <button
            onClick={onCloseClick}
            disabled={!canCloseTab}
            className="editor-file-tab-close-action"
            aria-label={t('close')}
          >
            <MaterialIcon type="close" />
          </button>
        </div>
      </div>
    </div>
  )
})
