<?php

namespace Acms\Services\SiteSeed;

use InvalidArgumentException;

/**
 * SiteSeed 実行オプション（不変 DTO）
 *
 * fromCliArgs 生成時に検証を完了させる。Web UI (setup/import.php) は ImportController が
 * 直接コンストラクタで組み立てる（SetupSession 経由のユーザーIDなど、CLI とは値の出所が
 * 異なるため専用ファクトリは持たない）。
 *
 * 動作モードは asChild フラグで切り替える:
 *   - asChild=false (default): 既存 blog (targetBlogId) の中身を bin で置き換える
 *   - asChild=true            : targetBlogId を親として、bin の内容を子 blog として作成する
 *
 * 注: `Acms\Services\Blog\Import::run` は **常に対象 blog の全データ (contents + config) を
 * 削除して yaml で置き換える** 動作のため、過去の `config_import` / `config_delete` /
 * `contents_drop` のような部分制御フラグは持たない (該当機能は管理画面の Backup/Import
 * を使うこと)。
 */
final class SiteSeedOptions
{
    public function __construct(
        public readonly string $binName,
        public readonly bool $asChild = false,
        public readonly ?int $targetBlogId = null,
        public readonly ?string $blogCode = null,
        public readonly ?string $blogName = null,
        public readonly ?string $blogDomain = null,
        public readonly ?int $userId = null,
        public readonly bool $resetSchema = false,
        public readonly bool $skipCacheFlush = false,
        public readonly bool $dryRun = false,
        public readonly bool $quiet = false,
        public readonly bool $json = false,
    ) {
        $this->validate();
    }

    private function validate(): void
    {
        if ($this->binName === '') {
            throw new InvalidArgumentException('binName is required');
        }
        if ($this->targetBlogId === null) {
            throw new InvalidArgumentException('targetBlogId is required');
        }
        if ($this->targetBlogId <= 0) {
            throw new InvalidArgumentException(
                sprintf('targetBlogId must be an integer >= 1: %d', $this->targetBlogId)
            );
        }
        if ($this->asChild) {
            if ($this->blogCode === null || $this->blogCode === '') {
                throw new InvalidArgumentException('--as-child requires --blog-code');
            }
            if ($this->blogName === null || $this->blogName === '') {
                throw new InvalidArgumentException('--as-child requires --blog-name');
            }
        }
    }

    /**
     * CLI の argv から生成する
     *
     * @param array<int, string> $argv $argv 相当
     */
    public static function fromCliArgs(array $argv): self
    {
        /** @var list<string> $positional */
        $positional = [];
        /** @var array<string, true> $flags */
        $flags = [];
        /** @var array<string, string> $values */
        $values = [];

        for ($i = 1; $i < count($argv); $i++) {
            $arg = $argv[$i];
            if (str_starts_with($arg, '--')) {
                $body = substr($arg, 2);
                if (str_contains($body, '=')) {
                    [$key, $val] = explode('=', $body, 2);
                    $values[$key] = $val;
                } else {
                    $flags[$body] = true;
                }
            } else {
                $positional[] = $arg;
            }
        }

        $binName = $positional[0] ?? '';
        if ($binName === '') {
            throw new InvalidArgumentException('bin name must be supplied as a positional argument');
        }

        $asChild = isset($flags['as-child']);

        if ($asChild) {
            if (!isset($values['parent-blog-id'])) {
                throw new InvalidArgumentException('--as-child requires --parent-blog-id');
            }
            $targetBlogId = self::intOrNull($values['parent-blog-id'], '--parent-blog-id');
        } else {
            if (!isset($values['blog-id'])) {
                throw new InvalidArgumentException('--blog-id is required (use --parent-blog-id with --as-child)');
            }
            $targetBlogId = self::intOrNull($values['blog-id'], '--blog-id');
        }

        $userId = isset($values['user-id'])
            ? self::intOrNull($values['user-id'], '--user-id')
            : null;

        return new self(
            binName: $binName,
            asChild: $asChild,
            targetBlogId: $targetBlogId,
            blogCode: $values['blog-code'] ?? null,
            blogName: $values['blog-name'] ?? null,
            blogDomain: $values['blog-domain'] ?? null,
            userId: $userId,
            resetSchema: isset($flags['reset-schema']),
            skipCacheFlush: isset($flags['skip-cache-flush']),
            dryRun: isset($flags['dry-run']),
            quiet: isset($flags['quiet']),
            json: isset($flags['json']),
        );
    }

    private static function intOrNull(string $value, string $label): int
    {
        if (!preg_match('/^\d+$/', $value)) {
            throw new InvalidArgumentException(sprintf('%s must be an integer: %s', $label, $value));
        }
        return (int) $value;
    }
}
