import type { CoreService, SearchService } from '../../core/tokens'
import type { SearchConfig, SearchEngine, SearchResult } from './types'
import { createAlgoliaEngine } from './engines/algolia'
import { createCSEEngine } from './engines/cse'
import { createFuseEngine } from './engines/fuse'
import { createPagefindEngine } from './engines/pagefind'

/**
 * Search module — orchestrates search engines, autocomplete, and UI lifecycle.
 *
 * Responsibilities:
 * - Select and initialize the configured search engine backend.
 * - Manage search dialog open/close and keyboard shortcuts.
 * - Handle autocomplete, result rendering, and UI state reset.
 */
export class SearchModule implements SearchService {
  #engine: SearchEngine | undefined
  #autocompleteInstance: any
  #openDialog: (() => void) | undefined
  #closeDialog: (() => void) | undefined
  #initialized = false

  constructor(private readonly core: CoreService) {}

  /** Create the appropriate search engine based on type. */
  #createEngine(type: string, searchConfig: SearchConfig): SearchEngine {
    switch (type) {
      case 'algolia':
        return createAlgoliaEngine(searchConfig)
      case 'fuse':
        return createFuseEngine(searchConfig)
      case 'cse':
        return createCSEEngine(this.core.config.cse)
      case 'pagefind':
        return createPagefindEngine(searchConfig, this.core.config.pagefind!)
      default:
        console.warn(`[FixIt] Unknown search type: "${type}". Supported types: algolia, fuse, cse, pagefind.`)
        return {
          async search() {
            return []
          },
        }
    }
  }

  /** Initialize @algolia/autocomplete-js instance. */
  #initAutosearch() {
    const searchConfig = this.core.config.search
    if (!searchConfig || !this.#engine)
      return

    searchConfig.placeholder = searchConfig.placeholder || 'Search...'
    searchConfig.clearText = searchConfig.clearText || 'Clear'

    const autocompleteLib = (window as any)['@algolia/autocomplete-js']
    if (!autocompleteLib?.autocomplete) {
      console.error('[FixIt] @algolia/autocomplete-js is not available on window')
      return
    }
    const { autocomplete } = autocompleteLib

    const { maxResultLength = 10 } = searchConfig
    const engine = this.#engine

    // Render the panel inside .search-modal (sibling of .search-modal-header)
    // so it stays within the <dialog> and benefits from the flex layout.
    const panelContainer = document.querySelector('#search-dialog .search-modal')
    let submitBtn: HTMLButtonElement | null = null

    this.#autocompleteInstance = autocomplete({
      container: '.search-modal-header',
      panelContainer: panelContainer || undefined,
      placeholder: searchConfig.placeholder,
      defaultActiveItemId: 0,
      detachedMediaQuery: 'none',
      translations: {
        clearButtonTitle: searchConfig.clearText,
      },
      classNames: {
        root: 'search-autocomplete',
        form: 'search-form',
        input: 'search-input',
        inputWrapper: 'search-input-wrapper',
        inputWrapperPrefix: 'search-input-prefix',
        inputWrapperSuffix: 'search-input-suffix',
        submitButton: 'search-submit-btn',
        clearButton: 'search-clear-btn',
        loadingIndicator: 'search-loading',
        label: 'search-label',
        list: 'search-list',
        item: 'search-item',
        panel: 'search-panel',
        panelLayout: 'search-panel-layout',
        source: 'search-source',
        sourceFooter: 'search-source-footer',
        sourceHeader: 'search-source-header',
        sourceNoResults: 'search-source-no-results',
      },
      getSources: ({ query }: { query: string }) => {
        if (!query.trim())
          return []
        return [{
          sourceId: 'search',
          async getItems(): Promise<SearchResult[]> {
            return engine.search(query, maxResultLength)
          },
          getItemUrl({ item }: { item: SearchResult }) {
            return item.uri
          },
          templates: {
            item({ item, html: h }: { item: SearchResult, html: any }) {
              const title = h`<span class="suggestion-title" dangerouslySetInnerHTML=${{ __html: item.title }}></span>`
              const icon = item.icon ? h`<span class="suggestion-icon" dangerouslySetInnerHTML=${{ __html: item.icon }}></span>` : ''
              const date = item.date ? h`<span class="suggestion-date">${item.date}</span>` : ''
              const heading = item.heading ? h`<span class="suggestion-heading"><span class="suggestion-heading-mark">#</span> <span dangerouslySetInnerHTML=${{ __html: item.heading }}></span></span>` : ''
              const context = h`<div class="suggestion-context" dangerouslySetInnerHTML=${{ __html: item.context }}></div>`
              const cats = item.categories?.length ? h`<span class="suggestion-category"><i class="fa-regular fa-folder" aria-hidden="true"></i> ${item.categories.join(', ')}</span>` : ''
              const cols = item.collections?.length ? h`<span class="suggestion-collection"><i class="fa-solid fa-layer-group" aria-hidden="true"></i> ${item.collections.join(', ')}</span>` : ''
              const tags = item.tags?.length ? h`<span class="suggestion-tag"><i class="fa-solid fa-tags" aria-hidden="true"></i> ${item.tags.join(', ')}</span>` : ''
              const hasMeta = cats || cols || tags
              const meta = hasMeta ? h`<div class="suggestion-meta">${cats}${cols}${tags}</div>` : ''
              return h`<div class="search-item-wrapper"><div><a href="${item.uri}">${title}</a>${icon}${heading}${date}</div>${context}${meta}</div>`
            },
            noResults({ html: h }: { html: any }) {
              return h`<div class="search-empty"><i class="fa-solid fa-magnifying-glass search-empty-icon" aria-hidden="true"></i><p>${searchConfig.noResultsFound}: <span class="search-query">"${query}"</span></p></div>`
            },
          },
          onSelect: ({ item }: { item: SearchResult }) => {
            if (item.uri) {
              this.#closeDialog?.()
              window.location.assign(item.uri)
            }
          },
        }]
      },
      onSubmit: ({ state }: { state: { activeItemId: number | null, collections: Array<{ items: SearchResult[] }> } }) => {
        const { activeItemId, collections } = state
        if (activeItemId === null || !collections.length)
          return

        const items = collections[0].items
        const item = items[activeItemId]
        if (item?.uri) {
          this.#closeDialog?.()
          window.location.assign(item.uri)
        }
      },
      onStateChange: ({ state }: { state: { query: string, collections: Array<{ items: SearchResult[] }> } }) => {
        if (!submitBtn)
          return

        const hasResults = state.collections.some(collection => collection.items.length > 0)
        submitBtn.disabled = !state.query.trim() || !hasResults
      },
    })

    // Cache submit button after autocomplete creates it
    submitBtn = document.querySelector('.search-submit-btn')

    // Replace clear button SVG with text
    const clearBtn = document.querySelector('.search-autocomplete .search-clear-btn')
    if (clearBtn) {
      clearBtn.textContent = searchConfig.clearText
    }

    // Prevent autocomplete input blur on mousedown
    document.addEventListener('mousedown', (e) => {
      e.stopPropagation()
    })

    // Close button
    document.querySelector('.search-close-btn')?.addEventListener('click', () => this.#closeDialog?.())

    // Preload pagefind on first focus
    if (searchConfig.type === 'pagefind' && this.#engine.preload) {
      const container = document.querySelector('.search-autocomplete')
      container?.addEventListener('focusin', () => {
        this.#engine!.preload!().catch((error: Error) => {
          console.error(error)
        })
      }, { once: true })
    }
  }

  /** Initialize the search dialog open/close lifecycle using native <dialog>. */
  #initDialog() {
    const dialog = document.getElementById('search-dialog') as HTMLDialogElement | null
    if (!dialog)
      return

    const focusInput = () => {
      requestAnimationFrame(() => {
        const input = dialog.querySelector('input') as HTMLInputElement | null
        input?.focus()
      })
    }

    const open = () => {
      dialog.showModal()
      focusInput()
    }

    const close = () => {
      if (dialog.open)
        dialog.close()
    }

    const resetAutocomplete = () => {
      if (this.#autocompleteInstance) {
        this.#autocompleteInstance.setQuery('')
        this.#autocompleteInstance.setIsOpen(false)
      }
    }

    // Expose open/close functions for use in autocomplete callbacks and keyboard shortcuts
    this.#openDialog = open
    this.#closeDialog = close

    document.querySelector('.search-trigger.desktop')?.addEventListener('click', open)
    document.querySelector('.search-trigger.mobile')?.addEventListener('click', () => {
      this.core.closeMaskOverlay('menu-mobile')
      open()
    })

    // Reset autocomplete state when dialog closes (Escape, backdrop click, etc.)
    dialog.addEventListener('close', resetAutocomplete)

    // @algolia/autocomplete-js calls preventDefault() on Escape,
    // which prevents the native <dialog> close. Close manually.
    dialog.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && dialog.open) {
        e.preventDefault()
        close()
      }
    })
  }

  /** Initialize keyboard shortcuts. */
  #initKeyboardShortcuts() {
    document.addEventListener('keydown', (e) => {
      if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
        e.preventDefault()
        this.#openDialog?.()
      }
    })
  }

  /** Initialize the search overlay, autocomplete, and engine-specific logic. */
  setup() {
    const searchConfig = this.core.config.search
    if (!searchConfig || !searchConfig.type)
      return

    // Initialize engine once
    if (!this.#engine) {
      this.#engine = this.#createEngine(searchConfig.type, searchConfig)
    }

    // Initialize dialog and autocomplete once
    if (!this.#initialized) {
      this.#initialized = true
      this.#initAutosearch()
      this.#initDialog()
      this.#initKeyboardShortcuts()
    }
  }
}
