function! s:enhance_syntax() abort
  syntax case match

  syntax keyword healthError ERROR
        \ containedin=markdownCodeBlock,mkdListItemLine
  highlight link healthError Error

  syntax keyword healthWarning WARNING
        \ containedin=markdownCodeBlock,mkdListItemLine
  highlight link healthWarning WarningMsg

  syntax keyword healthSuccess SUCCESS
        \ containedin=markdownCodeBlock,mkdListItemLine
  highlight healthSuccess guibg=#5fff00 guifg=#080808 ctermbg=82 ctermfg=232

  syntax match healthHelp "|.\{-}|" contains=healthBar
        \ containedin=markdownCodeBlock,mkdListItemLine
  syntax match healthBar  "|" contained conceal
  highlight link healthHelp Identifier

  " We do not care about markdown syntax errors in :CheckHealth output.
  highlight! link markdownError Normal
endfunction

" Runs the specified healthchecks.
" Runs all discovered healthchecks if a:plugin_names is empty.
function! health#check(plugin_names) abort
  let healthchecks = empty(a:plugin_names)
        \ ? s:discover_health_checks()
        \ : s:to_fn_names(a:plugin_names)

  tabnew
  setlocal wrap breakindent
  setlocal filetype=markdown
  setlocal conceallevel=2 concealcursor=nc
  setlocal keywordprg=:help iskeyword=@,48-57,_,192-255,-,#
  call s:enhance_syntax()

  if empty(healthchecks)
    call setline(1, 'ERROR: No healthchecks found.')
  else
    redraw|echo 'Running healthchecks...'
    for c in healthchecks
      let output = ''
      call append('$', split(printf("\n%s\n%s", c, repeat('=',72)), "\n"))
      try
        let output = "\n\n".execute('call '.c.'()')
      catch
        if v:exception =~# '^Vim\%((\a\+)\)\=:E117.*\V'.c
          let output = execute(
                \ 'call health#report_error(''No healthcheck found for "'
                \ .s:to_plugin_name(c)
                \ .'" plugin.'')')
        else
          let output = execute(
                \ 'call health#report_error(''Failed to run healthcheck for "'
                \ .s:to_plugin_name(c)
                \ .'" plugin. Exception:''."\n".v:throwpoint."\n".v:exception)')
        endif
      endtry
      call append('$', split(output, "\n") + [''])
      redraw
    endfor
  endif

  " needed for plasticboy/vim-markdown, because it uses fdm=expr
  normal! zR
  setlocal nomodified
  redraw|echo ''
endfunction

" Starts a new report.
function! health#report_start(name) abort
  echo "\n## " . a:name
endfunction

" Indents lines *except* line 1 of a string if it contains newlines.
function! s:indent_after_line1(s, columns) abort
  let lines = split(a:s, "\n", 0)
  if len(lines) < 2  " We do not indent line 1, so nothing to do.
    return a:s
  endif
  for i in range(1, len(lines)-1)  " Indent lines after the first.
    let lines[i] = substitute(lines[i], '^\s*', repeat(' ', a:columns), 'g')
  endfor
  return join(lines, "\n")
endfunction

" Changes ':h clipboard' to ':help |clipboard|'.
function! s:help_to_link(s) abort
  return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n]+)', ':help |\1|', 'g')
endfunction

" Format a message for a specific report item
function! s:format_report_message(status, msg, ...) abort " {{{
  let output = '  - ' . a:status . ': ' . s:indent_after_line1(a:msg, 4)
  let suggestions = []

  " Optional parameters
  if a:0 > 0
    let suggestions = type(a:1) == type("") ? [a:1] : a:1
    if type(suggestions) != type([])
      echoerr "Expected String or List"
    endif
  endif

  " Report each suggestion
  if len(suggestions) > 0
    let output .= "\n    - SUGGESTIONS:"
  endif
  for suggestion in suggestions
    let output .= "\n      - " . s:indent_after_line1(suggestion, 10)
  endfor

  return s:help_to_link(output)
endfunction " }}}

" Use {msg} to report information in the current section
function! health#report_info(msg) abort " {{{
  echo s:format_report_message('INFO', a:msg)
endfunction " }}}

" Reports a successful healthcheck.
function! health#report_ok(msg) abort " {{{
  echo s:format_report_message('SUCCESS', a:msg)
endfunction " }}}

" Reports a health warning.
function! health#report_warn(msg, ...) abort " {{{
  if a:0 > 0
    echo s:format_report_message('WARNING', a:msg, a:1)
  else
    echo s:format_report_message('WARNING', a:msg)
  endif
endfunction " }}}

" Reports a failed healthcheck.
function! health#report_error(msg, ...) abort " {{{
  if a:0 > 0
    echo s:format_report_message('ERROR', a:msg, a:1)
  else
    echo s:format_report_message('ERROR', a:msg)
  endif
endfunction " }}}

function! s:filepath_to_function(name) abort
  return substitute(substitute(substitute(a:name, '.*autoload[\/]', '', ''),
        \ '\.vim', '#check', ''), '[\/]', '#', 'g')
endfunction

function! s:discover_health_checks() abort
  let healthchecks = globpath(&runtimepath, 'autoload/health/*.vim', 1, 1)
  let healthchecks = map(healthchecks, '<SID>filepath_to_function(v:val)')
  return healthchecks
endfunction

" Translates a list of plugin names to healthcheck function names.
function! s:to_fn_names(plugin_names) abort
  let healthchecks = []
  for p in a:plugin_names
    call add(healthchecks, 'health#'.p.'#check')
  endfor
  return healthchecks
endfunction

" Extracts 'foo' from 'health#foo#check'.
function! s:to_plugin_name(fn_name) abort
  return substitute(a:fn_name,
        \ '\v.*health\#(.+)\#check.*', '\1', '')
endfunction
