<?php

declare(strict_types=1);

namespace App\Framework\Database\StoredProcedure;

/**
 * Defines a stored procedure or function for database management
 */
final class StoredProcedureDefinition
{
    /**
     * @var array<string, string> Parameters with their types
     */
    private array $parameters = [];

    /**
     * @var string The SQL body of the stored procedure
     */
    private string $body = '';

    /**
     * @var string|null Return type for functions (null for procedures)
     */
    private ?string $returnType = null;

    /**
     * @var bool Whether this is a function (true) or procedure (false)
     */
    private bool $isFunction = false;

    /**
     * Private constructor to enforce the use of named constructors
     */
    private function __construct(
        private readonly string $name
    ) {
    }

    /**
     * Create a new stored procedure definition
     */
    public static function createProcedure(string $name): self
    {
        $instance = new self($name);
        $instance->isFunction = false;
        $instance->returnType = null;

        return $instance;
    }

    /**
     * Create a new stored function definition
     */
    public static function createFunction(string $name): self
    {
        $instance = new self($name);
        $instance->isFunction = true;

        return $instance;
    }

    /**
     * Add a parameter to the stored procedure/function
     */
    public function withParameter(string $name, string $type): self
    {
        $this->parameters[$name] = $type;

        return $this;
    }

    /**
     * Set the SQL body of the stored procedure/function
     */
    public function withBody(string $body): self
    {
        $this->body = $body;

        return $this;
    }

    /**
     * Set the return type for a function
     * Only applicable for functions, not procedures
     */
    public function returnsType(string $type): self
    {
        if (! $this->isFunction) {
            throw new \InvalidArgumentException('Return type can only be set for functions, not procedures');
        }
        $this->returnType = $type;

        return $this;
    }

    /**
     * Get the name of the stored procedure/function
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Get the parameters of the stored procedure/function
     *
     * @return array<string, string> Parameters with their types
     */
    public function getParameters(): array
    {
        return $this->parameters;
    }

    /**
     * Get the SQL body of the stored procedure/function
     */
    public function getBody(): string
    {
        return $this->body;
    }

    /**
     * Get the return type of the function
     */
    public function getReturnType(): ?string
    {
        return $this->returnType;
    }

    /**
     * Check if this is a function
     */
    public function isFunction(): bool
    {
        return $this->isFunction;
    }

    /**
     * Generate SQL for creating the stored procedure/function
     *
     * @param string $driver The database driver name (mysql, pgsql, sqlite)
     * @return string The SQL statement to create the stored procedure/function
     */
    public function toSql(string $driver): string
    {
        return match($driver) {
            'mysql' => $this->toMySql(),
            'pgsql' => $this->toPostgreSql(),
            'sqlite' => $this->toSqlite(),
            default => throw new \InvalidArgumentException("Unsupported driver: {$driver}")
        };
    }

    /**
     * Generate MySQL SQL for creating the stored procedure/function
     */
    private function toMySql(): string
    {
        $parameterList = $this->buildMySqlParameters();

        if ($this->isFunction) {
            if ($this->returnType === null) {
                throw new \RuntimeException('Return type must be specified for functions');
            }

            return "CREATE FUNCTION {$this->name}({$parameterList}) RETURNS {$this->returnType}\n" .
                   "BEGIN\n" .
                   $this->body . "\n" .
                   "END;";
        } else {
            return "CREATE PROCEDURE {$this->name}({$parameterList})\n" .
                   "BEGIN\n" .
                   $this->body . "\n" .
                   "END;";
        }
    }

    /**
     * Generate PostgreSQL SQL for creating the stored procedure/function
     */
    private function toPostgreSql(): string
    {
        $parameterList = $this->buildPostgreSqlParameters();

        if ($this->isFunction) {
            if ($this->returnType === null) {
                throw new \RuntimeException('Return type must be specified for functions');
            }

            return "CREATE OR REPLACE FUNCTION {$this->name}({$parameterList}) RETURNS {$this->returnType} AS $$\n" .
                   "BEGIN\n" .
                   $this->body . "\n" .
                   "END;\n" .
                   "$$ LANGUAGE plpgsql;";
        } else {
            return "CREATE OR REPLACE PROCEDURE {$this->name}({$parameterList}) AS $$\n" .
                   "BEGIN\n" .
                   $this->body . "\n" .
                   "END;\n" .
                   "$$ LANGUAGE plpgsql;";
        }
    }

    /**
     * Generate SQLite SQL for creating the stored procedure/function
     *
     * Note: SQLite doesn't natively support stored procedures, so we create a function
     */
    private function toSqlite(): string
    {
        if (! $this->isFunction) {
            throw new \RuntimeException('SQLite only supports functions, not procedures');
        }

        if ($this->returnType === null) {
            throw new \RuntimeException('Return type must be specified for functions');
        }

        // SQLite has limited support for user-defined functions and they must be registered via PHP
        return "-- SQLite functions must be registered via PHP API\n" .
               "-- This is a placeholder for: {$this->name}\n" .
               "-- Parameters: " . implode(', ', array_keys($this->parameters)) . "\n" .
               "-- Returns: {$this->returnType}";
    }

    /**
     * Build MySQL parameter list
     */
    private function buildMySqlParameters(): string
    {
        $params = [];
        foreach ($this->parameters as $name => $type) {
            $params[] = "IN {$name} {$type}";
        }

        return implode(', ', $params);
    }

    /**
     * Build PostgreSQL parameter list
     */
    private function buildPostgreSqlParameters(): string
    {
        $params = [];
        foreach ($this->parameters as $name => $type) {
            $params[] = "{$name} {$type}";
        }

        return implode(', ', $params);
    }
}
