function delay(msecs) {
  return new Promise((resolve) => setTimeout(resolve, msecs));
}

class LineBreakTransformer {
  chunks = '';
  port = null;

  transform(chunk, controller) {
    // Append new chunks to existing chunks.
    this.chunks += chunk;
    // For each line breaks in chunks, send the parsed lines out.
    const lines = this.chunks.split('\r\n');
    this.chunks = lines.pop();
    lines.forEach((line) => controller.enqueue(line + '\r\n'));
  }

  flush(controller) {
    // When the stream is closed, flush any remaining chunks out.
    controller.enqueue(this.chunks);
  }
}

export class SerialConsole {
  connected = false;
  constructor(port) {
    this.port = port;
    this.controller = new AbortController();
    this.signal = this.controller.signal;
    this.onOutput = (text) => {
      console.log(text);
    };
  }

  async connect() {
    try {
      await this.port.open({ baudRate: 115200 });
      this.connected = true;
      await this.port.readable
        .pipeThrough(new TextDecoderStream(), { signal: this.signal })
        .pipeThrough(new TransformStream(new LineBreakTransformer()))
        .pipeTo(
          new WritableStream({
            write: (chunk) => {
              this.addLine(chunk.replace('\r', ''));
            },
          }),
        );

        // Check AFTER the pipeTo has completed (or been aborted)
      if (!this.signal.aborted) {
        this.addLine('\n\n*** Terminal disconnected');
        this.connected = false;
      }
    } catch (e) {
      this.addLine(`\n\n*** Terminal disconnected: ${e}`);
      this.connected = false;
    } finally {
      await delay(100);
    }
  }

  addLine(text) {
    this.onOutput(text);
  }

  async sendCommand(command) {
    const encoder = new TextEncoder();
    const writer = this.port.writable.getWriter(); // Get writer from 'this.port'
    await writer.write(encoder.encode(command + '\r\n'));
    try {
      writer.releaseLock();
    } catch (err) {
      console.error('Ignoring release lock error', err);
    }
  }

  async disconnect() {
    this.controller.abort();
    await delay(50);
    await this.port.close();
  }

  async reset() {
    console.debug('Triggering reset');
    await this.port.setSignals({
      dataTerminalReady: false,
      requestToSend: true,
    });
    await delay(250);
    await this.port.setSignals({
      dataTerminalReady: false,
      requestToSend: false,
    });

    await delay(1250);
  }
}