// src/transport/web-transports/web-serialport.ts

import Logger from '../../logger.js';
import { allocUint8Array } from '../../utils/utils.js';
import { Mutex } from 'async-mutex';

import {
  ModbusTimeoutError,
  ModbusTooManyEmptyReadsError,
  ModbusFlushError,
  WebSerialTransportError,
  WebSerialConnectionError,
  WebSerialReadError,
  WebSerialWriteError,
  ModbusCRCError,
  ModbusParityError,
  ModbusNoiseError,
  ModbusFramingError,
  ModbusOverrunError,
  ModbusCollisionError,
  ModbusConfigError,
  ModbusBaudRateError,
  ModbusSyncError,
  ModbusFrameBoundaryError,
  ModbusLRCError,
  ModbusChecksumError,
  ModbusDataConversionError,
  ModbusBufferOverflowError,
  ModbusBufferUnderrunError,
  ModbusMemoryError,
  ModbusStackOverflowError,
  ModbusResponseError,
  ModbusInvalidAddressError,
  ModbusInvalidFunctionCodeError,
  ModbusInvalidQuantityError,
  ModbusIllegalDataAddressError,
  ModbusIllegalDataValueError,
  ModbusSlaveBusyError,
  ModbusAcknowledgeError,
  ModbusSlaveDeviceFailureError,
  ModbusMalformedFrameError,
  ModbusInvalidFrameLengthError,
  ModbusInvalidTransactionIdError,
  ModbusUnexpectedFunctionCodeError,
  ModbusConnectionRefusedError,
  ModbusConnectionTimeoutError,
  ModbusNotConnectedError,
  ModbusAlreadyConnectedError,
  ModbusInsufficientDataError,
  ModbusGatewayPathUnavailableError,
  ModbusGatewayTargetDeviceError,
  ModbusInvalidStartingAddressError,
  ModbusMemoryParityError,
  ModbusBroadcastError,
  ModbusGatewayBusyError,
  ModbusDataOverrunError,
} from '../../errors.js';

import {
  Transport,
  WebSerialPort,
  WebSerialPortOptions,
  WebSerialTransportOptions,
  DeviceStateHandler,
  ConnectionErrorType,
  PortStateHandler,
  RSMode,
} from '../../types/modbus-types.js';

const WEB_SERIAL_CONSTANTS = {
  MIN_BAUD_RATE: 300,
  MAX_BAUD_RATE: 115200,
  MAX_READ_BUFFER_SIZE: 65536,
  POLL_INTERVAL_MS: 10,
} as const;

const loggerInstance = new Logger();
loggerInstance.setLogFormat(['timestamp', 'level', 'logger']);
loggerInstance.setCustomFormatter('logger', value => (value ? `[${value}]` : ''));
const logger = loggerInstance.createLogger('WebSerialTransport');
logger.setLevel('info');

const ERROR_HANDLERS: Record<string, () => void> = {
  [ModbusTimeoutError.name]: () => logger.error('Timeout error detected'),
  [ModbusCRCError.name]: () => logger.error('CRC error detected'),
  [ModbusParityError.name]: () => logger.error('Parity error detected'),
  [ModbusNoiseError.name]: () => logger.error('Noise error detected'),
  [ModbusFramingError.name]: () => logger.error('Framing error detected'),
  [ModbusOverrunError.name]: () => logger.error('Overrun error detected'),
  [ModbusCollisionError.name]: () => logger.error('Collision error detected'),
  [ModbusConfigError.name]: () => logger.error('Configuration error detected'),
  [ModbusBaudRateError.name]: () => logger.error('Baud rate error detected'),
  [ModbusSyncError.name]: () => logger.error('Sync error detected'),
  [ModbusFrameBoundaryError.name]: () => logger.error('Frame boundary error detected'),
  [ModbusLRCError.name]: () => logger.error('LRC error detected'),
  [ModbusChecksumError.name]: () => logger.error('Checksum error detected'),
  [ModbusDataConversionError.name]: () => logger.error('Data conversion error detected'),
  [ModbusBufferOverflowError.name]: () => logger.error('Buffer overflow error detected'),
  [ModbusBufferUnderrunError.name]: () => logger.error('Buffer underrun error detected'),
  [ModbusMemoryError.name]: () => logger.error('Memory error detected'),
  [ModbusStackOverflowError.name]: () => logger.error('Stack overflow error detected'),
  [ModbusResponseError.name]: () => logger.error('Response error detected'),
  [ModbusInvalidAddressError.name]: () => logger.error('Invalid address error detected'),
  [ModbusInvalidFunctionCodeError.name]: () => logger.error('Invalid function code error detected'),
  [ModbusInvalidQuantityError.name]: () => logger.error('Invalid quantity error detected'),
  [ModbusIllegalDataAddressError.name]: () => logger.error('Illegal data address error detected'),
  [ModbusIllegalDataValueError.name]: () => logger.error('Illegal data value error detected'),
  [ModbusSlaveBusyError.name]: () => logger.error('Slave busy error detected'),
  [ModbusAcknowledgeError.name]: () => logger.error('Acknowledge error detected'),
  [ModbusSlaveDeviceFailureError.name]: () => logger.error('Slave device failure error detected'),
  [ModbusMalformedFrameError.name]: () => logger.error('Malformed frame error detected'),
  [ModbusInvalidFrameLengthError.name]: () => logger.error('Invalid frame length error detected'),
  [ModbusInvalidTransactionIdError.name]: () =>
    logger.error('Invalid transaction ID error detected'),
  [ModbusUnexpectedFunctionCodeError.name]: () =>
    logger.error('Unexpected function code error detected'),
  [ModbusConnectionRefusedError.name]: () => logger.error('Connection refused error detected'),
  [ModbusConnectionTimeoutError.name]: () => logger.error('Connection timeout error detected'),
  [ModbusNotConnectedError.name]: () => logger.error('Not connected error detected'),
  [ModbusAlreadyConnectedError.name]: () => logger.error('Already connected error detected'),
  [ModbusInsufficientDataError.name]: () => logger.error('Insufficient data error detected'),
  [ModbusGatewayPathUnavailableError.name]: () =>
    logger.error('Gateway path unavailable error detected'),
  [ModbusGatewayTargetDeviceError.name]: () => logger.error('Gateway target device error detected'),
  [ModbusInvalidStartingAddressError.name]: () =>
    logger.error('Invalid starting address error detected'),
  [ModbusMemoryParityError.name]: () => logger.error('Memory parity error detected'),
  [ModbusBroadcastError.name]: () => logger.error('Broadcast error detected'),
  [ModbusGatewayBusyError.name]: () => logger.error('Gateway busy error detected'),
  [ModbusDataOverrunError.name]: () => logger.error('Data overrun error detected'),
  [ModbusTooManyEmptyReadsError.name]: () => logger.error('Too many empty reads error detected'),
  [ModbusFlushError.name]: () => logger.error('Flush error detected'),
  [WebSerialTransportError.name]: () => logger.error('WebSerial transport error detected'),
  [WebSerialConnectionError.name]: () => logger.error('WebSerial connection error detected'),
  [WebSerialReadError.name]: () => logger.error('WebSerial read error detected'),
  [WebSerialWriteError.name]: () => logger.error('WebSerial write error detected'),
};

const handleModbusError = (err: Error): void => {
  const handler = ERROR_HANDLERS[err.constructor.name];
  if (handler) {
    handler();
  } else {
    logger.error(`Unknown error: ${err.message}`);
  }
};

class WebSerialTransport implements Transport {
  public isOpen: boolean = false;

  private portFactory: () => Promise<WebSerialPort>;
  private port: WebSerialPort | null = null;
  private options: Required<WebSerialTransportOptions>;
  private reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
  private writer: WritableStreamDefaultWriter<Uint8Array> | null = null;
  private readBuffer: Uint8Array;

  private _connectedSlaveIds: Set<number> = new Set();

  private _isPortReady: boolean = false;
  private _reconnectAttempts: number = 0;
  private _shouldReconnect: boolean = true;
  private _isConnecting: boolean = false;
  private _isDisconnecting: boolean = false;

  private _isFlushing: boolean = false;
  private _pendingFlushPromises: Array<() => void> = [];
  private _reconnectTimer: NodeJS.Timeout | null = null;
  private _emptyReadCount: number = 0;
  private _readLoopActive: boolean = false;
  private _readLoopAbortController: AbortController | null = null;
  private _operationMutex: Mutex;

  private _connectionPromise: Promise<void> | null = null;
  private _resolveConnection: (() => void) | null = null;
  private _rejectConnection: ((reason?: Error | string | null) => void) | null = null;

  private _portClosePromise: Promise<void> | null = null;
  private _portCloseResolve: (() => void) | null = null;

  private _deviceStateHandler: DeviceStateHandler | null = null;
  private _portStateHandler: PortStateHandler | null = null;

  private _wasEverConnected: boolean = false;

  constructor(portFactory: () => Promise<WebSerialPort>, options: WebSerialTransportOptions = {}) {
    if (typeof portFactory !== 'function') {
      throw new WebSerialTransportError(
        'A port factory function must be provided to WebSerialTransport'
      );
    }
    this.portFactory = portFactory;
    this.options = {
      baudRate: 9600,
      dataBits: 8,
      stopBits: 1,
      parity: 'none',
      readTimeout: 1000,
      writeTimeout: 1000,
      reconnectInterval: 3000,
      maxReconnectAttempts: Infinity,
      maxEmptyReadsBeforeReconnect: 10,
      RSMode: options.RSMode || 'RS485',
      ...options,
    };
    this.readBuffer = new Uint8Array(0);
    this._operationMutex = new Mutex();
  }

  public getRSMode(): RSMode {
    return this.options.RSMode;
  }

  public setDeviceStateHandler(handler: DeviceStateHandler): void {
    this._deviceStateHandler = handler;
  }

  public setPortStateHandler(handler: PortStateHandler): void {
    this._portStateHandler = handler;
  }

  public async disableDeviceTracking(): Promise<void> {
    this._deviceStateHandler = null;
    logger.debug('Device connection tracking disabled');
  }

  public async enableDeviceTracking(handler?: DeviceStateHandler): Promise<void> {
    if (handler) {
      this._deviceStateHandler = handler;
    }
    logger.debug('Device connection tracking enabled');
  }

  public notifyDeviceConnected(slaveId: number): void {
    if (this._connectedSlaveIds.has(slaveId)) {
      return;
    }
    this._connectedSlaveIds.add(slaveId);
    if (this._deviceStateHandler) {
      this._deviceStateHandler(slaveId, true);
    }
  }

  public notifyDeviceDisconnected(
    slaveId: number,
    errorType: ConnectionErrorType,
    errorMessage: string
  ): void {
    if (!this._connectedSlaveIds.has(slaveId)) {
      return;
    }
    this._connectedSlaveIds.delete(slaveId);
    if (this._deviceStateHandler) {
      this._deviceStateHandler(slaveId, false, { type: errorType, message: errorMessage });
    }
  }

  public removeConnectedDevice(slaveId: number): void {
    if (this._connectedSlaveIds.has(slaveId)) {
      this._connectedSlaveIds.delete(slaveId);
      logger.debug(`Manually removed device ${slaveId} from connected set`);
    }
  }

  public isPortReady(): boolean {
    const ready = this.isOpen && this._isPortReady && this.writer !== null;
    logger.debug(
      `isPortReady check: isOpen=${this.isOpen}, _isPortReady=${this._isPortReady}, writer=${this.writer !== null}, result=${ready}`
    );
    return ready;
  }

  private async _notifyPortConnected(): Promise<void> {
    this._wasEverConnected = true;
    if (this._portStateHandler) {
      this._portStateHandler(true, [], undefined);
    }
  }

  private async _notifyPortDisconnected(
    errorType: ConnectionErrorType = ConnectionErrorType.UnknownError,
    errorMessage: string = 'Port disconnected'
  ): Promise<void> {
    if (!this._wasEverConnected) {
      logger.debug('Skipping DISCONNECTED — port was never connected');
      return;
    }

    if (this._portStateHandler) {
      this._portStateHandler(false, [], { type: errorType, message: errorMessage });
    }
  }

  private _handlePortClose(): void {
    logger.info('WebSerial port physically closed (via close())');
    if (this._portCloseResolve) {
      this._portCloseResolve();
      this._portCloseResolve = null;
    }
    this._handleConnectionLoss('Port closed via close()');
  }

  private _watchForPortClose(): void {
    if (!this._portClosePromise) return;

    this._portClosePromise
      .then(() => {
        this._handlePortClose();
      })
      .catch(() => {});
  }

  private async _releaseAllResources(hardClose = false): Promise<void> {
    logger.debug('Releasing WebSerial resources');

    this._readLoopActive = false;
    if (this._readLoopAbortController) {
      this._readLoopAbortController.abort();
      this._readLoopAbortController = null;
    }

    if (this.reader) {
      try {
        await this.reader.cancel();
        this.reader.releaseLock();
      } catch (err: unknown) {
        logger.debug('Error cancelling reader:', (err as Error).message);
      }
      this.reader = null;
    }

    if (this.writer) {
      try {
        this.writer.releaseLock();
        if (hardClose && this.port && this.port.writable) {
          await this.writer.close().catch(() => {});
        }
      } catch (err: unknown) {
        logger.debug('Error releasing writer:', (err as Error).message);
      }
      this.writer = null;
    }

    if (hardClose && this.port) {
      try {
        await this.port.close();
        logger.debug('Port closed successfully');
        if (this._portCloseResolve) {
          this._portCloseResolve();
          this._portCloseResolve = null;
        }
      } catch (err: unknown) {
        logger.warn(`Error closing port: ${(err as Error).message}`);
      }
      this.port = null;
    }

    this.isOpen = false;
    this._isPortReady = false;
    this.readBuffer = allocUint8Array(0);
    this._emptyReadCount = 0;

    this._connectedSlaveIds.clear();
  }

  async connect(): Promise<void> {
    if (this._isConnecting) {
      logger.warn('Connection had already started, waiting...');
      return this._connectionPromise ?? Promise.resolve();
    }

    if (!this.isOpen && !this._isConnecting && !this._isDisconnecting) {
      logger.debug('Transport in disconnected state, resetting resources');
      await this._releaseAllResources(false);
    }

    this._isConnecting = true;
    this._connectionPromise = new Promise((resolve, reject) => {
      this._resolveConnection = resolve;
      this._rejectConnection = reject;
    });

    try {
      if (this._reconnectTimer) {
        clearTimeout(this._reconnectTimer);
        this._reconnectTimer = null;
      }

      this._shouldReconnect = true;
      this._reconnectAttempts = 0;
      this._emptyReadCount = 0;

      logger.debug('Requesting new SerialPort instance from factory...');

      if (this.port && this.isOpen) {
        logger.debug('Closing existing port before reconnecting');
        await this._releaseAllResources(true);
      }

      this.port = await this.portFactory();
      if (!this.port || typeof this.port.open !== 'function') {
        throw new WebSerialConnectionError(
          'Port factory did not return a valid SerialPort object.'
        );
      }
      logger.debug('New SerialPort instance acquired.');

      if (
        this.options.baudRate < WEB_SERIAL_CONSTANTS.MIN_BAUD_RATE ||
        this.options.baudRate > WEB_SERIAL_CONSTANTS.MAX_BAUD_RATE
      ) {
        throw new ModbusConfigError(`Invalid baud rate: ${this.options.baudRate}`);
      }

      await this.port.open({
        baudRate: this.options.baudRate,
        dataBits: this.options.dataBits,
        stopBits: this.options.stopBits,
        parity: this.options.parity,
        flowControl: 'none',
      } as WebSerialPortOptions);

      const readable = this.port.readable;
      const writable = this.port.writable;

      if (!readable || !writable) {
        const errorMsg = 'Serial port not readable/writable after open';
        logger.error(errorMsg);
        await this._releaseAllResources(true);
        throw new WebSerialConnectionError(errorMsg);
      }

      if (this.reader) {
        try {
          await this.reader.cancel();
          this.reader.releaseLock();
        } catch (err: unknown) {
          logger.debug(`Could not cancel previous reader, continuing: ${(err as Error).message}`);
        }
        this.reader = null;
      }
      if (this.writer) {
        try {
          this.writer.releaseLock();
        } catch (err: unknown) {
          logger.debug(`Could not release previous writer, continuing: ${(err as Error).message}`);
        }
        this.writer = null;
      }

      this.reader = readable.getReader();
      this.writer = writable.getWriter();
      logger.debug(`New writer created: ${this.writer !== null}, reader: ${this.reader !== null}`);
      this.isOpen = true;
      this._isPortReady = true;
      this._reconnectAttempts = 0;

      this._portClosePromise = new Promise<void>(resolve => {
        this._portCloseResolve = resolve;
      });
      this._watchForPortClose();

      this._startReading();
      logger.info('WebSerial port opened successfully with new instance');

      await this._notifyPortConnected();

      if (this._resolveConnection) {
        this._resolveConnection();
        this._resolveConnection = null;
        this._rejectConnection = null;
      }
    } catch (err: unknown) {
      logger.error(`Failed to open WebSerial port: ${(err as Error).message}`);
      this.isOpen = false;
      this._isPortReady = false;
      this._isConnecting = false;

      if (this._wasEverConnected) {
        await this._notifyPortDisconnected(
          ConnectionErrorType.ConnectionLost,
          (err as Error).message
        );
      }

      if (this._shouldReconnect && this._reconnectAttempts < this.options.maxReconnectAttempts) {
        logger.info('Auto-reconnect enabled, starting reconnect process...');
        this._scheduleReconnect(err as Error);
      } else {
        if (this._rejectConnection) {
          this._rejectConnection(err as Error);
          this._resolveConnection = null;
          this._rejectConnection = null;
        }
        throw err;
      }
    } finally {
      this._isConnecting = false;
    }
  }

  private _startReading(): void {
    if (!this.isOpen || !this.reader || this._readLoopActive) {
      logger.warn('Cannot start reading: port not open, no reader, or loop already active');
      return;
    }

    this._readLoopActive = true;
    this._readLoopAbortController = new AbortController();
    logger.debug('Starting read loop');

    const loop = async (): Promise<void> => {
      try {
        while (
          this.isOpen &&
          this.reader &&
          this._readLoopAbortController &&
          !this._readLoopAbortController.signal.aborted
        ) {
          try {
            const { value, done } = await this.reader.read();

            if (done || this._readLoopAbortController.signal.aborted) {
              logger.debug('Read stream ended. It might restart if the port is still open.');
              break;
            }

            if (value && value.length > 0) {
              this._emptyReadCount = 0;
              if (
                this.readBuffer.length + value.length >
                WEB_SERIAL_CONSTANTS.MAX_READ_BUFFER_SIZE
              ) {
                logger.error('Buffer overflow detected');
                throw new ModbusBufferOverflowError(
                  this.readBuffer.length + value.length,
                  WEB_SERIAL_CONSTANTS.MAX_READ_BUFFER_SIZE
                );
              }

              const newBuffer = new Uint8Array(this.readBuffer.length + value.length);
              newBuffer.set(this.readBuffer, 0);
              newBuffer.set(value, this.readBuffer.length);
              this.readBuffer = newBuffer;
            } else {
              this._emptyReadCount++;
              logger.debug(`Empty read received, count: ${this._emptyReadCount}`);
              if (this._emptyReadCount >= this.options.maxEmptyReadsBeforeReconnect) {
                logger.warn(
                  `Max empty reads (${this.options.maxEmptyReadsBeforeReconnect}) reached. Triggering reconnect.`
                );
                this._handleConnectionLoss('Too many empty reads');
                break;
              }
            }
          } catch (readErr: unknown) {
            if (this._readLoopAbortController.signal.aborted) {
              logger.debug('Read loop aborted');
              break;
            }
            logger.warn(`Read error: ${(readErr as Error).message}`);
            this._handleConnectionLoss(`Read error: ${(readErr as Error).message}`);
            break;
          }
        }
      } catch (loopErr: unknown) {
        logger.error(`Unexpected error in read loop: ${(loopErr as Error).message}`);
        this._handleConnectionLoss(`Read loop failed: ${(loopErr as Error).message}`);
      } finally {
        this._readLoopActive = false;
        logger.debug('Read loop finished');
      }
    };

    loop().catch(err => {
      logger.error('Read loop promise rejected:', err);
      this._handleConnectionLoss(`Read loop promise rejected: ${err.message}`);
    });
  }

  async write(buffer: Uint8Array): Promise<void> {
    if (this._isFlushing) {
      logger.debug('Write operation aborted due to ongoing flush');
      throw new ModbusFlushError();
    }

    if (!this.isPortReady()) {
      logger.warn(`Write attempted on disconnected port`);
      throw new WebSerialWriteError('Port is not ready for writing');
    }

    if (buffer.length === 0) {
      throw new ModbusBufferUnderrunError(0, 1);
    }

    const release = await this._operationMutex.acquire();
    try {
      const timeout = this.options.writeTimeout;
      const abort = new AbortController();
      const timer = setTimeout(() => abort.abort(), timeout);

      try {
        await this.writer!.write(buffer);
        logger.debug(`Wrote ${buffer.length} bytes to WebSerial port`);
      } catch (err: unknown) {
        if (abort.signal.aborted) {
          logger.warn(`Write timeout on WebSerial port`);
          const timeoutError = new ModbusTimeoutError('Write timeout');
          this._onError(timeoutError);
          throw timeoutError;
        } else {
          logger.error(`Write error on WebSerial port: ${(err as Error).message}`);
          const isPhysical =
            (err as Error).message.includes('failed to write') ||
            (err as Error).message.includes('device disconnected') ||
            (err as Error).message.includes('The device has been lost');

          if (isPhysical) {
            if (this.writer) {
              try {
                this.writer.releaseLock();
              } catch (err: unknown) {
                logger.debug(
                  `Could not release writer is physical, continuing: ${(err as Error).message}`
                );
              }
              this.writer = null;
            }
            if (this.port && this.isOpen) {
              this.port.close().catch(() => {});
            }
            this._handleConnectionLoss('Write failed — port unplugged');
          } else {
            this._onError(err as Error);
          }
          throw err;
        }
      } finally {
        clearTimeout(timer);
        abort.abort();
      }
    } finally {
      release();
    }
  }

  async read(length: number, timeout: number = this.options.readTimeout): Promise<Uint8Array> {
    if (!this.isPortReady()) {
      throw new WebSerialReadError('Port is not ready');
    }

    if (length <= 0) {
      throw new ModbusDataConversionError(length, 'positive integer');
    }

    const release = await this._operationMutex.acquire();
    const start = Date.now();
    try {
      return new Promise<Uint8Array>((resolve, reject) => {
        const check = () => {
          if (!this.isOpen) {
            return reject(new WebSerialReadError('Port is closed'));
          }
          if (this._isFlushing) {
            return reject(new ModbusFlushError());
          }
          if (this.readBuffer.length >= length) {
            const data = this.readBuffer.slice(0, length);
            this.readBuffer = this.readBuffer.slice(length);
            if (data.length !== length) {
              return reject(new ModbusInsufficientDataError(data.length, length));
            }
            return resolve(data);
          }

          if (Date.now() - start > timeout) {
            return reject(
              new ModbusTimeoutError(`Read timeout: No data received within ${timeout}ms`)
            );
          }

          setTimeout(check, WEB_SERIAL_CONSTANTS.POLL_INTERVAL_MS);
        };
        check();
      });
    } finally {
      release();
    }
  }

  async disconnect(): Promise<void> {
    logger.info('Disconnecting WebSerial transport...');
    this._shouldReconnect = false;
    this._isDisconnecting = true;

    try {
      if (this._reconnectTimer) {
        clearTimeout(this._reconnectTimer);
        this._reconnectTimer = null;
      }

      await this._releaseAllResources(true);
      await this._notifyPortDisconnected(
        ConnectionErrorType.ManualDisconnect,
        'Port closed by user'
      );

      if (this._rejectConnection) {
        this._rejectConnection(new WebSerialConnectionError('Connection manually disconnected'));
        this._resolveConnection = null;
        this._rejectConnection = null;
      }

      logger.info('WebSerial transport disconnected successfully');
    } catch (err: unknown) {
      logger.error(`Error during WebSerial transport shutdown: ${(err as Error).message}`);
    } finally {
      this._isDisconnecting = false;
      this.isOpen = false;
      this._isPortReady = false;
      this.port = null;
      this.reader = null;
      this.writer = null;
    }
  }

  async flush(): Promise<void> {
    logger.debug('Flushing WebSerial transport buffer');

    if (this._isFlushing) {
      logger.warn('Flush already in progress');
      await Promise.all(this._pendingFlushPromises).catch(() => {});
      return;
    }

    this._isFlushing = true;
    const flushPromise = new Promise<void>(resolve => {
      this._pendingFlushPromises.push(resolve);
    });

    try {
      this.readBuffer = allocUint8Array(0);
      this._emptyReadCount = 0;
      logger.debug('WebSerial read buffer flushed');
    } finally {
      this._isFlushing = false;
      this._pendingFlushPromises.forEach(resolve => resolve());
      this._pendingFlushPromises = [];
      logger.debug('WebSerial transport flush completed');
    }

    return flushPromise;
  }

  private _onError(err: Error): void {
    handleModbusError(err);
    logger.warn(`Modbus error: ${err.message}`);
  }

  private _handleConnectionLoss(reason: string): void {
    if (!this.isOpen && !this._isConnecting) return;

    logger.warn(`Connection loss detected: ${reason}`);

    if (this._isConnecting && this._rejectConnection) {
      const err = new WebSerialConnectionError(`Connection lost during connect: ${reason}`);
      this._rejectConnection(err);
      this._resolveConnection = null;
      this._rejectConnection = null;
    }

    this.isOpen = false;
    this._isPortReady = false;
    this._readLoopActive = false;

    if (this.writer) {
      try {
        this.writer.releaseLock();
      } catch (err: unknown) {
        logger.debug(
          `Could not release writer is connection loss, continuing: ${(err as Error).message}`
        );
      }
      this.writer = null;
    }
    if (this.reader) {
      try {
        this.reader.cancel();
        this.reader.releaseLock();
      } catch (err: unknown) {
        logger.debug(
          `Could not release reader is connection loss, continuing: ${(err as Error).message}`
        );
      }
      this.reader = null;
    }

    this._notifyPortDisconnected(ConnectionErrorType.ConnectionLost, reason);

    if (this._shouldReconnect && !this._isDisconnecting) {
      this._scheduleReconnect(new Error(reason));
    }
  }

  private _scheduleReconnect(err: Error): void {
    if (!this._shouldReconnect || this._isDisconnecting) {
      logger.info('Reconnect disabled or disconnecting, not scheduling');
      return;
    }

    if (this._reconnectTimer) {
      clearTimeout(this._reconnectTimer);
    }

    if (this._reconnectAttempts >= this.options.maxReconnectAttempts) {
      logger.error(
        `Max reconnect attempts (${this.options.maxReconnectAttempts}) reached for WebSerial port`
      );
      const maxAttemptsError = new WebSerialConnectionError(
        `Max reconnect attempts (${this.options.maxReconnectAttempts}) reached`
      );

      this._notifyPortDisconnected(ConnectionErrorType.MaxReconnect, maxAttemptsError.message);

      if (this._rejectConnection) {
        this._rejectConnection(maxAttemptsError);
        this._resolveConnection = null;
        this._rejectConnection = null;
      }
      this._shouldReconnect = false;

      return;
    }

    this._reconnectAttempts++;
    logger.info(
      `Scheduling reconnect in ${this.options.reconnectInterval} ms (attempt ${this._reconnectAttempts}) due to: ${err.message}`
    );

    this._reconnectTimer = setTimeout(() => {
      this._reconnectTimer = null;
      this._attemptReconnect();
    }, this.options.reconnectInterval);
  }

  private async _attemptReconnect(): Promise<void> {
    try {
      if (this.port && this.isOpen) {
        await this._releaseAllResources(true);
      }

      this.port = await this.portFactory();
      if (!this.port || typeof this.port.open !== 'function') {
        throw new WebSerialConnectionError(
          'Port factory did not return a valid SerialPort object.'
        );
      }

      await this.port.open({
        baudRate: this.options.baudRate,
        dataBits: this.options.dataBits,
        stopBits: this.options.stopBits,
        parity: this.options.parity,
        flowControl: 'none',
      } as WebSerialPortOptions);

      const readable = this.port.readable;
      const writable = this.port.writable;

      if (!readable || !writable) {
        throw new WebSerialConnectionError('Serial port not readable/writable after open');
      }

      if (this.reader) {
        try {
          await this.reader.cancel();
          this.reader.releaseLock();
        } catch (err: unknown) {
          logger.debug(
            `Could not release reader is attempt reconnect, continuing: ${(err as Error).message}`
          );
        }
        this.reader = null;
      }
      if (this.writer) {
        try {
          this.writer.releaseLock();
        } catch (err: unknown) {
          logger.debug(
            `Could not release writer is attempt reconnect, continuing: ${(err as Error).message}`
          );
        }
        this.writer = null;
      }

      this.reader = readable.getReader();
      this.writer = writable.getWriter();

      logger.debug(`New writer created: ${this.writer !== null}, reader: ${this.reader !== null}`);

      this.isOpen = true;
      this._isPortReady = true;
      this._reconnectAttempts = 0;

      this._portClosePromise = new Promise<void>(resolve => {
        this._portCloseResolve = resolve;
      });
      this._watchForPortClose();

      this._startReading();
      logger.info(`Reconnect successful`);
      await this._notifyPortConnected();

      if (this._resolveConnection) {
        this._resolveConnection();
        this._resolveConnection = null;
        this._rejectConnection = null;
      }
    } catch (err: unknown) {
      logger.warn(`Reconnect attempt failed: ${(err as Error).message}`);
      this._reconnectAttempts++;

      if (
        this._shouldReconnect &&
        !this._isDisconnecting &&
        this._reconnectAttempts <= this.options.maxReconnectAttempts
      ) {
        this._scheduleReconnect(err as Error);
      } else {
        const maxAttemptsError = new WebSerialConnectionError(
          `Max reconnect attempts (${this.options.maxReconnectAttempts}) reached`
        );

        await this._notifyPortDisconnected(
          ConnectionErrorType.MaxReconnect,
          maxAttemptsError.message
        );

        if (this._rejectConnection) {
          this._rejectConnection(maxAttemptsError);
          this._resolveConnection = null;
          this._rejectConnection = null;
        }
        this._shouldReconnect = false;
      }
    }
  }

  destroy(): void {
    logger.info('Destroying WebSerial transport...');
    this._shouldReconnect = false;

    if (this._reconnectTimer) {
      clearTimeout(this._reconnectTimer);
      this._reconnectTimer = null;
    }

    if (this._rejectConnection) {
      this._rejectConnection(new WebSerialTransportError('Transport destroyed'));
      this._resolveConnection = null;
      this._rejectConnection = null;
    }

    if (this._portCloseResolve) {
      this._portCloseResolve();
      this._portCloseResolve = null;
    }

    this._readLoopActive = false;
    this._releaseAllResources(true).catch(() => {});

    this._notifyPortDisconnected(ConnectionErrorType.Destroyed, 'Transport destroyed');

    this.isOpen = false;
    this._isPortReady = false;
    this._isConnecting = false;
    this._isDisconnecting = false;
  }
}

export = WebSerialTransport;
