local Buffer = require 'u.buffer'
local TreeBuilder = require('u.renderer').TreeBuilder
local tracker = require 'u.tracker'
local utils = require 'u.utils'
local Window = require 'my.window'

local TIMEOUT = 4000
local ICONS = {
  [vim.log.levels.TRACE] = { text = '󰃤', group = 'DiagnosticSignOk' },
  [vim.log.levels.DEBUG] = { text = '󰃤', group = 'DiagnosticSignOk' },
  [vim.log.levels.INFO] = { text = '', group = 'DiagnosticSignInfo' },
  [vim.log.levels.WARN] = { text = '', group = 'DiagnosticSignWarn' },
  [vim.log.levels.ERROR] = { text = '', group = 'DiagnosticSignError' },
}
local DEFAULT_ICON = { text = '', group = 'DiagnosticSignOk' }

--- @alias Notification {
---   kind: number;
---   id: number;
---   text: string;
--- }

local M = {}

--- @type Window | nil
local notifs_w

local s_notifications_raw = tracker.create_signal {}
local s_notifications = s_notifications_raw:debounce(50)

-- Render effect:
tracker.create_effect(function()
  --- @type Notification[]
  local notifs = s_notifications:get()

  if #notifs == 0 then
    if notifs_w then
      notifs_w:close(true)
      notifs_w = nil
    end
    return
  end

  vim.schedule(function()
    local editor_size = utils.get_editor_dimensions()
    local avail_width = editor_size.width
    local float_width = 40
    local win_config = {
      relative = 'editor',
      anchor = 'NE',
      row = 0,
      col = avail_width,
      width = float_width,
      height = math.min(#notifs, editor_size.height - 3),
      border = 'single',
      focusable = false,
    }
    if not notifs_w or not vim.api.nvim_win_is_valid(notifs_w.win) then
      notifs_w = Window.new(Buffer.create(false, true), win_config)
      vim.wo[notifs_w.win].cursorline = false
      vim.wo[notifs_w.win].list = false
      vim.wo[notifs_w.win].listchars = ''
      vim.wo[notifs_w.win].number = false
      vim.wo[notifs_w.win].relativenumber = false
      vim.wo[notifs_w.win].wrap = false
    else
      notifs_w:set_config(win_config)
    end

    notifs_w:render(TreeBuilder.new()
      :nest(function(tb)
        for idx, notif in ipairs(notifs) do
          if idx > 1 then tb:put '\n' end

          local notif_icon = ICONS[notif.kind] or DEFAULT_ICON
          tb:put_h('text', { hl = notif_icon.group }, notif_icon.text)
          tb:put { '  ', notif.text }
        end
      end)
      :tree())
    vim.api.nvim_win_call(notifs_w.win, function()
      -- scroll to bottom:
      vim.cmd.normal 'G'
      -- scroll all the way to the left:
      vim.cmd.normal '9999zh'
    end)
  end)
end)

local _orig_notify

--- @param msg string
--- @param level integer|nil
--- @param opts table|nil
local function my_notify(msg, level, opts)
  vim.schedule(function() _orig_notify(msg, level, opts) end)
  if level == nil then level = vim.log.levels.INFO end
  if level < vim.log.levels.INFO then return end

  local id = math.random(math.huge)

  --- @param notifs Notification[]
  s_notifications_raw:schedule_update(function(notifs)
    table.insert(notifs, { kind = level, id = id, text = msg })
    return notifs
  end)

  vim.defer_fn(function()
    --- @param notifs Notification[]
    s_notifications_raw:schedule_update(function(notifs)
      for i, notif in ipairs(notifs) do
        if notif.id == id then
          table.remove(notifs, i)
          break
        end
      end
      return notifs
    end)
  end, TIMEOUT)
end

local _once_msgs = {}
local function my_notify_once(msg, level, opts)
  if vim.tbl_contains(_once_msgs, msg) then return false end
  table.insert(_once_msgs, msg)
  vim.notify(msg, level, opts)
  return true
end

function M.setup()
  if _orig_notify == nil then _orig_notify = vim.notify end

  vim.notify = my_notify
  vim.notify_once = my_notify_once
end

return M