import { memo, useCallback, useEffect, useMemo, useState } from "react"
import Icon from "@aet/icons/macro"
import {
  ControlledMenu,
  MenuItem as Item,
  type RadioChangeEvent,
} from "@szhsin/react-menu"
import { useTranslation } from "react-i18next"
import { useIsMounted, useToggle } from "@react-hookz/web"
import { css } from "@emotion/css"
import { useEvent } from "@aet/hooks"
import { gql, useMutation, useQuery } from "~/generated/graphql"
import { FileState } from "~/shared/models/enums.generated"
import type * as types from "~/generated/graphql"
import { GET_FILES, useTreeContext } from "./index"
import { showErrorToast } from "~/shared/hooks/useShowApolloError"
import { useDropdownMenu } from "~/options/hooks/useDropdownMenu"
import { isGlobalStyle } from "~/shared"
import { closeFile } from "~/options/store/actions"
import { token } from "~/options/theme"
import { useDispatch } from "~/options/store"
import { SubLabelEditor } from "./SubLabelEditor"

const CONTEXT_MENU_SINGLE_QUERY = gql<"ContextMenuSingleQuery">`
  query ContextMenuSingleQuery($id: ID!) {
    file(id: $id) {
      id
      enabled
      state
      icon
      subLabel
    }
  }
`

const CONTEXT_MENU_MULTIPLE_QUERY = gql<"ContextMenuMultipleQuery">`
  query ContextMenuMultipleQuery($ids: [ID!]!) {
    files(filter: { id: $ids }) {
      id
      enabled
      state
      icon
      subLabel
    }
  }
`

const UPDATE_FILE = gql<"SetFileFromContextMenu">`
  mutation SetFileFromContextMenu($ids: [ID!]!, $input: PartialFileInput!) {
    setFiles(ids: $ids, input: $input) {
      id
      enabled
      state
    }
  }
`

const DELETE_FILE = gql<"DeleteFileFromContextMenu">`
  mutation DeleteFileFromContextMenu($ids: [ID!]!) {
    deleteFiles(ids: $ids)
  }
`

function useOnMenuItemClick(fn: () => void) {
  return useEvent((e: RadioChangeEvent) => {
    e.syntheticEvent.stopPropagation()
    fn()
  })
}

type Files = types.ContextMenuMultipleQueryQuery["files"]

const Menus = memo<{ files: Files; onMount?(): void }>(({ files, onMount }) => {
  const dispatch = useDispatch()
  const isMounted = useIsMounted()()
  const { t } = useTranslation()
  const [setFiles] = useMutation(UPDATE_FILE, {
    refetchQueries: [{ query: GET_FILES }],
  })
  const ids = useMemo(() => files.map(x => x.id), [files])
  const { setRenamingFileID, setModifyingIconFileID } = useTreeContext()
  const [showLabelDialog, setShowLabelDialog] = useToggle(false)

  const closeFiles = useCallback(() => {
    ids.forEach(id => {
      dispatch(closeFile(id))
    })
  }, [dispatch, ids])

  const [strongDeleteFile] = useMutation(DELETE_FILE, {
    refetchQueries: [{ query: GET_FILES }],
    onError: showErrorToast,
    onCompleted: closeFiles,
  })

  const update = useEvent(
    (partial: Omit<types.PartialFileInput, "id">, close?: boolean) => {
      setFiles({
        variables: { ids, input: partial },
        onCompleted: close ? closeFiles : undefined,
      })
    },
  )

  useEffect(() => {
    if (isMounted) {
      onMount?.()
    }
  }, [isMounted, onMount])

  const enable = useOnMenuItemClick(() => update({ enabled: true }))
  const disable = useOnMenuItemClick(() => update({ enabled: false }, true))
  const deleteState = useOnMenuItemClick(() => update({ state: FileState.None }))
  const hide = useOnMenuItemClick(() => update({ state: FileState.Hidden }))
  const archive = useOnMenuItemClick(() => update({ state: FileState.Archived }))
  const deleteFile = useOnMenuItemClick(() => update({ state: FileState.Deleted }, true))
  const definitelyDelete = useOnMenuItemClick(() =>
    strongDeleteFile({ variables: { ids } }),
  )
  const rename = useOnMenuItemClick(() => setRenamingFileID(ids[0]))
  const setLabel = useEvent((label: string) => update({ subLabel: label }))

  const modifyIcon = useOnMenuItemClick(() => setModifyingIconFileID(ids[0]))

  const some = (predicate: (file: Files[number]) => boolean) => files.some(predicate)
  const all: typeof some = predicate => files.every(predicate)

  const globalSelected = some(isGlobalStyle)

  return (
    <>
      <SubLabelEditor
        value={files[0]?.subLabel ?? ""}
        onChange={setLabel}
        open={showLabelDialog}
        onClose={() => setShowLabelDialog(false)}
      />
      {files.length === 1 && !globalSelected && (
        <Item key="rename" onClick={rename}>
          <Icon icon="BiRename" />
          {t("editor.verbRename")}
        </Item>
      )}
      {files.length === 1 && !globalSelected && (
        <Item key="modifyIcon" onClick={() => setShowLabelDialog()}>
          <Icon icon="BsTags" />
          {t("editor.verbModifyLabel")}
        </Item>
      )}
      {files.length === 1 && !globalSelected && (
        <Item key="modifyIcon" onClick={modifyIcon}>
          <Icon icon="HiStar" />
          {t("editor.verbModifyIcon")}
        </Item>
      )}
      {some(x => x.state === FileState.None && x.enabled) && (
        <Item key="disable" onClick={disable}>
          <Icon icon="VscDebugPause" />
          {t("editor.verbDisable")}
        </Item>
      )}
      {some(x => x.state === FileState.None && !x.enabled) && (
        <Item key="enable" onClick={enable}>
          <Icon icon="FaPlay" />
          {t("editor.verbEnable")}
        </Item>
      )}
      {all(x => x.state === FileState.None) && (
        <Item key="unhide" onClick={hide}>
          <Icon icon="BiHide" />
          {t("editor.verbHide")}
        </Item>
      )}
      {some(x => x.state === FileState.None) && (
        <>
          {!globalSelected && (
            <Item key="archive" onClick={archive}>
              <Icon icon="VscArchive" />
              {t("editor.verbArchive")}
            </Item>
          )}
          <Item key="delete" onClick={deleteFile}>
            <Icon icon="VscTrash" />
            {t("editor.verbDelete")}
          </Item>
        </>
      )}
      {some(x => x.state === FileState.Archived) && !globalSelected && (
        <Item key="unarchive" onClick={deleteState}>
          <Icon icon="RiInboxUnarchiveFill" />
          {t("editor.verbUnarchive")}
        </Item>
      )}
      {all(x => x.state === FileState.Hidden) && (
        <Item key="unhide" onClick={deleteState}>
          <Icon icon="BiShow" />
          {t("editor.verbUnhide")}
        </Item>
      )}
      {all(x => x.state === FileState.Deleted) && (
        <>
          <Item key="restore" onClick={deleteState}>
            <Icon icon="FaTrashRestore" />
            {t("editor.verbRestore")}
          </Item>
          <Item
            key="permDelete"
            onClick={definitelyDelete}
            className={css`
              color: ${token.danger.fg};
            `}
          >
            <Icon icon="VscTrash" />
            {t("editor.verbDefinitelyDelete")}
          </Item>
        </>
      )}
    </>
  )
})

export type FileItemHook = ReturnType<typeof useFileItemMenu>

export function useFileItemMenu(ids: string[]) {
  const isSingular = ids.length === 1

  const [skip, setSkip] = useState(true)
  const {
    menuProps,
    toggleMenu,
    onMouse: onMenu,
  } = useDropdownMenu(css`
    .szh-menu--state-open {
      min-width: 171px;
    }
  `)

  const multiple = useQuery(CONTEXT_MENU_MULTIPLE_QUERY, {
    fetchPolicy: "cache-first",
    variables: { ids },
    skip: skip || isSingular,
    onError: showErrorToast,
  })

  const single = useQuery(CONTEXT_MENU_SINGLE_QUERY, {
    fetchPolicy: "cache-first",
    variables: { id: ids[0] },
    skip: skip || !isSingular,
    onError: showErrorToast,
  })

  useEffect(() => {
    if (menuProps.state === "open") {
      setSkip(false)
    }
  }, [menuProps.state])

  const files = isSingular
    ? single.data?.file
      ? [single.data.file]
      : undefined
    : multiple.data?.files

  return {
    toggleMenu,
    onMenu,
    menu: files != null && (
      <ControlledMenu {...menuProps}>
        <Menus files={files} />
      </ControlledMenu>
    ),
  }
}
