<?php

declare(strict_types=1);

namespace App\Framework\Ulid;

use App\Framework\DateTime\Clock;
use DateTimeImmutable;
use InvalidArgumentException;

/**
 * Extrahiert Zeitstempel aus einer validen ULID.
 */
final readonly class UlidParser
{
    public function __construct(
        private UlidValidator $validator
    ) {
    }

    /**
     * @param string $ulid
     * @return int Millisekunden seit Unix-Epoch
     * @throws InvalidArgumentException
     */
    public function getTimestampMs(string $ulid): int
    {
        if (! $this->validator->isValid($ulid)) {
            throw new InvalidArgumentException("Ungültige ULID: $ulid");
        }

        // erste 10 Zeichen = 10×5=50 Bit, wir brauchen die ersten 48 Bit
        $bits = '';
        for ($i = 0; $i < 10; $i++) {
            $bits .= str_pad(decbin($this->charValue($ulid[$i])), 5, '0', STR_PAD_LEFT);
        }
        $timeBits = substr($bits, 0, 48);

        return bindec($timeBits);
    }

    /**
     * @param string $ulid
     * @param Clock $clock
     * @return DateTimeImmutable
     */
    public function getDateTime(string $ulid, Clock $clock): DateTimeImmutable
    {
        $ms = $this->getTimestampMs($ulid);
        $sec = floor($ms / 1000);
        $usec = ($ms % 1000) * 1000;

        return $clock->fromString(sprintf("%d %06d", $sec, $usec), 'U u');
    }

    /**
     * Converts a Base32 character into its corresponding integer value.
     *
     * @param string $char The Base32 character to convert.
     * @return int The integer value of the specified Base32 character.
     * @throws InvalidArgumentException If the provided character is not a valid Base32 character.
     */
    private function charValue(string $char): int
    {
        static $map = null;
        if ($map === null) {
            $alphabet = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
            $map = array_flip(str_split($alphabet));
        }
        $upper = strtoupper($char);
        if (! isset($map[$upper])) {
            throw new InvalidArgumentException("Ungültiges Base32-Zeichen: $char");
        }

        return $map[$upper];
    }
}
