using System.Text.RegularExpressions;

namespace Gherkin;

public class TokenMatcher(IGherkinDialectProvider dialectProvider = null) : ITokenMatcher
{
    private readonly Regex LANGUAGE_PATTERN = new Regex("^\\s*#\\s*language\\s*:\\s*([a-zA-Z\\-_]+)\\s*$");
    private readonly IGherkinDialectProvider dialectProvider = dialectProvider ?? new GherkinDialectProvider();
    private GherkinDialect currentDialect;
    private string activeDocStringSeparator = null;
    private int indentToRemove = 0;

    public GherkinDialect CurrentDialect
    {
        get
        {
            if (currentDialect == null)
                currentDialect = dialectProvider.DefaultDialect;
            return currentDialect;
        }
    }

    public TokenMatcher(string defaultLanguage) : this(new GherkinDialectProvider(defaultLanguage))
    {
    }

    public void Reset()
    {
        activeDocStringSeparator = null;
        indentToRemove = 0;
        if (currentDialect != dialectProvider.DefaultDialect)
            currentDialect = dialectProvider.DefaultDialect;
    }

    protected virtual void SetTokenMatched(Token token, TokenType matchedType, string text = null, string keyword = null, int? indent = null, GherkinLineSpan[] items = null)
    {
        token.MatchedType = matchedType;
        token.MatchedKeyword = keyword;
        token.MatchedText = text;
        token.MatchedItems = items;
        token.MatchedGherkinDialect = CurrentDialect;
        token.MatchedIndent = indent ?? (token.Line == null ? 0 : token.Line.Indent);
        token.Location = new Ast.Location(token.Location.Line, token.MatchedIndent + 1);
    }

    public bool Match_EOF(Token token)
    {
        if (token.IsEOF)
        {
            SetTokenMatched(token, TokenType.EOF);
            return true;
        }
        return false;
    }

    public bool Match_Other(Token token)
    {
        var text = token.Line.GetLineText(indentToRemove); //take the entire line, except removing DocString indents
        SetTokenMatched(token, TokenType.Other, UnescapeDocString(text), indent: 0);
        return true;
    }

    public bool Match_Empty(Token token)
    {
        if (token.Line.IsEmpty())
        {
            SetTokenMatched(token, TokenType.Empty);
            return true;
        }
        return false;
    }

    public bool Match_Comment(Token token)
    {
        if (token.Line.StartsWith(GherkinLanguageConstants.COMMENT_PREFIX))
        {
            var text = token.Line.GetLineText(); //take the entire line
            SetTokenMatched(token, TokenType.Comment, text, indent: 0);
            return true;
        }
        return false;
    }

    private ParserException CreateTokenMatcherException(Token token, string message)
    {
        return new AstBuilderException(message, new Ast.Location(token.Location.Line, token.Line.Indent + 1));
    }

    public bool Match_Language(Token token)
    {
        var match = LANGUAGE_PATTERN.Match(token.Line.GetLineText());

        if (match.Success)
        {
            var language = match.Groups[1].Value;
            SetTokenMatched(token, TokenType.Language, language);

            try
            {
                currentDialect = dialectProvider.GetDialect(language, token.Location);
            }
            catch (NotSupportedException ex)
            {
                throw CreateTokenMatcherException(token, ex.Message);
            }

            return true;
        }
        return false;
    }

    public bool Match_TagLine(Token token)
    {
        if (token.Line.StartsWith(GherkinLanguageConstants.TAG_PREFIX))
        {
            SetTokenMatched(token, TokenType.TagLine, items: token.Line.GetTags().ToArray());
            return true;
        }
        return false;
    }

    public bool Match_FeatureLine(Token token)
    {
        return MatchTitleLine(token, TokenType.FeatureLine, CurrentDialect.FeatureKeywords);
    }

    public bool Match_RuleLine(Token token)
    {
        return MatchTitleLine(token, TokenType.RuleLine, CurrentDialect.RuleKeywords);
    }

    public bool Match_BackgroundLine(Token token)
    {
        return MatchTitleLine(token, TokenType.BackgroundLine, CurrentDialect.BackgroundKeywords);
    }

    public bool Match_ScenarioLine(Token token)
    {
        return MatchTitleLine(token, TokenType.ScenarioLine, CurrentDialect.ScenarioKeywords)
            || MatchTitleLine(token, TokenType.ScenarioLine, CurrentDialect.ScenarioOutlineKeywords);
    }

    public bool Match_ExamplesLine(Token token)
    {
        return MatchTitleLine(token, TokenType.ExamplesLine, CurrentDialect.ExamplesKeywords);
    }

    private bool MatchTitleLine(Token token, TokenType tokenType, string[] keywords)
    {
        foreach (var keyword in keywords)
        {
            if (token.Line.StartsWithTitleKeyword(keyword))
            {
                var title = token.Line.GetRestTrimmed(keyword.Length + GherkinLanguageConstants.TITLE_KEYWORD_SEPARATOR.Length);
                SetTokenMatched(token, tokenType, keyword: keyword, text: title);
                return true;
            }
        }
        return false;
    }

    public bool Match_DocStringSeparator(Token token)
    {
        return activeDocStringSeparator == null
            // open
            ? Match_DocStringSeparator(token, GherkinLanguageConstants.DOCSTRING_SEPARATOR, true) ||
              Match_DocStringSeparator(token, GherkinLanguageConstants.DOCSTRING_ALTERNATIVE_SEPARATOR, true)
            // close
            : Match_DocStringSeparator(token, activeDocStringSeparator, false);
    }

    private bool Match_DocStringSeparator(Token token, string separator, bool isOpen)
    {
        if (token.Line.StartsWith(separator))
        {
            string contentType = null;
            if (isOpen)
            {
                contentType = token.Line.GetRestTrimmed(separator.Length);
                activeDocStringSeparator = separator;
                indentToRemove = token.Line.Indent;
            }
            else
            {
                activeDocStringSeparator = null;
                indentToRemove = 0;
            }

            SetTokenMatched(token, TokenType.DocStringSeparator, contentType, separator);
            return true;
        }
        return false;
    }


    public bool Match_StepLine(Token token)
    {
        var keywords = CurrentDialect.StepKeywords;
        foreach (var keyword in keywords)
        {
            if (token.Line.StartsWith(keyword))
            {
                var stepText = token.Line.GetRestTrimmed(keyword.Length);
                SetTokenMatched(token, TokenType.StepLine, keyword: keyword, text: stepText);
                return true;
            }
        }
        return false;
    }

    public bool Match_TableRow(Token token)
    {
        if (token.Line.StartsWith(GherkinLanguageConstants.TABLE_CELL_SEPARATOR))
        {
            SetTokenMatched(token, TokenType.TableRow, items: token.Line.GetTableCells().ToArray());
            return true;
        }
        return false;
    }

    private string UnescapeDocString(string text)
    {
        return activeDocStringSeparator != null ? text.Replace("\\\"\\\"\\\"", "\"\"\"").Replace("\\`\\`\\`", "```") : text;
    }
}
