<?php

declare(strict_types=1);

/**
 * URLs for the admin's compiled assets (stylesheet, JS bundles, flag subset).
 *
 * WHERE THEY LIVE, AND WHY IT MATTERS
 * -----------------------------------
 * The files ship in `templates/admin/assets/`. That looks odd for static assets, and it is
 * deliberate: a core update replaces only the paths in UpdateManager::CORE_MANAGED_PATHS,
 * and it runs the UpdateManager that is ALREADY on the install — the previous version's.
 * So adding a new directory to that list does nothing for anyone updating FROM an older
 * release; the files simply never arrive. `templates/admin` has been on that list in every
 * version ever shipped, and `public/templates` is a symlink into it, so anything under
 * `templates/admin/assets/` is both delivered by any updater and served straight off disk
 * by nginx (try_files) and Apache (RewriteCond -f).
 *
 * (1.1.23 learned this the hard way: it put the stylesheet in `public/assets/admin/`, and
 * every site that updated to it got an admin with no CSS at all.)
 *
 * CACHING
 * -------
 * `/templates/admin/assets/...` has no far-future expiry in the stock server config, so it
 * is revalidated. When `public/assets/` is writable we therefore mirror the files there
 * once per version — that path DOES carry `expires 30d; Cache-Control: public, immutable`
 * — and serve the mirrored URL. If the mirror cannot be written, everything still works
 * from the canonical path; only the caching is weaker. Never a hard failure.
 */
final class AdminAssets
{
    private const SOURCE_DIR = '/templates/admin/assets';
    private const MIRROR_DIR = '/public/assets/admin';
    private const MIRROR_URL = '/assets/admin';
    private const SOURCE_URL = '/templates/admin/assets';
    private const MARKER_KEY = 'admin_assets_mirrored';

    private static ?string $base = null;

    /**
     * URL for one asset, cache-busted by the release version.
     * $file is a path relative to templates/admin/assets (e.g. 'tailwind.css',
     * 'flag-icons/flag-icons.css').
     */
    public static function url(string $file): string
    {
        return self::base() . '/' . ltrim($file, '/') . '?v=' . rawurlencode(APP_VERSION);
    }

    private static function base(): string
    {
        if (self::$base !== null) {
            return self::$base;
        }

         
         
        if (Setting::get(self::MARKER_KEY, '') === APP_VERSION
            && is_file(APP_ROOT . self::MIRROR_DIR . '/tailwind.css')) {
            return self::$base = self::MIRROR_URL;
        }

        try {
            if (self::mirror()) {
                Setting::set(self::MARKER_KEY, APP_VERSION);
                return self::$base = self::MIRROR_URL;
            }
        } catch (\Throwable $e) {
             
            error_log('AdminAssets: mirror failed, serving from ' . self::SOURCE_URL . ' — ' . $e->getMessage());
        }

        return self::$base = self::SOURCE_URL;
    }

    /**
     * Copy templates/admin/assets -> public/assets/admin. Returns false (never throws for a
     * plain permission problem) when the mirror could not be completed, so the caller falls
     * back to the canonical path instead of serving half a stylesheet.
     */
    private static function mirror(): bool
    {
        $src = APP_ROOT . self::SOURCE_DIR;
        $dst = APP_ROOT . self::MIRROR_DIR;
        if (!is_dir($src)) {
            return false;
        }
        if (!is_dir($dst) && !@mkdir($dst, 0755, true) && !is_dir($dst)) {
            return false;
        }

        $it = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($src, FilesystemIterator::SKIP_DOTS),
            RecursiveIteratorIterator::SELF_FIRST
        );
        foreach ($it as $item) {
            $rel = substr($item->getPathname(), strlen($src) + 1);
            $target = $dst . '/' . $rel;
            if ($item->isDir()) {
                if (!is_dir($target) && !@mkdir($target, 0755, true) && !is_dir($target)) {
                    return false;
                }
                continue;
            }
             
            if (is_file($target) && filesize($target) === $item->getSize() && filemtime($target) >= $item->getMTime()) {
                continue;
            }
             
             
            $tmp = $target . '.' . getmypid() . '.tmp';
            if (!@copy($item->getPathname(), $tmp)) {
                @unlink($tmp);
                return false;
            }
            @chmod($tmp, 0644);
            if (!@rename($tmp, $target)) {
                @unlink($tmp);
                return false;
            }
        }

         
        return is_file($dst . '/tailwind.css');
    }
}
