<?php

namespace App\ValueObjects;

use InvalidArgumentException;
use Stringable;

class Postcode implements Stringable {

    private const UK_POSTCODE_REGEX = '/^
        (GIR0AA)|
        (
            (?:[A-PR-UWYZ][0-9][0-9]?|
             [A-PR-UWYZ][A-HK-Y][0-9][0-9]?|
             [A-PR-UWYZ][0-9][A-HJKSTUW]|
             [A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY])
            [0-9][ABD-HJLNP-UW-Z]{2}
        )
    $/ix';

    public $raw;

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

        if (!$this->isValid($raw)) {
            throw new InvalidArgumentException("Invalid postcode provided: {$raw}");
        }
    }

    private static function sanitize(string $postcode): string {
        return strtoupper(preg_replace('/\s+/', '', trim($postcode)));
    }

    private static function isValid(string $postcode): bool {
        return (bool)preg_match(self::UK_POSTCODE_REGEX, $postcode);
    }

    /**
     * ✅ Optional field
     * ❌ Invalid input throws
     */
    public static function fromNullable(?string $postcode): ?self {
        if ($postcode === null || trim($postcode) === '') {
            return null;
        }

        $sanitised = self::sanitize($postcode);

        if (!self::isValid($sanitised)) {
            throw new InvalidArgumentException(
                "Invalid UK postcode provided: {$postcode}"
            );
        }

        return new self($sanitised);
    }

    /**
     * Strict constructor for non-null contexts
     */
    public static function fromString(string $postcode): self {
        $sanitised = self::sanitize($postcode);

        if (!self::isValid($sanitised)) {
            throw new InvalidArgumentException(
                "Invalid UK postcode provided: {$postcode}"
            );
        }

        echo $sanitised;

        return new self($sanitised);
    }

    /**
     * DB value (no spaces)
     */
    public function getRaw(): string {
        return $this->raw;
    }

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

    /**
     * Human display value
     */
    public function format(): string {
        return substr($this->raw, 0, -3) . ' ' . substr($this->raw, -3);
    }
}
