import { keyframes } from "@emotion/react"
import styled from "@emotion/styled"
import Icon from "@aet/icons/macro"
import { memo, useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { toast } from "react-hot-toast"
import { chunk } from "lodash"
import { useEvent } from "@aet/hooks"
import { gql, useMutation } from "~/generated/graphql"
import { CheckboxState, TristateCheckbox } from "~/options/components/TristateCheckbox"
import { PrimaryPill } from "~/shared/styled"
import { token, vars } from "~/options/theme"
import type { File as IFile } from "~/shared/models"
import { fromLegacy, v1ToNow } from "./mapFn"
import { GET_FILES } from "../../FileTree"

const ImportFile = gql<"ImportFile">`
  mutation ImportFile($inputs: [PartialFileInput!]!) {
    newFiles(inputs: $inputs) {
      id
    }
  }
`

export const FileView = memo<{ file: File }>(({ file }) => {
  const { t } = useTranslation()
  const [importFile] = useMutation(ImportFile, {
    refetchQueries: [GET_FILES],
  })
  const [importing, setImporting] = useState(false)

  const [content, setContent] = useState<IFile[] | null>(null)

  const [checked, setChecked] = useState<number[]>([])
  const [imported, setImported] = useState<number[]>([])
  const [failed, setFailed] = useState<number[]>([])

  const onLoad = useEvent((e: ProgressEvent<FileReader>) => {
    const context = e.target!.result as string
    const json = JSON.parse(context)
    let list: IFile[]
    if ("directories" in json) {
      list = fromLegacy(json)
    } else if (typeof json.schema === "number") {
      list = v1ToNow(json)
    } else {
      toast.error(t("import.invalidFile"))
      return
    }

    setContent(list)
    setChecked(list.map((_, i) => i))
    setImporting(false)
    setImported([])
    setFailed([])
  })

  useEffect(() => {
    if (!file) return

    const reader = new FileReader()
    reader.onload = onLoad
    reader.readAsText(file)
    // onLoad is always stable but ESLint doesn’t know
  }, [onLoad, file])

  const handleSelectAllClick: React.MouseEventHandler<HTMLInputElement> = useEvent(e => {
    if (e.currentTarget.checked) {
      setChecked(content!.map((_, i) => i))
    } else {
      setChecked([])
    }
  })

  const onImport = useEvent(async () => {
    setImporting(true)
    for (const indices of chunk(checked, 100)) {
      try {
        await importFile({
          variables: { inputs: indices.map(i => content![i]) },
        })
        setImported(imported => [...imported, ...indices])
      } catch (e) {
        toast.error(<div style={{ whiteSpace: "pre-line" }}>{e.message}</div>)
        setFailed(failed => [...failed, ...indices])
      }
    }
    await wait(200)
    setImporting(false)
    setImported([])
  })

  if (!content) {
    return null
  }

  return (
    <>
      <FilesViewContainer>
        <FileRow>
          <CheckboxAll
            checked={
              checked.length === content.length
                ? CheckboxState.Checked
                : checked.length === 0
                ? CheckboxState.Empty
                : CheckboxState.Indeterminate
            }
            onClick={handleSelectAllClick}
          />
          <SelectAll>
            <FileName>{t("import.selectAll")}</FileName>
            {importing &&
              (failed.length ? (
                <Icon icon="MdOutlineReportGmailerrorred" />
              ) : imported.length === checked.length ? (
                <Icon icon="ImCheckmark" />
              ) : (
                <Spinner />
              ))}
          </SelectAll>
        </FileRow>

        {content.map((c, i) => (
          <div key={i}>
            <FileRow>
              <Checkbox
                type="checkbox"
                checked={checked.includes(i)}
                disabled={importing}
                onChange={e => {
                  if (e.currentTarget.checked) {
                    setChecked([...checked, i])
                  } else {
                    setChecked(checked.filter(x => x !== i))
                  }
                }}
              />
              <FileName>
                {c.name}
                {c.tags.map(t => (
                  <Tag key={t}>{t}</Tag>
                ))}
              </FileName>
              {importing &&
                checked.includes(i) &&
                (failed.includes(i) ? (
                  <Icon icon="MdOutlineReportGmailerrorred" />
                ) : imported.includes(i) ? (
                  <Icon icon="ImCheckmark" />
                ) : (
                  <Spinner />
                ))}
            </FileRow>
          </div>
        ))}
      </FilesViewContainer>
      <ImportButton
        title="Import"
        disabled={importing || !checked.length}
        onClick={onImport}
      >
        {t("tab.importNFile", { n: checked.length })}
      </ImportButton>
    </>
  )
})

const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

const SelectAll = styled.label`
  display: flex;
  align-items: center;
  flex: 1;
  font-weight: 600;
`
const FilesViewContainer = styled.div`
  max-height: 200px;
  overflow: scroll;
`

const FileRow = styled.div`
  display: flex;
  align-items: center;
  line-height: 1.7em;
`

const Checkbox = styled.input`
  margin-right: 5px;
`

const CheckboxAll = Checkbox.withComponent(TristateCheckbox)

const ImportButton = styled(PrimaryPill)`
  margin-top: 15px;
`
const FileName = styled.span`
  display: flex;
  align-items: center;
  flex: 1;
`

const SpinnerRotation = keyframes`
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
`

const Spinner = styled.div`
  width: 14px;
  height: 14px;
  border: 2px solid ${token.border.default};
  border-bottom-color: transparent;
  border-radius: 50%;
  display: inline-block;
  box-sizing: border-box;
  animation: ${SpinnerRotation} 1s linear infinite;
`

const Tag = styled.span`
  background-color: ${vars.backgroundActive};
  border-radius: 3px;
  font-size: 0.8em;
  line-height: calc(1em + 2px);
  margin-left: 7px;
  padding: 0 3px;
`
