import { ActionIconButton } from '@renderer/components/Buttons'
import NarrowLayout from '@renderer/pages/home/Messages/NarrowLayout'
import { scrollElementIntoView } from '@renderer/utils'
import { Tooltip } from 'antd'
import { debounce } from 'lodash'
import { CaseSensitive, ChevronDown, ChevronUp, User, WholeWord, X } from 'lucide-react'
import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'

interface Props {
  children?: React.ReactNode
  searchTarget: React.RefObject<React.ReactNode> | React.RefObject<HTMLElement> | HTMLElement
  /**
   * 过滤`node`，`node`只会是`Node.TEXT_NODE`类型的文本节点
   *
   * 返回`true`表示该`node`会被搜索
   */
  filter: NodeFilter
  includeUser?: boolean
  onIncludeUserChange?: (value: boolean) => void
  /**
   * 是否显示“包含用户问题”切换按钮（默认为 true）。
   * 在富文本编辑器场景通常不需要该按钮。
   */
  showUserToggle?: boolean
  /**
   * 搜索条定位方式
   */
  positionMode?: 'fixed' | 'absolute' | 'sticky'
}

enum SearchCompletedState {
  NotSearched,
  Searched
}

export interface ContentSearchRef {
  disable(): void
  enable(initialText?: string): void
  // 搜索下一个并定位
  searchNext(): void
  // 搜索上一个并定位
  searchPrev(): void
  // 搜索并定位
  search(): void
  // 搜索但不定位，或者说是更新
  silentSearch(): void
  focus(): void
}

const escapeRegExp = (string: string): string => {
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
}

const findRangesInTarget = (
  target: HTMLElement,
  filter: NodeFilter,
  searchText: string,
  isCaseSensitive: boolean,
  isWholeWord: boolean
): Range[] => {
  CSS.highlights.clear()
  const ranges: Range[] = []

  const escapedSearchText = escapeRegExp(searchText)

  // 检查搜索文本是否仅包含拉丁字母
  const hasOnlyLatinLetters = /^[a-zA-Z\s]+$/.test(searchText)

  // 只有当搜索文本仅包含拉丁字母时才应用大小写敏感
  const regexFlags = hasOnlyLatinLetters && isCaseSensitive ? 'g' : 'gi'
  const regexPattern = isWholeWord ? `\\b${escapedSearchText}\\b` : escapedSearchText
  const searchRegex = new RegExp(regexPattern, regexFlags)
  const treeWalker = document.createTreeWalker(target, NodeFilter.SHOW_TEXT, filter)
  const allTextNodes: { node: Node; startOffset: number }[] = []
  let fullText = ''

  // 1. 拼接所有文本节点内容
  while (treeWalker.nextNode()) {
    allTextNodes.push({
      node: treeWalker.currentNode,
      startOffset: fullText.length
    })
    fullText += treeWalker.currentNode.nodeValue
  }

  // 2.在完整文本中查找匹配项
  let match: RegExpExecArray | null = null
  while ((match = searchRegex.exec(fullText))) {
    const matchStart = match.index
    const matchEnd = matchStart + match[0].length

    // 3. 将匹配项的索引映射回DOM Range
    let startNode: Node | null = null
    let endNode: Node | null = null
    let startOffset = 0
    let endOffset = 0

    // 找到起始节点和偏移
    for (const nodeInfo of allTextNodes) {
      if (
        matchStart >= nodeInfo.startOffset &&
        matchStart < nodeInfo.startOffset + (nodeInfo.node.nodeValue?.length ?? 0)
      ) {
        startNode = nodeInfo.node
        startOffset = matchStart - nodeInfo.startOffset
        break
      }
    }

    // 找到结束节点和偏移
    for (const nodeInfo of allTextNodes) {
      if (
        matchEnd > nodeInfo.startOffset &&
        matchEnd <= nodeInfo.startOffset + (nodeInfo.node.nodeValue?.length ?? 0)
      ) {
        endNode = nodeInfo.node
        endOffset = matchEnd - nodeInfo.startOffset
        break
      }
    }

    // 如果起始和结束节点都找到了，则创建一个 Range
    if (startNode && endNode) {
      const range = new Range()
      range.setStart(startNode, startOffset)
      range.setEnd(endNode, endOffset)
      ranges.push(range)
    }
  }

  return ranges
}

// eslint-disable-next-line @eslint-react/no-forward-ref
export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
  (
    { searchTarget, filter, includeUser = false, onIncludeUserChange, showUserToggle = true, positionMode = 'fixed' },
    ref
  ) => {
    const target: HTMLElement | null = (() => {
      if (searchTarget instanceof HTMLElement) {
        return searchTarget
      } else {
        return (searchTarget.current as HTMLElement) ?? null
      }
    })()
    const containerRef = React.useRef<HTMLDivElement>(null)
    const searchInputRef = React.useRef<HTMLInputElement>(null)
    const [enableContentSearch, setEnableContentSearch] = useState(false)
    const [searchCompleted, setSearchCompleted] = useState(SearchCompletedState.NotSearched)
    const [isCaseSensitive, setIsCaseSensitive] = useState(false)
    const [isWholeWord, setIsWholeWord] = useState(false)
    const [allRanges, setAllRanges] = useState<Range[]>([])
    const [currentIndex, setCurrentIndex] = useState(-1)
    const prevSearchText = useRef('')
    const { t } = useTranslation()

    const resetSearch = useCallback(() => {
      CSS.highlights.clear()
      setAllRanges([])
      setSearchCompleted(SearchCompletedState.NotSearched)
    }, [])

    const locateByIndex = useCallback(
      (shouldScroll = true) => {
        // 清理旧的高亮
        CSS.highlights.clear()

        if (allRanges.length > 0) {
          // 1. 创建并注册所有匹配项的高亮
          const allMatchesHighlight = new Highlight(...allRanges)
          CSS.highlights.set('search-matches', allMatchesHighlight)

          // 2. 如果有当前项，为其创建并注册一个特殊的高亮
          if (currentIndex !== -1 && allRanges[currentIndex]) {
            const currentMatchRange = allRanges[currentIndex]
            const currentMatchHighlight = new Highlight(currentMatchRange)
            CSS.highlights.set('current-match', currentMatchHighlight)

            // 3. 将当前项滚动到视图中
            // 获取第一个文本节点的父元素来进行滚动
            const parentElement = currentMatchRange.startContainer.parentElement
            if (shouldScroll && parentElement) {
              // 优先在指定的滚动容器内滚动，避免滚动整个页面导致索引错乱/看起来"跳到第一条"
              scrollElementIntoView(parentElement, target)
            }
          }
        }
      },
      [allRanges, currentIndex, target]
    )

    const search = useCallback(
      (jump = false) => {
        const searchText = searchInputRef.current?.value.trim() ?? null
        setSearchCompleted(SearchCompletedState.Searched)
        if (target && searchText !== null && searchText !== '') {
          const ranges = findRangesInTarget(target, filter, searchText, isCaseSensitive, isWholeWord)
          setAllRanges(ranges)
          setCurrentIndex(jump && ranges.length > 0 ? 0 : -1)
        }
      },
      [target, filter, isCaseSensitive, isWholeWord]
    )

    const implementation = useMemo(
      () => ({
        disable: () => {
          setEnableContentSearch(false)
          CSS.highlights.clear()
        },
        enable: (initialText?: string) => {
          setEnableContentSearch(true)
          if (searchInputRef.current) {
            const inputEl = searchInputRef.current
            if (initialText && initialText.trim().length > 0) {
              inputEl.value = initialText
              requestAnimationFrame(() => {
                inputEl.focus()
                inputEl.select()
                search(false)
              })
            } else {
              requestAnimationFrame(() => {
                inputEl.focus()
                inputEl.select()
              })
            }
          }
        },
        searchNext: () => {
          if (allRanges.length > 0) {
            setCurrentIndex((prev) => (prev < allRanges.length - 1 ? prev + 1 : 0))
          }
        },
        searchPrev: () => {
          if (allRanges.length > 0) {
            setCurrentIndex((prev) => (prev > 0 ? prev - 1 : allRanges.length - 1))
          }
        },
        resetSearchState: () => {
          setSearchCompleted(SearchCompletedState.NotSearched)
        },
        search: () => {
          search(true)
          locateByIndex(true)
        },
        silentSearch: () => {
          search(false)
          locateByIndex(false)
        },
        focus: () => {
          searchInputRef.current?.focus()
        }
      }),
      [allRanges.length, locateByIndex, search]
    )

    const _searchHandlerDebounce = useMemo(() => debounce(implementation.search, 300), [implementation.search])

    const searchHandler = useCallback(() => {
      _searchHandlerDebounce()
    }, [_searchHandlerDebounce])

    const userInputHandler = useCallback(
      (event: React.ChangeEvent<HTMLInputElement>) => {
        const value = event.target.value.trim()
        if (value.length === 0) {
          resetSearch()
        } else {
          searchHandler()
        }
        prevSearchText.current = value
      },
      [searchHandler, resetSearch]
    )

    const keyDownHandler = useCallback(
      (event: React.KeyboardEvent<HTMLInputElement>) => {
        if (event.key === 'Enter') {
          event.preventDefault()
          const value = (event.target as HTMLInputElement).value.trim()
          if (value.length === 0) {
            resetSearch()
            return
          }
          if (event.shiftKey) {
            implementation.searchPrev()
          } else {
            implementation.searchNext()
          }
        } else if (event.key === 'Escape') {
          event.stopPropagation()
          implementation.disable()
        }
      },
      [implementation, resetSearch]
    )

    const searchInputFocus = useCallback(() => {
      requestAnimationFrame(() => searchInputRef.current?.focus())
    }, [])

    const userOutlinedButtonOnClick = useCallback(() => {
      onIncludeUserChange?.(!includeUser)
      searchInputFocus()
    }, [includeUser, onIncludeUserChange, searchInputFocus])

    useImperativeHandle(ref, () => implementation, [implementation])

    useEffect(() => {
      locateByIndex()
    }, [currentIndex, locateByIndex])

    useEffect(() => {
      if (enableContentSearch && searchInputRef.current?.value.trim()) {
        search(true)
      }
    }, [isCaseSensitive, isWholeWord, enableContentSearch, search])

    const prevButtonOnClick = () => {
      implementation.searchPrev()
      searchInputFocus()
    }

    const nextButtonOnClick = () => {
      implementation.searchNext()
      searchInputFocus()
    }

    const closeButtonOnClick = () => {
      implementation.disable()
    }

    const caseSensitiveButtonOnClick = () => {
      setIsCaseSensitive(!isCaseSensitive)
      searchInputFocus()
    }

    const wholeWordButtonOnClick = () => {
      setIsWholeWord(!isWholeWord)
      searchInputFocus()
    }

    return (
      <Container
        ref={containerRef}
        style={enableContentSearch ? {} : { display: 'none' }}
        $overlayPosition={positionMode === 'absolute' ? 'absolute' : 'static'}>
        <NarrowLayout style={{ width: '100%' }}>
          <SearchBarContainer $position={positionMode}>
            <InputWrapper>
              <Input
                ref={searchInputRef}
                onInput={userInputHandler}
                onKeyDown={keyDownHandler}
                placeholder={t('chat.assistant.search.placeholder')}
                style={{ lineHeight: '20px' }}
              />
              <ToolBar>
                {showUserToggle && (
                  <Tooltip title={t('button.includes_user_questions')} mouseEnterDelay={0.8} placement="bottom">
                    <ActionIconButton onClick={userOutlinedButtonOnClick}>
                      <User size={18} style={{ color: includeUser ? 'var(--color-link)' : 'var(--color-icon)' }} />
                    </ActionIconButton>
                  </Tooltip>
                )}
                <Tooltip title={t('button.case_sensitive')} mouseEnterDelay={0.8} placement="bottom">
                  <ActionIconButton onClick={caseSensitiveButtonOnClick}>
                    <CaseSensitive
                      size={18}
                      style={{ color: isCaseSensitive ? 'var(--color-link)' : 'var(--color-icon)' }}
                    />
                  </ActionIconButton>
                </Tooltip>
                <Tooltip title={t('button.whole_word')} mouseEnterDelay={0.8} placement="bottom">
                  <ActionIconButton onClick={wholeWordButtonOnClick}>
                    <WholeWord size={18} style={{ color: isWholeWord ? 'var(--color-link)' : 'var(--color-icon)' }} />
                  </ActionIconButton>
                </Tooltip>
              </ToolBar>
            </InputWrapper>
            <Separator></Separator>
            <SearchResults>
              {searchCompleted !== SearchCompletedState.NotSearched && allRanges.length > 0 ? (
                <>
                  <SearchResultCount>{currentIndex + 1}</SearchResultCount>
                  <SearchResultSeparator>/</SearchResultSeparator>
                  <SearchResultTotalCount>{allRanges.length}</SearchResultTotalCount>
                </>
              ) : (
                <SearchResultsPlaceholder>0/0</SearchResultsPlaceholder>
              )}
            </SearchResults>
            <ToolBar>
              <ActionIconButton onClick={prevButtonOnClick} disabled={allRanges.length === 0}>
                <ChevronUp size={18} />
              </ActionIconButton>
              <ActionIconButton onClick={nextButtonOnClick} disabled={allRanges.length === 0}>
                <ChevronDown size={18} />
              </ActionIconButton>
              <ActionIconButton onClick={closeButtonOnClick}>
                <X size={18} />
              </ActionIconButton>
            </ToolBar>
          </SearchBarContainer>
        </NarrowLayout>
        <Placeholder />
      </Container>
    )
  }
)

ContentSearch.displayName = 'ContentSearch'

const Container = styled.div<{ $overlayPosition: 'static' | 'absolute' }>`
  display: flex;
  flex-direction: row;
  position: ${({ $overlayPosition }) => $overlayPosition};
  top: ${({ $overlayPosition }) => ($overlayPosition === 'absolute' ? '0' : 'auto')};
  left: ${({ $overlayPosition }) => ($overlayPosition === 'absolute' ? '0' : 'auto')};
  right: ${({ $overlayPosition }) => ($overlayPosition === 'absolute' ? '0' : 'auto')};
  z-index: 999;
`

const SearchBarContainer = styled.div<{ $position: 'fixed' | 'absolute' | 'sticky' }>`
  border: 1px solid var(--color-primary);
  border-radius: 10px;
  transition: all 0.2s ease;
  position: ${({ $position }) => $position};
  top: 15px;
  left: 20px;
  right: 20px;
  margin-bottom: 5px;
  padding: 5px 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--color-background);
  flex: 1 1 auto; /* Take up input's previous space */
`

const Placeholder = styled.div`
  width: 5px;
`

const InputWrapper = styled.div`
  display: flex;
  align-items: center;
  flex: 1 1 auto; /* Take up input's previous space */
`

const Input = styled.input`
  border: none;
  color: var(--color-text);
  background-color: transparent;
  outline: none;
  width: 100%;
  padding: 0 5px; /* Adjust padding, wrapper will handle spacing */
  flex: 1; /* Allow input to grow */
  font-size: 14px;
  font-family: Ubuntu;
`

const ToolBar = styled.div`
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: tpx;
`

const Separator = styled.div`
  width: 1px;
  height: 1.5em;
  background-color: var(--color-border);
  margin-left: 2px;
  margin-right: 2px;
  flex: 0 0 auto;
`

const SearchResults = styled.div`
  display: flex;
  justify-content: center;
  width: 80px;
  margin: 0 2px;
  flex: 0 0 auto;
  color: var(--color-text-1);
  font-size: 14px;
  font-family: Ubuntu;
`

const SearchResultsPlaceholder = styled.span`
  color: var(--color-text-1);
  opacity: 0.5;
`

const SearchResultCount = styled.span`
  color: var(--color-text);
`

const SearchResultSeparator = styled.span`
  color: var(--color-text);
  margin: 0 4px;
`

const SearchResultTotalCount = styled.span`
  color: var(--color-text);
`
