import styled from "@emotion/styled"
import { css, cx } from "@emotion/css"
import { useTranslation } from "react-i18next"
import Icon from "@aet/icons/macro"
import React, { memo, useEffect, useRef, useState } from "react"
import { useClickOutside, useEvent } from "@aet/hooks"
import { gql, useMutation } from "~/generated/graphql"
import { color, token, vars } from "~/options/theme"
import { type FileItemHook, useFileItemMenu } from "./FileItemContextMenu"
import { FileState } from "~/shared/models/enums.generated"
import { type TreeItem, useTreeContext } from "./index"
import { useDispatch } from "~/options/store"
import { renameFile } from "~/options/store/actions"
import { Favicon } from "./Icon"
import { isGlobalStyle } from "~/shared"
import { useInputOnChange } from "~/shared/hooks/useInput"

interface BaseFileItemProps {
  active: boolean
  children?: React.ReactNode
  className?: string
  item: TreeItem
  menu: FileItemHook["menu"]
  onClick: (e?: React.MouseEvent) => void
  onContextMenu: FileItemHook["onMenu"]
  readOnly?: boolean
  style?: React.CSSProperties
}

const RENAME_FILE = gql<"RenameFile">`
  mutation RenameFile($id: ID!, $name: String!) {
    setFile(input: { name: $name }, id: $id) {
      name
      id
    }
  }
`

export const AbstractBaseFileItem = memo<
  Pick<
    BaseFileItemProps,
    | "active"
    | "children"
    | "className"
    | "onClick"
    | "onContextMenu"
    | "readOnly"
    | "style"
  > & {
    icon?: React.ReactNode
    item: { id: string; name: string; subLabel?: string | null }
    lang?: string
    onRename: (newName: string) => Promise<void>
    title?: string
  }
>(props => {
  const {
    active,
    children,
    className,
    icon,
    item,
    lang,
    onContextMenu,
    onClick,
    onRename,
    readOnly,
    ...rest
  } = props
  const { t } = useTranslation()
  const containerRef = useRef<HTMLDivElement>(null)
  const inputRef = useRef<HTMLInputElement>(null)
  const { renamingFileID, setRenamingFileID } = useTreeContext()
  const renaming = !readOnly && renamingFileID === item.id

  useClickOutside(containerRef, () => setRenamingFileID(null), { skip: !renaming })

  const isGlobal = isGlobalStyle(item)
  const itemName = isGlobal ? t("file.nameGlobalStyle") : item.name
  const [input, setInput] = useState(itemName)
  const onInput = useInputOnChange(setInput)

  const onKeyDown = useEvent(async (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (!renaming) return

    if (e.key === "Enter") {
      const newName = input
      if (newName !== itemName) {
        await onRename(newName)
      }

      setRenamingFileID(null)
    } else if (e.key === "Escape") {
      setRenamingFileID(null)
    }
  })

  useEffect(() => {
    if (!renaming) {
      setInput(itemName)
    }
  }, [itemName, renaming])

  useEffect(() => {
    if (renaming) {
      inputRef.current?.focus()
    }
  }, [renaming, renamingFileID])

  const onEnter = useEvent((e: React.KeyboardEvent<HTMLDivElement>) => {
    if (e.key === "Enter") {
      onClick()
    }
  })

  return (
    <ListItem
      {...rest}
      ref={containerRef}
      className={cx(
        { active, renaming },
        renamingFileID != null && renamingFileID !== item.id && RENAMING_OTHER_FILE,
        className,
      )}
      tabIndex={0}
      onKeyDown={onEnter}
      onClick={onClick}
      onContextMenu={onContextMenu}
    >
      {icon}
      {renaming ? (
        <Input
          ref={inputRef}
          onKeyDown={onKeyDown}
          onChange={onInput}
          lang={lang}
          value={input}
          tabIndex={-1}
        />
      ) : (
        <ItemLabel
          className={cx({ renaming })}
          onKeyDown={onKeyDown}
          onChange={onInput}
          lang={lang}
          tabIndex={-1}
        >
          {itemName}
          {item.subLabel != null && <SubLabel>({item.subLabel})</SubLabel>}
        </ItemLabel>
      )}
      {children}
    </ListItem>
  )
})

const BaseFileItem = memo<BaseFileItemProps>(props => {
  const { children, className, item, menu, ...rest } = props
  const { t } = useTranslation()
  const dispatch = useDispatch()

  const [rename] = useMutation(RENAME_FILE)

  const itemName = isGlobalStyle(item) ? t("file.nameGlobalStyle") : item.name

  const onRename = useEvent(async (newName: string) => {
    if (newName !== itemName) {
      await rename({
        variables: {
          id: item.id,
          name: newName,
        },
      })
      dispatch(renameFile(item.id, newName))
    }
  })

  return (
    <AbstractBaseFileItem
      {...rest}
      item={item}
      className={cx(
        item.enabled === false && DISABLED_FILE,
        item.state === FileState.Deleted && DELETED_FILE,
        item.state === FileState.Archived && ARCHIVED_FILE,
        className,
      )}
      onRename={onRename}
      title={
        `${itemName}` +
        (item.enabled === false
          ? ` (${t("editor.disabled")})`
          : item.state === FileState.Deleted
          ? ` (${t("editor.deleted")})`
          : item.state === FileState.Archived
          ? ` (${t("editor.archived")})`
          : "")
      }
      lang={item.nameLanguage || undefined}
      icon={isGlobalStyle(item) ? <GlobeIcon /> : <Favicon item={item} />}
    >
      {children}
      {menu}
    </AbstractBaseFileItem>
  )
})

interface FileItemProps {
  active: boolean
  children?: React.ReactNode
  className?: string
  item: TreeItem
  multi: boolean
  onOpen(id: string): void
  onToggle(checked: boolean): void
  readOnly?: boolean
  selectedFileIds: string[]
  style?: React.CSSProperties
}

export const FileItem = memo<FileItemProps>(props => {
  const { active, item, onOpen, onToggle, multi, selectedFileIds: _, ...rest } = props
  const { toggleMenu, menu, onMenu } = useFileItemMenu([item.id])
  const { toggleMultiSelect } = useTreeContext()

  const onClick = useEvent((e?: React.MouseEvent) => {
    if (multi) {
      onToggle(!active)
    } else if (e?.metaKey) {
      toggleMultiSelect()
      setTimeout(() => onToggle(true))
    } else {
      onOpen(item.id)
    }

    toggleMenu(false)
  })

  if (multi) {
    return (
      <BaseFileItem
        {...rest}
        active={active}
        item={item}
        menu={menu}
        onContextMenu={onMenu}
        onClick={onClick}
      />
    )
  }

  return (
    <BaseFileItem
      {...rest}
      item={item}
      active={active}
      menu={menu}
      onContextMenu={onMenu}
      onClick={onClick}
    />
  )
})

const RENAMING_OTHER_FILE = css`
  opacity: 0.3;
`
const DISABLED_FILE = css`
  font-style: italic;
`
const DELETED_FILE = css`
  opacity: 0.4;
`
const ARCHIVED_FILE = css`
  opacity: 0.6;
`

export const ListItem = styled.div`
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  padding: 7px 11px;
  margin-bottom: 1px;

  display: flex;
  align-items: center;
  &:hover,
  &.active:hover {
    background-color: ${vars.backgroundHover};
  }
  &.active {
    // #282e34, #6e768166
    background-color: ${color("#afb8c133", "#282e34")};
  }
  &.renaming {
    background-color: transparent;
    border: 1px solid ${token.accent.emphasis};
  }
`

const Input = styled.input`
  background: transparent;
  border: none;
  width: 100%;
  flex: 1;
`
const ItemLabel = styled.div`
  background: transparent;
  border: none;
  width: 100%;
  flex: 1;
  margin-left: 2.5px;
`

const GlobeIcon = styled(Icon.of("BsGlobe"))`
  margin-right: 4px;
  font-size: 1.2em;
`

const SubLabel = styled.span`
  opacity: 0.5;
  margin-left: 4px;
`
