use Red::AST::Unary;
use Red::AST::Infixes;
use Red::AST::Value;
use Red::AST::StringFuncs;
use Red::AST::DateTimeFuncs;
use Red::AST::JsonItem;
use Red::AST::JsonRemoveItem;
use Red::Type::Json;

=head2 Red::ColumnMethods

#| Red::Column methods
unit role Red::ColumnMethods;

#| Tests if that column value starts with a specific sub-string
#| is usually translated for SQL as `column like 'substr%'`
multi method starts-with(Str() $text is readonly) {
    Red::AST::Like.new(self, ast-value "{ $text }%") but Red::ColumnMethods
}

multi method starts-with(Str() $text is rw) {
    Red::AST::Like.new(self, ast-value("{ $text }%"), :bind-left) but Red::ColumnMethods
}

#| Tests if that column value ends with a specific sub-string
#| is usually translated for SQL as `column like '%substr'`
multi method ends-with(Str() $text is readonly) {
    Red::AST::Like.new(self, ast-value "%{ $text }") but Red::ColumnMethods
}

multi method ends-with(Str() $text is rw) {
    Red::AST::Like.new(self, ast-value("%{ $text }"), :bind-left) but Red::ColumnMethods
}

#| Tests if that column value contains a specific sub-string
#| is usually translated for SQL as `column like %'substr%'`
multi method contains(Str() $text is readonly) {
    Red::AST::Like.new(self, ast-value "%{ $text }%") but Red::ColumnMethods
}

multi method contains(Str() $text is rw) {
    Red::AST::Like.new(self, ast-value("%{ $text }%"), :bind-left) but Red::ColumnMethods
}

#| Return a substring of the column value
method substr($base where { .returns ~~ Str }: Int() $offset = 0, Int() $size?) {
    Red::AST::Substring.new(:$base, :$offset, |(:$size with $size)) but Red::ColumnMethods
}

#| Return the year from the date column
method year($base where { .returns ~~ (Date|DateTime|Instant) }:) {
    Red::AST::DateTimePart.new(:$base, :part(Red::AST::DateTime::Part::year)) but Red::ColumnMethods
}

#| Return the month from the date column
method month($base where { .returns ~~ (Date|DateTime|Instant) }:) {
    Red::AST::DateTimePart.new(:$base, :part(Red::AST::DateTime::Part::month)) but Red::ColumnMethods
}

#| Return the day from the date column
method day($base where { .returns ~~ (Date|DateTime|Instant) }:) {
    Red::AST::DateTimePart.new(:$base, :part(Red::AST::DateTime::Part::day)) but Red::ColumnMethods
}

#| Return the date from a datetime, timestamp etc
method yyyy-mm-dd($base where { .returns ~~ (Date|DateTime|Instant) }:) {
     Red::AST::DateTimeCoerce.new(:$base) but Red::ColumnMethods
}

method now   { self.attr.type.now   }
method today { self.attr.type.today }

#| Return a value from a json hash key
multi method AT-KEY(\SELF where { .returns ~~ Json }: $key where { $_ ~~ Str or ( $_ ~~ Red::AST and .returns ~~ Str )}) is rw {
    my $obj = Red::AST::JsonItem.new(SELF, ast-value $key);
    Proxy.new:
            FETCH => -> $ { $obj but Red::ColumnMethods },
            STORE => -> $, $value {
                my $val = do given $value {
                    when Red::AST { $_ }
                    default {
                        my &deflator = Json.deflator;
                        ast-value :type(Json), .&deflator;
                    }
                }
                @*UPDATE.push: Pair.new: $obj, $val;
                $obj but Red::ColumnMethods
            }
}

#| Delete and return a value from a json hash key
multi method DELETE-KEY(\SELF where { .returns ~~ Json }: $key where { $_ ~~ Str or ( $_ ~~ Red::AST and .returns ~~ Str )}) {
    my $obj = Red::AST::JsonItem.new(SELF, ast-value $key);
    @*UPDATE.push: Pair.new: SELF, Red::AST::JsonRemoveItem.new: SELF, ast-value $key;
    $obj
}

#| Returns a value from a json array index
method AT-POS(\SELF where { .returns ~~ Json }: $key where { $_ ~~ Int or ( $_ ~~ Red::AST and .returns ~~ Int )}) is rw {
    my $obj = Red::AST::JsonItem.new(SELF, ast-value $key);
    Proxy.new:
            FETCH => -> $ { $obj but Red::ColumnMethods },
            STORE => -> $, $value {
                my $val = do given $value {
                    when Red::AST { $_ }
                    default {
                        my &deflator = Json.deflator;
                        ast-value .&deflator, :type(Json)
                    }
                }
                @*UPDATE.push: Pair.new: $obj, $val;
                $obj but Red::ColumnMethods
            }
}