"=============================================================================
" File    : autoload/unite/sources/outline/defaults/javascript.vim
" Author  : h1mesuke <himesuke@gmail.com>
" Updated : 2012-01-11
"
" Contributed by hamaco
"
" Licensed under the MIT license:
" http://www.opensource.org/licenses/mit-license.php
"
"=============================================================================

" Default outline info for JavaScript
" Version: 0.1.1

" TODO: Use jsctags for much better heading list!

function! unite#sources#outline#defaults#javascript#outline_info() abort
  return s:outline_info
endfunction

let s:Util = unite#sources#outline#import('Util')

"---------------------------------------
" Sub Patterns

let s:pat_indent  = '\<\h\w*\>'

let s:pat_assign = '\%(\%(var\|let\|const\)\s\+\)\=\(' . s:pat_indent . '\%(\.' . s:pat_indent . '\)*\)\s*='
" NOTE: This sub pattern contains 1 capture;  1:lvalue

let s:pat_label  = '\(' . s:pat_indent . '\)\s*:'
" NOTE: This sub pattern contains 1 capture;  1:label

let s:pat_rvalue = '\(function\s*(\([^)]*\))\|(\(.*\))\s*{\|\s*{\|\(\w\+\)\s*(\(.*\))\s*{\)'
" NOTE: This sub pattern contains 2 captures; 1:rvalue [, 2:arg_list]

let s:pat_def =  '\%(\%(export\s\+\%(default\s\+\)\=\)\=function\>\)'

let s:pat_es6_class = '^\s*\%(export\s\+\%(default\s\+\)\=\)\=class\s\+\(\S\+\)\s*{$'
" NOTE: This sub pattern contains 1 capture;  1:className

let s:pat_es6_method = '^\s*\(\%(static\s\+\)\?\w\+\)\s*(\([^)]*\))\s*{$'
" NOTE: This sub pattern contains 2 capture;  1:methodName [, 2:arg_list]

"-----------------------------------------------------------------------------
" Outline Info

let s:outline_info = {
      \ 'heading-1': s:Util.shared_pattern('cpp', 'heading-1'),
      \ 'heading'  : '^\s*\%(' . s:pat_def . '\|' .
      \   '\%(' .
      \     '\%(export\s\+\%(default\s\+\)\=\)\=class\s\+\(\S\+\)\s\+\%(extends\s\+\w\+\)\?\|\s*\%(static\s\+\)\?\w\+\s*\|' . s:pat_assign . '\|' . s:pat_label .
      \   '\)\s*' . s:pat_rvalue . '\)',
      \
      \ 'skip': {
      \   'header': s:Util.shared_pattern('cpp', 'header'),
      \ },
      \
      \ 'not_match_patterns': [
      \   s:Util.shared_pattern('*', 'parameter_list'),
      \ ],
      \}

function! s:outline_info.create_heading(which, heading_line, matched_line, context) abort
  let h_lnum = a:context.heading_lnum
  " Level 1 to 3 are reserved for comment headings.
  let level = s:Util.get_indent_level(a:context, h_lnum) + 3
  let heading = {
        \ 'word' : a:heading_line,
        \ 'level': level,
        \ 'type' : 'generic',
        \ }

  if a:which == 'heading-1' && s:Util._cpp_is_in_comment(a:heading_line, a:matched_line)
    let m_lnum = a:context.matched_lnum
    let heading.type = 'comment'
    let heading.level = s:Util.get_comment_heading_level(a:context, m_lnum)

  elseif a:which ==# 'heading'

    let matched_list = matchlist(a:heading_line,
          \ '^\s*' . s:pat_def . '\s\+\(' . s:pat_indent . '\)\s*(\(.*\))')
    if len(matched_list) > 0
      " function Foo(...) -> Foo(...)
      " function foo(...) -> foo(...)
      let [func_name, arg_list] = matched_list[1:2]
      let heading.word = func_name . '(' . arg_list . ')'
    endif

    let matched_list = matchlist(a:heading_line,
          \ '^\s*\%(' . s:pat_assign . '\|' . s:pat_label . '\)\s*' . s:pat_rvalue)
    if len(matched_list) > 0
      let [lvalue, label, rvalue, arg_list] = matched_list[1:4]
      if lvalue =~ '\S'
        " Assign
        if lvalue =~ '\.'
          " Property
          let prop_chain = split(lvalue, '\.')
          let prop_name = prop_chain[-1]
          if rvalue =~ '^f'
            if prop_name =~ '^\u'
              " Foo.Bar = function(...) -> Foo.Bar(...)
              let heading.word = lvalue . '(' . arg_list . ')'
            else
              " Foo.bar = function(...) -> bar(...)
              "let heading.level += 1
              let heading.word = prop_name . '(' . arg_list . ')'
            endif
          else
            if match(prop_chain, '^\u') >= 0
              " Foo.Bar = { -> Foo.Bar
              " Foo.bar = { -> Foo.bar
              let heading.word = lvalue
            else
              " foo.bar = {
              let heading.level = 0
            endif
          endif
        elseif lvalue =~ '^\u'
          " Variale
          if rvalue =~ '^f'
            " var Foo = function(...) -> Foo(...)
            let heading.word = lvalue . '(' . arg_list . ')'
          else
            " var Foo = { -> Foo
            let heading.word = lvalue
          endif
        else
          " var foo = ...
          let heading.level = 0
        endif
      else
        " Label
        if rvalue =~ '^f'
          " foo: function(...) -> foo(...)
          let heading.word = label . '(' . arg_list . ')'
        else
          " foo: {
          let heading.level = 0
        endif
      endif
    else
      let matched_list = matchlist(a:heading_line, s:pat_es6_class)
      if len(matched_list) > 0
        let heading.level = 1
        let heading.word = 'class ' . matched_list[1]
      else
        let heading.level = 2
        let matched_list = matchlist(a:heading_line, s:pat_es6_method)
        if len(matched_list) > 0
          if match(a:heading_line, '^\s*\%(for\|if\|while\|switch\)\>') != -1
            let heading.level = 0
          else
            let [func_name, arg_list] = matched_list[1:2]
            let heading.word = func_name . '(' . arg_list . ')'
          endif
        endif
      endif
    endif
  endif

  if heading.level > 0
    return heading
  else
    return {}
  endif
endfunction