# #region SupersetContextTemplatesMixin [C:3] [TYPE Module] [SEMANTICS superset, search, dataset, execution]
# @LAYER Infrastructure
# @BRIEF Deterministically detect runtime variables and Jinja references from dataset query-bearing fields without execution.
# @RELATION DEPENDS_ON -> [TemplateVariable]
# @RELATION DEPENDS_ON -> [SupersetContextExtractorBase]
# #endregion SupersetContextTemplatesMixin
# #region _templates_imports [TYPE Block]
from __future__ import annotations

import re
from typing import Any, cast

from ...logger import belief_scope, logger

logger = cast(Any, logger)
# #endregion _templates_imports
# #region SupersetContextTemplatesMixin [C:3] [TYPE Class]
# @BRIEF Mixin providing template variable discovery for the composed SupersetContextExtractor.
class SupersetContextTemplatesMixin:
    # #region SupersetContextTemplatesMixin.discover_template_variables [TYPE Function] [C:4]
    # @PURPOSE: Detect runtime variables and Jinja references from dataset query-bearing fields.
    # @PRE  dataset_payload is a Superset dataset-detail style payload with query-bearing fields when available.
    # @POST  returns deduplicated explicit variable records without executing Jinja or fabricating runtime values.
    # @SIDE_EFFECT  none.
    # @DATA_CONTRACT  Input[dataset_payload:Dict[str,Any]] -> Output[List[Dict[str,Any]]]
    def discover_template_variables(
        self, dataset_payload: dict[str, Any]
    ) -> list[dict[str, Any]]:
        with belief_scope("SupersetContextExtractor.discover_template_variables"):
            discovered: list[dict[str, Any]] = []
            seen_variable_names: set[str] = set()
            for expression_source in self._collect_query_bearing_expressions(
                dataset_payload
            ):
                for filter_match in re.finditer(
                    r"filter_values\(\s*['\"]([^'\"]+)['\"]\s*\)",
                    expression_source,
                    flags=re.IGNORECASE,
                ):
                    variable_name = str(filter_match.group(1) or "").strip()
                    if not variable_name:
                        continue
                    self._append_template_variable(
                        discovered=discovered,
                        seen_variable_names=seen_variable_names,
                        variable_name=variable_name,
                        expression_source=expression_source,
                        variable_kind="native_filter",
                        is_required=True,
                        default_value=None,
                    )
                for url_param_match in re.finditer(
                    r"url_param\(\s*['\"]([^'\"]+)['\"]\s*(?:,\s*([^)]+))?\)",
                    expression_source,
                    flags=re.IGNORECASE,
                ):
                    variable_name = str(url_param_match.group(1) or "").strip()
                    if not variable_name:
                        continue
                    default_literal = url_param_match.group(2)
                    self._append_template_variable(
                        discovered=discovered,
                        seen_variable_names=seen_variable_names,
                        variable_name=variable_name,
                        expression_source=expression_source,
                        variable_kind="parameter",
                        is_required=default_literal is None,
                        default_value=self._normalize_default_literal(
                            default_literal
                        ),
                    )
                for jinja_match in re.finditer(
                    r"\{\{\s*(.*?)\s*\}\}", expression_source, flags=re.DOTALL
                ):
                    expression = str(jinja_match.group(1) or "").strip()
                    if not expression:
                        continue
                    if any(
                        token in expression
                        for token in (
                            "filter_values(",
                            "url_param(",
                            "get_filters(",
                        )
                    ):
                        continue
                    variable_name = self._extract_primary_jinja_identifier(expression)
                    if not variable_name:
                        continue
                    self._append_template_variable(
                        discovered=discovered,
                        seen_variable_names=seen_variable_names,
                        variable_name=variable_name,
                        expression_source=expression_source,
                        variable_kind="derived"
                        if "." in expression or "|" in expression
                        else "parameter",
                        is_required=True,
                        default_value=None,
                    )
            logger.reflect(
                "Template variable discovery completed deterministically",
                extra={
                    "dataset_id": dataset_payload.get("id"),
                    "variable_count": len(discovered),
                    "variable_names": [
                        item["variable_name"] for item in discovered
                    ],
                },
            )
            return discovered
    # #endregion SupersetContextTemplatesMixin.discover_template_variables
    # #region SupersetContextTemplatesMixin._collect_query_bearing_expressions [TYPE Function] [C:3]
    # @PURPOSE: Collect SQL and expression-bearing dataset fields for deterministic template-variable discovery.
    # @RELATION DEPENDS_ON -> [SupersetContextTemplatesMixin.discover_template_variables]
    def _collect_query_bearing_expressions(
        self, dataset_payload: dict[str, Any]
    ) -> list[str]:
        expressions: list[str] = []
        def append_expression(candidate: Any) -> None:
            if not isinstance(candidate, str):
                return
            normalized = candidate.strip()
            if normalized:
                expressions.append(normalized)
        append_expression(dataset_payload.get("sql"))
        append_expression(dataset_payload.get("query"))
        append_expression(dataset_payload.get("template_sql"))
        metrics_payload = dataset_payload.get("metrics") or []
        if isinstance(metrics_payload, list):
            for metric in metrics_payload:
                if isinstance(metric, str):
                    append_expression(metric)
                    continue
                if not isinstance(metric, dict):
                    continue
                append_expression(metric.get("expression"))
                append_expression(metric.get("sqlExpression"))
                append_expression(metric.get("metric_name"))
        columns_payload = dataset_payload.get("columns") or []
        if isinstance(columns_payload, list):
            for column in columns_payload:
                if not isinstance(column, dict):
                    continue
                append_expression(column.get("sqlExpression"))
                append_expression(column.get("expression"))
        return expressions
    # #endregion SupersetContextTemplatesMixin._collect_query_bearing_expressions
    # #region SupersetContextTemplatesMixin._append_template_variable [TYPE Function] [C:2]
    # @PURPOSE: Append one deduplicated template-variable descriptor.
    def _append_template_variable(
        self,
        discovered: list[dict[str, Any]],
        seen_variable_names: set[str],
        variable_name: str,
        expression_source: str,
        variable_kind: str,
        is_required: bool,
        default_value: Any,
    ) -> None:
        normalized_name = str(variable_name or "").strip()
        if not normalized_name:
            return
        seen_key = normalized_name.lower()
        if seen_key in seen_variable_names:
            return
        seen_variable_names.add(seen_key)
        discovered.append(
            {
                "variable_name": normalized_name,
                "expression_source": expression_source,
                "variable_kind": variable_kind,
                "is_required": is_required,
                "default_value": default_value,
                "mapping_status": "unmapped",
            }
        )
    # #endregion SupersetContextTemplatesMixin._append_template_variable
    # #region SupersetContextTemplatesMixin._extract_primary_jinja_identifier [TYPE Function] [C:2]
    # @PURPOSE: Extract a deterministic primary identifier from a Jinja expression without executing it.
    def _extract_primary_jinja_identifier(self, expression: str) -> str | None:
        matched = re.match(r"([A-Za-z_][A-Za-z0-9_]*)", expression.strip())
        if matched is None:
            return None
        candidate = matched.group(1)
        if candidate in {
            "if",
            "else",
            "for",
            "set",
            "True",
            "False",
            "none",
            "None",
        }:
            return None
        return candidate
    # #endregion SupersetContextTemplatesMixin._extract_primary_jinja_identifier
    # #region SupersetContextTemplatesMixin._normalize_default_literal [TYPE Function] [C:2]
    # @PURPOSE: Normalize literal default fragments from template helper calls into JSON-safe values.
    def _normalize_default_literal(self, literal: str | None) -> Any:
        normalized_literal = str(literal or "").strip()
        if not normalized_literal:
            return None
        if (
            normalized_literal.startswith("'") and normalized_literal.endswith("'")
        ) or (
            normalized_literal.startswith('"') and normalized_literal.endswith('"')
        ):
            return normalized_literal[1:-1]
        lowered = normalized_literal.lower()
        if lowered in {"true", "false"}:
            return lowered == "true"
        if lowered in {"null", "none"}:
            return None
        try:
            return int(normalized_literal)
        except ValueError:
            try:
                return float(normalized_literal)
            except ValueError:
                return normalized_literal
    # #endregion SupersetContextTemplatesMixin._normalize_default_literal
# #endregion SupersetContextTemplatesMixin
