<?php

declare(strict_types=1);

namespace App\Framework\Console;

/**
 * Definition of a console command argument or option
 */
final readonly class ArgumentDefinition
{
    public function __construct(
        public string $name,
        public ArgumentType $type = ArgumentType::STRING,
        public bool $required = false,
        public mixed $default = null,
        public string $description = '',
        public ?string $shortName = null,
        /** @var string[] */
        public array $allowedValues = []
    ) {
        // Validate configuration
        if (empty(trim($this->name))) {
            throw new \InvalidArgumentException('Argument name cannot be empty');
        }

        if ($this->shortName !== null && strlen($this->shortName) !== 1) {
            throw new \InvalidArgumentException('Short name must be exactly one character');
        }

        if ($this->required && $this->default !== null) {
            throw new \InvalidArgumentException('Required arguments cannot have default values');
        }

        if ($this->type === ArgumentType::BOOLEAN && ! empty($this->allowedValues)) {
            throw new \InvalidArgumentException('Boolean arguments cannot have allowed values');
        }
    }

    /**
     * Create a required string argument
     */
    public static function required(string $name, string $description = ''): self
    {
        return new self($name, ArgumentType::STRING, required: true, description: $description);
    }

    /**
     * Create an optional string argument with default
     */
    public static function optional(string $name, string $default = '', string $description = ''): self
    {
        return new self($name, ArgumentType::STRING, default: $default, description: $description);
    }

    /**
     * Create a boolean flag
     */
    public static function flag(string $name, ?string $shortName = null, string $description = ''): self
    {
        return new self($name, ArgumentType::BOOLEAN, description: $description, shortName: $shortName);
    }

    /**
     * Create an email argument
     */
    public static function email(string $name, bool $required = true, string $description = ''): self
    {
        return new self($name, ArgumentType::EMAIL, required: $required, description: $description);
    }

    /**
     * Create an integer argument
     */
    public static function integer(string $name, bool $required = false, ?int $default = null, string $description = ''): self
    {
        return new self($name, ArgumentType::INTEGER, required: $required, default: $default, description: $description);
    }

    /**
     * Create a choice argument with allowed values
     */
    public static function choice(string $name, array $allowedValues, bool $required = false, ?string $default = null, string $description = ''): self
    {
        return new self(
            $name,
            ArgumentType::STRING,
            required: $required,
            default: $default,
            description: $description,
            allowedValues: $allowedValues
        );
    }

    /**
     * Get the display name for help text
     */
    public function getDisplayName(): string
    {
        $display = $this->name;

        if ($this->shortName) {
            $display = "{$this->shortName}, {$display}";
        }

        return $display;
    }

    /**
     * Get usage text for this argument
     */
    public function getUsageText(): string
    {
        if ($this->type === ArgumentType::BOOLEAN) {
            return $this->required ? "--{$this->name}" : "[--{$this->name}]";
        }

        $usage = "--{$this->name}";

        if (! empty($this->allowedValues)) {
            $usage .= "=" . implode('|', $this->allowedValues);
        } else {
            $usage .= "={$this->type->getExample()}";
        }

        return $this->required ? "<{$usage}>" : "[{$usage}]";
    }

    /**
     * Validate a value against this argument definition
     */
    public function validateValue(mixed $value): void
    {
        if ($this->required && ($value === null || $value === '')) {
            throw new \InvalidArgumentException("Required argument '{$this->name}' is missing");
        }

        if (! empty($this->allowedValues) && ! in_array($value, $this->allowedValues, true)) {
            throw new \InvalidArgumentException(
                "Invalid value '{$value}' for argument '{$this->name}'. Allowed values: " .
                implode(', ', $this->allowedValues)
            );
        }
    }
}
