<?php

namespace Acms\Services\Setup\Install;

use Acms\Services\Setup\Http\Request;
use Acms\Services\Setup\Http\Response;
use Acms\Services\Setup\SetupSession;
use Acms\Services\Setup\Tool\Screen;
use Acms\Services\Facades\Cache;
use Field;

/**
 * インストールウィザードの Action（司令塔）。旧 setup/install.php の Install_Factory。
 *
 * ADR（Action–Domain–Responder）として `__invoke(Request): Response` で振る舞う。phase（0〜6）に対応する
 * {@see InstallStep} を解決し、check()（進行可否）→ commit()（副作用。check 通過かつ副作用リクエスト時のみ）を
 * 呼んで {@see StepResult} を得る。可変状態や step 文字列の密輸は持たず、描画は {@see InstallResponder} に委譲する。
 * 再実行ガード（W1）・CSRF（W2）・セッション開始は middleware（InstalledGuardMiddleware / CsrfMiddleware /
 * SessionMiddleware）が担う。
 */
class Factory
{
    private Field $Post;

    public function __construct(
        private readonly SetupSession $session,
        private readonly InstallResponder $responder = new InstallResponder(),
    ) {
    }

    public function __invoke(Request $request): Response
    {
        $this->Post = $request->post();

        // install はステップ状態を持たない設計のためセッションを全消去するが、CSRF トークンだけは
        // 正規フロー判定のためリセットをまたいで保持する（W1/W2 は middleware が保持トークンを参照）。
        $this->session->resetKeepingToken();

        $phase = $this->Post->get('phase', '0');
        // 副作用を伴うリクエストの判定。CsrfMiddleware の shouldEnforce（install.php で配線）と
        // 同一でなければならない（ズレると CSRF 適用範囲と commit 実行範囲が食い違う）ため、両者が
        // 参照する単一のメソッドに集約する。
        $isSideEffectRequest = self::isSideEffectRequest($this->Post);

        $step = $this->resolveStep($phase);
        if ($step === null) {
            return Response::html('Invalid step is selected. Please initialize system, and retry installation.');
        }
        if ($phase === '3') {
            $this->applyDatabaseDefaults();
        }

        // check()（進行可否＋Step_3 の DB 作成等の副作用）と commit()（永続化）は「送信」＝副作用
        // リクエストのときだけ実行する。非副作用（前ステップの『進む』で phase だけ渡って到達した初期表示）
        // では呼ばない。呼ぶと (1) 未入力段階の検証エラー（例: 「ドメイン名が入力されていません」）が出て
        // しまい、(2) Step_3 のように check() 内で DB を CREATE/ALTER する副作用が単なる画面表示で走って
        // しまう。初期表示は viewData() だけで組む。Step_1（動作環境チェック）は do=true で到達するため対象外。
        $result = StepResult::ok();
        if ($isSideEffectRequest) {
            $result = $step->check($this->Post);
            if ($result->errors->isEmpty()) {
                $result = $result->merge($step->commit($this->Post));
            }
        }

        // 完了フェーズはリダイレクト（旧 Step_6 の header()+die() を Response::redirect へ置換）。
        if ($result->redirect !== null) {
            return Response::redirect($result->redirect);
        }

        // 画面種別: 副作用でなければ入力(apply)。副作用ならエラー有無で完了(result)/エラー(error)。
        $screen = !$isSideEffectRequest
            ? Screen::Form->value
            : ($result->errors->isEmpty() ? Screen::Done->value : Screen::Invalid->value);

        $data = array_merge($step->viewData($this->Post), $result->data);
        $response = $this->responder->render(
            $step->template(),
            $screen,
            $result->errors,
            $result->notices,
            $data,
            $this->Post,
            $this->session
        );
        Cache::allFlush();

        return $response;
    }

    /**
     * 副作用（DB/ファイル書き込み・setup 撤去）を伴うリクエストか。do=true のほか、setup を
     * 撤去する完了フェーズ（6）も含める。install.php の CsrfMiddleware と Action で共用する単一判定。
     */
    public static function isSideEffectRequest(Field $post): bool
    {
        return $post->get('do') !== '' || $post->get('phase', '0') === '6';
    }

    private function resolveStep(string $phase): ?InstallStep
    {
        return match ($phase) {
            '0' => new Step0(),
            '1' => new Step1(),
            '2' => new Step2(),
            '3' => new Step3(),
            '4' => new Step4(),
            '5' => new Step5(),
            '6' => new Step6(),
            default => null,
        };
    }

    /**
     * phase 3 の DB フォームで、未入力項目に db_default.php の既定値を補う（入力の正規化）。
     * 旧 Factory が phase 3 分岐で行っていた Post への既定値注入を踏襲する。
     */
    private function applyDatabaseDefaults(): void
    {
        include SETUP_PATH . 'lib/db_default.php';

        if ($this->Post->get('db_host') === '' && isset($dbDefaultHost)) {
            $this->Post->set('db_host', $dbDefaultHost);
        }
        if ($this->Post->get('db_create') === '' && isset($dbDefaultCreate)) {
            $this->Post->set('db_create', $dbDefaultCreate);
        }
        if ($this->Post->get('db_name') === '' && isset($dbDefaultName)) {
            $this->Post->set('db_name', $dbDefaultName);
        }
        if ($this->Post->get('db_user') === '' && isset($dbDefaultUser)) {
            $this->Post->set('db_user', $dbDefaultUser);
        }
        if ($this->Post->get('db_pass') === '' && isset($dbDefaultPass)) {
            $this->Post->set('db_pass', $dbDefaultPass);
        }
        if ($this->Post->get('db_prefix') === '' && isset($dbDefaultPrefix)) {
            $this->Post->set('db_prefix', $dbDefaultPrefix);
        }
        if ($this->Post->get('db_charset') === '' && isset($dbDefaultCharset)) {
            $this->Post->set('db_charset', $dbDefaultCharset);
        }
    }
}
