<?php

namespace Acms\Services\Common;

use Acms\Services\Cache\PageCacheWriter;
use Acms\Services\Crypto\Encrypter;
use Acms\Services\Crypto\RandomGenerator;
use Acms\Services\Crypto\SaltRepository;
use Acms\Services\Facades\Application;
use Acms\Services\Facades\Fulltext;
use Acms\Services\Facades\Response;
use Acms\Services\Field\FieldRepository;
use Acms\Services\Mailer\ConfigResolver;
use Acms\Services\Mailer\TemplateRenderer;
use Acms\Services\Markdown\Contracts\Converter;
use Acms\Services\Response\Emitter;
use Acms\Services\Response\FileDownloader;
use Acms\Services\Response\HeaderBuilder;
use Acms\Services\Security\CsrfTokenManager;
use Acms\Services\Security\PostRateLimiter;
use Acms\Services\Storage\AssetSynchronizer;
use Acms\Services\Storage\Contracts\Filesystem;
use Acms\Services\Tag\TagParser;
use Acms\Services\Template\ThemeInheritanceResolver;
use Acms\Services\Url\DeliveryUrlRewriter;
use Acms\Services\Url\ManagedDomainRegistry;
use Acms\Services\Url\UriObjectBuilder;
use Acms\Services\Url\UrlResolver;
use Acms\Services\Validator\FileUploadValidator;
use Acms\Services\View\BuildContext;
use Acms\Services\View\JsModulesBuilder;
use Field;

/**
 * 旧 god class（約 3,800 行）の後方互換レイヤー。
 *
 * 横断的関心事はドメイン別サービスへ移設済みで、本クラスに残るのは
 * (1) 自前ロジック（exceptionArray / fixAliasPath / camelize）と
 * (2) 各サービスへの @deprecated 委譲シムだけ。
 *
 * 移行先の対応表（@deprecated タグのサービス名に対応する実体）:
 *   url                        → Acms\Services\Url\UrlResolver
 *   url.managed-domain         → Acms\Services\Url\ManagedDomainRegistry
 *   url.delivery-rewriter      → Acms\Services\Url\DeliveryUrlRewriter
 *   crypto / crypto.salt / crypto.random
 *                              → Acms\Services\Crypto\{Encrypter, SaltRepository, RandomGenerator}
 *   security.csrf              → Acms\Services\Security\CsrfTokenManager
 *   security.post-rate-limiter → Acms\Services\Security\PostRateLimiter
 *   response                   → Acms\Services\Response\Emitter
 *   response.header-builder    → Acms\Services\Response\HeaderBuilder
 *   response.downloader        → Acms\Services\Response\FileDownloader
 *   field                      → Acms\Services\Field\FieldRepository
 *   url.uri-object             → Acms\Services\Url\UriObjectBuilder
 *   view.build-context         → Acms\Services\View\BuildContext
 *   view.js-modules            → Acms\Services\View\JsModulesBuilder
 *   mailer.template-renderer / mailer.config-resolver
 *                              → Acms\Services\Mailer\{TemplateRenderer, ConfigResolver}
 *   tag.parser                 → Acms\Services\Tag\TagParser
 *   template.theme-inheritance → Acms\Services\Template\ThemeInheritanceResolver
 *   validator.file-upload      → Acms\Services\Validator\FileUploadValidator
 *   storage.asset-synchronizer → Acms\Services\Storage\AssetSynchronizer
 *   cache.page-writer          → Acms\Services\Cache\PageCacheWriter
 *   fulltext                   → Acms\Services\Fulltext\Helper
 *   markdown                   → Acms\Services\Markdown\MarkdownConverter
 *
 * Why not シムを消して行数を削る: @deprecated の委譲シムは外部プラグインの後方互換のために
 * 存在する。行数（現在 953 行）の内訳は docblock 約 520 行・コード約 360 行で、
 * そのうち自前ロジックは 50 行しかない。「god class」という元の問題は既に解消しており、
 * これ以上削るには 72 個の単純委譲を __call + 別名表へ畳む必要がある。
 * それは Common:: の呼び出し 730 箇所の解決経路を変え、IDE の定義ジャンプと
 * 静的解析の追跡性を失うため、本 PR の範囲では採らない。
 *
 * Why not すべての処理をサービスへ移設しない（exceptionArray がここに残る理由）:
 * 「呼び出し元が本クラスしかない処理」を別クラスへ切り出すと、委譲するだけの薄いクラスが
 * 増えるだけで（Speculative Generality）、しかも移設先パッケージから Common への逆方向依存を
 * 生む。移設の判断基準は「本クラス以外に直接の利用者が居るか」とし、居ないものはここに実装を置く。
 * 上表の移設先はいずれもこの基準を満たしている（例: ManagedDomainRegistry は 14 ファイル、
 * TagParser は 8 ファイルが本クラスを経由せず直接使う）。parseMarkdown も、サービス差し替え機構
 * （docs/steering/20260830-service-override-mechanism/design.md）により Unit モデル・Twig・
 * プラグインが 'markdown' サービスの直接の利用者になったため、この基準を満たして移設した。
 */
class Helper
{
    /**
     * V2モジュール／V1強制ビルドの実行時フラグを保持するコンテナ singleton を取得する。
     *
     * Why not プロパティに保持する（コンストラクタで new / make する）: BuildContext は
     * setV2Module() / setForceV1Build() で書き換わる可変状態を持ち、リクエスト内で 1 インスタンスを
     * 共有することが契約。Helper 内で new すると、@deprecated タグが移行先として案内している
     * コンテナ側 singleton（'view.build-context'）と状態が分裂し、Common::setV2Module() の書き込みが
     * 新 API の読み手に届かなくなる。コンストラクタで make() する形も同じ singleton を掴めるが、
     * ViewServiceProvider の登録より先に 'common' が解決される順序に暗黙依存するため、
     * 参照時に毎回コンテナから引く（singleton なので同一インスタンスが返る）。
     */
    private function buildContext(): BuildContext
    {
        $buildContext = Application::make('view.build-context');
        assert($buildContext instanceof BuildContext);
        return $buildContext;
    }

    /**
     * 管理ドメインレジストリのコンテナ singleton（'url.managed-domain'）を取得する。
     *
     * Why not Helper 内で new する: このレジストリは DB 由来の管理ドメイン一覧をインスタンス内で
     * メモ化する。Helper が独自インスタンスを持つと、コンテナ側を使う FieldTrait（'url'）や
     * JsModulesBuilder（'url.managed-domain'）とメモ化キャッシュが分裂し、同一リクエスト内で
     * blog / alias の DISTINCT クエリが二重に走るうえ、リクエスト途中でドメインが増えた場合に
     * 片方だけ古い一覧を保持して管理ドメイン判定が食い違う。テストでメモ化を分離したい場合は
     * `new Helper()` ではなく {@see ManagedDomainRegistry::clearCache()} を使う。
     */
    private function managedDomainRegistry(): ManagedDomainRegistry
    {
        $registry = Application::make('url.managed-domain');
        assert($registry instanceof ManagedDomainRegistry);
        return $registry;
    }

    /**
     * メディア配信先URL書き換えのコンテナ singleton（'url.delivery-rewriter'）を取得する。
     */
    private function deliveryUrlRewriter(): DeliveryUrlRewriter
    {
        $rewriter = Application::make('url.delivery-rewriter');
        assert($rewriter instanceof DeliveryUrlRewriter);
        return $rewriter;
    }

    /**
     * URL 解決のコンテナ singleton（'url'）を取得する。
     */
    private function urlResolver(): UrlResolver
    {
        $resolver = Application::make('url');
        assert($resolver instanceof UrlResolver);
        return $resolver;
    }

    /**
     * PageCacheWriter のコンテナ singleton（'cache.page-writer'）を取得する。
     */
    private function pageCacheWriter(): PageCacheWriter
    {
        $pageCacheWriter = Application::make('cache.page-writer');
        assert($pageCacheWriter instanceof PageCacheWriter);
        return $pageCacheWriter;
    }

    /**
     * Encrypter のコンテナ singleton（'crypto'）を取得する。
     */
    private function encrypter(): Encrypter
    {
        $encrypter = Application::make('crypto');
        assert($encrypter instanceof Encrypter);
        return $encrypter;
    }

    /**
     * RandomGenerator のコンテナ singleton（'crypto.random'）を取得する。
     */
    private function randomGenerator(): RandomGenerator
    {
        $randomGenerator = Application::make('crypto.random');
        assert($randomGenerator instanceof RandomGenerator);
        return $randomGenerator;
    }

    /**
     * SaltRepository のコンテナ singleton（'crypto.salt'）を取得する。
     */
    private function saltRepository(): SaltRepository
    {
        $saltRepository = Application::make('crypto.salt');
        assert($saltRepository instanceof SaltRepository);
        return $saltRepository;
    }

    /**
     * FieldRepository のコンテナ singleton（'field'）を取得する。
     */
    private function fieldRepository(): FieldRepository
    {
        $fieldRepository = Application::make('field');
        assert($fieldRepository instanceof FieldRepository);
        return $fieldRepository;
    }

    /**
     * ConfigResolver のコンテナ singleton（'mailer.config-resolver'）を取得する。
     */
    private function mailConfigResolver(): ConfigResolver
    {
        $mailConfigResolver = Application::make('mailer.config-resolver');
        assert($mailConfigResolver instanceof ConfigResolver);
        return $mailConfigResolver;
    }

    /**
     * TemplateRenderer のコンテナ singleton（'mailer.template-renderer'）を取得する。
     */
    private function mailTemplateRenderer(): TemplateRenderer
    {
        $mailTemplateRenderer = Application::make('mailer.template-renderer');
        assert($mailTemplateRenderer instanceof TemplateRenderer);
        return $mailTemplateRenderer;
    }

    /**
     * Emitter のコンテナ singleton（'response'）を取得する。
     */
    private function emitter(): Emitter
    {
        $emitter = Application::make('response');
        assert($emitter instanceof Emitter);
        return $emitter;
    }

    /**
     * HeaderBuilder のコンテナ singleton（'response.header-builder'）を取得する。
     */
    private function headerBuilder(): HeaderBuilder
    {
        $headerBuilder = Application::make('response.header-builder');
        assert($headerBuilder instanceof HeaderBuilder);
        return $headerBuilder;
    }

    /**
     * CsrfTokenManager のコンテナ singleton（'security.csrf'）を取得する。
     */
    private function csrfTokenManager(): CsrfTokenManager
    {
        $csrfTokenManager = Application::make('security.csrf');
        assert($csrfTokenManager instanceof CsrfTokenManager);
        return $csrfTokenManager;
    }

    /**
     * PostRateLimiter のコンテナ singleton（'security.post-rate-limiter'）を取得する。
     */
    private function postRateLimiter(): PostRateLimiter
    {
        $postRateLimiter = Application::make('security.post-rate-limiter');
        assert($postRateLimiter instanceof PostRateLimiter);
        return $postRateLimiter;
    }

    /**
     * AssetSynchronizer のコンテナ singleton（'storage.asset-synchronizer'）を取得する。
     */
    private function assetSynchronizer(): AssetSynchronizer
    {
        $assetSynchronizer = Application::make('storage.asset-synchronizer');
        assert($assetSynchronizer instanceof AssetSynchronizer);
        return $assetSynchronizer;
    }

    /**
     * TagParser のコンテナ singleton（'tag.parser'）を取得する。
     */
    private function tagParser(): TagParser
    {
        $tagParser = Application::make('tag.parser');
        assert($tagParser instanceof TagParser);
        return $tagParser;
    }

    /**
     * ThemeInheritanceResolver のコンテナ singleton（'template.theme-inheritance'）を取得する。
     */
    private function themeInheritanceResolver(): ThemeInheritanceResolver
    {
        $themeInheritanceResolver = Application::make('template.theme-inheritance');
        assert($themeInheritanceResolver instanceof ThemeInheritanceResolver);
        return $themeInheritanceResolver;
    }

    /**
     * UriObjectBuilder のコンテナ singleton（'url.uri-object'）を取得する。
     */
    private function uriObjectBuilder(): UriObjectBuilder
    {
        $uriObjectBuilder = Application::make('url.uri-object');
        assert($uriObjectBuilder instanceof UriObjectBuilder);
        return $uriObjectBuilder;
    }

    /**
     * FileUploadValidator のコンテナ singleton（'validator.file-upload'）を取得する。
     */
    private function fileUploadValidator(): FileUploadValidator
    {
        $fileUploadValidator = Application::make('validator.file-upload');
        assert($fileUploadValidator instanceof FileUploadValidator);
        return $fileUploadValidator;
    }

    /**
     * JsModulesBuilder のコンテナ singleton（'view.js-modules'）を取得する。
     */
    private function jsModulesBuilder(): JsModulesBuilder
    {
        $jsModulesBuilder = Application::make('view.js-modules');
        assert($jsModulesBuilder instanceof JsModulesBuilder);
        return $jsModulesBuilder;
    }

    /**
     * V2モジュールとして実行中か判定
     * @return boolean
     * @deprecated 'view.build-context' サービスを使用してください。
     */
    public function isV2Module(): bool
    {
        return $this->buildContext()->isV2Module();
    }

    /**
     * 強制的にV1ビルドを行うか判定
     * @return boolean
     * @deprecated 'view.build-context' サービスを使用してください。
     */
    public function isForceV1Build(): bool
    {
        return $this->buildContext()->isForceV1Build();
    }

    /**
     * V2モジュールとして実行中か設定
     * @param boolean $isV2Module
     * @return void
     * @deprecated 'view.build-context' サービスを使用してください。
     */
    public function setV2Module(bool $isV2Module): void
    {
        $this->buildContext()->setV2Module($isV2Module);
    }

    /**
     * 強制的にV1ビルドを行うか設定
     * @param boolean $isForceV1Build
     * @return void
     * @deprecated 'view.build-context' サービスを使用してください。
     */
    public function setForceV1Build(bool $isForceV1Build): void
    {
        $this->buildContext()->setForceV1Build($isForceV1Build);
    }

    /**
     * 指定されたドメインが管理ドメインかどうかを判定
     * @param string $domain チェックするドメイン
     * @param array<string> $additionalDomains 追加で許可するドメインのリスト
     * @return bool
     * @deprecated 'url.managed-domain' サービスを使用してください。
     */
    public function isManagedDomain(string $domain, array $additionalDomains = []): bool
    {
        return $this->managedDomainRegistry()->isManagedDomain($domain, $additionalDomains);
    }

    /**
     * window.postMessage の origin 検証に使う最小限のオリジン集合を、
     * scheme 付きオリジン（scheme://host[:port]）の配列として取得する
     * @return string[]
     * @deprecated 'url.managed-domain' サービスを使用してください。
     */
    public function getManagedOrigins(): array
    {
        return $this->managedDomainRegistry()->getManagedOrigins();
    }

    /**
     * 管理ドメインのリストを取得（キャッシュあり）
     *
     * Why not 削除する: 移設前は protected で、外部の拡張・テストが「DB 依存を除去するために
     * オーバーライドする」対象になっていた（旧 HelperTest と同型）。呼び出し互換のために委譲シムを残す。
     * ただしオーバーライドしても isSafeUrl() / isManagedDomain() の結果は変わらない（それらは
     * 'url.managed-domain' サービスを参照する）。差し替えたい場合は ManagedDomainRegistry の
     * サブクラスをコンテナに登録すること。
     * @param array<string> $additionalDomains 追加で許可するドメインのリスト
     * @return array<string>
     * @deprecated 'url.managed-domain' サービスを使用してください。
     */
    protected function getManagedDomains(array $additionalDomains = []): array
    {
        return $this->managedDomainRegistry()->getManagedDomains($additionalDomains);
    }

    /**
     * getManagedOrigins() の組み立てロジックを、グローバル定数（HTTP_HOST 等）に依存しない
     * 純粋関数として切り出したもの。
     *
     * Why not 削除する: 移設前は public static だったため、外部プラグインから
     * `Helper::buildPreviewAllowedOrigins(...)` で呼べていた。他メソッドと同様の @deprecated
     * 委譲シムとして残す（削除すると Call to undefined method で fatal になる）。
     * @param string $ownHost 現在のリクエストのホスト名（HTTP_HOST 相当）
     * @param string|null $targetDomain プレビュー対象ブログのドメイン（blogDomain(BID) 相当。未設定なら空文字/null）
     * @param bool $isSecure 現在のリクエストが https か（HTTPS 相当）
     * @param string $portValue ポート文字列（":8080" 形式。既定ポートなら空文字。HTTP_PORT_VALUE 相当）
     * @return string[]
     * @deprecated ManagedDomainRegistry::buildPreviewAllowedOrigins() を使用してください。
     */
    public static function buildPreviewAllowedOrigins(
        string $ownHost,
        ?string $targetDomain,
        bool $isSecure,
        string $portValue
    ): array {
        return ManagedDomainRegistry::buildPreviewAllowedOrigins($ownHost, $targetDomain, $isSecure, $portValue);
    }

    /**
     * メディアの配信先URLを書き換え
     * @param string $url
     * @return string
     * @deprecated 'url.delivery-rewriter' サービスを使用してください。
     */
    public function replaceDeliveryUrl(string $url): string
    {
        return $this->deliveryUrlRewriter()->replaceDeliveryUrl($url);
    }

    /**
     * メディアの配信先URLを書き換え（全て）
     * @param string $html
     * @return string
     * @deprecated 'url.delivery-rewriter' サービスを使用してください。
     */
    public function replaceDeliveryUrlAll(string $html): string
    {
        return $this->deliveryUrlRewriter()->replaceDeliveryUrlAll($html);
    }

    /**
     * URL を完全な absolute URL（scheme + host + path）に変換する。
     * @param string $path 入力パス
     * @param string $offset URL の前置オフセット（DIR_OFFSET 相当）
     * @param ?string $baseUrl 解決の基準とする base URL。null の場合は BASE_URL を使用
     * @deprecated 'url' サービスを使用してください。
     */
    public function toAbsoluteUrl(
        string $path,
        string $offset = '',
        ?string $baseUrl = null,
    ): string {
        return $this->urlResolver()->toAbsoluteUrl($path, $offset, $baseUrl);
    }

    /**
     * URL を root-relative URL（`/path` 形式、scheme/host を含まない）に変換する。
     * @param string $path 入力パス
     * @param string $offset URL の前置オフセット（DIR_OFFSET 相当）
     * @param ?string $baseUrl 解決の基準とする base URL。null の場合は BASE_URL を使用
     * @deprecated 'url' サービスを使用してください。
     */
    public function toRootRelativeUrl(
        string $path,
        string $offset = '',
        ?string $baseUrl = null,
    ): string {
        return $this->urlResolver()->toRootRelativeUrl($path, $offset, $baseUrl);
    }

    /**
     * V2モジュール、V2APIビルド時に、URLを絶対URLに変換
     * それ以外はそのままのURLを返す
     * @param string $path
     * @param string $offset
     * @return string
     * @deprecated 'url' サービスを使用してください。
     */
    public function resolveUrl($path, $offset = ''): string
    {
        return $this->urlResolver()->resolveUrl($path, $offset);
    }

    /**
     * 単一の相対URLを絶対URL（フルURL）に変換する
     * @param string $url
     * @param string $baseUrl
     * @return string
     * @deprecated 'url' サービスを使用してください。
     */
    public function convertRelativeUrlToAbsolute(string $url, string $baseUrl): string
    {
        return $this->urlResolver()->convertRelativeUrlToAbsolute($url, $baseUrl);
    }

    /**
     * HTML内のアセット参照（画像・動画・スタイルシート等）を絶対URLに変換する。
     * @deprecated 'url' サービスを使用してください。
     */
    public function convertAssetUrlsToAbsolute(string $html, string $baseUrl): string
    {
        return $this->urlResolver()->convertAssetUrlsToAbsolute($html, $baseUrl);
    }

    /**
     * @return string
     * @deprecated 'crypto' サービスを使用してください。
     */
    public function getEncryptIv()
    {
        return $this->encrypter()->getEncryptIv();
    }

    /**
     * @param string $string
     * @param string $iv
     * @return string
     * @deprecated 'crypto' サービスを使用してください。
     */
    public function encrypt($string, $iv)
    {
        return $this->encrypter()->encrypt($string, $iv);
    }

    /**
     * @param string $cipherText
     * @param string $iv
     * @return string
     * @throws \LengthException IVの長さが不正な場合、または暗号文の長さがブロックサイズの倍数でない場合
     * @deprecated 'crypto' サービスを使用してください。
     */
    public function decrypt($cipherText, $iv)
    {
        return $this->encrypter()->decrypt($cipherText, $iv);
    }

    /**
     * アプリ全体で使用するSaltを更新・設定
     * @return void
     * @deprecated 'crypto.salt' サービスを使用してください。
     */
    public function setAppSalt(): void
    {
        $this->saltRepository()->setAppSalt();
    }

    /**
     * 現在のソルトを取得
     * @return string
     * @deprecated 'crypto.salt' サービスを使用してください。
     */
    public function getCurrentSalt(): string
    {
        return $this->saltRepository()->getCurrentSalt();
    }

    /**
     * 現在のソルトを設定
     * @param string $salt
     * @return void
     * @deprecated 'crypto.salt' サービスを使用してください。
     */
    public function setCurrentSalt(string $salt): void
    {
        $this->saltRepository()->setCurrentSalt($salt);
    }

    /**
     * 1つ前のソルトを取得
     * @return string
     * @deprecated 'crypto.salt' サービスを使用してください。
     */
    public function getPreviousSalt(): string
    {
        return $this->saltRepository()->getPreviousSalt();
    }

    /**
     * 1つ前のソルトを設定
     * @param string $salt
     * @return void
     * @deprecated 'crypto.salt' サービスを使用してください。
     */
    public function setPreviousSalt(string $salt): void
    {
        $this->saltRepository()->setPreviousSalt($salt);
    }

    /**
     * アプリの固定ソルトを取得
     * @return string
     * @deprecated 'crypto.salt' サービスを使用してください。
     */
    public function getAppSalt(): string
    {
        return $this->saltRepository()->getAppSalt();
    }

    /**
     * マークダウン文字列を解析する
     * @param string $txt
     * @return string
     * @deprecated 'markdown' サービス（Acms\Services\Markdown\Contracts\Converter）を使用してください。
     */
    public function parseMarkdown($txt)
    {
        $converter = Application::make('markdown');
        assert($converter instanceof Converter);
        return $converter->convert($txt);
    }

    /**
     * すぐにリダイレクトし、同一プロセスのバックグラウンドで処理を実行
     * @param string $url
     * @param int $statusCode リダイレクトの HTTP ステータスコード（既定: 301）
     * @deprecated 'response' サービスを使用してください。
     */
    public function backgroundRedirect($url, $statusCode = 301)
    {
        $this->emitter()->backgroundRedirect($url, $statusCode);
    }

    /**
     * セキュリティヘッダー
     * @deprecated 'response' サービスを使用してください。
     */
    public function addSecurityHeader()
    {
        $this->emitter()->addSecurityHeader();
    }

    /**
     * キャッシュ無効で安全なレスポンスヘッダーを組み立てます。
     * @return void
     * @deprecated 'response' サービス（Acms\Services\Response\Emitter）の safeHeaders() を使用してください。
     */
    public function setSafeHeadersWithoutCache(int $code = 200, string $mime = 'text/html'): void
    {
        $this->emitter()->safeHeaders($code, $mime);
    }

    /**
     * POST リクエストのボディサイズが PHP の post_max_size を超過しているか判定する。
     * @param string $requestMethod リクエストメソッド（$_SERVER['REQUEST_METHOD']）
     * @param int|null $contentLength Content-Length（ヘッダ不在時は null）
     * @param int $postMaxBytes post_max_size をバイトに換算した値（0 以下は無制限）
     * @return bool 超過していれば true
     * @deprecated 'response.header-builder' サービスを使用してください。
     */
    public function isPostSizeExceeded(string $requestMethod, ?int $contentLength, int $postMaxBytes): bool
    {
        return $this->headerBuilder()->isPostSizeExceeded($requestMethod, $contentLength, $postMaxBytes);
    }

    /**
     * CSRFトークンを生成
     * @return string
     * @deprecated 'security.csrf' サービスを使用してください。
     */
    public function createCsrfToken(): string
    {
        return $this->csrfTokenManager()->createCsrfToken();
    }

    /**
     * CSRFトークンをFromに付与
     * @param string $tpl
     * @return string
     * @deprecated 'security.csrf' サービスを使用してください。
     */
    public function addCsrfToken($tpl)
    {
        return $this->csrfTokenManager()->addCsrfToken($tpl);
    }

    /**
     * CSRFトークンの存在チェック
     * @return boolean
     * @deprecated 'security.csrf' サービスを使用してください。
     */
    public function csrfTokenExists(): bool
    {
        return $this->csrfTokenManager()->csrfTokenExists();
    }

    /**
     * CSRFトークンのチェック
     * @param string $token
     * @return boolean
     * @deprecated 'security.csrf' サービスを使用してください。
     */
    public function checkCsrfToken(string $token): bool
    {
        return $this->csrfTokenManager()->checkCsrfToken($token);
    }

    /**
     * ToDo: リファクタリング
     * @param string $name
     * @return string
     * @deprecated 'security.csrf' サービスを使用してください。
     */
    public function getHttpHeader(string $name): string
    {
        return $this->csrfTokenManager()->getHttpHeader($name);
    }

    /**
     * 許可されたajaxアクセスか判定（どのようなtpl指定であっても許可する）
     *
     * level-0: チェックをしない
     * level-1: RefererとAjaxリクエスト判定
     * level-2: CSRFトークンチェック
     * @param int $level
     * @return bool
     * @deprecated 'security.csrf' サービスを使用してください。
     */
    public function isAuthorizedAjaxRequest(int $level = 1): bool
    {
        return $this->csrfTokenManager()->isAuthorizedAjaxRequest($level);
    }

    /**
     * 管理画面でテンプレート直で書かれているパスを、エイリアスを含んだURLに修正
     * @param string $txt
     * @return string
     */
    public function fixAliasPath($txt)
    {
        $regex  = '@' .
            '<\s*a(?:"[^"]*"|\'[^\']*\'|[^\'">])*href\s*=\s*("[^"]+"|\'[^\']+\'|[^\'"\s>]+)(?:"[^"]*"|\'[^\']*\'|[^\'">])*>|' .
            '<\s*form(?:"[^"]*"|\'[^\']*\'|[^\'">])*action\s*=\s*("[^"]+"|\'[^\']+\'|[^\'"\s>]+)(?:"[^"]*"|\'[^\']*\'|[^\'">])*>' .
            '@';
        $offset = 0;
        while (preg_match($regex, $txt, $match, PREG_OFFSET_CAPTURE, $offset)) {
            $offset = $match[0][1] + strlen($match[0][0]);
            for ($mpt = 1; $mpt <= 2; $mpt++) {
                if (!empty($match[$mpt][0])) {
                    break;
                }
            }

            $path = trim($match[$mpt][0], '\'"'); // @phpstan-ignore-line
            if (preg_match('/^(?=.*bid\/\d+\/)(?!.*aid\/\d+\/).*$/', $path, $pathMatch)) {
                $path = preg_replace('/bid\/(\d+)\//', 'bid/$1/aid/' . AID . '/', $path);
                $txt = substr_replace($txt, '"' . $path . '"', $match[$mpt][1], strlen($match[$mpt][0])); // @phpstan-ignore-line
            }
        }
        return $txt;
    }

    /**
     * extract()後の削除フィールドを取得
     * @return \Field
     * @deprecated 'field' サービスを使用してください。
     */
    public function getDeleteField()
    {
        return $this->fieldRepository()->getDeleteField();
    }

    /**
     * メールテンプレートの解決
     * @param string $path
     * @param Field $field
     * @param string|null $charset
     * @return string
     * @deprecated 'mailer.template-renderer' サービスを使用してください。
     */
    public function getMailTxt($path, $field, $charset = null)
    {
        return $this->mailTemplateRenderer()->getMailTxt($path, $field, $charset);
    }

    /**
     * 文字列直接入力 (フォームの件名・本文など運営者の管理画面入力) からメール本文を組み立てる。
     * @param string $txt
     * @param Field $field
     * @return string
     * @deprecated 'mailer.template-renderer' サービスを使用してください。
     */
    public function getMailTxtFromTxt($txt, $field)
    {
        return $this->mailTemplateRenderer()->getMailTxtFromTxt($txt, $field);
    }

    /**
     * メール設定の取得
     * @param array{
     *   smtp-host?: string,
     *   smtp-port?: string,
     *   smtp-user?: string,
     *   smtp-pass?: string,
     *   smtp-verify-peer?: string,
     *   mail_from?: string,
     *   sendmail_path?: string,
     *   additional_headers?: string,
     *   smtp-google?: string,
     *   smtp-google-user?: string
     * } $argConfig
     * @return non-empty-array<'additional_headers'|'mail_from'|'sendmail_path'|'smtp-google'|'smtp-google-user'|'smtp-host'|'smtp-pass'|'smtp-verify-peer'|'smtp-port'|'smtp-user',
     *   string
     * >
     * @deprecated 'mailer.config-resolver' サービスを使用してください。
     */
    public function mailConfig($argConfig = [])
    {
        return $this->mailConfigResolver()->mailConfig($argConfig);
    }

    /**
     * パスワードジェネレータ
     * @param int $len パスワードの長さ
     * @return string
     * @deprecated 'crypto.random' サービスを使用してください。
     */
    public function genPass(int $len): string
    {
        return $this->randomGenerator()->genPass($len);
    }

    /**
     * タグの配列化
     * @param string $string
     * @param bool $checkReserved
     * @return string[]
     * @deprecated 'tag.parser' サービスを使用してください。
     */
    public function getTagsFromString(string $string, $checkReserved = true)
    {
        return $this->tagParser()->getTagsFromString($string, $checkReserved);
    }

    /**
     * エントリーのフルテキストを取得
     *
     * Why not ロジックをここに置く: フルテキスト関連の読み書き・正規化は Services\Fulltext に集約した
     * （CMS-7772 のついでに実施したボーイスカウト対応）。本メソッドは既存の呼び出し元（Common ファサード
     * 経由）を破壊しないための後方互換シムとして残す。
     * @deprecated Fulltext::loadEntryFulltext() を直接使用してください。
     * @param int $eid
     * @return string
     */
    public function loadEntryFulltext($eid)
    {
        return Fulltext::loadEntryFulltext($eid);
    }

    /**
     * ユーザーのフルテキストを取得
     * @deprecated Fulltext::loadUserFulltext() を直接使用してください。
     * @param int $uid
     * @return string
     */
    public function loadUserFulltext($uid)
    {
        return Fulltext::loadUserFulltext($uid);
    }

    /**
     * カテゴリのフルテキストを取得
     * @deprecated Fulltext::loadCategoryFulltext() を直接使用してください。
     * @param int $cid
     * @return string
     */
    public function loadCategoryFulltext($cid)
    {
        return Fulltext::loadCategoryFulltext($cid);
    }

    /**
     * ブログのフルテキストを取得
     * @deprecated Fulltext::loadBlogFulltext() を直接使用してください。
     * @param int $bid
     * @return string
     */
    public function loadBlogFulltext($bid)
    {
        return Fulltext::loadBlogFulltext($bid);
    }

    /**
     * フルテキストの保存
     * @deprecated Fulltext::saveFulltext() を直接使用してください。
     * @param string $type フルテキストのタイプ
     * @param int $id
     * @param string|null $fulltext
     * @param int $targetBid
     * @return void
     */
    public function saveFulltext($type, $id, $fulltext = null, $targetBid = BID)
    {
        Fulltext::saveFulltext($type, $id, $fulltext, $targetBid);
    }

    /**
     * ファイルダウンロード
     * @param string $path
     * @param string $fileName
     * @param string|false $extension // 指定すると、Content-Disposition: inline になります。
     * @param boolean $remove
     * @param Filesystem $storage
     * @return never
     * @deprecated 'response.downloader' サービスを使用してください。
     */
    public function download($path, $fileName, $extension = false, $remove = false, $storage = null)
    {
        $downloader = Application::make('response.downloader');
        assert($downloader instanceof FileDownloader);
        $downloader->handle($path, $fileName, $extension, $remove, $storage);
    }

    /**
     * カスタムフィールドキャッシュの削除
     * @param 'bid'|'uid'|'cid'|'mid'|'eid'|'unit_id' $type
     * @param ($type is 'unit_id' ? string : int) $id
     * @param int|null $rvid
     * @deprecated 'field' サービスを使用してください。
     */
    public function deleteFieldCache($type, $id, $rvid = null)
    {
        $this->fieldRepository()->deleteFieldCache($type, $id, $rvid);
    }

    /**
     * @deprecated 'field' サービスを使用してください。
     */
    public function flushCache()
    {
        $this->fieldRepository()->flushCache();
    }

    /**
     * カスタムフィールドの削除
     * @param 'bid'|'uid'|'cid'|'mid'|'eid'|'unit_id' $type
     * @param ($type is 'unit_id' ? string : int) $id
     * @param int|null $rvid
     * @param int|null $blogId
     * @return void
     * @deprecated 'field' サービスを使用してください。
     */
    public function deleteField($type, $id, $rvid = null, $blogId = null)
    {
        $this->fieldRepository()->delete($type, $id, $rvid, $blogId);
    }

    /**
     * ブログID, カテゴリーID, エントリーID，ユーザーIDの
     * いずれか指定されたカスタムフィールドをFieldオブジェクトで返す
     * @param null|int $bid
     * @param null|int $uid
     * @param null|int $cid
     * @param null|int $mid
     * @param null|int $eid
     * @param null|string $unitId
     * @param null|int $rvid
     * @param bool $rewrite
     * @return Field
     * @deprecated 'field' サービスを使用してください。
     */
    public function loadField($bid = null, $uid = null, $cid = null, $mid = null, $eid = null, $unitId = null, $rvid = null, $rewrite = false)
    {
        return $this->fieldRepository()->load($bid, $uid, $cid, $mid, $eid, $unitId, $rvid, $rewrite);
    }

    /**
     * カスタムフィールドの保存
     * @param 'bid'|'uid'|'cid'|'mid'|'eid'|'unit_id' $type
     * @param ($type is 'unit_id' ? string : int) $id
     * @param Field|null $field
     * @param Field|null $deleteField
     * @param int|null $rvid
     * @param int $targetBid
     * @return bool
     * @deprecated 'field' サービスを使用してください。
     */
    public function saveField($type, $id, $field = null, $deleteField = null, $rvid = null, $targetBid = BID)
    {
        return $this->fieldRepository()->save($type, $id, $field, $deleteField, $rvid, $targetBid);
    }

    /**
     * URIオブジェクトの取得
     * @param \Field $Post
     * @return \Field
     * @deprecated 'url.uri-object' サービスを使用してください。
     */
    public function getUriObject(Field $Post)
    {
        return $this->uriObjectBuilder()->getUriObject($Post);
    }

    /**
     * POSTデータからデータの抜き出し
     *
     * image/file カスタムフィールドで旧ファイルを消す操作（削除チェック・差し替え）があった場合、
     * 物理削除は即時には行わず Field のメタ情報 'removeOld' に記録する。
     * 実際の物理削除はバリデーション通過後に呼ばれる saveField() 内で参照チェック
     * （field / field_rev の両テーブルを検索）の上で行う。
     * よって旧ファイルを残すべきかどうか（新バージョン保存・作業領域のみの保存）は
     * 呼び出し元が判定する必要はなく、参照チェックが自動的に判断する。
     * @param string $scp
     * @param \ACMS_Validator|null $V
     * @param \Field|null $deleteField
     * @return \Field_Validation
     * @deprecated 'field' サービスを使用してください。
     */
    public function extract($scp = 'field', $V = null, $deleteField = null)
    {
        return $this->fieldRepository()->extract($scp, $V, $deleteField);
    }

    /**
     * @return array
     * @deprecated 'view.js-modules' サービスを使用してください。
     */
    public function getJsModules()
    {
        return $this->jsModulesBuilder()->getJsModules();
    }

    /**
     * a-blog cms で管理しているドメインのURLかチェックする
     * @param string $url
     * @return bool
     * @deprecated 'url.managed-domain' サービスを使用してください。
     */
    public function isSafeUrl($url)
    {
        return $this->managedDomainRegistry()->isSafeUrl($url);
    }

    /**
     * @param mixed $data
     * @return never
     * @deprecated 'response' サービス（Acms\Services\Response\Emitter）の json() を使用してください。
     */
    public function responseJson($data)
    {
        // Why not $this->emitter()->json(): make() の戻りは mixed で、Emitter::json() の
        // never（送出して die）が静的解析に伝わらず「never を宣言しているのに終了しない」と判定される。
        // Response ファサードは @method static never json() を宣言しているためこちらを経由する。
        Response::json($data);
    }

    /**
     * @param string $lockKey
     * @deprecated 'security.post-rate-limiter' サービスを使用してください。
     */
    public function logLockPost($lockKey)
    {
        $this->postRateLimiter()->logLockPost($lockKey);
    }

    /**
     * @param string $lockKey
     * @param int $trialTime 試行時間
     * @param int $trialNumber 試行回数
     * @param int $lockTime ロックタイム
     * @param bool $remoteAddr 接続元IPアドレスをチェックするかどうか
     * @return bool
     * @deprecated 'security.post-rate-limiter' サービスを使用してください。
     */
    public function validateLockPost($lockKey, $trialTime = 5, $trialNumber = 5, $lockTime = 15, $remoteAddr = true)
    {
        return $this->postRateLimiter()->validateLockPost($lockKey, $trialTime, $trialNumber, $lockTime, $remoteAddr);
    }

    /**
     * @param string $str
     * @return string
     */
    public function camelize(string $str): string
    {
        return lcfirst(strtr(ucwords(strtr($str, ['_' => ' '])), [' ' => '']));
    }

    /**
     * セキュリティヘッダーを追加
     * @param bool $noCache
     * @return void
     * @deprecated 'response' サービスを使用してください。
     */
    public function clientCacheHeader(bool $noCache = false): void
    {
        $this->emitter()->clientCacheHeader($noCache);
    }

    /**
     * clientCacheHeader() が送出する Cache-Control 等のヘッダー文字列を組み立てる。
     * @param bool $noCache
     * @return string[]
     * @deprecated 'response.header-builder' サービスを使用してください。
     */
    public function resolveClientCacheHeaders(bool $noCache = false): array
    {
        return $this->headerBuilder()->resolveClientCacheHeaders($noCache);
    }

    /**
     * クライアント・中間キャッシュ向けの Cache-Control 系ヘッダーを組み立てる純粋関数。
     * @param int $cacheExpireClient
     * @param int|null $lastModifiedTime Last-Modified に使う時刻。null の場合 REQUEST_TIME を使う
     * @return string[]
     * @deprecated 'response.header-builder' サービスを使用してください。
     */
    public function buildCacheableHeaders(int $cacheExpireClient, ?int $lastModifiedTime = null): array
    {
        return $this->headerBuilder()->buildCacheableHeaders($cacheExpireClient, $lastModifiedTime);
    }

    /**
     * @param string $chid
     * @param string $contents
     * @param string $mime
     * @deprecated 'cache.page-writer' サービスを使用してください。
     */
    public function saveCache($chid, $contents, $mime)
    {
        $this->pageCacheWriter()->saveCache($chid, $contents, $mime);
    }

    /**
     * 例外情報を連想配列に変換
     *
     * Why not 非推奨にしてサービスへ移設しない: AcmsLogger（Logger ファサード）は 'acms-logger'
     * （Monolog\Logger 本体）に解決されるため、ログ用のフォーマッタをファサード経由で呼ぶ手段がない。
     * 148箇所という利用実績もあり Common::exceptionArray() が実質的な正式経路なので、移設先を
     * 別クラスに切っても Common を経由し続けることになる。委譲するだけの1メソッドクラスを挟まず
     * ここに実装を置く（他の @deprecated シムと違い、新規コードもこのまま使ってよい）。
     * @param \Throwable $th
     * @param array $add
     * @return (string|int)[]
     */
    public function exceptionArray(\Throwable $th, array $add = []): array
    {
        return array_merge([
            'message' => $th->getMessage(),
            'file' => $th->getFile(),
            'line' => $th->getLine(),
            'trace' => getExceptionTraceAsString($th),
        ], $add);
    }

    /**
     * ファイルアップロードを検証
     * @param string $name
     * @return void
     * @throws \RuntimeException
     * @deprecated 'validator.file-upload' サービスを使用してください。
     */
    public function validateFileUpload($name)
    {
        $this->fileUploadValidator()->validateFileUpload($name);
    }

    /**
     * 指定されたテーマの継承テーマ・システムテーマすべてのテーマの配列を取得
     * @param string $theme
     * @return string[]
     * @deprecated 'template.theme-inheritance' サービスを使用してください。
     */
    public function getInheritedThemes(string $theme): array
    {
        return $this->themeInheritanceResolver()->getInheritedThemes($theme);
    }

    /**
     * プライベートストレージの設定がローカルかどうか
     * @return boolean
     * @deprecated 'storage.asset-synchronizer' サービスを使用してください。
     */
    public function isLocalPrivateStorage(): bool
    {
        return $this->assetSynchronizer()->isLocalPrivateStorage();
    }

    /**
     * パブリックストレージの設定がローカルかどうか
     * @return boolean
     * @deprecated 'storage.asset-synchronizer' サービスを使用してください。
     */
    public function isLocalPublicStorage(): bool
    {
        return $this->assetSynchronizer()->isLocalPublicStorage();
    }

    /**
     * ローカルのディレクトリをS3などのリモートストレージにアップロード
     * @param string $from
     * @param string $to
     * @param boolean $isPublic
     * @return void
     * @deprecated 'storage.asset-synchronizer' サービスを使用してください。
     */
    public function uploadAssetDirectory(string $from, string $to, bool $isPublic): void
    {
        $this->assetSynchronizer()->uploadAssetDirectory($from, $to, $isPublic);
    }
}
