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

local colors = {
  bg = "#333842",
  brown = "#504945",
  white = "#f8f8f0",
  grey = "#8F908A",
  black = "#000000",
  pink = "#f92672",
  green = "#a6e22e",
  blue = "#66d9ef",
  yellow = "#e6db74",
  orange = "#fd971f",
  purple = "#ae81ff",
  red = "#e95678",
  diag = {
    warn = utils.get_highlight("DiagnosticSignWarn").fg,
    error = utils.get_highlight("DiagnosticSignError").fg,
    hint = utils.get_highlight("DiagnosticSignHint").fg,
    info = utils.get_highlight("DiagnosticSignInfo").fg
  },
  git = {
    del = "#e95678",
    add = "#a6e22e",
    change = "#ae81ff"
  }
}

vim.o.laststatus = 3

local ViMode = {
  -- get vim current mode, this information will be required by the provider
  -- and the highlight functions, so we compute it only once per component
  -- evaluation and store it as a component attribute
  init = function(self)
    self.mode = vim.fn.mode(1) -- :h mode()
  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 = {
      -- change the strings if you like it vvvvverbose!
      ["n"] = "NORMAL ",
      ["no"] = "N·OPERATOR PENDING ",
      ["v"] = "VISUAL ",
      ["V"] = "V·LINE ",
      [""] = "V·BLOCK ",
      ["s"] = "SELECT ",
      ["S"] = "S·LINE ",
      [""] = "S·BLOCK ",
      ["i"] = "INSERT ",
      ["R"] = "REPLACE ",
      ["Rv"] = "V·REPLACE ",
      ["c"] = "COMMAND ",
      ["cv"] = "VIM EX ",
      ["ce"] = "EX ",
      ["r"] = "PROMPT ",
      ["rm"] = "MORE ",
      ["r?"] = "CONFIRM ",
      ["!"] = "SHELL ",
      ["t"] = "TERMINAL "
    },
    mode_colors = {
      n = colors.green,
      i = colors.pink,
      v = colors.blue,
      V = colors.blue,
      [""] = colors.blue,
      c = colors.red,
      s = colors.purple,
      S = colors.purple,
      [""] = colors.purple,
      R = colors.orange,
      r = colors.orange,
      ["!"] = colors.red,
      t = colors.red
    }
  },
  -- We can now access the value of mode() that, by now, would have been
  -- computed by `init()` and use it to index our strings dictionary.
  -- note how `static` fields become just regular attributes once the
  -- component is instantiated.
  -- To be extra meticulous, we can also add some vim statusline syntax to
  -- control the padding and make sure our string is always at least 2
  -- characters long. Plus a nice Icon.
  provider = function(self)
    return " %2(" .. self.mode_names[self.mode] .. "%)"
  end,
  -- Same goes for the highlight. Now the foreground will change according to the current mode.
  hl = function(self)
    local mode = self.mode:sub(1, 1) -- get only the first mode character
    return {bg = self.mode_colors[mode], fg = colors.bg, bold = true}
  end
}

local FileNameBlock = {
  -- let's first set up some attributes needed by this component and it's children
  init = function(self)
    self.filename = vim.api.nvim_buf_get_name(0)
  end
}
-- We can now define some children separately and add them later

local FileIcon = {
  init = function(self)
    local filename = self.filename
    local extension = vim.fn.fnamemodify(filename, ":e")
    self.icon, self.icon_color = require("nvim-web-devicons").get_icon_color(filename, extension, {default = true})
  end,
  provider = function(self)
    return self.icon and (self.icon .. " ")
  end,
  hl = function(self)
    return {fg = self.icon_color, bg = colors.bg}
  end
}

local FileName = {
  provider = function(self)
    -- first, trim the pattern relative to the current directory. For other
    -- options, see :h filename-modifers
    local filename = vim.fn.fnamemodify(self.filename, ":.")
    if filename == "" then
      return "[No Name]"
    end
    -- now, if the filename would occupy more than 1/4th of the available
    -- space, we trim the file path to its initials
    -- See Flexible Components section below for dynamic truncation
    if not conditions.width_percent_below(#filename, 0.25) then
      filename = vim.fn.pathshorten(filename)
    end
    return filename
  end,
  hl = {fg = utils.get_highlight("Directory").fg, bg = colors.bg}
}

local FileFlags = {
  {
    provider = function()
      if vim.bo.modified then
        return " [+]"
      end
    end,
    hl = {fg = colors.green, bg = colors.bg}
  },
  {
    provider = function()
      if (not vim.bo.modifiable) or vim.bo.readonly then
        return ""
      end
    end,
    hl = {fg = colors.orange}
  }
}

-- Now, let's say that we want the filename color to change if the buffer is
-- modified. Of course, we could do that directly using the FileName.hl field,
-- but we'll see how easy it is to alter existing components using a "modifier"
-- component

local FileNameModifer = {
  hl = function()
    if vim.bo.modified then
      -- use `force` because we need to override the child's hl foreground
      return {fg = colors.cyan, bold = true, force = true, bg = colors.bg}
    end
  end
}

-- let's add the children to our FileNameBlock component
FileNameBlock =
  utils.insert(
  FileNameBlock,
  FileIcon,
  utils.insert(FileNameModifer, FileName), -- a new table where FileName is a child of FileNameModifier
  unpack(FileFlags), -- A small optimisation, since their parent does nothing
  {provider = "%<"} -- this means that the statusline is cut here when there's not enough space
)

local Diagnostics = {
  condition = conditions.has_diagnostics,
  static = {
    error_icon = vim.fn.sign_getdefined("DiagnosticSignError")[1].text,
    warn_icon = vim.fn.sign_getdefined("DiagnosticSignWarn")[1].text,
    info_icon = vim.fn.sign_getdefined("DiagnosticSignInfo")[1].text,
    hint_icon = vim.fn.sign_getdefined("DiagnosticSignHint")[1].text
  },
  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})
    self.hints = #vim.diagnostic.get(0, {severity = vim.diagnostic.severity.HINT})
    self.info = #vim.diagnostic.get(0, {severity = vim.diagnostic.severity.INFO})
  end,
  {
    provider = function(self)
      -- 0 is just another output, we can decide to print it or not!
      return self.errors > 0 and (self.error_icon .. self.errors .. " ")
    end,
    hl = {fg = colors.diag.error, bg = colors.bg}
  },
  {
    provider = function(self)
      return self.warnings > 0 and (self.warn_icon .. self.warnings .. " ")
    end,
    hl = {fg = colors.diag.warn, bg = colors.bg}
  },
  {
    provider = function(self)
      return self.info > 0 and (self.info_icon .. self.info .. " ")
    end,
    hl = {fg = colors.diag.info, bg = colors.bg}
  },
  {
    provider = function(self)
      return self.hints > 0 and (self.hint_icon .. self.hints)
    end,
    hl = {fg = colors.diag.hint, bg = colors.bg}
  }
}

local Git = {
  condition = conditions.is_git_repo,
  init = function(self)
    self.status_dict = vim.b.gitsigns_status_dict
    self.has_changes = self.status_dict.added ~= 0 or self.status_dict.removed ~= 0 or self.status_dict.changed ~= 0
  end,
  hl = {fg = colors.orange, bg = colors.bg},
  {
    -- git branch name
    provider = function(self)
      return " " .. self.status_dict.head
    end,
    hl = {bold = true, bg = colors.bg}
  },
  -- You could handle delimiters, icons and counts similar to Diagnostics
  {
    condition = function(self)
      return self.has_changes
    end,
    provider = " "
  },
  {
    provider = function(self)
      local count = self.status_dict.added or 0
      return count > 0 and ("  " .. count)
    end,
    hl = {fg = colors.git.add, bg = colors.bg}
  },
  {
    provider = function(self)
      local count = self.status_dict.removed or 0
      return count > 0 and ("  " .. count)
    end,
    hl = {fg = colors.git.del, bg = colors.bg}
  },
  {
    provider = function(self)
      local count = self.status_dict.changed or 0
      return count > 0 and ("  " .. count)
    end,
    hl = {fg = colors.git.change, bg = colors.bg}
  }
}

local WorkDir = {
  provider = function()
    local icon = " "
    local cwd = vim.fn.getcwd(0)
    cwd = vim.fn.fnamemodify(cwd, ":~")
    if not conditions.width_percent_below(#cwd, 0.25) then
      cwd = vim.fn.pathshorten(cwd)
    end
    local trail = cwd:sub(-1) == "/" and "" or "/"
    return icon .. cwd .. trail
  end,
  hl = {fg = colors.blue, bold = true, bg = colors.bg}
}

require("fidget").setup(
  {
    notification = {
      window = {
        winblend = 0
      }
    },
    progress = {
      ignore = {"null-ls"}
    }
  }
)

local TerminalName = {
  -- we could add a condition to check that buftype == 'terminal'
  -- or we could do that later (see #conditional-statuslines below)
  provider = function()
    local tname, _ = vim.api.nvim_buf_get_name(0):gsub(".*:", "")
    return " " .. tname
  end,
  hl = {bold = true, bg = colors.bg}
}

local Ruler = {
  -- %l = current line number
  -- %L = number of lines in the buffer
  -- %c = column number
  -- %P = percentage through file of displayed window
  provider = "%7 %p%% Ln %l, Col %c"
}

local Align = {provider = "%=", hl = {bg = colors.bg}}
local Space = {provider = "  "}

local FileInfoBlock = {
  -- let's first set up some attributes needed by this component and it's children
  init = function(self)
    self.filename = vim.api.nvim_buf_get_name(0)
  end
}

local FileType = {
  provider = function()
    return vim.bo.filetype
  end,
  hl = {fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg}
}

local FileEncoding = {
  provider = function()
    local enc = (vim.bo.fenc ~= "" and vim.bo.fenc) or vim.o.enc -- :h 'enc'
    return enc:upper()
  end
}

FileInfoBlock =
  utils.insert(
  FileInfoBlock,
  FileEncoding,
  Space,
  FileIcon,
  FileType,
  {provider = "%<"} -- this means that the statusline is cut here when there's not enough space
)

local FileNameShort = {
  provider = function(self)
    -- first, trim the pattern relative to the current directory. For other
    -- options, see :h filename-modifers
    local filename = vim.fn.fnamemodify(self.filename, ":t")
    if filename == "" then
      return "[No Name]"
    end
    return filename
  end,
  hl = {fg = colors.gray, bg = colors.bg}
}

local FileNameShortBlock = {
  init = function(self)
    self.filename = vim.api.nvim_buf_get_name(0)
  end
}

FileNameShortBlock =
  utils.insert(
  FileNameShortBlock,
  FileIcon,
  FileNameShort,
  {provider = "%<"} -- this means that the statusline is cut here when there's not enough space
)

local DefaultStatusline = {
  ViMode,
  Space,
  FileNameBlock,
  Space,
  Diagnostics,
  Align,
  Ruler,
  Space,
  FileInfoBlock,
  Space,
  Git
}

local SpecialStatusline = {
  condition = function()
    return conditions.buffer_matches(
      {
        buftype = {"nofile", "prompt", "help", "quickfix"},
        filetype = {"^git.*", "fugitive"}
      }
    )
  end,
  FileType,
  Space,
  Align
}

local TerminalStatusline = {
  condition = function()
    return conditions.buffer_matches({buftype = {"terminal"}})
  end,
  TerminalName,
  Align
}

local StatusLines = {
  fallthrough = false,
  SpecialStatusline,
  TerminalStatusline,
  DefaultStatusline
}

local GSpace = {provider = " ", hl = {bg = colors.bg}}

local WinBars = {
  fallthrough = false,
  {
    -- An inactive winbar for regular files
    condition = function()
      return conditions.buffer_matches({buftype = {"terminal"}}) and not conditions.is_active()
    end,
    utils.surround({"", ""}, colors.bright_bg, {hl = {fg = "gray", force = true}, GSpace, TerminalName, Align})
  },
  {
    -- A special winbar for terminals
    condition = function()
      return conditions.buffer_matches({buftype = {"terminal"}})
    end,
    utils.surround(
      {"", ""},
      colors.dark_red,
      {
        GSpace,
        TerminalName,
        Align
      }
    )
  },
  {
    -- An inactive winbar for regular files
    condition = function()
      return not conditions.is_active()
    end,
    utils.surround({"", ""}, colors.bright_bg, {hl = {fg = "gray", force = true}, GSpace, FileNameShortBlock, Align})
  },
  -- A winbar for regular files
  {
    GSpace,
    FileNameShortBlock,
    -- GSpace,
    Align
  }
}

vim.api.nvim_create_autocmd(
  "User",
  {
    pattern = "HeirlineInitWinbar",
    callback = function(args)
      local buf = args.buf
      local buftype = vim.tbl_contains({"prompt", "nofile", "help", "quickfix"}, vim.bo[buf].buftype)
      local filetype = vim.tbl_contains({"gitcommit", "fugitive"}, vim.bo[buf].filetype)
      if buftype or filetype then
        vim.opt_local.winbar = nil
      end
    end
  }
)

-- we redefine the filename component, as we probably only want the tail and not the relative path
local TablineFileName = {
  provider = function(self)
    -- self.filename will be defined later, just keep looking at the example!
    local filename = self.filename
    filename = filename == "" and "[No Name]" or vim.fn.fnamemodify(filename, ":t")
    return filename
  end,
  hl = function(self)
    return {bold = self.is_active or self.is_visible, italic = true}
  end
}

local TablineFileFlags = {
  {
    provider = function(self)
      if vim.bo[self.bufnr].modified then
        return " [+]"
      end
    end,
    hl = {fg = colors.green}
  },
  {
    provider = function(self)
      if not vim.bo[self.bufnr].modifiable or vim.bo[self.bufnr].readonly then
        return ""
      end
    end,
    hl = {fg = "orange"}
  }
}

local TablineDiagnostics = {
  static = {
    error_icon = " " .. vim.fn.sign_getdefined("DiagnosticSignError")[1].text,
    warn_icon = " " .. vim.fn.sign_getdefined("DiagnosticSignWarn")[1].text,
    info_icon = " " .. vim.fn.sign_getdefined("DiagnosticSignInfo")[1].text,
    hint_icon = " " .. vim.fn.sign_getdefined("DiagnosticSignHint")[1].text
  },
  init = function(self)
    self.errors = #vim.diagnostic.get(self.bufnr, {severity = vim.diagnostic.severity.ERROR})
    self.warnings = #vim.diagnostic.get(self.bufnr, {severity = vim.diagnostic.severity.WARN})
    self.hints = #vim.diagnostic.get(self.bufnr, {severity = vim.diagnostic.severity.HINT})
    self.info = #vim.diagnostic.get(self.bufnr, {severity = vim.diagnostic.severity.INFO})
  end,
  {
    provider = function(self)
      return self.errors > 0 and (self.error_icon .. self.errors .. " ")
    end,
    hl = {fg = colors.diag.error}
  },
  {
    provider = function(self)
      return self.warnings > 0 and (self.warn_icon .. self.warnings .. " ")
    end,
    hl = {fg = colors.diag.warn}
  },
  {
    provider = function(self)
      return self.info > 0 and (self.info_icon .. self.info .. " ")
    end,
    hl = {fg = colors.diag.info}
  },
  {
    provider = function(self)
      return self.hints > 0 and (self.hint_icon .. self.hints)
    end,
    hl = {fg = colors.diag.hint}
  }
}

local TablineFileIcon = {
  init = function(self)
    local filename = self.filename
    local extension = vim.fn.fnamemodify(filename, ":e")
    self.icon, self.icon_color = require("nvim-web-devicons").get_icon_color(filename, extension, {default = true})
  end,
  provider = function(self)
    return self.icon and (" " .. self.icon .. " ")
  end,
  hl = function(self)
    return {fg = self.icon_color}
  end
}

-- Here the filename block finally comes together
local TablineFileNameBlock = {
  init = function(self)
    self.filename = vim.api.nvim_buf_get_name(self.bufnr)
  end,
  hl = function(self)
    if self.is_active then
      return "TabLineSel"
    else
      return "TabLine"
    end
  end,
  on_click = {
    callback = function(_, minwid, _, button)
      if (button == "m") then -- close on mouse middle click
        vim.api.nvim_buf_delete(minwid, {force = true})
      else
        vim.api.nvim_win_set_buf(0, minwid)
      end
    end,
    minwid = function(self)
      return self.bufnr
    end,
    name = "heirline_tabline_buffer_callback"
  },
  TablineFileIcon,
  TablineFileName,
  TablineFileFlags,
  TablineDiagnostics
}

-- a nice "x" button to close the buffer
local TablineCloseButton = {
  condition = function(self)
    return not vim.bo[self.bufnr].modified
  end,
  {provider = " "},
  {
    provider = "",
    hl = {fg = "gray"},
    on_click = {
      callback = function(_, minwid)
        vim.api.nvim_buf_delete(minwid, {force = false})
      end,
      minwid = function(self)
        return self.bufnr
      end,
      name = "heirline_tabline_close_buffer_callback"
    }
  }
}

-- The final touch!
local TablineBufferBlock =
  utils.surround(
  {"", ""},
  function(self)
    if self.is_active then
      return utils.get_highlight("TabLineSel").bg
    else
      return utils.get_highlight("TabLine").bg
    end
  end,
  {TablineFileNameBlock, TablineCloseButton}
)

-- and here we go
local BufferLine =
  utils.make_buflist(
  TablineBufferBlock,
  {provider = "", hl = {fg = "gray"}}, -- left truncation, optional (defaults to "<")
  {provider = "", hl = {fg = "gray"}} -- right trunctation, also optional (defaults to ...... yep, ">")
  -- by the way, open a lot of buffers and try clicking them ;)
)

local TabLineOffset = {
  condition = function(self)
    local win = vim.api.nvim_tabpage_list_wins(0)[1]
    local bufnr = vim.api.nvim_win_get_buf(win)
    self.winid = win

    if vim.bo[bufnr].filetype == "neo-tree" then
      self.title = " NeoTree"
      return true
    -- elseif vim.bo[bufnr].filetype == "TagBar" then
    --     ...
    end
  end,
  provider = function(self)
    local title = self.title
    local width = vim.api.nvim_win_get_width(self.winid)
    local pad = math.ceil((width - #title) / 2)
    return string.rep(" ", pad) .. title .. string.rep(" ", pad)
  end,
  hl = function(self)
    if vim.api.nvim_get_current_win() == self.winid then
      return "TablineSel"
    else
      return "Tabline"
    end
  end
}

local Tabpage = {
  provider = function(self)
    return "%" .. self.tabnr .. "T " .. self.tabnr .. " %T"
  end,
  hl = function(self)
    if not self.is_active then
      return "TabLine"
    else
      return "TabLineSel"
    end
  end
}

local TabpageClose = {
  provider = "%999X  %X",
  hl = "TabLine"
}

local TabPages = {
  -- only show this component if there's 2 or more tabpages
  condition = function()
    return #vim.api.nvim_list_tabpages() >= 2
  end,
  {provider = "%="},
  utils.make_tablist(Tabpage),
  TabpageClose
}

local TabLine = {TabLineOffset, BufferLine, TabPages}

-- require "heirline".setup(StatusLines, WinBars, TabLine)
require "heirline".setup(
  {
    statusline = StatusLines,
    winbar = WinBars,
    tabline = TabLine,
    opts = {
      disable_winbar_cb = function(args)
        local buf = args.buf
        local buftype = vim.tbl_contains({"prompt", "nofile", "help", "quickfix"}, vim.bo[buf].buftype)
        local filetype = vim.tbl_contains({"gitcommit", "fugitive", "Trouble", "packer"}, vim.bo[buf].filetype)
        return buftype or filetype
      end
    }
  }
)
vim.o.showtabline = 2

vim.cmd([[au FileType * if index(['wipe', 'delete', 'unload'], &bufhidden) >= 0 | set nobuflisted | endif]])

vim.api.nvim_create_augroup("Heirline", {clear = true})
vim.api.nvim_create_autocmd(
  "ColorScheme",
  {
    callback = function()
      local colors = setup_colors()
      utils.on_colorscheme(colors)
    end,
    group = "Heirline"
  }
)

local function get_bufs()
  return vim.tbl_filter(
    function(bufnr)
      return vim.api.nvim_buf_is_loaded(bufnr) and vim.bo[bufnr].buflisted
    end,
    vim.api.nvim_list_bufs()
  )
end

local function goto_buf(index)
  local bufs = get_bufs()
  if index > #bufs then
    index = #bufs
  end
  vim.api.nvim_win_set_buf(0, bufs[index])
end

local function addKey(key, index)
  vim.keymap.set(
    "",
    "<A-" .. key .. ">",
    function()
      goto_buf(index)
    end,
    {noremap = true, silent = true}
  )
end

for i = 1, 9 do
  addKey(i, i)
end
addKey("0", 10)
