"""Builders for the messages almost every call needs: object identities, typed values, search
conditions and access control lists.

Usage::

    from mfiles_values import is_not_deleted, is_object_type, lookup, property_value, text
"""

from __future__ import annotations

import datetime

import mfiles_client  # noqa: F401  (puts the generated stubs in gen/ on the import path)
from google.protobuf.timestamp_pb2 import Timestamp
from mfiles import enums_pb2 as enums
from mfiles import structs_pb2 as structs

# The ID of the built-in named ACL "Full control for all internal users".
NAMED_ACL_FULL_CONTROL_FOR_ALL_INTERNAL_USERS = -1


def obj_id(object_type: int, item_id: int) -> structs.ObjID:
    """An object: its object type (0 = document) and its ID."""
    return structs.ObjID(type=object_type, item_id=structs.ItemID(internal_id=item_id))


def obj_ver(object_type: int, item_id: int, version: int) -> structs.ObjVer:
    """A specific version of an object. Writes to a checked-in object need this."""
    return structs.ObjVer(
        obj_id=obj_id(object_type, item_id),
        version=structs.ObjVerVersion(type=enums.OBJ_VER_VERSION_TYPE_SPECIFIC,
                                      internal_version=version))


# [START typed-values]
def text(value: str) -> structs.TypedValue:
    """A single-line text value."""
    return structs.TypedValue(type=enums.DATATYPE_TEXT, data=structs.TypedValueUnion(text=value))


def integer(value: int) -> structs.TypedValue:
    """An integer value."""
    return structs.TypedValue(type=enums.DATATYPE_INTEGER,
                              data=structs.TypedValueUnion(integer=value))


def boolean(value: bool) -> structs.TypedValue:
    """A Boolean value."""
    return structs.TypedValue(type=enums.DATATYPE_BOOLEAN,
                              data=structs.TypedValueUnion(boolean=value))


def date(value: datetime.date) -> structs.TypedValue:
    """A date value: midnight UTC of the day."""
    timestamp = Timestamp()
    timestamp.FromDatetime(datetime.datetime(value.year, value.month, value.day))
    return structs.TypedValue(type=enums.DATATYPE_DATE,
                              data=structs.TypedValueUnion(date=timestamp))


def timestamp(value: datetime.datetime) -> structs.TypedValue:
    """A timestamp value (date and time). A naive datetime is taken as UTC."""
    result = Timestamp()
    result.FromDatetime(value)
    return structs.TypedValue(type=enums.DATATYPE_TIMESTAMP,
                              data=structs.TypedValueUnion(timestamp=result))


def lookup(value_list: int, item: int) -> structs.TypedValue:
    """A lookup: item ``item`` of value list (or object type) ``value_list``."""
    return structs.TypedValue(type=enums.DATATYPE_LOOKUP,
                              data=structs.TypedValueUnion(lookup=_lookup_item(value_list, item)))


def multi_select_lookup(value_list: int, *items: int) -> structs.TypedValue:
    """A multi-select lookup."""
    return structs.TypedValue(
        type=enums.DATATYPE_MULTI_SELECT_LOOKUP,
        data=structs.TypedValueUnion(multi_select_lookup=structs.MultiSelectLookup(
            values=[_lookup_item(value_list, item) for item in items])))


def empty(datatype: int) -> structs.TypedValue:
    """An empty value of the given data type; it clears a property."""
    return structs.TypedValue(type=datatype, is_null_value=True)


def _lookup_item(value_list: int, item: int) -> structs.Lookup:
    return structs.Lookup(value_list_item_info=structs.ItemInfo(obj_id=obj_id(value_list, item)))
# [END typed-values]


def property_value(property_def: int, value: structs.TypedValue) -> structs.PropertyValue:
    """A property value: property definition ``property_def``."""
    return structs.PropertyValue(property_def=property_def, value=value)


# [START conditions]
def property_condition(property_def: int, condition_type: int,
                       value: structs.TypedValue) -> structs.SearchCondition:
    """A condition on a property value, e.g. property 100 (Class) equals a lookup."""
    return structs.SearchCondition(
        expression=structs.Expression(
            type=enums.EXPRESSION_TYPE_PROPERTY_VALUE,
            data=structs.ExpressionUnion(property_value=structs.ExpressionUnion.PropertyValueData(
                property_def=property_def))),
        type=condition_type,
        value=value)


def status_condition(status: int, condition_type: int,
                     value: structs.TypedValue) -> structs.SearchCondition:
    """A condition on a status value, e.g. the object type or the deleted flag."""
    return structs.SearchCondition(
        expression=structs.Expression(
            type=enums.EXPRESSION_TYPE_STATUS_VALUE,
            data=structs.ExpressionUnion(status_value=structs.ExpressionUnion.StatusValueData(
                type=status))),
        type=condition_type,
        value=value)


def is_object_type(object_type: int) -> structs.SearchCondition:
    """Objects of one object type. Value list -11 lists the object types."""
    return status_condition(enums.STATUS_TYPE_OBJECT_TYPE, enums.CONDITION_TYPE_EQUAL,
                            lookup(-11, object_type))


def is_not_deleted() -> structs.SearchCondition:
    """Objects that are not deleted."""
    return status_condition(enums.STATUS_TYPE_IS_DELETED, enums.CONDITION_TYPE_EQUAL,
                            boolean(False))
# [END conditions]


def all_of(*conditions: structs.SearchCondition) -> list[structs.SearchConditionArray]:
    """Combines conditions with AND, in the form SearchObjectsRequest.conditions takes."""
    return [structs.SearchConditionArray(value=conditions)]


def sort_by(key: int, descending: bool = False) -> structs.SearchResultsSorting:
    """Sorts search results by one key, such as a property definition ID."""
    return structs.SearchResultsSorting(rules=structs.SortRuleValueArray(values=[
        structs.SortRule(key=key, options=enums.SortRuleFlags(sort_descending=descending))]))


# [START named-acl]
def named_acl(named_acl_id: int) -> structs.AccessControlList:
    """An access control list that takes its permissions from a named ACL.

    Pass one when you create an object: an object created without an ACL gets an empty ACL, and
    not even its creator can see it.
    """
    return structs.AccessControlList(custom_component=structs.AccessControlListComponent(
        has_named_acl=True, named_acl_id=named_acl_id))
# [END named-acl]
