import fuzzysort from "fuzzysort"
import { firstBy } from "remeda"
import { createMemo, createResource } from "solid-js"
import type { BoxRenderable } from "@opentui/core"
import { useSDK } from "@tui/context/sdk"
import { useSync } from "@tui/context/sync"
import { Locale } from "@/util/locale"
import type { PromptInfo } from "./history"
import { useFrecency } from "./frecency"
import { removeLineRange, extractLineRange, createFilePart } from "./autocomplete-utils"
import type { AutocompleteOption, AutocompleteStore } from "./autocomplete-types"

type InsertPartFn = (text: string, part: PromptInfo["parts"][number]) => void

export function useAutocompleteOptions(
  store: AutocompleteStore,
  search: () => string,
  anchor: () => BoxRenderable,
  insertPart: InsertPartFn,
  syncDir: string,
) {
  const sdk = useSDK()
  const sync = useSync()
  const frecency = useFrecency()

  function createFilePartLocal(item: string, lineRange?: { startLine: number; endLine?: number }) {
    return createFilePart(item, syncDir, lineRange)
  }

  const [files] = createResource(
    () => search(),
    async (query) => {
      if (!store.visible || store.visible === "/") return []
      const { lineRange, baseQuery } = extractLineRange(query ?? "")
      const result = await sdk.client.find.files({ query: baseQuery })
      const options: AutocompleteOption[] = []
      if (!result.error && result.data) {
        const sortedFiles = result.data.sort((a, b) => {
          const aScore = frecency.getFrecency(a)
          const bScore = frecency.getFrecency(b)
          if (aScore !== bScore) return bScore - aScore
          const aDepth = a.split("/").length
          const bDepth = b.split("/").length
          if (aDepth !== bDepth) return aDepth - bDepth
          return a.localeCompare(b)
        })
        const width = anchor().width - 4
        options.push(
          ...sortedFiles.map((item): AutocompleteOption => {
            const { filename, part } = createFilePartLocal(item, lineRange)
            const isDir = item.endsWith("/")
            return {
              display: Locale.truncateMiddle(filename, width),
              value: filename,
              isDirectory: isDir,
              path: item,
              onSelect: () => insertPart(filename, part),
            }
          }),
        )
      }
      return options
    },
    { initialValue: [] },
  )

  const mcpResources = createMemo(() => {
    if (!store.visible || store.visible === "/") return []
    const options: AutocompleteOption[] = []
    for (const res of Object.values(sync.data.mcp_resource)) {
      const text = `${res.name} (${res.uri})`
      options.push({
        display: text,
        value: text,
        description: res.description,
        onSelect: () => {
          insertPart(res.name, {
            type: "file",
            mime: res.mimeType ?? "text/plain",
            filename: res.name,
            url: res.uri,
            source: { type: "resource", text: { start: 0, end: 0, value: "" }, clientName: res.client, uri: res.uri },
          })
        },
      })
    }
    return options
  })

  const agents = createMemo(() => {
    return sync.data.agent
      .filter((agent) => !agent.hidden && agent.mode !== "primary")
      .map((agent): AutocompleteOption => ({
        display: "@" + agent.name,
        onSelect: () => {
          insertPart(agent.name, {
            type: "agent",
            name: agent.name,
            source: { start: 0, end: 0, value: "" },
          })
        },
      }))
  })

  const commands = createMemo((): AutocompleteOption[] => {
    const results: AutocompleteOption[] = []
    for (const serverCommand of sync.data.command) {
      if (serverCommand.source === "skill") continue
      const label = serverCommand.source === "mcp" ? ":mcp" : ""
      results.push({
        display: "/" + serverCommand.name + label,
        description: serverCommand.description,
        onSelect: () => {},
      })
    }
    results.sort((a, b) => a.display.localeCompare(b.display))
    const max = firstBy(results, [(x) => x.display.length, "desc"])?.display.length
    if (!max) return results
    return results.map((item) => ({ ...item, display: item.display.padEnd(max + 2) }))
  })

  const options = createMemo((prev: AutocompleteOption[] | undefined) => {
    const filesValue = files()
    const mixed: AutocompleteOption[] =
      store.visible === "@" ? [...agents(), ...(filesValue || []), ...mcpResources()] : [...commands()]
    const searchValue = search()
    if (!searchValue) return mixed
    if (files.loading && prev && prev.length > 0) return prev
    const result = fuzzysort.go(removeLineRange(searchValue), mixed, {
      keys: [
        (obj) => removeLineRange((obj.value ?? obj.display).trimEnd()),
        "description",
        (obj) => obj.aliases?.join(" ") ?? "",
      ],
      limit: 10,
      scoreFn: (objResults) => {
        const displayResult = objResults[0]
        let score = objResults.score
        if (displayResult && displayResult.target.startsWith(store.visible + searchValue)) score *= 2
        const frecencyScore = objResults.obj.path ? frecency.getFrecency(objResults.obj.path) : 0
        return score * (1 + frecencyScore)
      },
    })
    return result.map((arr) => arr.obj)
  })

  return { files, options }
}
