<?php

declare(strict_types=1);

namespace App\Framework\OutputBuffer;

/**
 * Output Buffer Service
 *
 * Framework-compliant wrapper for PHP's output buffering with type safety,
 * error handling, and automatic cleanup.
 *
 * @example
 * // Capture output from a callback
 * $captured = OutputBuffer::capture(function() {
 *     echo "Hello World";
 *     return 42;
 * });
 * // $captured->content === "Hello World"
 * // $captured->result === 42
 *
 * @example
 * // Manual buffer management
 * $handle = OutputBuffer::start();
 * echo "Some output";
 * $content = $handle->clean();
 */
final class OutputBuffer
{
    /**
     * Capture output from a callback
     *
     * Automatically starts, executes callback, and cleans buffer.
     * Guaranteed cleanup even on exceptions.
     *
     * @param \Closure $callback Function to execute while capturing output
     * @param OutputBufferConfig|null $config Optional configuration
     * @return CapturedOutput The captured output and callback result
     * @throws OutputBufferException If buffer operations fail
     */
    public static function capture(
        \Closure $callback,
        ?OutputBufferConfig $config = null
    ): CapturedOutput {
        $config ??= OutputBufferConfig::default();
        $levelBefore = ob_get_level();

        $success = ob_start(
            callback: $config->getCallback(),
            chunk_size: $config->chunkSize,
            flags: $config->getFlagsBitmask()
        );

        if (!$success) {
            throw OutputBufferException::failedToCapture();
        }

        try {
            $result = $callback();
            $output = ob_get_clean();

            if ($output === false) {
                throw OutputBufferException::failedToClean($levelBefore + 1);
            }

            return new CapturedOutput(
                content: $output,
                result: $result,
                level: $levelBefore + 1
            );
        } catch (\Throwable $e) {
            // Ensure cleanup on exception
            if (ob_get_level() > $levelBefore) {
                ob_end_clean();
            }
            throw $e;
        }
    }

    /**
     * Start a new output buffer and return a handle
     *
     * Returns a BufferHandle that can be used to manipulate the buffer.
     * Manual cleanup is required - use capture() for automatic cleanup.
     *
     * @param OutputBufferConfig|null $config Optional configuration
     * @return BufferHandle Handle to the active buffer
     * @throws OutputBufferException If buffer cannot be started
     */
    public static function start(?OutputBufferConfig $config = null): BufferHandle
    {
        $config ??= OutputBufferConfig::default();

        $success = ob_start(
            callback: $config->getCallback(),
            chunk_size: $config->chunkSize,
            flags: $config->getFlagsBitmask()
        );

        if (!$success) {
            throw OutputBufferException::failedToCapture();
        }

        return new BufferHandle(
            level: ob_get_level(),
            config: $config
        );
    }

    /**
     * Get current buffer level
     */
    public static function getCurrentLevel(): int
    {
        return ob_get_level();
    }

    /**
     * Check if output buffering is active
     */
    public static function isActive(): bool
    {
        return ob_get_level() > 0;
    }

    /**
     * Clean all output buffers
     *
     * USE WITH CAUTION: This cleans ALL active buffers, not just framework ones.
     *
     * @return int Number of buffers cleaned
     */
    public static function cleanAll(): int
    {
        $count = 0;
        while (ob_get_level() > 0) {
            ob_end_clean();
            $count++;
        }
        return $count;
    }

    /**
     * Get status information about all active buffers
     *
     * @return array<int, array{level: int, type: int, status: int, name: string, del: bool}>
     */
    public static function getStatus(): array
    {
        return ob_get_status(true);
    }

    /**
     * Execute callback with output buffering temporarily disabled
     *
     * Useful for debugging or logging during buffered operations.
     *
     * @template T
     * @param \Closure(): T $callback
     * @return T
     */
    public static function withoutBuffering(\Closure $callback): mixed
    {
        $level = ob_get_level();
        $buffers = [];

        // Save and close all buffers
        while (ob_get_level() > 0) {
            $buffers[] = ob_get_clean();
        }

        try {
            return $callback();
        } finally {
            // Restore all buffers in reverse order
            foreach (array_reverse($buffers) as $content) {
                ob_start();
                echo $content;
            }
        }
    }
}
