# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Django ORM utilities."""

from django.db.models import BooleanField, CharField, Func, JSONField, Value
from django.db.models.expressions import Combinable, CombinedExpression


class JSONBConcat(Func):
    """Concatenate two JSONB objects."""

    arg_joiner = ' || '
    template = '%(expressions)s'
    output_field = JSONField()


class JSONBEach(Func):
    """
    Expand a JSON object into a set of key/value pairs.

    Since Django doesn't support set-returning functions very well, this is
    just a minimal wrapper and in practice needs to be used with ``RawSQL``.
    """

    function = "jsonb_each"
    arity = 1


class JSONBEachText(Func):
    """
    Expand a JSON object into a set of key/value pairs, parsing values as text.

    Since Django doesn't support set-returning functions very well, this is
    just a minimal wrapper and in practice needs to be used with ``RawSQL``.
    """

    function = "jsonb_each_text"
    arity = 1


class JSONBEmptyArray(Func):
    """Represent an empty JSONB array."""

    template = "'[]'::jsonb"
    output_field = JSONField(default=list)


class JSONBToRecordSet(Func):
    """
    Expand a JSON array into a set of its rows.

    Since Django doesn't support set-returning functions very well, this is
    just a minimal wrapper and in practice needs to be used with ``RawSQL``.
    The join must explicitly define the row structure using the table name
    and ``use_alias=False``.
    """

    function = "jsonb_to_recordset"
    arity = 1


class JSONBTypeOf(Func):
    """Return the type of a JSON value."""

    function = "jsonb_typeof"
    arity = 1
    output_field = CharField()


class JSONStringValue(CombinedExpression):
    """Parse a JSON value already known to be a string."""

    output_field = CharField()

    def __init__(self, item: Combinable) -> None:
        """Construct the expression."""
        super().__init__(item, "->>", Value(0))


class Encode(Func):
    """
    Encode binary data into a text representation.

    Note that this follows PostgreSQL's naming for encode/decode, which is
    the other way round from Python's (where "encode" goes from text to
    binary).
    """

    function = "encode"
    arity = 2
    output_field = CharField()


class CompareBoolean(CombinedExpression):
    """Compare two expressions, returning a boolean."""

    output_field = BooleanField()
