namespace ZB.MOM.WW.ScadaBridge.Commons.Types;

/// <summary>
/// Shared, dependency-free RFC-4180-ish splitter for a single physical CSV line.
/// Extracted so the instance-override CSV parsers (<see cref="OverrideCsvParser"/>
/// and <see cref="NativeAlarmSourceOverrideCsvParser"/>) apply identical quoting
/// rules without duplicating the subtle field-state machine.
/// </summary>
/// <remarks>
/// Quoting rules:
/// <list type="bullet">
/// <item>A field is <i>quoted</i> only if its first non-whitespace char is a
/// <c>"</c> (leading whitespace before the opening quote is allowed and ignored).
/// Inside a quoted field, commas are literal and <c>""</c> is an escaped single
/// quote; the closing <c>"</c> must be the last non-whitespace char of the field
/// (trailing whitespace after the close is allowed and ignored).</item>
/// <item>A <c>"</c> appearing anywhere else in an unquoted field (i.e. after
/// non-whitespace content) is a <b>literal</b> character and is preserved.</item>
/// <item>Unquoted fields are whitespace-trimmed; quoted field values are kept
/// verbatim.</item>
/// </list>
/// </remarks>
public static class CsvLineSplitter
{
    /// <summary>
    /// Splits a single physical CSV line into its fields, applying the quoting rules
    /// described on <see cref="CsvLineSplitter"/>. Returns <c>true</c> with the split
    /// <paramref name="fields"/> on success; returns <c>false</c> when a quoted field
    /// is opened but never closed before end-of-line (the caller emits a per-line
    /// "unterminated" error).
    /// </summary>
    /// <param name="line">The physical line to split (no trailing newline).</param>
    /// <param name="fields">The split fields on success; an empty list on failure.</param>
    /// <returns><c>true</c> when the line split cleanly; <c>false</c> on an unterminated quoted field.</returns>
    public static bool TrySplit(string line, out List<string> fields)
    {
        fields = new List<string>();
        var field = new System.Text.StringBuilder();
        var inQuotes = false;   // currently between an opening and closing quote
        var quoted = false;     // this field opened with a quote → keep value verbatim
        var sawContent = false; // any non-whitespace char seen in the current field yet

        for (var i = 0; i < line.Length; i++)
        {
            var c = line[i];

            if (inQuotes)
            {
                if (c == '"')
                {
                    // Doubled quote inside a quoted field → a single literal quote.
                    if (i + 1 < line.Length && line[i + 1] == '"')
                    {
                        field.Append('"');
                        i++;
                    }
                    else
                    {
                        inQuotes = false; // closing quote; only trailing whitespace may follow
                    }
                }
                else
                {
                    field.Append(c);
                }

                continue;
            }

            switch (c)
            {
                case ',':
                    fields.Add(Finalize(field, quoted));
                    field.Clear();
                    inQuotes = false;
                    quoted = false;
                    sawContent = false;
                    break;
                case '"' when !sawContent:
                    // Opening quote: first non-whitespace char of the field. Any
                    // leading whitespace seen so far is part of the (ignored) prefix.
                    field.Clear();
                    inQuotes = true;
                    quoted = true;
                    sawContent = true;
                    break;
                default:
                    // After a quoted field has closed, only whitespace may appear
                    // before the next delimiter — it is ignored, not appended.
                    if (quoted)
                        break;

                    // A '"' here (sawContent already true) falls through as a literal.
                    if (!char.IsWhiteSpace(c))
                        sawContent = true;
                    field.Append(c);
                    break;
            }
        }

        if (inQuotes)
            return false; // opened a quoted field that was never closed

        fields.Add(Finalize(field, quoted));
        return true;
    }

    private static string Finalize(System.Text.StringBuilder field, bool quoted)
    {
        var text = field.ToString();
        return quoted ? text : text.Trim(); // only unquoted whitespace is trimmed
    }
}
