<?php

namespace Appwrite;

class Query implements \JsonSerializable
{
    protected string $method;
    protected ?string $attribute;
    protected ?array $values;

    public function __construct(string $method, ?string $attribute = null, $values = null)
    {
        $this->method = $method;
        $this->attribute = $attribute;
        
        if (is_null($values) || is_array($values)) {
            $this->values = $values;
        } else {
            $this->values = [$values];
        }

    }

    public function __toString(): string
    {
        return json_encode($this);
    }

    public function jsonSerialize(): mixed
    {
        return array_filter([
            'method' => $this->method,
            'attribute' => $this->attribute,
            'values' => $this->values,
        ]);
    }

    /**
     * Equal
     *
     * @param string $attribute
     * @param mixed $value
     * @return string
     */
    public static function equal(string $attribute, mixed $value): string
    {
        return (new Query('equal', $attribute, $value))->__toString();
    }

    /**
     * Not Equal
     *
     * @param string $attribute
     * @param mixed $value
     * @return string
     */
    public static function notEqual(string $attribute, mixed $value): string
    {
        return (new Query('notEqual', $attribute, $value))->__toString();
    }

    /**
     * Less Than
     *
     * @param string $attribute
     * @param mixed $value
     * @return string
     */
    public static function lessThan(string $attribute, mixed $value): string
    {
        return (new Query('lessThan', $attribute, $value))->__toString();
    }

    /**
     * Less Than or Equal
     *
     * @param string $attribute
     * @param mixed $value
     * @return string
     */
    public static function lessThanEqual(string $attribute, mixed $value): string
    {
        return (new Query('lessThanEqual', $attribute, $value))->__toString();
    }

    /**
     * Greater Than
     *
     * @param string $attribute
     * @param mixed $value
     * @return string
     */
    public static function greaterThan(string $attribute, mixed $value): string
    {
        return (new Query('greaterThan', $attribute, $value))->__toString();
    }

    /**
     * Greater Than or Equal
     *
     * @param string $attribute
     * @param mixed $value
     * @return string
     */
    public static function greaterThanEqual(string $attribute, mixed $value): string
    {
        return (new Query('greaterThanEqual', $attribute, $value))->__toString();
    }

    /**
     * Search
     *
     * @param string $attribute
     * @param string $value
     * @return string
     */
    public static function search(string $attribute, string $value): string
    {
        return (new Query('search', $attribute, $value))->__toString();
    }

    /**
     * Is Null
     *
     * @param string $attribute
     * @return string
     */
    public static function isNull(string $attribute): string
    {
        return (new Query('isNull', $attribute, null))->__toString();
    }

    /**
     * Is Not Null
     *
     * @param string $attribute
     * @return string
     */
    public static function isNotNull(string $attribute): string
    {
        return (new Query('isNotNull', $attribute, null))->__toString();
    }

    /**
     * Between
     *
     * @param string $attribute
     * @param string|int|float $start
     * @param string|int|float $end
     * @return string
     */
    public static function between(string $attribute, mixed $start, mixed $end): string
    {
        return (new Query('between', $attribute, [$start, $end]))->__toString();
    }

    /**
     * Starts With
     *
     * @param string $attribute
     * @param string $value
     * @return string
     */
    public static function startsWith(string $attribute, string $value): string
    {
        return (new Query('startsWith', $attribute, $value))->__toString();
    }

    /**
     * Ends With
     *
     * @param string $attribute
     * @param string $value
     * @return string
     */
    public static function endsWith(string $attribute, string $value): string
    {
        return (new Query('endsWith', $attribute, $value))->__toString();
    }

    /**
     * Select
     *
     * @param array<string> $attributes
     * @return string
     */
    public static function select(array $attributes): string
    {
        return (new Query('select', null, $attributes))->__toString();
    }

    /**
     * Cursor After
     *
     * @param string $documentId
     * @return string
     */
    public static function cursorAfter(string $documentId): string
    {
        return (new Query('cursorAfter', null, $documentId))->__toString();
    }

    /**
     * Cursor Before
     *
     * @param string $documentId
     * @return string
     */
    public static function cursorBefore(string $documentId): string
    {
        return (new Query('cursorBefore', null, $documentId))->__toString();
    }

    /**
     * Order Asc
     *
     * @param string $attribute
     * @return string
     */
    public static function orderAsc(string $attribute): string
    {
        return (new Query('orderAsc', $attribute, null))->__toString();
    }

    /**
     * Order Desc
     *
     * @param string $attribute
     * @return string
     */
    public static function orderDesc(string $attribute): string
    {
        return (new Query('orderDesc', $attribute, null))->__toString();
    }

    /**
     * Order Random
     *
     * @return string
     */
    public static function orderRandom(): string
    {
        return (new Query('orderRandom', null, null))->__toString();
    }

    /**
     * Limit
     *
     * @param int $limit
     * @return string
     */
    public static function limit(int $limit): string
    {
        return (new Query('limit', null, $limit))->__toString();
    }

    /**
     * Offset
     *
     * @param int $offset
     * @return string
     */
    public static function offset(int $offset): string
    {
        return (new Query('offset', null, $offset))->__toString();
    }

    /**
     * Contains
     *
     * @param string $attribute
     * @param mixed $value
     * @return string
     */
    public static function contains(string $attribute, $value): string
    {
        return (new Query('contains', $attribute, $value))->__toString();
    }

    /**
     * Not Contains
     *
     * @param string $attribute
     * @param mixed $value
     * @return string
     */
    public static function notContains(string $attribute, $value): string
    {
        return (new Query('notContains', $attribute, $value))->__toString();
    }

    /**
     * Not Search
     *
     * @param string $attribute
     * @param string $value
     * @return string
     */
    public static function notSearch(string $attribute, string $value): string
    {
        return (new Query('notSearch', $attribute, $value))->__toString();
    }

    /**
     * Not Between
     *
     * @param string $attribute
     * @param string|int|float $start
     * @param string|int|float $end
     * @return string
     */
    public static function notBetween(string $attribute, mixed $start, mixed $end): string
    {
        return (new Query('notBetween', $attribute, [$start, $end]))->__toString();
    }

    /**
     * Not Starts With
     *
     * @param string $attribute
     * @param string $value
     * @return string
     */
    public static function notStartsWith(string $attribute, string $value): string
    {
        return (new Query('notStartsWith', $attribute, $value))->__toString();
    }

    /**
     * Not Ends With
     *
     * @param string $attribute
     * @param string $value
     * @return string
     */
    public static function notEndsWith(string $attribute, string $value): string
    {
        return (new Query('notEndsWith', $attribute, $value))->__toString();
    }

    /**
     * Created Before
     *
     * @param string $value
     * @return string
     */
    public static function createdBefore(string $value): string
    {
        return (new Query('createdBefore', null, $value))->__toString();
    }

    /**
     * Created After
     *
     * @param string $value
     * @return string
     */
    public static function createdAfter(string $value): string
    {
        return (new Query('createdAfter', null, $value))->__toString();
    }

    /**
     * Created Between
     *
     * @param string $start
     * @param string $end
     * @return string
     */
    public static function createdBetween(string $start, string $end): string
    {
        return (new Query('createdBetween', null, [$start, $end]))->__toString();
    }

    /**
     * Updated Before
     *
     * @param string $value
     * @return string
     */
    public static function updatedBefore(string $value): string
    {
        return (new Query('updatedBefore', null, $value))->__toString();
    }

    /**
     * Updated After
     *
     * @param string $value
     * @return string
     */
    public static function updatedAfter(string $value): string
    {
        return (new Query('updatedAfter', null, $value))->__toString();
    }

    /**
     * Updated Between
     *
     * @param string $start
     * @param string $end
     * @return string
     */
    public static function updatedBetween(string $start, string $end): string
    {
        return (new Query('updatedBetween', null, [$start, $end]))->__toString();
    }

    /**
     * Or
     *
     * @param array<string> $queries
     * @return string
     */
    public static function or(array $queries): string
    {
        foreach ($queries as &$query) {
            $query = \json_decode($query, true);
        }
        return (new Query('or', null, $queries))->__toString();
    }

    /**
     * And
     *
     * @param array<string> $queries
     * @return string
     */
    public static function and(array $queries): string
    {
        foreach ($queries as &$query) {
            $query = \json_decode($query, true);
        }
        return (new Query('and', null, $queries))->__toString();
    }

    /**
     * Distance Equal
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @param int|float $distance
     * @return string
     */
    public static function distanceEqual(string $attribute, array $values, int|float $distance, bool $meters = true): string
    {
        return (new Query('distanceEqual', $attribute, [[$values, $distance, $meters]]))->__toString();
    }

    /**
     * Distance Not Equal
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @param int|float $distance
     * @return string
     */
    public static function distanceNotEqual(string $attribute, array $values, int|float $distance, bool $meters = true): string
    {
        return (new Query('distanceNotEqual', $attribute, [[$values, $distance, $meters]]))->__toString();
    }

    /**
     * Distance Greater Than
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @param int|float $distance
     * @return string
     */
    public static function distanceGreaterThan(string $attribute, array $values, int|float $distance, bool $meters = true): string
    {
        return (new Query('distanceGreaterThan', $attribute, [[$values, $distance, $meters]]))->__toString();
    }

    /**
     * Distance Less Than
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @param int|float $distance
     * @param bool $meters
     * @return string
     */
    public static function distanceLessThan(string $attribute, array $values, int|float $distance, bool $meters = true): string
    {
        return (new Query('distanceLessThan', $attribute, [[$values, $distance, $meters]]))->__toString();
    }

    /**
     * Intersects
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @return string
     */
    public static function intersects(string $attribute, array $values): string
    {
        return (new Query('intersects', $attribute, [$values]))->__toString();
    }

    /**
     * Not Intersects
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @return string
     */
    public static function notIntersects(string $attribute, array $values): string
    {
        return (new Query('notIntersects', $attribute, [$values]))->__toString();
    }

    /**
     * Crosses
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @return string
     */
    public static function crosses(string $attribute, array $values): string
    {
        return (new Query('crosses', $attribute, [$values]))->__toString();
    }

    /**
     * Not Crosses
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @return string
     */
    public static function notCrosses(string $attribute, array $values): string
    {
        return (new Query('notCrosses', $attribute, [$values]))->__toString();
    }

    /**
     * Overlaps
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @return string
     */
    public static function overlaps(string $attribute, array $values): string
    {
        return (new Query('overlaps', $attribute, [$values]))->__toString();
    }

    /**
     * Not Overlaps
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @return string
     */
    public static function notOverlaps(string $attribute, array $values): string
    {
        return (new Query('notOverlaps', $attribute, [$values]))->__toString();
    }

    /**
     * Touches
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @return string
     */
    public static function touches(string $attribute, array $values): string
    {
        return (new Query('touches', $attribute, [$values]))->__toString();
    }

    /**
     * Not Touches
     *
     * @param string $attribute
     * @param array<mixed> $values
     * @return string
     */
    public static function notTouches(string $attribute, array $values): string
    {
        return (new Query('notTouches', $attribute, [$values]))->__toString();
    }
}
