<?php

declare(strict_types=1);

namespace PhpWA\AppState;

use InvalidArgumentException;
use PhpWA\Exception\ProtocolException;

final class AppStateHashState
{
    /** @var int */ private $version;
    /** @var string */ private $hash;

    public function __construct(int $version, string $hash)
    {
        if ($version < 0 || strlen($hash) !== 128) { throw new InvalidArgumentException('App-state hash state is invalid.'); }
        $this->version = $version; $this->hash = $hash;
    }

    public static function initial(): self { return new self(0, str_repeat("\0", 128)); }

    public static function decode(string $json): self
    {
        try { $data = json_decode($json, true, 8, JSON_THROW_ON_ERROR); }
        catch (\Throwable $error) { throw new ProtocolException('Stored app-state hash state is malformed.', 0, $error); }
        if (!is_array($data) || array_keys($data) !== ['v', 'version', 'hash'] || $data['v'] !== 1
            || !is_int($data['version']) || !is_string($data['hash'])) {
            throw new ProtocolException('Stored app-state hash state has an invalid shape.');
        }
        $hash = base64_decode($data['hash'], true);
        if (!is_string($hash)) { throw new ProtocolException('Stored app-state hash is not valid base64.'); }
        try { return new self($data['version'], $hash); }
        catch (InvalidArgumentException $error) { throw new ProtocolException('Stored app-state hash violates its bounds.', 0, $error); }
    }

    public function encode(): string
    {
        try { $json = json_encode(['v' => 1, 'version' => $this->version, 'hash' => base64_encode($this->hash)], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); }
        catch (\Throwable $error) { throw new ProtocolException('Unable to serialize app-state hash state.', 0, $error); }
        if (!is_string($json)) { throw new ProtocolException('Unable to serialize app-state hash state.'); }
        return $json;
    }

    public function version(): int { return $this->version; }
    public function hash(): string { return $this->hash; }
}
