<?php

declare(strict_types=1);

namespace App\Modules\Theme;

/**
 * ThemeLoader -- Resolves and validates a theme by code, with fallback.
 *
 * Responsibilities:
 *   - Read theme.json for a requested theme code.
 *   - Validate required fields are present.
 *   - Resolve the absolute path to the entry file.
 *   - Validate the theme folder exists.
 *   - Fallback to theme-001 if the requested theme cannot be loaded.
 *   - Never crash.
 *
 * No SQL. No HTML.
 *
 * @package Inveetaire
 * @ref     RC-009
 */
class ThemeLoader
{
    /** @var string Default fallback theme code. */
    public const FALLBACK_THEME = 'theme-001';

    /** @var string Absolute path to the themes root directory. */
    private string $themesPath;

    /** @var ThemeManifest */
    private ThemeManifest $manifest;

    public function __construct()
    {
        $this->themesPath = ROOT_PATH . '/themes';
        $this->manifest   = new ThemeManifest();
    }

    /**
     * Load a theme by code, falling back to theme-001 if unavailable.
     *
     * Returns an array containing:
     *   - manifest : array  -- The parsed theme.json data.
     *   - path     : string -- Absolute path to the theme folder.
     *   - entry    : string -- Absolute path to the theme entry file.
     *
     * @param string $themeCode The requested theme code (e.g. "theme-001").
     * @return array Resolved theme data. Always returns a valid result.
     */
    public function load(string $themeCode): array
    {
        $resolved = $this->tryLoad($themeCode);

        // If primary load failed and it is not already the fallback, try fallback
        if ($resolved === null && $themeCode !== self::FALLBACK_THEME) {
            $resolved = $this->tryLoad(self::FALLBACK_THEME);
        }

        // If even the fallback fails, return an emergency stub to prevent a crash.
        if ($resolved === null) {
            return $this->emergencyStub();
        }

        return $resolved;
    }

    /**
     * Attempt to load a theme without fallback. Returns null on any failure.
     *
     * @param string $themeCode
     * @return array|null
     */
    private function tryLoad(string $themeCode): ?array
    {
        $themeDir = $this->themesPath . DIRECTORY_SEPARATOR . $themeCode;

        // 1. Validate theme folder exists
        if (!is_dir($themeDir)) {
            return null;
        }

        // 2. Parse and validate the manifest
        $manifestPath = $themeDir . DIRECTORY_SEPARATOR . 'theme.json';
        $manifest = $this->manifest->parse($manifestPath);
        if ($manifest === null) {
            return null;
        }

        // 3. Resolve and validate the entry file
        $entryFile = $themeDir . DIRECTORY_SEPARATOR . $manifest['entry'];
        if (!file_exists($entryFile)) {
            return null;
        }

        return [
            'manifest' => $manifest,
            'path'     => $themeDir,
            'entry'    => $entryFile,
        ];
    }

    /**
     * Emergency stub -- returned only when even the fallback theme is missing.
     * Prevents crashes during development. Should never reach production.
     *
     * @return array
     */
    private function emergencyStub(): array
    {
        return [
            'manifest' => [
                'code'    => self::FALLBACK_THEME,
                'name'    => 'Default',
                'version' => '0.0.0',
                'author'  => 'Inveetaire',
                'preview' => 'preview.webp',
                'entry'   => 'index.php',
            ],
            'path'  => $this->themesPath . DIRECTORY_SEPARATOR . self::FALLBACK_THEME,
            'entry' => $this->themesPath . DIRECTORY_SEPARATOR . self::FALLBACK_THEME . DIRECTORY_SEPARATOR . 'index.php',
        ];
    }
}