<?php

declare(strict_types=1);

namespace App\Framework\Database\Schema\Index\Analysis;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\Profiling\QueryLogger;
use App\Framework\Database\Schema\Index\AdvancedIndexType;
use App\Framework\Database\Schema\Index\MySQLIndex;
use App\Framework\Database\Schema\Index\PostgreSQLIndex;
use App\Framework\Database\Schema\Index\SQLiteIndex;

/**
 * Recommends indexes based on query patterns
 */
final class IndexRecommender
{
    /**
     * @var ConnectionInterface The database connection
     */
    private ConnectionInterface $connection;

    /**
     * @var QueryLogger|null The query logger for analyzing query patterns
     */
    private ?QueryLogger $queryLogger;

    /**
     * Create a new index recommender
     */
    public function __construct(ConnectionInterface $connection, ?QueryLogger $queryLogger = null)
    {
        $this->connection = $connection;
        $this->queryLogger = $queryLogger;
    }

    /**
     * Get index recommendations based on query patterns
     *
     * @param string|null $table Optional table name to filter results
     * @param int $minQueryCount Minimum number of times a query pattern must be seen to generate a recommendation
     * @param float $minExecutionTime Minimum execution time (in seconds) for a query to be considered slow
     * @return array<array<string, mixed>> Index recommendations
     * @throws DatabaseException If the database doesn't support index recommendations
     */
    public function getRecommendations(
        ?string $table = null,
        int $minQueryCount = 10,
        float $minExecutionTime = 0.1
    ): array {
        $driver = $this->getDriverName();

        // If we have a query logger, use it to analyze query patterns
        if ($this->queryLogger !== null) {
            return $this->getRecommendationsFromQueryLogger($driver, $table, $minQueryCount, $minExecutionTime);
        }

        // Otherwise, use database-specific methods
        return match($driver) {
            'mysql' => $this->getMySqlRecommendations($table),
            'pgsql' => $this->getPostgreSqlRecommendations($table),
            default => throw new DatabaseException("Index recommendations not supported for {$driver}")
        };
    }

    /**
     * Generate SQL for creating recommended indexes
     *
     * @param array<array<string, mixed>> $recommendations The index recommendations
     * @return array<string> SQL statements for creating the recommended indexes
     */
    public function generateSql(array $recommendations): array
    {
        $driver = $this->getDriverName();
        $statements = [];

        foreach ($recommendations as $recommendation) {
            if (! isset($recommendation['table'], $recommendation['columns'])) {
                continue;
            }

            $table = $recommendation['table'];
            $columns = $recommendation['columns'];
            $name = $recommendation['name'] ?? null;
            $type = $recommendation['type'] ?? 'index';
            $whereClause = $recommendation['where_clause'] ?? null;

            // Convert type string to AdvancedIndexType
            $indexType = match($type) {
                'primary' => AdvancedIndexType::PRIMARY,
                'unique' => AdvancedIndexType::UNIQUE,
                'fulltext' => AdvancedIndexType::FULLTEXT,
                'spatial' => AdvancedIndexType::SPATIAL,
                'gin' => AdvancedIndexType::GIN,
                'gist' => AdvancedIndexType::GIST,
                'brin' => AdvancedIndexType::BRIN,
                'hash' => AdvancedIndexType::HASH,
                'btree' => AdvancedIndexType::BTREE,
                'rtree' => AdvancedIndexType::RTREE,
                default => AdvancedIndexType::INDEX
            };

            // Create the appropriate index definition based on the driver
            $sql = match($driver) {
                'mysql' => $this->generateMySqlIndexSql($table, $columns, $name, $indexType, $whereClause),
                'pgsql' => $this->generatePostgreSqlIndexSql($table, $columns, $name, $indexType, $whereClause),
                'sqlite' => $this->generateSqliteIndexSql($table, $columns, $name, $indexType, $whereClause),
                default => throw new DatabaseException("Index generation not supported for {$driver}")
            };

            $statements[] = $sql;
        }

        return $statements;
    }

    /**
     * Analyze a specific query and recommend indexes
     *
     * @param string $sql The SQL query to analyze
     * @param array<mixed> $params The query parameters
     * @return array<array<string, mixed>> Index recommendations
     * @throws DatabaseException If the database doesn't support query analysis
     */
    public function analyzeQuery(string $sql, array $params = []): array
    {
        $driver = $this->getDriverName();

        return match($driver) {
            'mysql' => $this->analyzeMySqlQuery($sql, $params),
            'pgsql' => $this->analyzePostgreSqlQuery($sql, $params),
            default => throw new DatabaseException("Query analysis not supported for {$driver}")
        };
    }

    /**
     * Get recommendations from the query logger
     */
    private function getRecommendationsFromQueryLogger(
        string $driver,
        ?string $table = null,
        int $minQueryCount = 10,
        float $minExecutionTime = 0.1
    ): array {
        if ($this->queryLogger === null) {
            return [];
        }

        $recommendations = [];
        $queryStats = $this->queryLogger->getQueryStatistics();

        foreach ($queryStats as $pattern => $stats) {
            // Skip queries that don't meet the minimum criteria
            if ($stats['count'] < $minQueryCount || $stats['avg_time'] < $minExecutionTime) {
                continue;
            }

            // Skip non-SELECT queries
            if (! preg_match('/^\s*SELECT/i', $pattern)) {
                continue;
            }

            // If a table filter is specified, skip queries that don't involve that table
            if ($table !== null && ! preg_match('/\bFROM\s+`?' . preg_quote($table, '/') . '`?\b/i', $pattern)) {
                continue;
            }

            // Analyze the query pattern and extract potential index recommendations
            $queryRecommendations = $this->analyzeQueryPattern($driver, $pattern, $stats);

            foreach ($queryRecommendations as $recommendation) {
                // Skip recommendations for tables other than the specified one
                if ($table !== null && $recommendation['table'] !== $table) {
                    continue;
                }

                $recommendations[] = $recommendation;
            }
        }

        return $recommendations;
    }

    /**
     * Analyze a query pattern and extract potential index recommendations
     */
    private function analyzeQueryPattern(string $driver, string $pattern, array $stats): array
    {
        $recommendations = [];

        // Extract tables and conditions from the query pattern
        $tables = $this->extractTablesFromQuery($pattern);
        $conditions = $this->extractConditionsFromQuery($pattern);
        $joins = $this->extractJoinsFromQuery($pattern);
        $orderBy = $this->extractOrderByFromQuery($pattern);
        $groupBy = $this->extractGroupByFromQuery($pattern);

        // Generate recommendations for WHERE conditions
        foreach ($conditions as $table => $tableConditions) {
            $columns = array_keys($tableConditions);

            if (empty($columns)) {
                continue;
            }

            // Skip if the table is not in the FROM clause
            if (! in_array($table, $tables)) {
                continue;
            }

            $recommendations[] = [
                'table' => $table,
                'columns' => $columns,
                'name' => $this->generateIndexName($table, $columns),
                'type' => 'index',
                'reason' => 'WHERE clause conditions',
                'query_count' => $stats['count'],
                'avg_time' => $stats['avg_time'],
                'total_time' => $stats['total_time'],
            ];
        }

        // Generate recommendations for JOIN conditions
        foreach ($joins as $joinInfo) {
            $leftTable = $joinInfo['left_table'];
            $rightTable = $joinInfo['right_table'];
            $leftColumn = $joinInfo['left_column'];
            $rightColumn = $joinInfo['right_column'];

            // Recommend index on the right table's join column
            $recommendations[] = [
                'table' => $rightTable,
                'columns' => [$rightColumn],
                'name' => $this->generateIndexName($rightTable, [$rightColumn]),
                'type' => 'index',
                'reason' => 'JOIN condition',
                'query_count' => $stats['count'],
                'avg_time' => $stats['avg_time'],
                'total_time' => $stats['total_time'],
            ];

            // If the left table is not the main table, also recommend an index on it
            if (! in_array($leftTable, $tables)) {
                $recommendations[] = [
                    'table' => $leftTable,
                    'columns' => [$leftColumn],
                    'name' => $this->generateIndexName($leftTable, [$leftColumn]),
                    'type' => 'index',
                    'reason' => 'JOIN condition',
                    'query_count' => $stats['count'],
                    'avg_time' => $stats['avg_time'],
                    'total_time' => $stats['total_time'],
                ];
            }
        }

        // Generate recommendations for ORDER BY clauses
        foreach ($orderBy as $table => $columns) {
            if (empty($columns)) {
                continue;
            }

            $recommendations[] = [
                'table' => $table,
                'columns' => $columns,
                'name' => $this->generateIndexName($table, $columns, 'order'),
                'type' => 'index',
                'reason' => 'ORDER BY clause',
                'query_count' => $stats['count'],
                'avg_time' => $stats['avg_time'],
                'total_time' => $stats['total_time'],
            ];
        }

        // Generate recommendations for GROUP BY clauses
        foreach ($groupBy as $table => $columns) {
            if (empty($columns)) {
                continue;
            }

            $recommendations[] = [
                'table' => $table,
                'columns' => $columns,
                'name' => $this->generateIndexName($table, $columns, 'group'),
                'type' => 'index',
                'reason' => 'GROUP BY clause',
                'query_count' => $stats['count'],
                'avg_time' => $stats['avg_time'],
                'total_time' => $stats['total_time'],
            ];
        }

        return $recommendations;
    }

    /**
     * Extract tables from a query
     */
    private function extractTablesFromQuery(string $query): array
    {
        $tables = [];

        // Extract tables from FROM clause
        if (preg_match('/\bFROM\s+`?([a-zA-Z0-9_]+)`?/i', $query, $matches)) {
            $tables[] = $matches[1];
        }

        return $tables;
    }

    /**
     * Extract conditions from a query
     */
    private function extractConditionsFromQuery(string $query): array
    {
        $conditions = [];

        // Extract conditions from WHERE clause
        if (preg_match('/\bWHERE\s+(.*?)(?:\bGROUP BY|\bORDER BY|\bLIMIT|\bFOR UPDATE|\bLOCK IN|\bUNION|\bINTERSECT|\bEXCEPT|\bMINUS|\z)/is', $query, $matches)) {
            $whereClause = $matches[1];

            // Extract column names from conditions
            preg_match_all('/\b([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\s*(?:=|<>|!=|>|<|>=|<=|LIKE|IN|NOT IN|BETWEEN|IS NULL|IS NOT NULL)/i', $whereClause, $matches, PREG_SET_ORDER);

            foreach ($matches as $match) {
                $table = $match[1];
                $column = $match[2];

                if (! isset($conditions[$table])) {
                    $conditions[$table] = [];
                }

                $conditions[$table][$column] = true;
            }
        }

        return $conditions;
    }

    /**
     * Extract joins from a query
     */
    private function extractJoinsFromQuery(string $query): array
    {
        $joins = [];

        // Extract JOIN conditions
        preg_match_all('/\b(?:INNER|LEFT|RIGHT|FULL|CROSS)?\s*JOIN\s+`?([a-zA-Z0-9_]+)`?\s+(?:AS\s+`?([a-zA-Z0-9_]+)`?)?\s+ON\s+`?([a-zA-Z0-9_]+)`?\.`?([a-zA-Z0-9_]+)`?\s*=\s*`?([a-zA-Z0-9_]+)`?\.`?([a-zA-Z0-9_]+)`?/i', $query, $matches, PREG_SET_ORDER);

        foreach ($matches as $match) {
            $rightTable = $match[1];
            $rightAlias = $match[2] ?: $rightTable;
            $leftTable = $match[3];
            $leftColumn = $match[4];
            $rightTableInCondition = $match[5];
            $rightColumn = $match[6];

            // Ensure the right table in the condition matches the right table or its alias
            if ($rightTableInCondition === $rightAlias) {
                $joins[] = [
                    'left_table' => $leftTable,
                    'left_column' => $leftColumn,
                    'right_table' => $rightTable,
                    'right_column' => $rightColumn,
                ];
            }
        }

        return $joins;
    }

    /**
     * Extract ORDER BY columns from a query
     */
    private function extractOrderByFromQuery(string $query): array
    {
        $orderByColumns = [];

        // Extract ORDER BY clause
        if (preg_match('/\bORDER BY\s+(.*?)(?:\bLIMIT|\bFOR UPDATE|\bLOCK IN|\z)/is', $query, $matches)) {
            $orderByClause = $matches[1];

            // Extract column names from ORDER BY
            preg_match_all('/\b([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)(?:\s+(?:ASC|DESC))?/i', $orderByClause, $matches, PREG_SET_ORDER);

            foreach ($matches as $match) {
                $table = $match[1];
                $column = $match[2];

                if (! isset($orderByColumns[$table])) {
                    $orderByColumns[$table] = [];
                }

                $orderByColumns[$table][] = $column;
            }
        }

        return $orderByColumns;
    }

    /**
     * Extract GROUP BY columns from a query
     */
    private function extractGroupByFromQuery(string $query): array
    {
        $groupByColumns = [];

        // Extract GROUP BY clause
        if (preg_match('/\bGROUP BY\s+(.*?)(?:\bHAVING|\bORDER BY|\bLIMIT|\bFOR UPDATE|\bLOCK IN|\z)/is', $query, $matches)) {
            $groupByClause = $matches[1];

            // Extract column names from GROUP BY
            preg_match_all('/\b([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)/i', $groupByClause, $matches, PREG_SET_ORDER);

            foreach ($matches as $match) {
                $table = $match[1];
                $column = $match[2];

                if (! isset($groupByColumns[$table])) {
                    $groupByColumns[$table] = [];
                }

                $groupByColumns[$table][] = $column;
            }
        }

        return $groupByColumns;
    }

    /**
     * Generate a name for an index
     */
    private function generateIndexName(string $table, array $columns, string $suffix = ''): string
    {
        $name = 'idx_' . $table . '_' . implode('_', $columns);

        if ($suffix) {
            $name .= '_' . $suffix;
        }

        // Ensure the name is not too long
        if (strlen($name) > 63) {
            $name = substr($name, 0, 60) . '_' . substr(md5($name), 0, 3);
        }

        return $name;
    }

    /**
     * Get MySQL index recommendations
     */
    private function getMySqlRecommendations(?string $table = null): array
    {
        $whereClause = '';
        $params = [];

        if ($table !== null) {
            $whereClause = 'AND t.TABLE_NAME = ?';
            $params[] = $table;
        }

        // Use MySQL's sys schema to get index recommendations
        try {
            $sql = <<<SQL
                SELECT
                    table_schema,
                    table_name,
                    index_columns AS columns,
                    'index' AS type,
                    NULL AS name,
                    rows_examined,
                    rows_examined / executions AS avg_rows_examined,
                    executions AS query_count,
                    query_sample AS sample_query
                FROM
                    sys.schema_tables_with_full_table_scans
                WHERE
                    table_schema NOT IN ('mysql', 'performance_schema', 'information_schema', 'sys')
                    {$whereClause}
                ORDER BY
                    rows_examined DESC
            SQL;

            $recommendations = $this->connection->query($sql, $params)->fetchAll();

            // Process the recommendations to extract column names
            foreach ($recommendations as &$recommendation) {
                if (isset($recommendation['columns'])) {
                    // Extract column names from the sample query
                    $columns = $this->extractColumnsFromSampleQuery($recommendation['sample_query'], $recommendation['table_name']);
                    $recommendation['columns'] = $columns;
                    $recommendation['name'] = $this->generateIndexName($recommendation['table_name'], $columns);
                }
            }

            return $recommendations;
        } catch (\Throwable $e) {
            // Fallback if sys schema is not available
            return [
                [
                    'error' => 'Sys schema not available. Enable it to get index recommendations.',
                    'message' => $e->getMessage(),
                ],
            ];
        }
    }

    /**
     * Extract columns from a sample query
     */
    private function extractColumnsFromSampleQuery(string $query, string $tableName): array
    {
        $columns = [];

        // Extract conditions from WHERE clause
        if (preg_match('/\bWHERE\s+(.*?)(?:\bGROUP BY|\bORDER BY|\bLIMIT|\bFOR UPDATE|\bLOCK IN|\z)/is', $query, $matches)) {
            $whereClause = $matches[1];

            // Extract column names from conditions
            preg_match_all('/\b' . preg_quote($tableName, '/') . '\.([a-zA-Z0-9_]+)\s*(?:=|<>|!=|>|<|>=|<=|LIKE|IN|NOT IN|BETWEEN|IS NULL|IS NOT NULL)/i', $whereClause, $matches);

            if (isset($matches[1])) {
                $columns = array_unique($matches[1]);
            }
        }

        // If no columns found, use a default
        if (empty($columns)) {
            $columns = ['id'];
        }

        return $columns;
    }

    /**
     * Get PostgreSQL index recommendations
     */
    private function getPostgreSqlRecommendations(?string $table = null): array
    {
        $whereClause = '';
        $params = [];

        if ($table !== null) {
            $whereClause = 'AND t.tablename = $1';
            $params[] = $table;
        }

        // Use PostgreSQL's pg_stat_statements to get index recommendations
        try {
            $sql = <<<SQL
                SELECT
                    schemaname AS table_schema,
                    t.tablename AS table_name,
                    array_agg(a.attname) AS columns,
                    'index' AS type,
                    NULL AS name,
                    s.seq_scan AS seq_scans,
                    s.seq_tup_read AS rows_examined,
                    s.seq_tup_read / GREATEST(s.seq_scan, 1) AS avg_rows_examined
                FROM
                    pg_stat_user_tables s
                JOIN
                    pg_class c ON s.relid = c.oid
                JOIN
                    pg_attribute a ON c.oid = a.attrelid
                JOIN
                    pg_stat_user_tables t ON s.relid = t.relid
                WHERE
                    s.seq_scan > 0
                    AND a.attnum > 0
                    AND NOT a.attisdropped
                    AND a.attnotnull
                    {$whereClause}
                GROUP BY
                    schemaname, t.tablename, s.seq_scan, s.seq_tup_read
                HAVING
                    COUNT(a.attname) > 0
                ORDER BY
                    s.seq_tup_read DESC
            SQL;

            $recommendations = $this->connection->query($sql, $params)->fetchAll();

            // Process the recommendations to extract column names
            foreach ($recommendations as &$recommendation) {
                if (isset($recommendation['columns'])) {
                    // Convert PostgreSQL array to PHP array
                    $columns = $this->parsePostgreSqlArray($recommendation['columns']);
                    $recommendation['columns'] = $columns;
                    $recommendation['name'] = $this->generateIndexName($recommendation['table_name'], $columns);
                }
            }

            return $recommendations;
        } catch (\Throwable $e) {
            // Fallback if pg_stat_statements is not available
            return [
                [
                    'error' => 'pg_stat_statements extension not available. Enable it to get index recommendations.',
                    'message' => $e->getMessage(),
                ],
            ];
        }
    }

    /**
     * Parse a PostgreSQL array string into a PHP array
     */
    private function parsePostgreSqlArray(string $arrayString): array
    {
        // Remove the curly braces
        $arrayString = trim($arrayString, '{}');

        // Split by comma, but respect quoted values
        $result = [];
        $current = '';
        $inQuotes = false;

        for ($i = 0; $i < strlen($arrayString); $i++) {
            $char = $arrayString[$i];

            if ($char === '"' && ($i === 0 || $arrayString[$i - 1] !== '\\')) {
                $inQuotes = ! $inQuotes;
            } elseif ($char === ',' && ! $inQuotes) {
                $result[] = trim($current);
                $current = '';
            } else {
                $current .= $char;
            }
        }

        if ($current !== '') {
            $result[] = trim($current);
        }

        // Remove quotes from values
        foreach ($result as &$value) {
            $value = trim($value, '"');
        }

        return $result;
    }

    /**
     * Analyze a MySQL query and recommend indexes
     */
    private function analyzeMySqlQuery(string $sql, array $params = []): array
    {
        try {
            // Use EXPLAIN to analyze the query
            $explainSql = 'EXPLAIN ' . $sql;
            $explainResult = $this->connection->query($explainSql, $params)->fetchAll();

            $recommendations = [];

            foreach ($explainResult as $row) {
                // Look for table scans (type = ALL) or index scans with high row counts
                if (($row['type'] === 'ALL' || ($row['type'] === 'index' && isset($row['rows']) && $row['rows'] > 1000))
                    && isset($row['table']) && ! empty($row['possible_keys']) && isset($row['key']) && $row['key'] === null) {

                    // Extract columns from the possible_keys
                    $possibleKeys = explode(',', $row['possible_keys']);
                    $columns = [];

                    foreach ($possibleKeys as $key) {
                        // Get the columns for this key
                        $keyColumns = $this->getColumnsForKey($row['table'], trim($key));
                        $columns = array_merge($columns, $keyColumns);
                    }

                    // Remove duplicates
                    $columns = array_unique($columns);

                    if (! empty($columns)) {
                        $recommendations[] = [
                            'table' => $row['table'],
                            'columns' => $columns,
                            'name' => $this->generateIndexName($row['table'], $columns),
                            'type' => 'index',
                            'reason' => 'Table scan detected',
                            'rows' => $row['rows'] ?? 0,
                        ];
                    }
                }
            }

            return $recommendations;
        } catch (\Throwable $e) {
            return [
                [
                    'error' => 'Query analysis failed',
                    'message' => $e->getMessage(),
                ],
            ];
        }
    }

    /**
     * Get columns for a key in MySQL
     */
    private function getColumnsForKey(string $table, string $key): array
    {
        try {
            $sql = <<<SQL
                SELECT
                    COLUMN_NAME
                FROM
                    information_schema.STATISTICS
                WHERE
                    TABLE_NAME = ?
                    AND INDEX_NAME = ?
                ORDER BY
                    SEQ_IN_INDEX
            SQL;

            $result = $this->connection->query($sql, [$table, $key])->fetchAll();

            $columns = [];
            foreach ($result as $row) {
                $columns[] = $row['COLUMN_NAME'];
            }

            return $columns;
        } catch (\Throwable $e) {
            return [];
        }
    }

    /**
     * Analyze a PostgreSQL query and recommend indexes
     */
    private function analyzePostgreSqlQuery(string $sql, array $params = []): array
    {
        try {
            // Use EXPLAIN to analyze the query
            $explainSql = 'EXPLAIN (FORMAT JSON) ' . $sql;
            $explainResult = $this->connection->query($explainSql, $params)->fetch();

            if (! isset($explainResult[0])) {
                return [];
            }

            $explainJson = json_decode($explainResult[0], true);

            if (! is_array($explainJson) || ! isset($explainJson[0]['Plan'])) {
                return [];
            }

            $recommendations = [];
            $this->analyzePostgreSqlPlan($explainJson[0]['Plan'], $recommendations);

            return $recommendations;
        } catch (\Throwable $e) {
            return [
                [
                    'error' => 'Query analysis failed',
                    'message' => $e->getMessage(),
                ],
            ];
        }
    }

    /**
     * Recursively analyze a PostgreSQL execution plan
     */
    private function analyzePostgreSqlPlan(array $plan, array &$recommendations): void
    {
        // Look for sequential scans on tables
        if ($plan['Node Type'] === 'Seq Scan' && isset($plan['Relation Name']) && isset($plan['Filter'])) {
            $table = $plan['Relation Name'];

            // Extract columns from the filter
            $columns = $this->extractColumnsFromPostgreSqlFilter($plan['Filter'], $table);

            if (! empty($columns)) {
                $recommendations[] = [
                    'table' => $table,
                    'columns' => $columns,
                    'name' => $this->generateIndexName($table, $columns),
                    'type' => 'index',
                    'reason' => 'Sequential scan detected',
                    'rows' => $plan['Plan Rows'] ?? 0,
                    'cost' => $plan['Total Cost'] ?? 0,
                ];
            }
        }

        // Recursively analyze child plans
        if (isset($plan['Plans']) && is_array($plan['Plans'])) {
            foreach ($plan['Plans'] as $childPlan) {
                $this->analyzePostgreSqlPlan($childPlan, $recommendations);
            }
        }
    }

    /**
     * Extract columns from a PostgreSQL filter expression
     */
    private function extractColumnsFromPostgreSqlFilter(string $filter, string $table): array
    {
        $columns = [];

        // Extract column names from the filter
        preg_match_all('/\(([a-zA-Z0-9_]+)\s+(?:[=<>!]|~~|!~~|~~\*|!~~\*|<>|!=|>=|<=|@>|<@|&&|\?\||\?&)/i', $filter, $matches);

        if (isset($matches[1])) {
            $columns = array_unique($matches[1]);
        }

        return $columns;
    }

    /**
     * Generate MySQL index SQL
     */
    private function generateMySqlIndexSql(
        string $table,
        array $columns,
        ?string $name,
        AdvancedIndexType $indexType,
        ?string $whereClause
    ): string {
        $index = MySQLIndex::create($name, $columns, $indexType);

        return $index->toSql($table);
    }

    /**
     * Generate PostgreSQL index SQL
     */
    private function generatePostgreSqlIndexSql(
        string $table,
        array $columns,
        ?string $name,
        AdvancedIndexType $indexType,
        ?string $whereClause
    ): string {
        if ($whereClause !== null) {
            $index = PostgreSQLIndex::partial($name, $columns, $whereClause, $indexType === AdvancedIndexType::UNIQUE);
        } else {
            $index = PostgreSQLIndex::create($name, $columns, $indexType);
        }

        return $index->toSql($table);
    }

    /**
     * Generate SQLite index SQL
     */
    private function generateSqliteIndexSql(
        string $table,
        array $columns,
        ?string $name,
        AdvancedIndexType $indexType,
        ?string $whereClause
    ): string {
        if ($whereClause !== null) {
            $index = SQLiteIndex::partial($name, $columns, $whereClause, $indexType === AdvancedIndexType::UNIQUE);
        } else {
            $index = SQLiteIndex::create($name, $columns, $indexType);
        }

        return $index->toSql($table);
    }

    /**
     * Get the database driver name
     */
    private function getDriverName(): string
    {
        return $this->connection->getPdo()->getAttribute(\PDO::ATTR_DRIVER_NAME);
    }
}
