using Gherkin.Ast;
using System.Text.RegularExpressions;

namespace Gherkin;

public class GherkinLine : IGherkinLine
{
    private static char[] inlineWhitespaceChars = [' ', '\t', '\u00A0'];

    private readonly string lineText;
    private readonly string trimmedLineText;
    public int LineNumber { get; }

    public GherkinLine(string line, int lineNumber)
    {
        LineNumber = lineNumber;

        lineText = line;
        trimmedLineText = lineText.TrimStart();
    }

    public void Detach()
    {
        //nop
    }

    public int Indent
    {
        get { return lineText.Length - trimmedLineText.Length; }
    }

    public bool IsEmpty()
    {
        return trimmedLineText.Length == 0;
    }

    public bool StartsWith(string text)
    {
        return trimmedLineText.StartsWith(text);
    }

    public bool StartsWithTitleKeyword(string text)
    {
        return StringUtils.StartsWith(trimmedLineText, text) &&
            StartsWithFrom(trimmedLineText, text.Length, GherkinLanguageConstants.TITLE_KEYWORD_SEPARATOR);
    }

    private static bool StartsWithFrom(string text, int textIndex, string value)
    {
        return string.CompareOrdinal(text, textIndex, value, 0, value.Length) == 0;
    }

    public string GetLineText(int indentToRemove)
    {
        if (indentToRemove < 0 || indentToRemove > Indent)
            return trimmedLineText;

        return lineText.Substring(indentToRemove);
    }

    public string GetRestTrimmed(int length)
    {
        return trimmedLineText.Substring(length).Trim();
    }

    public IEnumerable<GherkinLineSpan> GetTags()
    {
        var uncommentedLine = Regex.Split(trimmedLineText, @"\s" + GherkinLanguageConstants.COMMENT_PREFIX)[0];
        int position = Indent;
        foreach (string item in uncommentedLine.Split(GherkinLanguageConstants.TAG_PREFIX[0]))
        {
            if (item.Length > 0)
            {
                var tagName = GherkinLanguageConstants.TAG_PREFIX + item.TrimEnd(inlineWhitespaceChars);
                if (tagName.Length == 1)
                    continue;

                if (tagName.IndexOfAny(inlineWhitespaceChars) >= 0)
                    throw new InvalidTagException("A tag may not contain whitespace", new Location(LineNumber, position));

                yield return new GherkinLineSpan(position, tagName);
                position += item.Length;
            }
            position++; // separator
        }
    }

    public IEnumerable<GherkinLineSpan> GetTableCells()
    {
        var items = SplitCells(trimmedLineText).ToList();
        bool isBeforeFirst = true;
        foreach (var item in items.Take(items.Count - 1)) // skipping the one after last
        {
            if (!isBeforeFirst)
            {
                int trimmedStart;
                var cellText = Trim(item.Item1, out trimmedStart);
                var cellPosition = item.Item2 + trimmedStart;

                if (cellText.Length == 0)
                    cellPosition = item.Item2;

                yield return new GherkinLineSpan(Indent + cellPosition + 1, cellText);
            }

            isBeforeFirst = false;
        }
    }

    private IEnumerable<Tuple<string, int>> SplitCells(string row)
    {
        var rowEnum = row.GetEnumerator();

        string cell = "";
        int pos = 0;
        int startPos = 0;
        while (rowEnum.MoveNext())
        {
            pos++;
            char c = rowEnum.Current;
            if (c.ToString() == GherkinLanguageConstants.TABLE_CELL_SEPARATOR)
            {
                yield return Tuple.Create(cell, startPos);
                cell = "";
                startPos = pos;
            }
            else if (c == GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR)
            {
                if (rowEnum.MoveNext())
                {
                    pos++;
                    c = rowEnum.Current;
                    if (c == GherkinLanguageConstants.TABLE_CELL_NEWLINE_ESCAPE)
                    {
                        cell += "\n";
                    }
                    else
                    {
                        if (c.ToString() != GherkinLanguageConstants.TABLE_CELL_SEPARATOR && c != GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR)
                        {
                            cell += GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR;
                        }
                        cell += c;
                    }
                }
                else
                {
                    cell += GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR;
                }
            }
            else
            {
                cell += c;
            }
        }
        yield return Tuple.Create(cell, startPos);
    }

    private string Trim(string s, out int trimmedStart)
    {
        trimmedStart = 0;
        while (trimmedStart < s.Length && inlineWhitespaceChars.Contains(s[trimmedStart]))
            trimmedStart++;

        return s.Trim(inlineWhitespaceChars);
    }
}
