import {exec, get_state, open_run_window, reload_run_window} from '../index.js'
import {Editor} from './editor.js'
import {Files} from './files.js'
import {CallTree} from './calltree.js'
import {Logs} from './logs.js'
import {Eval} from './eval.js'
import {el} from './domutils.js'
import {FLAGS} from '../feature_flags.js'

export class UI {
  constructor(container, state){
    this.change_entrypoint = this.change_entrypoint.bind(this)
    this.change_html_file = this.change_html_file.bind(this)
    this.open_run_window = this.open_run_window.bind(this)

    this.files = new Files(this)

    this.tabs = {}
    this.debugger = {}

    container.appendChild(
      (this.root = el('div', 
          'root ' + (FLAGS.embed_value_explorer ? 'embed_value_explorer' : ''),
        this.editor_container = el('div', 'editor_container'),
        FLAGS.embed_value_explorer
          ? null
          : (this.eval_container = el('div', {class: 'eval'})),
        el('div', 'bottom', 
          this.debugger_container = el('div', 'debugger',
            el('div', 'tabs', 
              this.tabs.calltree = el('div', 'tab', 
                el('a', {
                  click: () => this.set_active_tab('calltree'),
                  href: 'javascript: void(0)',
                }, 'Call tree (F2)')
              ),
              this.tabs.logs = el('div', 'tab', 
                el('a', {
                  click: () => this.set_active_tab('logs'),
                  href: 'javascript: void(0)',
                }, 'Logs (F3)')
              ),
              this.entrypoint_select = el('div', 'entrypoint_select')
            ),
            this.debugger.calltree = el('div', {
              'class': 'tab_content', 
              tabindex: 0,
            }),
            this.debugger.logs = el('div', {
              'class': 'tab_content logs', 
              tabindex: 0,
            }),
          ),
          this.problems_container = el('div', {"class": 'problems', tabindex: 0}),
        ),

        this.files.el,

        this.statusbar = el('div', 'statusbar',
          this.status = el('div', 'status'),
          this.current_module = el('div', 'current_module'),
          /*
          // Fullscreen cancelled on escape, TODO
          el('a', {
            "class" : 'request_fullscreen',
            href: 'javascript:void(0)',
            click: e => document.body.requestFullscreen(),
          },
            'Fullscreen'
          ),
          */

          el('a', {
            'class': 'open_run_window',
            href: 'javascript: void(0)',
            click: this.open_run_window,
          },
            '(Re)open run window (F6)'
          ),

          this.options = el('div', 'options',
            el('label', {'for': 'standard'},
              el('input', {
                id: 'standard', 
                type: 'radio', 
                name: 'keyboard',
                checked: localStorage.keyboard == 'standard' 
                         || localStorage.keyboard == null,
                change: () => {
                  this.editor.set_keyboard_handler('standard')
                }
              }),
              'Standard'
            ),
            el('label', {'for': 'vim'},
              el('input', {
                id: 'vim', 
                type: 'radio', 
                name: 'keyboard',
                checked: localStorage.keyboard == 'vim',
                change: () => {
                  this.editor.set_keyboard_handler('vim')
                }
              }),
              'VIM'
            )
          ),
          el('a', {
              'class': 'show_help',
              href: 'javascript: void(0)', 
              click: () => this.help_dialog.showModal(),
            }, 
            'Help',
          ),
          el('a', {
              'class': 'github',
              href: 'https://github.com/leporello-js/leporello-js',
              target: '__blank',
            }, 'Github'),
          this.help_dialog = this.render_help(),
        )
      ))
    )

    window.addEventListener('keydown', () => this.clear_status(), true)
    window.addEventListener('click', () => this.clear_status(), true)

    window.addEventListener('keydown', e => {
      if(e.key == 'F2') {
        this.set_active_tab('calltree')
      }

      if(e.key == 'F3'){
        this.set_active_tab('logs')
      }

      if(e.key == 'F5'){
        this.fullscreen_editor()
      }

      if(e.key == 'F6'){
        this.open_run_window()
      }
    })

    if(!FLAGS.embed_value_explorer) {
      this.eval = new Eval(this, this.eval_container)
    } else {
      // Stub
      this.eval = {
        show_value_or_error(){},
        clear_value_or_error(){},
        focus_value_or_error(){},
      }
    }

    this.editor = new Editor(this, this.editor_container)

    this.calltree = new CallTree(this, this.debugger.calltree)
    this.logs = new Logs(this, this.debugger.logs)

    // TODO jump to another module
    // TODO use exec
    const jump_to_fn_location = (e) => {
      let loc
      if((loc = e.target.dataset.location) != null){
        loc = JSON.parse(loc)
        this.editor.set_cursor_position(loc.index)
        this.editor.focus()
      }
    }

    // TODO when click in calltree, do not jump to location, navigateCallTree
    // instead
    this.debugger.calltree.addEventListener('click', jump_to_fn_location)

    this.render_entrypoint_select(state)
    this.render_current_module(state.current_module)

    this.set_active_tab('calltree', true)
  }

  set_active_tab(tab_id, skip_focus = false) {
    Object.values(this.tabs).forEach(el => el.classList.remove('active'))
    this.tabs[tab_id].classList.add('active')
    Object.values(this.debugger).forEach(el => el.style.display = 'none')
    this.debugger[tab_id].style.display = 'block'
    if(!skip_focus) {
      this.debugger[tab_id].focus()
    }
  }

  render_entrypoint_select(state) {
    this.entrypoint_select.replaceChildren(
      el('span', 'entrypoint_title', 'js entrypoint'),
      el('select', {
        click: e => e.stopPropagation(),
        change: this.change_entrypoint,
      },
        Object
          .keys(state.files)
          .sort()
          .filter(f => f == '' || f.endsWith('.js') || f.endsWith('.mjs'))
          .map(f =>
            el('option', 
              state.entrypoint == f
                ? { value: f, selected: true }
                : { value: f},
              f == '' ? "*scratch*" : f
            )
          )
      ),
      el('span', 'entrypoint_title', 'html page'),
      el('select', {
        click: e => e.stopPropagation(),
        change: this.change_html_file,
      },
        ['']
          .concat(
            Object
              .keys(state.files)
              .sort()
              .filter(f => f.endsWith('.htm') || f.endsWith('.html'))
          )
          .map(f =>
            el('option', 
              state.html_file == f
                ? { value: f, selected: true }
                : { value: f},
              f == '' ? 'about:blank' : f
            )
          )
      ),
    )
  }

  open_run_window() {
    open_run_window(get_state())
  }

  change_entrypoint(e) {
    const file = e.target.value
    exec('change_entrypoint', file)
    this.editor.focus()
  }

  change_html_file(e) {
    const html_file = e.target.value
    exec('change_html_file', html_file)
    reload_run_window(get_state())
  }

  render_debugger(state) {
    this.debugger_container.style = ''
    this.problems_container.style = 'display: none'
    this.calltree.render_calltree(state)
    this.logs.render_logs(null, state.logs)
  }

  render_problems(problems) {
    this.debugger_container.style = 'display: none'
    this.problems_container.style = ''
    this.problems_container.innerHTML = ''
    problems.forEach(p => {
      const s = this.editor.get_session(p.module)
      const pos = s.doc.indexToPosition(p.index)
      const module = p.module == '' ? "*scratch*" : p.module
      this.problems_container.appendChild(
        el('div', 'problem', 
          el('a', {
            href: 'javascript:void(0)', 
            click: () => exec('goto_problem', p)
          },
            `${module}:${pos.row + 1}:${pos.column} - ${p.message}`
          )
        )
      )
    })
  }

  set_status(text){
    this.current_module.style = 'display: none'
    this.status.style = ''
    this.status.innerText = text
  }

  clear_status(){
    this.render_current_module(get_state().current_module)
  }

  render_current_module(current_module) {
    this.status.style = 'display: none'
    this.current_module.innerText = 
      current_module == ''
        ? '*scratch*'
        : current_module
    this.current_module.style = ''
  }

  render_help() {
    const options =  [
      ['Focus value explorer', 'F1'],
      ['Navigate value explorer', '← → ↑ ↓ or hjkl'],
      ['Leave value explorer', 'F1 or Esc'],
      ['Focus call tree view', 'F2'],
      ['Navigate call tree view', '← → ↑ ↓ or hjkl'],
      ['Leave call tree view', 'F2 or Esc'],
      ['Focus console logs', 'F3'],
      ['Navigate console logs', '↑ ↓ or jk'],
      ['Leave console logs', 'F3 or Esc'],
      ['Jump to definition', 'F4', 'gd'],
      ['Expand selection to eval expression', 'Ctrl-↓ or Ctrl-j'],
      ['Collapse selection', 'Ctrl-↑ or Ctrl-k'],
      ['Step into call', 'Ctrl-i', '\\i'],
      ['Step out of call', 'Ctrl-o', '\\o'],
      ['When in call tree view, jump to return statement', 'Enter'],
      ['When in call tree view, jump to function arguments', 'a'],
      ['Expand/collapse editor to fullscreen', 'F5'],
    ]
    return el('dialog', 'help_dialog',
      el('table', 'help',
        el('thead', '', 
          el('th', '', 'Action'),
          el('th', 'key', 'Standard'),
          el('th', 'key', 'VIM'),
        ),
        el('tbody', '', 
          options.map(([text, standard, vim]) => 
            el('tr', '', 
              el('td', '', text),
              el('td', 
                vim == null 
                  ? {'class': 'key spanned', colspan: 2} 
                  : {'class': 'key'}, 
                standard
              ),
              vim == null 
                ? null 
                : el('td', 'key', vim),
            )
          )
        )
      ),
      el('form', {method: 'dialog'}, 
        el('button', null, 'Close'),
      ),
    )
  }

  fullscreen_editor() {
    this.root.classList.toggle('fullscreen_editor')
    this.editor.ace_editor.resize()
    if(this.root.classList.contains('fullscreen_editor')) {
      this.editor.focus()
    }
  }

}
