local Layout = require("nui.layout")
local Popup = require("nui.popup")
local nui_utils = require("nui.utils")
local Tree = require("nui.tree")
local conf = require("llm.config")
local state = require("llm.state")
local F = require("llm.common.api")
local LOG = require("llm.common.log")
local dmenu = require("llm.common.dynamic_menu")

local json = vim.json
local _layout = {}

local function string2number(percent)
  return tonumber((string.gsub(percent, "%%", ""))) / 100
end
local function reformat_size(size)
  if type(size) == "string" then
    size = string2number(size)
  end
  return size
end

--- @param size1 {height: number, width: number}
--- @param size2 {height: number, width: number}
--- @return string|nil
--- @return number
local function get_sublayout_opts(size1, size2)
  local opts = { nil, 0 }
  if size1.height == size2.height and size1.width + size2.width == 1 then
    opts = { "width", size1.height }
  elseif size1.width == size2.width and size1.height + size2.height == 1 then
    opts = { "height", size1.width }
  end
  return unpack(opts)
end

local _not = {
  height = "width",
  width = "height",
}

local _dir = {
  height = "row",
  width = "col",
}

---create chat ui
---@param layout_opts table|nil
---@param popup_input_opts table|nil
---@param popup_output_opts table|nil
---@param popup_other_opts table|nil
function _layout.chat_ui(layout_opts, popup_input_opts, popup_output_opts, popup_other_opts)
  local layout = layout_opts or conf.configs.chat_ui_opts
  local input = popup_input_opts or conf.configs.chat_ui_opts.input.float
  local output = popup_output_opts or conf.configs.chat_ui_opts.output.float
  local other = popup_other_opts or conf.configs.chat_ui_opts.history.float
  local models = conf.configs.chat_ui_opts.models.float

  state.input.popup = Popup({
    enter = input.enter,
    focusable = input.focusable,
    zindex = input.zindex,
    border = input.border,
    win_options = input.win_options,
    buf_options = input.buf_options,
  })
  state.llm.popup = Popup({
    enter = output.enter,
    focusable = output.focusable,
    zindex = output.zindex,
    border = output.border,
    win_options = output.win_options,
    buf_options = output.buf_options,
  })
  if popup_other_opts then
    state.other.popup = Popup({
      enter = other.enter,
      focusable = other.focusable,
      zindex = other.zindex,
      border = other.border,
      win_options = other.win_options,
    })
  else
    if not state.history.hl then
      state.history.hl = other.win_options.winhighlight:match("Normal:(.-),")
      other.win_options.winhighlight = other.win_options.winhighlight:gsub("Normal:(.-),", "Normal:LlmGrayLight,")
      F.FormatHl(state.history.hl, "history")
    end

    setmetatable(state.history, {
      __index = dmenu({
        enter = other.enter,
        focusable = other.focusable,
        zindex = other.zindex,
        border = other.border,
        win_options = other.win_options,
      }, {
        update = function()
          local width = vim.api.nvim_win_get_width(state.history.popup.winid)
          state.history.list = { Tree.Node({ text = "current", _text = "current", id = 1 }) }
          for i, item in ipairs(F.ListFilesInPath()) do
            table.insert(
              state.history.list,
              Tree.Node({ text = nui_utils._.truncate_text(item, width), _text = item, id = i + 1 })
            )
          end
          state.history.popup.tree:set_nodes(nui_utils.defaults(state.history.list, {}))
          state.history.popup.tree:render()
          vim.api.nvim_win_set_cursor(state.history.popup.winid, { 1, 0 })
          state.history.popup._.on_change(state.history.popup.tree:get_node(1))
        end,
        on_change = function(item)
          F.SetItemHl(state.history.popup, state.history.hl)
          local text = item._text
          if text == "current" then
            state.session.filename = text
            if not state.session[text] then
              state.session[text] = F.DeepCopy(conf.session.messages)
            end
            F.RefreshLLMText(state.session[text])
          else
            local sess_file = string.format("%s/%s", conf.configs.history_path, text)
            state.session.filename = text
            if not state.session[text] then
              local file = io.open(sess_file, "r")
              if file then
                local messages = json.decode(file:read())
                state.session[text] = messages
                file:close()
              end
            end
            F.RefreshLLMText(state.session[text])
          end
        end,
      }),
    })

    if conf.configs.models then
      if not state.models.hl then
        state.models.hl = models.win_options.winhighlight:match("Normal:(.-),")
        models.win_options.winhighlight = models.win_options.winhighlight:gsub("Normal:(.-),", "Normal:LlmGrayLight,")
        F.FormatHl(state.models.hl, "models")
      end
      setmetatable(state.models, {
        __index = dmenu({
          enter = models.enter,
          focusable = models.focusable,
          zindex = models.zindex,
          border = models.border,
          win_options = models.win_options,
        }, {
          update = function()
            local width = vim.api.nvim_win_get_width(state.models.popup.winid)
            state.models.list = {}
            for i, item in ipairs(conf.configs.models) do
              table.insert(
                state.models.list,
                Tree.Node({ text = nui_utils._.truncate_text(item.name, width), _text = item, id = i })
              )
            end
            state.models.popup.tree:set_nodes(nui_utils.defaults(state.models.list, {}))
            state.models.popup.tree:render()
            vim.api.nvim_win_set_cursor(state.models.popup.winid, { 1, 0 })
            state.models.popup._.on_change(state.models.popup.tree:get_node(1))
          end,
          on_change = function(item)
            F.SetItemHl(state.models.popup, state.models.hl)
            F.ResetModel(conf.configs, conf.configs, item.id)
            F.SetModelInfo(conf.configs, "Chat", item.id)
          end,
        }),
      })
    end
  end
  for _, size in ipairs({ input.size, output.size, other.size }) do
    size.height = reformat_size(size.height)
    size.width = reformat_size(size.width)
  end
  if popup_input_opts == nil and popup_output_opts == nil and popup_other_opts == nil and conf.configs.save_session then
    local _align, _size = nil, nil
    _align, _size = get_sublayout_opts(input.size, output.size)
    if conf.configs.models then
      if _align then
        if output.order < input.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.llm.popup, { size = output.size[_align] }),
                Layout.Box(state.input.popup, { size = input.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box({
                Layout.Box(state.models.popup, { size = "30%", grow = 1 }),
                Layout.Box(state.history.popup, { size = "70%" }),
              }, { dir = "col", size = other.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.input.popup, { size = input.size[_align] }),
                Layout.Box(state.llm.popup, { size = output.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box({
                Layout.Box(state.models.popup, { size = "30%", grow = 1 }),
                Layout.Box(state.history.popup, { size = "70%" }),
              }, { dir = "col", size = other.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        end
        return
      end

      _align, _size = get_sublayout_opts(output.size, other.size)
      if _align then
        if output.order < other.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.llm.popup, { size = output.size[_align] }),
                Layout.Box({
                  Layout.Box(state.models.popup, { size = "30%", grow = 1 }),
                  Layout.Box(state.history.popup, { size = "70%" }),
                }, { dir = "col", size = other.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.input.popup, { size = input.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box({
                  Layout.Box(state.models.popup, { size = "30%", grow = 1 }),
                  Layout.Box(state.history.popup, { size = "70%" }),
                }, { dir = "col", size = other.size[_align], grow = 1 }),
                Layout.Box(state.llm.popup, { size = output.size[_align] }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.input.popup, { size = input.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        end
        return
      end

      _align, _size = get_sublayout_opts(input.size, other.size)
      if _align then
        if input.order < other.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.input.popup, { size = input.size[_align] }),
                Layout.Box({
                  Layout.Box(state.models.popup, { size = "30%", grow = 1 }),
                  Layout.Box(state.history.popup, { size = "70%" }),
                }, { dir = "col", size = other.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.llm.popup, { size = output.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box({
                  Layout.Box(state.models.popup, { size = "30%", grow = 1 }),
                  Layout.Box(state.history.popup, { size = "70%" }),
                }, { dir = "col", size = other.size[_align], grow = 1 }),
                Layout.Box(state.input.popup, { size = input.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.llm.popup, { size = output.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        end
        return
      end
    else
      if _align then
        if output.order < input.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.llm.popup, { size = output.size[_align] }),
                Layout.Box(state.input.popup, { size = input.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.history.popup, { size = other.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.input.popup, { size = input.size[_align] }),
                Layout.Box(state.llm.popup, { size = output.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.history.popup, { size = other.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        end
        return
      end

      _align, _size = get_sublayout_opts(output.size, other.size)
      if _align then
        if output.order < other.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.llm.popup, { size = output.size[_align] }),
                Layout.Box(state.history.popup, { size = other.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.input.popup, { size = input.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.history.popup, { size = other.size[_align] }),
                Layout.Box(state.llm.popup, { size = output.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.input.popup, { size = input.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        end
        return
      end

      _align, _size = get_sublayout_opts(input.size, other.size)
      if _align then
        if input.order < other.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.input.popup, { size = input.size[_align] }),
                Layout.Box(state.history.popup, { size = other.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.llm.popup, { size = output.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.history.popup, { size = other.size[_align] }),
                Layout.Box(state.input.popup, { size = input.size[_align], grow = 1 }),
              }, { dir = _dir[_not[_align]], size = _size }),
              Layout.Box(state.llm.popup, { size = output.size[_not[_align]] }),
            }, { dir = _dir[_align] })
          )
        end
        return
      end
    end
  else
    if input.size.col == output.size.col then
      if conf.configs.models then
        if output.order < input.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.models.popup, { size = "20%", grow = 1 }),
                Layout.Box(state.llm.popup, { size = "80%" }),
              }, { dir = "row", size = output.size.height }),
              Layout.Box(state.input.popup, { size = input.size.height }),
            }, { dir = "col" })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box(state.input.popup, { size = input.size.height }),
              Layout.Box({
                Layout.Box(state.models.popup, { size = "20%", grow = 1 }),
                Layout.Box(state.llm.popup, { size = "80%" }),
              }, { dir = "row", size = output.size.height }),
            }, { dir = "col" })
          )
        end
      else
        if output.order < input.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box(state.llm.popup, { size = output.size.height }),
              Layout.Box(state.input.popup, { size = input.size.height }),
            }, { dir = "col" })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box(state.input.popup, { size = input.size.height }),
              Layout.Box(state.llm.popup, { size = output.size.height }),
            }, { dir = "col" })
          )
        end
      end
    elseif input.size.row == output.size.row then
      if conf.configs.models then
        if output.order < input.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box({
                Layout.Box(state.models.popup, { size = "20%", grow = 1 }),
                Layout.Box(state.llm.popup, { size = "80%" }),
              }, { dir = "col", size = output.size.width }),
              Layout.Box(state.input.popup, { size = input.size.width }),
            }, { dir = "row" })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box(state.input.popup, { size = input.size.width }),
              Layout.Box({
                Layout.Box(state.models.popup, { size = "20%", grow = 1 }),
                Layout.Box(state.llm.popup, { size = "80%" }),
              }, { dir = "col", size = output.size.width }),
            }, { dir = "row" })
          )
        end
      else
        if output.order < input.order then
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box(state.llm.popup, { size = output.size.width }),
              Layout.Box(state.input.popup, { size = input.size.width }),
            }, { dir = "row" })
          )
        else
          state.layout.popup = Layout(
            { relative = layout.relative, position = layout.position, size = layout.size },
            Layout.Box({
              Layout.Box(state.input.popup, { size = input.size.width }),
              Layout.Box(state.llm.popup, { size = output.size.width }),
            }, { dir = "row" })
          )
        end
      end
    end
  end
end

function _layout:update_history()
  if state.history.popup then
    state.history.update()
  end
end

function _layout:mount()
  state.layout.popup:mount()

  self:update_history()

  if state.models.popup then
    state.models.update()
  end
end

return _layout
