-- Tests for vim.ui.select(), including integration with builtins (:tselect, z=).

local t = require('test.testutil')
local describe, it, before_each, finally = t.describe, t.it, t.before_each, t.finally
local retry = t.retry
local n = require('test.functional.testnvim')()
local clear = n.clear
local exec_lua = n.exec_lua
local api = n.api
local eq = t.eq
local neq = t.neq
local write_file = t.write_file

before_each(clear)

--- Mock async vim.ui.select impl. Imitates fzf-lua/telescope/snacks: opens a transient floating
--- window, then schedules on_choice to fire on the next event-loop tick.
---
--- Sets `_G._captured` so tests can assert the user choice.
--- @param pick integer|nil 1-based index to "pick" (nil cancels).
local function setup_async_picker(pick)
  exec_lua(function()
    _G._captured = nil
    --- @diagnostic disable-next-line: duplicate-set-field
    vim.ui.select = function(items, opts, on_choice)
      _G._captured = { items = items, opts = opts }
      -- Open a floating window like a real picker would.
      local buf = vim.api.nvim_create_buf(false, true)
      local win = vim.api.nvim_open_win(buf, false, {
        relative = 'editor',
        row = 1,
        col = 1,
        width = 30,
        height = math.min(#items, 5),
      })
      _G._captured.win = win
      -- Defer the choice so the wait actually has to pump events.
      vim.defer_fn(function()
        if vim.api.nvim_win_is_valid(win) then
          vim.api.nvim_win_close(win, true)
        end
        if pick then
          on_choice(items[pick], pick)
        else
          on_choice(nil, nil)
        end
      end, 30)
    end
  end, pick)
end

--- Mock fzf-lua-style picker: opens a floating window with a *terminal* buffer running a small
--- shell command. When the command exits we treat the user as having "picked" `pick`.
local function setup_term_picker(pick)
  exec_lua(function(pick_, prog)
    _G._captured = nil
    --- @diagnostic disable-next-line: duplicate-set-field
    vim.ui.select = function(items, opts, on_choice)
      _G._captured = { items = items, opts = opts }
      local buf = vim.api.nvim_create_buf(false, true)
      local win = vim.api.nvim_open_win(buf, true, {
        relative = 'editor',
        row = 1,
        col = 1,
        width = 30,
        height = math.min(#items, 5),
      })
      vim.fn.jobstart({ prog }, {
        term = true,
        on_exit = function()
          if vim.api.nvim_win_is_valid(win) then
            vim.api.nvim_win_close(win, true)
          end
          if pick_ then
            on_choice(items[pick_], pick_)
          else
            on_choice(nil, nil)
          end
        end,
      })
    end
  end, pick, n.testprg('shell-test'))
end

describe('vim.ui.select()', function()
  it('can select an item', function()
    local result = exec_lua [[
      local items = {
        { name = 'Item 1' },
        { name = 'Item 2' },
      }
      local opts = {
        format_item = function(entry)
          return entry.name
        end
      }
      local selected
      local cb = function(item)
        selected = item
      end
      -- inputlist would require input and block the test;
      local choices
      vim.fn.inputlist = function(x)
        choices = x
        return 1
      end
      vim.ui.select(items, opts, cb)
      vim.wait(100, function() return selected ~= nil end)
      return {selected, choices}
    ]]
    eq({ name = 'Item 1' }, result[1])
    eq({
      'Select one of:',
      '1: Item 1',
      '2: Item 2',
    }, result[2])
  end)

  describe('via :tselect', function()
    local function prepare_test()
      -- Create dummy source files so the jump succeeds.
      write_file('XselTagA.c', 'int foo;\n')
      write_file('XselTagB.c', 'int foo = 1;\n')
      finally(function()
        os.remove('XselTagA.c')
        os.remove('XselTagB.c')
        os.remove('XselTags')
      end)
      write_file(
        'XselTags',
        '!_TAG_FILE_FORMAT\t2\t/extended format/\n'
          .. 'foo\tXselTagA.c\t/^int foo;$/;"\tv\n'
          .. 'foo\tXselTagB.c\t/^int foo = 1;$/;"\tv\n'
      )
      api.nvim_set_option_value('tags', 'XselTags', {})
    end

    it('passes items, gets user choice', function()
      prepare_test()

      local got = exec_lua(function()
        --- @diagnostic disable-next-line: duplicate-set-field
        vim.ui.select = function(items, opts, on_choice)
          _G._captured = { items = items, kind = opts.kind }
          -- Pick the second match.
          on_choice(items[2], 2)
        end
        vim.cmd('tselect foo')
      end)
      -- on_choice queues `:[idx]tag` via feedkeys; let typeahead drain.
      retry(nil, 1000, function()
        eq('XselTagB.c', api.nvim_eval('expand("%:t")'))
      end)
      got = exec_lua(function()
        return _G._captured
      end)

      eq('tag', got.kind)
      eq(2, #got.items)
      eq('foo', got.items[1].tag)
      eq('XselTagB.c', got.items[2].file)
    end)

    it('does nothing when the user cancels', function()
      prepare_test()

      local before = api.nvim_buf_get_name(0)
      exec_lua(function()
        vim.ui.select = function(_, _, on_choice)
          on_choice(nil, nil)
        end
        vim.cmd('tselect foo')
      end)

      eq(before, api.nvim_buf_get_name(0))
    end)

    it('+ async picker', function()
      prepare_test()

      setup_async_picker(2)
      exec_lua([[vim.cmd('tselect foo')]])
      retry(nil, 1000, function()
        eq('XselTagB.c', api.nvim_eval('expand("%:t")'))
      end)
      eq('tag', exec_lua([[return _G._captured and _G._captured.opts.kind]]))
    end)

    it('+ async terminal-based picker', function()
      prepare_test()

      setup_term_picker(2)
      exec_lua([[vim.cmd('tselect foo')]])
      retry(nil, 1000, function()
        eq('XselTagB.c', api.nvim_eval('expand("%:t")'))
      end)
    end)
  end)

  describe('via z=', function()
    local function prepare_test()
      api.nvim_set_option_value('spell', true, {})
      api.nvim_set_option_value('spelllang', 'en_us', {})
      api.nvim_buf_set_lines(0, 0, -1, false, { 'helo' })
    end

    it('passes items, gets user choice', function()
      prepare_test()

      exec_lua(function()
        vim.cmd('normal! gg0')
        --- @diagnostic disable-next-line: duplicate-set-field
        vim.ui.select = function(items, opts, on_choice)
          _G._captured = { items = items, kind = opts.kind, prompt = opts.prompt }
          -- Pick the first suggestion.
          on_choice(items[1], 1)
        end
        vim.cmd('normal! z=')
      end)
      -- z= delegates to vim.ui.select, see `_core/spell:select_suggest`. on_choice queues
      -- `:normal! [idx]z=` via feedkeys; let typeahead drain.
      retry(nil, 1000, function()
        t.neq('helo', api.nvim_buf_get_lines(0, 0, -1, false)[1])
      end)
      local got = exec_lua([[return _G._captured]])

      eq('spell', got.kind)
      t.matches('helo', got.prompt)
      eq(got.items[1].word, api.nvim_buf_get_lines(0, 0, -1, false)[1])
    end)

    it('does nothing when the user cancels', function()
      prepare_test()

      exec_lua(function()
        vim.cmd('normal! gg0')
        vim.ui.select = function(_, _, on_choice)
          on_choice(nil, nil)
        end
        vim.cmd('normal! z=')
      end)

      eq('helo', api.nvim_buf_get_lines(0, 0, -1, false)[1])
    end)

    it('+ async picker', function()
      prepare_test()

      setup_async_picker(1)
      exec_lua([[vim.cmd('normal! gg0z=')]])
      retry(nil, 1000, function()
        neq('helo', api.nvim_buf_get_lines(0, 0, -1, false)[1])
      end)
      eq('spell', exec_lua([[return _G._captured and _G._captured.opts.kind]]))
    end)

    it('+ async terminal-based picker', function()
      prepare_test()

      setup_term_picker(1)
      exec_lua([[vim.cmd('normal! gg0z=')]])
      retry(nil, 1000, function()
        neq('helo', api.nvim_buf_get_lines(0, 0, -1, false)[1])
      end)
    end)
  end)

  describe('via ":browse oldfiles"', function()
    it('+ async picker', function()
      finally(function()
        os.remove('XselOldA')
        os.remove('XselOldB')
      end)
      write_file('XselOldA', 'a\n')
      write_file('XselOldB', 'b\n')
      local cwd = exec_lua([[return vim.uv.cwd()]])

      setup_async_picker(2)
      exec_lua(function(cwd_)
        -- v:oldfiles is normally populated via shada; inject directly for the test.
        vim.v.oldfiles = { cwd_ .. '/XselOldA', cwd_ .. '/XselOldB' }
        vim.cmd('browse oldfiles')
      end, cwd)
      retry(nil, 1000, function()
        eq('XselOldB', api.nvim_eval('expand("%:t")'))
      end)
      eq('oldfiles', exec_lua([[return _G._captured and _G._captured.opts.kind]]))
    end)
  end)
end)
