<?php

declare(strict_types=1);

namespace BotGuard\Core;

use RuntimeException;

/**
 * Plain-PHP template renderer with layout support.
 *
 * Views live in app/Views and are addressed with dot notation (e.g.
 * 'admin.dashboard' -> app/Views/admin/dashboard.php). Data is extracted into
 * local scope; a child view may declare a layout via $this->layout(...) and
 * expose sections through section()/yield(). All dynamic output should be
 * escaped with e() in the templates themselves.
 */
final class View
{
    private string $layout = '';

    /** @var array<string, string> */
    private array $sections = [];

    /** @var array<string, mixed> Data shared with every view. */
    private array $shared = [];

    public function __construct(private string $viewPath)
    {
    }

    public function share(string $key, mixed $value): void
    {
        $this->shared[$key] = $value;
    }

    /**
     * Render a view to a string, wrapping it in its declared layout if any.
     *
     * @param array<string, mixed> $data
     */
    public function render(string $view, array $data = []): string
    {
        $this->layout = '';
        $content = $this->renderFile($this->resolve($view), array_merge($this->shared, $data));

        if ($this->layout !== '') {
            $layout = $this->layout;
            $this->layout = '';
            $this->sections['content'] = $content;

            return $this->renderFile($this->resolve($layout), array_merge($this->shared, $data));
        }

        return $content;
    }

    /**
     * Declare the layout a view should be wrapped in. Called from within a view.
     */
    public function layout(string $layout): void
    {
        $this->layout = $layout;
    }

    /**
     * Define a named section's content. Called from within a view.
     */
    public function section(string $name, string $value): void
    {
        $this->sections[$name] = $value;
    }

    /**
     * Output a previously-defined section (or the main content).
     */
    public function yield(string $name, string $default = ''): string
    {
        return $this->sections[$name] ?? $default;
    }

    /**
     * Include a partial view inline.
     *
     * @param array<string, mixed> $data
     */
    public function partial(string $view, array $data = []): string
    {
        return $this->renderFile($this->resolve($view), array_merge($this->shared, $data));
    }

    /**
     * @param array<string, mixed> $data
     */
    private function renderFile(string $file, array $data): string
    {
        if (! is_file($file)) {
            throw new RuntimeException("View file not found: {$file}");
        }

        extract($data, EXTR_SKIP);

        ob_start();
        include $file;

        return (string) ob_get_clean();
    }

    private function resolve(string $view): string
    {
        $relative = str_replace('.', DIRECTORY_SEPARATOR, $view);

        return $this->viewPath . DIRECTORY_SEPARATOR . $relative . '.php';
    }
}
