<?php

declare(strict_types=1);

namespace Acms\Services\Asset;

use Acms\Services\Facades\Logger;

/**
 * リクエストスコープのJS/CSSアセットレジストリ。
 *
 * プラグイン・拡張アプリは Facade（{@see \Acms\Services\Facades\Asset}）経由でこのクラスの
 * register系メソッドを呼び、main.php は render() でハンドル一覧を依存解決済みのHTML文字列として取得する。
 *
 * 登録タイミングは呼び出し側が選ぶ。常時読み込みたいだけならプラグインの ServiceProvider::init() から
 * 呼んでよい。BID/CID/EID/VIEW 等のコンテキストに応じて出し分けたい場合は、既存の Hook（例:
 * afterBuild($res)。main.php では render() より前に発火するため間に合う）から呼べば足りるため、
 * このクラス専用の新規 Hook は設けていない（既存の差し込み口を優先する方針。@docs/foundation/functional-design.md）。
 */
class AssetManager
{
    /** @var array<string, AssetDefinition> */
    private array $definitions = [];

    /**
     * @param array{deps?: string[], position?: string, attrs?: array<string, string|bool>, version?: ?string} $options
     */
    public function script(string $handle, string $src, array $options = []): void
    {
        $this->register(AssetDefinition::script($handle, $src, $options));
    }

    /**
     * @param array{deps?: string[], position?: string, attrs?: array<string, string|bool>, version?: ?string} $options
     */
    public function style(string $handle, string $src, array $options = []): void
    {
        $this->register(AssetDefinition::style($handle, $src, $options));
    }

    /**
     * @param array{deps?: string[], position?: string, attrs?: array<string, string|bool>} $options
     */
    public function inlineScript(string $handle, string $code, array $options = []): void
    {
        $this->register(AssetDefinition::inlineScript($handle, $code, $options));
    }

    /**
     * @param array{deps?: string[], position?: string, attrs?: array<string, string|bool>} $options
     */
    public function inlineStyle(string $handle, string $code, array $options = []): void
    {
        $this->register(AssetDefinition::inlineStyle($handle, $code, $options));
    }

    /**
     * @param array{deps?: string[], position?: string} $options
     */
    public function raw(string $handle, string $html, array $options = []): void
    {
        $this->register(AssetDefinition::raw($handle, $html, $options));
    }

    public function has(string $handle): bool
    {
        return isset($this->definitions[$handle]);
    }

    public function dequeue(string $handle): void
    {
        unset($this->definitions[$handle]);
    }

    private function register(AssetDefinition $definition): void
    {
        if (isset($this->definitions[$definition->handle])) {
            $this->logNotice(
                "Asset handle '{$definition->handle}' is already registered. Ignoring duplicate registration. "
                . 'Call dequeue() first if you intend to replace it.'
            );
            return;
        }
        $this->definitions[$definition->handle] = $definition;
    }

    /**
     * 指定した position のアセットを依存解決した上でHTML文字列として返す。
     * main.php から position ごとに1回ずつ呼ばれる想定（プラグイン向けAPIではない）。
     *
     * 注意: deps は同一 position 内の解決のみを保証する。position をまたぐ deps 指定は
     * DependencyResolver 側で「不明なハンドル」警告になる（構造的に順序が決まるため誤りではないが、
     * 実務上ほぼ使われない想定であり、意図的に対応範囲外としている）。
     */
    public function render(string $position): string
    {
        $targets = array_filter(
            $this->definitions,
            static fn (AssetDefinition $definition): bool => $definition->position === $position
        );

        $result = DependencyResolver::sort($targets);
        foreach ($result['warnings'] as $warning) {
            $this->logNotice($warning);
        }

        return AssetRenderer::buildTags($result['sorted']);
    }

    protected function logNotice(string $message): void
    {
        Logger::notice($message);
    }
}
