<?php

namespace Acms\Services\SiteSeed;

use Acms\Contracts\ServiceProvider;
use Acms\Services\Container;
use Acms\Services\Facades\Application;
use Acms\Services\Facades\LocalStorage;

/**
 * SiteSeed サービスを DI コンテナに登録する
 *
 * Facade は作らず、呼び出し側は `Application::make('site.seed')` で取得する。
 */
class SiteSeedServiceProvider extends ServiceProvider
{
    public function register(Container $container)
    {
        $container->singleton('site.seed', function () {
            return new SiteSeeder(
                new SiteSeedPathResolver($this->defaultBinRoot()),
                SiteSeedDestination::fromScriptDir(),
            );
        });
    }

    public function init()
    {
    }

    /**
     * デフォルトの bin ディレクトリを解決する
     *
     * `{ablogcms root}` は `LIB_DIR`（`config/app.php::setPath()` が定義する、リクエストされた
     * パス起点の絶対パス。web / setup / 将来の CLI いずれでも同じ関数で定義され、docroot が
     * symlink 経由でも追従する。`UpdateServiceProvider` 等と同じ既存の使い方）の親ディレクトリを
     * 使う。旧実装は `dirname(__DIR__, 3)`（PHP ソースの実体パス、symlink 解決済み）を起点にしており、
     * docroot が symlink 経由の本番環境では実体側とリクエスト側のパスがズレ、
     * `Installer::install()` 実行時だけ「Bin directory not found」になっていた（CMS-7828）。
     *
     * 実際の探索ロジックは {@see self::resolveBinRootFromAblogcmsRoot()} に切り出してある
     * (LIB_DIR はテスト側から差し替え不可なグローバル定数のため、探索ロジック自体は任意の
     * ルートを受け取れる形にして symlink 構成を伴うテストを可能にしている)。
     */
    private function defaultBinRoot(): string
    {
        $ablogcmsRoot = rtrim(dirname(rtrim(LIB_DIR, '/')), '/');

        return self::resolveBinRootFromAblogcmsRoot($ablogcmsRoot);
    }

    /**
     * 与えられた ablogcms root 配下から setup の bin ディレクトリを解決する。
     *
     * setup ディレクトリ名は配布時は `setup` だが、インストーラの最終ステップ
     * (`Install_Step_6`) で任意の名前にリネームされる仕様がある。リネーム後でも
     * bin/yaml インポートが動くように、2 段階でディレクトリを解決する。
     *
     *   1. 自動探索 — `$ablogcmsRoot/<dir>/setup-application.php` を走査して、ヒットした
     *      ディレクトリの bin/ を採用する。setup 実行時限定の定数（旧 SETUP_PATH /
     *      SETUP_DIR）に依存しないため、Installer など setup フロー外から呼ばれる場合や、
     *      将来 CLI 経由で呼ばれるようになった場合でも同じロジックで解決できる。
     *      `$ablogcmsRoot` 自身や探索対象の各ディレクトリが symlink であっても、
     *      `LocalStorage::isFile()` / `isDirectory()` は内部で `realpath()` を通すため
     *      透過的に解決される（symlink 構成のテストは
     *      {@see \Acms\Tests\Unit\Services\SiteSeed\SiteSeedServiceProviderTest} 参照）。
     *      マーカーファイルは旧実装では存在しない `setup.php` を探していたため常に
     *      ヒットせず、実質的に 2. のフォールバックのみで動いていた（CMS-7828 で発見）。
     *      setup/{index,import,update,install,fix}.php がいずれも require する
     *      `setup-application.php` に修正した。
     *   2. フォールバック — リネーム前の標準パス `setup/bin/`。
     *
     * 前提: 配布物の中で `setup-application.php` を直下に持つディレクトリは setup 専用 (リネーム
     * 後を含む) のみ、という規約に依存している。
     */
    public static function resolveBinRootFromAblogcmsRoot(string $ablogcmsRoot): string
    {
        $ablogcmsRoot = rtrim($ablogcmsRoot, '/');

        // 配布物直下の各ディレクトリを走査し、直下に setup-application.php を持つもの（＝リネーム後を含む
        // setup ディレクトリ）の bin/ を採用する。
        foreach (LocalStorage::getDirectoryList($ablogcmsRoot) as $dir) {
            if (!LocalStorage::isFile($dir . '/setup-application.php')) {
                continue;
            }
            $candidate = $dir . '/bin/';
            if (LocalStorage::isDirectory($candidate)) {
                return $candidate;
            }
        }

        return $ablogcmsRoot . '/setup/bin/';
    }
}
