---@diagnostic disable: undefined-field, missing-fields
-- Regression test for https://github.com/dmtrKovalenko/fff.nvim/issues/389
-- When find_files_in_dir(dir) runs with dir != neovim's cwd, the Rust indexer
-- reports paths relative to `dir`, but the Lua side used to resolve them
-- against neovim's cwd when calling :edit / preview / quickfix — so files
-- opened as phantom buffers and previews showed "No preview available".

local fff_rust = require('fff.rust')
local picker_ui = require('fff.picker_ui.picker_ui')
local file_picker = require('fff.file_picker')

--- Normalise a path so that comparisons work on every OS.
--- Windows complicates things: Rust may return forward-slash paths while
--- vim.fn.resolve uses backslashes, temp paths may contain 8.3 short names
--- (RUNNER~1), and the filesystem is case-insensitive.
--- vim.uv.fs_realpath expands 8.3 names on Windows (unlike vim.fn.resolve).
--- @param p string
--- @return string
local function norm(p)
  -- fs_realpath is the closest Lua equivalent of Rust's std::fs::canonicalize
  -- and expands 8.3 short names on Windows.
  local rp = vim.uv.fs_realpath(p) or vim.fn.fnamemodify(vim.fn.resolve(p), ':p')
  local n = vim.fs.normalize(rp)
  -- Strip trailing slash for consistent comparison
  n = n:gsub('/$', '')
  -- Case-fold on Windows (drive letters, 8.3 short names, etc.)
  if vim.fn.has('win32') == 1 then n = n:lower() end
  return n
end

--- `change_indexing_directory` swaps the picker on a background thread, so the
--- `FILE_PICKER` global may still point at the *old* picker for a moment —
--- `wait_for_initial_scan` on the old picker returns immediately and the
--- search then runs against the new, still-empty index. Poll `health_check`
--- until `base_path` matches the expected dir before waiting on the scan.
local function wait_for_reindex(expected_dir, timeout_ms)
  local expected = norm(expected_dir)
  local deadline = vim.uv.hrtime() + timeout_ms * 1e6
  while vim.uv.hrtime() < deadline do
    local ok, health = pcall(fff_rust.health_check, expected)
    if ok and health and health.file_picker and health.file_picker.base_path then
      if norm(health.file_picker.base_path) == expected then return true end
    end
    vim.wait(20, function() return false end)
  end
  return false
end

local function wait_for_scan(expected_dir, timeout_ms)
  assert.is_true(wait_for_reindex(expected_dir, timeout_ms), 'reindex to ' .. expected_dir .. ' did not complete')
  fff_rust.wait_for_initial_scan(timeout_ms)
end

describe('picker find_files_in_dir path resolution (issue #389)', function()
  local sandbox_root, target_dir, other_cwd, target_filename, second_filename

  before_each(function()
    sandbox_root = vim.fn.tempname()
    target_dir = sandbox_root .. '/target-dir'
    other_cwd = sandbox_root .. '/other-cwd'
    vim.fn.mkdir(target_dir, 'p')
    vim.fn.mkdir(other_cwd, 'p')

    target_filename = 'issue389_target.lua'
    local fd = assert(io.open(target_dir .. '/' .. target_filename, 'w'))
    fd:write('-- issue #389 regression fixture\nreturn true\n')
    fd:close()

    second_filename = 'issue389_second.lua'
    fd = assert(io.open(target_dir .. '/' .. second_filename, 'w'))
    fd:write('-- issue #389 second fixture\nreturn true\n')
    fd:close()

    -- Clear the DirChanged autocmd that a previous test run (e.g. fff_core_spec)
    -- may have installed.  Without this, the :cd below triggers a scheduled
    -- change_indexing_directory(other_cwd) that races with our explicit
    -- change_indexing_directory(target_dir) and overwrites the FILE_PICKER.
    pcall(vim.api.nvim_del_augroup_by_name, 'fff_file_tracking')

    vim.cmd('cd ' .. vim.fn.fnameescape(other_cwd))
    -- Equivalent to require('fff').setup({}) — just seeds vim.g.fff — but
    -- avoids the top-level fff module lookup which plenary's sandboxed
    -- require can miss depending on package.path.
    vim.g.fff = {}
    file_picker.setup()
  end)

  after_each(function()
    pcall(picker_ui.close)
    pcall(fff_rust.stop_background_monitor)
    pcall(fff_rust.cleanup_file_picker)
    if sandbox_root then vim.fn.delete(sandbox_root, 'rf') end
  end)

  it(':edit opens the file inside base_path even when neovim cwd differs', function()
    assert.are_not.equal(norm(target_dir), norm(vim.fn.getcwd()))

    assert.is_true(require('fff.core').change_indexing_directory(target_dir))
    wait_for_scan(target_dir, 10000)

    local items = file_picker.search_files('', nil, nil, nil, nil)
    assert.is_true(#items > 0, 'indexer returned no items for target_dir (norm=' .. norm(target_dir) .. ')')

    local target_item
    for _, item in ipairs(items) do
      if item.name == target_filename then
        target_item = item
        break
      end
    end
    assert.is_not_nil(target_item, 'target fixture file missing from results')
    -- On Windows Rust may use backslash separators; compare just the filename.
    local rel = target_item.relative_path
    assert.are.equal(target_filename, rel:match('[^/\\]+$') or rel)
    assert.is_nil(
      vim.uv.fs_stat(target_item.relative_path),
      'relative_path should not resolve against cwd — if it does, the test fixture is wrong'
    )

    -- Drive the same code path that user hits when pressing <CR>: populate
    -- UI state as open_ui_with_state would and invoke select('edit').
    picker_ui.state.active = true
    picker_ui.state.filtered_items = items
    picker_ui.state.cursor = (function()
      for i, item in ipairs(items) do
        if item.name == target_filename then return i end
      end
      return 1
    end)()
    picker_ui.state.query = ''
    picker_ui.state.mode = nil
    picker_ui.state.location = nil
    picker_ui.state.suggestion_source = nil
    picker_ui.state.selected_files = {}
    picker_ui.state.selected_file_order = {}
    picker_ui.state.selected_items = {}

    picker_ui.select('edit')

    -- select('edit') defers the actual :edit via vim.schedule (see picker_ui.lua)
    -- to let picker float teardown finish before opening the file. Flush here.
    vim.wait(2000, function() return vim.api.nvim_buf_get_name(0) ~= '' end)

    local bufname = vim.api.nvim_buf_get_name(0)
    assert.is_true(bufname ~= '', 'expected :edit to open a buffer with a non-empty name')

    local stat = vim.uv.fs_stat(bufname)
    assert.is_not_nil(stat, 'opened buffer points at a non-existent file: ' .. bufname)

    -- The opened file must be the fixture inside target_dir, not a phantom
    -- file under cwd.
    local expected = norm(target_dir .. '/' .. target_filename)
    local actual = norm(bufname)
    assert.are.equal(expected, actual)
  end)

  it(':edit loads all selected files as listed buffers', function()
    assert.is_true(require('fff.core').change_indexing_directory(target_dir))
    wait_for_scan(target_dir, 10000)

    local items = file_picker.search_files('', nil, nil, nil, nil)
    local selected = {}
    for _, item in ipairs(items) do
      if item.name == target_filename or item.name == second_filename then table.insert(selected, item) end
    end
    assert.are.equal(2, #selected, 'expected both fixture files in picker results')

    picker_ui.state.active = true
    picker_ui.state.filtered_items = items
    picker_ui.state.cursor = 1
    picker_ui.state.query = ''
    picker_ui.state.mode = nil
    picker_ui.state.location = nil
    picker_ui.state.suggestion_source = nil
    picker_ui.state.selected_files = {}
    picker_ui.state.selected_file_order = {}
    picker_ui.state.selected_items = {}

    for _, item in ipairs(selected) do
      picker_ui.state.selected_files[item.relative_path] = true
      table.insert(picker_ui.state.selected_file_order, item.relative_path)
    end

    picker_ui.select('edit')

    local expected_first = norm(target_dir .. '/' .. selected[1].name)
    vim.wait(2000, function() return norm(vim.api.nvim_buf_get_name(0)) == expected_first end)
    assert.are.equal(expected_first, norm(vim.api.nvim_buf_get_name(0)))

    for _, item in ipairs(selected) do
      local path = norm(target_dir .. '/' .. item.name)
      local bufnr = vim.fn.bufnr(path)
      assert.is_true(bufnr > 0, 'expected selected file to have a buffer: ' .. path)
      assert.are.equal(1, vim.fn.buflisted(bufnr), 'expected selected file to be listed: ' .. path)
    end
  end)
end)
