# src/rhosocial/activerecord/backend/dialect/mixins.py
"""
SQL dialect mixins for protocol implementations.

This module provides mixin classes that implement the functionality
defined in the protocol interfaces. This allows dialects to compose
only the features they support, rather than inheriting all functionality
from a base class.
"""
from typing import Any, List, Optional, Tuple, Dict, Union, TYPE_CHECKING

from .exceptions import UnsupportedFeatureError
from ..expression import bases
from ..expression.bases import ToSQLProtocol
from ..expression.statements import ReturningClause

if TYPE_CHECKING: # pragma: no cover
    from ..expression.advanced_functions import OrderedSetAggregation
    from ..expression.query_parts import QualifyClause, JoinExpression, OrderByClause
    from ..expression.graph import GraphEdgeDirection, MatchClause
    from ..expression.statements import (
        CreateTableExpression, DropTableExpression, AlterTableExpression,
        CreateViewExpression, DropViewExpression, TruncateExpression,
        CreateSchemaExpression, DropSchemaExpression,
        CreateIndexExpression, DropIndexExpression,
        CreateSequenceExpression, DropSequenceExpression, AlterSequenceExpression,
        CreateMaterializedViewExpression, DropMaterializedViewExpression,
        RefreshMaterializedViewExpression,
        CreateTriggerExpression, DropTriggerExpression,
        CreateFunctionExpression, DropFunctionExpression
    )


class WindowFunctionMixin:
    """Mixin for window function support."""

    def supports_window_functions(self) -> bool:
        """Whether window functions are supported."""
        return False

    def supports_window_frame_clause(self) -> bool:
        """Whether window frame clauses (ROWS/RANGE) are supported."""
        return False

    def format_window_function_call(
        self,
        call: "WindowFunctionCall"
    ) -> Tuple[str, tuple]:
        """Format window function call."""
        if not self.supports_window_functions():
            raise UnsupportedFeatureError(self.name, "window functions")

        all_params = []

        # Format function arguments
        arg_parts = []
        for arg in call.args:
            if isinstance(arg, bases.BaseExpression):
                arg_sql, arg_params = arg.to_sql()
                arg_parts.append(arg_sql)
                all_params.extend(arg_params)
            else:
                # Literal value
                arg_parts.append(self.get_parameter_placeholder())
                all_params.append(arg)

        func_sql = f"{call.function_name}({', '.join(arg_parts)})"

        if call.window_spec is None:
            # No window specification
            sql = func_sql
        else:
            if isinstance(call.window_spec, str):
                # Reference to named window
                window_part = self.format_identifier(call.window_spec)
            else:
                # Inline window specification
                window_spec_sql, window_spec_params = self.format_window_specification(call.window_spec)
                window_part = f"({window_spec_sql})" if window_spec_sql else "()"
                all_params.extend(window_spec_params)

            sql = f"{func_sql} OVER {window_part}"

        # Apply type casts if any (before alias)
        if call.cast_types:
            for target_type in call.cast_types:
                sql, all_params_tuple = self.format_cast_expression(sql, target_type, tuple(all_params), None)
                all_params = list(all_params_tuple)

        # Apply alias if any (after type casts)
        if call.alias:
            sql = f"{sql} AS {self.format_identifier(call.alias)}"

        return sql, tuple(all_params)

    def format_window_specification(
            self,
            spec: "WindowSpecification"
    ) -> Tuple[str, tuple]:
        """Format window specification."""
        if not self.supports_window_functions():
            raise UnsupportedFeatureError(self.name, "window functions")

        all_params = []

        parts = []

        # PARTITION BY
        if spec.partition_by:
            partition_parts = []
            for part in spec.partition_by:
                if isinstance(part, bases.BaseExpression):
                    part_sql, part_params = part.to_sql()
                    partition_parts.append(part_sql)
                    all_params.extend(part_params)
                else:
                    partition_parts.append(self.format_identifier(str(part)))
            parts.append("PARTITION BY " + ", ".join(partition_parts))

        # ORDER BY
        if spec.order_by and spec.order_by.expressions:
            # spec.order_by is now a single OrderByClause, so call its to_sql method
            # The OrderByClause.to_sql() method already includes "ORDER BY" keyword
            clause_sql, clause_params = spec.order_by.to_sql()
            parts.append(clause_sql)
            all_params.extend(clause_params)

        # Frame
        if spec.frame:
            # We need to implement format_window_frame_specification in the mixin
            frame_sql, frame_params = self.format_window_frame_specification(spec.frame)
            parts.append(frame_sql)
            all_params.extend(frame_params)

        # If no window specification components are provided, raise an error
        if not parts:
            raise ValueError("Window specification must have at least one component: PARTITION BY, ORDER BY, or FRAME.")

        return " ".join(parts), tuple(all_params)

    def format_window_frame_specification(
            self,
            spec: "WindowFrameSpecification"
    ) -> Tuple[str, tuple]:
        """Format window frame specification."""
        if not self.supports_window_frame_clause():
            raise UnsupportedFeatureError(self.name, "window frame specification")

        parts = [spec.frame_type]
        if spec.end_frame:
            parts.append(f"BETWEEN {spec.start_frame} AND {spec.end_frame}")
        else:
            parts.append(spec.start_frame)
        return " ".join(parts), ()

    def format_window_clause(
            self,
            clause: "WindowClause"
    ) -> Tuple[str, tuple]:
        """Format complete WINDOW clause."""
        if not self.supports_window_functions():
            raise UnsupportedFeatureError(self.name, "WINDOW clause")

        if not clause.definitions:
            raise ValueError("WindowClause must contain at least one window definition.")

        all_params = []
        def_parts = []

        for defn in clause.definitions:
            def_sql, def_params = self.format_window_definition(defn)
            def_parts.append(def_sql)
            all_params.extend(def_params)

        return f"WINDOW {', '.join(def_parts)}", tuple(all_params)

    def format_window_definition(
            self,
            spec: "WindowDefinition"
    ) -> Tuple[str, tuple]:
        """Format named window definition."""
        if not self.supports_window_functions():
            raise UnsupportedFeatureError(self.name, "window definition")

        spec_sql, spec_params = self.format_window_specification(spec.specification)
        window_def = f"{self.format_identifier(spec.name)} AS ({spec_sql})"
        return window_def, spec_params


class CTEMixin:
    """Mixin for Common Table Expression (CTE) support."""

    def supports_basic_cte(self) -> bool:
        """Whether basic CTEs are supported."""
        return False

    def supports_recursive_cte(self) -> bool:
        """Whether recursive CTEs are supported."""
        return False

    def supports_materialized_cte(self) -> bool:
        """Whether MATERIALIZED hint is supported."""
        return False

    def format_cte(
        self,
        name: str,
        query_sql: str,
        columns: Optional[List[str]] = None,
        recursive: bool = False,
        materialized: Optional[bool] = None,
        dialect_options: Optional[Dict[str, Any]] = None
    ) -> str:
        """Format a single CTE definition."""
        materialized_hint = ""
        if materialized is not None:
            materialized_hint = "MATERIALIZED " if materialized else "NOT MATERIALIZED "

        name_part = self.format_identifier(name)
        columns_part = f" ({', '.join(self.format_identifier(c) for c in columns)})" if columns else ""
        return f"{name_part}{columns_part} AS {materialized_hint}({query_sql})"

    def format_with_query(
            self,
            cte_sql_parts: List[str],
            main_query_sql: str,
            dialect_options: Optional[Dict[str, Any]] = None,
            has_recursive: bool = False  # Added parameter to indicate if any CTE is recursive
    ) -> str:
        """Format a complete query with WITH clause."""
        if not cte_sql_parts:
            return main_query_sql
        with_clause = self._format_with_clause(cte_sql_parts, has_recursive)
        return f"{with_clause} {main_query_sql}"

    def _format_with_clause(self, ctes_sql: List[str], has_recursive: bool = False) -> str:
        """Helper to format complete WITH clause from list of CTE definitions."""
        if not ctes_sql:
            return ""
        recursive_str = "RECURSIVE " if has_recursive else ""
        return f"WITH {recursive_str}{', '.join(ctes_sql)}"


class AdvancedGroupingMixin:
    """Mixin for advanced grouping operations (ROLLUP, CUBE, GROUPING SETS)."""

    def supports_rollup(self) -> bool:
        """Whether ROLLUP is supported."""
        return False

    def supports_cube(self) -> bool:
        """Whether CUBE is supported."""
        return False

    def supports_grouping_sets(self) -> bool:
        """Whether GROUPING SETS are supported."""
        return False

    def format_grouping_expression(
            self,
            operation: str,
            expressions: List["bases.BaseExpression"]
    ) -> Tuple[str, tuple]:
        """
        Formats a grouping expression (ROLLUP, CUBE, GROUPING SETS).

        Args:
            operation: The grouping operation ('ROLLUP', 'CUBE', or 'GROUPING SETS').
            expressions: List of expressions to group by.

        Returns:
            Tuple of (SQL string, parameters tuple) for the formatted expression.
        """
        # Check feature support based on operation type
        if operation.upper() == "ROLLUP":
            if not self.supports_rollup():
                raise UnsupportedFeatureError(self.name, "ROLLUP")
        elif operation.upper() == "CUBE":
            if not self.supports_cube():
                raise UnsupportedFeatureError(self.name, "CUBE")
        elif operation.upper() == "GROUPING SETS":
            if not self.supports_grouping_sets():
                raise UnsupportedFeatureError(self.name, "GROUPING SETS")

        all_params = []
        if operation.upper() == "GROUPING SETS":
            # For GROUPING SETS, expressions is a list of lists
            sets_parts = []
            for expr_list in expressions:
                expr_parts = []
                for expr in expr_list:
                    expr_sql, expr_params = expr.to_sql()
                    expr_parts.append(expr_sql)
                    all_params.extend(expr_params)
                sets_parts.append(f"({', '.join(expr_parts)})")
            inner_expr = ", ".join(sets_parts)
            sql = f"{operation.upper()}({inner_expr})"
        else:
            # For ROLLUP and CUBE, expressions is a simple list
            expr_parts = []
            for expr in expressions:
                expr_sql, expr_params = expr.to_sql()
                expr_parts.append(expr_sql)
                all_params.extend(expr_params)
            inner_expr = ", ".join(expr_parts)
            sql = f"{operation.upper()}({inner_expr})"

        return sql, tuple(all_params)


class ReturningMixin:
    """Mixin for RETURNING clause support."""

    def supports_returning_clause(self) -> bool:
        """Whether RETURNING clause is supported."""
        return False

    def format_returning_clause(
            self,
            clause: "ReturningClause"
    ) -> Tuple[str, Tuple]:
        """
        Format a RETURNING clause.

        Args:
            clause: ReturningClause object containing expressions to return

        Returns:
            Tuple of (SQL string, parameters tuple)
        """
        if not self.supports_returning_clause():
            raise UnsupportedFeatureError(self.name, "RETURNING clause")

        all_params = []
        expr_parts = []
        for expr in clause.expressions:
            expr_sql, expr_params = expr.to_sql()
            expr_parts.append(expr_sql)
            all_params.extend(expr_params)

        returning_sql = f"RETURNING {', '.join(expr_parts)}"

        # Add alias if provided
        if clause.alias:
            returning_sql += f" AS {self.format_identifier(clause.alias)}"

        return returning_sql, tuple(all_params)


class UpsertMixin:
    """Mixin for UPSERT operation support."""

    def supports_upsert(self) -> bool:
        """Whether UPSERT is supported."""
        return False

    def get_upsert_syntax_type(self) -> str:
        """
        Get UPSERT syntax type.

        Returns:
            'ON CONFLICT' (PostgreSQL) or 'ON DUPLICATE KEY' (MySQL)
        """
        return "ON CONFLICT"

    def format_on_conflict_clause(self, expr: "OnConflictClause") -> Tuple[str, tuple]:
        """Format ON CONFLICT clause."""
        all_params = []

        # Start with ON CONFLICT
        parts = ["ON CONFLICT"]

        # Add conflict target if specified
        if expr.conflict_target:
            target_parts = []
            for target in expr.conflict_target:
                if isinstance(target, str):
                    # Column name as string
                    target_parts.append(self.format_identifier(target))
                elif isinstance(target, ToSQLProtocol):
                    # Column expression
                    target_sql, target_params = target.to_sql()
                    target_parts.append(target_sql)
                    all_params.extend(target_params)
                else:
                    # Other types - format as identifier
                    target_parts.append(self.format_identifier(str(target)))

            if target_parts:
                parts.append(f"({', '.join(target_parts)})")

        # Add DO NOTHING or DO UPDATE
        if expr.do_nothing:
            parts.append("DO NOTHING")
        elif expr.update_assignments:
            # DO UPDATE SET assignments
            update_parts = []
            for col, expr_val in expr.update_assignments.items():
                if isinstance(expr_val, bases.BaseExpression):
                    val_sql, val_params = expr_val.to_sql()
                    update_parts.append(f"{self.format_identifier(col)} = {val_sql}")
                    all_params.extend(val_params)
                else:
                    update_parts.append(f"{self.format_identifier(col)} = {self.get_parameter_placeholder()}")
                    all_params.append(expr_val)

            parts.append(f"DO UPDATE SET {', '.join(update_parts)}")

            # Add WHERE clause if specified
            if expr.update_where:
                where_sql, where_params = expr.update_where.to_sql()
                parts.append(f"WHERE {where_sql}")
                all_params.extend(where_params)
        else:
            # Default to DO NOTHING if no action specified
            parts.append("DO NOTHING")

        return " ".join(parts), tuple(all_params)


class LateralJoinMixin:
    """Mixin for LATERAL join support."""

    def supports_lateral_join(self) -> bool:
        """Whether LATERAL joins are supported."""
        return False

    def format_lateral_expression(
            self,
            expr_sql: str,
            expr_params: Tuple[Any, ...],
            alias: Optional[str],
            join_type: str
    ) -> Tuple[str, Tuple]:
        """Format LATERAL expression."""
        if alias is not None:
            sql = f"{join_type.upper()} JOIN LATERAL {expr_sql} AS {self.format_identifier(alias)}"
        else:
            sql = f"{join_type.upper()} JOIN LATERAL {expr_sql}"
        return sql, expr_params

    def format_table_function_expression(
            self,
            func_name: str,
            args_sql: List[str],
            args_params: Tuple[Any, ...],
            alias: Optional[str],
            column_names: Optional[List[str]]
    ) -> Tuple[str, Tuple]:
        """Format table-valued function expression."""
        args_str = ", ".join(args_sql)

        cols_sql = ""
        if column_names:
            cols_sql = f"({', '.join(self.format_identifier(name) for name in column_names)})"

        if alias is not None:
            sql = f"{func_name.upper()}({args_str}) AS {self.format_identifier(alias)}{cols_sql}"
        else:
            sql = f"{func_name.upper()}({args_str}){cols_sql}"
        return sql, args_params


class JoinMixin:
    """Mixin for JOIN clause support."""

    def supports_inner_join(self) -> bool:
        """Whether INNER JOIN is supported. Defaults to True."""
        return True

    def supports_left_join(self) -> bool:
        """Whether LEFT JOIN is supported. Defaults to True."""
        return True

    def supports_right_join(self) -> bool:
        """Whether RIGHT JOIN is supported. Defaults to False."""
        return False

    def supports_full_join(self) -> bool:
        """Whether FULL JOIN is supported. Defaults to False."""
        return False

    def supports_cross_join(self) -> bool:
        """Whether CROSS JOIN is supported. Defaults to True."""
        return True

    def supports_natural_join(self) -> bool:
        """Whether NATURAL JOIN is supported. Defaults to True."""
        return True

    def format_join_expression(self, join_expr: "JoinExpression") -> Tuple[str, Tuple]:
        """
        Generic implementation for formatting a JOIN expression.
        This method validates support for the given join type using protocol methods.
        """
        from ..expression import QueryExpression, JoinExpression
        join_type_upper = join_expr.join_type.upper()

        # Use split to handle cases like "LEFT OUTER JOIN"
        join_type_keyword = join_type_upper.split()[0]

        # Check for feature support
        if join_type_keyword == "INNER" and not self.supports_inner_join():
            raise UnsupportedFeatureError(self.name, "INNER JOIN")
        elif join_type_keyword == "LEFT" and not self.supports_left_join():
            raise UnsupportedFeatureError(self.name, "LEFT JOIN")
        elif join_type_keyword == "RIGHT" and not self.supports_right_join():
            raise UnsupportedFeatureError(self.name, "RIGHT JOIN")
        elif join_type_keyword == "FULL" and not self.supports_full_join():
            raise UnsupportedFeatureError(self.name, "FULL JOIN")
        elif join_type_keyword == "CROSS" and not self.supports_cross_join():
            raise UnsupportedFeatureError(self.name, "CROSS JOIN")

        if join_expr.natural and not self.supports_natural_join():
            raise UnsupportedFeatureError(self.name, "NATURAL JOIN")

        all_params = []

        # Format left and right sides of the join
        left_sql, left_params = join_expr.left_table.to_sql()
        if isinstance(join_expr.left_table, (QueryExpression, JoinExpression)):
            left_sql = f"({left_sql})"
        all_params.extend(left_params)

        right_sql, right_params = join_expr.right_table.to_sql()
        if isinstance(join_expr.right_table, (QueryExpression, JoinExpression)):
            right_sql = f"({right_sql})"
        all_params.extend(right_params)

        # Build the join clause
        join_parts = [left_sql]

        if join_expr.natural:
            join_parts.append("NATURAL")

        join_parts.append(join_type_upper)
        join_parts.append(right_sql)

        # Handle ON or USING clause, which are not used with NATURAL JOIN
        if not join_expr.natural:
            if join_expr.condition:
                cond_sql, cond_params = join_expr.condition.to_sql()
                join_parts.append(f"ON {cond_sql}")
                all_params.extend(cond_params)
            elif join_expr.using:
                using_cols = ", ".join(self.format_identifier(col) for col in join_expr.using)
                join_parts.append(f"USING ({using_cols})")
            elif "CROSS" not in join_type_upper:
                raise ValueError(f"{join_type_upper} requires a condition or USING clause.")

        sql = " ".join(join_parts)

        # Add alias for the joined result if provided
        if join_expr.alias:
            sql = f"({sql}) AS {self.format_identifier(join_expr.alias)}"

        return sql, tuple(all_params)


class ArrayMixin:
    """Mixin for array type support."""

    def supports_array_type(self) -> bool:
        """Whether array types are supported."""
        return False

    def supports_array_constructor(self) -> bool:
        """Whether ARRAY constructor is supported."""
        return False

    def supports_array_access(self) -> bool:
        """Whether array subscript access is supported."""
        return False

    def format_array_expression(
        self,
        expr: "ArrayExpression"
    ) -> Tuple[str, Tuple]:
        """Format array expression."""
        all_params = ()

        if expr.operation.upper() == "CONSTRUCTOR" and expr.elements is not None:
            element_parts = []
            all_params = []
            for elem in expr.elements:
                elem_sql, elem_params = elem.to_sql()
                element_parts.append(elem_sql)
                all_params.extend(elem_params)
            sql = f"ARRAY[{', '.join(element_parts)}]"
            all_params = tuple(all_params)
        elif expr.operation.upper() == "ACCESS" and expr.base_expr and expr.index_expr:
            base_sql, base_params = expr.base_expr.to_sql()
            index_sql, index_params = expr.index_expr.to_sql()
            sql = f"({base_sql}[{index_sql}])"
            all_params = base_params + index_params
        else:
            # Default case for unsupported operations
            sql = "ARRAY[]"

        # Apply type casts if any (before alias)
        if expr.cast_types:
            for target_type in expr.cast_types:
                sql, all_params = self.format_cast_expression(sql, target_type, all_params, None)

        # Apply alias if any (after type casts)
        if expr.alias:
            sql = f"{sql} AS {self.format_identifier(expr.alias)}"

        return sql, all_params


class JSONMixin:
    """Mixin for JSON type support."""

    def supports_json_type(self) -> bool:
        """Whether JSON type is supported."""
        return False

    def get_json_access_operator(self) -> str:
        """
        Get JSON access operator.

        Returns:
            '->' (PostgreSQL/MySQL/SQLite) or other dialect-specific operator
        """
        return "->"

    def supports_json_table(self) -> bool:
        """Whether JSON_TABLE function is supported."""
        return False

    def format_json_expression(
        self,
        expr: "JSONExpression"
    ) -> Tuple[str, Tuple]:
        """Format JSON expression."""
        if isinstance(expr.column, bases.BaseExpression):
            col_sql, col_params = expr.column.to_sql()
        else:
            col_sql, col_params = self.format_identifier(str(expr.column)), ()
        sql = f"({col_sql} {expr.operation} ?)"
        params = col_params + (expr.path,)

        # Apply type casts if any (before alias)
        if expr.cast_types:
            for target_type in expr.cast_types:
                sql, params = self.format_cast_expression(sql, target_type, params, None)

        # Apply alias if any (after type casts)
        if expr.alias:
            sql = f"{sql} AS {self.format_identifier(expr.alias)}"

        return sql, params

    def format_json_table_expression(
            self,
            json_col_sql: str,
            path: str,
            columns: List[Dict[str, Any]],
            alias: Optional[str],
            params: tuple
    ) -> Tuple[str, Tuple]:
        """
        Formats a JSON_TABLE expression.

        Args:
            json_col_sql: SQL for the JSON column/expression.
            path: The JSON path expression.
            columns: A list of dictionaries, each defining a column.
            alias: The alias for the resulting table.
            params: Parameters for the JSON column expression.

        Returns:
            Tuple of (SQL string, parameters tuple) for the formatted expression.
        """
        if not self.supports_json_table():
            raise UnsupportedFeatureError(self.name, "JSON_TABLE function")

        cols_defs = [f"{col['name']} {col['type']} PATH '{col['path']}'" for col in columns]
        columns_sql = f"COLUMNS({', '.join(cols_defs)})"
        if alias is not None:
            sql = f"JSON_TABLE({json_col_sql}, '{path}' {columns_sql}) AS {self.format_identifier(alias)}"
        else:
            sql = f"JSON_TABLE({json_col_sql}, '{path}' {columns_sql})"
        return sql, params


class ExplainMixin:
    """Mixin for EXPLAIN statement support."""

    def supports_explain_analyze(self) -> bool:
        """Whether EXPLAIN ANALYZE is supported."""
        return False

    def supports_explain_format(self, format_type: str) -> bool:
        """
        Check if specific EXPLAIN format is supported.

        Args:
            format_type: Format type (e.g., 'JSON', 'XML', 'YAML')

        Returns:
            True if format is supported
        """
        return False

    def format_explain_statement(self, expr: "ExplainExpression") -> Tuple[str, tuple]:
        """Format EXPLAIN statement."""
        statement_sql, statement_params = expr.statement.to_sql()
        options = expr.options
        if options is None:
            return f"EXPLAIN {statement_sql}", statement_params

        parts = ["EXPLAIN"]
        # Import here to avoid circular imports
        from ..expression.statements import ExplainType
        # Determine if ANALYZE should be included based on the type field
        # If type is ANALYZE, or if the boolean analyze field is True
        if (hasattr(options, 'type') and options.type == ExplainType.ANALYZE) or options.analyze:
            parts.append("ANALYZE")
        if options.format:
            parts.append(f"FORMAT {options.format.value.upper()}")
        # Only show costs=False if it's explicitly set to False, since True is default
        if not options.costs:
            parts.append("COSTS OFF")
        if options.buffers:
            parts.append("BUFFERS")
        if options.timing and options.analyze:
            parts.append("TIMING ON")
        if options.verbose:
            parts.append("VERBOSE")
        if options.settings:
            parts.append("SETTINGS")  # PostgreSQL-specific option, not SQL standard
        if options.wal:
            parts.append("WAL")  # PostgreSQL-specific option, not SQL standard

        return f"{' '.join(parts)} {statement_sql}", statement_params


class GraphMixin:
    """Mixin for graph query (MATCH) support."""

    def supports_graph_match(self) -> bool:
        """Whether graph query MATCH clause is supported."""
        return False

    def format_graph_vertex(
            self,
            variable: str,
            table: str
    ) -> Tuple[str, tuple]:
        """
        Formats a graph vertex expression.

        Args:
            variable: The vertex variable name.
            table: The vertex table name.

        Returns:
            Tuple of (SQL string, parameters tuple) for the formatted expression.
        """
        if not self.supports_graph_match():
            raise UnsupportedFeatureError(self.name, "graph MATCH clause")

        sql = f"({variable} IS {self.format_identifier(table)})"
        return sql, ()

    def format_graph_edge(
            self,
            variable: str,
            table: str,
            direction: "GraphEdgeDirection"
    ) -> Tuple[str, tuple]:
        """
        Formats a graph edge expression.

        Args:
            variable: The edge variable name.
            table: The edge table name.
            direction: The edge direction.

        Returns:
            Tuple of (SQL string, parameters tuple) for the formatted expression.
        """
        if not self.supports_graph_match():
            raise UnsupportedFeatureError(self.name, "graph MATCH clause")

        from ..expression.graph import GraphEdgeDirection  # Import here to avoid circular import

        # For different directions, construct the correct syntax
        if direction == GraphEdgeDirection.RIGHT:
            # Right-directed: -[var IS table]->
            sql = f"-[{variable} IS {self.format_identifier(table)}]->"
        elif direction == GraphEdgeDirection.LEFT:
            # Left-directed: <-[var IS table]-
            sql = f"<-[{variable} IS {self.format_identifier(table)}]-"
        elif direction == GraphEdgeDirection.ANY:
            # Bidirectional: <-[var IS table]->
            sql = f"<-[{variable} IS {self.format_identifier(table)}]->"
        else:  # GraphEdgeDirection.NONE (undirected)
            # Undirected: -[var IS table]-
            sql = f"-[{variable} IS {self.format_identifier(table)}]-"

        return sql, ()

    def format_match_clause(
            self,
            clause: "MatchClause"
    ) -> Tuple[str, tuple]:
        """
        Formats a MATCH clause.

        Args:
            clause: MatchClause object containing the match expression

        Returns:
            Tuple of (SQL string, parameters tuple) for the formatted clause.
        """
        if not self.supports_graph_match():
            raise UnsupportedFeatureError(self.name, "graph MATCH clause")

        # This method is called from MatchClause.to_sql(), so we need to format the MATCH clause
        # with the path components from the clause
        path_sql, all_params = [], []
        for part in clause.path:
            sql, params = part.to_sql()
            path_sql.append(sql)
            all_params.extend(params)

        match_sql = f"MATCH {' '.join(path_sql)}"
        return match_sql, tuple(all_params)


class FilterClauseMixin:
    """Mixin for aggregate FILTER clause support."""

    def supports_filter_clause(self) -> bool:
        """Whether FILTER (WHERE ...) clause is supported in aggregate functions."""
        return False

    def format_filter_clause(
            self,
            condition_sql: str,
            condition_params: tuple
    ) -> Tuple[str, Tuple]:
        """
        Format a FILTER (WHERE ...) clause.

        Args:
            condition_sql: SQL string for the WHERE condition.
            condition_params: Parameters for the WHERE condition.

        Returns:
            Tuple of (SQL string, parameters tuple) for the formatted clause.
        """
        if not self.supports_filter_clause():
            raise UnsupportedFeatureError(self.name, "FILTER clause in aggregate functions")

        return f"FILTER (WHERE {condition_sql})", condition_params


class OrderedSetAggregationMixin:
    """Mixin for ordered-set aggregate function support (WITHIN GROUP (ORDER BY ...))."""

    def supports_ordered_set_aggregation(self) -> bool:
        """Whether ordered-set aggregate functions are supported."""
        return False

    def format_ordered_set_aggregation(
        self,
        aggregation: "OrderedSetAggregation"
    ) -> Tuple[str, Tuple]:
        """
        Formats an ordered-set aggregate function call.

        Args:
            aggregation: OrderedSetAggregation object to format

        Returns:
            Tuple of (SQL string, parameters tuple) for the formatted expression.
        """
        if not self.supports_ordered_set_aggregation():
            raise UnsupportedFeatureError(self.name, "ordered-set aggregate functions")

        # Format function arguments
        func_args_sql, func_args_params = [], []
        for arg in aggregation.args:
            arg_sql, arg_params = arg.to_sql()
            func_args_sql.append(arg_sql)
            func_args_params.extend(arg_params)

        # Get the ORDER BY SQL from the OrderByClause object
        order_by_sql, order_by_params = aggregation.order_by.to_sql()
        sql = f"{aggregation.func_name.upper()}({', '.join(func_args_sql)}) WITHIN GROUP ({order_by_sql})"

        all_params = func_args_params + list(order_by_params)

        # Apply type casts if any (before alias)
        if aggregation.cast_types:
            for target_type in aggregation.cast_types:
                sql, all_params_tuple = self.format_cast_expression(sql, target_type, tuple(all_params), None)
                all_params = list(all_params_tuple)

        # Apply alias if any (after type casts)
        if aggregation.alias:
            sql = f"{sql} AS {self.format_identifier(aggregation.alias)}"

        return sql, tuple(all_params)


class MergeMixin:
    """Mixin for MERGE statement support."""

    def supports_merge_statement(self) -> bool:
        """Whether MERGE statement is supported."""
        return False

    def format_merge_statement(self, expr: "MergeExpression") -> Tuple[str, tuple]:
        """Format MERGE statement."""
        all_params: List[Any] = []
        target_sql, target_params = expr.target_table.to_sql()
        all_params.extend(target_params)
        source_sql, source_params = expr.source.to_sql()
        all_params.extend(source_params)
        on_sql, on_params = expr.on_condition.to_sql()
        all_params.extend(on_params)

        merge_sql_parts = [f"MERGE INTO {target_sql}", f"USING {source_sql}", f"ON {on_sql}"]

        # Import here to avoid circular imports
        from ..expression.statements import MergeActionType

        for action in expr.when_matched:
            action_sql_parts = []
            if action.condition:
                cond_sql, cond_params = action.condition.to_sql()
                action_sql_parts.append(f"WHEN MATCHED AND {cond_sql}")
                all_params.extend(cond_params)
            else:
                action_sql_parts.append("WHEN MATCHED")

            if action.action_type == MergeActionType.UPDATE:
                assignments = []
                for col, as_expr in action.assignments.items():
                    as_sql, as_params = as_expr.to_sql()
                    assignments.append(f"{self.format_identifier(col)} = {as_sql}")
                    all_params.extend(as_params)
                action_sql_parts.append(f"THEN UPDATE SET {', '.join(assignments)}")
            elif action.action_type == MergeActionType.DELETE:
                action_sql_parts.append("THEN DELETE")
            merge_sql_parts.append(" ".join(action_sql_parts))

        for action in expr.when_not_matched:
            action_sql_parts = []
            if action.condition:
                cond_sql, cond_params = action.condition.to_sql()
                action_sql_parts.append(f"WHEN NOT MATCHED AND {cond_sql}")
                all_params.extend(cond_params)
            else:
                action_sql_parts.append("WHEN NOT MATCHED")

            if action.action_type == MergeActionType.INSERT:
                insert_cols, insert_vals = [], []
                for col, val_expr in action.assignments.items():
                    insert_cols.append(self.format_identifier(col))
                    val_sql, val_params = val_expr.to_sql()
                    insert_vals.append(val_sql)
                    all_params.extend(val_params)
                if insert_cols:
                    action_sql_parts.append(f"THEN INSERT ({', '.join(insert_cols)}) VALUES ({', '.join(insert_vals)})")
                else:
                    action_sql_parts.append("THEN INSERT DEFAULT VALUES")
            merge_sql_parts.append(" ".join(action_sql_parts))

        # Handle WHEN NOT MATCHED BY SOURCE clauses
        for action in expr.when_not_matched_by_source:
            action_sql_parts = []
            if action.condition:
                cond_sql, cond_params = action.condition.to_sql()
                action_sql_parts.append(f"WHEN NOT MATCHED BY SOURCE AND {cond_sql}")
                all_params.extend(cond_params)
            else:
                action_sql_parts.append("WHEN NOT MATCHED BY SOURCE")

            if action.action_type == MergeActionType.UPDATE:
                assignments = []
                for col, as_expr in action.assignments.items():
                    as_sql, as_params = as_expr.to_sql()
                    assignments.append(f"{self.format_identifier(col)} = {as_sql}")
                    all_params.extend(as_params)
                action_sql_parts.append(f"THEN UPDATE SET {', '.join(assignments)}")
            elif action.action_type == MergeActionType.DELETE:
                action_sql_parts.append("THEN DELETE")
            merge_sql_parts.append(" ".join(action_sql_parts))

        return " ".join(merge_sql_parts), tuple(all_params)


class TemporalTableMixin:
    """Mixin for temporal table support."""

    def supports_temporal_tables(self) -> bool:
        """Whether temporal tables are supported."""
        return False

    def format_temporal_options(
            self,
            options: Dict[str, Any]
    ) -> Tuple[str, tuple]:
        """Format temporal table options."""
        if not options:
            raise ValueError(
                "Temporal options cannot be empty. If no temporal options are needed, don't call format_temporal_options.")
        sql_parts, params = ["FOR SYSTEM_TIME"], []
        # Add temporal options to SQL parts based on the options provided
        for key, value in options.items():
            sql_parts.append(f"{key.upper()} ?")
            params.append(value)
        return " ".join(sql_parts), tuple(params)


class QualifyClauseMixin:
    """Mixin for QUALIFY clause support."""

    def supports_qualify_clause(self) -> bool:
        """Whether QUALIFY clause is supported."""
        return False

    def format_qualify_clause(
            self,
            clause: "QualifyClause"
    ) -> Tuple[str, tuple]:
        """Format QUALIFY clause."""
        if not self.supports_qualify_clause():
            raise UnsupportedFeatureError(self.name, "QUALIFY clause")

        condition_sql, condition_params = clause.condition.to_sql()
        return f"QUALIFY {condition_sql}", condition_params


class LockingMixin:
    """Mixin for locking clause support."""

    def supports_for_update_skip_locked(self) -> bool:
        """Whether FOR UPDATE SKIP LOCKED is supported."""
        return False

    def format_for_update_clause(
            self,
            clause: "ForUpdateClause"
    ) -> Tuple[str, tuple]:
        """Default implementation for FOR UPDATE clause."""
        all_params = []
        sql_parts = ["FOR UPDATE"]

        # Handle OF columns if specified
        if clause.of_columns:
            of_parts = []
            for col in clause.of_columns:
                if isinstance(col, str):
                    of_parts.append(self.format_identifier(col))
                elif isinstance(col, ToSQLProtocol):  # BaseExpression
                    col_sql, col_params = col.to_sql()
                    of_parts.append(col_sql)
                    all_params.extend(col_params)
            if of_parts:
                sql_parts.append(f"OF {', '.join(of_parts)}")

        # Handle NOWAIT/SKIP LOCKED options
        if clause.nowait:
            sql_parts.append("NOWAIT")
        elif clause.skip_locked:
            sql_parts.append("SKIP LOCKED")

        return " ".join(sql_parts), tuple(all_params)


class SetOperationMixin:
    """Mixin for set operation (UNION, INTERSECT, EXCEPT) support."""

    def supports_union(self) -> bool:
        """Whether UNION operation is supported."""
        return False

    def supports_union_all(self) -> bool:
        """Whether UNION ALL operation is supported."""
        return False

    def supports_intersect(self) -> bool:
        """Whether INTERSECT operation is supported."""
        return False

    def supports_except(self) -> bool:
        """Whether EXCEPT operation is supported."""
        return False

    def supports_set_operation_order_by(self) -> bool:
        """Whether set operations support ORDER BY clauses."""
        return False

    def supports_set_operation_limit_offset(self) -> bool:
        """Whether set operations support LIMIT and OFFSET clauses."""
        return False

    def supports_set_operation_for_update(self) -> bool:
        """Whether set operations support FOR UPDATE clauses."""
        return False

    def format_set_operation_expression(
        self,
        left: "bases.BaseExpression",
        right: "bases.BaseExpression",
        operation: str,
        alias: Optional[str],
        all_: bool,
        order_by_clause: Optional["OrderByClause"] = None,
        limit_offset_clause: Optional["LimitOffsetClause"] = None,
        for_update_clause: Optional["ForUpdateClause"] = None
    ) -> Tuple[str, Tuple]:
        """Format set operation expression (UNION, INTERSECT, EXCEPT)."""
        left_sql, left_params = left.to_sql()
        right_sql, right_params = right.to_sql()
        all_str = " ALL" if all_ else ""

        # Build the base set operation SQL
        base_sql = f"{left_sql} {operation}{all_str} {right_sql}"

        all_params = list(left_params + right_params)
        sql_parts = [f"({base_sql})"]

        # Add alias if present
        if alias:
            sql_parts.append(f"AS {self.format_identifier(alias)}")

        # Add ORDER BY clause if present
        if order_by_clause:
            order_by_sql, order_by_params = order_by_clause.to_sql()
            sql_parts.append(order_by_sql)
            all_params.extend(order_by_params)

        # Add LIMIT/OFFSET clause if present
        if limit_offset_clause:
            limit_offset_sql, limit_offset_params = limit_offset_clause.to_sql()
            sql_parts.append(limit_offset_sql)
            all_params.extend(limit_offset_params)

        # Add FOR UPDATE clause if present
        if for_update_clause:
            for_update_sql, for_update_params = for_update_clause.to_sql()
            sql_parts.append(for_update_sql)
            all_params.extend(for_update_params)

        sql = " ".join(sql_parts)
        return sql, tuple(all_params)


# ============================================================
# DDL (Data Definition Language) Mixins
# ============================================================

class TableMixin:
    """Mixin for table DDL support."""

    def supports_create_table(self) -> bool:
        """Whether CREATE TABLE is supported."""
        return True

    def supports_drop_table(self) -> bool:
        """Whether DROP TABLE is supported."""
        return True

    def supports_alter_table(self) -> bool:
        """Whether ALTER TABLE is supported."""
        return True

    def supports_temporary_table(self) -> bool:
        """Whether TEMPORARY tables are supported."""
        return True

    def supports_if_not_exists_table(self) -> bool:
        """Whether CREATE TABLE IF NOT EXISTS is supported."""
        return False

    def supports_if_exists_table(self) -> bool:
        """Whether DROP TABLE IF EXISTS is supported."""
        return False

    def supports_table_partitioning(self) -> bool:
        """Whether table partitioning is supported."""
        return False

    def supports_table_tablespace(self) -> bool:
        """Whether tablespace specification is supported."""
        return False

    def supports_drop_column(self) -> bool:
        """Whether DROP COLUMN is supported."""
        return True

    def supports_alter_column_type(self) -> bool:
        """Whether altering column data type is supported."""
        return True

    def supports_rename_column(self) -> bool:
        """Whether RENAME COLUMN is supported."""
        return True

    def supports_rename_table(self) -> bool:
        """Whether RENAME TABLE is supported."""
        return True

    def supports_add_constraint(self) -> bool:
        """Whether ADD CONSTRAINT is supported."""
        return True

    def supports_drop_constraint(self) -> bool:
        """Whether DROP CONSTRAINT is supported."""
        return True

    def format_create_table_statement(
        self,
        expr: "CreateTableExpression"
    ) -> Tuple[str, tuple]:
        """Format CREATE TABLE statement. Override in dialect."""
        raise UnsupportedFeatureError(self.name, "CREATE TABLE")

    def format_drop_table_statement(
        self,
        expr: "DropTableExpression"
    ) -> Tuple[str, tuple]:
        """Format DROP TABLE statement. Override in dialect."""
        raise UnsupportedFeatureError(self.name, "DROP TABLE")

    def format_alter_table_statement(
        self,
        expr: "AlterTableExpression"
    ) -> Tuple[str, tuple]:
        """Format ALTER TABLE statement. Override in dialect."""
        raise UnsupportedFeatureError(self.name, "ALTER TABLE")


class ViewMixin:
    """Mixin for view DDL support."""

    def supports_create_view(self) -> bool:
        """Whether CREATE VIEW is supported."""
        return True

    def supports_drop_view(self) -> bool:
        """Whether DROP VIEW is supported."""
        return True

    def supports_or_replace_view(self) -> bool:
        """Whether CREATE OR REPLACE VIEW is supported."""
        return False

    def supports_temporary_view(self) -> bool:
        """Whether TEMPORARY views are supported."""
        return False

    def supports_materialized_view(self) -> bool:
        """Whether materialized views are supported."""
        return False

    def supports_refresh_materialized_view(self) -> bool:
        """Whether REFRESH MATERIALIZED VIEW is supported."""
        return False

    def supports_materialized_view_tablespace(self) -> bool:
        """Whether tablespace specification for materialized views is supported."""
        return False

    def supports_materialized_view_storage_options(self) -> bool:
        """Whether storage options for materialized views are supported."""
        return False

    def supports_if_exists_view(self) -> bool:
        """Whether DROP VIEW IF EXISTS is supported."""
        return False

    def supports_view_check_option(self) -> bool:
        """Whether WITH CHECK OPTION is supported."""
        return False

    def supports_cascade_view(self) -> bool:
        """Whether DROP VIEW CASCADE is supported."""
        return False

    def format_create_view_statement(
        self,
        expr: "CreateViewExpression"
    ) -> Tuple[str, tuple]:
        """Format CREATE VIEW statement. Override in dialect."""
        raise UnsupportedFeatureError(self.name, "CREATE VIEW")

    def format_drop_view_statement(
        self,
        expr: "DropViewExpression"
    ) -> Tuple[str, tuple]:
        """Format DROP VIEW statement. Override in dialect."""
        raise UnsupportedFeatureError(self.name, "DROP VIEW")

    def format_create_materialized_view_statement(
        self,
        expr: "CreateMaterializedViewExpression"
    ) -> Tuple[str, tuple]:
        """Format CREATE MATERIALIZED VIEW statement."""
        if not self.supports_materialized_view():
            raise UnsupportedFeatureError(self.name, "CREATE MATERIALIZED VIEW")

        parts = ["CREATE MATERIALIZED VIEW"]
        parts.append(self.format_identifier(expr.view_name))

        if expr.column_aliases:
            cols = ', '.join(self.format_identifier(c) for c in expr.column_aliases)
            parts.append(f"({cols})")

        if expr.tablespace and self.supports_materialized_view_tablespace():
            parts.append(f"TABLESPACE {self.format_identifier(expr.tablespace)}")

        query_sql, query_params = expr.query.to_sql()
        parts.append(f"AS {query_sql}")

        if expr.with_data:
            parts.append("WITH DATA")
        else:
            parts.append("WITH NO DATA")

        return ' '.join(parts), query_params

    def format_drop_materialized_view_statement(
        self,
        expr: "DropMaterializedViewExpression"
    ) -> Tuple[str, tuple]:
        """Format DROP MATERIALIZED VIEW statement."""
        if not self.supports_materialized_view():
            raise UnsupportedFeatureError(self.name, "DROP MATERIALIZED VIEW")

        parts = ["DROP MATERIALIZED VIEW"]
        if expr.if_exists:
            parts.append("IF EXISTS")
        parts.append(self.format_identifier(expr.view_name))
        if expr.cascade:
            parts.append("CASCADE")
        return ' '.join(parts), ()

    def format_refresh_materialized_view_statement(
        self,
        expr: "RefreshMaterializedViewExpression"
    ) -> Tuple[str, tuple]:
        """Format REFRESH MATERIALIZED VIEW statement."""
        if not self.supports_refresh_materialized_view():
            raise UnsupportedFeatureError(self.name, "REFRESH MATERIALIZED VIEW")

        parts = ["REFRESH MATERIALIZED VIEW"]
        if expr.concurrent:
            parts.append("CONCURRENTLY")
        parts.append(self.format_identifier(expr.view_name))
        if expr.with_data is not None:
            parts.append("WITH DATA" if expr.with_data else "WITH NO DATA")
        return ' '.join(parts), ()


class TruncateMixin:
    """Mixin for TRUNCATE support."""

    def supports_truncate(self) -> bool:
        """Whether TRUNCATE is supported."""
        return True

    def supports_truncate_table_keyword(self) -> bool:
        """Whether TABLE keyword is supported."""
        return True

    def supports_truncate_restart_identity(self) -> bool:
        """Whether RESTART IDENTITY is supported."""
        return False

    def supports_truncate_cascade(self) -> bool:
        """Whether CASCADE option is supported."""
        return False

    def format_truncate_statement(
        self,
        expr: "TruncateExpression"
    ) -> Tuple[str, tuple]:
        """Format TRUNCATE statement. Override in dialect."""
        raise UnsupportedFeatureError(self.name, "TRUNCATE")


class SchemaMixin:
    """Mixin for schema DDL support."""

    def supports_create_schema(self) -> bool:
        """Whether CREATE SCHEMA is supported."""
        return False

    def supports_drop_schema(self) -> bool:
        """Whether DROP SCHEMA is supported."""
        return False

    def supports_schema_if_not_exists(self) -> bool:
        """Whether CREATE SCHEMA IF NOT EXISTS is supported."""
        return False

    def supports_schema_if_exists(self) -> bool:
        """Whether DROP SCHEMA IF EXISTS is supported."""
        return False

    def supports_schema_cascade(self) -> bool:
        """Whether DROP SCHEMA CASCADE is supported."""
        return False

    def supports_schema_authorization(self) -> bool:
        """Whether AUTHORIZATION clause is supported."""
        return False

    def format_create_schema_statement(
        self,
        expr: "CreateSchemaExpression"
    ) -> Tuple[str, tuple]:
        """Format CREATE SCHEMA statement per SQL standard."""
        parts = ["CREATE SCHEMA"]
        if expr.if_not_exists:
            parts.append("IF NOT EXISTS")
        parts.append(self.format_identifier(expr.schema_name))
        if expr.authorization:
            parts.append(f"AUTHORIZATION {self.format_identifier(expr.authorization)}")
        return ' '.join(parts), ()

    def format_drop_schema_statement(
        self,
        expr: "DropSchemaExpression"
    ) -> Tuple[str, tuple]:
        """Format DROP SCHEMA statement per SQL standard."""
        parts = ["DROP SCHEMA"]
        if expr.if_exists:
            parts.append("IF EXISTS")
        parts.append(self.format_identifier(expr.schema_name))
        if expr.cascade:
            parts.append("CASCADE")
        return ' '.join(parts), ()


class IndexMixin:
    """Mixin for index DDL support."""

    def supports_create_index(self) -> bool:
        """Whether CREATE INDEX is supported."""
        return True

    def supports_drop_index(self) -> bool:
        """Whether DROP INDEX is supported."""
        return True

    def supports_unique_index(self) -> bool:
        """Whether UNIQUE indexes are supported."""
        return True

    def supports_index_if_not_exists(self) -> bool:
        """Whether CREATE INDEX IF NOT EXISTS is supported."""
        return False

    def supports_index_if_exists(self) -> bool:
        """Whether DROP INDEX IF EXISTS is supported."""
        return False

    def supports_index_type(self) -> bool:
        """Whether index type specification is supported."""
        return False

    def supports_partial_index(self) -> bool:
        """Whether partial indexes are supported."""
        return False

    def supports_functional_index(self) -> bool:
        """Whether functional indexes are supported."""
        return False

    def supports_index_include(self) -> bool:
        """Whether INCLUDE clause is supported."""
        return False

    def supports_index_tablespace(self) -> bool:
        """Whether tablespace specification is supported."""
        return False

    def supports_concurrent_index(self) -> bool:
        """Whether CREATE INDEX CONCURRENTLY is supported."""
        return False

    def get_supported_index_types(self) -> List[str]:
        """Return list of supported index types."""
        return ['BTREE']
    
    def supports_fulltext_index(self) -> bool:
        """Whether FULLTEXT indexes are supported."""
        return False
    
    def supports_fulltext_parser(self) -> bool:
        """Whether FULLTEXT parser plugin is supported."""
        return False
    
    def supports_fulltext_boolean_mode(self) -> bool:
        """Whether BOOLEAN MODE is supported."""
        return self.supports_fulltext_index()
    
    def supports_fulltext_query_expansion(self) -> bool:
        """Whether QUERY EXPANSION is supported."""
        return self.supports_fulltext_index()
    
    def format_fulltext_match(
        self,
        columns: List[str],
        search_term: str,
        mode: Optional[str] = None
    ) -> Tuple[str, Tuple]:
        """Format MATCH ... AGAINST expression."""
        if not self.supports_fulltext_index():
            raise UnsupportedFeatureError(self.name, "FULLTEXT search")
        
        cols_str = ', '.join(self.format_identifier(c) for c in columns)
        
        if mode:
            mode_upper = mode.upper()
            if mode_upper == 'BOOLEAN':
                return f"MATCH({cols_str}) AGAINST(? IN BOOLEAN MODE)", (search_term,)
            elif mode_upper in ('QUERY EXPANSION', 'WITH QUERY EXPANSION'):
                return f"MATCH({cols_str}) AGAINST(? WITH QUERY EXPANSION)", (search_term,)
        
        # Default: NATURAL LANGUAGE MODE
        return f"MATCH({cols_str}) AGAINST(? IN NATURAL LANGUAGE MODE)", (search_term,)

    def format_create_fulltext_index_statement(
        self,
        expr: "CreateFulltextIndexExpression"
    ) -> Tuple[str, tuple]:
        """Format CREATE FULLTEXT INDEX statement from expression object."""
        if not self.supports_fulltext_index():
            raise UnsupportedFeatureError(self.name, "FULLTEXT INDEX")

        parts = ["CREATE FULLTEXT INDEX"]
        if expr.if_not_exists and self.supports_index_if_not_exists():
            parts.append("IF NOT EXISTS")
        parts.append(self.format_identifier(expr.index_name))
        parts.append("ON")
        parts.append(self.format_identifier(expr.table_name))

        cols_str = ', '.join(self.format_identifier(c) for c in expr.columns)
        parts.append(f"({cols_str})")

        if expr.parser and self.supports_fulltext_parser():
            parts.append(f"WITH PARSER {self.format_identifier(expr.parser)}")

        return ' '.join(parts), ()

    def format_drop_fulltext_index_statement(
        self,
        expr: "DropFulltextIndexExpression"
    ) -> Tuple[str, tuple]:
        """Format DROP FULLTEXT INDEX statement from expression object."""
        if not self.supports_fulltext_index():
            raise UnsupportedFeatureError(self.name, "FULLTEXT INDEX")

        parts = ["DROP INDEX"]
        if expr.if_exists and self.supports_index_if_exists():
            parts.append("IF EXISTS")
        parts.append(self.format_identifier(expr.index_name))
        parts.append("ON")
        parts.append(self.format_identifier(expr.table_name))

        return ' '.join(parts), ()

    def format_create_index_statement(
        self,
        expr: "CreateIndexExpression"
    ) -> Tuple[str, tuple]:
        """Format CREATE INDEX statement per SQL standard."""
        all_params = []
        parts = ["CREATE"]

        if expr.unique:
            parts.append("UNIQUE")
        parts.append("INDEX")
        if expr.if_not_exists:
            parts.append("IF NOT EXISTS")
        parts.append(self.format_identifier(expr.index_name))
        parts.append("ON")
        parts.append(self.format_identifier(expr.table_name))

        if expr.index_type:
            parts.append(f"USING {expr.index_type}")

        col_parts = []
        for col in expr.columns:
            if isinstance(col, ToSQLProtocol):
                col_sql, col_params = col.to_sql()
                col_parts.append(col_sql)
                all_params.extend(col_params)
            else:
                col_parts.append(self.format_identifier(str(col)))
        parts.append(f"({', '.join(col_parts)})")

        if expr.include:
            include_cols = ', '.join(self.format_identifier(c) for c in expr.include)
            parts.append(f"INCLUDE ({include_cols})")

        if expr.where:
            where_sql, where_params = expr.where.to_sql()
            parts.append(f"WHERE {where_sql}")
            all_params.extend(where_params)

        if expr.tablespace:
            parts.append(f"TABLESPACE {self.format_identifier(expr.tablespace)}")

        return ' '.join(parts), tuple(all_params)

    def format_drop_index_statement(
        self,
        expr: "DropIndexExpression"
    ) -> Tuple[str, tuple]:
        """Format DROP INDEX statement per SQL standard."""
        parts = ["DROP INDEX"]
        if expr.if_exists:
            parts.append("IF EXISTS")
        parts.append(self.format_identifier(expr.index_name))
        if expr.table_name:
            parts.append("ON")
            parts.append(self.format_identifier(expr.table_name))
        return ' '.join(parts), ()


class SequenceMixin:
    """Mixin for sequence DDL support."""

    def supports_sequence(self) -> bool:
        """Whether sequence objects are supported."""
        return False

    def supports_create_sequence(self) -> bool:
        """Whether CREATE SEQUENCE is supported."""
        return False

    def supports_drop_sequence(self) -> bool:
        """Whether DROP SEQUENCE is supported."""
        return False

    def supports_alter_sequence(self) -> bool:
        """Whether ALTER SEQUENCE is supported."""
        return False

    def supports_sequence_if_not_exists(self) -> bool:
        """Whether CREATE SEQUENCE IF NOT EXISTS is supported."""
        return False

    def supports_sequence_if_exists(self) -> bool:
        """Whether DROP SEQUENCE IF EXISTS is supported."""
        return False

    def supports_sequence_cycle(self) -> bool:
        """Whether CYCLE option is supported."""
        return False

    def supports_sequence_cache(self) -> bool:
        """Whether CACHE option is supported."""
        return False

    def supports_sequence_order(self) -> bool:
        """Whether ORDER option is supported."""
        return False

    def supports_sequence_owned_by(self) -> bool:
        """Whether OWNED BY clause is supported."""
        return False

    def format_create_sequence_statement(
        self,
        expr: "CreateSequenceExpression"
    ) -> Tuple[str, tuple]:
        """Format CREATE SEQUENCE statement per SQL standard."""
        parts = ["CREATE SEQUENCE"]
        if expr.if_not_exists:
            parts.append("IF NOT EXISTS")
        parts.append(self.format_identifier(expr.sequence_name))

        if expr.start is not None:
            parts.append(f"START WITH {expr.start}")
        if expr.increment is not None:
            parts.append(f"INCREMENT BY {expr.increment}")
        if expr.minvalue is not None:
            parts.append(f"MINVALUE {expr.minvalue}")
        if expr.maxvalue is not None:
            parts.append(f"MAXVALUE {expr.maxvalue}")
        if expr.cycle:
            parts.append("CYCLE")
        else:
            parts.append("NO CYCLE")
        if expr.cache is not None:
            parts.append(f"CACHE {expr.cache}")
        if expr.order:
            parts.append("ORDER")
        if expr.owned_by:
            parts.append(f"OWNED BY {expr.owned_by}")

        return ' '.join(parts), ()

    def format_drop_sequence_statement(
        self,
        expr: "DropSequenceExpression"
    ) -> Tuple[str, tuple]:
        """Format DROP SEQUENCE statement per SQL standard."""
        parts = ["DROP SEQUENCE"]
        if expr.if_exists:
            parts.append("IF EXISTS")
        parts.append(self.format_identifier(expr.sequence_name))
        return ' '.join(parts), ()

    def format_alter_sequence_statement(
        self,
        expr: "AlterSequenceExpression"
    ) -> Tuple[str, tuple]:
        """Format ALTER SEQUENCE statement per SQL standard."""
        parts = [f"ALTER SEQUENCE {self.format_identifier(expr.sequence_name)}"]

        if expr.restart is not None:
            parts.append(f"RESTART WITH {expr.restart}")
        if expr.start is not None:
            parts.append(f"START WITH {expr.start}")
        if expr.increment is not None:
            parts.append(f"INCREMENT BY {expr.increment}")
        if expr.minvalue is not None:
            parts.append(f"MINVALUE {expr.minvalue}")
        if expr.maxvalue is not None:
            parts.append(f"MAXVALUE {expr.maxvalue}")
        if expr.cycle is not None:
            parts.append("CYCLE" if expr.cycle else "NO CYCLE")
        if expr.cache is not None:
            parts.append(f"CACHE {expr.cache}")
        if expr.order is not None:
            parts.append("ORDER" if expr.order else "NO ORDER")
        if expr.owned_by is not None:
            if expr.owned_by:
                parts.append(f"OWNED BY {expr.owned_by}")
            else:
                parts.append("OWNED BY NONE")

        return ' '.join(parts), ()


class ILIKEMixin:
    """Mixin for ILIKE (case-insensitive LIKE) support."""

    def supports_ilike(self) -> bool:
        """Whether ILIKE operator is supported."""
        return False

    def format_ilike_expression(
        self,
        column: Any,
        pattern: str,
        negate: bool = False
    ) -> Tuple[str, Tuple]:
        """
        Format ILIKE expression (case-insensitive pattern matching).
        
        Default implementation uses LOWER() function for databases without native ILIKE.
        Override this method for databases with native ILIKE support (e.g., PostgreSQL).
        """
        if not self.supports_ilike():
            from .exceptions import UnsupportedFeatureError
            raise UnsupportedFeatureError(self.name, "ILIKE")
        
        # Default implementation for databases without native ILIKE
        # Uses LOWER(column) LIKE LOWER(pattern)
        if isinstance(column, str):
            col_sql = self.format_identifier(column)
        elif isinstance(column, ToSQLProtocol):
            # Expression object
            col_sql, col_params = column.to_sql()
        else:
            # Fallback to string representation
            col_sql = str(column)
            col_params = ()

        # Use LOWER() for case-insensitive comparison
        if negate:
            sql = f"LOWER({col_sql}) NOT LIKE LOWER(?)"
        else:
            sql = f"LOWER({col_sql}) LIKE LOWER(?)"

        return sql, (pattern.lower(),)


class TriggerMixin:
    """Mixin for trigger DDL support (SQL:1999/PSM)."""

    def supports_trigger(self) -> bool:
        return False

    def supports_create_trigger(self) -> bool:
        return False

    def supports_drop_trigger(self) -> bool:
        return False

    def supports_instead_of_trigger(self) -> bool:
        return False

    def supports_statement_trigger(self) -> bool:
        return False

    def supports_trigger_referencing(self) -> bool:
        return False

    def supports_trigger_when(self) -> bool:
        return False

    def supports_trigger_if_not_exists(self) -> bool:
        return False

    def format_create_trigger_statement(
        self,
        expr: "CreateTriggerExpression"
    ) -> Tuple[str, tuple]:
        """Format CREATE TRIGGER statement per SQL:1999."""
        from .exceptions import UnsupportedFeatureError

        if not self.supports_trigger():
            raise UnsupportedFeatureError(self.name, "triggers")

        parts = ["CREATE TRIGGER"]

        if expr.if_not_exists and self.supports_trigger_if_not_exists():
            parts.append("IF NOT EXISTS")

        parts.append(self.format_identifier(expr.trigger_name))

        parts.append(expr.timing.value)

        if expr.update_columns:
            cols = ", ".join(self.format_identifier(c) for c in expr.update_columns)
            events_str = f"UPDATE OF {cols}"
        else:
            events_str = " OR ".join(e.value for e in expr.events)
        parts.append(events_str)

        parts.append("ON")
        parts.append(self.format_identifier(expr.table_name))

        if expr.referencing and self.supports_trigger_referencing():
            parts.append(expr.referencing)

        parts.append(expr.level.value)

        all_params = []
        if expr.condition and self.supports_trigger_when():
            cond_sql, cond_params = expr.condition.to_sql()
            parts.append(f"WHEN ({cond_sql})")
            all_params.extend(cond_params)

        parts.append("EXECUTE")
        parts.append(self.format_identifier(expr.function_name))

        return " ".join(parts), tuple(all_params)

    def format_drop_trigger_statement(
        self,
        expr: "DropTriggerExpression"
    ) -> Tuple[str, tuple]:
        """Format DROP TRIGGER statement per SQL:1999."""
        from .exceptions import UnsupportedFeatureError

        if not self.supports_trigger():
            raise UnsupportedFeatureError(self.name, "triggers")

        parts = ["DROP TRIGGER"]

        if expr.if_exists:
            parts.append("IF EXISTS")

        parts.append(self.format_identifier(expr.trigger_name))

        if expr.table_name:
            parts.append("ON")
            parts.append(self.format_identifier(expr.table_name))

        return " ".join(parts), ()


class FunctionMixin:
    """Mixin for function DDL support (SQL/PSM)."""

    def supports_function(self) -> bool:
        return False

    def supports_create_function(self) -> bool:
        return False

    def supports_drop_function(self) -> bool:
        return False

    def supports_function_or_replace(self) -> bool:
        return False

    def supports_function_parameters(self) -> bool:
        return False

    def format_create_function_statement(
        self,
        expr: "CreateFunctionExpression"
    ) -> Tuple[str, tuple]:
        """Format CREATE FUNCTION statement per SQL/PSM."""
        from .exceptions import UnsupportedFeatureError

        if not self.supports_function():
            raise UnsupportedFeatureError(self.name, "functions")

        parts = ["CREATE FUNCTION"]

        if expr.or_replace and self.supports_function_or_replace():
            parts.insert(1, "OR REPLACE")

        parts.append(self.format_identifier(expr.function_name))

        if expr.parameters and self.supports_function_parameters():
            param_strs = []
            for p in expr.parameters:
                name = p.get("name", "")
                param_type = p.get("type", "")
                if name and param_type:
                    param_strs.append(f"{name} {param_type}")
                elif param_type:
                    param_strs.append(param_type)
            parts.append(f"({', '.join(param_strs)})")
        else:
            parts.append("()")

        if expr.returns:
            parts.append(f"RETURNS {expr.returns}")

        if expr.language:
            parts.append(f"LANGUAGE {expr.language}")

        if expr.body:
            parts.append("AS")
            parts.append(f"$${expr.body}$$")

        return " ".join(parts), ()

    def format_drop_function_statement(
        self,
        expr: "DropFunctionExpression"
    ) -> Tuple[str, tuple]:
        """Format DROP FUNCTION statement per SQL/PSM."""
        from .exceptions import UnsupportedFeatureError

        if not self.supports_function():
            raise UnsupportedFeatureError(self.name, "functions")

        parts = ["DROP FUNCTION"]

        if expr.if_exists:
            parts.append("IF EXISTS")

        parts.append(self.format_identifier(expr.function_name))

        if expr.parameters:
            param_types = ", ".join(expr.parameters)
            parts.append(f"({param_types})")

        if expr.cascade:
            parts.append("CASCADE")

        return " ".join(parts), ()


class GeneratedColumnMixin:
    """Mixin for generated column (computed column) support."""

    def supports_generated_columns(self) -> bool:
        """Whether generated columns are supported."""
        return False

    def supports_stored_generated_columns(self) -> bool:
        """Whether STORED generated columns are supported."""
        return False

    def supports_virtual_generated_columns(self) -> bool:
        """Whether VIRTUAL generated columns are supported."""
        return False

