"""Query polymorphic mixin — polymorphic query support."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from sparqlmojo.orm.security import escape_sparql_iri

if TYPE_CHECKING:
    from sparqlmojo.engine.session import Session
    from sparqlmojo.orm.filtering import FilterLike
    from sparqlmojo.orm.model import Model


class QueryPolymorphicMixin:
    """Mixin providing polymorphic query support."""

    # Type stubs for attributes set in Query.__init__
    if TYPE_CHECKING:
        model: type[Model]
        _session: Session | None
        _conditions: list[FilterLike]
        _filter_by_iri_bindings: dict[str, str]
        _values: dict[str, list[Any]] | None

        def _extract_subject_conditions(
            self,
        ) -> tuple[list[str], list[FilterLike]]: ...
        def _build_values_clause(
            self, subject_filter_values: list[str] | None = None
        ) -> str: ...
        def _add_subject_field_bind(
            self, where_lines: list[str], s_var: str = "?s"
        ) -> None: ...
        def _add_iri_filter_by_binds(self, where_lines: list[str]) -> None: ...

    def _get_polymorphic_subclass_models(self) -> list[type[Model]]:
        """Return registered non-abstract subclasses for polymorphic queries."""
        from sparqlmojo.orm.model_registry import ModelRegistry

        return ModelRegistry.get_subclass_models(self.model)

    def _collect_polymorphic_fields(
        self, subclass_models: list[type[Model]]
    ) -> dict[str, Any]:
        """Collect all fields from base model and subclasses (excluding SubjectField).

        Fields from subclasses are merged; base model fields take precedence
        on name collision.

        Args:
            subclass_models: List of subclass model classes

        Returns:
            Combined dict of field_name -> field_info for all models
        """
        merged: dict[str, Any] = {}

        # Add fields from subclasses first (lower priority)
        for sub_model in subclass_models:
            sub_rdf_fields: dict[str, Any] = getattr(sub_model, "_rdf_fields", {})
            for name, field_info in sub_rdf_fields.items():
                if not field_info.is_subject() and name != "rdf_type":
                    if name not in merged:
                        merged[name] = field_info

        # Add base model fields (higher priority, overwrite subclass)
        for name, field_info in self.model._rdf_fields.items():
            if not field_info.is_subject() and name != "rdf_type":
                merged[name] = field_info

        return merged

    def _build_select_clause_polymorphic(
        self, s_var: str, distinct_modifier: str, all_fields: dict[str, Any]
    ) -> str:
        """Build SELECT clause for polymorphic queries.

        Includes ?type variable (after ?s) and all fields from base + subclasses.

        Args:
            s_var: Subject variable (e.g., "?s")
            distinct_modifier: "DISTINCT " or "" depending on query settings
            all_fields: Combined fields from base model and subclasses

        Returns:
            Complete SELECT clause string for polymorphic query
        """
        select_vars: list[str] = [s_var, "?type"]

        # Add SubjectField if present (BIND variable)
        if self.model._subject_field_name:
            select_vars.append(f"?{self.model._subject_field_name}")

        # Add all merged fields
        for name in all_fields:
            select_vars.append(f"?{name}")

        return f"SELECT {distinct_modifier}" + " ".join(select_vars)

    def _build_where_patterns_polymorphic(
        self,
        s_var: str,
        subclass_models: list[type[Model]],
        all_fields: dict[str, Any],
    ) -> list[str]:
        """Build WHERE clause patterns for polymorphic queries.

        Uses VALUES ?type { ... } instead of the default rdf_type required pattern.
        All non-SubjectField, non-rdf_type fields are made OPTIONAL.

        Args:
            s_var: Subject variable (e.g., "?s")
            subclass_models: List of subclass model classes
            all_fields: Combined fields from base model and subclasses

        Returns:
            List of WHERE clause pattern strings
        """
        where_lines: list[str] = []

        # Extract subject equality conditions
        subject_filter_values: list[str]
        remaining_conditions: list["FilterLike"]
        subject_filter_values, remaining_conditions = self._extract_subject_conditions()

        # Subject VALUES clause (from filter_by on SubjectField)
        values_clause: str = self._build_values_clause(subject_filter_values or None)
        if values_clause:
            where_lines.append(values_clause)

        # Add BIND clause for SubjectField
        self._add_subject_field_bind(where_lines, s_var)

        # Required pattern: bind the ?type variable
        where_lines.append(f"{s_var} a ?type .")

        # VALUES ?type clause with all subclass type IRIs
        type_iris: list[str] = []
        for sub_model in subclass_models:
            rdf_type_val: str | None = sub_model.rdf_type_iri()
            if rdf_type_val:
                escaped: str = escape_sparql_iri(rdf_type_val)
                type_iris.append(f"<{escaped}>")

        # Also include base model rdf_type if it has one
        base_rdf_type: str | None = self.model.rdf_type_iri()
        if base_rdf_type:
            escaped_base: str = escape_sparql_iri(base_rdf_type)
            base_iri_str: str = f"<{escaped_base}>"
            if base_iri_str not in type_iris:
                type_iris.insert(0, base_iri_str)

        if type_iris:
            type_iris_str: str = " ".join(type_iris)
            where_lines.append(f"VALUES ?type {{ {type_iris_str} }}")

        # OPTIONAL patterns for all merged fields
        for name, field_info in all_fields.items():
            predicate: str = field_info.predicate
            if self._session:
                predicate = self._session.expand_iri(predicate)
            predicate_wrapped: str = f"<{escape_sparql_iri(predicate)}>"
            pattern: str = f"{s_var} {predicate_wrapped} ?{name} ."
            where_lines.append(f"OPTIONAL {{ {pattern} }}")

        # IRI filter_by binds
        self._add_iri_filter_by_binds(where_lines)

        # Filter conditions
        for cond in remaining_conditions:
            where_lines.append(cond.to_sparql(self.model))

        return where_lines
