export function treeEntries(parent: string, nodes: ReadonlyArray<{ name: string; type: "file" | "directory" }>) {
  const prefix = parent.replace(/^\/+|\/+$/g, "")
  return nodes.map((node) => {
    const path = prefix ? `${prefix}/${node.name}` : node.name
    return node.type === "directory" ? path + "/" : path
  })
}

export function pickerTreeEntries(
  parent: string,
  nodes: ReadonlyArray<{ name: string; type: "file" | "directory" }>,
  mode: "directory" | "file",
) {
  return treeEntries(parent, mode === "directory" ? nodes.filter((node) => node.type === "directory") : nodes)
}

export function pickerSearchEntries<T extends { type: "file" | "directory" }>(
  nodes: readonly T[],
  mode: "directory" | "file",
) {
  return mode === "directory" ? nodes.filter((node) => node.type === "directory") : [...nodes]
}

export function pickerMode(mode: "directory" | "file", base?: string) {
  if (mode === "file") {
    return {
      includeFiles: true,
      action: "file" as const,
      entries(parent: string, nodes: ReadonlyArray<{ name: string; type: "file" | "directory" }>) {
        return treeEntries(parent, nodes)
      },
      navigation(path: string) {
        return treePathWithin(base, path) ? path : undefined
      },
      result(root: string, selected: string) {
        return selected || undefined
      },
      selection(root: string, path: string) {
        if (!treePathWithin(base, root)) return
        return selectedTreePath(root, path, "file", base)
      },
    }
  }
  return {
    includeFiles: false,
    action: "directory" as const,
    entries(parent: string, nodes: ReadonlyArray<{ name: string; type: "file" | "directory" }>) {
      return treeEntries(
        parent,
        nodes.filter((node) => node.type === "directory"),
      )
    },
    navigation(path: string) {
      return path
    },
    result(root: string, selected: string, valid = true) {
      if (!valid) return
      return selected || (root ? nativePickerPath(root) : undefined)
    },
    selection(root: string, path: string) {
      return selectedTreePath(root, path, "directory")
    },
  }
}

export function pickerFileSearchQuery(root: string, input: string, home: string) {
  const value = input
    .replace(/\\/g, "/")
    .replace(/^~(?=\/|$)/, home)
    .replace(/\/+$/, "")
  const base = root.replace(/\\/g, "/").replace(/\/+$/, "")
  if (value === base) return ""
  if (value.startsWith(base + "/")) return value.slice(base.length + 1)
  return value
}

export function pickerAbsoluteInput(input: string, home: string, current: string) {
  const value = normalizePickerDrive(input).replace(/^~(?=\/|$)/, normalizePickerDrive(home))
  const absolute = pickerRoot(value) ? value : joinPickerPath(current, value)
  return canonicalPickerPath(absolute)
}

export function treePathWithin(base: string | undefined, path: string) {
  return pickerRelativePath(base, path) !== undefined
}

export function canonicalPickerPath(path: string) {
  const value = normalizePickerDrive(path)
  const root = pickerRoot(value)
  const parts = value.slice(root.length).split("/")
  const resolved = parts.reduce<string[]>((output, part) => {
    if (!part || part === ".") return output
    if (part === "..") {
      output.pop()
      return output
    }
    output.push(part)
    return output
  }, [])
  return joinPickerPath(root, resolved.join("/"))
}

export function pickerRelativePath(base: string | undefined, path: string) {
  if (!base) return
  const rootPath = canonicalPickerPath(base)
  const targetPath = canonicalPickerPath(path)
  const insensitive = /^[A-Za-z]:\//.test(rootPath) || rootPath.startsWith("//")
  const root = insensitive ? rootPath.toLowerCase() : rootPath
  const target = insensitive ? targetPath.toLowerCase() : targetPath
  if (target === root) return ""
  const prefix = root.endsWith("/") ? root : root + "/"
  if (!target.startsWith(prefix)) return
  return targetPath.slice(prefix.length)
}

export function currentPickerSuggestions<T>(result: { query: string; items: readonly T[] } | undefined, query: string) {
  if (result?.query !== query) return []
  return result.items
}

export function preloadTreeDirectories(
  parent: string,
  nodes: ReadonlyArray<{ name: string; type: "file" | "directory" }>,
) {
  return treeEntries(
    parent,
    nodes.filter((node) => node.type === "directory"),
  )
}

export function advanceTreePreload(advanced: Set<string>, path: string) {
  if (advanced.has(path)) return false
  advanced.add(path)
  return true
}

export function activeTreeNavigation(request: number, current: number) {
  return request === current
}

export function createPriorityTaskQueue<T>(concurrency: number) {
  type Job = {
    key: string
    priority: "user" | "background"
    promise: Promise<T>
    run: () => void
  }

  const jobs = new Map<string, Job>()
  const user: Job[] = []
  const background: Job[] = []
  let active = 0

  const drain = () => {
    while (active < concurrency) {
      const job = user.pop() ?? background.shift()
      if (!job) return
      active++
      job.run()
    }
  }

  const schedule = (key: string, priority: Job["priority"], task: () => Promise<T>) => {
    const existing = jobs.get(key)
    if (existing) {
      if (priority === "user") promote(key)
      return existing.promise
    }

    const deferred = Promise.withResolvers<T>()
    const job: Job = {
      key,
      priority,
      promise: deferred.promise,
      run: () => {
        const complete = () => {
          active--
          jobs.delete(key)
          drain()
        }
        Promise.resolve()
          .then(task)
          .then(
            (value) => {
              complete()
              deferred.resolve(value)
            },
            (error) => {
              complete()
              deferred.reject(error)
            },
          )
      },
    }
    jobs.set(key, job)
    ;(priority === "user" ? user : background).push(job)
    drain()
    return job.promise
  }

  const promote = (key: string) => {
    const job = jobs.get(key)
    if (!job || job.priority === "user") return
    const index = background.indexOf(job)
    if (index === -1) return
    background.splice(index, 1)
    job.priority = "user"
    user.push(job)
  }

  return { schedule, promote }
}

export function nextTreeScrollTop(current: number, delta: number, scrollHeight: number, clientHeight: number) {
  return Math.min(Math.max(0, scrollHeight - clientHeight), Math.max(0, current + delta))
}

export function nextSuggestionIndex(current: number, delta: -1 | 1, count: number) {
  if (count === 0) return -1
  return (current + delta + count) % count
}

export function absoluteTreePath(root: string, path: string) {
  const base = trimPickerPath(root)
  const relative = path.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "")
  if (!relative) return base || "/"
  if (!base || base === "/") return "/" + relative
  if (base.endsWith("/")) return base + relative
  return `${base}/${relative}`
}

export function selectedTreePath(root: string, path: string, mode: "directory" | "file", base?: string) {
  const directory = path.endsWith("/")
  if (mode === "file") {
    if (directory) return
    if (!base) return path
    const absolute = absoluteTreePath(root, path)
    return pickerRelativePath(base, absolute)
  }
  return directory ? nativePickerPath(absoluteTreePath(root, path)) : undefined
}

export function nativePickerPath(path: string) {
  const value = trimPickerPath(path)
  if (/^[A-Za-z]:\//.test(value) || value.startsWith("//")) return value.replaceAll("/", "\\")
  return value
}
import { getFilename } from "@novaclaw/core/util/path"
import fuzzysort from "fuzzysort"
import { ServerSDK } from "@/context/server-sdk"

export function cleanPickerInput(value: string) {
  const first = (value ?? "").split(/\r?\n/)[0] ?? ""
  return first.replace(/[\u0000-\u001F\u007F]/g, "").trim()
}

export function normalizePickerPath(input: string) {
  const value = input.replaceAll("\\", "/")
  if (value.startsWith("//") && !value.startsWith("///")) return "//" + value.slice(2).replace(/\/+/g, "/")
  return value.replace(/\/+/g, "/")
}

export function normalizePickerDrive(input: string) {
  const value = normalizePickerPath(input)
  if (/^[A-Za-z]:$/.test(value)) return value + "/"
  return value
}

export function trimPickerPath(input: string) {
  const value = normalizePickerDrive(input)
  if (value === "/" || value === "//" || /^[A-Za-z]:\/$/.test(value)) return value
  return value.replace(/\/+$/, "")
}

export function joinPickerPath(base: string | undefined, relative: string) {
  const root = trimPickerPath(base ?? "")
  const path = trimPickerPath(relative).replace(/^\/+/, "")
  if (!root) return path
  if (!path) return root
  if (root.endsWith("/")) return root + path
  return root + "/" + path
}

export function pickerRoot(input: string) {
  const value = normalizePickerDrive(input)
  if (value.startsWith("//")) {
    const [server, share] = value.slice(2).split("/")
    if (server && share) return `//${server}/${share}`
    return "//"
  }
  if (value.startsWith("/")) return "/"
  if (/^[A-Za-z]:\//.test(value)) return value.slice(0, 3)
  return ""
}

export function pickerParent(input: string) {
  const value = trimPickerPath(input)
  const root = pickerRoot(value)
  if (value === root) return value
  if (value === "/" || value === "//" || /^[A-Za-z]:\/$/.test(value)) return value
  const index = value.lastIndexOf("/")
  if (index < root.length) return root
  if (index <= 0) return "/"
  if (index === 2 && /^[A-Za-z]:/.test(value)) return value.slice(0, 3)
  return value.slice(0, index)
}

function pickerTilde(absolute: string, home: string) {
  const path = trimPickerPath(absolute)
  if (!home) return ""
  const root = trimPickerPath(home)
  if (/^[A-Za-z]:\//.test(root)) return ""
  if (path === root) return "~"
  if (path.startsWith(root + "/")) return "~" + path.slice(root.length)
  return ""
}

export function displayPickerPath(path: string, input: string, home: string) {
  const value = trimPickerPath(path)
  if (/^[A-Za-z]:\//.test(trimPickerPath(home)) || /^[A-Za-z]:\//.test(value)) return value.replaceAll("/", "\\")
  return pickerTilde(value, home) || value
}

export function createDirectorySearch(args: { sdk: ServerSDK; base: () => string | undefined; home: () => string }) {
  const cache = new Map<string, Promise<Array<{ name: string; absolute: string }>>>()
  let current = 0

  const scoped = (value: string) => {
    const base = args.base()
    if (!base) return
    const raw = normalizePickerDrive(value)
    if (!raw) return { directory: trimPickerPath(base), path: "" }
    const home = args.home()
    if (raw === "~") return { directory: trimPickerPath(home || base), path: "" }
    if (raw.startsWith("~/")) return { directory: trimPickerPath(home || base), path: raw.slice(2) }
    const root = pickerRoot(raw)
    if (root) return { directory: trimPickerPath(root), path: raw.slice(root.length) }
    return { directory: trimPickerPath(base), path: raw }
  }

  const directories = async (directory: string) => {
    const key = trimPickerPath(directory)
    const existing = cache.get(key)
    if (existing) return existing
    const request = args.sdk.client.file
      .list({ directory: key, path: "" })
      .then((result) => result.data ?? [])
      .catch(() => [])
      .then((nodes) =>
        nodes
          .filter((node) => node.type === "directory")
          .map((node) => ({ name: node.name, absolute: trimPickerPath(normalizePickerDrive(node.absolute)) })),
      )
    cache.set(key, request)
    return request
  }

  const match = async (directory: string, query: string, limit: number) => {
    const items = await directories(directory)
    if (!query) return items.slice(0, limit).map((item) => item.absolute)
    return fuzzysort.go(query, items, { key: "name", limit }).map((item) => item.obj.absolute)
  }

  return async (filter: string) => {
    const token = ++current
    const active = () => token === current
    const value = cleanPickerInput(filter)
    const input = scoped(value)
    if (!input) return [] as string[]
    const raw = normalizePickerDrive(value)
    const pathInput = raw.startsWith("~") || !!pickerRoot(raw) || raw.includes("/")
    const query = normalizePickerDrive(input.path)
    if (!pathInput) {
      const results = await args.sdk.client.find
        .files({ directory: input.directory, query, type: "directory", limit: 50 })
        .then((result) => result.data ?? [])
        .catch(() => [])
      if (!active()) return []
      return results.map((path) => joinPickerPath(input.directory, path)).slice(0, 50)
    }
    const segments = query.replace(/^\/+/, "").split("/")
    const head = segments.slice(0, -1).filter((part) => part && part !== ".")
    const tail = segments.at(-1) ?? ""
    let paths = [input.directory]
    for (const part of head) {
      if (!active()) return []
      if (part === "..") {
        paths = paths.map(pickerParent)
        continue
      }
      paths = Array.from(new Set((await Promise.all(paths.map((path) => match(path, part, 4)))).flat())).slice(0, 12)
      if (!active() || paths.length === 0) return []
    }
    const matches = Array.from(new Set((await Promise.all(paths.map((path) => match(path, tail, 50)))).flat()))
    if (!active()) return []
    const base = raw.startsWith("~") ? trimPickerPath(input.directory) : ""
    if (raw.endsWith("/") || !tail) return Array.from(new Set([base, ...matches].filter(Boolean))).slice(0, 50)
    const target = matches.find((path) => getFilename(path).toLowerCase() === tail.toLowerCase())
    if (!target) return matches.slice(0, 50)
    const children = await match(target, "", 30)
    if (!active()) return []
    return Array.from(new Set([base, ...matches, ...children].filter(Boolean))).slice(0, 50)
  }
}
