let s:t_list = type([])
let s:t_func = type(function('tr'))

function! s:_vital_loaded(V) abort
  let s:String = a:V.import('Data.String')
endfunction

function! s:_vital_depends() abort
  return ['Data.String']
endfunction

function! s:new() abort
  let options = copy(s:options)
  let options._options = {}
  return options
endfunction


" Private --------------------------------------------------------------------
function! s:_filter(arglead, candidates) abort
  let pattern = '^' . s:String.escape_pattern(a:arglead)
  let candidates = copy(a:candidates)
  call filter(candidates, 'v:val =~# pattern')
  return candidates
endfunction

function! s:_complete_choice(arglead, cmdline, cursorpos, choices) abort
  let leading = matchstr(a:arglead, '\%(-[^-]\|--\S\+=\)')
  let candidates = map(
        \ copy(a:choices),
        \ 'leading . v:val'
        \)
  return s:_filter(a:arglead, candidates)
endfunction

function! s:_complete_callback(arglead, cmdline, cursorpos, callback) abort
  let m = matchlist(a:arglead, '\(-[^-]\|--\S\+=\)\(.*\)')
  let candidates = a:callback(m[2], a:cmdline, a:cursorpos)
  return map(
        \ copy(candidates),
        \ 'm[1] . v:val'
        \)
endfunction

function! s:_compare_options(lhs, rhs) abort
  let lhs = a:lhs.names[-1]
  let rhs = a:rhs.names[-1]
  return lhs ==# rhs ? 0 : (lhs < rhs ? -1 : 1)
endfunction


" Options --------------------------------------------------------------------
let s:options = {}

function! s:options.define(query, description, ...) abort
  let self._options[a:query] = extend(copy(s:option), {
        \ 'names': split(a:query, '|'),
        \ 'value': a:0 ? a:1 : v:null,
        \ 'description': a:description,
        \})
endfunction

function! s:options.help(...) abort
  let lwidth = max(map(keys(self._options), 'len(v:val)')) + 3
  let rwidth = get(a:000, 0, 80) - lwidth
  let text = ['options:']
  call map(
        \ sort(values(self._options), function('s:_compare_options')),
        \ 'extend(text, v:val.help(lwidth, rwidth))'
        \)
  redraw | echo join(text, "\n")
endfunction

function! s:options.complete(arglead, cmdline, cursorpos) abort
  let leading = matchstr(a:arglead, '^\%(-[^-]\|--\S\+=\)')
  let candidates = []
  for option in values(self._options)
    if index(option.names, leading) != -1
      return option.complete(a:arglead, a:cmdline, a:cursorpos)
    endif
    call extend(candidates, option.names)
  endfor
  return s:_filter(a:arglead, candidates)
endfunction


" Option ---------------------------------------------------------------------
let s:option = {}

function! s:option.help(lwidth, rwidth) abort
  let rhs = s:String.wrap(self.description, a:rwidth)
  let lhs = [s:String.pad_right(join(self.names), a:lwidth)]
  let lhs += repeat([repeat(' ', a:lwidth)], len(rhs) - 1)
  return map(range(len(lhs)), 'lhs[v:val] . rhs[v:val]')
endfunction

function! s:option.complete(arglead, cmdline, cursorpos) abort
  if type(self.value) == s:t_list
    return s:_complete_choice(a:arglead, a:cmdline, a:cursorpos, self.value)
  elseif type(self.value) == s:t_func
    return s:_complete_callback(a:arglead, a:cmdline, a:cursorpos, self.value)
  endif
  return []
endfunction