-- Modified from https://github.com/lauranaujokat/nvim/blob/4102c789d05667f636107e3dae4ac589053ee88d/lua/setups/heirline.lua#L4

local conditions = require('heirline.conditions');
local utils = require('heirline.utils');

---@class Palette
---@field [string] any
local dracula = require('dracula').colors();

local colors = {
    bright_bg = dracula.selection,
    dark_bg = dracula.menu,
    bright_fg = dracula.fg,
    red = dracula.red,
    dark_red = utils.get_highlight('DiffDelete').bg,
    green = dracula.green,
    blue = dracula.blue,
    gray = utils.get_highlight('NonText').fg,
    orange = utils.get_highlight('Constant').fg,
    purple = utils.get_highlight('Statement').fg,
    cyan = dracula.cyan,
    diag_warn = utils.get_highlight('DiagnosticWarn').fg,
    diag_error = utils.get_highlight('DiagnosticError').fg,
    diag_hint = utils.get_highlight('DiagnosticHint').fg,
    diag_info = utils.get_highlight('DiagnosticInfo').fg,
    git_del = utils.get_highlight('GitSignsDelete').fg,
    git_add = utils.get_highlight('GitSignsAdd').fg,
    git_change = utils.get_highlight('GitSignsChange').fg,
};

require('heirline').load_colors(colors);

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);

        -- 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,

    static = {
        mode_names = {
            n = 'N',
            no = 'N?',
            nov = 'N?',
            noV = 'N?',
            ['no\22'] = 'N?',
            niI = 'Ni',
            niR = 'Nr',
            niV = 'Nv',
            nt = 'Nt',
            v = 'V',
            vs = 'Vs',
            V = 'V_',
            Vs = 'Vs',
            ['\22'] = '^V',
            ['\22s'] = '^V',
            s = 'S',
            S = 'S_',
            ['\19'] = '^S',
            i = 'I',
            ic = 'Ic',
            ix = 'Ix',
            R = 'R',
            Rc = 'Rc',
            Rx = 'Rx',
            Rv = 'Rv',
            Rvc = 'Rv',
            Rvx = 'Rv',
            c = 'C',
            cv = 'Ex',
            r = '...',
            rm = 'M',
            ['r?'] = '?',
            ['!'] = '!',
            t = 'T',
        },

        mode_colors = {
            n = 'red',
            i = 'green',
            v = 'cyan',
            V = 'cyan',
            ['\22'] = 'cyan',
            c = 'orange',
            s = 'purple',
            S = 'purple',
            ['\19'] = 'purple',
            R = 'orange',
            r = 'orange',
            ['!'] = 'red',
            t = 'red',
        },
    },

    -- 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 ' ' .. 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 { fg = self.mode_colors[mode], bold = true };
    end,

    -- Re-evaluate the component only on ModeChanged event
    update = {
        'ModeChanged',
    },
};

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

-- FileNameBlock children
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 };
    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 },
};

local FileFlags = {
    {
        condition = function()
            return vim.bo.modified;
        end,
        provider = '[+]',
        hl = { fg = 'green' },
    },
    {
        condition = function()
            return not vim.bo.modifiable or vim.bo.readonly;
        end,
        provider = '',
        hl = { fg = 'orange' },
    },
};

local FileNameModifer = {
    hl = function()
        if vim.bo.modified then
            -- use `force` because we need to override the child's hl foreground
            return { fg = 'cyan', bold = true, force = true };
        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 Ruler = {
    provider = ' line: %l col: %c',
    hl = { fg = 'green', bold = false },
};

local ScrollRuler = {
    -- %l = current line number
    -- %L = number of lines in the buffer
    -- %c = column number
    -- %P = percentage through file of displayed window
    provider = '%P',
};

local ScrollBar = {
    static = {
        sbar = { '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█' },
        -- sbar = { '🭶', '🭷', '🭸', '🭹', '🭺', '🭻' }
    },

    provider = function(self)
        local curr_line = vim.api.nvim_win_get_cursor(0)[1];
        local lines = vim.api.nvim_buf_line_count(0);
        local i = math.floor((curr_line - 1) / lines * #self.sbar) + 1;
        return string.rep(self.sbar[i], 2);
    end,

    hl = { fg = 'cyan', bg = 'bright_bg' },
};

local LSPActive = {
    condition = conditions.lsp_attached,
    update = { 'LspAttach', 'LspDetach' },

    provider = function()
        local names = {};
        for _, server in pairs(vim.lsp.get_clients()) do
            table.insert(names, server.name);
        end;
        return ' [' .. table.concat(names, ' ') .. '] ';
    end,

    hl = { fg = 'green', bold = false },
};

local spinner_frames = { '⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏' };

-- From https://github.com/mhartington/dotfiles/blob/5961460e3a492f7815259a692fca5ca2a1df924a/config/nvim/lua/mh/statusline/lsp_status.lua#L4
local function get_lsp_progress()
    local messages = require('lsp-status/messaging').messages;
    local buf_messages = messages();
    local msgs = {};

    for _, msg in ipairs(buf_messages) do
        local contents;

        if msg.progress then
            contents = msg.title;

            if msg.spinner then
                contents = spinner_frames[(msg.spinner % #spinner_frames) + 1] .. ' ' .. contents;
            end;
        elseif msg.status then
            contents = msg.content;

            if msg.uri then
                local space = math.min(60, math.floor(0.6 * vim.fn.winwidth(0)));
                local filename = vim.uri_to_fname(msg.uri);

                filename = vim.fn.fnamemodify(filename, ':~:.');

                if #filename > space then
                    filename = vim.fn.pathshorten(filename);
                end;

                contents = '(' .. filename .. ') ' .. contents;
            end;
        else
            contents = msg.content;
        end;

        table.insert(msgs, contents);
    end;

    return table.concat(msgs, ' ');
end;

local LSPMessages = {
    provider = function()
        local progress = get_lsp_progress();

        if progress == '' then
            return '';
        else
            return '  ' .. progress;
        end;
    end,
    hl = { fg = 'purple' },
};

local Diagnostics = {
    condition = conditions.has_diagnostics,

    static = {
        error_icon = '  ',
        warn_icon = '  ',
        info_icon = '  ',
        hint_icon = '  ',
    },

    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,

    update = { 'DiagnosticChanged', 'BufEnter' },

    {
        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 = 'diag_error' },
    },
    {
        provider = function(self)
            return self.warnings > 0 and (self.warn_icon .. self.warnings);
        end,
        hl = { fg = 'diag_warn' },
    },
    {
        provider = function(self)
            return self.info > 0 and (self.info_icon .. self.info);
        end,
        hl = { fg = 'diag_info' },
    },
    {
        provider = function(self)
            return self.hints > 0 and (self.hint_icon .. self.hints);
        end,
        hl = { fg = 'diag_hint' },
    },
};

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 = 'orange' },

    { -- git branch name
        provider = function(self)
            return ' ' .. self.status_dict.head;
        end,
        hl = { bold = true },
    },
    -- 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 = 'git_add' },
    },
    {
        provider = function(self)
            local count = self.status_dict.removed or 0;
            return count > 0 and ('-' .. count);
        end,
        hl = { fg = 'git_del' },
    },
    {
        provider = function(self)
            local count = self.status_dict.changed or 0;
            return count > 0 and ('~' .. count);
        end,
        hl = { fg = 'git_change' },
    },
    {
        condition = function(self)
            return self.has_changes;
        end,
        provider = ')',
    },
};

local Align = { provider = '%=' };
local Space = { provider = ' ' };

Left = utils.surround({ '', '' }, 'bright_bg', { ViMode, Diagnostics, LSPMessages });
Middle = utils.surround({ '', '' }, 'bright_bg', { LSPActive, FileNameBlock, Ruler });
Right = utils.surround({ '', '' }, 'bright_bg', { Git, Space, ScrollRuler, Space, ScrollBar });

local DefaultStatusline = {
    hl = { bg = 'dark_bg' },
    condition = function()
        return true;
    end,
    Left,
    Align,
    Middle,
    Align,
    Right,
};

local StatusLines = {
    hl = function()
        if conditions.is_active() then
            return 'StatusLine';
        else
            return 'StatusLineNC';
        end;
    end,

    -- the first statusline with no condition, or which condition returns true is used.
    -- think of it as a switch case with breaks to stop fallthrough.
    fallthrough = false,

    DefaultStatusline,
};

-- Make it global
vim.opt.laststatus = 3;

require('heirline').setup({
    statusline = StatusLines,
});