// Port of Go conf/lex.go — state-machine tokenizer for NATS config files.
// Reference: golang/nats-server/conf/lex.go

using System.Buffers;
using System.Globalization;
using System.Text;

namespace NATS.Server.Configuration;

public sealed class NatsConfLexer
{
    private const char Eof = '\0';
    private const char MapStartChar = '{';
    private const char MapEndChar = '}';
    private const char KeySepEqual = '=';
    private const char KeySepColon = ':';
    private const char ArrayStartChar = '[';
    private const char ArrayEndChar = ']';
    private const char ArrayValTerm = ',';
    private const char MapValTerm = ',';
    private const char CommentHashStart = '#';
    private const char CommentSlashStart = '/';
    private const char DqStringStart = '"';
    private const char DqStringEnd = '"';
    private const char SqStringStart = '\'';
    private const char SqStringEnd = '\'';
    private const char OptValTerm = ';';
    private const char TopOptStart = '{';
    private const char TopOptTerm = '}';
    private const char TopOptValTerm = ',';
    private const char BlockStartChar = '(';
    private const char BlockEndChar = ')';

    private delegate LexState? LexState(NatsConfLexer lx);

    private readonly string _input;
    private int _start;
    private int _pos;
    private int _width;
    private int _line;
    private readonly List<Token> _items;
    private readonly Stack<LexState> _stack;
    private readonly List<string> _stringParts;
    private LexState? _stringStateFn;

    // Start position of the current line (after newline character).
    private int _lstart;

    // Start position of the line from the current item.
    private int _ilstart;

    private NatsConfLexer(string input)
    {
        _input = input;
        _start = 0;
        _pos = 0;
        _width = 0;
        _line = 1;
        _items = [];
        _stack = new Stack<LexState>();
        _stringParts = [];
        _lstart = 0;
        _ilstart = 0;
    }

    public static IReadOnlyList<Token> Tokenize(string input)
    {
        ArgumentNullException.ThrowIfNull(input);
        var lx = new NatsConfLexer(input);
        LexState? state = LexTop;
        while (state is not null)
        {
            state = state(lx);
        }

        return lx._items;
    }

    private void Push(LexState state) => _stack.Push(state);

    private LexState? Pop()
    {
        if (_stack.Count == 0)
        {
            return Errorf("BUG in lexer: no states to pop.");
        }

        return _stack.Pop();
    }

    private void Emit(TokenType type)
    {
        string val;
        if (_stringParts.Count > 0)
        {
            val = string.Concat(_stringParts) + _input[_start.._pos];
            _stringParts.Clear();
        }
        else
        {
            val = _input[_start.._pos];
        }

        var pos = _pos - _ilstart - val.Length;
        _items.Add(new Token(type, val, _line, pos));
        _start = _pos;
        _ilstart = _lstart;
    }

    private void EmitString()
    {
        string finalString;
        if (_stringParts.Count > 0)
        {
            finalString = string.Concat(_stringParts) + _input[_start.._pos];
            _stringParts.Clear();
        }
        else
        {
            finalString = _input[_start.._pos];
        }

        var pos = _pos - _ilstart - finalString.Length;
        _items.Add(new Token(TokenType.String, finalString, _line, pos));
        _start = _pos;
        _ilstart = _lstart;
    }

    private void AddCurrentStringPart(int offset)
    {
        _stringParts.Add(_input[_start..(_pos - offset)]);
        _start = _pos;
    }

    private LexState? AddStringPart(string s)
    {
        _stringParts.Add(s);
        _start = _pos;
        return _stringStateFn;
    }

    private bool HasEscapedParts() => _stringParts.Count > 0;

    private char Next()
    {
        if (_pos >= _input.Length)
        {
            _width = 0;
            return Eof;
        }

        var span = _input.AsSpan(_pos);
        var status = Rune.DecodeFromUtf16(span, out var rune, out var consumed);
        if (status != OperationStatus.Done || consumed <= 0)
        {
            consumed = 1;
            rune = new Rune(_input[_pos]);
        }

        if (rune.Value == '\n')
        {
            _line++;
            _lstart = _pos;
        }

        _width = consumed;
        _pos += consumed;
        return rune.IsBmp ? (char)rune.Value : '\uFFFD';
    }

    private void Ignore()
    {
        _start = _pos;
        _ilstart = _lstart;
    }

    private void Backup()
    {
        _pos -= _width;
        if (_pos < _input.Length && _input[_pos] == '\n')
        {
            _line--;
        }
    }

    private char Peek()
    {
        var r = Next();
        Backup();
        return r;
    }

    private LexState? Errorf(string message)
    {
        var pos = _pos - _lstart;
        _items.Add(new Token(TokenType.Error, message, _line, pos));
        return null;
    }

    private LexState? Errorf(string format, params object?[] args)
    {
        if (args.Length == 0)
            return Errorf(format);

        var escapedArgs = new object?[args.Length];
        for (var i = 0; i < args.Length; i++)
        {
            escapedArgs[i] = args[i] is char c ? EscapeSpecial(c) : args[i];
        }

        return Errorf(string.Format(CultureInfo.InvariantCulture, format, escapedArgs));
    }

    // --- Helper methods ---

    private static bool IsWhitespace(char c) => c is '\t' or ' ';

    private static bool IsNL(char c) => c is '\n' or '\r';

    private static bool IsKeySeparator(char c) => c is KeySepEqual or KeySepColon;

    private static bool IsNumberSuffix(char c) =>
        c is 'k' or 'K' or 'm' or 'M' or 'g' or 'G' or 't' or 'T' or 'p' or 'P' or 'e' or 'E';

    // --- State functions ---

    private static LexState? LexTop(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (char.IsWhiteSpace(r))
        {
            return LexSkip(lx, LexTop);
        }

        switch (r)
        {
            case TopOptStart:
                lx.Push(LexTop);
                return LexSkip(lx, LexBlockStart);
            case CommentHashStart:
                lx.Push(LexTop);
                return LexCommentStart;
            case CommentSlashStart:
            {
                var rn = lx.Next();
                if (rn == CommentSlashStart)
                {
                    lx.Push(LexTop);
                    return LexCommentStart;
                }

                lx.Backup();
                goto case Eof;
            }

            case Eof:
                if (lx._pos > lx._start)
                {
                    return lx.Errorf("Unexpected EOF.");
                }

                lx.Emit(TokenType.Eof);
                return null;
        }

        // Back up and let the key lexer handle it.
        lx.Backup();
        lx.Push(LexTopValueEnd);
        return LexKeyStart;
    }

    private static LexState? LexTopValueEnd(NatsConfLexer lx)
    {
        var r = lx.Next();
        switch (r)
        {
            case CommentHashStart:
                lx.Push(LexTop);
                return LexCommentStart;
            case CommentSlashStart:
            {
                var rn = lx.Next();
                if (rn == CommentSlashStart)
                {
                    lx.Push(LexTop);
                    return LexCommentStart;
                }

                lx.Backup();
                if (IsWhitespace(r))
                {
                    return LexTopValueEnd;
                }

                break;
            }

            default:
                if (IsWhitespace(r))
                {
                    return LexTopValueEnd;
                }

                break;
        }

        if (IsNL(r) || r == Eof || r == OptValTerm || r == TopOptValTerm || r == TopOptTerm)
        {
            lx.Ignore();
            return LexTop;
        }

        return lx.Errorf($"Expected a top-level value to end with a new line, comment or EOF, but got '{EscapeSpecial(r)}' instead.");
    }

    private static LexState? LexBlockStart(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (char.IsWhiteSpace(r))
        {
            return LexSkip(lx, LexBlockStart);
        }

        switch (r)
        {
            case TopOptStart:
                lx.Push(LexBlockEnd);
                return LexSkip(lx, LexBlockStart);
            case TopOptTerm:
                lx.Ignore();
                return lx.Pop();
            case CommentHashStart:
                lx.Push(LexBlockStart);
                return LexCommentStart;
            case CommentSlashStart:
            {
                var rn = lx.Next();
                if (rn == CommentSlashStart)
                {
                    lx.Push(LexBlockStart);
                    return LexCommentStart;
                }

                lx.Backup();
                goto case Eof;
            }

            case Eof:
                if (lx._pos > lx._start)
                {
                    return lx.Errorf("Unexpected EOF.");
                }

                lx.Emit(TokenType.Eof);
                return null;
        }

        lx.Backup();
        lx.Push(LexBlockValueEnd);
        return LexKeyStart;
    }

    private static LexState? LexBlockValueEnd(NatsConfLexer lx)
    {
        var r = lx.Next();
        switch (r)
        {
            case CommentHashStart:
                lx.Push(LexBlockValueEnd);
                return LexCommentStart;
            case CommentSlashStart:
            {
                var rn = lx.Next();
                if (rn == CommentSlashStart)
                {
                    lx.Push(LexBlockValueEnd);
                    return LexCommentStart;
                }

                lx.Backup();
                if (IsWhitespace(r))
                {
                    return LexBlockValueEnd;
                }

                break;
            }

            default:
                if (IsWhitespace(r))
                {
                    return LexBlockValueEnd;
                }

                break;
        }

        if (IsNL(r) || r == OptValTerm || r == TopOptValTerm)
        {
            lx.Ignore();
            return LexBlockStart;
        }

        if (r == TopOptTerm)
        {
            lx.Backup();
            return LexBlockEnd;
        }

        return lx.Errorf($"Expected a block-level value to end with a new line, comment or EOF, but got '{EscapeSpecial(r)}' instead.");
    }

    private static LexState? LexBlockEnd(NatsConfLexer lx)
    {
        var r = lx.Next();
        switch (r)
        {
            case CommentHashStart:
                lx.Push(LexBlockStart);
                return LexCommentStart;
            case CommentSlashStart:
            {
                var rn = lx.Next();
                if (rn == CommentSlashStart)
                {
                    lx.Push(LexBlockStart);
                    return LexCommentStart;
                }

                lx.Backup();
                if (IsNL(r) || IsWhitespace(r))
                {
                    return LexBlockEnd;
                }

                break;
            }

            default:
                if (IsNL(r) || IsWhitespace(r))
                {
                    return LexBlockEnd;
                }

                break;
        }

        if (r == OptValTerm || r == TopOptValTerm)
        {
            lx.Ignore();
            return LexBlockStart;
        }

        if (r == TopOptTerm)
        {
            lx.Ignore();
            return lx.Pop();
        }

        return lx.Errorf($"Expected a block-level to end with a '}}', but got '{EscapeSpecial(r)}' instead.");
    }

    private static LexState? LexKeyStart(NatsConfLexer lx)
    {
        var r = lx.Peek();
        if (IsKeySeparator(r))
        {
            return lx.Errorf($"Unexpected key separator '{r}'");
        }

        if (char.IsWhiteSpace(r))
        {
            lx.Next();
            return LexSkip(lx, LexKeyStart);
        }

        if (r == DqStringStart)
        {
            lx.Next();
            return LexSkip(lx, LexDubQuotedKey);
        }

        if (r == SqStringStart)
        {
            lx.Next();
            return LexSkip(lx, LexQuotedKey);
        }

        lx.Ignore();
        lx.Next();
        return LexKey;
    }

    private static LexState? LexDubQuotedKey(NatsConfLexer lx)
    {
        var r = lx.Peek();
        if (r == DqStringEnd)
        {
            lx.Emit(TokenType.Key);
            lx.Next();
            return LexSkip(lx, LexKeyEnd);
        }

        if (r == Eof)
        {
            if (lx._pos > lx._start)
            {
                return lx.Errorf("Unexpected EOF.");
            }

            lx.Emit(TokenType.Eof);
            return null;
        }

        lx.Next();
        return LexDubQuotedKey;
    }

    private static LexState? LexQuotedKey(NatsConfLexer lx)
    {
        var r = lx.Peek();
        if (r == SqStringEnd)
        {
            lx.Emit(TokenType.Key);
            lx.Next();
            return LexSkip(lx, LexKeyEnd);
        }

        if (r == Eof)
        {
            if (lx._pos > lx._start)
            {
                return lx.Errorf("Unexpected EOF.");
            }

            lx.Emit(TokenType.Eof);
            return null;
        }

        lx.Next();
        return LexQuotedKey;
    }

    private LexState? KeyCheckKeyword(LexState fallThrough, LexState? push)
    {
        var key = _input[_start.._pos].ToLowerInvariant();
        if (key == "include")
        {
            Ignore();
            if (push is not null)
            {
                Push(push);
            }

            return LexIncludeStart;
        }

        Emit(TokenType.Key);
        return fallThrough;
    }

    private static LexState? LexIncludeStart(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (IsWhitespace(r))
        {
            return LexSkip(lx, LexIncludeStart);
        }

        lx.Backup();
        return LexInclude;
    }

    private static LexState? LexIncludeQuotedString(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (r == SqStringEnd)
        {
            lx.Backup();
            lx.Emit(TokenType.Include);
            lx.Next();
            lx.Ignore();
            return lx.Pop();
        }

        if (r == Eof)
        {
            return lx.Errorf("Unexpected EOF in quoted include");
        }

        return LexIncludeQuotedString;
    }

    private static LexState? LexIncludeDubQuotedString(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (r == DqStringEnd)
        {
            lx.Backup();
            lx.Emit(TokenType.Include);
            lx.Next();
            lx.Ignore();
            return lx.Pop();
        }

        if (r == Eof)
        {
            return lx.Errorf("Unexpected EOF in double quoted include");
        }

        return LexIncludeDubQuotedString;
    }

    private static LexState? LexIncludeString(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (IsNL(r) || r == Eof || r == OptValTerm || r == MapEndChar || IsWhitespace(r))
        {
            lx.Backup();
            lx.Emit(TokenType.Include);
            return lx.Pop();
        }

        if (r == SqStringEnd)
        {
            lx.Backup();
            lx.Emit(TokenType.Include);
            lx.Next();
            lx.Ignore();
            return lx.Pop();
        }

        return LexIncludeString;
    }

    private static LexState? LexInclude(NatsConfLexer lx)
    {
        var r = lx.Next();
        switch (r)
        {
            case SqStringStart:
                lx.Ignore();
                return LexIncludeQuotedString;
            case DqStringStart:
                lx.Ignore();
                return LexIncludeDubQuotedString;
            case ArrayStartChar:
                return lx.Errorf("Expected include value but found start of an array");
            case MapStartChar:
                return lx.Errorf("Expected include value but found start of a map");
            case BlockStartChar:
                return lx.Errorf("Expected include value but found start of a block");
            case '\\':
                return lx.Errorf("Expected include value but found escape sequence");
        }

        if (char.IsDigit(r) || r == '-')
        {
            return lx.Errorf("Expected include value but found start of a number");
        }

        if (IsNL(r))
        {
            return lx.Errorf("Expected include value but found new line");
        }

        lx.Backup();
        return LexIncludeString;
    }

    private static LexState? LexKey(NatsConfLexer lx)
    {
        var r = lx.Peek();
        if (char.IsWhiteSpace(r) && !IsNL(r))
        {
            // Spaces signal we could be looking at a keyword, e.g. include.
            return lx.KeyCheckKeyword(LexKeyEnd, null);
        }

        if (IsKeySeparator(r) || r == Eof)
        {
            lx.Emit(TokenType.Key);
            return LexKeyEnd;
        }

        if (IsNL(r))
        {
            // Newline after key with no separator — check for keyword.
            return lx.KeyCheckKeyword(LexKeyEnd, null);
        }

        lx.Next();
        return LexKey;
    }

    private static LexState? LexKeyEnd(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (char.IsWhiteSpace(r))
        {
            return LexSkip(lx, LexKeyEnd);
        }

        if (IsKeySeparator(r))
        {
            return LexSkip(lx, LexValue);
        }

        if (r == Eof)
        {
            lx.Emit(TokenType.Eof);
            return null;
        }

        // We start the value here.
        lx.Backup();
        return LexValue;
    }

    private static LexState? LexValue(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (IsWhitespace(r))
        {
            return LexSkip(lx, LexValue);
        }

        switch (r)
        {
            case ArrayStartChar:
                lx.Ignore();
                lx.Emit(TokenType.ArrayStart);
                return LexArrayValue;
            case MapStartChar:
                lx.Ignore();
                lx.Emit(TokenType.MapStart);
                return LexMapKeyStart;
            case SqStringStart:
                lx.Ignore();
                return LexQuotedString;
            case DqStringStart:
                lx.Ignore();
                lx._stringStateFn = LexDubQuotedString;
                return LexDubQuotedString;
            case '-':
                return LexNegNumberStart;
            case BlockStartChar:
                lx.Ignore();
                return LexBlock;
            case '.':
                return lx.Errorf("Floats must start with a digit");
        }

        if (char.IsDigit(r))
        {
            lx.Backup();
            return LexNumberOrDateOrStringOrIPStart;
        }

        if (IsNL(r))
        {
            return lx.Errorf("Expected value but found new line");
        }

        lx.Backup();
        lx._stringStateFn = LexString;
        return LexString;
    }

    private static LexState? LexArrayValue(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (char.IsWhiteSpace(r))
        {
            return LexSkip(lx, LexArrayValue);
        }

        switch (r)
        {
            case CommentHashStart:
                lx.Push(LexArrayValue);
                return LexCommentStart;
            case CommentSlashStart:
            {
                var rn = lx.Next();
                if (rn == CommentSlashStart)
                {
                    lx.Push(LexArrayValue);
                    return LexCommentStart;
                }

                lx.Backup();
                // fallthrough to ArrayValTerm check
                if (r == ArrayValTerm)
                {
                    return lx.Errorf($"Unexpected array value terminator '{ArrayValTerm}'.");
                }

                break;
            }

            case ArrayValTerm:
                return lx.Errorf($"Unexpected array value terminator '{ArrayValTerm}'.");
            case ArrayEndChar:
                return LexArrayEnd;
        }

        lx.Backup();
        lx.Push(LexArrayValueEnd);
        return LexValue;
    }

    private static LexState? LexArrayValueEnd(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (IsWhitespace(r))
        {
            return LexSkip(lx, LexArrayValueEnd);
        }

        switch (r)
        {
            case CommentHashStart:
                lx.Push(LexArrayValueEnd);
                return LexCommentStart;
            case CommentSlashStart:
            {
                var rn = lx.Next();
                if (rn == CommentSlashStart)
                {
                    lx.Push(LexArrayValueEnd);
                    return LexCommentStart;
                }

                lx.Backup();
                // fallthrough
                if (r == ArrayValTerm || IsNL(r))
                {
                    return LexSkip(lx, LexArrayValue);
                }

                break;
            }

            case ArrayEndChar:
                return LexArrayEnd;
        }

        if (r == ArrayValTerm || IsNL(r))
        {
            return LexSkip(lx, LexArrayValue);
        }

        return lx.Errorf($"Expected an array value terminator ',' or an array terminator ']', but got '{EscapeSpecial(r)}' instead.");
    }

    private static LexState? LexArrayEnd(NatsConfLexer lx)
    {
        lx.Ignore();
        lx.Emit(TokenType.ArrayEnd);
        return lx.Pop();
    }

    private static LexState? LexMapKeyStart(NatsConfLexer lx)
    {
        var r = lx.Peek();
        if (IsKeySeparator(r))
        {
            return lx.Errorf($"Unexpected key separator '{r}'.");
        }

        if (r == ArrayEndChar)
        {
            return lx.Errorf($"Unexpected array end '{r}' processing map.");
        }

        if (char.IsWhiteSpace(r))
        {
            lx.Next();
            return LexSkip(lx, LexMapKeyStart);
        }

        if (r == MapEndChar)
        {
            lx.Next();
            return LexSkip(lx, LexMapEnd);
        }

        if (r == CommentHashStart)
        {
            lx.Next();
            lx.Push(LexMapKeyStart);
            return LexCommentStart;
        }

        if (r == CommentSlashStart)
        {
            lx.Next();
            var rn = lx.Next();
            if (rn == CommentSlashStart)
            {
                lx.Push(LexMapKeyStart);
                return LexCommentStart;
            }

            lx.Backup();
        }

        if (r == SqStringStart)
        {
            lx.Next();
            return LexSkip(lx, LexMapQuotedKey);
        }

        if (r == DqStringStart)
        {
            lx.Next();
            return LexSkip(lx, LexMapDubQuotedKey);
        }

        if (r == Eof)
        {
            return lx.Errorf("Unexpected EOF processing map.");
        }

        lx.Ignore();
        lx.Next();
        return LexMapKey;
    }

    private static LexState? LexMapQuotedKey(NatsConfLexer lx)
    {
        var r = lx.Peek();
        if (r == Eof)
        {
            return lx.Errorf("Unexpected EOF processing quoted map key.");
        }

        if (r == SqStringEnd)
        {
            lx.Emit(TokenType.Key);
            lx.Next();
            return LexSkip(lx, LexMapKeyEnd);
        }

        lx.Next();
        return LexMapQuotedKey;
    }

    private static LexState? LexMapDubQuotedKey(NatsConfLexer lx)
    {
        var r = lx.Peek();
        if (r == Eof)
        {
            return lx.Errorf("Unexpected EOF processing double quoted map key.");
        }

        if (r == DqStringEnd)
        {
            lx.Emit(TokenType.Key);
            lx.Next();
            return LexSkip(lx, LexMapKeyEnd);
        }

        lx.Next();
        return LexMapDubQuotedKey;
    }

    private static LexState? LexMapKey(NatsConfLexer lx)
    {
        var r = lx.Peek();
        if (r == Eof)
        {
            return lx.Errorf("Unexpected EOF processing map key.");
        }

        if (char.IsWhiteSpace(r) && !IsNL(r))
        {
            return lx.KeyCheckKeyword(LexMapKeyEnd, LexMapValueEnd);
        }

        if (IsNL(r))
        {
            return lx.KeyCheckKeyword(LexMapKeyEnd, LexMapValueEnd);
        }

        if (IsKeySeparator(r))
        {
            lx.Emit(TokenType.Key);
            return LexMapKeyEnd;
        }

        lx.Next();
        return LexMapKey;
    }

    private static LexState? LexMapKeyEnd(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (char.IsWhiteSpace(r))
        {
            return LexSkip(lx, LexMapKeyEnd);
        }

        if (IsKeySeparator(r))
        {
            return LexSkip(lx, LexMapValue);
        }

        // We start the value here.
        lx.Backup();
        return LexMapValue;
    }

    private static LexState? LexMapValue(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (char.IsWhiteSpace(r))
        {
            return LexSkip(lx, LexMapValue);
        }

        if (r == MapValTerm)
        {
            return lx.Errorf($"Unexpected map value terminator '{MapValTerm}'.");
        }

        if (r == MapEndChar)
        {
            return LexSkip(lx, LexMapEnd);
        }

        lx.Backup();
        lx.Push(LexMapValueEnd);
        return LexValue;
    }

    private static LexState? LexMapValueEnd(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (IsWhitespace(r))
        {
            return LexSkip(lx, LexMapValueEnd);
        }

        switch (r)
        {
            case CommentHashStart:
                lx.Push(LexMapValueEnd);
                return LexCommentStart;
            case CommentSlashStart:
            {
                var rn = lx.Next();
                if (rn == CommentSlashStart)
                {
                    lx.Push(LexMapValueEnd);
                    return LexCommentStart;
                }

                lx.Backup();
                // fallthrough
                if (r == OptValTerm || r == MapValTerm || IsNL(r))
                {
                    return LexSkip(lx, LexMapKeyStart);
                }

                break;
            }
        }

        if (r == OptValTerm || r == MapValTerm || IsNL(r))
        {
            return LexSkip(lx, LexMapKeyStart);
        }

        if (r == MapEndChar)
        {
            return LexSkip(lx, LexMapEnd);
        }

        return lx.Errorf($"Expected a map value terminator ',' or a map terminator '}}', but got '{EscapeSpecial(r)}' instead.");
    }

    private static LexState? LexMapEnd(NatsConfLexer lx)
    {
        lx.Ignore();
        lx.Emit(TokenType.MapEnd);
        return lx.Pop();
    }

    private bool IsBool()
    {
        var str = _input[_start.._pos].ToLowerInvariant();
        return str is "true" or "false" or "on" or "off" or "yes" or "no";
    }

    private bool IsVariable()
    {
        if (_start >= _input.Length)
        {
            return false;
        }

        if (_input[_start] == '$')
        {
            _start += 1;
            return true;
        }

        return false;
    }

    private static LexState? LexQuotedString(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (r == SqStringEnd)
        {
            lx.Backup();
            lx.Emit(TokenType.String);
            lx.Next();
            lx.Ignore();
            return lx.Pop();
        }

        if (r == Eof)
        {
            if (lx._pos > lx._start)
            {
                return lx.Errorf("Unexpected EOF.");
            }

            lx.Emit(TokenType.Eof);
            return null;
        }

        return LexQuotedString;
    }

    private static LexState? LexDubQuotedString(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (r == '\\')
        {
            lx.AddCurrentStringPart(1);
            return LexStringEscape;
        }

        if (r == DqStringEnd)
        {
            lx.Backup();
            lx.EmitString();
            lx.Next();
            lx.Ignore();
            return lx.Pop();
        }

        if (r == Eof)
        {
            if (lx._pos > lx._start)
            {
                return lx.Errorf("Unexpected EOF.");
            }

            lx.Emit(TokenType.Eof);
            return null;
        }

        return LexDubQuotedString;
    }

    private static LexState? LexString(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (r == '\\')
        {
            lx.AddCurrentStringPart(1);
            return LexStringEscape;
        }

        // Termination of non-quoted strings.
        if (IsNL(r) || r == Eof || r == OptValTerm ||
            r == ArrayValTerm || r == ArrayEndChar || r == MapEndChar ||
            IsWhitespace(r))
        {
            lx.Backup();
            if (lx.HasEscapedParts())
            {
                lx.EmitString();
            }
            else if (lx.IsBool())
            {
                lx.Emit(TokenType.Bool);
            }
            else if (lx.IsVariable())
            {
                lx.Emit(TokenType.Variable);
            }
            else
            {
                lx.EmitString();
            }

            return lx.Pop();
        }

        if (r == SqStringEnd)
        {
            lx.Backup();
            lx.EmitString();
            lx.Next();
            lx.Ignore();
            return lx.Pop();
        }

        return LexString;
    }

    private static LexState? LexBlock(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (r == BlockEndChar)
        {
            lx.Backup();
            lx.Backup();

            // Looking for a ')' character on a line by itself.
            // If the previous character isn't a newline, keep processing.
            if (lx.Next() != '\n')
            {
                lx.Next();
                return LexBlock;
            }

            lx.Next();

            // Make sure the next character is a newline or EOF.
            var next = lx.Next();
            if (next is '\n' or Eof)
            {
                lx.Backup();
                lx.Backup();
                lx.Emit(TokenType.String);
                lx.Next();
                lx.Ignore();
                return lx.Pop();
            }

            lx.Backup();
            return LexBlock;
        }

        if (r == Eof)
        {
            return lx.Errorf("Unexpected EOF processing block.");
        }

        return LexBlock;
    }

    private static LexState? LexStringEscape(NatsConfLexer lx)
    {
        var r = lx.Next();
        return r switch
        {
            'x' => LexStringBinary(lx),
            't' => lx.AddStringPart("\t"),
            'n' => lx.AddStringPart("\n"),
            'r' => lx.AddStringPart("\r"),
            '"' => lx.AddStringPart("\""),
            '\\' => lx.AddStringPart("\\"),
            _ => lx.Errorf($"Invalid escape character '{EscapeSpecial(r)}'. Only the following escape characters are allowed: \\xXX, \\t, \\n, \\r, \\\", \\\\."),
        };
    }

    private static LexState? LexStringBinary(NatsConfLexer lx)
    {
        var r1 = lx.Next();
        if (IsNL(r1) || r1 == Eof)
        {
            return lx.Errorf("Expected two hexadecimal digits after '\\x', but hit end of line");
        }

        var r2 = lx.Next();
        if (IsNL(r2) || r2 == Eof)
        {
            return lx.Errorf("Expected two hexadecimal digits after '\\x', but hit end of line");
        }

        var hexStr = lx._input[(lx._pos - 2)..lx._pos];
        try
        {
            var bytes = Convert.FromHexString(hexStr);
            return lx.AddStringPart(System.Text.Encoding.Latin1.GetString(bytes));
        }
        catch (FormatException)
        {
            return lx.Errorf($"Expected two hexadecimal digits after '\\x', but got '{hexStr}'");
        }
    }

    private static LexState? LexNumberOrDateOrStringOrIPStart(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (!char.IsDigit(r))
        {
            if (r == '.')
            {
                return lx.Errorf("Floats must start with a digit, not '.'.");
            }

            return lx.Errorf($"Expected a digit but got '{EscapeSpecial(r)}'.");
        }

        return LexNumberOrDateOrStringOrIP;
    }

    private static LexState? LexNumberOrDateOrStringOrIP(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (r == '-')
        {
            if (lx._pos - lx._start != 5)
            {
                return lx.Errorf("All ISO8601 dates must be in full Zulu form.");
            }

            return LexDateAfterYear;
        }

        if (char.IsDigit(r))
        {
            return LexNumberOrDateOrStringOrIP;
        }

        if (r == '.')
        {
            return LexFloatStart;
        }

        if (IsNumberSuffix(r))
        {
            return LexConvenientNumber;
        }

        // Check if this is a terminator or a string character.
        if (!(IsNL(r) || r == Eof || r == MapEndChar || r == OptValTerm || r == MapValTerm || IsWhitespace(r) || r == ArrayValTerm || r == ArrayEndChar))
        {
            // Treat it as a string value.
            lx._stringStateFn = LexString;
            return LexString;
        }

        lx.Backup();
        lx.Emit(TokenType.Integer);
        return lx.Pop();
    }

    private static LexState? LexConvenientNumber(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (r is 'b' or 'B' or 'i' or 'I')
        {
            return LexConvenientNumber;
        }

        lx.Backup();
        if (IsNL(r) || r == Eof || r == MapEndChar || r == OptValTerm || r == MapValTerm ||
            IsWhitespace(r) || char.IsDigit(r) || r == ArrayValTerm || r == ArrayEndChar)
        {
            lx.Emit(TokenType.Integer);
            return lx.Pop();
        }

        // This is not a number, treat as string.
        lx._stringStateFn = LexString;
        return LexString;
    }

    private static LexState? LexDateAfterYear(NatsConfLexer lx)
    {
        // Expected: MM-DDTHH:MM:SSZ
        char[] formats =
        [
            '0', '0', '-', '0', '0',
            'T',
            '0', '0', ':', '0', '0', ':', '0', '0',
            'Z',
        ];

        foreach (var f in formats)
        {
            var r = lx.Next();
            if (f == '0')
            {
                if (!char.IsDigit(r))
                {
                    return lx.Errorf($"Expected digit in ISO8601 datetime, but found '{EscapeSpecial(r)}' instead.");
                }
            }
            else if (f != r)
            {
                return lx.Errorf($"Expected '{f}' in ISO8601 datetime, but found '{EscapeSpecial(r)}' instead.");
            }
        }

        lx.Emit(TokenType.DateTime);
        return lx.Pop();
    }

    private static LexState? LexNegNumberStart(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (!char.IsDigit(r))
        {
            if (r == '.')
            {
                return lx.Errorf("Floats must start with a digit, not '.'.");
            }

            return lx.Errorf($"Expected a digit but got '{EscapeSpecial(r)}'.");
        }

        return LexNegNumber;
    }

    private static LexState? LexNegNumber(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (char.IsDigit(r))
        {
            return LexNegNumber;
        }

        if (r == '.')
        {
            return LexFloatStart;
        }

        if (IsNumberSuffix(r))
        {
            return LexConvenientNumber;
        }

        lx.Backup();
        lx.Emit(TokenType.Integer);
        return lx.Pop();
    }

    private static LexState? LexFloatStart(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (!char.IsDigit(r))
        {
            return lx.Errorf($"Floats must have a digit after the '.', but got '{EscapeSpecial(r)}' instead.");
        }

        return LexFloat;
    }

    private static LexState? LexFloat(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (char.IsDigit(r))
        {
            return LexFloat;
        }

        // Not a digit; if it's another '.', this might be an IP address.
        if (r == '.')
        {
            return LexIPAddr;
        }

        lx.Backup();
        lx.Emit(TokenType.Float);
        return lx.Pop();
    }

    private static LexState? LexIPAddr(NatsConfLexer lx)
    {
        var r = lx.Next();
        if (char.IsDigit(r) || r is '.' or ':' or '-')
        {
            return LexIPAddr;
        }

        lx.Backup();
        lx.Emit(TokenType.String);
        return lx.Pop();
    }

    private static LexState? LexCommentStart(NatsConfLexer lx)
    {
        lx.Ignore();
        lx.Emit(TokenType.Comment);
        return LexComment;
    }

    private static LexState? LexComment(NatsConfLexer lx)
    {
        var r = lx.Peek();
        if (IsNL(r) || r == Eof)
        {
            // Match Go behavior: emit comment body as a text token.
            lx.Emit(TokenType.Text);
            return lx.Pop();
        }

        lx.Next();
        return LexComment;
    }

    private static LexState LexSkip(NatsConfLexer lx, LexState nextState)
    {
        lx.Ignore();
        return nextState;
    }

    private static string EscapeSpecial(char c) => c switch
    {
        '\n' => "\\n",
        '\r' => "\\r",
        '\t' => "\\t",
        Eof => "EOF",
        _ => c.ToString(),
    };
}
