import dataclasses
from typing import List, Any, Dict, Union

from spinta.components import Page, Property
from spinta.core.ufuncs import Env, Expr
from spinta.types.datatype import DataType, Object
from spinta.utils.schema import NA


class QueryPage:
    page_: Page
    size: int
    select: list
    sort: list

    def __init__(self):
        self.size = 0
        self.select = []
        self.sort = []
        self.page_ = Page()


class QueryParams:
    prioritize_uri: bool = False
    lang_priority: List[str] = None
    expand: Expr = None
    default_expand: bool = False
    lang: List = None
    push: bool = False


class Selected:
    # Item index in select list.
    item: int = None
    # Model property if a property is selected.
    prop: Property = None
    # A value or an Expr for further processing on selected value.
    prep: Any = NA

    def __init__(
        self,
        item: int = None,
        prop: Property = None,
        # `prop` can be Expr or any other value.
        prep: Any = NA,
    ):
        self.item = item
        self.prop = prop
        self.prep = prep

    def __repr__(self):
        return self.debug()

    def __eq__(self, other):
        if isinstance(other, Selected):
            return self.item == other.item and self.prop == other.prop and self.prep == other.prep
        return False

    def debug(self, indent: str = ''):
        prop = self.prop.place if self.prop else 'None'
        if isinstance(self.prep, Selected):
            return (
                f'{indent}Selected('
                f'item={self.item}, '
                f'prop={prop}, '
                f'prep=...)\n'
                   ) + self.prep.debug(indent + '  ')
        elif isinstance(self.prep, (tuple, list)):
            return (
                f'{indent}Selected('
                f'item={self.item}, '
                f'prop={prop}, '
                f'prep={type(self.prep).__name__}...)\n'
            ) + ''.join([
                p.debug(indent + '- ')
                if isinstance(p, Selected)
                else str(p)
                for p in self.prep
            ])
        else:
            return (
                f'{indent}Selected('
                f'item={self.item}, '
                f'prop={prop}, '
                f'prep={self.prep})\n'
            )


class BaseQueryBuilder(Env):
    page: QueryPage
    expand: List[Property] = None
    query_params: QueryParams = None
    # `resolved` is used to map which prop.place properties are already
    # resolved, usually it maps to Selected, but different DataType's can return
    # different results.
    resolved: Dict[str, Selected]
    selected: Dict[str, Selected] = None

    def init_query_params(self, params: QueryParams):
        if params is None:
            params = QueryParams()
        self.query_params = params
        self._set_expanded_properties(params)

    # By default, when expand = None we think that nothing is expanded
    # in case we allow default_expand then we set it to empty list
    # if expand is empty list, we assume all are expanded
    # ?expand() will result in [] and ?expand(name) will result in [name]
    def _set_expanded_properties(self, params: QueryParams):
        prop_expr = params.expand
        if prop_expr is None and not params.default_expand:
            self.expand = None
            return

        self.expand = []
        if prop_expr is not None:
            self.expand = self.resolve(prop_expr)


class Star:
    def __str__(self):
        return '*'


class Func:
    pass


@dataclasses.dataclass
class ReservedProperty(Func):
    dtype: DataType
    param: str


@dataclasses.dataclass
class NestedProperty(Func):
    # Used to mark object nesting
    # for example object -> datatype

    left: Union[Object]
    right: Any


@dataclasses.dataclass
class ResultProperty(Func):
    # Used when result is calculated at ResultBuilder level
    # for example: checksum()
    expr: Expr
