<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

namespace App\Filament\Pages;

use App\Enums\SupportCenter\HostSystemUpdateExecutionStatus;
use App\Enums\SupportCenter\HostSystemUpdateExecutionStep;
use App\Enums\SupportCenter\HostSystemUpdateRecommendedMethod;
use App\Enums\SupportCenter\HostSystemUpdateStepPhase;
use App\Exceptions\SupportCenter\HostSystemUpdateExecutionException;
use App\Exceptions\SupportCenter\HostSystemUpdateRecoveryException;
use App\Exceptions\SupportCenter\SupportCenterException;
use App\Filament\Concerns\InteractsWithDberpPermissions;
use App\Filament\Concerns\RegistersDberpNavigationWhenAccessible;
use App\Filament\Pages\Concerns\InteractsWithDberpNavigationGroupBreadcrumbs;
use App\Filament\Support\DberpFilamentInfolistIconResolver;
use App\Models\HostSystemUpdateCheck;
use App\Models\HostSystemUpdateExecution;
use App\Models\HostSystemUpdateExecutionLog;
use App\Models\User;
use App\Services\DberpPermissionService;
use App\Services\DberpSystemUpdateNoticeService;
use App\Services\SupportCenter\HostSystemUpdateBrowserFailureFinalizer;
use App\Services\SupportCenter\HostSystemUpdateCheckService;
use App\Services\SupportCenter\HostSystemUpdateEntitlementGate;
use App\Services\SupportCenter\HostSystemUpdateExecutionLogPruningService;
use App\Services\SupportCenter\HostSystemUpdateOneClickOrchestrator;
use App\Services\SupportCenter\HostSystemUpdateRollbackService;
use App\Services\SupportCenter\HostSystemUpdateStepProgress;
use App\Services\SupportCenter\HostSystemUpdateStepProgressStore;
use App\Services\SupportCenter\HostSystemUpdateStepRunner;
use App\Support\DberpMaintenanceDownExceptPaths;
use App\Support\HostSystemUpdateCenterDisplay;
use BackedEnum;
use DomainException;
use Filament\Actions\Action;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\EmbeddedSchema;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\View as SchemaView;
use Filament\Schemas\Schema;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Throwable;
use UnitEnum;

/**
 * @property-read Schema $content
 * @property-read Schema $infolist
 */
final class HostSystemUpdateCenterPage extends Page
{
    use InteractsWithDberpNavigationGroupBreadcrumbs;
    use InteractsWithDberpPermissions;
    use RegistersDberpNavigationWhenAccessible;

    protected static ?string $slug = DberpMaintenanceDownExceptPaths::UPDATE_CENTER_PAGE_SLUG;

    protected static ?int $navigationSort = 83;

    public bool $updateInProgress = false;

    public bool $updateProgressPanelVisible = false;

    public int $updateProgressPanelPollAttempts = 0;

    public ?string $updateSessionId = null;

    private const UPDATE_PROGRESS_PANEL_MAX_POLL_ATTEMPTS = 60;

    private const UPDATE_PROGRESS_WATCHDOG_SECONDS = 8;

    private const UPDATE_COMPLETED_NOTIFICATION_CACHE_TTL_SECONDS = 86400;

    public function mount(): void
    {
        app(HostSystemUpdateExecutionLogPruningService::class)->pruneDueSafely();

        if (! self::canExecuteUpdate()) {
            return;
        }

        $execution = app(HostSystemUpdateCenterDisplay::class)->latestExecution();

        if (! $execution instanceof HostSystemUpdateExecution
            || ! in_array((string) $execution->status, [
                HostSystemUpdateExecutionStatus::Pending->value,
                HostSystemUpdateExecutionStatus::Prechecking->value,
                HostSystemUpdateExecutionStatus::Ready->value,
                HostSystemUpdateExecutionStatus::Running->value,
            ], true)) {
            return;
        }

        $progress = app(HostSystemUpdateStepProgressStore::class)
            ->latestNonTerminalForExecution((string) $execution->execution_no);

        if ($progress === null) {
            return;
        }

        $this->updateInProgress = true;
        $this->updateProgressPanelVisible = true;
        $this->updateProgressPanelPollAttempts = 0;
        $this->updateSessionId = $progress->sessionId;
        $this->scheduleNextUpdateStep();
    }

    public static function getNavigationGroup(): string|UnitEnum|null
    {
        return __('dberp.nav.system');
    }

    public static function getNavigationLabel(): string
    {
        return __('dberp.labels.host_system_update_center');
    }

    public static function getNavigationIcon(): string|BackedEnum|Htmlable|null
    {
        return null;
    }

    public static function canAccess(): bool
    {
        return self::allowsPermission(DberpPermissionService::VIEW_HOST_SYSTEM_UPDATE);
    }

    public static function getNavigationBadge(): ?string
    {
        return self::resolveNavigationBadgeData()['badge'] ?? null;
    }

    /**
     * @return string|array<string>|null
     */
    public static function getNavigationBadgeColor(): string|array|null
    {
        return self::resolveNavigationBadgeData()['color'] ?? null;
    }

    public static function getNavigationBadgeTooltip(): string|Htmlable|null
    {
        return self::resolveNavigationBadgeData()['tooltip'] ?? null;
    }

    /**
     * @return array{badge: string, color: string, tooltip: string}|null
     */
    private static function resolveNavigationBadgeData(): ?array
    {
        $user = Auth::user();

        if (! $user instanceof User) {
            return null;
        }

        return app(DberpSystemUpdateNoticeService::class)->navigationBadgeDataFor($user);
    }

    public static function canCheckUpdate(): bool
    {
        return self::allowsPermission(DberpPermissionService::CHECK_HOST_SYSTEM_UPDATE);
    }

    public static function canDownloadPackage(): bool
    {
        return self::allowsPermission(DberpPermissionService::DOWNLOAD_HOST_SYSTEM_UPDATE);
    }

    public static function canExecuteUpdate(): bool
    {
        return self::allowsPermission(DberpPermissionService::EXECUTE_HOST_SYSTEM_UPDATE);
    }

    public static function canRollbackUpdate(): bool
    {
        return self::allowsPermission(DberpPermissionService::ROLLBACK_HOST_SYSTEM_UPDATE);
    }

    public static function canRunOneClickUpdate(): bool
    {
        return self::canCheckUpdate()
            && self::canDownloadPackage()
            && self::canExecuteUpdate();
    }

    public function canShowOneClickUpdateAction(): bool
    {
        if (! self::canRunOneClickUpdate()) {
            return false;
        }

        $display = app(HostSystemUpdateCenterDisplay::class);

        if (! $display->hasUpdateAvailable()) {
            return false;
        }

        $gate = app(HostSystemUpdateEntitlementGate::class);

        return $gate->canCheckUpdate() && $gate->canDownloadPackage();
    }

    public function getTitle(): string|Htmlable
    {
        return __('dberp.system_update_center.page_title');
    }

    public function getSubheading(): string|Htmlable|null
    {
        return __('dberp.system_update_center.page_intro');
    }

    /**
     * @return array<Action>
     */
    protected function getHeaderActions(): array
    {
        $display = app(HostSystemUpdateCenterDisplay::class);
        $actions = [];

        if (self::canCheckUpdate()) {
            $actions[] = Action::make('checkForUpdate')
                ->label(__('dberp.system_update_center.actions.check_update'))
                ->icon('heroicon-o-arrow-path')
                ->color('gray')
                ->disabled(fn (): bool => $this->isUpdateFlowBlockingUi())
                ->action(fn (): mixed => $this->refreshOfficialStatus());
        }

        if (self::canRunOneClickUpdate()) {
            $actions[] = $this->makeRunOneClickUpdateAction();
        }

        return $actions;
    }

    public function content(Schema $schema): Schema
    {
        return $schema->components([
            Section::make(__('dberp.system_update_center.sections.update_in_progress'))
                ->icon('heroicon-o-arrow-path')
                ->iconColor('primary')
                ->schema([
                    SchemaView::make('components.dberp.system-update.progress-panel')
                        ->viewData(fn (): array => $this->updateProgressPanelViewData())
                        ->columnSpanFull(),
                ])
                ->visible(fn (): bool => $this->shouldShowUpdateProgressPanel()),
            Section::make(__('dberp.system_update_notice.panel.title'))
                ->icon('heroicon-o-bell-alert')
                ->iconColor(fn (): string => ($this->systemUpdateNoticePanelData()['banner_tone'] ?? 'primary') === 'warning'
                    ? 'warning'
                    : 'primary')
                ->schema([
                    SchemaView::make('components.dberp.system-update.local-notice-panel')
                        ->viewData(fn (): array => [
                            'panel' => $this->systemUpdateNoticePanelData(),
                        ])
                        ->columnSpanFull(),
                ])
                ->footer([
                    Actions::make($this->systemUpdateNoticePanelActions()),
                ])
                ->visible(fn (): bool => $this->systemUpdateNoticePanelData() !== null),
            Section::make(__('dberp.system_update_center.sections.update_environment'))
                ->icon('heroicon-o-server-stack')
                ->iconColor('primary')
                ->collapsible()
                ->collapsed(fn (): bool => app(HostSystemUpdateCenterDisplay::class)->recommendedUpdateMethod()
                    === HostSystemUpdateRecommendedMethod::BrowserAvailable)
                ->schema([
                    SchemaView::make('components.dberp.system-update.update-environment-hub')
                        ->viewData(fn (): array => [
                            'panel' => app(HostSystemUpdateCenterDisplay::class)->updateEnvironmentHubPanelData(),
                        ])
                        ->columnSpanFull(),
                ])
                ->visible(fn (): bool => self::canRunOneClickUpdate()
                    && app(HostSystemUpdateCenterDisplay::class)->updateEnvironmentHubPanelData() !== null),
            EmbeddedSchema::make('infolist'),
            Section::make(__('dberp.system_update_center.sections.recovery'))
                ->schema([
                    TextEntry::make('recovery_hint')
                        ->label(__('dberp.system_update_center.rows.recovery_hint'))
                        ->state(fn (): string => (string) __('dberp.system_update_center.workflow.recovery_hint'))
                        ->columnSpanFull(),
                ])
                ->footer([
                    Actions::make($this->recoveryActions()),
                ])
                ->visible(fn (): bool => self::canRollbackUpdate()
                    && app(HostSystemUpdateCenterDisplay::class)->hasRollbackAvailable()),
        ]);
    }

    private function makeRunOneClickUpdateAction(): Action
    {
        $display = app(HostSystemUpdateCenterDisplay::class);

        return Action::make('runOneClickUpdate')
            ->label(__('dberp.system_update_center.actions.update_now'))
            ->icon('heroicon-o-arrow-up-circle')
            ->color(fn (): string => $display->isForceUpdate() ? 'danger' : 'primary')
            ->requiresConfirmation()
            ->modalHeading(__('dberp.system_update_center.confirm.update_now_heading'))
            ->modalDescription(__('dberp.system_update_center.confirm.update_now_body'))
            ->visible(fn (): bool => $this->canShowOneClickUpdateAction())
            ->disabled(fn (): bool => $this->isUpdateFlowBlockingUi()
                || $display->oneClickUnavailableReason() !== null)
            ->action(fn (): mixed => $this->runOneClickUpdate());
    }

    /**
     * @return list<Action>
     */
    private function recoveryActions(): array
    {
        return [
            Action::make('rollbackUpdate')
                ->label(__('dberp.system_update_center.actions.rollback'))
                ->icon('heroicon-o-arrow-uturn-left')
                ->color('danger')
                ->requiresConfirmation()
                ->modalHeading(__('dberp.system_update_center.confirm.rollback_heading'))
                ->modalDescription(__('dberp.system_update_center.confirm.rollback_body'))
                ->disabled(fn (): bool => $this->isUpdateFlowBlockingUi())
                ->action(fn (): mixed => $this->rollbackUpdate()),
        ];
    }

    public function defaultInfolist(Schema $schema): Schema
    {
        if (! $schema->hasCustomColumns()) {
            $schema->columns(1);
        }

        return $schema
            ->inlineLabel(false)
            ->record(null);
    }

    public function infolist(Schema $schema): Schema
    {
        $display = app(HostSystemUpdateCenterDisplay::class);

        return $schema->components([
            Section::make(__('dberp.system_update_center.sections.overview'))
                ->icon(DberpFilamentInfolistIconResolver::section('update'))
                ->iconColor('primary')
                ->schema([
                    TextEntry::make('current_version')
                        ->label(__('dberp.system_update_center.rows.current_version'))
                        ->state($display->displayVersion())
                        ->badge()
                        ->color('primary')
                        ->copyable(),
                    TextEntry::make('latest_version')
                        ->label(__('dberp.system_update_center.rows.latest_version'))
                        ->state($display->latestVersionLabel() ?? (string) __('dberp.system_update_center.values.unknown_latest'))
                        ->badge()
                        ->color('info'),
                    TextEntry::make('update_available')
                        ->label(__('dberp.system_update_center.rows.update_available'))
                        ->state($display->updateAvailableLabel())
                        ->badge()
                        ->color(fn (): string => $display->updateAvailableBadgeColor()),
                    TextEntry::make('last_checked_at')
                        ->label(__('dberp.system_update_center.rows.last_checked_at'))
                        ->state($display->lastCheckedAtLabel() ?? (string) __('dberp.system_update_center.values.never_checked')),
                    TextEntry::make('release_notes')
                        ->label(__('dberp.system_update_center.rows.release_notes'))
                        ->state($display->releaseNotesSummary())
                        ->visible(fn (): bool => filled($display->releaseNotesSummary()))
                        ->extraAttributes(['class' => 'whitespace-pre-line leading-relaxed'])
                        ->columnSpanFull(),
                    TextEntry::make('workflow_hint')
                        ->label(__('dberp.system_update_center.rows.guidance'))
                        ->state($display->workflowHint())
                        ->columnSpanFull(),
                    TextEntry::make('maintenance_notice')
                        ->label(__('dberp.system_update_center.rows.maintenance_notice'))
                        ->state($display->maintenanceNotice())
                        ->color('warning')
                        ->columnSpanFull(),
                    TextEntry::make('unavailable_reason')
                        ->label(__('dberp.system_update_center.rows.unavailable_reason'))
                        ->state($display->oneClickUnavailableReason())
                        ->color('warning')
                        ->visible(fn (): bool => filled($display->oneClickUnavailableReason()))
                        ->columnSpanFull(),
                    TextEntry::make('environment_blocking_reason')
                        ->label(__('dberp.system_update_center.rows.environment_blocking_reason'))
                        ->state($display->environmentBlockingReason())
                        ->color('danger')
                        ->visible(fn (): bool => ! $display->canRunBrowserUpdate()
                            && self::canRunOneClickUpdate())
                        ->columnSpanFull(),
                    TextEntry::make('last_check_message')
                        ->label(__('dberp.system_update_center.rows.last_check_message'))
                        ->state($display->lastCheckMessage())
                        ->color('danger')
                        ->visible(fn (): bool => filled($display->lastCheckMessage()))
                        ->columnSpanFull(),
                ])
                ->columns(2),
            Section::make(__('dberp.system_update_center.sections.latest_result'))
                ->schema([
                    TextEntry::make('latest_result')
                        ->label(__('dberp.system_update_center.rows.latest_result'))
                        ->state($display->latestUpdateResultLabel())
                        ->badge()
                        ->color(fn (): string => $display->latestUpdateResultBadgeColor()),
                    TextEntry::make('latest_result_detail')
                        ->label(__('dberp.system_update_center.rows.latest_result_detail'))
                        ->state($display->latestUpdateResultDetail())
                        ->visible(fn (): bool => filled($display->latestUpdateResultDetail()))
                        ->columnSpanFull(),
                    SchemaView::make('components.dberp.system-update.failure-recovery-panel')
                        ->viewData(fn (): array => [
                            'panel' => $display->failureRecoveryPanelData(),
                        ])
                        ->visible(fn (): bool => $display->failureRecoveryPanelData() !== null)
                        ->columnSpanFull(),
                    TextEntry::make('in_progress_notice')
                        ->label(__('dberp.system_update_center.rows.in_progress_notice'))
                        ->state($display->syncExecutionLimitationNote())
                        ->color('warning')
                        ->visible(fn (): bool => filled($display->syncExecutionLimitationNote())
                            && ! $this->shouldShowUpdateProgressPanel())
                        ->columnSpanFull(),
                ])
                ->columns(2),
        ]);
    }

    public function runOneClickUpdate(): void
    {
        if (! self::canRunOneClickUpdate()) {
            $this->notifyOperationDenied();

            return;
        }

        if ($this->isUpdateFlowBlockingUi()) {
            Notification::make()
                ->warning()
                ->title(__('dberp.system_update_center.notifications.update_in_progress_title'))
                ->body(__('dberp.system_update_center.notifications.update_in_progress_body'))
                ->send();

            return;
        }

        $display = app(HostSystemUpdateCenterDisplay::class);

        if (! $display->canRunBrowserUpdate()) {
            Notification::make()
                ->danger()
                ->title(__('dberp.system_update_center.errors.environment_not_ready'))
                ->body($display->environmentBlockingReason()
                    ?? (string) __('dberp.system_update_center.errors.environment_not_ready'))
                ->send();

            return;
        }

        $this->updateInProgress = true;
        $this->updateProgressPanelVisible = true;
        $this->updateProgressPanelPollAttempts = 0;
        $this->scrollToUpdateProgressPanel();

        try {
            $this->updateSessionId = app(HostSystemUpdateOneClickOrchestrator::class)->beginSteppedUpdate();
            $this->advanceUpdateStep();
        } catch (SupportCenterException|HostSystemUpdateExecutionException|HostSystemUpdateRecoveryException|DomainException $exception) {
            $message = $exception instanceof SupportCenterException
                || $exception instanceof HostSystemUpdateExecutionException
                || $exception instanceof HostSystemUpdateRecoveryException
                ? $exception->userFacingMessage()
                : $exception->getMessage();

            $this->finishUpdateFlow(
                failed: true,
                body: $message,
            );
        } catch (Throwable $exception) {
            report($exception);

            $this->finishUpdateFlow(
                failed: true,
                body: (string) __('dberp.system_update_center.notifications.update_now_failed_body'),
            );
        }
    }

    public function advanceUpdateStep(): void
    {
        if (! $this->updateInProgress || $this->updateSessionId === null) {
            return;
        }

        try {
            $result = app(HostSystemUpdateStepRunner::class)->runNextStep($this->updateSessionId);

            if ($result->terminal) {
                $this->finishUpdateFlow(
                    failed: $result->failed,
                    body: $result->userMessage,
                    execution: $result->execution,
                );

                return;
            }

            $this->scheduleNextUpdateStep();
        } catch (SupportCenterException|HostSystemUpdateExecutionException|HostSystemUpdateRecoveryException|DomainException $exception) {
            $message = $exception instanceof SupportCenterException
                || $exception instanceof HostSystemUpdateExecutionException
                || $exception instanceof HostSystemUpdateRecoveryException
                ? $exception->userFacingMessage()
                : $exception->getMessage();

            $this->finishUpdateFlow(failed: true, body: $message);
        } catch (Throwable $exception) {
            report($exception);

            $this->finishUpdateFlow(
                failed: true,
                body: (string) __('dberp.system_update_center.notifications.update_now_failed_body'),
            );
        }
    }

    private function scheduleNextUpdateStep(): void
    {
        if (! $this->updateInProgress) {
            return;
        }

        $this->js('setTimeout(() => { $wire.advanceUpdateStep() }, 250)');
    }

    /**
     * 分步更新进行中看门狗：检测会话丢失、库内已失败/已完成但 Livewire 未收尾的情况。
     */
    public function watchdogBrowserUpdateStep(): void
    {
        if (! $this->updateInProgress) {
            return;
        }

        $display = app(HostSystemUpdateCenterDisplay::class);
        $execution = $display->latestExecution();
        $sessionId = $this->updateSessionId;

        if ($sessionId === null) {
            if ($execution instanceof HostSystemUpdateExecution) {
                $status = (string) ($execution->status ?? '');

                if ($this->isCompletedExecution($execution)) {
                    $this->finishUpdateFlow(failed: false, execution: $execution);

                    return;
                }

                if (in_array($status, [
                    HostSystemUpdateExecutionStatus::Failed->value,
                    HostSystemUpdateExecutionStatus::RollbackRequired->value,
                    HostSystemUpdateExecutionStatus::RollbackFailed->value,
                    HostSystemUpdateExecutionStatus::ManualRecoveryRequired->value,
                ], true)) {
                    $this->finishUpdateFlow(
                        failed: true,
                        body: (string) ($execution->error_message
                            ?? __('dberp.system_update_center.errors.browser_update_interrupted')),
                        execution: $execution,
                    );

                    return;
                }
            }

            app(HostSystemUpdateBrowserFailureFinalizer::class)->finalize(null, $execution);
            $this->finishUpdateFlow(
                failed: true,
                body: (string) __('dberp.system_update_center.errors.browser_update_interrupted'),
                execution: $execution,
            );

            return;
        }

        $progress = app(HostSystemUpdateStepProgressStore::class)->read($sessionId);

        if ($progress === null) {
            // 成功收尾会先删会话文件；此时库状态已是 Completed，不能再按中断失败处理。
            if ($this->isCompletedExecution($execution)) {
                $this->finishUpdateFlow(failed: false, execution: $execution);

                return;
            }

            app(HostSystemUpdateBrowserFailureFinalizer::class)->finalize($sessionId, $execution);
            $this->finishUpdateFlow(
                failed: true,
                body: (string) __('dberp.system_update_center.errors.browser_update_interrupted'),
                execution: $execution,
            );

            return;
        }

        if ($progress->terminal) {
            $this->finishUpdateFlow(
                failed: $progress->failed,
                body: $progress->errorMessage,
                execution: $execution,
            );

            return;
        }

        if ($execution instanceof HostSystemUpdateExecution) {
            $status = (string) ($execution->status ?? '');

            if ($this->isCompletedExecution($execution)) {
                $this->finishUpdateFlow(failed: false, execution: $execution);

                return;
            }

            if (in_array($status, [
                HostSystemUpdateExecutionStatus::Failed->value,
                HostSystemUpdateExecutionStatus::RollbackRequired->value,
                HostSystemUpdateExecutionStatus::RollbackFailed->value,
                HostSystemUpdateExecutionStatus::ManualRecoveryRequired->value,
            ], true)) {
                app(HostSystemUpdateBrowserFailureFinalizer::class)->finalize($sessionId, $execution);
                $this->finishUpdateFlow(
                    failed: true,
                    body: (string) ($execution->error_message ?? $progress->errorMessage),
                    execution: $execution,
                );

                return;
            }

            $this->resumeStalledUpdateHandoff($progress, $execution);
        }
    }

    /**
     * 浏览器定时调用丢失，或子进程已完成但父请求被网关中断时，由看门狗按持久化证据幂等续跑。
     */
    private function resumeStalledUpdateHandoff(
        HostSystemUpdateStepProgress $progress,
        HostSystemUpdateExecution $execution,
    ): void {
        $currentStep = (string) ($execution->current_step ?? '');

        if ($progress->phase === HostSystemUpdateStepPhase::MigrateBatch) {
            if ($currentStep === HostSystemUpdateExecutionStep::RuntimeReadiness->value
                || ($currentStep === HostSystemUpdateExecutionStep::RunMigrations->value
                    && $this->latestMigrationBatchFinished($execution))) {
                $this->advanceUpdateStep();
            }

            return;
        }

        $phaseStep = $this->executionStepForPhase($progress->phase);

        if ($phaseStep !== null
            && $currentStep === $phaseStep->value
            && $this->latestStepCompleted($execution, $phaseStep)) {
            app(HostSystemUpdateStepProgressStore::class)->advancePhase($progress);
            $this->scheduleNextUpdateStep();

            return;
        }

        $previousStep = $this->previousExecutionStepForPhase($progress->phase);

        if ($previousStep !== null && $currentStep === $previousStep->value) {
            $this->advanceUpdateStep();
        }
    }

    private function executionStepForPhase(HostSystemUpdateStepPhase $phase): ?HostSystemUpdateExecutionStep
    {
        return match ($phase) {
            HostSystemUpdateStepPhase::ResetRuntime => HostSystemUpdateExecutionStep::ResetRuntime,
            HostSystemUpdateStepPhase::ClearBootstrapCache => HostSystemUpdateExecutionStep::ClearBootstrapCache,
            HostSystemUpdateStepPhase::RuntimeReadiness => HostSystemUpdateExecutionStep::RuntimeReadiness,
            HostSystemUpdateStepPhase::ClearCache => HostSystemUpdateExecutionStep::ClearCache,
            HostSystemUpdateStepPhase::PostUpdateHealthCheck => HostSystemUpdateExecutionStep::PostUpdateHealthCheck,
            HostSystemUpdateStepPhase::WriteVersion => HostSystemUpdateExecutionStep::WriteVersion,
            default => null,
        };
    }

    private function previousExecutionStepForPhase(HostSystemUpdateStepPhase $phase): ?HostSystemUpdateExecutionStep
    {
        return match ($phase) {
            HostSystemUpdateStepPhase::ResetRuntime => HostSystemUpdateExecutionStep::ReplaceVendor,
            HostSystemUpdateStepPhase::ClearBootstrapCache => HostSystemUpdateExecutionStep::ResetRuntime,
            HostSystemUpdateStepPhase::RuntimeReadiness => HostSystemUpdateExecutionStep::ClearBootstrapCache,
            HostSystemUpdateStepPhase::PostUpdateHealthCheck => HostSystemUpdateExecutionStep::ClearCache,
            HostSystemUpdateStepPhase::WriteVersion => HostSystemUpdateExecutionStep::PostUpdateHealthCheck,
            HostSystemUpdateStepPhase::CommitVendorBackup => HostSystemUpdateExecutionStep::WriteVersion,
            HostSystemUpdateStepPhase::Complete => HostSystemUpdateExecutionStep::CommitVendorBackup,
            default => null,
        };
    }

    private function latestStepCompleted(
        HostSystemUpdateExecution $execution,
        HostSystemUpdateExecutionStep $step,
    ): bool {
        return $execution->logs()
            ->where('step', $step->value)
            ->where('status', HostSystemUpdateExecutionLog::STATUS_COMPLETED)
            ->exists();
    }

    private function latestMigrationBatchFinished(HostSystemUpdateExecution $execution): bool
    {
        $latest = $execution->logs()
            ->where('step', HostSystemUpdateExecutionStep::RunMigrations->value)
            ->where('status', HostSystemUpdateExecutionLog::STATUS_TRANSITION)
            ->latest('id')
            ->first();
        $context = $latest?->context;

        return is_array($context) && (int) ($context['pending'] ?? -1) === 0;
    }

    private function finishUpdateFlow(
        bool $failed,
        ?string $body = null,
        ?HostSystemUpdateExecution $execution = null,
    ): void {
        $sessionId = $this->updateSessionId;

        $this->updateInProgress = false;
        $this->updateSessionId = null;

        if ($failed) {
            app(HostSystemUpdateBrowserFailureFinalizer::class)->finalize($sessionId, $execution);

            $this->updateProgressPanelVisible = false;
            $this->updateProgressPanelPollAttempts = 0;

            Notification::make()
                ->danger()
                ->title(__('dberp.system_update_center.notifications.update_now_failed_title'))
                ->body($body ?? (string) __('dberp.system_update_center.notifications.update_now_failed_body'))
                ->send();

            return;
        }

        $status = (string) ($execution?->status ?? HostSystemUpdateExecutionStatus::Completed->value);

        if ($status === HostSystemUpdateExecutionStatus::Completed->value) {
            $this->notifyUpdateCompletedOnce($execution, $sessionId);
            $this->beginUpdateProgressPanelDismissalPolling();

            return;
        }

        $this->updateProgressPanelVisible = false;
        $this->updateProgressPanelPollAttempts = 0;

        Notification::make()
            ->danger()
            ->title(__('dberp.system_update_center.notifications.update_now_failed_title'))
            ->body((string) ($execution?->error_message
                ?? $body
                ?? __('dberp.system_update_center.notifications.update_now_failed_body')))
            ->send();
    }

    public function pollUpdateProgressPanel(): void
    {
        if (! $this->updateProgressPanelVisible || $this->updateInProgress) {
            return;
        }

        $this->updateProgressPanelPollAttempts++;

        if (
            app(HostSystemUpdateCenterDisplay::class)->isReadyToDismissUpdateProgressPanel()
            || $this->updateProgressPanelPollAttempts >= self::UPDATE_PROGRESS_PANEL_MAX_POLL_ATTEMPTS
        ) {
            $this->dismissUpdateProgressPanel();
        }
    }

    public function dismissUpdateProgressPanel(): void
    {
        if ($this->updateInProgress) {
            return;
        }

        $this->updateProgressPanelVisible = false;
        $this->updateProgressPanelPollAttempts = 0;

        $this->redirect(self::getUrl(), navigate: true);
    }

    private function beginUpdateProgressPanelDismissalPolling(): void
    {
        // 成功通知与 SPA 刷新不能挤在同一次 Livewire 响应里，否则 Filament 会把同一条提示展示两次。
        $this->js('setTimeout(() => { $wire.pollUpdateProgressPanel() }, 400)');
    }

    private function notifyUpdateCompletedOnce(?HostSystemUpdateExecution $execution, ?string $sessionId): void
    {
        if (! $this->claimUpdateCompletedNotificationSlot($execution, $sessionId)) {
            return;
        }

        Notification::make()
            ->success()
            ->title(__('dberp.system_update_center.notifications.update_now_success_title'))
            ->body(__('dberp.system_update_center.notifications.update_now_success_body'))
            ->send();
    }

    /**
     * 按本次执行号占位，避免分步收尾与看门狗各发一次成功提示。
     */
    private function claimUpdateCompletedNotificationSlot(?HostSystemUpdateExecution $execution, ?string $sessionId): bool
    {
        $token = trim((string) ($execution?->execution_no ?? ''));

        if ($token === '') {
            $token = trim((string) ($sessionId ?? ''));
        }

        if ($token === '') {
            return true;
        }

        try {
            return Cache::add(
                'dberp.host_system_update.ui_success_notified.'.$token,
                1,
                now()->addSeconds(self::UPDATE_COMPLETED_NOTIFICATION_CACHE_TTL_SECONDS),
            );
        } catch (Throwable) {
            return true;
        }
    }

    private function isCompletedExecution(?HostSystemUpdateExecution $execution): bool
    {
        return $execution instanceof HostSystemUpdateExecution
            && (string) ($execution->status ?? '') === HostSystemUpdateExecutionStatus::Completed->value;
    }

    private function isUpdateFlowBlockingUi(): bool
    {
        return $this->updateInProgress
            || $this->updateProgressPanelVisible
            || app(HostSystemUpdateCenterDisplay::class)->isUpdateRunning();
    }

    public function refreshOfficialStatus(): void
    {
        if (! self::canCheckUpdate()) {
            $this->notifyOperationDenied();

            return;
        }

        if (! app(HostSystemUpdateEntitlementGate::class)->canCheckUpdate()) {
            $this->notifyEntitlementDenied(app(HostSystemUpdateEntitlementGate::class)->checkReason());

            return;
        }

        try {
            $check = app(HostSystemUpdateCheckService::class)->check();

            Notification::make()
                ->success()
                ->title(__('dberp.system_update_center.notifications.check_success_title'))
                ->body($this->checkSuccessNotificationBody($check))
                ->send();

            $this->redirect(self::getUrl(), navigate: true);

            return;
        } catch (SupportCenterException $exception) {
            Notification::make()
                ->danger()
                ->title(__('dberp.system_update_center.notifications.check_failed_title'))
                ->body($exception->userFacingMessage())
                ->send();
        }
    }

    public function rollbackUpdate(): void
    {
        if (! self::canRollbackUpdate()) {
            $this->notifyOperationDenied();

            return;
        }

        $execution = app(HostSystemUpdateCenterDisplay::class)->latestExecution();

        if (! $execution instanceof HostSystemUpdateExecution
            || (string) ($execution->status ?? '') !== HostSystemUpdateExecutionStatus::RollbackRequired->value) {
            Notification::make()
                ->warning()
                ->title(__('dberp.system_update_center.notifications.rollback_unavailable_title'))
                ->body(__('dberp.system_update_center.notifications.rollback_unavailable_body'))
                ->send();

            return;
        }

        try {
            app(HostSystemUpdateRollbackService::class)->rollback($execution);

            Notification::make()
                ->success()
                ->title(__('dberp.system_update_center.notifications.rollback_success_title'))
                ->body(__('dberp.system_update_center.notifications.rollback_success_body'))
                ->send();
        } catch (HostSystemUpdateRecoveryException $exception) {
            Notification::make()
                ->danger()
                ->title(__('dberp.system_update_center.notifications.rollback_failed_title'))
                ->body($exception->userFacingMessage())
                ->send();
        }
    }

    private function shouldShowUpdateProgressPanel(): bool
    {
        return $this->updateProgressPanelVisible
            || $this->updateInProgress
            || app(HostSystemUpdateCenterDisplay::class)->isUpdateRunning();
    }

    /**
     * @return array{
     *     progressPercent: int,
     *     currentPhase: string,
     *     progressLabel: string,
     *     notice: string,
     *     logEntries: list<array{time: string, step_label: string, status_label: string, status_color: string, message: string}>,
     *     logsEmptyMessage: string,
     *     logsHeading: string,
     *     pollForDismissal: bool,
     * }
     */
    private function updateProgressPanelViewData(): array
    {
        $display = app(HostSystemUpdateCenterDisplay::class);

        return [
            'progressPercent' => $display->executionProgressPercent(),
            'currentPhase' => $this->resolveUpdateProgressPhaseLabel($display),
            'progressLabel' => $display->executionProgressLabel(),
            'notice' => (string) __('dberp.system_update_center.workflow.update_in_progress_notice'),
            'logEntries' => $display->recentExecutionLogEntries(),
            'logsEmptyMessage' => (string) __('dberp.system_update_center.values.no_execution_logs'),
            'logsHeading' => (string) __('dberp.system_update_center.rows.execution_process_log'),
            'pollForDismissal' => $this->updateProgressPanelVisible && ! $this->updateInProgress,
            'pollWhileRunning' => $this->updateInProgress,
            'pollWhileRunningInterval' => self::UPDATE_PROGRESS_WATCHDOG_SECONDS.'s',
            'terminal' => $display->updateProgressTerminalPanelData(),
        ];
    }

    private function resolveUpdateProgressPhaseLabel(HostSystemUpdateCenterDisplay $display): string
    {
        $currentStep = trim((string) ($display->latestExecution()?->current_step ?? ''));

        if ($currentStep !== '') {
            return $display->executionStepLabel();
        }

        return (string) __('dberp.system_update_center.workflow.preparing_update');
    }

    private function scrollToUpdateProgressPanel(): void
    {
        $this->js('document.getElementById("dberp-update-progress-panel")?.scrollIntoView({ behavior: "smooth", block: "start" })');
    }

    private function checkSuccessNotificationBody(HostSystemUpdateCheck $check): string
    {
        if ($check->update_available) {
            return (string) __('dberp.system_update_center.notifications.check_success_body_has_update');
        }

        return (string) __('dberp.system_update_center.notifications.check_success_body_up_to_date');
    }

    private function notifyOperationDenied(): void
    {
        Notification::make()
            ->danger()
            ->title(__('dberp.system_update_center.notifications.permission_denied_title'))
            ->body(__('dberp.system_update_center.notifications.permission_denied_body'))
            ->send();
    }

    private function notifyEntitlementDenied(string $reason): void
    {
        Notification::make()
            ->warning()
            ->title(__('dberp.system_update_center.notifications.entitlement_denied_title'))
            ->body($reason)
            ->send();
    }

    /**
     * @return array<string, mixed>|null
     */
    private function systemUpdateNoticePanelData(): ?array
    {
        return app(DberpSystemUpdateNoticeService::class)->panelViewDataFor(Auth::user());
    }

    /**
     * @return list<Action>
     */
    private function systemUpdateNoticePanelActions(): array
    {
        $panel = $this->systemUpdateNoticePanelData();

        if ($panel === null) {
            return [];
        }

        $noticeId = (int) $panel['notice_id'];
        $primaryColor = ($panel['banner_tone'] ?? 'primary') === 'warning' ? 'warning' : 'primary';

        $actions = [
            Action::make('openSystemUpdateNoticePrimary')
                ->label($panel['primary_action_label'])
                ->icon('heroicon-o-arrow-top-right-on-square')
                ->color($primaryColor)
                ->url($panel['primary_action_url']),
        ];

        if (filled($panel['secondary_action_url'] ?? null)) {
            $actions[] = Action::make('openSystemUpdateNoticeSecondary')
                ->label($panel['secondary_action_label'])
                ->icon('heroicon-o-document-text')
                ->color('gray')
                ->url($panel['secondary_action_url']);
        }

        $actions[] = Action::make('markSystemUpdateNoticeRead')
            ->label(__('dberp.system_update_notice.actions.mark_read'))
            ->icon('heroicon-o-check')
            ->color('gray')
            ->action(fn (): mixed => $this->markSystemUpdateNoticeAsRead($noticeId));

        if (($panel['dismissible'] ?? true) === true) {
            $actions[] = Action::make('dismissSystemUpdateNotice')
                ->label(__('dberp.system_update_notice.actions.dismiss'))
                ->icon('heroicon-o-x-mark')
                ->color('gray')
                ->requiresConfirmation()
                ->modalHeading(__('dberp.system_update_notice.confirm.dismiss_heading'))
                ->modalDescription(__('dberp.system_update_notice.confirm.dismiss'))
                ->action(fn (): mixed => $this->dismissSystemUpdateNotice($noticeId));
        }

        return $actions;
    }

    public function markSystemUpdateNoticeAsRead(int $noticeId): void
    {
        $user = Auth::user();

        if ($user === null) {
            return;
        }

        if (! app(DberpSystemUpdateNoticeService::class)->markAsRead($user, $noticeId)) {
            return;
        }

        Notification::make()
            ->success()
            ->title(__('dberp.system_update_notice.messages.marked_read'))
            ->send();
    }

    public function dismissSystemUpdateNotice(int $noticeId): void
    {
        $user = Auth::user();

        if ($user === null) {
            return;
        }

        if (! app(DberpSystemUpdateNoticeService::class)->dismiss($user, $noticeId)) {
            return;
        }

        Notification::make()
            ->success()
            ->title(__('dberp.system_update_notice.messages.dismissed'))
            ->send();

        $this->redirect(self::getUrl(), navigate: true);
    }
}
