using System.Text;
namespace RioJoy.Core.Overlay;
///
/// 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: ; 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.
///
/// Each row maps directly to an label set
/// (region name → text), so this is the bridge from the old sheet to a profile's
/// .
///
public static class GoobieDataImporter
{
/// A parsed data file: the field names and one label map per row.
public sealed record Sheet(
IList Fields,
IList> Rows);
public static Sheet Load(string path) => Parse(File.ReadAllText(path));
/// Parse the data file text into fields + rows.
public static Sheet Parse(string text)
{
if (text is null) throw new ArgumentNullException(nameof(text));
List> lists = ReadLists(text);
if (lists.Count == 0)
throw new FormatException("No lists found; expected a header list of field names.");
List fields = lists[0];
var rows = new List>();
for (int i = 1; i < lists.Count; i++)
{
List values = lists[i];
var map = new Dictionary(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);
}
///
/// Convenience: the label map for a single row (default the first), with empty
/// values dropped — ready to hand to .
///
public static IDictionary 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> ReadLists(string s)
{
var lists = new List>();
List? 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();
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;
}
}