<?php

declare(strict_types=1);

namespace App\Framework\Core\ValueObjects;

use App\Framework\Core\ValueObjects\Services\VersionParser;
use InvalidArgumentException;

/**
 * Value Object für semantische Versionierung
 * Unterstützt SemVer 2.0.0 Format: major.minor.patch[-prerelease][+buildmetadata]
 */
final readonly class Version
{
    public function __construct(
        private int $major,
        private int $minor,
        private int $patch = 0,
        private ?string $preRelease = null,
        private ?string $buildMetadata = null
    ) {
        if ($major < 0 || $minor < 0 || $patch < 0) {
            throw new InvalidArgumentException('Version components must be non-negative');
        }

        $parser = new VersionParser();

        if ($preRelease !== null && ! $parser->isValidPreRelease($preRelease)) {
            throw new InvalidArgumentException("Invalid pre-release format: $preRelease");
        }

        if ($buildMetadata !== null && ! $parser->isValidBuildMetadata($buildMetadata)) {
            throw new InvalidArgumentException("Invalid build metadata format: $buildMetadata");
        }
    }

    /**
     * Erstellt Version aus String
     */
    public static function fromString(string $version): self
    {
        $parser = new VersionParser();
        $parts = $parser->parse($version);

        return new self(
            major: $parts['major'],
            minor: $parts['minor'],
            patch: $parts['patch'],
            preRelease: $parts['preRelease'],
            buildMetadata: $parts['buildMetadata']
        );
    }

    /**
     * Erstellt Version aus Komponenten (alias für Konstruktor für bessere Lesbarkeit)
     */
    public static function fromComponents(
        int $major,
        int $minor,
        int $patch = 0,
        ?string $preRelease = null,
        ?string $buildMetadata = null
    ): self {
        return new self($major, $minor, $patch, $preRelease, $buildMetadata);
    }

    /**
     * Gibt den vollständigen Versions-String zurück
     */
    public function getValue(): string
    {
        $version = "{$this->major}.{$this->minor}.{$this->patch}";

        if ($this->preRelease !== null) {
            $version .= "-{$this->preRelease}";
        }

        if ($this->buildMetadata !== null) {
            $version .= "+{$this->buildMetadata}";
        }

        return $version;
    }

    /**
     * Gibt die Major-Version zurück
     */
    public function getMajor(): int
    {
        return $this->major;
    }

    /**
     * Gibt die Minor-Version zurück
     */
    public function getMinor(): int
    {
        return $this->minor;
    }

    /**
     * Gibt die Patch-Version zurück
     */
    public function getPatch(): int
    {
        return $this->patch;
    }

    /**
     * Gibt den Pre-Release-String zurück (falls vorhanden)
     */
    public function getPreRelease(): ?string
    {
        return $this->preRelease;
    }

    /**
     * Gibt die Build-Metadaten zurück (falls vorhanden)
     */
    public function getBuildMetadata(): ?string
    {
        return $this->buildMetadata;
    }

    /**
     * Prüft ob diese Version mit einer anderen kompatibel ist (gleiche Major-Version)
     */
    public function isCompatibleWith(self $other): bool
    {
        return $this->major === $other->major;
    }

    /**
     * Prüft ob diese Version neuer als eine andere ist
     */
    public function isNewerThan(self $other): bool
    {
        if ($this->major !== $other->major) {
            return $this->major > $other->major;
        }

        if ($this->minor !== $other->minor) {
            return $this->minor > $other->minor;
        }

        if ($this->patch !== $other->patch) {
            return $this->patch > $other->patch;
        }

        // Bei gleicher Kern-Version: Stable > Pre-Release
        if ($this->preRelease === null && $other->preRelease !== null) {
            return true;
        }

        if ($this->preRelease !== null && $other->preRelease === null) {
            return false;
        }

        // Beide haben Pre-Release oder beide nicht
        if ($this->preRelease !== null && $other->preRelease !== null) {
            return version_compare($this->preRelease, $other->preRelease, '>');
        }

        return false; // Gleiche Version
    }

    /**
     * Prüft ob diese Version älter als eine andere ist
     */
    public function isOlderThan(self $other): bool
    {
        return $other->isNewerThan($this);
    }

    /**
     * Prüft ob diese Version gleich einer anderen ist
     */
    public function equals(self $other): bool
    {
        return $this->major === $other->major
            && $this->minor === $other->minor
            && $this->patch === $other->patch
            && $this->preRelease === $other->preRelease;
        // Build-Metadaten werden bei Gleichheit ignoriert (SemVer 2.0.0)
    }

    /**
     * Prüft ob dies eine Pre-Release-Version ist
     */
    public function isPreRelease(): bool
    {
        return $this->preRelease !== null;
    }

    /**
     * Prüft ob dies eine stabile Version ist
     */
    public function isStable(): bool
    {
        return $this->preRelease === null;
    }

    /**
     * Gibt eine neue Version mit erhöhter Major-Version zurück
     */
    public function incrementMajor(): self
    {
        return new self($this->major + 1, 0, 0);
    }

    /**
     * Gibt eine neue Version mit erhöhter Minor-Version zurück
     */
    public function incrementMinor(): self
    {
        return new self($this->major, $this->minor + 1, 0);
    }

    /**
     * Gibt eine neue Version mit erhöhter Patch-Version zurück
     */
    public function incrementPatch(): self
    {
        return new self($this->major, $this->minor, $this->patch + 1);
    }

    /**
     * Gibt nur die Kern-Version ohne Pre-Release und Build-Metadaten zurück
     */
    public function getVersionCore(): string
    {
        return "{$this->major}.{$this->minor}.{$this->patch}";
    }

    /**
     * Gibt eine kurze Version (major.minor) zurück
     */
    public function getShortVersion(): string
    {
        return "{$this->major}.{$this->minor}";
    }

    /**
     * String-Repräsentation
     */
    public function toString(): string
    {
        return $this->getValue();
    }

    public function __toString(): string
    {
        return $this->toString();
    }
}
