pcall(require, "luacov")

local Popup = require("nui.popup")
local Text = require("nui.text")
local h = require("tests.helpers")

local eq = h.eq

describe("nui.popup", function()
  local popup_options = {}
  local popup

  before_each(function()
    popup_options = {
      ns_id = vim.api.nvim_create_namespace("NuiTest"),
      position = "50%",
      size = {
        height = 2,
        width = 8,
      },
    }
  end)

  after_each(function()
    if popup then
      popup:unmount()
      popup = nil
    end
  end)

  describe("(#deprecated) border.highlight", function()
    it("works for 'hl_group'", function()
      local hl_group = "NuiPopupTest"

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          highlight = hl_group,
          padding = { 0 },
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      eq(vim.api.nvim_win_get_option(popup.border.winid, "winhighlight"), "FloatBorder:" .. hl_group)
    end)

    it("works for 'FloatBorder:hl_group'", function()
      local hl_group = "NuiPopupTest"

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          highlight = "FloatBorder:" .. hl_group,
          padding = { 0 },
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      eq(vim.api.nvim_win_get_option(popup.border.winid, "winhighlight"), "FloatBorder:" .. hl_group)
    end)
  end)

  describe("border.padding", function()
    local function assert_padding(padding, target_popup)
      local border_char_size = 1

      local popup_win_config = vim.api.nvim_win_get_config(target_popup.winid)
      eq(popup_win_config.win, target_popup.border.winid)
      eq(popup_win_config.row[vim.val_idx], border_char_size + padding.top)
      eq(popup_win_config.col[vim.val_idx], border_char_size + padding.left)

      local border_win_config = vim.api.nvim_win_get_config(target_popup.border.winid)
      eq(border_win_config.width, popup_options.size.width + border_char_size * 2 + padding.right + padding.left)
      eq(border_win_config.height, popup_options.size.height + border_char_size * 2 + padding.top + padding.bottom)
    end

    it("supports list table", function()
      local padding = {
        top = 2,
        right = 2,
        bottom = 1,
        left = 1,
      }

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          padding = { padding.top, padding.right, padding.bottom, padding.left },
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      assert_padding(padding, popup)
    end)

    it("supports partial list table", function()
      local padding = {
        top = 2,
        right = 1,
        bottom = 2,
        left = 1,
      }

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          padding = { padding.top, padding.right },
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      assert_padding(padding, popup)
    end)

    it("supports map table", function()
      local padding = {
        top = 2,
        right = 2,
        bottom = 1,
        left = 1,
      }

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          padding = padding,
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      assert_padding(padding, popup)
    end)
  end)

  describe("border.style", function()
    describe("for complex border", function()
      it("is normalized", function()
        local index_name = {
          "top_left",
          "top",
          "top_right",
          "right",
          "bottom_right",
          "bottom",
          "bottom_left",
          "left",
        }

        local char_map

        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            padding = { 0 },
          },
        })

        popup = Popup(popup_options)
        char_map = popup.border._.char
        eq(char_map, "none")

        popup_options.border.style = "shadow"
        popup = Popup(popup_options)
        char_map = popup.border._.char
        eq(char_map, "shadow")

        popup_options.border.style = "rounded"
        popup = Popup(popup_options)
        char_map = popup.border._.char
        for _, name in ipairs(index_name) do
          eq(type(char_map[name]:content()), "string")
          eq(char_map[name].extmark.hl_group, "FloatBorder")
        end

        popup_options.border.style = h.popup.create_border_style_list()
        popup_options.border.style[1] = { popup_options.border.style[1], "TopLeft" }
        popup_options.border.style[2] = Text(popup_options.border.style[2])
        popup_options.border.style[3] = Text(popup_options.border.style[3], "TopRight")
        popup_options.border.style[6] = { popup_options.border.style[6] }
        popup = Popup(popup_options)
        char_map = popup.border._.char
        for _, name in ipairs(index_name) do
          eq(type(char_map[name]:content()), "string")
          if name == "top_left" then
            eq(char_map[name].extmark.hl_group, "TopLeft")
          elseif name == "top_right" then
            eq(char_map[name].extmark.hl_group, "TopRight")
          else
            eq(char_map[name].extmark.hl_group, "FloatBorder")
          end
        end

        popup_options.border.style = h.popup.create_border_style_map()
        popup_options.border.style.top_left = { popup_options.border.style.top_left, "TopLeft" }
        popup_options.border.style.top = Text(popup_options.border.style.top)
        popup_options.border.style.top_right = Text(popup_options.border.style.top_right, "TopRight")
        popup_options.border.style.bottom = { popup_options.border.style.bottom }
        popup = Popup(popup_options)
        char_map = popup.border._.char
        for _, name in ipairs(index_name) do
          eq(type(char_map[name]:content()), "string")
          if name == "top_left" then
            eq(char_map[name].extmark.hl_group, "TopLeft")
          elseif name == "top_right" then
            eq(char_map[name].extmark.hl_group, "TopRight")
          else
            eq(char_map[name].extmark.hl_group, "FloatBorder")
          end
        end
      end)

      it("supports string name", function()
        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = "rounded",
            padding = { 0 },
          },
        })

        popup = Popup(popup_options)

        popup:mount()

        h.assert_buf_lines(popup.border.bufnr, {
          "╭────────╮",
          "│        │",
          "│        │",
          "╰────────╯",
        })
      end)

      it("supports list table", function()
        local style = h.popup.create_border_style_list()

        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = style,
            padding = { 0 },
          },
        })

        popup = Popup(popup_options)

        popup:mount()

        h.popup.assert_border_lines(popup_options, popup.border.bufnr)
      end)

      it("supports partial list table", function()
        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = { "-" },
            padding = { 0 },
          },
        })

        popup = Popup(popup_options)

        popup:mount()

        popup_options.border.style = { "-", "-", "-", "-", "-", "-", "-", "-" }

        h.popup.assert_border_lines(popup_options, popup.border.bufnr)
      end)

      it("supports map table", function()
        local style = h.popup.create_border_style_map()

        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = style,
            padding = { 0 },
          },
        })

        popup = Popup(popup_options)

        popup:mount()

        h.popup.assert_border_lines(popup_options, popup.border.bufnr)
      end)

      it("supports (char, hl_group) tuple in partial list table", function()
        local hl_group = "NuiPopupTest"

        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = { { "-", hl_group } },
            padding = { 0 },
          },
        })

        popup = Popup(popup_options)

        popup:mount()

        popup_options.border.style = { "-", "-", "-", "-", "-", "-", "-", "-" }

        h.popup.assert_border_lines(popup_options, popup.border.bufnr)
        h.popup.assert_border_highlight(popup_options, popup.border.bufnr, hl_group, true)
      end)

      it("supports (char, hl_group) tuple in map table", function()
        local hl_group = "NuiPopupTest"
        local style = h.popup.create_border_style_map_with_tuple(hl_group)

        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = style,
            padding = { 0 },
          },
        })

        popup = Popup(popup_options)

        popup:mount()

        h.popup.assert_border_lines(popup_options, popup.border.bufnr)
        h.popup.assert_border_highlight(popup_options, popup.border.bufnr, hl_group)
      end)

      it("supports nui.text in map table", function()
        local hl_group = "NuiPopupTest"
        local style = h.popup.create_border_style_map_with_nui_text(hl_group)

        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = style,
            padding = { 0 },
          },
        })

        popup = Popup(popup_options)

        popup:mount()

        h.popup.assert_border_lines(popup_options, popup.border.bufnr)
        h.popup.assert_border_highlight(popup_options, popup.border.bufnr, hl_group)
      end)
    end)

    describe("for simple border", function()
      it("supports nui.text as char", function()
        local hl_group = "NuiPopupTest"

        local style = h.popup.create_border_style_list()
        style[2] = Text(style[2], hl_group)
        style[6] = Text(style[6])

        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = style,
          },
        })

        popup = Popup(popup_options)

        popup:mount()

        local win_config = vim.api.nvim_win_get_config(popup.winid)

        eq(win_config.border[2], { style[2]:content(), hl_group })
        eq(win_config.border[6], style[6]:content())
      end)

      it("supports (char, hl_group) tuple as char", function()
        local hl_group = "NuiPopupTest"

        local style = h.popup.create_border_style_list()
        style[2] = { style[2], hl_group }
        style[6] = { style[6] }

        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = style,
          },
        })

        popup = Popup(popup_options)

        popup:mount()

        local win_config = vim.api.nvim_win_get_config(popup.winid)

        eq(win_config.border[2], { style[2][1], style[2][2] })
        eq(win_config.border[6], style[6][1])
      end)

      it("throws if map table missing keys", function()
        local style = h.popup.create_border_style_map()
        style["top"] = nil

        popup_options = vim.tbl_deep_extend("force", popup_options, {
          border = {
            style = style,
          },
        })

        local ok, err = pcall(Popup, popup_options)
        eq(ok, false)
        eq(type(string.match(err, "missing named border: top")), "string")
      end)
    end)
  end)

  describe("border.text", function()
    it("throws error if borderless", function()
      local text = "popup"

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "none",
          text = {
            top = text,
          },
        },
      })

      local ok, err = pcall(Popup, popup_options)
      eq(ok, false)
      eq(type(string.match(err, "text not supported for style:")), "string")
    end)

    it("throws error if invalid border style", function()
      local text = "popup"

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "__invalid__",
          text = {
            top = text,
          },
        },
      })

      local ok, err = pcall(Popup, popup_options)
      eq(ok, false)
      eq(type(string.match(err, "invalid border style name")), "string")
    end)

    it("supports simple text", function()
      local text = "popup"

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "single",
          text = {
            top = text,
          },
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      h.assert_buf_lines(popup.border.bufnr, {
        "┌─popup──┐",
      }, 1, 1)
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, text, "FloatTitle")
    end)

    it("supports nui.text", function()
      local text = "popup"
      local hl_group = "NuiPopupTest"

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "single",
          text = {
            top = Text(text),
            bottom = Text(text, hl_group),
          },
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      h.assert_buf_lines(popup.border.bufnr, {
        "┌─popup──┐",
      }, 1, 1)
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, text, "FloatTitle")

      h.assert_buf_lines(popup.border.bufnr, {
        "└─popup──┘",
      }, 4, 4)
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 4, text, hl_group)
    end)
  end)

  describe("method :mount", function()
    it("sets winhighlight correctly", function()
      local hl_group = "NuiPopupTest"
      local winhighlight = "Normal:Normal,FloatBorder:" .. hl_group

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          text = {
            top = "text",
          },
        },
        win_options = {
          winhighlight = winhighlight,
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      eq(vim.api.nvim_win_get_option(popup.border.winid, "winhighlight"), winhighlight)
    end)

    it("does nothing if popup mounted", function()
      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          text = {
            top = "text",
          },
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      local bufnr, winid = popup.border.bufnr, popup.border.winid
      eq(type(bufnr), "number")
      eq(type(winid), "number")

      popup.border:mount()

      eq(bufnr, popup.border.bufnr)
      eq(winid, popup.border.winid)
    end)
  end)

  describe("method :unmount", function()
    it("does nothing if popup not mounted", function()
      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          text = {
            top = "text",
          },
        },
      })

      popup = Popup(popup_options)

      eq(type(popup.border.bufnr), "nil")
      eq(type(popup.border.winid), "nil")

      popup.border:unmount()

      eq(type(popup.border.bufnr), "nil")
      eq(type(popup.border.winid), "nil")
    end)
  end)

  describe("method :set_text", function()
    it("works", function()
      local text_top, text_bottom = "top", "bot"

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          text = {
            top = text_top,
            top_align = "left",
          },
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      h.assert_buf_lines(popup.border.bufnr, {
        "╭top─────╮",
        "│        │",
        "│        │",
        "╰────────╯",
      })

      popup.border:set_text("top", text_top, "center")

      h.assert_buf_lines(popup.border.bufnr, {
        "╭──top───╮",
        "│        │",
        "│        │",
        "╰────────╯",
      })

      popup.border:set_text("top", text_top, "right")

      h.assert_buf_lines(popup.border.bufnr, {
        "╭─────top╮",
        "│        │",
        "│        │",
        "╰────────╯",
      })

      local hl_group = "NuiPopupTest"

      popup.border:set_text("bottom", Text(text_bottom, hl_group))

      h.assert_buf_lines(popup.border.bufnr, {
        "╭─────top╮",
        "│        │",
        "│        │",
        "╰──bot───╯",
      })

      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 4, text_bottom, hl_group)
    end)

    it("does nothing for simple border", function()
      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      eq(type(popup.border.bufnr), "nil")

      popup.border:set_text("top", "text")

      eq(type(popup.border.bufnr), "nil")
    end)
  end)

  describe("method :set_highlight", function()
    it("works for simple border", function()
      local style = h.popup.create_border_style_map()

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = style,
        },
        win_options = {
          winhighlight = "Normal:Normal,FloatBorder:Normal",
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      eq(popup.border.winid, nil)

      local hl_group = "NuiPopupTest"

      popup.border:set_highlight(hl_group)

      eq(vim.api.nvim_win_get_option(popup.winid, "winhighlight"), "FloatBorder:" .. hl_group .. ",Normal:Normal")
    end)

    it("works for complex border", function()
      local style = h.popup.create_border_style_map()

      local hl_group = "NuiPopupTest"

      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = style,
          padding = { 0 },
        },
        win_options = {
          winhighlight = "Normal:Normal,FloatBorder:" .. hl_group,
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      eq(vim.api.nvim_win_get_option(popup.winid, "winhighlight"), "Normal:Normal,FloatBorder:" .. hl_group)
      eq(vim.api.nvim_win_get_option(popup.border.winid, "winhighlight"), "Normal:Normal,FloatBorder:" .. hl_group)

      local hl_group_override = "NuiPopupTestOverride"

      popup.border:set_highlight(hl_group_override)

      eq(
        vim.api.nvim_win_get_option(popup.winid, "winhighlight"),
        "FloatBorder:" .. hl_group_override .. ",Normal:Normal"
      )
      eq(
        vim.api.nvim_win_get_option(popup.border.winid, "winhighlight"),
        "FloatBorder:" .. hl_group_override .. ",Normal:Normal"
      )
    end)
  end)
end)