import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import { WebLinksAddon } from '@xterm/addon-web-links'
import { Mutex } from './Mutex.js'

/**
 * xterm.js based terminal output for the execution dialog.
 *
 * the xterm.js methods for write(), reset() and clear() appear to be async,
 * but they do not return a Promise and instead use a callback. When calling
 * these methods in quick succession, the output can get garbled due to race
 * conditions.
 *
 * To avoid this, this class uses Mutex around those methods to ensure that
 * only one write OR reset is executed at a time, is completed, and the calls
 * occour in sequential order.
 */
export class OutputTerminal {
  constructor (executionTrackingId) {
    this.executionTrackingId = executionTrackingId
    this.writeMutex = new Mutex()
    const linkHandler = {
      activate (event, text, _range) {
        event.preventDefault()
        window.open(text, '_blank')
      }
    }

    this.terminal = new Terminal({
      convertEol: true,
      linkHandler,
      scrollback: 10000
    })

    const fitAddon = new FitAddon()
    this.terminal.loadAddon(fitAddon)
    this.terminal.fit = fitAddon

    this.terminal.loadAddon(new WebLinksAddon((event, uri) => linkHandler.activate(event, uri)))
    this.linkHandlerConfigured = true
  }

  async write (out, then) {
    const unlock = await this.writeMutex.lock()

    try {
      await new Promise(resolve => {
        this.terminal.write(out, () => {
          resolve()
        })
      })
    } finally {
      unlock()

      if (then != null && then !== undefined) {
        then()
      }
    }
  }

  async reset () {
    const unlock = await this.writeMutex.lock()

    try {
      await new Promise(resolve => {
        this.terminal.clear()
        this.terminal.reset()
        resolve()
      })
    } finally {
      unlock()
    }
  }

  fit () {
    this.terminal.fit.fit()
  }

  open (el) {
    this.terminal.open(el)
  }

  close () {
    this.terminal.dispose()
  }

  resize (cols, rows) {
    this.terminal.resize(cols, rows)
  }

  /**
   * Get the terminal buffer content as a string.
   * This method is intended for use in integration tests to verify output.
   * @returns {string} The terminal buffer content as a string
   */
  getBufferAsString () {
    if (!this.terminal) {
      return ''
    }

    const buffer = this.terminal.buffer.active
    let text = ''
    for (let i = 0; i < buffer.length; i++) {
      const line = buffer.getLine(i)
      if (line) {
        text += line.translateToString(true) + '\n'
      }
    }
    return text
  }
}
