<?php

declare(strict_types=1);

namespace App\Framework\Database\Schema\Index;

/**
 * MySQL-specific index functionality
 */
final class MySQLIndex
{
    /**
     * @var AdvancedIndexDefinition The underlying index definition
     */
    private AdvancedIndexDefinition $definition;

    /**
     * Create a new MySQL index
     */
    private function __construct(AdvancedIndexDefinition $definition)
    {
        $this->definition = $definition;
    }

    /**
     * Create a standard B-tree index
     *
     * @param string|null $name The name of the index
     * @param array<string> $columns The columns to index
     * @return self
     */
    public static function btree(?string $name, array $columns): self
    {
        return new self(AdvancedIndexDefinition::btree($name, $columns));
    }

    /**
     * Create a unique index
     *
     * @param string|null $name The name of the index
     * @param array<string> $columns The columns to index
     * @return self
     */
    public static function unique(?string $name, array $columns): self
    {
        return new self(AdvancedIndexDefinition::create($name, $columns, AdvancedIndexType::UNIQUE));
    }

    /**
     * Create a primary key
     *
     * @param array<string> $columns The columns to index
     * @return self
     */
    public static function primary(array $columns): self
    {
        return new self(AdvancedIndexDefinition::create('PRIMARY', $columns, AdvancedIndexType::PRIMARY));
    }

    /**
     * Create a FULLTEXT index
     *
     * @param string|null $name The name of the index
     * @param array<string> $columns The columns to index
     * @return self
     */
    public static function fulltext(?string $name, array $columns): self
    {
        return new self(AdvancedIndexDefinition::create($name, $columns, AdvancedIndexType::FULLTEXT));
    }

    /**
     * Create a SPATIAL index
     *
     * @param string|null $name The name of the index
     * @param array<string> $columns The columns to index
     * @return self
     */
    public static function spatial(?string $name, array $columns): self
    {
        return new self(AdvancedIndexDefinition::create($name, $columns, AdvancedIndexType::SPATIAL));
    }

    /**
     * Create a HASH index
     *
     * @param string|null $name The name of the index
     * @param array<string> $columns The columns to index
     * @return self
     */
    public static function hash(?string $name, array $columns): self
    {
        return new self(AdvancedIndexDefinition::hash($name, $columns));
    }

    /**
     * Create a RTREE index
     *
     * @param string|null $name The name of the index
     * @param array<string> $columns The columns to index
     * @return self
     */
    public static function rtree(?string $name, array $columns): self
    {
        return new self(AdvancedIndexDefinition::rtree($name, $columns));
    }

    /**
     * Create a functional index on expressions
     *
     * @param string|null $name The name of the index
     * @param array<string> $expressions The SQL expressions to index
     * @param AdvancedIndexType $type The type of index (default: standard B-tree)
     * @return self
     */
    public static function functional(
        ?string $name,
        array $expressions,
        AdvancedIndexType $type = AdvancedIndexType::INDEX
    ): self {
        return new self(AdvancedIndexDefinition::functional($name, $type, $expressions));
    }

    /**
     * Set the index key block size
     *
     * @param int $size The key block size in bytes
     * @return self
     */
    public function keyBlockSize(int $size): self
    {
        $this->definition->options['key_block_size'] = $size;

        return $this;
    }

    /**
     * Set the index parser
     *
     * @param string $parser The parser name (e.g., 'ngram')
     * @return self
     */
    public function parser(string $parser): self
    {
        $this->definition->options['parser'] = $parser;

        return $this;
    }

    /**
     * Set the index algorithm
     *
     * @param string $algorithm The algorithm (INPLACE, COPY, etc.)
     * @return self
     */
    public function algorithm(string $algorithm): self
    {
        $this->definition->options['algorithm'] = strtoupper($algorithm);

        return $this;
    }

    /**
     * Set the index lock option
     *
     * @param string $lock The lock option (DEFAULT, NONE, SHARED, EXCLUSIVE)
     * @return self
     */
    public function lock(string $lock): self
    {
        $this->definition->options['lock'] = strtoupper($lock);

        return $this;
    }

    /**
     * Set the index comment
     *
     * @param string $comment The comment text
     * @return self
     */
    public function comment(string $comment): self
    {
        $this->definition->options['comment'] = $comment;

        return $this;
    }

    /**
     * Set the index visibility
     *
     * @param bool $visible Whether the index is visible to the optimizer
     * @return self
     */
    public function visible(bool $visible = true): self
    {
        $this->definition->options['visible'] = $visible;

        return $this;
    }

    /**
     * Generate SQL for this index
     *
     * @param string $table The table name
     * @return string The SQL statement
     */
    public function toSql(string $table): string
    {
        $sql = $this->definition->toSql('mysql', $table);

        // Add MySQL-specific options
        $options = [];

        if (isset($this->definition->options['key_block_size'])) {
            $options[] = "KEY_BLOCK_SIZE = {$this->definition->options['key_block_size']}";
        }

        if (isset($this->definition->options['parser'])) {
            $options[] = "PARSER {$this->definition->options['parser']}";
        }

        if (isset($this->definition->options['comment'])) {
            $options[] = "COMMENT '{$this->definition->options['comment']}'";
        }

        if (isset($this->definition->options['visible'])) {
            $options[] = $this->definition->options['visible'] ? 'VISIBLE' : 'INVISIBLE';
        }

        if (! empty($options)) {
            $sql .= ' ' . implode(' ', $options);
        }

        // Add algorithm and lock options for ALTER TABLE statements
        if (strpos($sql, 'ALTER TABLE') === 0) {
            $algorithmAndLock = [];

            if (isset($this->definition->options['algorithm'])) {
                $algorithmAndLock[] = "ALGORITHM = {$this->definition->options['algorithm']}";
            }

            if (isset($this->definition->options['lock'])) {
                $algorithmAndLock[] = "LOCK = {$this->definition->options['lock']}";
            }

            if (! empty($algorithmAndLock)) {
                // Insert after the table name
                $tableNameEnd = strpos($sql, ' ', 12) + 1; // 12 = length of "ALTER TABLE "
                $sql = substr($sql, 0, $tableNameEnd) . implode(', ', $algorithmAndLock) . ' ' . substr($sql, $tableNameEnd);
            }
        }

        return $sql;
    }

    /**
     * Get the underlying index definition
     */
    public function getDefinition(): AdvancedIndexDefinition
    {
        return $this->definition;
    }
}
