local M = {}

M.state = { initialized = false }

--- Setup the file picker with the given configuration
--- @param config table Configuration options
function M.setup(config) vim.g.fff = config end

--- Find files in current directory.
--- When opts.resume is true, resumes the last find_files picker (or opens a new one if none saved).
--- When opts.on_submit is set, it replaces the default `:edit` action on user selection.
--- Signature: `fun(item: table, ctx: { action: string, path: string, relative_path: string, location: table|nil, query: string, mode: string|nil })`.
--- @param opts? table Optional configuration {renderer = custom_renderer, resume = boolean, on_submit = function}
function M.find_files(opts)
  local picker_ok, picker_ui = pcall(require, 'fff.picker_ui.picker_ui')
  if not picker_ok then
    vim.notify('Failed to load picker UI: ' .. picker_ui, vim.log.levels.ERROR)
    return
  end

  if opts and opts.resume then
    picker_ui.resume_find_files(opts)
    return
  end

  picker_ui.open(opts)
end

--- Live grep: search file contents in the current directory.
--- When opts.resume is true, resumes the last live_grep picker (or opens a new one if none saved).
--- @param opts? {cwd?: string, title?: string, prompt?: string, layout?: table, grep?: {max_file_size?: number, smart_case?: boolean, max_matches_per_file?: number, modes?: string[]}, query?: string, resume?: boolean} Optional configuration overrides
function M.live_grep(opts)
  local picker_ok, picker_ui = pcall(require, 'fff.picker_ui.picker_ui')
  if not picker_ok then
    vim.notify('Failed to load picker UI: ' .. picker_ui, vim.log.levels.ERROR)
    return
  end

  if opts and opts.resume then
    picker_ui.resume_live_grep(opts)
    return
  end

  local config = require('fff.conf').get()
  local grep_renderer = require('fff.picker_ui.grep_renderer')

  local grep_config = vim.tbl_deep_extend('force', config.grep or {}, (opts and opts.grep) or {})

  local picker_opts = vim.tbl_deep_extend('force', {
    title = 'Live Grep',
    mode = 'grep',
    renderer = grep_renderer,
    grep_config = grep_config,
  }, opts or {})

  picker_ui.open(picker_opts)
end

--- Live grep prefilled with the current word (normal mode) or the visual selection (visual mode).
--- @param opts? table Forwarded to `live_grep`; `query` is overwritten by the resolved text.
function M.live_grep_under_cursor(opts)
  local mode = vim.fn.mode()
  local query
  if mode == 'v' or mode == 'V' or mode == '\22' then
    -- Exit visual so '< / '> marks settle, then read the range directly —
    -- no yank, no register clobber.
    vim.cmd('normal! ' .. vim.api.nvim_replace_termcodes('<Esc>', true, false, true))
    local s = vim.fn.getpos("'<")
    local e = vim.fn.getpos("'>")
    local lines = vim.fn.getregion(s, e, { type = mode })
    query = table.concat(lines, ' ')
  else
    query = vim.fn.expand('<cword>')
  end

  opts = vim.tbl_deep_extend('force', opts or {}, { query = query })
  M.live_grep(opts)
end

--- Changes the directory indexed by the file picker to the git root and opens the file picker
--- @deprecated Use `find_files` instead
function M.find_in_git_root()
  local fuzzy = require('fff.core').ensure_initialized()
  local ok, git_root = pcall(fuzzy.get_git_root)

  if not ok or not git_root then
    vim.notify('Not in a git repository', vim.log.levels.WARN)
    return
  end

  M.find_files_in_dir(git_root)
end

--- Clear FFF caches (both in-memory state and on-disk database files)
--- @param scope? string Cache scope: all|frecency|files
function M.clear_cache(scope)
  local fuzzy = require('fff.fuzzy')
  if not scope or scope == '' then scope = 'all' end

  local errors = {}

  if scope == 'all' or scope == 'files' then
    local ok, err = pcall(fuzzy.cleanup_file_picker)
    if not ok then table.insert(errors, 'cleanup file picker: ' .. tostring(err)) end
  end

  if scope == 'all' or scope == 'frecency' then
    local ok, err = pcall(fuzzy.destroy_frecency_db)
    if not ok then table.insert(errors, 'destroy frecency db: ' .. tostring(err)) end

    ok, err = pcall(fuzzy.destroy_query_db)
    if not ok then table.insert(errors, 'destroy query db: ' .. tostring(err)) end
  end

  if #errors > 0 then
    vim.notify('FFF: errors clearing cache: ' .. table.concat(errors, '; '), vim.log.levels.ERROR)
    return false
  end

  vim.notify('Cleared FFF cache: ' .. scope, vim.log.levels.INFO)
  return true
end

--- Trigger rescan of files in the current directory
function M.scan_files()
  local fuzzy = require('fff.core').ensure_initialized()
  local ok = pcall(fuzzy.scan_files)
  if not ok then vim.notify('Failed to scan files', vim.log.levels.ERROR) end
end

--- Refresh git status for the active file lock
function M.refresh_git_status()
  local fuzzy = require('fff.core').ensure_initialized()
  local ok, updated_files_count = pcall(fuzzy.refresh_git_status)
  if ok then
    vim.notify('Refreshed git status for ' .. tostring(updated_files_count) .. ' files', vim.log.levels.INFO)
  else
    vim.notify('Failed to refresh git status', vim.log.levels.ERROR)
  end
end

--- Search files programmatically
--- @param query string Search query
--- @param max_results number Maximum number of results
--- @return table List of matching files
function M.search(query, max_results)
  local fuzzy = require('fff.core').ensure_initialized()
  local config = require('fff.conf').get()
  max_results = max_results or config.max_results
  local max_threads = config.max_threads or 4
  local combo_boost_score_multiplier = config.history and config.history.combo_boost_score_multiplier or 100
  local min_combo_count = config.history and config.history.min_combo_count or 3
  -- Args: query, max_threads, current_file, combo_boost_score_multiplier, min_combo_count, offset, page_size
  local ok, search_result = pcall(
    fuzzy.fuzzy_search_files,
    query,
    max_threads,
    nil,
    combo_boost_score_multiplier,
    min_combo_count,
    0,
    max_results
  )
  if ok and search_result.items then return search_result.items end
  return {}
end

--- @class fff.FileSearchOpts
--- @field mode? "files"|"directories"|"mixed" Item type to search (default: 'files').
--- @field max_results? number Max items per page (default: config.max_results).
--- @field page? number 0-based page index (default: 0).
--- @field current_file? string Path to deprioritize (default: nil).
--- @field max_threads? number Worker threads (default: config.max_threads).
--- @field combo_boost_score_multiplier? number Override history combo boost.
--- @field min_combo_count? number Override history min_combo_count.
--- @field cwd? string If set and different from the current indexed root, switch the index to this directory before searching. Implies waiting for the new scan unless `wait_for_index_ms = 0`.
--- @field wait_for_index_ms? number Block up to this many ms for the index to be ready (default: 10000 when `cwd` triggers a re-index, 0 otherwise). Set to 0 to never block.

--- Switch the indexed root if `cwd` is set and different from the current
--- `base_path`, then optionally block until the new scan completes.
--- Returns `true` when the index is ready (or no wait requested), or `false`
--- with an error message on timeout / invalid cwd.
--- @param cwd string|nil
--- @param wait_for_index_ms number|nil
--- @return boolean ok, string? err
local function ensure_indexed(cwd, wait_for_index_ms)
  -- ensure_initialized is idempotent; first call kicks off the initial scan
  -- at config.base_path.
  require('fff.core').ensure_initialized()
  local config = require('fff.conf').get()

  local is_windows = vim.fn.has('win32') == 1
  local function canon(p)
    if not p or p == '' then return '' end
    local abs = vim.fn.fnamemodify(vim.fn.expand(p), ':p')
    abs = (abs:gsub('[/\\]+$', ''))
    -- fs_realpath resolves Windows 8.3 short names (RUNNER~1 -> runneradmin)
    -- so picker base_path (canonicalized in rust) compares equal to the cwd
    -- argument. fnamemodify(':p') alone keeps the short form on Windows.
    local realpath_ok, realpath = pcall(vim.uv.fs_realpath, abs)
    if realpath_ok and realpath then abs = realpath end
    local normalized = vim.fs.normalize(abs)
    if is_windows then normalized = normalized:lower() end
    return normalized
  end

  local cwd_triggered_reindex = false

  if cwd and cwd ~= '' then
    local expanded = vim.fn.expand(cwd)
    if vim.fn.isdirectory(expanded) ~= 1 then return false, 'cwd does not exist: ' .. expanded end

    if canon(config.base_path) ~= canon(expanded) then
      if not require('fff.core').change_indexing_directory(expanded) then
        return false, 'failed to change indexing directory to ' .. expanded
      end
      cwd_triggered_reindex = true
    end
  end

  -- Default: only wait when cwd actually swapped the picker. Callers can
  -- pass wait_for_index_ms explicitly to force a wait on first-time init too.
  local wait_ms = wait_for_index_ms
  if wait_ms == nil then wait_ms = cwd_triggered_reindex and 10000 or 0 end
  if wait_ms <= 0 then return true end

  local fff_rust = require('fff.rust')

  -- The picker swap runs on a background thread; wait_for_scan reads the
  -- picker pointer once at entry, so polling health_check first guarantees
  -- we wait on the new picker rather than racing the old one.
  if cwd_triggered_reindex then
    local target = canon(cwd)
    local deadline = vim.uv.hrtime() + wait_ms * 1e6
    local matched = false
    while vim.uv.hrtime() < deadline do
      local ok, health = pcall(fff_rust.health_check, target)
      if ok and health and health.file_picker and health.file_picker.base_path then
        if canon(health.file_picker.base_path) == target then
          matched = true
          break
        end
      end
      vim.wait(20, function() return false end)
    end
    if not matched then return false, 'timeout waiting for re-index swap' end
    -- Subtract the time we spent polling so the scan wait stays bounded.
    local remaining = math.max(0, math.floor((deadline - vim.uv.hrtime()) / 1e6))
    if remaining == 0 then return false, 'timeout waiting for index scan' end
    wait_ms = remaining
  end

  local scan_ok = require('fff.file_picker').wait_for_initial_scan(wait_ms)
  if not scan_ok then return false, 'timeout waiting for index scan' end
  return true
end

--- Programmatic file search.
--- Returns the full structured result so callers can read scores, totals,
--- and (for `files`/`mixed` modes) the parsed `location`.
---
--- For `mixed` mode each item has a `type` field of `"file"` or `"directory"`.
--- @param query string Search query (constraint syntax supported)
--- @param opts? fff.FileSearchOpts
--- @return { items: table[], scores: table[], total_matched: number, total_files?: number, total_dirs?: number, location?: table }
function M.file_search(query, opts)
  vim.validate({
    query = { query, 'string' },
    opts = { opts, 'table', true },
  })
  opts = opts or {}

  local indexed_ok, err = ensure_indexed(opts.cwd, opts.wait_for_index_ms)
  if not indexed_ok then
    vim.notify('FFF file_search: ' .. err, vim.log.levels.ERROR)
    return { items = {}, scores = {}, total_matched = 0 }
  end

  local fuzzy = require('fff.fuzzy')
  local config = require('fff.conf').get()
  local mode = opts.mode or 'files'
  local max_threads = opts.max_threads or config.max_threads or 4
  local page_size = opts.max_results or config.max_results or 100
  local page_index = opts.page or 0
  local current_file = opts.current_file
  local combo_boost = opts.combo_boost_score_multiplier
    or (config.history and config.history.combo_boost_score_multiplier)
    or 100
  local min_combo = opts.min_combo_count or (config.history and config.history.min_combo_count) or 3

  local empty = { items = {}, scores = {}, total_matched = 0 }
  if mode == 'files' then
    local offset = page_index * page_size
    local ok, result =
      pcall(fuzzy.fuzzy_search_files, query, max_threads, current_file, combo_boost, min_combo, offset, page_size)
    if not ok then
      vim.notify('FFF file_search failed: ' .. tostring(result), vim.log.levels.ERROR)
      return empty
    end
    return result
  elseif mode == 'directories' then
    local ok, result = pcall(fuzzy.fuzzy_search_directories, query, max_threads, current_file, page_index, page_size)
    if not ok then
      vim.notify('FFF file_search(directories) failed: ' .. tostring(result), vim.log.levels.ERROR)
      return empty
    end
    return result
  elseif mode == 'mixed' then
    local ok, result =
      pcall(fuzzy.fuzzy_search_mixed, query, max_threads, current_file, combo_boost, min_combo, page_index, page_size)
    if not ok then
      vim.notify('FFF file_search(mixed) failed: ' .. tostring(result), vim.log.levels.ERROR)
      return empty
    end
    return result
  else
    error("fff.file_search: opts.mode must be 'files', 'directories', or 'mixed', got " .. tostring(mode))
  end
end

--- @class fff.ContentSearchOpts
--- @field mode? "plain"|"regex"|"fuzzy" Grep mode (default: 'plain').
--- @field max_file_size? number Skip files larger than N bytes (default: config.grep.max_file_size).
--- @field max_matches_per_file? number Cap matches per file, 0 = unlimited (default: config.grep.max_matches_per_file).
--- @field smart_case? boolean Case-insensitive when query is all lowercase (default: config.grep.smart_case).
--- @field page_size? number Max matches returned (default: 50).
--- @field file_offset? number File-based pagination offset (default: 0).
--- @field time_budget_ms? number Max wall-clock time, 0 = unlimited (default: config.grep.time_budget_ms).
--- @field trim_whitespace? boolean Strip leading whitespace from matched lines (default: config.grep.trim_whitespace).
--- @field cwd? string Switch indexed root before grepping (same semantics as `file_search`).
--- @field wait_for_index_ms? number Block up to this many ms for the index to be ready.

--- Programmatic content (grep) search.
--- Returns the full structured `GrepResult` (items, totals, regex fallback).
--- @param query string Grep query (`*.rs pattern`, glob constraints, etc. supported)
--- @param opts? fff.ContentSearchOpts
--- @return { items: table[], total_matched: number, total_files_searched: number, total_files: number, filtered_file_count: number, next_file_offset: number, regex_fallback_error?: string }
function M.content_search(query, opts)
  vim.validate({
    query = { query, 'string' },
    opts = { opts, 'table', true },
  })
  opts = opts or {}

  local mode = opts.mode or 'plain'
  if mode ~= 'plain' and mode ~= 'regex' and mode ~= 'fuzzy' then
    error("fff.content_search: opts.mode must be 'plain', 'regex', or 'fuzzy', got " .. tostring(mode))
  end

  local empty = {
    items = {},
    total_matched = 0,
    total_files_searched = 0,
    total_files = 0,
    filtered_file_count = 0,
    next_file_offset = 0,
  }

  local indexed_ok, err = ensure_indexed(opts.cwd, opts.wait_for_index_ms)
  if not indexed_ok then
    vim.notify('FFF content_search: ' .. err, vim.log.levels.ERROR)
    return empty
  end

  local config = require('fff.conf').get()
  local grep_cfg = config.grep or {}
  local grep = require('fff.picker_ui.grep_renderer')
  local merged_grep_cfg = {
    max_file_size = opts.max_file_size or grep_cfg.max_file_size,
    max_matches_per_file = opts.max_matches_per_file or grep_cfg.max_matches_per_file,
    smart_case = opts.smart_case == nil and grep_cfg.smart_case or opts.smart_case,
    time_budget_ms = opts.time_budget_ms or grep_cfg.time_budget_ms,
    trim_whitespace = opts.trim_whitespace == nil and grep_cfg.trim_whitespace or opts.trim_whitespace,
  }

  local ok, result = pcall(grep.search, query, opts.file_offset or 0, opts.page_size or 50, merged_grep_cfg, mode)
  if not ok then
    vim.notify('FFF content_search failed: ' .. tostring(result), vim.log.levels.ERROR)
    return empty
  end
  return result
end

--- Search and show results in a nice format
--- @param query string Search query
function M.search_and_show(query)
  if not query or query == '' then
    M.find_files()
    return
  end

  local results = M.search(query, 20)

  if #results == 0 then
    print('🔍 No files found matching "' .. query .. '"')
    return
  end

  -- Filter out directories (should already be done by Rust, but just in case)
  local files = {}
  for _, item in ipairs(results) do
    if not item.is_dir then table.insert(files, item) end
  end

  if #files == 0 then
    print('🔍 No files found matching "' .. query .. '"')
    return
  end

  print('🔍 Found ' .. #files .. ' files matching "' .. query .. '":')

  for i, file in ipairs(files) do
    if i <= 15 then
      local file_extension = vim.fn.fnamemodify(file.name, ':e')
      local icon = file_extension ~= '' and '.' .. file_extension or '📄'
      local frecency = file.total_frecency_score > 0 and ' ⭐' .. file.total_frecency_score or ''
      print('  ' .. i .. '. ' .. icon .. ' ' .. file.relative_path .. frecency)
    end
  end

  if #files > 15 then print('  ... and ' .. (#files - 15) .. ' more files') end

  print('Use :FFFFind to browse all files')
end

--- Get file preview
--- @param file_path string Path to the file
--- @return string|nil File content or nil if failed
function M.get_preview(file_path)
  local preview = require('fff.file_picker.preview')
  local temp_buf = vim.api.nvim_create_buf(false, true)
  local success = preview.preview(file_path, temp_buf)
  if not success then
    vim.api.nvim_buf_delete(temp_buf, { force = true })
    return nil
  end
  local lines = vim.api.nvim_buf_get_lines(temp_buf, 0, -1, false)
  vim.api.nvim_buf_delete(temp_buf, { force = true })
  return table.concat(lines, '\n')
end

--- Find files in a specific directory
--- @param directory string Directory path to search in
function M.find_files_in_dir(directory)
  if not directory then
    vim.notify('Directory path required for find_files_in_dir', vim.log.levels.ERROR)
    return
  end

  local picker_ok, picker_ui = pcall(require, 'fff.picker_ui.picker_ui')
  if picker_ok then
    picker_ui.open({
      title = 'Files in ' .. vim.fn.fnamemodify(directory, ':t'),
      cwd = directory,
    })
  else
    vim.notify('Failed to load picker UI', vim.log.levels.ERROR)
  end
end

--- Change the base directory for the file picker
--- @param new_path string New directory path to use as base
--- @return boolean `true` if successful, `false` otherwise
function M.change_indexing_directory(new_path) return require('fff.core').change_indexing_directory(new_path) end

--- Resume the most recently closed picker (find_files or live_grep).
--- Similar to Telescope's `require('telescope.builtin').resume()`.
---@return boolean true if a picker was resumed, false if there is nothing to resume
function M.resume()
  local picker_ok, picker_ui = pcall(require, 'fff.picker_ui.picker_ui')
  if not picker_ok then
    vim.notify('Failed to load picker UI: ' .. picker_ui, vim.log.levels.ERROR)
    return false
  end
  return picker_ui.resume()
end

-- Strip wrapper punctuation that frequently surrounds paths in prose: leading
-- markdown-link `[`, parens `(`, brackets `<`, quotes; trailing sentence
-- punctuation. We additionally truncate at the first closing wrapper so a
-- cWORD like `[file.lua](./somewhere)` collapses to just `file.lua`. We
-- deliberately keep `:` and digits inside the word so `path:line:col`
-- suffixes survive.
local function strip_path_wrappers(s)
  if not s or s == '' then return s end
  s = s:gsub('^[%(%[%{<"\'`]+', '')
  s = s:gsub('([%)%]%}>"\'`]).*$', '')
  s = s:gsub('[,;!%?]+$', '')
  s = s:gsub('([^%.])%.$', '%1')
  -- Drop a leading `./` or `.\` — purely presentational, but the rust scorer
  -- otherwise can't recognise the path as an exact filename / path match.
  s = s:gsub('^%./', '')
  s = s:gsub('^%.\\', '')
  return s
end

-- Split a `path:line:col` or `path:line` suffix off a path candidate.
-- Returns `(path, location|nil)`.
local function split_location_suffix(s)
  if not s or s == '' then return s, nil end
  local p, l, c = s:match('^(.-):(%d+):(%d+)$')
  if p and p ~= '' then return p, { line = tonumber(l), col = tonumber(c) } end
  local p2, l2 = s:match('^(.-):(%d+)$')
  if p2 and p2 ~= '' then return p2, { line = tonumber(l2) } end
  return s, nil
end

-- Heuristic: only a string with an explicit path separator (or `~`) is treated
-- as "definitely a path" worth resolving directly. Bare names like `foo.lua`
-- still go through the fuzzy picker so frecency / disambiguation can help.
local function looks_like_path(s)
  if not s or s == '' then return false end
  if vim.startswith(s, '~') then return true end
  return s:find('[/\\]') ~= nil
end

-- Resolve `path` to an existing file on disk. Tries (in order): expanded
-- absolute, base_path-relative, cwd-relative. Returns the absolute path on
-- success, otherwise `nil`.
local function resolve_existing_file(path)
  if not path or path == '' then return nil end
  local expanded = vim.fn.expand(path)

  -- Absolute (after ~ expansion): check directly
  if vim.fn.fnamemodify(expanded, ':p') == expanded then
    if vim.fn.filereadable(expanded) == 1 then return expanded end
    return nil
  end

  local seen = {}
  local function try(candidate)
    if not candidate or seen[candidate] then return nil end
    seen[candidate] = true
    if vim.fn.filereadable(candidate) == 1 then return candidate end
    return nil
  end

  local base = require('fff.conf').get().base_path
  if base and base ~= '' then
    local hit = try(vim.fs.normalize(base .. '/' .. expanded))
    if hit then return hit end
  end
  return try(vim.fs.normalize(vim.fn.getcwd() .. '/' .. expanded))
end

-- Open `abs_path` honouring the same window-targeting dance as `M.select`:
-- if the current window is `winfixbuf` / has a special buftype, retarget to
-- a suitable window, else fall back to `:split`. Optionally jumps to a
-- `location = { line, col }` after the buffer loads.
local function open_resolved_file(abs_path, relative_path, location, open_cb)
  local utils = require('fff.utils')
  local cwd_relative = vim.fn.fnamemodify(abs_path, ':.')

  if open_cb and type(open_cb) == 'function' then
    local cb_ok, cb_err = pcall(open_cb, abs_path, relative_path or cwd_relative)
    if not cb_ok then vim.notify('open_file_under_cursor open_cb error: ' .. tostring(cb_err), vim.log.levels.ERROR) end
  end

  local current_win = vim.api.nvim_get_current_win()
  local current_buf = vim.api.nvim_get_current_buf()
  local current_buftype = vim.api.nvim_get_option_value('buftype', { buf = current_buf })
  local current_modifiable = vim.api.nvim_get_option_value('modifiable', { buf = current_buf })
  local current_winfixbuf = utils.window_has_winfixbuf(current_win)

  local opened_via_split = false
  if current_buftype ~= '' or not current_modifiable or current_winfixbuf then
    local suitable_win = utils.find_suitable_window()
    if suitable_win then
      vim.api.nvim_set_current_win(suitable_win)
    elseif current_winfixbuf then
      vim.cmd('split ' .. vim.fn.fnameescape(cwd_relative))
      opened_via_split = true
    end
  end

  if not opened_via_split then vim.cmd('edit ' .. vim.fn.fnameescape(cwd_relative)) end

  if location then vim.schedule(function() require('fff.location_utils').jump_to_location(location) end) end
end

--- Try to open the file/path under the cursor.
---
--- Picks up the `<cWORD>` (whitespace-delimited token) from the current line,
--- strips wrapping punctuation (`[]`, `()`, quotes, trailing `,`/`.`/etc.),
--- and tries to open it. Resolution order:
---
---   1. **Direct path**: if the cWORD looks like a path (has `/`, `\`, or
---      `~`) and resolves to a real file (absolute, or relative to the
---      picker's `base_path`, then to neovim's cwd), open it directly. A
---      `:line:col` suffix is parsed and the cursor jumps to that location.
---      This skips the fuzzy picker entirely — when the user has clearly
---      typed a path, we don't second-guess them.
---   2. **Fuzzy match**: otherwise run a fuzzy search. If exactly one file
---      matches, or the top hit is an exact-path match, open it.
---   3. **Picker UI fallback**: if the cWORD looks like a path but several
---      files match ambiguously, open the picker UI with the cWORD as a
---      starter query.
---   4. **No-op**: if the cWORD is empty or matches nothing (and isn't a
---      resolvable path), do nothing — no surprise UI popup.
---
--- `:edit` is window-aware: if the current window has `winfixbuf` or a
--- special buftype, the file is opened in another suitable window or via
--- `:split`.
---
--- The optional `open_cb` is invoked **before** `:edit` runs with
--- `(absolute_path, relative_path)` — useful for plugins that want to mirror
--- the open into a side panel, log the access, etc.
--- @param open_cb fun(abs_path: string, relative_path: string)|nil
function M.open_file_under_cursor(open_cb)
  local raw_word = vim.fn.expand('<cWORD>')
  local query = strip_path_wrappers(raw_word)
  if not query or query == '' then return end

  -- Fast path: cWORD looks like a path AND resolves on disk → just open it.
  -- This catches the common `gf`-on-`./file_picker.rs` case where the fuzzy
  -- search would otherwise return many substring matches and pop the UI.
  local path_part, location = split_location_suffix(query)
  if looks_like_path(path_part) then
    local resolved = resolve_existing_file(path_part)
    if resolved then
      open_resolved_file(resolved, path_part, location, open_cb)
      return
    end
  end

  local picker_ok, picker_ui = pcall(require, 'fff.picker_ui.picker_ui')
  if not picker_ok then
    vim.notify('Failed to load picker UI', vim.log.levels.ERROR)
    return
  end

  picker_ui.open_with_callback(query, function(files, _, fuzzy_location, get_file_score)
    -- Empty results: don't pop up the picker UI on words that aren't paths.
    if not files or #files == 0 then return true end

    local first_score = get_file_score and get_file_score(1) or nil
    local exact = first_score and first_score.exact_match or false
    if #files ~= 1 and not exact then
      -- Ambiguous: let the picker UI surface the candidates.
      return false
    end

    local utils = require('fff.utils')
    local item = files[1]
    local abs_path = utils.canonicalize_fff_path(item.relative_path)
    if not abs_path then return true end

    open_resolved_file(abs_path, item.relative_path, fuzzy_location, open_cb)
    return true
  end)
end

return M
