---@class snacks.image.inline
---@field buf number
---@field imgs table<number, snacks.image.Placement>
---@field idx table<number, snacks.image.Placement>
local M = {}
M.__index = M

function M.new(buf)
  local self = setmetatable({}, M)
  self.buf = buf
  self.imgs = {}
  self.idx = {}
  local group = vim.api.nvim_create_augroup("snacks.image.inline." .. buf, { clear = true })

  local update = Snacks.util.debounce(function()
    self:update()
  end, { ms = 100 })

  vim.api.nvim_create_autocmd({ "BufWritePost", "WinScrolled" }, {
    group = group,
    buffer = buf,
    callback = vim.schedule_wrap(update),
  })
  vim.api.nvim_create_autocmd({ "ModeChanged", "CursorMoved" }, {
    group = group,
    buffer = buf,
    callback = function(ev)
      if ev.buf == self.buf and ev.buf == vim.api.nvim_get_current_buf() then
        self:conceal()
      end
    end,
  })
  vim.schedule(update)
  return self
end

function M:conceal()
  local mode = vim.fn.mode():sub(1, 1):lower() ---@type string
  for _, img in pairs(self.imgs) do
    img:show()
  end
  if vim.wo.concealcursor:find(mode) then
    return
  end
  local from, to = vim.fn.line("v"), vim.fn.line(".")
  from, to = math.min(from, to), math.max(from, to)
  local hide = self:get(from, to)
  for _, img in pairs(hide) do
    if img.opts.conceal then
      img:hide()
    end
  end
end

function M:visible()
  local ret = {} ---@type table<number, snacks.image.Placement>
  for _, win in ipairs(vim.fn.win_findbuf(self.buf)) do
    local info = vim.fn.getwininfo(win)[1]
    for k, v in pairs(self:get(math.max(info.topline - 1, 1), info.botline)) do
      ret[k] = v
    end
  end
  return ret
end

---@param from number 1-indexed inclusive
---@param to number 1-indexed inclusive
function M:get(from, to)
  local ret = {} ---@type table<number, snacks.image.Placement>
  local marks = vim.api.nvim_buf_get_extmarks(self.buf, Snacks.image.placement.ns, { from - 1, 0 }, { to - 1, -1 }, {
    overlap = true,
    hl_name = false,
  })
  for _, m in ipairs(marks) do
    local p = self.idx[m[1]] ---@type snacks.image.Placement?
    if p and not self.imgs[p.id] then
      self.idx[m[1]] = nil
      p = nil
    end
    if p then
      ret[p.id] = p
    end
  end
  return ret
end

function M:update()
  local conceal = Snacks.image.config.doc.conceal
  conceal = type(conceal) ~= "function" and function()
    return conceal
  end or conceal
  Snacks.image.doc.find_visible(self.buf, function(imgs)
    local visible = self:visible()
    local stats = { new = 0, del = 0, update = 0 }
    for _, i in ipairs(imgs) do
      local img ---@type snacks.image.Placement?
      for v, o in pairs(visible) do
        if o.img.src == i.src then
          img = o
          visible[v] = nil
          break
        end
      end
      if not img then
        stats.new = stats.new + 1
        img = Snacks.image.placement.new(
          self.buf,
          i.src,
          Snacks.config.merge({}, Snacks.image.config.doc, {
            pos = i.pos,
            range = i.range,
            inline = true,
            conceal = conceal(i.lang, i.type),
            type = i.type,
            ---@param p snacks.image.Placement
            on_update = function(p)
              for _, eid in ipairs(p.eids) do
                self.idx[eid] = p
              end
            end,
          })
        )
        for _, eid in ipairs(img.eids) do
          self.idx[eid] = img
        end
        self.imgs[img.id] = img
      else
        stats.update = stats.update + 1
        img.opts.pos = i.pos
        img.opts.range = i.range
        img:update()
      end
    end
    for _, img in pairs(visible) do
      stats.del = stats.del + 1
      img:close()
      self.imgs[img.id] = nil
    end
    for k, v in pairs(stats) do
      stats[k] = v > 0 and v or nil
    end
    -- Snacks.notify(
    --   vim.inspect({ all = vim.tbl_count(self.imgs), stats = stats }),
    --   { ft = "lua", id = "snacks.image.inline" }
    -- )
  end)
end

return M
