<?php

namespace Velis\Debugger;

use Throwable;
use Velis\Debugger\Module\AbstractDebuggerModule;
use Velis\Debugger\Module\DebuggerExceptionModule;
use Velis\Debugger\Module\DebuggerProfilerModule;
use Velis\Debugger\Renderer\RendererInterface;
use Velis\Dto\BaseDto;

/**
 * This is the utility class aimed to deliver the debugging information to the developer.
 * It runs declared modules, collecting the results and then returns them in the desired format as debug information.
 * It is also capable of rendering the debug information in the HTML format - using the "Whoops" library.
 * @author Szymon Janaczek <szymon.janaczek@velistech.com>
 */
class Debugger
{
    /**
     * If you want to add a new module, simply add it here.
     * Module should extend AbstractDebuggerModule.
     * @var class-string<AbstractDebuggerModule>[]
     */
    public static array $modules = [
        DebuggerExceptionModule::class,
        DebuggerProfilerModule::class,
    ];

    /**
     * Original throwable to debug.
     */
    private Throwable $throwable;

    /**
     * @var array<string, BaseDto>
     */
    public array $modulesOutput = [];

    private bool $isInitialized = false;

    public function __construct(
        protected RendererInterface $htmlRenderer,
        protected RendererInterface $arrayRenderer,
    ) {
    }

    /**
     * Run modules, collect their outputs and append to the $this->result array.
     */
    public function handle(Throwable $throwable): void
    {
        $this->throwable = $throwable;

        $modules = self::$modules;

        foreach ($modules as $moduleHandler) {
            $moduleName = $moduleHandler::getModuleName();
            $result = $this->runModule($moduleHandler);

            // If a module is not initialized, do not append it to the output.
            if (!is_null($result)) {
                $this->modulesOutput[$moduleName] = $result;
            }
        }

        $this->isInitialized = true;
    }

    /**
     * Return module output. If module is not initialized, return null.
     * @param class-string<AbstractDebuggerModule> $moduleHandler
     */
    private function runModule(string $moduleHandler): ?BaseDto
    {
        $moduleInstance = new $moduleHandler($this->throwable);
        $moduleInstance->handle();

        if (!$moduleInstance->isInitialized()) {
            return null;
        }

        return $moduleInstance->getResult();
    }

    public function toHtml(): string
    {
        return $this->htmlRenderer->get($this->throwable, $this->modulesOutput);
    }

    public function toArray(): array
    {
        return $this->arrayRenderer->get($this->throwable, $this->modulesOutput);
    }

    public function isInitialized(): bool
    {
        return $this->isInitialized;
    }
}
