using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System.Net.Sockets;
using System.Reflection;
using System.Text;

namespace Inspectron.Epson;

/// <summary>
/// Main SDK class for Epson TM-m30III printer operations
/// </summary>
public class EpsonPrinter : IEpsonPrinter
{
    private readonly ILogger? _logger;
    private TcpClient? _tcpClient;
    private NetworkStream? _stream;
    private string? _printerIp;
    private int _printerPort;
    private int _timeoutSeconds;
    private static bool _initialized;

    /// <summary>
    /// Returns true if connected to the printer
    /// </summary>
    public bool IsConnected => _tcpClient?.Connected ?? false;

    /// <summary>
    /// Initialize a new instance of EpsonPrinter
    /// </summary>
    /// <param name="logger">Optional logger for diagnostic output</param>
    public EpsonPrinter(ILogger? logger = null)
    {
        _logger = logger;
        RegisterCodepages();
    }

    private static void RegisterCodepages()
    {
        if (_initialized) return;
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        _initialized = true;
    }

    /// <summary>
    /// Connect to the printer
    /// </summary>
    /// <param name="ip">Printer IP address</param>
    /// <param name="port">Printer port (default 9100)</param>
    /// <param name="timeoutSeconds">Connection timeout in seconds (default 5)</param>
    /// <exception cref="EpsonConnectionException">Thrown when connection fails</exception>
    public async Task ConnectAsync(string ip, int port = 9100, int timeoutSeconds = 10)
    {
        _printerIp = ip;
        _printerPort = port;
        _timeoutSeconds = timeoutSeconds;

        try
        {
            _logger?.LogInformation("Connecting to printer at {Ip}:{Port}", ip, port);

            _tcpClient = new TcpClient();
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds));

            await _tcpClient.ConnectAsync(ip, port, cts.Token);
            _stream = _tcpClient.GetStream();

            _logger?.LogInformation("Successfully connected to printer");
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "Failed to connect to printer at {Ip}:{Port}", ip, port);
            Disconnect();
            throw new EpsonConnectionException(
                $"Failed to connect to printer at {ip}:{port}", ip, port, ex);
        }
    }

    /// <summary>
    /// Disconnect from the printer
    /// </summary>
    public void Disconnect()
    {
        _logger?.LogDebug("Disconnecting from printer");

        _stream?.Dispose();
        _stream = null;

        _tcpClient?.Dispose();
        _tcpClient = null;
    }

    /// <summary>
    /// Send a command and receive a 1-byte response (for DLE EOT commands)
    /// </summary>
    private async Task<byte> SendCommandAsync(byte[] command)
    {
        EnsureConnected();

        try
        {
            _logger?.LogTrace("Sending command: {Command}", BitConverter.ToString(command));

            await _stream!.WriteAsync(command);
            await _stream.FlushAsync();

            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(_timeoutSeconds));
            var buffer = new byte[1];
            var bytesRead = await _stream.ReadAsync(buffer, cts.Token);

            if (bytesRead == 0)
            {
                throw new EpsonCommandException("No response received from printer", command);
            }

            _logger?.LogTrace("Received response: 0x{Response:X2}", buffer[0]);
            return buffer[0];
        }
        catch (Exception ex) when (ex is not EpsonCommandException)
        {
            _logger?.LogError(ex, "Error sending command to printer");
            throw new EpsonCommandException("Failed to send command to printer", command, ex);
        }
    }

    /// <summary>
    /// Send a command without expecting a response (for printing commands)
    /// </summary>
    private async Task SendRawAsync(byte[] data)
    {
        EnsureConnected();

        try
        {
            

            _logger?.LogTrace("Sending raw data: {Length} bytes", data.Length);

            await _stream!.WriteAsync(data);
            await _stream.FlushAsync();
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "Error sending raw data to printer");
            throw new EpsonCommandException("Failed to send data to printer", data, ex);
        }
    }

    /// <summary>
    /// Get general printer status
    /// </summary>
    /// <returns>Printer status information</returns>
    public async Task<PrinterStatus> GetPrinterStatusAsync()
    {
        _logger?.LogDebug("Querying printer status");
        var response = await SendCommandAsync(EpsonCommands.GetPrinterStatus);
        return PrinterStatus.Parse(response);
    }



    /// <summary>
    /// Get offline status (reasons for offline)
    /// </summary>
    /// <returns>Offline status information</returns>
    public async Task<OfflineStatus> GetOfflineStatusAsync()
    {
        _logger?.LogDebug("Querying offline status");
        var response = await SendCommandAsync(EpsonCommands.GetOfflineStatus);
        return OfflineStatus.Parse(response);
    }

    /// <summary>
    /// Get error status
    /// </summary>
    /// <returns>Error status information</returns>
    public async Task<ErrorStatus> GetErrorStatusAsync()
    {
        _logger?.LogDebug("Querying error status");
        var response = await SendCommandAsync(EpsonCommands.GetErrorStatus);
        return ErrorStatus.Parse(response);
    }

    /// <summary>
    /// Get paper sensor status
    /// </summary>
    /// <returns>Paper sensor status information</returns>
    public async Task<PaperSensorStatus> GetPaperSensorStatusAsync()
    {
        _logger?.LogDebug("Querying paper sensor status");
        var response = await SendCommandAsync(EpsonCommands.GetPaperSensorStatus);
        return PaperSensorStatus.Parse(response);
    }

    public async Task<byte> GetTM220StatusAsync()
    {
        _logger?.LogDebug("Querying TM220 status");
        var response = await SendCommandAsync([0x10,0x04,0x01]);
        return response;
    }

    /// <summary>
    /// Get printer model/ID
    /// </summary>
    public async Task<byte?> GetPrinterIdAsync()
    {
        EnsureConnected();

        try
        {
            _logger?.LogDebug("Querying printer ID");

            await _stream!.WriteAsync(EpsonCommands.GetPrinterId);
            await _stream.FlushAsync();

            await Task.Delay(100); // Give printer time to respond

            var buffer = new byte[1];
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(_timeoutSeconds));
            var bytesRead = await _stream.ReadAsync(buffer, cts.Token);

            if (bytesRead > 0)
            {
                return buffer[0];
            }

            return null;
        }
        catch (Exception ex)
        {
            _logger?.LogWarning(ex, "Failed to get printer ID");
            return null;
        }
    }

    /// <summary>
    /// Get comprehensive overall status with health assessment
    /// </summary>
    /// <returns>Overall status with recommendations</returns>
    public async Task<OverallStatus> GetOverallStatusAsync()
    {
        _logger?.LogInformation("Getting overall printer status");

        var printerStatus = await GetPrinterStatusAsync();
        var offlineStatus = await GetOfflineStatusAsync();
        var errorStatus = await GetErrorStatusAsync();
        var paperStatus = await GetPaperSensorStatusAsync();
        var printerModel = await GetPrinterIdAsync();

        return DetermineOverallStatus(printerModel, printerStatus, offlineStatus, errorStatus, paperStatus);
    }


    public async Task InitAsync()
    {
        _logger?.LogInformation("Initializing printer");
        await SendRawAsync(EpsonCommands.Initialize);   
    }

    public async Task SetQuadrupleMode(bool enable)
    {
        byte[] command = enable
            ? new byte[] { 0x1C, 0x57, 0x01 } // Enable quadruple size
            : new byte[] { 0x1C, 0x57, 0x00 }; // Disable quadruple size
        _logger?.LogInformation("{Action} quadruple mode", enable ? "Enabling" : "Disabling");
        await SendRawAsync(command);
    }

    public async Task SetRedColor(bool enable)
    {
        byte[] command = enable
            ? new byte[] { 0x1B, 0x72, 0x01 } // Enable red color
            : new byte[] { 0x1B, 0x72, 0x00 }; // Disable red color
        _logger?.LogInformation("{Action} red color mode", enable ? "Enabling" : "Disabling");
        await SendRawAsync(command);
    }

    public async Task SetEmphasized(bool enable)
        {
        byte[] command = enable
            ? new byte[] { 0x1B, 0x45, 0x01 } // Enable emphasized mode
            : new byte[] { 0x1B, 0x45, 0x00 }; // Disable emphasized mode
        _logger?.LogInformation("{Action} emphasized mode", enable ? "Enabling" : "Disabling");
        await SendRawAsync(command);
    }

    public async Task SelectFont(EpsonCommands.PrinterFont font )
    {
        var header = EpsonCommands.SelectFontHeader;
        byte[] selectedFont;
        selectedFont = font switch
        {
            EpsonCommands.PrinterFont.A => [0],
            EpsonCommands.PrinterFont.B => [0x01],
            EpsonCommands.PrinterFont.C => [0x02],
            EpsonCommands.PrinterFont.D => [0x03],
            EpsonCommands.PrinterFont.E => [0x04],

            _ => throw new ArgumentOutOfRangeException(nameof(font), "Invalid font selection")
        };
        var command = new byte[header.Length + selectedFont.Length];
        Array.Copy(header, command, header.Length);
        Array.Copy(selectedFont, 0, command, header.Length, selectedFont.Length);
        _logger?.LogInformation("Selecting font: {Font}", font);
        await SendRawAsync(command);
    }
    /// <summary>
    /// Change to bigger font mode for TM-T20/220 printers
    /// </summary>
    /// <param name="biggerWidth"></param>
    /// <param name="biggerHeight"></param>
    /// <param name="secondaryFont"></param>
    /// <see cref="https://download4.epson.biz/sec_pubs/pos/reference_en/escpos/esc_exclamation.html"/>
    public async Task SetBiggerFontTM220(bool biggerWidth, bool biggerHeight=false, bool secondaryFont=true)
    {
        _logger?.LogInformation("Setting bigger font mode: Width={BiggerWidth}, Height={BiggerHeight}, SecondaryFont={SecondaryFont}",
            biggerWidth, biggerHeight, secondaryFont);

        byte value = 0x00;
        if (biggerWidth)
            value += 0x20;
        if (biggerHeight)
            value += 0x10;
        if (secondaryFont)
            value += 0x01;
        
        await SendRawCommandAsync([0x1b, 0x21, value]);
    }


    /// <summary>
    /// Print text to the printer
    /// </summary>
    /// <param name="text">Text to print</param>
    public async Task PrintTextAsync(string text)
    {
        _logger?.LogInformation("Printing text: {Length} characters", text.Length);

        await SendRawAsync(EpsonCommands.CP852Encoding);
        Encoding cp852 = Encoding.GetEncoding(852);
        var textBytes = cp852.GetBytes(text);
        
        await SendRawAsync(textBytes);
    }

    public async Task SetFontSizeAsync(int width, int height)
    {
        // width and height: 1-8
        if (width < 1 || width > 8 || height < 1 || height > 8)
            throw new ArgumentException("Size must be between 1 and 8");

        byte size = (byte)(((height - 1) & 0x07) | (((width - 1) & 0x07) << 4));
        byte[] cmd = new byte[] { 0x1D, 0x21, size };
        await SendRawAsync(cmd);
    }
    /// <summary>
    /// Print text, feed paper, and cut
    /// </summary>
    /// <param name="text">Text to print</param>
    /// <param name="feedLines">Number of lines to feed before cutting (default 5)</param>
    public async Task PrintTextAndCutAsync(string text, int feedLines = 5)
    {
        _logger?.LogInformation("Printing text with cut: {Length} characters, {FeedLines} feed lines",
            text.Length, feedLines);

        // Initialize printer
        await SendRawAsync(EpsonCommands.Initialize);

        // Print text
        await PrintTextAsync(text);
        await PrintTextAsync("\n");

        // Feed lines
        await SendRawAsync(EpsonCommands.FeedLines((byte)feedLines));

        // Cut paper
        await SendRawAsync(EpsonCommands.CutPaperFull);

        _logger?.LogInformation("Print job completed");
    }

    public async Task PrintBuffer()
    {
        var header = EpsonCommands.PrintAndFeed;
        var command = new byte[header.Length];
        await SendRawCommandAsync(command);


    }

    public async Task FeedLinesAsync(int lines)
    {
        _logger?.LogInformation("Feeding {Lines} lines", lines);
        await SendRawAsync(EpsonCommands.FeedLines((byte)lines));
    }

    public async Task CutAsync(bool fullCut=true)
    {
        _logger?.LogInformation("Cutting paper");
        await SendRawAsync(EpsonCommands.CutPaperFull);
    }

    /// <summary>
    /// Print an image from a file path
    /// </summary>
    /// <param name="imagePath">Path to the image file</param>
    /// <param name="maxWidth">Maximum width in pixels (default 384 for 80mm thermal printer)</param>
    public async Task LoadImageAsync(string imagePath, int maxWidth = 384)
    {
        _logger?.LogInformation("Printing image from file: {Path}", imagePath);

        if (!File.Exists(imagePath))
        {
            throw new FileNotFoundException($"Image file not found: {imagePath}", imagePath);
        }

        try
        {
            using var image = await Image.LoadAsync<Rgba32>(imagePath);
            await LoadImageAsync(image, maxWidth);
        }
        catch (Exception ex) when (ex is not FileNotFoundException)
        {
            _logger?.LogError(ex, "Failed to load or print image from file: {Path}", imagePath);
            throw new EpsonCommandException($"Failed to process image file: {imagePath}", Array.Empty<byte>(), ex);
        }
    }

    public async Task PrintLoadedImage()
    {
        var command = EpsonCommands.PrintLoadedImage;
        await SendRawAsync(command);
    }

    /// <summary>
    /// Print an image from a stream
    /// </summary>
    /// <param name="imageStream">Stream containing the image data</param>
    /// <param name="maxWidth">Maximum width in pixels (default 384 for 80mm thermal printer)</param>
    public async Task LoadImageAsync(Stream imageStream, int maxWidth = 384)
    {
        _logger?.LogInformation("Printing image from stream");

        try
        {
            using var image = await Image.LoadAsync<Rgba32>(imageStream);
            await LoadImageAsync(image, maxWidth);
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "Failed to load or print image from stream");
            throw new EpsonCommandException("Failed to process image stream", Array.Empty<byte>(), ex);
        }
    }

    public async Task SetAbsolutePrintPosition(int x)
    {
        if (x < 0 || x > 65535)
            throw new ArgumentOutOfRangeException(nameof(x), "Position must be between 0 and 65535");
        byte nL = (byte)(x & 0xFF);
        byte nH = (byte)((x >> 8) & 0xFF);
        var command = new byte[] { 0x1B, 0x24, nL, nH };
        _logger?.LogInformation("Setting absolute print position to {X} (nL={nL}, nH={nH})", x, nL, nH);
        await SendRawAsync(command);

    }

    public async Task SetDefaultLineSpacing()
    {
        _logger?.LogInformation("Setting default line spacing");
        await SendRawAsync([0x1B,0x32]);
    }



    public async Task SetCustomLineSpacing(int spacing = 24)
    {
        if (spacing < 0 || spacing > 255)
            throw new ArgumentOutOfRangeException(nameof(spacing), "Spacing must be between 0 and 255");
        var command = new byte[] { 0x1B, 0x33, (byte)spacing };
        _logger?.LogInformation("Setting custom line spacing to {Spacing}", spacing);
        await SendRawAsync(command);

    }

    /// <summary>
    /// Print an image from an ImageSharp Image object
    /// </summary>
    /// <param name="image">ImageSharp Image object</param>
    /// <param name="maxWidth">Maximum width in pixels (default 384 for 80mm thermal printer)</param>
    public async Task LoadImageAsync(Image<Rgba32> image, int maxWidth = 384)
    {
        EnsureConnected();

        _logger?.LogInformation("Processing image: {Width}x{Height} pixels, max width: {MaxWidth}",
            image.Width, image.Height, maxWidth);

        try
        {
            // Convert image to raster format
            var rasterData = EpsonImageConverter.ConvertToRasterData(
                image,
                maxWidth,
                out int finalWidth,
                out int finalHeight);

            _logger?.LogDebug("Image converted to raster: {Width}x{Height} pixels, {DataSize} bytes",
                finalWidth, finalHeight, rasterData.Length);

            // Create ESC/POS command
            var command = EpsonCommands.CreateRasterImageCommand(rasterData, finalWidth, finalHeight);

            _logger?.LogDebug("Sending image command: {CommandSize} bytes", command.Length);

            // Send to printer
            await SendRawAsync(command);

            _logger?.LogInformation("Image printed successfully");
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "Failed to process or print image");
            throw new EpsonCommandException("Failed to print image", Array.Empty<byte>(), ex);
        }
    }

    /// <summary>
    /// Print an image using bit-image mode (ESC * command) from a file path
    /// </summary>
    /// <param name="imagePath">Path to the image file</param>
    /// <param name="mode">Bit image mode (8-dot or 24-dot, single or double density)</param>
    /// <param name="maxWidth">Maximum width in pixels (default 384 for 80mm thermal printer)</param>
    public async Task PrintImageBitModeAsync(string imagePath, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.SingleDensity8Dot, int maxWidth = 10)
    {
        _logger?.LogInformation("Printing image using bit-image mode from file: {Path}", imagePath);

        if (!File.Exists(imagePath))
        {
            throw new FileNotFoundException($"Image file not found: {imagePath}", imagePath);
        }

        try
        {
            using var image = await Image.LoadAsync<Rgba32>(imagePath);
            await PrintImageBitModeAsync(image, mode, maxWidth);
        }
        catch (Exception ex) when (ex is not FileNotFoundException)
        {
            _logger?.LogError(ex, "Failed to load or print image from file: {Path}", imagePath);
            throw new EpsonCommandException($"Failed to process image file: {imagePath}", Array.Empty<byte>(), ex);
        }
    }

    /// <summary>
    /// Print an image using bit-image mode (ESC * command) from a stream
    /// </summary>
    /// <param name="imageStream">Stream containing the image data</param>
    /// <param name="mode">Bit image mode (8-dot or 24-dot, single or double density)</param>
    /// <param name="maxWidth">Maximum width in pixels (default 384 for 80mm thermal printer)</param>
    public async Task PrintImageBitModeAsync(Stream imageStream, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.DoubleDensity24Dot, int maxWidth = 384)
    {
        _logger?.LogInformation("Printing image using bit-image mode from stream");

        try
        {
            using var image = await Image.LoadAsync<Rgba32>(imageStream);
            await PrintImageBitModeAsync(image, mode, maxWidth);
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "Failed to load or print image from stream");
            throw new EpsonCommandException("Failed to process image stream", Array.Empty<byte>(), ex);
        }
    }

    /// <summary>
    /// Print an image using bit-image mode (ESC * command) from an ImageSharp Image object.
    /// This uses the legacy ESC * command which may be compatible with more printer models.
    /// For 24-dot modes, the image is printed in multiple horizontal slices of 24 dots height each.
    /// </summary>
    /// <param name="image">ImageSharp Image object</param>
    /// <param name="mode">Bit image mode (8-dot or 24-dot, single or double density)</param>
    /// <param name="maxWidth">Maximum width in pixels (default 384 for 80mm thermal printer)</param>
    public async Task PrintImageBitModeAsync(Image<Rgba32> image, EpsonCommands.BitImageMode mode, int maxWidth)
    {
        //EnsureConnected();

        _logger?.LogInformation("Processing image for bit-image mode: {Width}x{Height} pixels, mode: {Mode}, max width: {MaxWidth}",
            image.Width, image.Height, mode, maxWidth);

        try
        {
            // Convert image to column format
            var columnData = EpsonImageConverter.ConvertToColumnFormat(
                image,
                maxWidth,
                mode,
                out int finalWidth,
                out int finalHeight);

            _logger?.LogDebug("Image converted to column format: {Width}x{Height} pixels, {DataSize} bytes",
                finalWidth, finalHeight, columnData.Length);

            int bitsPerSlice = mode == EpsonCommands.BitImageMode.SingleDensity8Dot ||
                               mode == EpsonCommands.BitImageMode.DoubleDensity8Dot ? 8 : 24;
            int bytesPerColumn = bitsPerSlice / 8;
            int totalSlices = finalHeight / bitsPerSlice;

            _logger?.LogDebug("Printing in {SliceCount} slices of {BitsPerSlice} dots each", totalSlices, bitsPerSlice);

            // Print each horizontal slice
            for (int slice = 0; slice < totalSlices; slice++)
            {
                int sliceOffset = slice * finalWidth * bytesPerColumn;
                int sliceLength = finalWidth * bytesPerColumn;

                var sliceData = new byte[sliceLength];
                Array.Copy(columnData, sliceOffset, sliceData, 0, sliceLength);

                // Create and send ESC * command for this slice
                var command = EpsonCommands.CreateBitImageCommand(mode, sliceData, finalWidth);

                await SendRawAsync(command);

                // Move to next line after each slice (except the last one)
                if (slice < totalSlices - 1)
                {
                    await SendRawAsync(EpsonCommands.LineFeed);
                }
            }

            _logger?.LogInformation("Image printed successfully using bit-image mode");
        }
        catch (Exception ex)
        {
            _logger?.LogError(ex, "Failed to process or print image in bit-image mode");
            throw new EpsonCommandException("Failed to print image in bit-image mode", Array.Empty<byte>(), ex);
        }
    }

    /// <summary>
    /// Send raw ESC/POS command bytes to the printer
    /// </summary>
    /// <param name="command">Raw command bytes</param>
    public async Task SendRawCommandAsync(byte[] command)
    {
        _logger?.LogDebug("Sending raw command: {Length} bytes", command.Length);
        await SendRawAsync(command);
    }

    /// <summary>
    /// Determine overall health status from individual status components
    /// </summary>
    private OverallStatus DetermineOverallStatus(
        byte? printerModel,
        PrinterStatus printerStatus,
        OfflineStatus offlineStatus,
        ErrorStatus errorStatus,
        PaperSensorStatus paperStatus)
    {
        var recommendations = new List<string>();
        string statusText;
        string statusIcon;
        bool isReady;

        // Check for critical errors
        if (errorStatus.UnrecoverableError)
        {
            statusText = "CRITICAL ERROR - Unrecoverable error detected";
            statusIcon = "🔴";
            isReady = false;
            recommendations.Add("Power cycle the printer and contact support");
        }
        else if (errorStatus.RecoverableError || errorStatus.AutoCutterError)
        {
            statusText = "ERROR - Recoverable error detected";
            statusIcon = "🟠";
            isReady = false;
            recommendations.Add("Clear any errors by fixing the underlying issue");

            if (errorStatus.RecoverableError)
            {
                recommendations.Add("Check for paper jams and ensure paper is loaded correctly");
            }
            if (errorStatus.AutoCutterError)
            {
                recommendations.Add("Check for paper jams in cutter and remove any obstructions");
            }
        }
        // Check offline status
        else if (!printerStatus.IsOnline)
        {
            var reasons = new List<string>();
            if (offlineStatus.CoverOpen) reasons.Add("Cover open");
            if (offlineStatus.PaperEnd) reasons.Add("Paper out");
            if (offlineStatus.ErrorOccurred) reasons.Add("Error occurred");

            var reasonText = reasons.Count > 0 ? string.Join(", ", reasons) : "Unknown reason";
            statusText = $"OFFLINE - {reasonText}";
            statusIcon = "🟡";
            isReady = false;
            recommendations.Add("Bring printer online by resolving offline causes");
        }
        // Check paper status
        else if (!paperStatus.PaperPresent)
        {
            statusText = "WARNING - Paper out or not detected";
            statusIcon = "🟡";
            isReady = false;
            recommendations.Add("Load paper into the printer");
        }
        else if (paperStatus.PaperNearEnd)
        {
            statusText = "WARNING - Paper near end";
            statusIcon = "🟡";
            isReady = true; // Can still print, but warning
            recommendations.Add("Replace paper roll soon");
        }
        // Check cover
        else if (!printerStatus.IsCoverClosed)
        {
            statusText = "WARNING - Cover open";
            statusIcon = "🟡";
            isReady = false;
            recommendations.Add("Close the printer cover");
        }
        // All checks passed
        else
        {
            statusText = "READY - Printer is operational";
            statusIcon = "🟢";
            isReady = true;
        }

        return new OverallStatus
        {
            PrinterModel = printerModel?.ToString("X2"),
            PrinterStatus = printerStatus,
            OfflineStatus = offlineStatus,
            ErrorStatus = errorStatus,
            PaperStatus = paperStatus,
            StatusText = statusText,
            StatusIcon = statusIcon,
            IsReady = isReady,
            Recommendations = recommendations
        };
    }

    private void EnsureConnected()
    {
        if (!IsConnected)
        {
            throw new EpsonConnectionException("Not connected to printer. Call ConnectAsync first.");
        }
    }

    public async ValueTask DisposeAsync()
    {
        Disconnect();
        await Task.CompletedTask;
    }
}
