<?php

declare(strict_types=1);

namespace App\Framework\Database\StoredProcedure;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Exception\DatabaseException;
use App\Framework\Database\ResultInterface;
use App\Framework\Database\TypeConverter;

/**
 * Manages stored procedures and functions
 */
final readonly class StoredProcedureManager
{
    private StoredProcedureExecutor $executor;

    private ResultMapper $resultMapper;

    public function __construct(
        private ConnectionInterface $connection,
        ?TypeConverter $typeConverter = null
    ) {
        $this->executor = new StoredProcedureExecutor($connection);
        $this->resultMapper = new ResultMapper($typeConverter ?? new TypeConverter());
    }

    /**
     * Create a new stored procedure
     *
     * @param string $name The name of the procedure
     * @param callable(StoredProcedureDefinition): StoredProcedureDefinition $definitionCallback Callback to configure the procedure
     * @throws DatabaseException If the procedure creation fails
     */
    public function createProcedure(string $name, callable $definitionCallback): void
    {
        $definition = StoredProcedureDefinition::createProcedure($name);
        $definition = $definitionCallback($definition);

        $this->executor->create($definition);
    }

    /**
     * Create a new stored function
     *
     * @param string $name The name of the function
     * @param callable(StoredProcedureDefinition): StoredProcedureDefinition $definitionCallback Callback to configure the function
     * @throws DatabaseException If the function creation fails
     */
    public function createFunction(string $name, callable $definitionCallback): void
    {
        $definition = StoredProcedureDefinition::createFunction($name);
        $definition = $definitionCallback($definition);

        $this->executor->create($definition);
    }

    /**
     * Execute a stored procedure and map the results to objects
     *
     * @template T of object
     * @param string $procedureName The name of the procedure
     * @param array<string, mixed> $parameters The parameters for the procedure
     * @param class-string<T> $className The class to map results to
     * @return array<T> The mapped results
     * @throws DatabaseException If the procedure execution fails
     */
    public function executeProcedureAndMapToObjects(
        string $procedureName,
        array $parameters,
        string $className
    ): array {
        $result = $this->executor->executeProcedure($procedureName, $parameters);

        return $this->resultMapper->mapToObjects($result, $className);
    }

    /**
     * Execute a stored procedure and map the result to a single object
     *
     * @template T of object
     * @param string $procedureName The name of the procedure
     * @param array<string, mixed> $parameters The parameters for the procedure
     * @param class-string<T> $className The class to map the result to
     * @return T|null The mapped result or null if no result
     * @throws DatabaseException If the procedure execution fails
     */
    public function executeProcedureAndMapToObject(
        string $procedureName,
        array $parameters,
        string $className
    ): ?object {
        $result = $this->executor->executeProcedure($procedureName, $parameters);

        return $this->resultMapper->mapToObject($result, $className);
    }

    /**
     * Execute a stored procedure and return the raw result
     *
     * @param string $procedureName The name of the procedure
     * @param array<string, mixed> $parameters The parameters for the procedure
     * @return ResultInterface The raw result
     * @throws DatabaseException If the procedure execution fails
     */
    public function executeProcedure(string $procedureName, array $parameters = []): ResultInterface
    {
        return $this->executor->executeProcedure($procedureName, $parameters);
    }

    /**
     * Execute a stored procedure and map the results to associative arrays
     *
     * @param string $procedureName The name of the procedure
     * @param array<string, mixed> $parameters The parameters for the procedure
     * @return array<array<string, mixed>> The mapped results
     * @throws DatabaseException If the procedure execution fails
     */
    public function executeProcedureAndMapToArrays(string $procedureName, array $parameters = []): array
    {
        $result = $this->executor->executeProcedure($procedureName, $parameters);

        return $this->resultMapper->mapToAssociativeArrays($result);
    }

    /**
     * Execute a stored function and return the result
     *
     * @param string $functionName The name of the function
     * @param array<string, mixed> $parameters The parameters for the function
     * @return mixed The function result
     * @throws DatabaseException If the function execution fails
     */
    public function executeFunction(string $functionName, array $parameters = []): mixed
    {
        return $this->executor->executeFunction($functionName, $parameters);
    }

    /**
     * Drop a stored procedure
     *
     * @param string $procedureName The name of the procedure
     * @throws DatabaseException If the drop fails
     */
    public function dropProcedure(string $procedureName): void
    {
        $this->executor->dropProcedure($procedureName);
    }

    /**
     * Drop a stored function
     *
     * @param string $functionName The name of the function
     * @throws DatabaseException If the drop fails
     */
    public function dropFunction(string $functionName): void
    {
        $this->executor->dropFunction($functionName);
    }

    /**
     * Check if a stored procedure exists
     *
     * @param string $procedureName The name of the procedure
     * @return bool True if the procedure exists
     */
    public function procedureExists(string $procedureName): bool
    {
        return $this->executor->procedureExists($procedureName);
    }

    /**
     * Check if a stored function exists
     *
     * @param string $functionName The name of the function
     * @return bool True if the function exists
     */
    public function functionExists(string $functionName): bool
    {
        return $this->executor->functionExists($functionName);
    }

    /**
     * Get a list of all stored procedures
     *
     * @return array<string> List of procedure names
     */
    public function getAllProcedures(): array
    {
        return $this->executor->getAllProcedures();
    }

    /**
     * Get a list of all stored functions
     *
     * @return array<string> List of function names
     */
    public function getAllFunctions(): array
    {
        return $this->executor->getAllFunctions();
    }
}
