<?php

declare(strict_types=1);

namespace App\Services\Install;

use InvalidArgumentException;

/**
 * Web 安装进度快照（文件 JSON 与后续进度 API 共用）。
 */
final readonly class WebInstallProgressState
{
    public const INITIAL_LABEL = '等待开始安装';

    /**
     * @param  list<string>  $completedStages
     * @param  list<string>  $warnings
     * @param  list<string>  $errors
     */
    public function __construct(
        public string $installId,
        public WebInstallProgressStatus $status,
        public string $currentStage,
        public string $currentLabel,
        public int $percent,
        public array $completedStages,
        public array $warnings,
        public array $errors,
        public string $startedAt,
        public string $updatedAt,
        public ?WebInstallMigrationProgress $migrationProgress = null,
    ) {}

    /**
     * @return list<WebInstallPhase>
     */
    public static function progressablePhases(): array
    {
        return array_values(array_filter(
            WebInstallPhase::ordered(),
            static fn (WebInstallPhase $phase): bool => $phase !== WebInstallPhase::Seeding,
        ));
    }

    public static function initial(string $installId, string $occurredAt): self
    {
        return new self(
            installId: $installId,
            status: WebInstallProgressStatus::Pending,
            currentStage: '',
            currentLabel: self::INITIAL_LABEL,
            percent: 0,
            completedStages: [],
            warnings: [],
            errors: [],
            startedAt: $occurredAt,
            updatedAt: $occurredAt,
        );
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    public static function fromArray(array $payload): self
    {
        $installId = (string) ($payload['install_id'] ?? '');
        if ($installId === '') {
            throw new InvalidArgumentException('install_id 不能为空。');
        }

        $status = WebInstallProgressStatus::tryFrom((string) ($payload['status'] ?? ''));
        if ($status === null) {
            throw new InvalidArgumentException('status 无效。');
        }

        $migrationProgress = null;
        if (isset($payload['migration_progress']) && is_array($payload['migration_progress'])) {
            $migrationProgress = WebInstallMigrationProgress::fromArray($payload['migration_progress']);
        }

        return new self(
            installId: $installId,
            status: $status,
            currentStage: (string) ($payload['current_stage'] ?? ''),
            currentLabel: (string) ($payload['current_label'] ?? ''),
            percent: max(0, min(100, (int) ($payload['percent'] ?? 0))),
            completedStages: self::stringList($payload['completed_stages'] ?? []),
            warnings: self::stringList($payload['warnings'] ?? []),
            errors: self::stringList($payload['errors'] ?? []),
            startedAt: (string) ($payload['started_at'] ?? ''),
            updatedAt: (string) ($payload['updated_at'] ?? ''),
            migrationProgress: $migrationProgress,
        );
    }

    /**
     * @return array<string, mixed>
     */
    public function toArray(): array
    {
        $payload = [
            'install_id' => $this->installId,
            'status' => $this->status->value,
            'current_stage' => $this->currentStage,
            'current_label' => $this->currentLabel,
            'percent' => $this->percent,
            'completed_stages' => $this->completedStages,
            'warnings' => $this->warnings,
            'errors' => $this->errors,
            'started_at' => $this->startedAt,
            'updated_at' => $this->updatedAt,
        ];

        if ($this->migrationProgress !== null) {
            $payload['migration_progress'] = $this->migrationProgress->toArray();
        }

        return $payload;
    }

    public function percentForPhase(WebInstallPhase $phase, bool $completed): int
    {
        $phases = self::progressablePhases();
        $index = array_search($phase, $phases, true);

        if ($index === false) {
            return $this->percent;
        }

        $total = count($phases);
        if ($total === 0) {
            return 0;
        }

        $progressIndex = $completed ? $index + 1 : $index;

        return (int) floor(($progressIndex / $total) * 100);
    }

    public function percentDuringMigration(WebInstallMigrationProgress $migrationProgress): int
    {
        $phaseStart = $this->percentForPhase(WebInstallPhase::Migrating, completed: false);
        $phaseEnd = $this->percentForPhase(WebInstallPhase::Migrating, completed: true);

        if ($migrationProgress->total <= 0) {
            return $phaseStart;
        }

        $ratio = min(1, max(0, $migrationProgress->completed / $migrationProgress->total));

        return (int) floor($phaseStart + (($phaseEnd - $phaseStart) * $ratio));
    }

    /**
     * @return list<string>
     */
    private static function stringList(mixed $value): array
    {
        if (! is_array($value)) {
            return [];
        }

        $items = [];
        foreach ($value as $item) {
            if (is_string($item) && $item !== '') {
                $items[] = $item;
            }
        }

        return $items;
    }
}
