---@diagnostic disable: undefined-field, missing-fields
local fff = require('fff')
local fff_rust = require('fff.rust')
local file_picker = require('fff.file_picker')

local plugin_dir = vim.fn.fnamemodify(vim.fn.resolve(debug.getinfo(1, 'S').source:sub(2)), ':h:h')
local log_file = vim.fs.normalize(plugin_dir .. '/fff-test.log')

local ok, session_log = pcall(require('fff.rust').init_tracing, log_file, 'trace')
if not ok then session_log = nil end

-- comment to get a local log of this file
vim.api.nvim_create_autocmd('VimLeavePre', {
  once = true,
  callback = function()
    if os.getenv('CI') == nil and session_log then pcall(vim.fn.delete, session_log) end
  end,
})

local function init_picker_at_plugin_dir(timeout_ms)
  fff_rust.init_file_picker(plugin_dir)
  vim.wait(100, function() return false end)
  fff_rust.wait_for_initial_scan(timeout_ms or 30000)
end

local function find_result_by_name(items, name)
  for _, item in ipairs(items) do
    if item.name == name then return item end
  end
  return nil
end

describe('programmatic search APIs', function()
  describe('against the actual fff.nvim repo', function()
    before_each(function()
      pcall(vim.api.nvim_del_augroup_by_name, 'fff_file_tracking')
      vim.g.fff = {}
      file_picker.setup()
      init_picker_at_plugin_dir()
    end)

    after_each(function()
      pcall(fff_rust.stop_background_monitor)
      pcall(fff_rust.cleanup_file_picker)
      vim.g.fff = nil
    end)

    describe('file_search', function()
      it('defaults to mode=files and finds this very test file', function()
        local result = fff.file_search('programmatic_search_spec')

        assert.is_table(result)
        assert.is_table(result.items)
        assert.is_true(#result.items > 0, 'expected at least one match')
        assert.is_number(result.total_matched)
        assert.is_number(result.total_files)

        local hit = find_result_by_name(result.items, 'programmatic_search_spec.lua')
        assert.is_not_nil(hit, 'this spec file should appear in its own search results')
        ---@cast hit -nil
        assert.are.equal('file', hit.type)
        assert.is_string(hit.relative_path)
        local normalized = vim.fs.normalize(hit.relative_path)
        assert.is_true(
          normalized:find('tests/', 1, true) ~= nil,
          'expected relative_path under tests/, got ' .. tostring(hit.relative_path)
        )
        assert.is_number(hit.size)
      end)

      it('mode=directories finds the lua/fff/file_picker directory', function()
        local result = fff.file_search('file_picker', { mode = 'directories' })
        assert.is_true(#result.items > 0, 'expected at least one directory match')

        local lua_dir
        for _, item in ipairs(result.items) do
          if item.name == 'file_picker' and vim.fs.normalize(item.relative_path):find('lua/fff/', 1, true) then
            lua_dir = item
            break
          end
        end
        assert.is_not_nil(lua_dir, 'lua/fff/file_picker/ missing from directory results')
        assert.are.equal('directory', lua_dir.type)
        assert.is_nil(lua_dir.size, 'DirItem must not carry file-only fields')
        assert.is_nil(lua_dir.is_binary)
      end)

      it('mode=mixed returns both files and directories with type tags', function()
        local result = fff.file_search('file_picker', { mode = 'mixed' })
        assert.is_true(#result.items > 0, 'mixed search returned nothing')
        assert.is_number(result.total_files)
        assert.is_number(result.total_dirs)

        local seen_file, seen_dir = false, false
        for _, item in ipairs(result.items) do
          if item.type == 'file' then seen_file = true end
          if item.type == 'directory' then seen_dir = true end
        end
        assert.is_true(seen_file, 'mixed search did not return any files for "file_picker"')
        assert.is_true(seen_dir, 'mixed search did not return any directories for "file_picker"')
      end)

      it('rejects invalid mode', function()
        assert.has_error(function() fff.file_search('main', { mode = 'bogus' }) end)
      end)
    end)

    describe('content_search', function()
      -- Grep for an identifier that we own and is unlikely to disappear:
      -- `canonicalize_fff_path` is exported on `fff.utils` and consumed
      -- by both `picker_ui.lua` and `main.lua`, so it should appear in at
      -- least 2 different files.
      local marker = 'canonicalize_fff_path'

      it('defaults to plain mode and finds the marker', function()
        local result = fff.content_search(marker)
        assert.is_table(result)
        assert.is_true(#result.items > 0, 'plain content_search returned no matches for ' .. marker)

        local seen_files = {}
        for _, item in ipairs(result.items) do
          assert.is_string(item.relative_path)
          assert.is_number(item.line_number)
          assert.is_string(item.line_content)
          assert.is_true(item.line_content:find(marker, 1, true) ~= nil, 'matched line missing the marker')
          seen_files[item.relative_path] = true
        end
        local count = 0
        for _ in pairs(seen_files) do
          count = count + 1
        end
        assert.is_true(count >= 2, 'expected the marker in at least 2 files, got ' .. count)
      end)

      it('regex mode matches a pattern', function()
        local result = fff.content_search('canonicalize_\\w+', { mode = 'regex' })
        assert.is_true(#result.items > 0, 'regex content_search returned no matches')
        assert.is_nil(result.regex_fallback_error, 'regex compilation should not have fallen back')
      end)

      it('fuzzy mode tolerates query typos', function()
        -- Drop a letter from the marker; fuzzy mode should still match.
        local result = fff.content_search('canonicalize_pickr_path', { mode = 'fuzzy' })
        assert.is_true(#result.items > 0, 'fuzzy content_search returned no matches')
        for _, item in ipairs(result.items) do
          assert.is_number(item.fuzzy_score)
        end
      end)

      it('rejects invalid mode', function()
        assert.has_error(function() fff.content_search('foo', { mode = 'bogus' }) end)
      end)
    end)
  end)

  describe('cwd switching', function()
    -- These specifically prove the picker can swap to a directory it has
    -- never indexed, so they need an isolated throwaway sandbox by design.
    local sandbox_root

    before_each(function()
      pcall(vim.api.nvim_del_augroup_by_name, 'fff_file_tracking')
      vim.g.fff = {}
      file_picker.setup()
      init_picker_at_plugin_dir()
    end)

    after_each(function()
      pcall(fff_rust.stop_background_monitor)
      pcall(fff_rust.cleanup_file_picker)
      if sandbox_root then vim.fn.delete(sandbox_root, 'rf') end
      sandbox_root = nil
      vim.g.fff = nil
    end)

    it('file_search switches the indexed root and waits for the new scan', function()
      sandbox_root = vim.fn.tempname() .. '_other'
      local other_filename = 'totally_unique_other.lua'
      vim.fn.mkdir(sandbox_root, 'p')
      local fd = assert(io.open(sandbox_root .. '/' .. other_filename, 'w'))
      fd:write('-- only lives in the other sandbox\n')
      fd:close()

      -- Sanity: the file does not exist in the primary (fff.nvim) index.
      local before = fff.file_search(other_filename)
      assert.are.equal(0, #before.items, 'sandbox file leaked into primary fff.nvim index')

      local result = fff.file_search(other_filename, { cwd = sandbox_root })
      assert.is_true(#result.items > 0, 'cwd switch did not surface file from the new root')
      local hit = find_result_by_name(result.items, other_filename)
      assert.is_not_nil(hit, 'expected file from the new cwd missing from results')
    end)

    it('file_search returns an empty result for a non-existent cwd', function()
      local missing = vim.fn.tempname() .. '_does_not_exist'
      local result = fff.file_search('main', { cwd = missing })
      assert.are.equal(0, #result.items)
      assert.are.equal(0, result.total_matched)
    end)

    it('content_search switches indexed root before grepping', function()
      sandbox_root = vim.fn.tempname() .. '_other_grep'
      vim.fn.mkdir(sandbox_root, 'p')
      -- Build the marker by concatenation so the literal string doesn't
      -- appear anywhere in the fff.nvim tree (otherwise the "before" grep
      -- would find this very test file via its own marker constant).
      local marker = 'isolated_grep' .. '_marker_xyzzy'
      local fd = assert(io.open(sandbox_root .. '/grep_target.lua', 'w'))
      fd:write('-- ' .. marker .. '\n')
      fd:close()

      -- on windows metadata cache is updated lazily if we programmatically start searching
      -- the new directory right after we update it we can sometimes see a race window especially on CI
      -- this is fine in practice cause this is not a real use case for fff to search RIGHT AFTER mkdir
      if vim.fn.has('win32') == 1 then vim.wait(250, function() return false end) end

      -- Marker must not exist anywhere in the primary fff.nvim tree.
      local before = fff.content_search(marker)
      assert.are.equal(0, #before.items, 'marker leaked into primary fff.nvim tree')

      local result = fff.content_search(marker, { cwd = sandbox_root })
      assert.is_true(#result.items > 0, 'cwd switch did not surface match from the new root')
    end)
  end)
end)
