using System.Text;

namespace RioJoy.Core.Overlay;

/// <summary>
/// Imports the legacy "Goobie" label data file — the Google-Sheet export consumed
/// by sg-goobie-MFD.scm (see docs/reference/customBackground/TEST.data). The format
/// is Lisp-ish: <c>;</c> starts a comment to end of line, a parenthesized list of
/// bare field names (matching the template layer/region names) comes first, then
/// one parenthesized list of quoted strings per output image (row). Field 0 is the
/// row's file tag (output name); the rest are the per-region label text.
///
/// <para>Each row maps directly to an <see cref="OverlayLayoutEngine"/> label set
/// (region name → text), so this is the bridge from the old sheet to a profile's
/// <see cref="Profiles.RioProfile.OverlayLabels"/>.</para>
/// </summary>
public static class GoobieDataImporter
{
    /// <summary>A parsed data file: the field names and one label map per row.</summary>
    public sealed record Sheet(
        IList<string> Fields,
        IList<IDictionary<string, string>> Rows);

    public static Sheet Load(string path) => Parse(File.ReadAllText(path));

    /// <summary>Parse the data file text into fields + rows.</summary>
    public static Sheet Parse(string text)
    {
        if (text is null) throw new ArgumentNullException(nameof(text));

        List<List<string>> lists = ReadLists(text);
        if (lists.Count == 0)
            throw new FormatException("No lists found; expected a header list of field names.");

        List<string> fields = lists[0];
        var rows = new List<IDictionary<string, string>>();

        for (int i = 1; i < lists.Count; i++)
        {
            List<string> values = lists[i];
            var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            for (int f = 0; f < fields.Count && f < values.Count; f++)
                map[fields[f]] = values[f];
            rows.Add(map);
        }

        return new Sheet(fields, rows);
    }

    /// <summary>
    /// Convenience: the label map for a single row (default the first), with empty
    /// values dropped — ready to hand to <see cref="OverlayLayoutEngine.Layout"/>.
    /// </summary>
    public static IDictionary<string, string> LabelsForRow(Sheet sheet, int rowIndex = 0)
    {
        if (sheet is null) throw new ArgumentNullException(nameof(sheet));
        if (rowIndex < 0 || rowIndex >= sheet.Rows.Count)
            throw new ArgumentOutOfRangeException(nameof(rowIndex));

        return sheet.Rows[rowIndex]
            .Where(kv => !string.IsNullOrEmpty(kv.Value))
            .ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.OrdinalIgnoreCase);
    }

    // Tokenize into parenthesized lists. Barewords and "quoted strings" are both
    // returned as their text (quotes stripped); ';' comments run to end of line.
    private static List<List<string>> ReadLists(string s)
    {
        var lists = new List<List<string>>();
        List<string>? current = null;
        int i = 0;

        while (i < s.Length)
        {
            char c = s[i];

            if (c == ';')
            {
                while (i < s.Length && s[i] != '\n') i++;
            }
            else if (char.IsWhiteSpace(c))
            {
                i++;
            }
            else if (c == '(')
            {
                current = new List<string>();
                i++;
            }
            else if (c == ')')
            {
                if (current is not null) { lists.Add(current); current = null; }
                i++;
            }
            else if (c == '"')
            {
                i++;
                var sb = new StringBuilder();
                while (i < s.Length && s[i] != '"')
                {
                    if (s[i] == '\\' && i + 1 < s.Length) i++; // unescape \" and \\
                    sb.Append(s[i++]);
                }
                i++; // closing quote
                current?.Add(sb.ToString());
            }
            else
            {
                int start = i;
                while (i < s.Length && !char.IsWhiteSpace(s[i]) && s[i] is not ('(' or ')' or '"' or ';'))
                    i++;
                current?.Add(s[start..i]);
            }
        }

        return lists;
    }
}
