import type { BoxRenderable, ScrollBoxRenderable, TextareaRenderable } from "@opentui/core"
import fuzzysort from "fuzzysort"
import { createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js"
import { createStore } from "solid-js/store"
import { useTerminalDimensions } from "@opentui/solid"
import { useCommandDialog } from "@tui/component/dialog-command"
import type { ClawAutocompleteRef, ClawSlashOption } from "./autocomplete-types"
export type { ClawAutocompleteRef, ClawSlashOption } from "./autocomplete-types"
import { createAutocompleteRef } from "./autocomplete-ref"
import { AutocompletePopup } from "./autocomplete-popup"

export function ClawAutocomplete(props: {
  value: string
  slashes: () => ClawSlashOption[]
  anchor: () => BoxRenderable | undefined
  input: () => TextareaRenderable | undefined
  ref: (ref: ClawAutocompleteRef) => void
}) {
  const command = useCommandDialog()
  const dimensions = useTerminalDimensions()

  const [store, setStore] = createStore({
    index: 0,
    selected: 0,
    visible: false as boolean,
  })

  const [positionTick, setPositionTick] = createSignal(0)

  createEffect(() => {
    if (!store.visible) return
    let last = { x: 0, y: 0, width: 0 }
    const interval = setInterval(() => {
      const a = props.anchor()
      if (!a) return
      if (a.x !== last.x || a.y !== last.y || a.width !== last.width) {
        last = { x: a.x, y: a.y, width: a.width }
        setPositionTick((t) => t + 1)
      }
    }, 50)
    onCleanup(() => clearInterval(interval))
  })

  const position = createMemo(() => {
    if (!store.visible) return { x: 0, y: 0, width: 0 }
    dimensions()
    positionTick()
    const anchor = props.anchor()
    if (!anchor) return { x: 0, y: 0, width: 0 }
    const parent = anchor.parent
    const parentX = parent?.x ?? 0
    const parentY = parent?.y ?? 0
    return {
      x: anchor.x - parentX,
      y: anchor.y - parentY,
      width: anchor.width,
    }
  })

  const filter = createMemo(() => {
    if (!store.visible) return ""
    const inp = props.input()
    if (!inp) return ""
    props.value
    return inp.getTextRange(store.index + 1, inp.cursorOffset)
  })

  const [search, setSearch] = createSignal("")
  createEffect(() => {
    setSearch(filter())
  })

  const options = createMemo<ClawSlashOption[]>(() => {
    const all = props.slashes()
    const q = search()
    if (!q) return all
    const result = fuzzysort.go(q, all, {
      keys: [(o) => o.display.trimEnd(), "description", (o) => o.aliases?.join(" ") ?? ""],
      limit: 10,
      scoreFn: (results) => {
        const first = results[0]
        let score = results.score
        if (first && first.target.startsWith("/" + q)) score *= 2
        return score
      },
    })
    return result.map((r) => r.obj)
  })

  createEffect(() => {
    filter()
    setStore("selected", 0)
  })

  let scroll: ScrollBoxRenderable | undefined

  function move(direction: -1 | 1) {
    if (!store.visible) return
    const list = options()
    if (!list.length) return
    let next = store.selected + direction
    if (next < 0) next = list.length - 1
    if (next >= list.length) next = 0
    moveTo(next)
  }

  function moveTo(next: number) {
    setStore("selected", next)
    if (!scroll) return
    const viewportHeight = Math.min(height(), options().length)
    const scrollBottom = scroll.scrollTop + viewportHeight
    if (next < scroll.scrollTop) {
      scroll.scrollBy(next - scroll.scrollTop)
    } else if (next + 1 > scrollBottom) {
      scroll.scrollBy(next + 1 - scrollBottom)
    }
  }

  function select() {
    const selected = options()[store.selected]
    if (!selected) return
    const inp = props.input()
    if (inp) {
      const cursor = inp.logicalCursor
      inp.deleteRange(0, 0, cursor.row, cursor.col)
    }
    hide()
    selected.onSelect()
  }

  function show() {
    command.keybinds(false)
    setStore({
      visible: true,
      index: props.input()?.cursorOffset ?? 0,
    })
  }

  function dismiss() {
    if (!store.visible) return
    command.keybinds(true)
    setStore("visible", false)
  }

  function hide() {
    dismiss()
  }

  const height = createMemo(() => {
    const count = options().length || 1
    if (!store.visible) return Math.min(10, count)
    positionTick()
    const a = props.anchor()
    return Math.min(10, count, Math.max(1, a?.y ?? 1))
  })

  onMount(() => {
    props.ref(
      createAutocompleteRef({
        store,
        setStore,
        options,
        input: props.input,
        value: () => props.value,
        show,
        hide,
        select,
        move,
        dismiss,
        keybinds: command.keybinds,
      }),
    )

    onCleanup(() => {
      if (store.visible) command.keybinds(true)
    })
  })

  return (
    <AutocompletePopup
      visible={store.visible}
      options={options()}
      selected={store.selected}
      position={position()}
      height={height()}
      moveTo={moveTo}
      select={select}
      scrollRef={(r) => { scroll = r }}
    />
  )
}
