package JQ::Lite::Parser;

use strict;
use warnings;

use JQ::Lite::Util ();
use JSON::PP ();

sub parse_query {
    my ($query) = @_;

    return () unless defined $query;
    return () if $query =~ /^\s*\.\s*$/;

    my @parts = JQ::Lite::Util::_split_top_level_pipes($query);
    @parts = map {
        my $part = $_;
        $part =~ s/^\s+|\s+$//g;
        $part;
    } @parts;

    @parts = map {
        if ($_ eq '.[]') {
            '.[]';
        }
        elsif ($_ =~ /^\.(.+)$/) {
            my $rest = $1;
            if ($rest =~ /,/) {
                $_;    # preserve leading dot when sequence filters are present
            }
            elsif ($rest =~ /^\s*\[/) {
                $_;    # preserve leading dot for array indexing and bracket access
            }
            elsif ($rest =~ /^\s*[+\-*\/%]/
                || $rest =~ /[+\-*\/%]/
                || $rest =~ /(?:==|!=|>=|<=|>|<|\band\b|\bor\b)/i
                || $rest =~ /\b(?:floor|ceil|round|tonumber)\b/)
            {
                $_;
            }
            else {
                my $trimmed = $rest;
                $trimmed =~ s/^\s+|\s+$//g;
                if ($trimmed =~ /^"(?:[^"\\]|\\.)*"$/s) {
                    my $decoded = eval { JSON::PP::decode_json($trimmed) };
                    return $decoded if defined $decoded && !$@;
                }
                $rest;
            }
        }
        else {
            $_;
        }
    } @parts;

    @parts = map { _lower_object_shorthand($_) } @parts;

    my @expanded;
    for my $part (@parts) {
        next unless defined $part;

        my $trimmed = $part;
        $trimmed =~ s/^\s+|\s+$//g;

        if ($trimmed =~ /^\(.*\)$/s) {
            my $inner = JQ::Lite::Util::_strip_wrapping_parens($trimmed);
            if (defined $inner && length $inner && $inner ne $trimmed) {
                my @inner_parts = parse_query($inner);
                if (@inner_parts) {
                    push @expanded, @inner_parts;
                    next;
                }
            }
        }

        push @expanded, $trimmed;
    }

    return @expanded;
}

sub _lower_object_shorthand {
    my ($text) = @_;

    return $text unless defined $text;
    return $text if index($text, '{') == -1;

    my $result = '';
    my $len    = length $text;
    my $i      = 0;
    my $string;
    my $escape = 0;

    while ($i < $len) {
        my $char = substr($text, $i, 1);

        if (defined $string) {
            $result .= $char;
            if ($escape) {
                $escape = 0;
            }
            elsif ($char eq '\\') {
                $escape = 1;
            }
            elsif ($char eq $string) {
                undef $string;
            }
            $i++;
            next;
        }

        if ($char eq "'" || $char eq '"') {
            $string = $char;
            $result .= $char;
            $i++;
            next;
        }

        if ($char eq '{') {
            my ($body, $consumed) = _extract_object_body($text, $i);
            if (defined $body) {
                my $lowered = _lower_object_constructor($body);
                $result .= '{' . $lowered . '}';
                $i += $consumed;
                next;
            }
        }

        $result .= $char;
        $i++;
    }

    return $result;
}

sub _extract_object_body {
    my ($text, $start) = @_;

    my $len     = length $text;
    my $depth   = 0;
    my $string;
    my $escape  = 0;

    for (my $i = $start; $i < $len; $i++) {
        my $char = substr($text, $i, 1);

        if (defined $string) {
            if ($escape) {
                $escape = 0;
                next;
            }

            if ($char eq '\\') {
                $escape = 1;
                next;
            }

            if ($char eq $string) {
                undef $string;
            }

            next;
        }

        if ($char eq "'" || $char eq '"') {
            $string = $char;
            next;
        }

        if ($char eq '{') {
            $depth++;
            next;
        }

        if ($char eq '}') {
            $depth--;
            if ($depth == 0) {
                my $body = substr($text, $start + 1, $i - $start - 1);
                return ($body, $i - $start + 1);
            }
            next;
        }
    }

    return (undef, 1);
}

sub _lower_object_constructor {
    my ($inner) = @_;

    return $inner unless defined $inner;

    my @parts = JQ::Lite::Util::_split_top_level_commas($inner);
    return $inner unless @parts;

    my @transformed;
    for my $part (@parts) {
        next unless defined $part;

        my $trimmed = $part;
        $trimmed =~ s/^\s+|\s+$//g;
        next if $trimmed eq '';

        my ($lhs, $rhs) = JQ::Lite::Util::_split_top_level_colon($part);

        if (defined $lhs && defined $rhs) {
            my $key = $lhs;
            $key =~ s/^\s+|\s+$//g;

            my $value = _lower_object_shorthand($rhs);
            $value =~ s/^\s+|\s+$//g;

            push @transformed, "$key: $value";
            next;
        }

        if (!defined $lhs && $trimmed =~ /^[A-Za-z_][A-Za-z0-9_]*$/) {
            push @transformed, "$trimmed: .$trimmed";
            next;
        }

        if (defined $lhs && !defined $rhs) {
            my $key = $lhs;
            $key =~ s/^\s+|\s+$//g;
            next if $key eq '';
            push @transformed, "$key: .$key";
            next;
        }

        my $lowered = _lower_object_shorthand($trimmed);
        $lowered =~ s/^\s+|\s+$//g;
        push @transformed, $lowered if length $lowered;
    }

    return join(', ', @transformed);
}

1;
