local conditions = require("heirline.conditions")
local utils = require("heirline.utils")

local Spacer = { provider = " " }
local function rpad(child)
  return {
    condition = child.condition,
    child,
    Spacer,
  }
end
local function lpad(child)
  return {
    condition = child.condition,
    Spacer,
    child,
  }
end

local stl_static = {
  mode_color_map = {
    n = "function",
    i = "green",
    v = "statement",
    V = "statement",
    ["\22"] = "statement",
    c = "yellow",
    s = "statement",
    S = "statement",
    ["\19"] = "statement",
    R = "red",
    r = "red",
    ["!"] = "constant",
    t = "constant",
  },
  mode_color = function(self)
    local mode = vim.fn.mode(1):sub(1, 1) -- get only the first mode character
    return self.mode_color_map[mode]
  end,
}

local ViMode = {
  init = function(self)
    self.mode = vim.fn.mode(1) -- :h mode()

    -- execute this only once, this is required if you want the ViMode
    -- component to be updated on operator pending mode
    if not self.once then
      vim.api.nvim_create_autocmd("ModeChanged", {
        pattern = "*:*o",
        command = "redrawstatus",
      })
      self.once = true
    end
  end,
  -- Now we define some dictionaries to map the output of mode() to the
  -- corresponding string and color. We can put these into `static` to compute
  -- them at initialisation time.
  static = {
    mode_names = {
      n = "NORMAL",
      no = "NORMAL-",
      nov = "NORMAL-",
      noV = "NORMAL-",
      ["no\22"] = "NORMAL-",
      niI = "NORMAL-",
      niR = "NORMAL-",
      niV = "NORMAL-",
      nt = "NORMAL-",
      v = "VISUAL",
      vs = "VISUAL-",
      V = "V-LINE",
      Vs = "V-LINE-",
      ["\22"] = "V-BLOCK",
      ["\22s"] = "V-BLOCK-",
      s = "SELECT",
      S = "S-LINE",
      ["\19"] = "S-BLOCK",
      i = "INSERT",
      ic = "INSERT-",
      ix = "INSERT-",
      R = "REPLACE",
      Rc = "REPLACE-",
      Rx = "REPLACE-",
      Rv = "REPLACE-",
      Rvc = "REPLACE-",
      Rvx = "REPLACE-",
      c = "COMMAND",
      cv = "COMMAND-",
      r = "PROMPT",
      rm = "MORE",
      ["r?"] = "CONFIRM",
      ["!"] = "SHELL",
      t = "TERMINAL",
    },
  },
  provider = function(self) return " " .. self.mode_names[self.mode] .. " " end,
  hl = function(self) return { fg = "black", bg = self:mode_color(), bold = true } end,
  update = {
    "ModeChanged",
  },
}

local FileIcon = {
  init = function(self)
    self.icon, self.icon_color =
      require("nvim-web-devicons").get_icon_color_by_filetype(vim.bo.filetype, { default = true })
  end,
  provider = function(self) return self.icon and (self.icon .. " ") end,
  hl = function(self) return { fg = self.icon_color } end,
}

local FileType = {
  condition = function() return vim.bo.filetype ~= "" end,
  FileIcon,
  {
    provider = function() return vim.bo.filetype end,
  },
}

local FileName = {
  provider = function(self)
    local filename = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":.")
    if filename == "" then
      return "[No Name]"
    end
    -- now, if the filename would occupy more than 90% of the available
    -- space, we trim the file path to its initials
    if not conditions.width_percent_below(#filename, 0.90) then
      filename = vim.fn.pathshorten(filename)
    end
    return filename
  end,
}

local FileFlags = {
  {
    condition = function() return vim.bo.modified end,
    provider = " [+]",
  },
  {
    condition = function() return not vim.bo.modifiable or vim.bo.readonly end,
    provider = " ",
  },
}

local FullFileName = {
  hl = function()
    local fg
    if vim.bo.modified then
      fg = "yellow"
    else
      -- fg = conditions.is_active() and "tablinesel_fg" or "tabline_fg"
      fg = "tabline_fg"
    end
    return {
      fg = fg,
      -- bg = conditions.is_active() and "tablinesel_bg" or "winbar_bg",
      bg = "winbar_bg",
    }
  end,
  FileName,
  FileFlags,
  { provider = "%=" },
}

local function OverseerTasksForStatus(status)
  return {
    condition = function(self) return self.tasks[status] end,
    provider = function(self) return string.format("%s%d", self.symbols[status], #self.tasks[status]) end,
    hl = function(self)
      return {
        fg = utils.get_highlight(string.format("Overseer%s", status)).fg,
      }
    end,
  }
end
local Overseer = {
  condition = function() return package.loaded.overseer end,
  init = function(self)
    local tasks = require("overseer.task_list").list_tasks({ unique = true })
    local tasks_by_status = require("overseer.util").tbl_group_by(tasks, "status")
    self.tasks = tasks_by_status
  end,
  static = {
    symbols = {
      ["CANCELED"] = " ",
      ["FAILURE"] = "󰅚 ",
      ["SUCCESS"] = "󰄴 ",
      ["RUNNING"] = "󰑮 ",
    },
  },

  rpad(OverseerTasksForStatus("CANCELED")),
  rpad(OverseerTasksForStatus("RUNNING")),
  rpad(OverseerTasksForStatus("SUCCESS")),
  rpad(OverseerTasksForStatus("FAILURE")),
}

local Diagnostics = {
  condition = function() return #vim.diagnostic.get(0, { severity = { min = vim.diagnostic.severity.WARN } }) > 0 end,
  static = {
    error_icon = vim.g.nerd_font and "󰅚 " or "E",
    warn_icon = vim.g.nerd_font and "󰀪 " or "W",
  },
  init = function(self)
    self.errors = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.ERROR })
    self.warnings = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN })
  end,
  update = { "DiagnosticChanged", "BufEnter" },

  {
    provider = function(self) return self.errors > 0 and (self.error_icon .. self.errors .. " ") end,
    hl = { fg = "diag_error" },
  },
  {
    provider = function(self) return self.warnings > 0 and (self.warn_icon .. self.warnings .. " ") end,
    hl = { fg = "diag_warn" },
  },
}

local function setup_colors()
  return {
    fg = utils.get_highlight("StatusLine").fg or "none",
    bg = utils.get_highlight("StatusLine").bg or "none",
    winbar_fg = utils.get_highlight("WinBar").fg or "none",
    winbar_bg = utils.get_highlight("WinBar").bg or "none",
    tablinesel_fg = utils.get_highlight("TabLineSel").fg or "none",
    tablinesel_bg = utils.get_highlight("TabLineSel").bg or "none",
    tabline_fg = utils.get_highlight("TabLine").fg or "none",
    red = utils.get_highlight("DiagnosticError").fg or "none",
    yellow = utils.get_highlight("DiagnosticWarn").fg or "none",
    green = utils.get_highlight("DiagnosticOk").fg or "none",
    gray = utils.get_highlight("NonText").fg or "none",
    ["function"] = utils.get_highlight("Function").fg or "none",
    constant = utils.get_highlight("Constant").fg or "none",
    statement = utils.get_highlight("Statement").fg or "none",
    visual = utils.get_highlight("Visual").bg or "none",
    diag_warn = utils.get_highlight("DiagnosticWarn").fg or "none",
    diag_error = utils.get_highlight("DiagnosticError").fg or "none",
  }
end

local SessionName = {
  condition = function() return package.loaded.resession and require("resession").get_current() end,
  provider = function() return string.format("󰆓 %s", require("resession").get_current()) end,
}

local ArduinoStatus = {
  condition = function() return vim.bo.filetype == "arduino" end,
  provider = function()
    local port = vim.fn["arduino#GetPort"]()
    local line = string.format("[%s]", vim.g.arduino_board)
    if vim.g.arduino_programmer ~= "" then
      line = line .. string.format(" [%s]", vim.g.arduino_programmer)
    end
    if port ~= 0 then
      line = line .. string.format(" (%s:%s)", port, vim.g.arduino_serial_baud)
    end
    return line
  end,
}

-- HACK I don't know why, but the stock implementation of lsp_attached is causing error output
-- (UNKNOWN PLUGIN): Error executing lua: attempt to call a nil value
-- It gets written to raw stderr, which then messes up all of vim's rendering. It's something to do
-- with the require("vim.lsp") call deep in the vim metatable __index function. I don't know the
-- root cause, but I'm done debugging this for today.
conditions.lsp_attached = function()
  local lsp = rawget(vim, "lsp")
  if not lsp then
    return false
  elseif lsp.get_clients then
    return next(lsp.get_clients({ bufnr = 0 })) ~= nil
  else
    return next(lsp.get_active_clients({ bufnr = 0 })) ~= nil
  end
end

local LSPActive = {
  update = { "LspAttach", "LspDetach", "VimResized", "FileType", "BufEnter", "BufWritePost" },

  flexible = 1,
  {
    provider = function()
      local names = {}
      local lsp = rawget(vim, "lsp")
      if lsp then
        local clients
        if lsp.get_clients then
          clients = lsp.get_clients({ bufnr = 0 })
        else
          clients = lsp.get_active_clients({ bufnr = 0 })
        end
        for _, server in pairs(clients) do
          table.insert(names, server.name)
        end
      end
      local lint = package.loaded.lint
      if lint and vim.bo.buftype == "" then
        table.insert(names, "⫽")
        for _, linter in ipairs(lint.linters_by_ft[vim.bo.filetype] or {}) do
          table.insert(names, linter)
        end
      end
      local conform = package.loaded.conform
      if conform and vim.bo.buftype == "" then
        local formatters = conform.list_formatters(0)
        if not conform.will_fallback_lsp() then
          table.insert(names, "⫽")
          for _, formatter in ipairs(formatters) do
            table.insert(names, formatter.name)
          end
        end
      end
      if vim.tbl_isempty(names) then
        return ""
      else
        return " [" .. table.concat(names, " ") .. "]"
      end
    end,
  },
  {
    condition = conditions.lsp_attached,
    provider = " [LSP]",
  },
  {
    condition = conditions.lsp_attached,
    provider = " ",
  },
}

local Ruler = {
  provider = " %P %l:%c ",
  hl = function(self) return { fg = "black", bg = self:mode_color(), bold = true } end,
}

local ConjoinStatus = {
  condition = function()
    local ok, state = pcall(require, "conjoin.state")
    return ok and state.id ~= -1
  end,

  flexible = 1,
  {
    provider = function()
      local state = require("conjoin.state")
      local others = state.get_other_users()
      local names = vim.tbl_map(function(u) return u.name end, others)
      return string.format(" [%s]", table.concat(names, ", "))
    end,
  },
  {
    provider = function()
      local state = require("conjoin.state")
      local others = state.get_other_users()
      return string.format(" %d", #others)
    end,
  },
  {
    provider = " ",
  },
}

local ProfileRecording = {
  condition = function()
    local profile = package.loaded.profile
    return profile and profile.is_recording()
  end,
  provider = function() return "󰑊 " end,
  hl = function() return { fg = "red" } end,
  update = {
    "User",
    pattern = { "ProfileStart", "ProfileStop" },
  },
}

return {
  ViMode = ViMode,
  Ruler = Ruler,
  Spacer = Spacer,
  rpad = rpad,
  lpad = lpad,
  FileIcon = FileIcon,
  FileType = FileType,
  FullFileName = FullFileName,
  Overseer = Overseer,
  Diagnostics = Diagnostics,
  setup_colors = setup_colors,
  SessionName = SessionName,
  ArduinoStatus = ArduinoStatus,
  LSPActive = LSPActive,
  stl_static = stl_static,
  ConjoinStatus = ConjoinStatus,
  ProfileRecording = ProfileRecording,
}