<?php

namespace Acms\Services\Setup\Install;

use Acms\Services\Installer\InstallerException;
use Acms\Services\Installer\InstallerOptions;
use Acms\Services\Installer\InstallThemeCatalog;
use Acms\Services\Installer\InstallThemeCollector;
use Acms\Services\Setup\Tool\ToolErrors;
use Acms\Services\Facades\Application;
use Field;
use InvalidArgumentException;

/**
 * インストール phase 5（ブログ・管理者・テーマ）。旧 setup/install.php の Install_Step_5。
 *
 * 入力検証（W8: テーマのホワイトリスト検証を含む）ののち、site-seed-cli 由来の Installer::install
 * に「ルートブログ作成・管理者ユーザー作成・テーマ（bin）シード」を一括委譲する。旧来の Setup\Import
 * ベースの多段テーマインストールは廃し、Installer/SiteSeeder のドメイン層へ寄せている。
 */
class Step5 extends AbstractStep
{
    // テーマカタログは bin/ の FS 走査＋yaml パースを伴うため、1 リクエスト内で使い回す
    // （viewData と check の両方から参照される）。ステップは毎リクエスト new されるので instance キャッシュで十分。
    private ?InstallThemeCatalog $catalog = null;

    public function template(): string
    {
        return '@setup/install/bloguser.twig';
    }

    /**
     * @return array<string, mixed>
     */
    public function viewData(Field $post): array
    {
        // テーマ選択肢（コア既定＋bin 宣言。宣言ファイルを同梱した拡張テーマも並ぶ）と、
        // 比較表（comparison を持つテーマだけを動的に列挙）。
        $catalog = $this->catalog();
        return [
            'themes' => $catalog->visibleThemes(),
            'comparisonThemes' => $catalog->comparisonThemes(),
            'comparisonAttributes' => $catalog->comparisonAttributes(),
        ];
    }

    public function check(Field $post): StepResult
    {
        $errors = $this->checkInput($post);

        if (!preg_match(REGEX_VALID_ID, $post->get('user_code'))) {
            $errors = $errors->with('user_code_invalid');
        }
        if (!preg_match(REGEX_VALID_MAIL, $post->get('user_mail'))) {
            $errors = $errors->with('user_mail_invalid');
        }
        if (!preg_match(REGEX_VALID_PASSWD, $post->get('user_pass'))) {
            $errors = $errors->with('user_pass_invalid');
        }
        // 必須チェック（空）は checkInput() が blog_name_input で担う。ここでは文字数超過のみ検査する
        // （InstallerOptions::validateBlogName と同じ規則。ACMS_POST_Blog_Insert の maxlength(255) 相当）。
        if (InstallerOptions::validateBlogName($post->get('blog_name')) !== null) {
            $errors = $errors->with('blog_name_invalid');
        }

        // W8: select_theme をテーマカタログのホワイトリストで検証する。カタログはコア既定＋
        // `bin/<name>/theme.yaml` 宣言の集合なので、宣言ファイルを同梱した拡張テーマも受理される。
        if (!$this->catalog()->isAllowed($post->get('select_theme'))) {
            $errors = $errors->with('select_theme_invalid');
        }

        return new StepResult($errors, new ToolErrors());
    }

    private function catalog(): InstallThemeCatalog
    {
        return $this->catalog ??= (new InstallThemeCollector())->catalog(SETUP_PATH . 'bin');
    }

    public function commit(Field $post): StepResult
    {
        @set_time_limit(0);

        // select_theme はテーマの bin 名そのもの。子テーマ（関連 bin）の取り込みは Installer::install が
        // listRelatedBins（setup/bin/ の FS 走査で `*@<bin名>` を検出）で解決するため、bin 名をそのまま渡す。
        $binName = $post->get('select_theme');

        try {
            $options = new InstallerOptions(
                binName: $binName,
                adminUserCode: $post->get('user_code'),
                adminUserName: $post->get('user_name'),
                adminUserPassword: $post->get('user_pass'),
                adminUserMail: $post->get('user_mail'),
                blogName: $post->get('blog_name'),
                blogCode: '',
                blogDomain: DOMAIN,
            );

            /** @var \Acms\Services\Installer\Installer $installer */
            $installer = Application::make('installer');
            $installer->install($options);
        } catch (InstallerException $e) {
            // インストール失敗（bin 不在・ドメイン未設定・スキーマ作成失敗など）。
            return new StepResult((new ToolErrors())->with('yaml_exist'), new ToolErrors());
        } catch (InvalidArgumentException $e) {
            // InstallerOptions の入力検証失敗（check() のバリデーションをすり抜けた場合の最終防波堤。
            // 通常は blog_name_invalid / user_*_invalid で check() 側が先に弾く）。
            return new StepResult((new ToolErrors())->with('setup_input_invalid'), new ToolErrors());
        }

        return StepResult::ok();
    }
}
