<?php

namespace App\ValueObjects;

use InvalidArgumentException;
use Stringable;
use Throwable;

class Email implements Stringable
{
    /**
     * The validated, standardized email string.
     */
    public string $address;

    public function __construct(string $address)
    {
        $sanitizedAddress = $this->sanitize($address);

        if (!$this->isValid($sanitizedAddress)) {
            throw new InvalidArgumentException(
                "Invalid email address provided: {$address}"
            );
        }

        $this->address = $sanitizedAddress;
    }

    public static function tryFrom(?string $address): ?self
    {
        if (blank($address)) {
            return null;
        }

        try {
            return new self($address);
        } catch (Throwable) {
            return null;
        }
    }

    /**
     * Standardizes the email for storage.
     */
    private function sanitize(string $address): string
    {
        return strtolower(trim($address));
    }

    /**
     * Performs full email validation.
     */
    public function isValid(?string $email = null): bool
    {
        $email = $email ?? $this->address;

        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    /**
     * Extracts the local part (before @, excluding +tag).
     */
    public function getUsername(): string
    {
        $localPart = explode('@', $this->address)[0] ?? '';
        $plusPosition = strpos($localPart, '+');

        return $plusPosition !== false
            ? substr($localPart, 0, $plusPosition)
            : $localPart;
    }

    /**
     * Extracts the domain part.
     */
    public function getDomain(): string
    {
        return explode('@', $this->address)[1] ?? '';
    }

    /**
     * Extracts the plus-addressing tag, if present.
     */
    public function getPlusTag(): ?string
    {
        $localPart = explode('@', $this->address)[0] ?? '';
        $plusPosition = strpos($localPart, '+');

        if ($plusPosition === false) {
            return null;
        }

        $tag = substr($localPart, $plusPosition + 1);

        return $tag === '' ? null : $tag;
    }

    /**
     * Cast to string (normalized address).
     */
    public function __toString(): string
    {
        return $this->address;
    }
}
