-- Credit: https://github.com/shaunsingh/nord.nvim

local M = {}

M.colors = {
    black       = '#2E3440',
    grey1       = '#3B4252',
    grey2       = '#434C5E',
    grey3       = '#4C566A',
    grey_bright = '#616E88',
    fg          = '#D8DEE9',
    white1      = '#E5E9F0',
    white2      = '#ECEFF4',
    teal        = '#8FBCBB',
    cyan        = '#88C0D0',
    blue        = '#81A1C1',
    dark_blue   = '#5E81AC',
    red         = '#BF616A',
    orange      = '#D08770',
    yellow      = '#EBCB8B',
    green       = '#A3BE8C',
    purple      = '#B48EAD',
    highlight   = '#7B88A1'
}

local c = M.colors

local function hi(group, guifg, guibg, attr, guisp)
    local fg = guifg ~= '' and 'guifg=' .. guifg or 'guifg=NONE'
    local bg = guibg ~= '' and 'guibg=' .. guibg or 'guibg=NONE'
    local style = attr ~= '' and 'gui=' .. attr or 'gui=NONE'
    local sp = guisp ~= '' and 'guisp=' .. guisp or ''

    local hl = 'hi ' .. group .. ' ' .. fg .. ' ' .. bg .. ' ' .. style .. ' ' .. sp
    vim.cmd(hl)
end

-- Set terminal colors
local function set_vim_termcolors()
    vim.g.terminal_color_0 = c.grey1
    vim.g.terminal_color_1 = c.red
    vim.g.terminal_color_2 = c.green
    vim.g.terminal_color_3 = c.yellow
    vim.g.terminal_color_4 = c.blue
    vim.g.terminal_color_5 = c.purple
    vim.g.terminal_color_6 = c.cyan
    vim.g.terminal_color_7 = c.white1
    vim.g.terminal_color_8 = c.grey_bright
    vim.g.terminal_color_9 = c.red
    vim.g.terminal_color_10 = c.green
    vim.g.terminal_color_11 = c.yellow
    vim.g.terminal_color_12 = c.blue
    vim.g.terminal_color_13 = c.purple
    vim.g.terminal_color_14 = c.teal
    vim.g.terminal_color_15 = c.white2
end

-- Editor related groups
local function highlight_editors()
    -- Attributes
    hi('Bold'     , '', '', 'bold'     , '')
    hi('Italic'   , '', '', 'italic'   , '')
    hi('Underline', '', '', 'underline', '')

    -- Editor
    hi('ColorColumn', ''      , c.grey1, '', '')
    hi('Cursor'     , c.black , c.fg   , '', '')
    hi('CursorLine' , c.grey1 , ''     , '', '')
    hi('Error'      , c.fg    , c.red  , '', '')
    hi('iCursor'    , c.black , c.fg   , '', '')
    hi('LineNr'     , c.grey3 , ''     , '', '')
    hi('MatchParen' , c.cyan  , c.grey3, '', '')
    hi('NonText'    , c.grey2 , ''     , '', '')
    hi('Normal'     , c.fg    , c.black, '', '')
    hi('Pmenu'      , c.fg    , c.grey2, '', '')
    hi('PmenuSbar'  , c.fg    , c.grey2, '', '')
    hi('PmenuSel'   , c.cyan  , c.grey3, '', '')
    hi('PmenuThumb' , c.cyan  , c.grey3, '', '')
    hi('SpecialKey' , c.grey3 , ''     , '', '')
    hi('SpellBad'   , c.red   , c.black, 'undercurl', c.red)
    hi('SpellCap'   , c.yellow, c.black, 'undercurl', c.yellow)
    hi('SpellLocal' , c.white1, c.black, 'undercurl', c.white1)
    hi('SpellRare'  , c.white2, c.black, 'undercurl', c.white2)
    hi('Visual'     , ''      , c.grey2 , '', '')
    hi('VisualNOS'  , ''      , c.grey2 , '', '')
    -- Neovim support
    hi('healthError'  , c.red   , c.grey1, '', '')
    hi('healthSuccess', c.green , c.grey1, '', '')
    hi('healthWarning', c.yellow, c.grey1, '', '')
    hi('TermCursorNC' , ''      , c.grey2, '', '')

    -- Gutter
    hi('CursorColumn', c.grey1, ''     , '', '')
    hi('CursorLineNr', c.fg   , ''     , '', '')
    hi('Folded'      , c.grey3, c.grey1, '', '')
    hi('FoldColumn'  , c.grey3, c.black, '', '')
    hi('SignColumn'  , c.grey1, c.black, '', '')

    -- Navigation
    hi('Directory', c.cyan, '', '', '')

    -- Prompt
    hi('EndOfBuffer', c.grey1, ''      , '', '')
    hi('ErrorMsg'   , c.fg   , c.red   , '', '')
    hi('ModeMsg'    , c.green, ''      , '', '')
    hi('MoreMsg'    , c.cyan , ''      , '', '')
    hi('Question'   , c.fg   , ''      , '', '')
    hi('WarningMsg' , c.black, c.yellow, '', '')
    hi('WildMenu'   , c.cyan , c.grey1 , '', '')

    -- Statusline
    hi('StatusLine'      , c.cyan, c.grey3, '', '')
    hi('StatusLineNC'    , c.fg  , c.grey3, '', '')
    hi('StatusLineTerm'  , c.cyan, c.grey3, '', '')
    hi('StatusLineTermNC', c.fg  , c.grey3, '', '')

    -- Search
    hi('IncSearch', c.white2, c.dark_blue, 'underline', '')
    hi('Search'   , c.grey1 , c.cyan     , ''         , '')

    -- Tabline
    hi('TabLine'    , c.fg  , c.grey1, '', '')
    hi('TabLineFill', c.fg  , c.grey1, '', '')
    hi('TabLineSel' , c.cyan, c.grey3, '', '')

    -- Window
    hi('Title', c.fg, '', '', '')
    hi('VertSplit', c.grey2, c.black, '', '')
end

-- Syntax groups
local function highlight_syntax()
    -- Base syntax
    hi('Boolean', c.blue, '', '', '')
    hi('Character', c.fg, '', '', '')
    hi('Comment', c.grey_bright, '', 'italic', '')
    hi('Conceal', '', '', '', '')
    hi('Conditional', c.blue, '', '', '')
    hi('Constant', c.fg, '', '', '')
    hi('Decorator', c.orange, '', '', '')
    hi('Define', c.blue, '', '', '')
    hi('Delimiter', c.white2, '', '', '')
    hi('Exception', c.blue, '', '', '')
    hi('Float', c.purple, '', '', '')
    hi('Function', c.cyan, '', '', '')
    hi('Identifier', c.fg, '', '', '')
    hi('Include', c.blue, '', '', '')
    hi('Keyword', c.blue, '', '', '')
    hi('Label', c.blue, '', '', '')
    hi('Number', c.purple, '', '', '')
    hi('Operator', c.blue, '', '', '')
    hi('PreProc', c.blue, '', '', '')
    hi('Repeat', c.blue, '', '', '')
    hi('Special', c.fg, '', '', '')
    hi('SpecialChar', c.yellow, '', '', '')
    hi('SpecialComment', c.cyan, '', 'italic', '')
    hi('Statement', c.blue, '', '', '')
    hi('StorageClass', c.blue, '', '', '')
    hi('String', c.green, '', '', '')
    hi('Structure', c.blue, '', '', '')
    hi('Tag', c.fg, '', '', '')
    hi('Todo', c.yellow, '', '', '')
    hi('Type', c.blue, '', '', '')
    hi('Typedef', c.blue, '', '', '')
    vim.cmd('hi! link Annotation Decorator')
    vim.cmd('hi! link Macro Define')
    vim.cmd('hi! link PreCondit PreProc')
    vim.cmd('hi! link Variable Identifier')

    -- Diff
    hi('DiffAdd'   , c.green , c.grey1, '', '')
    hi('DiffChange', c.yellow, c.grey1, '', '')
    hi('DiffDelete', c.red   , c.grey1, '', '')
    hi('DiffText'  , c.blue  , c.grey1, '', '')
    -- Legacy diff groups for some plugins
    hi('diffOldFile', c.dark_blue, c.grey1, '', '')
    hi('diffNewFile', c.blue, c.grey1, '', '')
    hi('diffFile', c.cyan, c.grey1, '', '')
    hi('diffLine', c.purple, c.grey1, '', '')
    hi('diffIndexLine', c.fg, c.grey1, '', '')
    vim.cmd('hi! link diffAdded DiffAdd')
    vim.cmd('hi! link diffRemoved DiffDelete')
    vim.cmd('hi! link diffChanged DiffChange')
end

-- Treesitter
local function highlight_treesitter()
    hi('TSPunctDelimiter', c.fg, '', '', '')
    hi('TSPunctBracket', c.fg, '', '', '')
    hi('TSPunctSpecial', c.fg, '', '', '')
    hi('TSConstant', c.yellow, '', '', '')
    hi('TSConstBuiltin', c.blue, '', '', '')
    hi('TSConstMacro', c.yellow, '', '', '')
    hi('TSStringRegex', c.green, '', '', '')
    hi('TSString', c.green, '', '', '')
    hi('TSStringEscape', c.green, '', '', '')
    hi('TSCharacter', c.green, '', '', '')
    hi('TSNumber', c.purple, '', '', '')
    hi('TSBoolean', c.purple, '', '', '')
    hi('TSFloat', c.purple, '', '', '')
    hi('TSAttribute', c.teal, '', '', '')
    hi('TSNamespace', c.fg, '', '', '')
    hi('TSMethod', c.cyan, '', '', '')
    hi('TSField', c.fg, '', '', '')
    hi('TSProperty', c.fg, '', '', '')
    hi('TSConditional', c.blue, '', '', '')
    hi('TSParameter', c.purple, '', '', '')
    hi('TSParameterReference', c.purple, '', '', '')
    hi('TSRepeat', c.blue, '', '', '')
    hi('TSLabel', c.blue, '', '', '')
    hi('TSKeyword', c.blue, '', '', '')
    hi('TSKeywordFunction', c.blue, '', '', '')
    hi('TSKeywordOperator', c.blue, '', '', '')
    hi('TSOperator', c.blue, '', '', '')
    hi('TSException', c.red, '', '', '')
    hi('TSType', c.teal, '', '', '')
    hi('TSTypeBuiltin', c.blue, '', '', '')
    hi('TSStructure', c.blue, '', '', '')
    hi('TSInclude', c.blue, '', '', '')
    hi('TSVariableBuiltin', c.fg, '', '', '')
    hi('TSText', c.fg, '', '', '')
    hi('TSStrong', c.cyan, '', 'bold', '')
    hi('TSEmphasis', c.cyan, '', 'bold,italic', '')
    hi('TSUnderline', c.cyan, '', 'underline', '')
    hi('TSTitle', c.cyan, '', '', '')
    hi('TSLiteral', c.cyan, '', '', '')
    hi('TSURI', c.fg, '', '', '')
    hi('TSTag', c.blue, '', '', '')
    hi('TSTagDelimiter', c.fg, '', '', '')
    vim.cmd('hi! link TSAnnotation Annotation')
    vim.cmd('hi! link TSConstructor Function')
    vim.cmd('hi! link TSError Error')
    vim.cmd('hi! link TSFuncBuiltin Function')
    vim.cmd('hi! link TSFunction Function')
    vim.cmd('hi! link TSFuncMacro Function')
    vim.cmd('hi! link TSVariable Variable')
end

-- LSP groups
local function highlight_lsp()
    hi('LspDiagnosticsDefaultError', c.red, '', '', '')
    hi('LspDiagnosticsSignError', c.red, '', '', '')
    hi('LspDiagnosticsFloatingError', c.red, '', '', '')
    hi('LspDiagnosticsVirtualTextError', c.red, '', 'italic', '')
    hi('LspDiagnosticsUnderlineError', '', '', 'undercurl', c.red)

    hi('LspDiagnosticsDefaultWarning', c.yellow, '', '', '')
    hi('LspDiagnosticsSignWarning', c.yellow, '', '', '')
    hi('LspDiagnosticsFloatingWarning', c.yellow, '', '', '')
    hi('LspDiagnosticsVirtualTextWarning', c.yellow, '', 'italic', '')
    hi('LspDiagnosticsUnderlineWarning', '', '', 'undercurl', c.yellow)

    hi('LspDiagnosticsDefaultInformation', c.blue, '', '', '')
    hi('LspDiagnosticsSignInformation', c.blue, '', '', '')
    hi('LspDiagnosticsFloatingInformation', c.blue, '', '', '')
    hi('LspDiagnosticsVirtualTextInformation', c.blue, '', 'italic', '')
    hi('LspDiagnosticsUnderlineInformation', '', '', 'undercurl', c.blue)

    hi('LspDiagnosticsDefaultHint', c.cyan, '', '', '')
    hi('LspDiagnosticsSignHint', c.cyan, '', '', '')
    hi('LspDiagnosticsFloatingHint', c.cyan, '', '', '')
    hi('LspDiagnosticsVirtualTextHint', c.cyan, '', 'italic', '')
    hi('LspDiagnosticsUnderlineHint', '', '', 'undercurl', c.cyan)

    hi('LspReferenceText', c.fg, c.grey_bright, '', '')
    hi('LspReferenceRead', c.fg, c.grey_bright, '', '')
    hi('LspReferenceWrite', c.fg, c.grey_bright, '', '')
    hi('LspCodeLens', c.grey_bright, '', 'italic', '')
end

-- Specify groups for plugins
local function highlight_plugins()
    -- Gitsigns
    hi('GitSignsAddNr'   , c.green , '', '', '')
    hi('GitSignsChangeNr', c.yellow, '', '', '')
    hi('GitSignsDeleteNr', c.red   , '', '', '')

    -- ts-rainbow
    hi('rainbowcol1', c.red, '', 'bold', '')
    hi('rainbowcol2', c.orange, '', 'bold', '')
    hi('rainbowcol3', c.yellow, '', 'bold', '')
    hi('rainbowcol4', c.green, '', 'bold', '')
    hi('rainbowcol5', c.cyan, '', 'bold', '')
    hi('rainbowcol6', c.blue, '', 'bold', '')
    hi('rainbowcol7', c.purple, '', 'bold', '')

    -- BufferLine
    hi('BufferLineIndicatorSelected', c.cyan, '', '', '')
    hi('BufferLineFill', c.grey1, c.grey1, '', '')

    -- Dashboard
    hi('DashboardHeader'  , c.cyan       , '', 'bold'       , '')
    hi('DashboardCenter'  , c.blue       , '', 'bold'       , '')
    hi('DashboardShortcut', c.grey_bright, '', 'bold,italic', '')
    hi('DashboardFooter'  , c.green      , '', 'bold'       , '')

    -- NvimTree
    hi('NvimTreeRootFolder'     , c.teal     , '', 'bold', '')
    hi('NvimTreeGitDirty'       , c.yellow   , '', '', '')
    hi('NvimTreeGitNew'         , c.green    , '', '', '')
    hi('NvimTreeImageFile'      , c.purple   , '', '', '')
    hi('NvimTreeExecFile'       , c.green    , '', '', '')
    hi('NvimTreeSpecialFile'    , c.dark_blue, '', 'underline', '')
    hi('NvimTreeFolderName'     , c.blue     , '', '', '')
    hi('NvimTreeEmptyFolderName', c.grey3    , '', '', '')
    hi('NvimTreeFolderIcon'     , c.fg       , '', '', '')
    hi('NvimTreeIndentMarker'   , c.grey3    , '', '', '')
    hi('NvimTreeNormal'         , c.fg       , c.black, '', '')

    -- WhichKey
    hi('WhichKey'         , c.green      , '', 'bold', '')
    hi('WhichKeyGroup'    , c.cyan       , '', ''    , '')
    hi('WhichKeyDesc'     , c.blue       , '', ''    , '')
    hi('WhichKeySeperator', c.grey3      , '', ''    , '')
    hi('WhichKeyFloating' , c.fg         , '', ''    , '')
    hi('WhichKeyFloat'    , c.grey_bright, '', ''    , '')

    -- Indent Blankline
    hi('IndentBlanklineChar', c.grey1, '', '', '')
    hi('IndentBlanklineContextChar', c.grey1, '', '', '')

    -- vim-illuminate
    vim.cmd('hi! link illuminatedWord Underline')

    -- trouble.nvim
    hi('LspTroubleText', c.blue, '', 'bold', '')

    -- Telescope
    hi('TelescopePromptBorder', c.cyan, '', 'bold', '')
    hi('TelescopeResultsBorder', c.blue, '', 'bold', '')
    hi('TelescopePreviewBorder', c.green, '', 'bold', '')
    hi('TelescopeSelection', c.fg, c.grey2, '', '')
    hi('TelescopeMultiSelection', c.fg, c.grey2, 'bold', '')
    hi('TelescopeSelectionCaret', c.red, c.grey2, 'bold', '')
    hi('TelescopeMatching', c.purple, '', 'bold', '')

    -- Neogit
    hi('NeogitBranch', c.purple, '', '', '')
    hi('NeogitRemote', c.blue, '', '', '')
    hi('NeogitHunkHeader', c.cyan, c.grey2, 'bold', '')
    hi('NeogitHunkHeaderHighlight', c.yellow, c.grey2, 'bold', '')
    hi('NeogitDiffContextHighlight', c.fg, c.grey2, '', '')
    hi('NeogitDiffDeleteHighlight', c.red, c.grey2, '', '')
    hi('NeogitDiffAddHighlight', c.green, c.grey2, '', '')
    hi('NeogitNotificationInfo', c.green, '', '', '')
    hi('NeogitNotificationWarning', c.yellow, '', '', '')
    hi('NeogitNotificationError', c.red, '', '', '')
end

-- Main function
function M.highlight()
    -- Reset everything
    vim.cmd('hi clear')
    if vim.fn.exists('syntax_on') then vim.cmd('syntax reset') end
    vim.o.background = 'dark'
    vim.o.termguicolors = true

    -- Load highlight groups
    local async
    async = vim.loop.new_async(vim.schedule_wrap(function()
        set_vim_termcolors()
        highlight_plugins()
        highlight_treesitter()
        highlight_lsp()
        async:close()
    end))

    highlight_editors()
    highlight_syntax()
    async:send() -- Load the rest later
end

return M
