import styled from "@emotion/styled"
import { useCallback, useRef, useState } from "react"
import { token } from "~/options/theme"
import { useClickOutside, useEvent } from "@aet/hooks"
import { type Pos, SuggestionMenu } from "./SuggestionMenu"
import { Tag, type TagRender, defaultTagRender } from "./Tag"

export interface TagsInputProps {
  /** className for root element */
  className?: string
  /** value for name of input */
  name?: string
  /** placeholder for text input */
  placeholder?: string
  /** initial tags */
  value?: string[]
  /** onChange callback (added/removed) */
  onChange: (tags: string[]) => void
  /** input `onBlur` callback */
  onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void
  /** when to add tag (i.e. `"Enter"`, `" "`) */
  separators?: string[]
  /** disable backspace remove */
  disableBackspaceRemove?: boolean
  /** if tag is already added then callback */
  onExisting?: (tag: string) => void
  /** on tag removed callback */
  onRemoved?: (tag: string) => void
  /** disable input and tags */
  disabled?: boolean
  /** Remove the tag but keep the word in the input to edit it on using Backspace Key */
  isEditOnRemove?: boolean
  /** Custom validation before adding tag */
  beforeAddValidate?: (tag: string, existingTags: string[]) => boolean
  /** input `onKeyUp` callback */
  onKeyUp?: (e: React.KeyboardEvent<HTMLInputElement>) => void
  /** className for styling input and tags (e.g. `{ tag: "tag-cls", input: "input-cls" }`) */
  classNames?: {
    input?: string
    tag?: string
  }
  /** suggestions for tags */
  suggestions?: string[]
  /** custom tag renderer */
  tagRender?: TagRender
}

const defaultSeparators = ["Enter"]

export const TagsInput = ({
  beforeAddValidate,
  className,
  classNames,
  disableBackspaceRemove,
  disabled,
  isEditOnRemove,
  name,
  onBlur,
  onChange,
  onExisting,
  onKeyUp,
  onRemoved,
  placeholder,
  separators = defaultSeparators,
  suggestions,
  tagRender = defaultTagRender,
  value = [],
}: TagsInputProps) => {
  const containerRef = useRef<HTMLDivElement>(null)
  const [input, setInput] = useState("")
  const [inputPos, setInputPos] = useState<Pos | null>(null)
  const closeMenu = useCallback(() => setInputPos(null), [])

  const addTag = useEvent((text: string) => {
    if (beforeAddValidate && !beforeAddValidate(text, value)) {
      return false
    }

    if (value.includes(text)) {
      onExisting?.(text)
      return false
    }

    onChange([...value, text])
    return true
  })

  useClickOutside(containerRef, () => {
    setInputPos(null)
  })

  const handleOnKeyDown: React.KeyboardEventHandler<HTMLInputElement> = useEvent(e => {
    e.stopPropagation()

    const text = e.currentTarget.value

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

    if (!text && !disableBackspaceRemove && value.length && e.key === "Backspace") {
      e.currentTarget.value = isEditOnRemove ? `${value.at(-1)} ` : ""
      onChange([...value.slice(0, -1)])
    }

    if (text && separators.includes(e.key)) {
      e.preventDefault()
      if (addTag(text)) {
        setInput("")
      }
    }
  })

  const onInputChange = useEvent((e: React.FormEvent<HTMLInputElement>) => {
    setInput(e.currentTarget.value)
  })

  const onHandleInput = useEvent((e: React.FormEvent<HTMLInputElement>) => {
    const { inputType } = e.nativeEvent as InputEvent
    // Only intercept datalist selection events
    if (
      // Gecko
      inputType === "insertReplacementText" ||
      // WebKit and Blink
      inputType === undefined
    ) {
      if (addTag(e.currentTarget.value)) {
        setInput("")
      }
    }
  })

  const onTagRemove = useEvent((text: string) => {
    onChange(value.filter(tag => tag !== text))
    onRemoved?.(text)
  })

  const onInputFocus = useEvent((e: React.FocusEvent<HTMLInputElement>) => {
    const input = e.currentTarget
    const { top, left, height } = input.getBoundingClientRect()
    setInputPos({ x: left, y: top + height })
  })

  return (
    <TagsInputContainer ref={containerRef} aria-labelledby={name} className={className}>
      {value.map(tag =>
        tagRender({
          Tag,
          tag,
          defaultProps: {
            className: classNames?.tag,
            text: tag,
            remove: onTagRemove,
            disabled,
          },
        }),
      )}

      <Input
        className={classNames?.input}
        disabled={disabled}
        key="input"
        name={name}
        onBlur={onBlur}
        onFocus={onInputFocus}
        onInput={onHandleInput}
        onKeyDown={handleOnKeyDown}
        onKeyUp={onKeyUp}
        placeholder={placeholder}
        type="text"
        value={input}
        onChange={onInputChange}
      />

      <SuggestionMenu
        onClose={closeMenu}
        suggestions={suggestions}
        currentValues={value}
        onAddTag={addTag}
        inputValue={input}
        inputPos={inputPos}
      />
    </TagsInputContainer>
  )
}

const TagsInputContainer = styled.div`
  align-items: center;
  border: 1px solid ${token.border.default};
  border-radius: 0.375rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  line-height: 1.4;
  padding: 0.5rem;

  * {
    box-sizing: border-box;
    transition: all 0.2s ease;
  }

  &:focus-within {
    border-color: #3182ce;
    box-shadow: #3182ce 0px 0px 0px 1px;
  }
`

const Input = styled.input`
  border: 0;
  outline: 0;
  font-size: inherit;
  line-height: inherit;
  width: 50%;

  &::-webkit-calendar-picker-indicator {
    display: none !important;
  }
`
