using System.Globalization;
using System.Text;

namespace RioJoy.Core.Editing;

/// <summary>Section of the authoring sheet a cell belongs to (drives editor colour).</summary>
public enum SheetSection
{
    None,
    Contacts,   // 0x00–0x0F
    Secondary,  // 0x10–0x17
    Screen,     // 0x18–0x1F
    Letters,    // 0x20–0x37
    Throttle,   // 0x38–0x3F
    Joystick,   // 0x40–0x47
    Keypad,     // 0x50–0x5F
    ExtKeypad,  // 0x60–0x6F
}

/// <summary>
/// One cell of the cockpit authoring spreadsheet: its grid <see cref="Row"/>/<see cref="Col"/>,
/// the cell <see cref="Text"/>, and — when the text is a RIO address (two hex
/// digits, 0x00–0x6F) — the <see cref="Address"/> it edits and its
/// <see cref="Section"/>.
/// </summary>
public sealed record SheetCell(int Row, int Col, string Text, int? Address, SheetSection Section);

/// <summary>
/// Parses the legacy authoring spreadsheet (exported CSV, see
/// docs/reference/customBackground/config-sheet.csv) into a grid of
/// <see cref="SheetCell"/>s. This is the cockpit's <em>logical</em> layout — the
/// shape the editor mirrors — as opposed to the wallpaper's pixel positions, which
/// are a VGA chroma-split display artifact. Pure, so it is unit-tested.
/// </summary>
public static class SheetLayout
{
    public static IList<SheetCell> Load(string path) => Parse(File.ReadAllText(path));

    /// <summary>
    /// The RIO address a cell's text encodes (two hex digits in 0x00–0x6F), or null.
    /// </summary>
    public static int? AddressFromText(string text)
    {
        string t = text.Trim();
        if (t.Length != 2) return null;
        if (!int.TryParse(t, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int a)) return null;
        return a is >= 0 and <= 0x6F ? a : null;
    }

    /// <summary>Classify an address into a sheet section.</summary>
    public static SheetSection SectionForAddress(int address) => address switch
    {
        >= 0x00 and <= 0x0F => SheetSection.Contacts,
        >= 0x10 and <= 0x17 => SheetSection.Secondary,
        >= 0x18 and <= 0x1F => SheetSection.Screen,
        >= 0x20 and <= 0x37 => SheetSection.Letters,
        >= 0x38 and <= 0x3F => SheetSection.Throttle,
        >= 0x40 and <= 0x47 => SheetSection.Joystick,
        >= 0x50 and <= 0x5F => SheetSection.Keypad,
        >= 0x60 and <= 0x6F => SheetSection.ExtKeypad,
        _ => SheetSection.None,
    };

    /// <summary>
    /// Parse the CSV (one spreadsheet row per line) into non-empty cells. Cell text
    /// longer than <paramref name="maxTextLength"/> is dropped as a merged-cell
    /// artifact; the clean export has none.
    /// </summary>
    public static IList<SheetCell> Parse(string csv, int maxTextLength = 40)
    {
        if (csv is null) throw new ArgumentNullException(nameof(csv));

        string[] lines = csv.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
        var cells = new List<SheetCell>();

        for (int row = 0; row < lines.Length; row++)
        {
            if (lines[row].Length == 0) continue;
            List<string> fields = ParseLine(lines[row]);
            for (int col = 0; col < fields.Count; col++)
            {
                string text = fields[col].Trim();
                if (text.Length == 0 || text.Length > maxTextLength) continue;

                int? address = AddressFromText(text);
                SheetSection section = address is { } a ? SectionForAddress(a) : SheetSection.None;
                cells.Add(new SheetCell(row, col, text, address, section));
            }
        }

        return cells;
    }

    // Split one CSV line into fields, honouring "quoted" fields and "" escapes.
    private static List<string> ParseLine(string line)
    {
        var fields = new List<string>();
        var sb = new StringBuilder();
        bool inQuotes = false;

        for (int i = 0; i < line.Length; i++)
        {
            char c = line[i];
            if (inQuotes)
            {
                if (c == '"')
                {
                    if (i + 1 < line.Length && line[i + 1] == '"') { sb.Append('"'); i++; }
                    else inQuotes = false;
                }
                else sb.Append(c);
            }
            else if (c == '"') inQuotes = true;
            else if (c == ',') { fields.Add(sb.ToString()); sb.Clear(); }
            else sb.Append(c);
        }
        fields.Add(sb.ToString());
        return fields;
    }
}
