let port = null;
let reader = null;
let writer = null;
let readableAbort = null;
let writableAbort = null;

export function isConnected() {
  return port !== null;
}

export function getPort() {
  return port;
}

export function getWriter() {
  return writer;
}

export async function connect(baudRate = 115200, existingPort = null) {
  if (port) return port;
  port = existingPort || await navigator.serial.requestPort();
  try { await port.close(); } catch { /* already closed or never opened */ }
  await port.open({ baudRate });

  const textDecoder = new TextDecoderStream();
  const textEncoder = new TextEncoderStream();

  const readableController = new AbortController();
  const writableController = new AbortController();
  readableAbort = readableController;
  writableAbort = writableController;

  port.readable.pipeTo(textDecoder.writable, { signal: readableController.signal }).catch(() => {});
  textEncoder.readable.pipeTo(port.writable, { signal: writableController.signal }).catch(() => {});

  reader = textDecoder.readable.getReader();
  writer = textEncoder.writable.getWriter();

  readLoop();
  return port;
}

export async function disconnect() {
  if (reader) {
    try { await reader.cancel(); } catch (_) { /* already closed */ }
    reader = null;
  }
  if (writer) {
    try { await writer.close(); } catch (_) { /* already closed */ }
    writer = null;
  }
  if (readableAbort) {
    try { readableAbort.abort(); } catch (_) {}
    readableAbort = null;
  }
  if (writableAbort) {
    try { writableAbort.abort(); } catch (_) {}
    writableAbort = null;
  }
  if (port) {
    await new Promise(r => setTimeout(r, 50));
    try { await port.close(); } catch (_) { /* already closed */ }
    port = null;
  }
}

export async function writeString(str) {
  if (writer) await writer.write(str);
}

/** Send string in small chunks with delays to avoid overflowing device UART buffer (e.g. micro:bit).
 *  Optional onProgress(sentBytes, totalBytes) is called after each chunk. */
export async function writeStringChunked(str, chunkSize = 32, delayMs = 25, onProgress = null) {
  if (!writer) return;
  const total = str.length;
  for (let i = 0; i < str.length; i += chunkSize) {
    const chunk = str.slice(i, i + chunkSize);
    await writer.write(chunk);
    if (onProgress) onProgress(Math.min(i + chunk.length, total), total);
    if (i + chunkSize < str.length && delayMs > 0) {
      await new Promise((r) => setTimeout(r, delayMs));
    }
  }
}

const listeners = new Set();

export function onData(callback) {
  listeners.add(callback);
  return () => listeners.delete(callback);
}

async function readLoop() {
  try {
    while (reader) {
      const { value, done } = await reader.read();
      if (done) break;
      if (value) {
        for (const cb of listeners) cb(value);
      }
    }
  } catch (_) {
    /* port disconnected */
  }
}
