using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace Radzen.Blazor;

/// <summary>
/// Represents a rectangle used for barcode rendering, with position and size.
/// </summary>
public readonly struct BarcodeRect(double x, double y, double width, double height)
{
    /// <summary>
    /// The X position of the rectangle.
    /// </summary>
    public readonly double X = x;
    /// <summary>
    /// The Y position of the rectangle.
    /// </summary>
    public readonly double Y = y;
    /// <summary>
    /// The width of the rectangle.
    /// </summary>
    public readonly double Width = width;
    /// <summary>
    /// The height of the rectangle.
    /// </summary>
    public readonly double Height = height;
}

/// <summary>
/// Provides 1D barcode encoding utilities for common symbologies.
/// </summary>
public static class RadzenBarcodeEncoder
{
    // Code 128 patterns (0..106). Each entry is 6 digits (bar/space/bar/space/bar/space) module widths.
    // Stop code (106) is 7 digits in the spec (includes a final bar). We keep it as 7 digits and handle it.
    static readonly string[] Code128Patterns = new[]
    {
        "212222","222122","222221","121223","121322","131222","122213","122312","132212","221213",
        "221312","231212","112232","122132","122231","113222","123122","123221","223211","221132",
        "221231","213212","223112","312131","311222","321122","321221","312212","322112","322211",
        "212123","212321","232121","111323","131123","131321","112313","132113","132311","211313",
        "231113","231311","112133","112331","132131","113123","113321","133121","313121","211331",
        "231131","213113","213311","213131","311123","311321","331121","312113","312311","332111",
        "314111","221411","431111","111224","111422","121124","121421","141122","141221","112214",
        "112412","122114","122411","142112","142211","241211","221114","413111","241112","134111",
        "111242","121142","121241","114212","124112","124211","411212","421112","421211","212141",
        "214121","412121","111143","111341","131141","114113","114311","411113","411311","113141",
        "114131","311141","411131","211412","211214","211232","2331112"
    };

    /// <summary>
    /// Encodes a string into Code 128 subset B module widths.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <returns>The module widths (bar/space alternating, starting with bar).</returns>
    public static IReadOnlyList<int> EncodeCode128B(string value) => EncodeCode128B(value, out _);

    /// <summary>
    /// Encodes a string into Code 128 subset B module widths and returns the checksum.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <param name="checksum">The calculated checksum value.</param>
    /// <returns>The module widths (bar/space alternating, starting with bar).</returns>
    public static IReadOnlyList<int> EncodeCode128B(string value, out int checksum)
    {
        ArgumentNullException.ThrowIfNull(value);

        // Code 128 subset B supports ASCII 32..127 (inclusive). We treat 127 as DEL.
        var codes = new List<int>(value.Length + 3);
        const int startB = 104;
        const int stop = 106;

        codes.Add(startB);

        for (int i = 0; i < value.Length; i++)
        {
            var ch = value[i];
            int ascii = ch;
            if (ascii < 32 || ascii > 127)
            {
                throw new ArgumentException($"Code128B supports ASCII 32..127. Invalid character: U+{ascii:X4}.");
            }

            // In Code128B, code value is ascii - 32
            codes.Add(ascii - 32);
        }

        int checksumValue = codes[0];
        for (int i = 1; i < codes.Count; i++)
        {
            checksumValue += codes[i] * i;
        }
        checksumValue %= 103;

        // expose checksum (0..102)
        checksum = checksumValue;

        codes.Add(checksumValue);
        codes.Add(stop);

        // Convert codes to module pattern widths, alternating bar/space.
        // Most codes are 6 digits; stop is 7 digits.
        var modules = new List<int>(codes.Count * 6);
        foreach (var code in codes)
        {
            var p = Code128Patterns[code];
            for (int i = 0; i < p.Length; i++)
            {
                modules.Add(p[i] - '0');
            }
        }

        // Code 128 requires a 2-module termination bar after the stop pattern.
        // Many pattern tables omit it because it can be represented by extending the
        // final bar of the stop pattern by 2 modules.
        if (modules.Count > 0)
        {
            modules[^1] += 2;
        }

        return modules;
    }

    static readonly Dictionary<char, string> Code39Map = new Dictionary<char, string>()
    {
        // Each pattern is 9 elements (bar/space alternating, starting with bar).
        // 'n' = narrow (1), 'w' = wide (2). We expand to digits.
        ['0'] = "nnnwwnwnn",
        ['1'] = "wnnwnnnnw",
        ['2'] = "nnwwnnnnw",
        ['3'] = "wnwwnnnnn",
        ['4'] = "nnnwwnnnw",
        ['5'] = "wnnwwnnnn",
        ['6'] = "nnwwwnnnn",
        ['7'] = "nnnwnnwnw",
        ['8'] = "wnnwnnwnn",
        ['9'] = "nnwwnnwnn",
        ['A'] = "wnnnnwnnw",
        ['B'] = "nnwnnwnnw",
        ['C'] = "wnwnnwnnn",
        ['D'] = "nnnnwwnnw",
        ['E'] = "wnnnwwnnn",
        ['F'] = "nnwnwwnnn",
        ['G'] = "nnnnnwwnw",
        ['H'] = "wnnnnwwnn",
        ['I'] = "nnwnnwwnn",
        ['J'] = "nnnnwwwnn",
        ['K'] = "wnnnnnnww",
        ['L'] = "nnwnnnnww",
        ['M'] = "wnwnnnnwn",
        ['N'] = "nnnnwnnww",
        ['O'] = "wnnnwnnwn",
        ['P'] = "nnwnwnnwn",
        ['Q'] = "nnnnnnwww",
        ['R'] = "wnnnnnwwn",
        ['S'] = "nnwnnnwwn",
        ['T'] = "nnnnwnwwn",
        ['U'] = "wwnnnnnnw",
        ['V'] = "nwwnnnnnw",
        ['W'] = "wwwnnnnnn",
        ['X'] = "nwnnwnnnw",
        ['Y'] = "wwnnwnnnn",
        ['Z'] = "nwwnwnnnn",
        ['-'] = "nwnnnnwnw",
        ['.'] = "wwnnnnwnn",
        [' '] = "nwwnnnwnn",
        ['$'] = "nwnwnwnnn",
        ['/'] = "nwnwnnnwn",
        ['+'] = "nwnnnwnwn",
        ['%'] = "nnnwnwnwn",
        ['*'] = "nwnnwnwnn", // start/stop
    };

    /// <summary>
    /// Encodes a string into Code 39 module widths.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <returns>The module widths (bar/space alternating, starting with bar).</returns>
    public static IReadOnlyList<int> EncodeCode39(string value)
    {
        ArgumentNullException.ThrowIfNull(value);

        // Code39 traditionally uses uppercase
        var text = value.ToUpperInvariant();

        foreach (var ch in text)
        {
            if (!Code39Map.ContainsKey(ch))
            {
                throw new ArgumentException($"Code39 does not support character '{ch}'.");
            }
        }

        // Start + data + stop, inter-character gap (narrow space) between characters.
        var full = "*" + text + "*";
        var modules = new List<int>(full.Length * 10);

        for (int idx = 0; idx < full.Length; idx++)
        {
            var pat = Code39Map[full[idx]];
            for (int i = 0; i < pat.Length; i++)
            {
                modules.Add(pat[i] == 'w' ? 2 : 1);
            }

            // Inter-character gap (narrow space) except after last char.
            if (idx != full.Length - 1)
            {
                modules.Add(1);
            }
        }

        return modules;
    }

    /// <summary>
    /// Encodes a string into ITF (Interleaved 2 of 5) module widths.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <returns>The module widths (bar/space alternating, starting with bar).</returns>
    public static IReadOnlyList<int> EncodeItf(string value)
    {
        var digits = new string(value.Where(char.IsDigit).ToArray());
        if (digits.Length == 0)
        {
            throw new ArgumentException("ITF requires numeric input.");
        }
        if (digits.Length % 2 != 0)
        {
            // pad with leading zero (common behavior)
            digits = "0" + digits;
        }

        const int narrow = 1;
        const int wide = 3;

        static string Pat(char d) => d switch
        {
            '0' => "nnwwn",
            '1' => "wnnnw",
            '2' => "nwnnw",
            '3' => "wwnnn",
            '4' => "nnwnw",
            '5' => "wnwnn",
            '6' => "nwwnn",
            '7' => "nnnww",
            '8' => "wnnwn",
            '9' => "nwnwn",
            _ => throw new ArgumentException("ITF requires numeric input.")
        };

        var widths = new List<int>(digits.Length * 10 + 16);

        // Start: narrow bar, narrow space, narrow bar, narrow space  (1010)
        widths.Add(narrow);
        widths.Add(narrow);
        widths.Add(narrow);
        widths.Add(narrow);

        for (int i = 0; i < digits.Length; i += 2)
        {
            var a = Pat(digits[i]);
            var b = Pat(digits[i + 1]);

            for (int j = 0; j < 5; j++)
            {
                widths.Add(a[j] == 'w' ? wide : narrow); // bar
                widths.Add(b[j] == 'w' ? wide : narrow); // space
            }
        }

        // Stop: wide bar, narrow space, narrow bar (1101)
        widths.Add(wide);
        widths.Add(narrow);
        widths.Add(narrow);

        return widths;
    }

    /// <summary>
    /// Encodes a string into Codabar module widths.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <returns>The module widths (bar/space alternating, starting with bar).</returns>
    public static IReadOnlyList<int> EncodeCodabar(string value)
    {
        // Wikipedia table mapping (bars: 1=wide, spaces: 0=wide) for the standard symbol set.
        // Ensure start/stop are present; default to A ... B if missing.
        var raw = (value ?? string.Empty).Trim().ToUpperInvariant();
        if (raw.Length == 0)
        {
            throw new ArgumentException("Codabar requires a non-empty value.");
        }

        bool HasStartStop(string s)
        {
            if (s.Length < 2)
            {
                return false;
            }

            bool isStart = s[0] == 'A' || s[0] == 'B' || s[0] == 'C' || s[0] == 'D';
            bool isStop = s[s.Length - 1] == 'A' || s[s.Length - 1] == 'B' || s[s.Length - 1] == 'C' || s[s.Length - 1] == 'D';
            return isStart && isStop;
        }

        var text = HasStartStop(raw) ? raw : $"A{raw}B";

        static (string spaceBits, string barBits) Map(char ch) => ch switch
        {
            '0' => ("001", "0001"),
            '1' => ("001", "0010"),
            '2' => ("010", "0001"),
            '3' => ("100", "1000"),
            '4' => ("001", "0100"),
            '5' => ("001", "1000"),
            '6' => ("100", "0001"),
            '7' => ("100", "0010"),
            '8' => ("100", "0100"),
            '9' => ("010", "1000"),
            '-' => ("010", "0010"),
            '$' => ("010", "0100"),
            '.' => ("000", "0001"),
            '/' => ("000", "0010"),
            ':' => ("000", "0100"),
            '+' => ("000", "1000"),
            'A' => ("011", "0100"),
            'B' => ("110", "0001"),
            'C' => ("011", "0001"),
            'D' => ("011", "0010"),
            _ => throw new ArgumentException($"Codabar does not support character '{ch}'.")
        };

        const int narrow = 1;
        const int wide = 3;

        var widths = new List<int>(text.Length * 8);

        for (int idx = 0; idx < text.Length; idx++)
        {
            var ch = text[idx];
            var (spaceBits, barBits) = Map(ch);

            // Bars: 4 bits, 1=wide
            int BarWidth(int pos) => barBits[pos] == '1' ? wide : narrow;
            // Spaces: 3 bits, 0=wide (per wikipedia mapping table)
            int SpaceWidth(int pos) => spaceBits[pos] == '0' ? wide : narrow;

            widths.Add(BarWidth(0));
            widths.Add(SpaceWidth(0));
            widths.Add(BarWidth(1));
            widths.Add(SpaceWidth(1));
            widths.Add(BarWidth(2));
            widths.Add(SpaceWidth(2));
            widths.Add(BarWidth(3));

            // Inter-character narrow space (except after last char).
            if (idx != text.Length - 1)
            {
                widths.Add(narrow);
            }
        }

        return widths;
    }

    static readonly string[] EanL = new[]
    {
        "0001101","0011001","0010011","0111101","0100011","0110001","0101111","0111011","0110111","0001011"
    };
    static readonly string[] EanG = new[]
    {
        "0100111","0110011","0011011","0100001","0011101","0111001","0000101","0010001","0001001","0010111"
    };
    static readonly string[] EanR = new[]
    {
        "1110010","1100110","1101100","1000010","1011100","1001110","1010000","1000100","1001000","1110100"
    };
    static readonly string[] Ean13Parity = new[]
    {
        "LLLLLL","LLGLGG","LLGGLG","LLGGGL","LGLLGG","LGGLLG","LGGGLL","LGLGLG","LGLGGL","LGGLGL"
    };

    static int ComputeEanCheckDigit(string digitsWithoutCheck)
    {
        // digitsWithoutCheck is 7/11/12 digits depending on symbology.
        int sum = 0;
        bool weight3 = true;
        for (int i = digitsWithoutCheck.Length - 1; i >= 0; i--)
        {
            int d = digitsWithoutCheck[i] - '0';
            sum += weight3 ? d * 3 : d;
            weight3 = !weight3;
        }
        int mod = sum % 10;
        return (10 - mod) % 10;
    }

    /// <summary>
    /// Encodes a string into EAN-13 bit pattern.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <param name="checksumText">The calculated checksum digit.</param>
    /// <returns>The bit pattern (1=bar, 0=space).</returns>
    public static string EncodeEan13(string value, out string checksumText)
    {
        var digits = new string(value.Where(char.IsDigit).ToArray());
        if (digits.Length != 12 && digits.Length != 13)
        {
            throw new ArgumentException("EAN-13 requires 12 or 13 digits.");
        }

        if (digits.Length == 12)
        {
            var check = ComputeEanCheckDigit(digits);
            digits += check.ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            var expected = ComputeEanCheckDigit(digits[..12]);
            if (digits[12] - '0' != expected)
            {
                throw new ArgumentException("Invalid EAN-13 check digit.");
            }
        }

        checksumText = digits[^1].ToString();

        int first = digits[0] - '0';
        var parity = Ean13Parity[first];

        var sb = new StringBuilder(95);
        sb.Append("101");
        // digits 2..7 (index 1..6)
        for (int i = 1; i <= 6; i++)
        {
            int d = digits[i] - '0';
            sb.Append(parity[i - 1] == 'G' ? EanG[d] : EanL[d]);
        }
        sb.Append("01010");
        for (int i = 7; i <= 12; i++)
        {
            int d = digits[i] - '0';
            sb.Append(EanR[d]);
        }
        sb.Append("101");
        return sb.ToString();
    }

    /// <summary>
    /// Encodes a string into UPC-A bit pattern.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <param name="checksumText">The calculated checksum digit.</param>
    /// <returns>The bit pattern (1=bar, 0=space).</returns>
    public static string EncodeUpcA(string value, out string checksumText)
    {
        var digits = new string(value.Where(char.IsDigit).ToArray());
        if (digits.Length != 11 && digits.Length != 12)
        {
            throw new ArgumentException("UPC-A requires 11 or 12 digits.");
        }

        if (digits.Length == 11)
        {
            var check = ComputeEanCheckDigit(digits);
            digits += check.ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            var expected = ComputeEanCheckDigit(digits[..11]);
            if (digits[11] - '0' != expected)
            {
                throw new ArgumentException("Invalid UPC-A check digit.");
            }
        }

        checksumText = digits[^1].ToString();

        var sb = new StringBuilder(95);
        sb.Append("101");
        for (int i = 0; i < 6; i++)
        {
            int d = digits[i] - '0';
            sb.Append(EanL[d]);
        }
        sb.Append("01010");
        for (int i = 6; i < 12; i++)
        {
            int d = digits[i] - '0';
            sb.Append(EanR[d]);
        }
        sb.Append("101");
        return sb.ToString();
    }

    /// <summary>
    /// Encodes a string into EAN-8 bit pattern.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <param name="checksumText">The calculated checksum digit.</param>
    /// <returns>The bit pattern (1=bar, 0=space).</returns>
    public static string EncodeEan8(string value, out string checksumText)
    {
        var digits = new string(value.Where(char.IsDigit).ToArray());
        if (digits.Length != 7 && digits.Length != 8)
        {
            throw new ArgumentException("EAN-8 requires 7 or 8 digits.");
        }

        if (digits.Length == 7)
        {
            var check = ComputeEanCheckDigit(digits);
            digits += check.ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            var expected = ComputeEanCheckDigit(digits[..7]);
            if (digits[7] - '0' != expected)
            {
                throw new ArgumentException("Invalid EAN-8 check digit.");
            }
        }

        checksumText = digits[^1].ToString();

        var sb = new StringBuilder(67);
        sb.Append("101");
        for (int i = 0; i < 4; i++)
        {
            int d = digits[i] - '0';
            sb.Append(EanL[d]);
        }
        sb.Append("01010");
        for (int i = 4; i < 8; i++)
        {
            int d = digits[i] - '0';
            sb.Append(EanR[d]);
        }
        sb.Append("101");
        return sb.ToString();
    }

    /// <summary>
    /// Encodes an ISBN as EAN-13 bit pattern.
    /// </summary>
    /// <param name="value">The ISBN value to encode.</param>
    /// <param name="checksumText">The calculated checksum digit.</param>
    /// <returns>The bit pattern (1=bar, 0=space).</returns>
    public static string EncodeIsbnAsEan13(string value, out string checksumText)
    {
        var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
        if (raw.Length == 10)
        {
            // ISBN-10 -> EAN-13: 978 + first 9 digits + EAN check
            var core = raw[..9];
            if (!core.All(char.IsDigit))
            {
                throw new ArgumentException("Invalid ISBN-10.");
            }

            return EncodeEan13("978" + core, out checksumText);
        }
        if (raw.Length == 13)
        {
            if (!raw.All(char.IsDigit))
            {
                throw new ArgumentException("Invalid ISBN-13.");
            }

            return EncodeEan13(raw, out checksumText);
        }

        throw new ArgumentException("ISBN requires 10 or 13 characters.");
    }

    /// <summary>
    /// Encodes an ISSN as EAN-13 bit pattern.
    /// </summary>
    /// <param name="value">The ISSN value to encode.</param>
    /// <param name="checksumText">The calculated checksum digit.</param>
    /// <returns>The bit pattern (1=bar, 0=space).</returns>
    public static string EncodeIssnAsEan13(string value, out string checksumText)
    {
        // ISSN EAN-13: 977 + first 7 digits + 00 + EAN check
        var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
        if (raw.Length != 8)
        {
            throw new ArgumentException("ISSN requires 8 characters.");
        }

        var core = raw[..7];
        if (!core.All(char.IsDigit))
        {
            throw new ArgumentException("Invalid ISSN.");
        }

        return EncodeEan13("977" + core + "00", out checksumText);
    }

    /// <summary>
    /// Encodes a Pharmacode value and returns the bar geometry.
    /// </summary>
    /// <param name="value">The Pharmacode numeric value.</param>
    /// <param name="barHeight">The bar height in SVG units.</param>
    /// <param name="quietZone">The quiet zone in modules.</param>
    /// <returns>The bar rectangles and viewBox width.</returns>
    public static (IReadOnlyList<BarcodeRect> bars, double vbWidth) EncodePharmacode(string value, double barHeight, int quietZone)
    {
        ArgumentNullException.ThrowIfNull(value);

        // Pharmacode one-track: numbers 3..131070
        var digits = new string(value.Where(char.IsDigit).ToArray());
        if (!int.TryParse(digits, NumberStyles.None, CultureInfo.InvariantCulture, out var n))
        {
            throw new ArgumentException("Pharmacode requires a numeric value.");
        }
        if (n < 3 || n > 131070)
        {
            throw new ArgumentException("Pharmacode value must be in range 3..131070.");
        }

        var bars = new List<(int width, bool isWide)>();
        while (n > 0)
        {
            if (n % 2 == 0)
            {
                bars.Add((2, true));
                n = (n - 2) / 2;
            }
            else
            {
                bars.Add((1, false));
                n = (n - 1) / 2;
            }
        }
        bars.Reverse();

        double x = Math.Max(0, quietZone);
        var rects = new List<BarcodeRect>(bars.Count);
        foreach (var b in bars)
        {
            rects.Add(new BarcodeRect(x, 0, b.width, barHeight));
            x += b.width + 1; // 1 module gap
        }

        var vbWidth = x + Math.Max(0, quietZone);
        if (vbWidth <= 0)
        {
            vbWidth = 1;
        }

        return (rects, vbWidth);
    }

    // POSTNET digit encoding from Wikipedia (weights 7,4,2,1,0). 1=full bar, 0=half bar.
    static readonly Dictionary<char, string> PostnetDigitBits = new Dictionary<char, string>()
    {
        ['0'] = "11000",
        ['1'] = "00011",
        ['2'] = "00101",
        ['3'] = "00110",
        ['4'] = "01001",
        ['5'] = "01010",
        ['6'] = "01100",
        ['7'] = "10001",
        ['8'] = "10010",
        ['9'] = "10100",
    };

    /// <summary>
    /// Encodes a POSTNET value and returns the bar geometry.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <param name="barHeight">The bar height in SVG units.</param>
    /// <param name="quietZone">The quiet zone in modules.</param>
    /// <param name="checksumText">The calculated checksum digit.</param>
    /// <returns>The bar rectangles and viewBox width.</returns>
    public static (IReadOnlyList<BarcodeRect> bars, double vbWidth) EncodePostnet(string value, double barHeight, int quietZone, out string checksumText)
    {
        ArgumentNullException.ThrowIfNull(value);

        var digits = new string(value.Where(char.IsDigit).ToArray());
        if ((digits.Length != 5 && digits.Length != 9 && digits.Length != 11))
        {
            throw new ArgumentException("POSTNET requires 5, 9, or 11 digits (ZIP / ZIP+4 / Delivery Point).");
        }

        int sum = digits.Sum(ch => ch - '0');
        int check = (10 - (sum % 10)) % 10;
        checksumText = check.ToString(CultureInfo.InvariantCulture);

        var payload = digits + checksumText;

        double fullH = barHeight;
        double halfH = barHeight / 2.0;
        double halfY = fullH - halfH;

        double x = Math.Max(0, quietZone);
        var rects = new List<BarcodeRect>();

        // Start frame bar (full)
        rects.Add(new BarcodeRect(x, 0, 1, fullH));
        x += 2; // bar(1) + space(1)

        foreach (var ch in payload)
        {
            var bits = PostnetDigitBits[ch];
            for (int i = 0; i < 5; i++)
            {
                bool full = bits[i] == '1';
                rects.Add(full
                    ? new BarcodeRect(x, 0, 1, fullH)
                    : new BarcodeRect(x, halfY, 1, halfH));
                x += 2;
            }
        }

        // Stop frame bar (full)
        rects.Add(new BarcodeRect(x, 0, 1, fullH));
        x += 1;

        var vbWidth = x + Math.Max(0, quietZone);
        if (vbWidth <= 0)
        {
            vbWidth = 1;
        }

        return (rects, vbWidth);
    }

    // RM4SCC patterns and symbol matrix from Wikipedia:
    // Top patterns (values 1..6) and Bottom patterns (values 1..6) are:
    // 1=0011, 2=0101, 3=0110, 4=1001, 5=1010, 6=1100
    static readonly string[] Rm4Patterns = new[] { "0011", "0101", "0110", "1001", "1010", "1100" };

    // Matrix indexed by [topValue-1, bottomValue-1]
    static readonly char[,] Rm4Matrix = new char[6, 6]
    {
        { '0', '1', '2', '3', '4', '5' },
        { '6', '7', '8', '9', 'A', 'B' },
        { 'C', 'D', 'E', 'F', 'G', 'H' },
        { 'I', 'J', 'K', 'L', 'M', 'N' },
        { 'O', 'P', 'Q', 'R', 'S', 'T' },
        { 'U', 'V', 'W', 'X', 'Y', 'Z' }
    };

    static readonly Dictionary<char, (string top, string bottom)> Rm4CharToBits = BuildRm4CharToBits();

    static Dictionary<char, (string top, string bottom)> BuildRm4CharToBits()
    {
        var dict = new Dictionary<char, (string top, string bottom)>();
        for (int r = 0; r < 6; r++)
        {
            for (int c = 0; c < 6; c++)
            {
                dict[Rm4Matrix[r, c]] = (Rm4Patterns[r], Rm4Patterns[c]);
            }
        }
        return dict;
    }

    /// <summary>
    /// Encodes a RM4SCC value and returns the bar geometry.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <param name="barHeight">The bar height in SVG units.</param>
    /// <param name="quietZone">The quiet zone in modules.</param>
    /// <param name="checksumText">The calculated checksum character.</param>
    /// <returns>The bar rectangles and viewBox width.</returns>
    public static (IReadOnlyList<BarcodeRect> bars, double vbWidth) EncodeRm4scc(string value, double barHeight, int quietZone, out string checksumText)
    {
        ArgumentNullException.ThrowIfNull(value);

        var text = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
        if (string.IsNullOrEmpty(text))
        {
            throw new ArgumentException("RM4SCC requires alphanumeric input.");
        }

        // Only symbols present in the Wikipedia table (0-9, A-Z).
        foreach (var ch in text)
        {
            if (!(ch is >= '0' and <= '9') && !(ch is >= 'A' and <= 'Z'))
            {
                throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
            }
        }

        // Compute checksum per Wikipedia: sum top values and bottom values separately, mod 6, 0 => 6.
        int sumTop = 0;
        int sumBottom = 0;
        foreach (var ch in text)
        {
            if (!Rm4CharToBits.TryGetValue(ch, out var bits))
            {
                throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
            }
            int t = Array.IndexOf(Rm4Patterns, bits.top) + 1;
            int b = Array.IndexOf(Rm4Patterns, bits.bottom) + 1;
            sumTop += t;
            sumBottom += b;
        }

        int topVal = sumTop % 6;
        topVal = topVal == 0 ? 6 : topVal;
        int bottomVal = sumBottom % 6;
        bottomVal = bottomVal == 0 ? 6 : bottomVal;

        var checkChar = Rm4Matrix[topVal - 1, bottomVal - 1];

        checksumText = checkChar.ToString();

        // Encode start + data + checksum + stop.
        // Start/stop are single bars; we use ascender for start and descender for stop.
        double h = barHeight;
        double third = h / 3.0;
        double trackerY = third;
        double trackerH = third;

        BarcodeRect BarRect(double x, bool top, bool bottom)
        {
            return (top, bottom) switch
            {
                (false, false) => new BarcodeRect(x, trackerY, 1, trackerH), // tracker
                (true, false) => new BarcodeRect(x, 0, 1, trackerY + trackerH), // ascender (top + tracker)
                (false, true) => new BarcodeRect(x, trackerY, 1, h - trackerY), // descender (tracker + bottom)
                (true, true) => new BarcodeRect(x, 0, 1, h), // full
            };
        }

        double xPos = Math.Max(0, quietZone);
        var rects = new List<BarcodeRect>();

        // Start bar (ascender)
        rects.Add(BarRect(xPos, top: true, bottom: false));
        xPos += 2;

        void AddSymbol(char ch)
        {
            var (topBits, bottomBits) = Rm4CharToBits[ch];
            for (int i = 0; i < 4; i++)
            {
                bool top = topBits[i] == '1';
                bool bottom = bottomBits[i] == '1';
                rects.Add(BarRect(xPos, top, bottom));
                xPos += 2;
            }
        }

        foreach (var ch in text)
        {
            AddSymbol(ch);
        }

        AddSymbol(checkChar);

        // Stop bar (descender)
        rects.Add(BarRect(xPos, top: false, bottom: true));
        xPos += 1;

        var vbWidth = xPos + Math.Max(0, quietZone);
        if (vbWidth <= 0)
        {
            vbWidth = 1;
        }

        return (rects, vbWidth);
    }

    /// <summary>
    /// Encodes a value into MSI (Modified Plessey) bit pattern.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <param name="checksumText">The calculated checksum digit.</param>
    /// <returns>The bit pattern (1=bar, 0=space).</returns>
    public static string EncodeMsiPlessey(string value, out string checksumText)
    {
        var digits = new string(value.Where(char.IsDigit).ToArray());
        if (digits.Length == 0)
        {
            throw new ArgumentException("Plessey (MSI) requires numeric input.");
        }

        // Mod 10 (Luhn) check digit (common)
        int check = ComputeLuhnCheckDigit(digits);
        checksumText = check.ToString(CultureInfo.InvariantCulture);
        digits += checksumText;

        // MSI map from Wikipedia.
        static string DigitMap(char d) => d switch
        {
            '0' => "100100100100",
            '1' => "100100100110",
            '2' => "100100110100",
            '3' => "100100110110",
            '4' => "100110100100",
            '5' => "100110100110",
            '6' => "100110110100",
            '7' => "100110110110",
            '8' => "110100100100",
            '9' => "110100100110",
            _ => throw new ArgumentException("MSI requires numeric input.")
        };

        var sb = new StringBuilder();
        sb.Append("110"); // start
        foreach (var ch in digits)
        {
            sb.Append(DigitMap(ch));
        }

        sb.Append("1001"); // stop
        return sb.ToString();
    }

    static int ComputeLuhnCheckDigit(string digits)
    {
        int sum = 0;
        bool dbl = true;
        for (int i = digits.Length - 1; i >= 0; i--)
        {
            int d = digits[i] - '0';
            if (dbl)
            {
                d *= 2;
                if (d > 9)
                {
                    d -= 9;
                }
            }
            sum += d;
            dbl = !dbl;
        }
        return (10 - (sum % 10)) % 10;
    }

    /// <summary>
    /// Encodes a string into Telepen module widths.
    /// </summary>
    /// <param name="value">The value to encode.</param>
    /// <param name="checksumText">The calculated checksum value.</param>
    /// <returns>The module widths (bar/space alternating, starting with bar).</returns>
    public static IReadOnlyList<int> EncodeTelepen(string value, out string checksumText)
    {
        // Telepen algorithm per Wikipedia: even parity bytes, little-endian bit order, modulo-127 checksum.
        var bytes = Encoding.ASCII.GetBytes(value ?? string.Empty);

        int sum = 0;
        for (int i = 0; i < bytes.Length; i++)
        {
            if (bytes[i] > 0x7F)
            {
                throw new ArgumentException("Telepen supports ASCII only.");
            }

            sum = (sum + bytes[i]) % 127;
        }

        int check = (127 - sum) % 127;
        checksumText = check.ToString(CultureInfo.InvariantCulture);

        // Build payload: start '_' + data + checksum byte + stop 'z'
        var payload = new List<byte>(bytes.Length + 3) { (byte)'_' };
        payload.AddRange(bytes);
        payload.Add((byte)check);
        payload.Add((byte)'z');

        // Build bit stream LSB-first with even parity bit as MSB.
        var bitStream = new List<int>(payload.Count * 8);
        foreach (var b0 in payload)
        {
            int b = b0 & 0x7F;
            int ones = CountBits(b);
            int parityBit = (ones % 2 == 0) ? 0 : 1; // make total even
            int byteWithParity = b | (parityBit << 7);

            for (int i = 0; i < 8; i++)
            {
                bitStream.Add((byteWithParity >> i) & 1);
            }
        }

        // Encode bit stream into bar/space widths (narrow=1, wide=3).
        // We produce alternating bar/space widths list, starting with bar.
        const int narrow = 1;
        const int wide = 3;
        var widths = new List<int>();

        int idx = 0;
        while (idx < bitStream.Count)
        {
            if (bitStream[idx] == 1)
            {
                // "1" => narrow bar, narrow space
                widths.Add(narrow);
                widths.Add(narrow);
                idx += 1;
                continue;
            }

            // starts with 0
            if (idx + 1 < bitStream.Count && bitStream[idx + 1] == 0)
            {
                // "00" => wide bar, narrow space
                widths.Add(wide);
                widths.Add(narrow);
                idx += 2;
                continue;
            }

            if (idx + 2 < bitStream.Count && bitStream[idx] == 0 && bitStream[idx + 1] == 1 && bitStream[idx + 2] == 0)
            {
                // "010" => wide bar, wide space
                widths.Add(wide);
                widths.Add(wide);
                idx += 3;
                continue;
            }

            // General block 0 1^k 0 with k>=2
            if (idx + 3 >= bitStream.Count || bitStream[idx] != 0 || bitStream[idx + 1] != 1)
            {
                throw new ArgumentException("Invalid Telepen bit stream.");
            }

            int j = idx + 1;
            while (j < bitStream.Count && bitStream[j] == 1)
            {
                j++;
            }

            if (j >= bitStream.Count || bitStream[j] != 0)
            {
                throw new ArgumentException("Invalid Telepen bit stream.");
            }

            int k = j - (idx + 1); // number of 1s
            if (k < 2)
            {
                throw new ArgumentException("Invalid Telepen bit stream.");
            }

            // leading "01" => narrow bar, wide space
            widths.Add(narrow);
            widths.Add(wide);

            // middle extra 1s (k-2) => narrow bar, narrow space
            for (int m = 0; m < k - 2; m++)
            {
                widths.Add(narrow);
                widths.Add(narrow);
            }

            // trailing "10" => narrow bar, wide space
            widths.Add(narrow);
            widths.Add(wide);

            idx = j + 1;
        }

        return widths;
    }

    /// <summary>
    /// Encodes a barcode value and renders it into an SVG string.
    /// </summary>
    /// <param name="type">The barcode type.</param>
    /// <param name="value">The value to encode.</param>
    /// <param name="barHeight">The bar height in SVG units.</param>
    /// <param name="quietZoneModules">The quiet zone in modules.</param>
    /// <param name="foreground">The bar color.</param>
    /// <param name="background">The background color.</param>
    /// <returns>An SVG string representing the barcode.</returns>
    public static string ToSvg(RadzenBarcodeType type, string value, double barHeight = 50, int quietZoneModules = 10, string foreground = "#000000", string background = "#FFFFFF")
    {
        ArgumentNullException.ThrowIfNull(value);
        ArgumentNullException.ThrowIfNull(foreground);
        ArgumentNullException.ThrowIfNull(background);

        var (bars, viewBoxWidth, viewBoxHeight) = EncodeToBars(type, value, barHeight, quietZoneModules);

        if (viewBoxWidth <= 0)
        {
            viewBoxWidth = 1;
        }

        if (viewBoxHeight <= 0)
        {
            viewBoxHeight = 1;
        }

        var sb = new StringBuilder(bars.Count * 64 + 256);
        sb.Append(CultureInfo.InvariantCulture, $"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{F(viewBoxWidth)}\" height=\"{F(viewBoxHeight)}\" viewBox=\"0 0 {F(viewBoxWidth)} {F(viewBoxHeight)}\" shape-rendering=\"crispEdges\">");
        sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"0\" y=\"0\" width=\"{F(viewBoxWidth)}\" height=\"{F(viewBoxHeight)}\" fill=\"{background}\"/>");

        for (int i = 0; i < bars.Count; i++)
        {
            var bar = bars[i];
            sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{F(bar.X)}\" y=\"{F(bar.Y)}\" width=\"{F(bar.Width)}\" height=\"{F(bar.Height)}\" fill=\"{foreground}\"/>");
        }

        sb.Append("</svg>");
        return sb.ToString();
    }

    static (IReadOnlyList<BarcodeRect> bars, double vbWidth, double vbHeight) EncodeToBars(RadzenBarcodeType type, string value, double barHeight, int quietZoneModules)
    {
        switch (type)
        {
            case RadzenBarcodeType.Code128:
            {
                var widths = EncodeCode128B(value, out _);
                var (bars, vbWidth) = CreateFromWidths(widths, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Code39:
            {
                var widths = EncodeCode39(value);
                var (bars, vbWidth) = CreateFromWidths(widths, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Codabar:
            {
                var widths = EncodeCodabar(value);
                var (bars, vbWidth) = CreateFromWidths(widths, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Itf:
            {
                var widths = EncodeItf(value);
                var (bars, vbWidth) = CreateFromWidths(widths, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Ean13:
            {
                var bits = EncodeEan13(value, out _);
                var (bars, vbWidth) = CreateFromBits(bits, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Ean8:
            {
                var bits = EncodeEan8(value, out _);
                var (bars, vbWidth) = CreateFromBits(bits, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.UpcA:
            {
                var bits = EncodeUpcA(value, out _);
                var (bars, vbWidth) = CreateFromBits(bits, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Isbn:
            {
                var bits = EncodeIsbnAsEan13(value, out _);
                var (bars, vbWidth) = CreateFromBits(bits, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Issn:
            {
                var bits = EncodeIssnAsEan13(value, out _);
                var (bars, vbWidth) = CreateFromBits(bits, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Pharmacode:
            {
                var geometry = EncodePharmacode(value, barHeight, quietZoneModules);
                var (bars, vbWidth) = CreateFromRects(geometry);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Postnet:
            {
                var geometry = EncodePostnet(value, barHeight, quietZoneModules, out _);
                var (bars, vbWidth) = CreateFromRects(geometry);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Rm4scc:
            {
                var geometry = EncodeRm4scc(value, barHeight, quietZoneModules, out _);
                var (bars, vbWidth) = CreateFromRects(geometry);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Msi:
            {
                var bits = EncodeMsiPlessey(value, out _);
                var (bars, vbWidth) = CreateFromBits(bits, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            case RadzenBarcodeType.Telepen:
            {
                var widths = EncodeTelepen(value, out _);
                var (bars, vbWidth) = CreateFromWidths(widths, barHeight, quietZoneModules);
                return (bars, vbWidth, ComputeBarsHeight(bars, barHeight));
            }
            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, "Unsupported barcode type.");
        }
    }

    static (IReadOnlyList<BarcodeRect> bars, double vbWidth) CreateFromWidths(IReadOnlyList<int> widths, double barHeight, int quietZoneModules)
    {
        ArgumentNullException.ThrowIfNull(widths);

        var rects = new List<BarcodeRect>();
        double x = Math.Max(0, quietZoneModules);
        bool isBar = true;
        for (int i = 0; i < widths.Count; i++)
        {
            var w = widths[i];
            if (isBar && w > 0)
            {
                rects.Add(new BarcodeRect(x, 0, w, barHeight));
            }
            x += w;
            isBar = !isBar;
        }

        var vbWidth = x + Math.Max(0, quietZoneModules);
        if (vbWidth <= 0)
        {
            vbWidth = 1;
        }

        return (rects, vbWidth);
    }

    static (IReadOnlyList<BarcodeRect> bars, double vbWidth) CreateFromBits(string bits, double barHeight, int quietZoneModules)
    {
        ArgumentNullException.ThrowIfNull(bits);

        if (bits.Length == 0)
        {
            return (Array.Empty<BarcodeRect>(), 1);
        }

        var rects = new List<BarcodeRect>();
        var quiet = Math.Max(0, quietZoneModules);
        for (int i = 0; i < bits.Length;)
        {
            if (bits[i] != '1')
            {
                i++;
                continue;
            }

            int j = i + 1;
            while (j < bits.Length && bits[j] == '1')
            {
                j++;
            }

            rects.Add(new BarcodeRect(quiet + i, 0, j - i, barHeight));
            i = j;
        }

        var vbWidth = quiet + bits.Length + quiet;
        if (vbWidth <= 0)
        {
            vbWidth = 1;
        }

        return (rects, vbWidth);
    }

    static (IReadOnlyList<BarcodeRect> bars, double vbWidth) CreateFromRects((IReadOnlyList<BarcodeRect> bars, double vbWidth) geometry)
    {
        var vbWidth = geometry.vbWidth;
        if (vbWidth <= 0)
        {
            vbWidth = 1;
        }

        return (geometry.bars, vbWidth);
    }

    static double ComputeBarsHeight(IReadOnlyList<BarcodeRect> bars, double fallbackHeight)
    {
        if (bars.Count == 0)
        {
            return Math.Max(0, fallbackHeight);
        }

        double max = 0;
        for (int i = 0; i < bars.Count; i++)
        {
            var bar = bars[i];
            max = Math.Max(max, bar.Y + bar.Height);
        }
        return Math.Max(0, max);
    }

    static int CountBits(int v)
    {
        int c = 0;
        while (v != 0)
        {
            c += v & 1;
            v >>= 1;
        }
        return c;
    }

    static string F(double v) => v.ToString(CultureInfo.InvariantCulture);
}
