pcall(require, "luacov")

local Layout = require("nui.layout")
local Popup = require("nui.popup")
local Line = require("nui.line")
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, border_char_size + padding.top)
      eq(popup_win_config.col, 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(), "FloatBorder" })
      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], "FloatBorder" })
      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("throws error if invalid align", function()
      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "single",
          text = {
            top = "popup",
            top_align = "diagonal",
          },
        },
      })

      local ok, err = pcall(Popup, popup_options)
      eq(ok, false)
      eq(type(string.match(err, "invalid value align=diagonal")), "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 string[]", function()
      local text = { "a", "-", "b" }

      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, {
        "┌──a-b───┐",
      }, 1, 1)
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, "a", "FloatTitle")
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, "-", "FloatTitle")
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, "b", "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)

    it("supports nui.line", function()
      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "single",
          text = {
            top = Line({ Text("a", "NuiTestA"), Text("-"), Text("b", "NuiTestB") }),
          },
        },
      })

      popup = Popup(popup_options)

      popup:mount()

      h.assert_buf_lines(popup.border.bufnr, {
        "┌──a-b───┐",
      }, 1, 1)
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, "a", "NuiTestA")
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, "-", "FloatTitle")
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, "b", "NuiTestB")
    end)

    it("supports (text, hl_group)[]", function()
      local text = { { "a", "NuiTestA" }, "-", { "b", "NuiTestB" } }

      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, {
        "┌──a-b───┐",
      }, 1, 1)
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, "a", "NuiTestA")
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, "-", "FloatTitle")
      h.assert_highlight(popup.border.bufnr, popup_options.ns_id, 1, "b", "NuiTestB")
    end)
  end)

  describe("method :mount", function()
    it("ensure .bufnr is valid", function()
      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "single",
          text = {},
        },
      })

      popup = Popup(popup_options)

      popup.border.bufnr = nil
      popup:mount()
      eq(vim.api.nvim_buf_is_valid(popup.border.bufnr), true)
      popup:unmount()

      popup = Popup(popup_options)
      vim.api.nvim_buf_delete(popup.border.bufnr, { force = true })
      popup:mount()
      eq(vim.api.nvim_buf_is_valid(popup.border.bufnr), true)
      popup:unmount()
    end)

    it("sets winblend from popup", function()
      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          text = {
            top = "text",
          },
        },
        win_options = {
          winblend = 20,
        },
      })

      popup = Popup(popup_options)

      popup:mount()

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

    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)

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

      popup.border:unmount()

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

  describe("method :set_style", function()
    it("throws when trying to convert complex border to simple", function()
      popup_options = vim.tbl_deep_extend("force", popup_options, {
        border = {
          style = "rounded",
          padding = { 0 },
        },
      })

      popup = Popup(popup_options)

      local ok, err = pcall(function()
        popup.border:set_style("none")
      end)
      eq(ok, false)
      eq(type(string.match(err, "cannot change from previous style to none")), "string")
    end)
  end)

  describe("method :set_text", function()
    it("works before :mount", 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)

      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))

      popup:mount()

      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("works after :mount", 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("works w/ Layout before :mount", function()
      local text_top = "top"

      popup = Popup({
        border = {
          style = "rounded",
          text = {
            top = text_top,
            top_align = "left",
          },
        },
      })

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

      local layout = Layout(
        {
          position = popup_options.position,
          size = {
            width = popup_options.size.width + 2,
            height = popup_options.size.height + 2,
          },
        },
        Layout.Box({
          Layout.Box(popup, { size = "100%" }),
        })
      )

      layout:mount()

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

      layout:unmount()
    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)

  describe("empty char", function()
    for key, result in pairs({
      top_left = {
        "─────────╮",
        "│        │",
        "│        │",
        "╰────────╯",
      },
      top = {
        "╭        ╮",
        "│        │",
        "╰────────╯",
      },
      top_right = {
        "╭─────────",
        "│        │",
        "│        │",
        "╰────────╯",
      },
      right = {
        "╭───────╮",
        "│        ",
        "│        ",
        "╰───────╯",
      },
      bottom_right = {
        "╭────────╮",
        "│        │",
        "│        │",
        "╰─────────",
      },
      bottom = {
        "╭────────╮",
        "│        │",
        "╰        ╯",
      },
      bottom_left = {
        "╭────────╮",
        "│        │",
        "│        │",
        "─────────╯",
      },
      left = {
        "╭───────╮",
        "        │",
        "        │",
        "╰───────╯",
      },
    }) do
      it("- " .. key, function()
        local style = h.popup.create_border_style_map()
        style[key] = ""

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

        popup = Popup(popup_options)

        popup:mount()

        h.assert_buf_lines(popup.border.bufnr, result)
      end)
    end
  end)
end)
