import type { Expressions, ParameterExpression, StrictExpressions, TypedExpression } from './expressions/index.js';
import { UnaryOperator } from './expressions/unary-operator.js';
import { BinaryOperator } from './expressions/binary-operator.js';
import { TernaryOperator } from './expressions/ternary-operator.js';
import { ExpressionType } from './expressions/expression-type.js';
import { Expression } from './expressions/expression.js';
import { NewExpression } from './expressions/new-expression.js';
import { ConstantExpression } from './expressions/constant-expression.js';
import { MemberExpression } from './expressions/member-expression.js';
import { UnaryExpression } from './expressions/unary-expression.js';
import { BinaryExpression } from './expressions/binary-expression.js';
import { TernaryExpression } from './expressions/ternary-expression.js';
import { CallExpression } from './expressions/call-expression.js';

import identity from '../formatters/identity.js';
import negate from '../formatters/negate.js';
import booleanize from '../formatters/booleanize.js';
import type { ExpressionVisitor } from './expressions/visitors/expression-visitor.js';
import type { Formatter, FormatterFactory } from '../formatters/common.js';
import { formatters } from '../formatters/index.js';
import { escapeRegExp } from '../reflect.js';
import { AssignmentOperator } from './expressions/assignment-operator.js';
import { AssignmentExpression } from './expressions/assignment-expression.js';
import ErrorWithStatus, { HttpStatusCode } from '../errorWithStatus.js';


const jsonKeyRegex = /\s*(?:(?:"([^"]+)")|(?:'([^']+)')|(?:([a-zA-Z0-9_$]+)) *):\s*/;


export interface Cursor
{
    offset: number;
    freeze(): Cursor;
}

export class StringCursor implements Cursor
{
    /**
     * Gets the length of the string being parsed.
     */
    get length(): number { return this.string.length };

    /**
     * Gets the current character at the cursor position.
     */
    get char(): string { return this.string[this._offset]; };

    /**
     * Gets whether the cursor has reached the end of the file.
     */
    get eof(): boolean { return this._offset >= this.string.length; };

    /**
     * Creates a new StringCursor instance.
     * @param string - The string to parse.
     */
    constructor(public readonly string: string) { }

    /**
     * Gets the current line number based on the cursor position.
     * @returns The line number.
     */
    public getLineNo(): number
    {
        let n = 0;
        for (let i = 0; i <= this.offset; i++)
        {
            if (this.string[i] === '\n')
                n++;
        }
        return n;
    }

    public getLine(): string
    {
        const nextLine = this.string.indexOf('\n', this.offset);
        if (nextLine == -1)
            return this.string.substring(this._offset - this.getColumn());
        else
            return this.string.substring(this._offset - this.getColumn(), nextLine);
    }

    /**
     * Gets the current column position within the current line.
     * @returns The column number (0-based).
     */
    public getColumn(): number
    {
        for (let i = this.offset; i >= 0; i--)
        {
            if (this.string[i] == '\n')
                return this.offset - i;
        }
    }

    /**
     * Gets a human-readable representation of the current cursor position.
     * @returns A string in the format "line:column".
     */
    public getReadableOffset(): string
    {
        return this.getLineNo() + ':' + this.getColumn();
    }

    private _offset: number = 0;

    /**
     * Gets the current cursor offset in the string.
     */
    get offset(): number { return this._offset; };

    /**
     * Sets the cursor offset in the string.
     * @throws Error if the offset is beyond the string length.
     */
    set offset(value: number)
    {
        this._offset = value;
        if (this._offset > this.string.length)
            throw new Error('Cursor cannot go beyond the string limit');
    };

    /**
     * Creates a copy of the current cursor state.
     * @returns A new StringCursor with the same position and content.
     */
    public freeze()
    {
        const c = new StringCursor(this.string);
        c._offset = this._offset;
        return c;
    }

    /**
     * Executes a regular expression at the current cursor position.
     * @param regex - The regular expression to execute.
     * @returns The regex match result or null if no match at current position.
     */
    public exec(regex: RegExp)
    {
        if (!regex.global)
            regex = new RegExp(regex, 'g' + regex.flags);
        regex.lastIndex = this._offset;
        const result = regex.exec(this.string);
        if (result)
        {
            if (result.index != this._offset)
                return null;
            this.offset += result[0].length;
        }
        return result;
    }

    /**
     * Reads a specific string at the current cursor position.
     * @param s - The string to read.
     * @returns true if the string was found and consumed, false otherwise.
     */
    public read(s: string)
    {
        if (this.string.length < this._offset + s.length)
            return false;

        for (let i = 0; i < s.length; i++)
        {
            if (s.charCodeAt(i) !== this.string.charCodeAt(this._offset + i))
                return false;
        }

        this._offset += s.length;

        return true;
    }

    /**
     * Reads a specific string, skipping whitespace before and after.
     * @param s - The string to read.
     * @returns true if the string was found and consumed, false otherwise.
     */
    public trimRead(s: string)
    {
        this.skipWhitespace();

        if (this.read(s))
        {
            this.skipWhitespace();
            return true;
        }

        return false;
    }

    /**
     * Reads a specific string, skipping whitespace before the string.
     * @param s - The string to read.
     * @returns true if the string was found and consumed, false otherwise.
     */
    public trimStartRead(s: string)
    {
        this.skipWhitespace();

        return this.read(s);
    }

    /**
     * Reads a specific string, skipping whitespace after the string.
     * @param s - The string to read.
     * @returns true if the string was found and consumed, false otherwise.
     */
    public trimEndRead(s: string)
    {
        if (this.read(s))
        {
            this.skipWhitespace();
            return true;
        }

        return false;
    }

    /**
     * Skips any whitespace characters at the current cursor position.
     * @returns The skipped whitespace string or undefined if none found.
     */
    public skipWhitespace()
    {
        return this.exec(/\s+/)?.[0];
    }
}

export type ParsedOneOf = ParsedObject | ParsedArray | ParsedString | ParsedBoolean | ParsedNumber;

/**
 * @deprecated Please use ObservableObject.setValue instead which more versatile
 * Gets the setter function for a given expression and root object.
 * @param {string} expression - The expression to evaluate.
 * @param {T} root - The root object.
 * @returns {{ expression: string, target: T, set: (value: unknown) => void } | null} The setter function or null if not found.
 */
export function getSetter<T = unknown>(expression: string, root: T): { expression: string, target: T, set: (value: unknown) => void } | null
{
    let target = root;
    const parts = expression.split('.');

    while (parts.length > 1 && typeof (target) != 'undefined')
    {
        target = this.eval(parts[0], target);
        parts.shift();
    }
    if (typeof (target) == 'undefined')
        return null;

    return { expression: parts[0], target: target, set: function (value) { target[parts[0]] = value } };
}

/**
 * Parses a binary operator from a string.
 * @param {string} op - The operator string.
 * @returns {BinaryOperator} The parsed binary operator.
 */
function parseBinaryOperator(op: string): BinaryOperator
{
    if (op in BinaryOperator)
        return op as BinaryOperator;
    return BinaryOperator.Unknown;
}

function parseAssignmentOperator(op: string): AssignmentOperator
{
    if (op in AssignmentOperator)
        return op as AssignmentOperator;
    return AssignmentOperator.Unknown;
}

/**
 * Parses a ternary operator from a string.
 * @param {string} op - The operator string.
 * @returns {TernaryOperator} The parsed ternary operator.
 */
function parseTernaryOperator(op: string): TernaryOperator
{
    switch (op)
    {
        case '?': return TernaryOperator.Question;
        default: return TernaryOperator.Unknown;
    }
}

/**
 * Represents a format expression.
 * @extends Expression
 */
export class FormatExpression<TOutput> extends Expression 
{
    constructor(public readonly lhs: Expressions | (TypedExpression<unknown>), public readonly formatter: new (...args: unknown[]) => Formatter<TOutput>, public readonly settings: Expressions)
    {
        super();
    }

    get type(): ExpressionType.Format
    {
        return ExpressionType.Format;
    }

    accept(visitor: ExpressionVisitor): TypedExpression<TOutput>
    {
        return visitor.visitFormat(this);
    }

}

/**
 * Represents a parsed object.
 * @extends NewExpression
 */
export class ParsedObject<T extends object = object> extends NewExpression<T> 
{

    constructor(...init: MemberExpression<T, keyof T, T[keyof T]>[])
    {
        super(...init);
    }
}

/**
 * Represents a parsed array.
 * @extends NewExpression
 */
export class ParsedArray extends NewExpression<unknown[]> 
{

    constructor(...init: MemberExpression<unknown[], number, unknown>[])
    {
        super(...init);
        this.newType = '['
    }
}

/**
 * Represents a parsed string.
 * @extends ConstantExpression
 */
export class ParsedString extends ConstantExpression<string> 
{
    constructor(value: string)
    {
        super(value);
    }

    public toString(): string
    {
        return this.value;
    }
}

/**
 * Represents a parsed number.
 * @extends ConstantExpression
 */
export class ParsedNumber extends ConstantExpression<number> 
{
    constructor(value: string)
    {
        super(Number(value));
    }
}

/**
 * Represents a parsed boolean.
 * @extends ConstantExpression
 */
export class ParsedBoolean extends ConstantExpression<boolean> 
{
    constructor(value: string | boolean)
    {
        super(Boolean(value));
    }
}

/**
 * Represents a parser.
 */
export class Parser
{
    public static readonly parameterLess: Parser = new Parser();

    private parameters: Record<string, ParameterExpression<unknown>>;

    constructor(...parameters: ParameterExpression<unknown>[])
    {
        if (parameters)
        {
            this.parameters = {}
            parameters.forEach(param =>
            {
                this.parameters[param.name] = param
            });
        }
    }

    /**
     * Parses an expression.
     * @param {string} expression - The expression to parse.
     * @param {boolean} [parseFormatter=true] - Whether to parse formatters.
     * @param {() => void} [reset] - The reset function.
     * @returns {Expressions} The parsed expression.
     */
    public parse(expression: string, parseFormatter?: boolean, reset?: () => void): Expressions
    {
        expression = expression.trim();
        const cursor = new StringCursor(expression);
        const result = this.parseAny(cursor, (typeof parseFormatter !== 'boolean') || parseFormatter, reset);
        if (cursor.offset < cursor.length)
            throw new Error(`invalid character ${cursor.char} at ${cursor.offset}`);
        return result;
    }

    /**
     * Parses any expression.
     * @param {string} expression - The expression to parse.
     * @param {boolean} parseFormatter - Whether to parse formatters.
     * @param {() => void} [reset] - The reset function.
     * @param {StringCursor} cursor - The cursor tracking the current position.
     * @returns {Expressions} The parsed expression.
     */
    public parseAny(expression: StringCursor, parseFormatter: boolean, reset?: () => void): Expressions
    {
        switch (expression.char)
        {
            case '{':
                return this.parseObject(expression, parseFormatter);
            case '[':
                return this.parseArray(expression, parseFormatter, reset);
            case '"':
            case "'":
                return this.parseString(expression, expression.char, parseFormatter);
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '.':
                return this.parseNumber(expression, parseFormatter);
            default:
                return this.parseEval(expression, parseFormatter, reset);
        }
    }

    /**
     * Parses a number expression.
     * @param {string} expression - The expression to parse.
     * @param {boolean} parseFormatter - Whether to parse formatters.
     * @param {StringCursor} cursor - The cursor tracking the current position.
     * @returns {Expressions} The parsed number expression.
     */
    public parseNumber(expression: StringCursor, parseFormatter: boolean)
    {
        const match = expression.exec(/\d+(?:\.\d+)?/);
        if (!match)
            throw new Error('Invalid number at position ' + expression.offset);
        const result = new ParsedNumber(match[0]);

        return this.tryParseOperator(expression, result, parseFormatter);
    }

    /**
     * Parses a boolean expression.
     * @param {string} expression - The expression to parse.
     * @returns {ParsedBoolean} The parsed boolean expression.
     */
    public parseBoolean(expression: StringCursor): ParsedBoolean | FormatExpression<boolean>
    {
        let formatter = identity as FormatterFactory<boolean> & { instance: Formatter<boolean> };
        if (expression.char == '!')
        {
            formatter = negate;
            expression.offset++;
        }
        if (expression.char == '!')
        {
            formatter = booleanize;
            expression.offset++;
        }

        const boolMatch = expression.exec(/(?:true|false|undefined|null)/);
        if (boolMatch)
        {
            const result = new ParsedBoolean(boolMatch[0]);
            if (formatter !== identity)
            {
                return this.tryParseOperator(expression, new ParsedBoolean(formatter.instance.format(result.value)), true);
            }
            return this.tryParseOperator(expression, result, true);
        }
        else if (formatter !== identity)
        {
            return new FormatExpression(this.parseAny(expression, true), formatter, null);
        }
        return null;
    }

    /**
     * Parses an evaluation expression.
     * @param {string} expression - The expression to parse.
     * @param {boolean} parseFormatter - Whether to parse formatters.
     * @param {() => void} [reset] - The reset function.
     * @param {StringCursor} cursor - The cursor tracking the current position.
     * @returns {Expressions} The parsed evaluation expression.
     */
    public parseEval(expression: StringCursor, parseFormatter: boolean, reset?: () => void)
    {
        const b = this.parseBoolean(expression);
        if (b)
            return b;

        return this.parseFunction(expression, parseFormatter, reset);
    }

    /**
     * Parses a function expression.
     * @param {string} expression - The expression to parse.
     * @param {boolean} parseFormatter - Whether to parse formatters.
     * @param {() => void} [reset] - The reset function.
     * @returns {Expressions} The parsed function expression.
     */
    public parseFunction(expression: StringCursor, parseFormatter: boolean, reset?: () => void): BinaryExpression
    {
        let operator: UnaryOperator;
        while (expression.char === '!')
        {
            if (expression.char === '!')
            {
                operator = UnaryOperator.Not;

                expression.offset++;
            }
            if (expression[0] === '!')
            {
                operator = UnaryOperator.NotNot;
                expression.offset++;
            }
        }

        let item = expression.exec(/[\w$]*\??/)[0];

        const optional = item.endsWith('?')
        if (optional)
            item = item.substring(0, item.length - 1);

        if (item)
        {
            let result: Expressions;
            if (this.parameters)
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                result = new MemberExpression(this.parameters[''] as TypedExpression<any>, new ParsedString(item), optional) as TypedExpression<any>
            else
                result = new MemberExpression(null, new ParsedString(item), optional)

            if (typeof operator != 'undefined')
            {
                result = new UnaryExpression(result, operator);
            }
            return this.tryParseOperator(expression, result, parseFormatter, reset);
        }

        return this.tryParseOperator(expression, null, parseFormatter, reset);
    }

    /**
     * Parses a formatter expression.
     * @param {string} expression - The expression to parse.
     * @param {Expressions} lhs - The left-hand side expression.
     * @param {() => void} reset - The reset function.
     * @returns {Expressions} The parsed formatter expression.
     */
    public parseFormatter(expression: StringCursor, lhs: Expressions, reset: () => void): Expressions
    {
        const item = expression.exec(/\s*([\w.$]+)\s*/);
        reset?.();
        let settings: Expressions;
        if (expression.char === ':')
        {
            expression.offset++;
            settings = this.parseAny(expression, false);
        }

        const result = new FormatExpression(lhs, formatters.resolve<FormatterFactory<unknown>>(item[1]), settings);
        return this.tryParseOperator(expression, result, true, reset);
    }

    /**
     * Tries to parse an operator expression.
     * @param {string} expression - The expression to parse.
     * @param {Expressions} lhs - The left-hand side expression.
     * @param {boolean} parseFormatter - Whether to parse formatters.
     * @param {() => void} [reset] - The reset function.
     * @param {StringCursor} cursor - The cursor tracking the current position.
     * @returns {Expressions} The parsed operator expression.
     */
    public tryParseOperator(expression: StringCursor, lhs: Expressions, parseFormatter: boolean, reset?: () => void)
    {
        const operator = expression.exec(/\s*([<>=!+\-/*&|?.#[(]+)\s*/);
        if (operator)
        {
            let rhs: Expressions;
            const oldParameters = this.parameters;
            let binaryOperator = parseBinaryOperator(operator[1]);
            let assignmentOperator = parseAssignmentOperator(operator[1]);
            const ternaryOperator = parseTernaryOperator(operator[1]);
            let group = 0;

            switch (operator[1])
            {
                case '#':
                    if (!parseFormatter)
                    {
                        expression.offset -= operator[0].length;
                        return lhs;
                    }
                    return this.parseFormatter(expression, lhs, reset);
                case '(':
                case '.(':
                    reset?.();
                    if (lhs)
                    {
                        expression.offset--;
                        return this.parseFunctionCall(expression, lhs, parseFormatter);
                    }
                    else
                    {
                        lhs = this.parseAny(expression, parseFormatter, reset);
                        expression.offset++;
                        return this.tryParseOperator(expression, lhs, parseFormatter, reset);
                    }
                case '[': {
                    rhs = this.parseAny(expression, parseFormatter, reset);
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    const member = new MemberExpression(lhs as TypedExpression<any>, rhs as TypedExpression<any>, false);
                    expression.offset++; // Skip closing bracket
                    return this.tryParseOperator(expression, member, parseFormatter, reset);
                }
                case '?.':
                case '.': {
                    this.parameters = { ...this.parameters, '': lhs as ParameterExpression<unknown> };
                    const selfReset = (() => { this.parameters = oldParameters });
                    rhs = this.parseAny(expression, parseFormatter, reset || selfReset);
                    selfReset();
                    return rhs;
                }
                case '?': {
                    const second = this.parseAny(expression, parseFormatter, reset);

                    const operator2 = expression.exec(/\s*(:)\s*/);
                    if (!operator2)
                        throw new Error('Invalid ternary operator');

                    const third = this.parseAny(expression, parseFormatter, reset);
                    const ternary = new TernaryExpression(lhs, ternaryOperator, second, third);
                    return ternary;
                }
                default: {
                    reset?.();
                    if (ternaryOperator == TernaryOperator.Unknown && assignmentOperator == AssignmentOperator.Unknown && binaryOperator == BinaryOperator.Unknown)
                    {
                        while (operator[1][operator[1].length - 1] == '(')
                        {
                            group++;
                            operator[1] = operator[1].substring(0, operator[1].length - 1);
                        }
                        binaryOperator = parseBinaryOperator(operator[1]);
                        assignmentOperator = parseAssignmentOperator(operator[1]);
                        if (assignmentOperator == AssignmentOperator.Unknown && binaryOperator == BinaryOperator.Unknown)
                        {
                            throw new ErrorWithStatus(HttpStatusCode.BadRequest, `Invalid expression at offset ${expression.offset} (${expression.char})`)
                        }
                    }
                    rhs = this.parseAny(expression, parseFormatter);
                    expression.offset += group;
                    if (binaryOperator !== BinaryOperator.Unknown)
                    {
                        const binary = new BinaryExpression(lhs, binaryOperator, rhs);
                        if (group == 0)
                            return BinaryExpression.applyPrecedence(binary);

                        return this.tryParseOperator(expression, binary, parseFormatter, reset);
                    }
                    if (assignmentOperator !== AssignmentOperator.Unknown)
                        return new AssignmentExpression(lhs, assignmentOperator, rhs);
                }
            }
        }
        return lhs;
    }

    /**
     * Parses a function call expression.
     * @param {string} expression - The expression to parse.
     * @param {Expressions} lhs - The left-hand side expression.
     * @param {boolean} parseFormatter - Whether to parse formatters.
     * @param {StringCursor} cursor - The cursor tracking the current position.
     * @returns {Expressions} The parsed function call expression.
     */
    parseFunctionCall(expression: StringCursor, lhs: Expressions, parseFormatter: boolean)
    {
        const results: (StrictExpressions)[] = [];

        const optional = expression.offset > 1 && expression.string[expression.offset - 2] == '?';

        this.parseCSV(expression, () =>
        {
            const item = this.parseAny(expression, parseFormatter);
            results.push(item as StrictExpressions);
            return item;
        }, ')');

        if (lhs?.type == ExpressionType.MemberExpression)
            return this.tryParseOperator(
                expression,
                new CallExpression(
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    lhs.source as Expressions & TypedExpression<any>,
                    lhs.member,
                    results,
                    optional
                ),
                parseFormatter
            );

        return this.tryParseOperator(
            expression,
            new CallExpression(
                lhs as Expressions & TypedExpression<unknown>,
                null,
                results,
                optional
            ),
            parseFormatter,
        );
    }

    /**
     * Parses an array expression.
     * @param {string} expression - The expression to parse.
     * @param {boolean} parseFormatter - Whether to parse formatters.
     * @param {StringCursor} cursor - The cursor tracking the current position.
     * @param {() => void} [reset] - The reset function.
     * @returns {Expressions} The parsed array expression.
     */
    public parseArray(expression: StringCursor, parseFormatter: boolean, reset?: () => void)
    {
        const results: Expressions[] = [];
        this.parseCSV(expression, () =>
        {
            const item = this.parseAny(expression, true);
            results.push(item);
            return item;
        }, ']');

        //eslint-disable-next-line @typescript-eslint/no-explicit-any
        return this.tryParseOperator(expression, new ParsedArray(...results.map((v, i) => new MemberExpression<any, number, any>(v as TypedExpression<any>, new ParsedNumber(i.toString()), false))), parseFormatter, reset);
    }

    /**
     * Parses a string expression.
     * @param {string} expression - The expression to parse.
     * @param {string} start - The starting character of the string.
     * @param {boolean} parseFormatter - Whether to parse formatters.
     * @param {StringCursor} cursor - The cursor tracking the current position.
     * @returns {Expressions} The parsed string expression.
     */
    public parseString(expression: StringCursor, start: '"' | "'", parseFormatter: boolean)
    {
        start = escapeRegExp(start) as '"' | "'";
        const evaluatedRegex = expression.exec(new RegExp(start + "((?:[^\\" + start + "]|\\.)*)" + start));
        if (!evaluatedRegex)
            throw new Error('Invalid string at position ' + expression.offset);

        const result = evaluatedRegex[1];
        const parsedString = new ParsedString(result);

        return this.tryParseOperator(expression, parsedString, parseFormatter);
    }

    /**
     * Operates on two values using a binary operator.
     * @param {BinaryOperator} operator - The binary operator.
     * @param {unknown} [left] - The left-hand side value.
     * @param {unknown} [right] - The right-hand side value.
     * @returns {unknown} The result of the operation.
     */
    public static operate(operator: BinaryOperator, left?: unknown, right?: unknown)
    {
        switch (operator)
        {
            case BinaryOperator.Equal:
                return left == right;
            case BinaryOperator.StrictEqual:
                return left === right;
            case BinaryOperator.LessThan:
                return left < right;
            case BinaryOperator.LessThanOrEqual:
                return left <= right;
            case BinaryOperator.GreaterThan:
                return left > right;
            case BinaryOperator.GreaterThanOrEqual:
                return left >= right;
            case BinaryOperator.NotEqual:
                return left != right;
            case BinaryOperator.StrictNotEqual:
                return left !== right;
            case BinaryOperator.Plus:
                return (left as number) + (right as number);
            case BinaryOperator.Minus:
                return (left as number) - (right as number);
            case BinaryOperator.Div:
                return (left as number) / (right as number);
            case BinaryOperator.Times:
                return (left as number) * (right as number);
            case BinaryOperator.Or:
                return left || right;
            case BinaryOperator.And:
                return left && right;
            case BinaryOperator.Dot:
                if (right instanceof Function)
                    return right(left);
                return left[right as keyof typeof left];
            default:
                throw new Error('invalid operator' + operator);
        }
    }

    /**
     * Parses a CSV expression.
     * @param {string} expression - The expression to parse.
     * @param {(expression: string) => Expressions} parseItem - The function to parse each item.
     * @param {string} end - The ending character of the CSV.
     * @param {StringCursor} cursor - The cursor tracking the current position.
     * @returns {number} The length of the parsed CSV expression.
     */
    public parseCSV(expression: StringCursor, parseItem: () => Expressions, end: string): number
    {
        const startOffset = expression.offset;
        expression.offset++; // Skip opening character

        for (; expression.offset < expression.length && expression.char !== end; expression.offset++)
        {
            // Skip whitespace
            while ((expression.char === ' ' || expression.char == '\n' || expression.char == '\t') && expression.offset < expression.length)
                expression.offset++;

            if (expression.offset >= expression.length)
                break;

            if (expression.char === end)
            {
                expression.offset++;
                break;
            }

            parseItem(); // Remove unused assignment

            // Skip whitespace
            while (expression.char === ' ' || expression.char == '\n' || expression.char == '\t')
                expression.offset++;

            // Check for comma
            if (expression.char === ',')
                continue;

            // If no comma, must be end character
            if (expression.char === end)
                break;

            throw new Error(`Expected comma or ${end} at position ${expression.offset}, but found ${expression.char}`);
        }
        expression.offset++;

        return expression.offset - startOffset;
    }

    /**
     * Parses an object expression.
     * @param {string} expression - The expression to parse.
     * @param {boolean} parseFormatter - Whether to parse formatters.
     * @param {StringCursor} cursor - The cursor tracking the current position.
     * @returns {Expressions} The parsed object expression.
     */
    public parseObject(expression: StringCursor, parseFormatter: boolean)
    {
        const parsedObject: { key: string, value: Expressions }[] = [];
        this.parseCSV(expression, () =>
        {
            const keyMatch = expression.exec(jsonKeyRegex);

            const key = keyMatch[1] || keyMatch[2] || keyMatch[3];

            const item = this.parseAny(expression, true);
            parsedObject.push({ key, value: item });
            // console.log(expression);
            //console.log(length);
            return item;
        }, '}');

        //eslint-disable-next-line @typescript-eslint/no-explicit-any
        return this.tryParseOperator(expression, new ParsedObject(...parsedObject.map(v => new MemberExpression<any, string, any>(v.value as TypedExpression<any>, new ParsedString(v.key), false))), parseFormatter)
    }
}
