<?php
class View
{
    private static array $pushStacks = [];
    private static ?string $currentPush = null;
    private static ?View $instance = null;

    public static function getInstance(): View
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Render a view inside a layout.
     *
     * BUG 1 FIX: Pass stack content as template variables ($_styles, $_scripts)
     * so the layout can use them directly. Do NOT call getStack() in the layout
     * because stacks are cleared before layout rendering begins.
     */
    public function render(string $viewPath, array $data = [], string $layout = 'app', ?string $moduleName = null): void
    {
        $viewFile = $this->resolveViewFile($viewPath, $moduleName);
        if (!$viewFile || !file_exists($viewFile)) {
            throw new \RuntimeException("View not found: {$viewPath} (resolved: {$viewFile})");
        }

        // Reset stacks before rendering view — ensures clean state
        self::$pushStacks = [];

        // Render the view content — push/endPush calls happen here
        $content = $this->renderFile($viewFile, $data);

        // Capture stacks BEFORE clearing
        $stylesContent = self::getStack('styles');
        $scriptsContent = self::getStack('scripts');

        // Clear stacks for next render cycle
        self::$pushStacks = [];

        // Inject into layout data
        $data['content'] = $content;
        $data['_styles'] = $stylesContent;
        $data['_scripts'] = $scriptsContent;

        // Render layout wrapping the content
        $layoutFile = ROOT_PATH . '/views/layouts/' . $layout . '.php';
        if (!file_exists($layoutFile)) {
            // No layout — output raw content
            echo $content;
            return;
        }

        echo $this->renderFile($layoutFile, $data);
    }

    /**
     * Render a partial view (no layout wrapping).
     * Returns rendered HTML string.
     *
     * Resolves from core views directory. For module partials,
     * use renderModulePartial() or PHP include within module views.
     */
    public function partial(string $path, array $data = []): string
    {
        $file = ROOT_PATH . '/views/' . $path . '.php';
        if (!file_exists($file)) {
            return '';
        }
        return $this->renderFile($file, $data);
    }

    /**
     * Render a module's partial view.
     */
    public function modulePartial(string $module, string $path, array $data = []): string
    {
        $file = ROOT_PATH . '/modules/' . $module . '/views/' . $path . '.php';
        if (!file_exists($file)) {
            return '';
        }
        return $this->renderFile($file, $data);
    }

    /**
     * Start pushing content to a named stack.
     * Used in view files: <?php $this->push('styles') ?>
     */
    public function push(string $name): void
    {
        self::$currentPush = $name;
        ob_start();
    }

    /**
     * End push to named stack.
     * Used in view files: <?php $this->endPush() ?>
     */
    public function endPush(): void
    {
        if (self::$currentPush !== null) {
            $content = ob_get_clean();
            if (!isset(self::$pushStacks[self::$currentPush])) {
                self::$pushStacks[self::$currentPush] = '';
            }
            self::$pushStacks[self::$currentPush] .= $content;
            self::$currentPush = null;
        }
    }

    /**
     * Get content from a named stack.
     */
    public static function getStack(string $name): string
    {
        return self::$pushStacks[$name] ?? '';
    }

    private function resolveViewFile(string $viewPath, ?string $moduleName): ?string
    {
        if ($moduleName) {
            return ROOT_PATH . '/modules/' . $moduleName . '/views/' . $viewPath . '.php';
        }
        return ROOT_PATH . '/views/' . $viewPath . '.php';
    }

    /**
     * Render a PHP file with extracted data.
     * $this is available in the included file and refers to this View instance.
     */
    private function renderFile(string $__file, array $__data): string
    {
        extract($__data);
        $__view = $this;
        ob_start();
        try {
            include $__file;
        } catch (\Throwable $e) {
            ob_end_clean();
            throw $e;
        }
        return ob_get_clean();
    }
}