import { Callable, Callable1, CanvasLayer, Expression, GArray, GDictionary, GError, InputEvent, LineEdit, RichTextLabel } from 'godot'
import { onready } from 'godot.annotations'
import AutocompleteLine from './autocomplete_line'

const Action = {
  Toggle: '_dev_console_toggle',
  Cancel: 'ui_cancel',
  Up: 'ui_up',
  Down: 'ui_down',
  TextComplete: 'ui_text_completion_replace'
} as const

export default class DevConsole extends CanvasLayer {

  @onready("Container/VBoxContainer/Output")
  private output!: RichTextLabel

  @onready("Container/VBoxContainer/Input")
  private input!: AutocompleteLine

  private history: string[] = []
  private expression = new Expression()
  private parse_callable!: Callable1<string>
  private history_index: number = -1

  _ready(): void {
    this._toggle()
    const method_names = this.get_method_list().map(Callable.create((x: GDictionary) => x.get('name')))
    this.input.autocomplete_list = method_names
    this.parse_callable = Callable.create(
      this,
      this.submit_command
    )
  }

  _input(event: InputEvent): void {

    if (event.is_action_pressed(Action.Toggle)) {
      this._toggle()
      return
    }

    if (!this.visible) {
      return
    }

    if (event.is_action_pressed(Action.Cancel)) {
      this._cancel()
    } else if (event.is_action_pressed(Action.Up)) {
      this._previous_command()
    } else if (event.is_action_pressed(Action.TextComplete)) {
      //this._try_autocomplete()
    }
  }

  _reset() {
    this.history_index = -1
  }

  _cancel() {
    this._reset()

    if (this.input.has_focus()) {
      this.input.release_focus()
    } else {
      this._hide()
    }
  }

  _previous_command() {
    if (this.history_index < 0) {
      this.history_index = this.history.length
    }

    this.history_index -= 1
    this.input.text = this.history[this.history_index]
  }

  async _show() {
    await this.get_tree().process_frame.as_promise()

    this.input.text_submitted.connect(this.parse_callable)
    this.input.grab_focus()
  }

  _hide() {
    this.input.text_submitted.disconnect(this.parse_callable)
    this.input.release_focus()
  }

  async _toggle() {
    this._reset()

    this.visible = !this.visible

    if (this.visible) {
      this._show()
    } else {
      this._hide()
    }
  }

  print(value: string) {
    this.output.append_text(value)
    this.output.append_text('\n')
  }

  wrap_in_tag(text: string, tag: [string, string]): string {
    return `[${tag[0]}=${tag[1]}]${text}[/${tag[0]}]`
  }

  format(text: string, tags: Record<string, string>): string {
    return Object.entries(tags).reduce(this.wrap_in_tag, text)
  }

  print_error(error: string) {
    this.print(this.format(error, { color: 'red' }))
  }

  echo(command: string) {
    this.print(this.format(`> ${command}`, { color: 'white' }))
  }

  submit_command(command: string) {
    this.input.clear()
    this.history.push(command)
    this.echo(command)
    const error = this.expression.parse(command)

    if (error !== GError.OK) {
      this.print_error(this.expression.get_error_text())
      return
    }

    const result = this.expression.execute(new GArray(), this)

    if (this.expression.has_execute_failed()) {
      this.print_error(this.expression.get_error_text())
    } else {
      this.print(result.toString())
    }
  }
}
