<?php

declare(strict_types=1);

namespace PhpWA\Crypto\Signal;

use InvalidArgumentException;

final class SignalSession
{
    /** @var int */ private $registrationId;
    /** @var SignalRatchet */ private $ratchet;
    /** @var SignalSessionIndex */ private $index;
    /** @var array<string,SignalChain> */ private $chains;
    /** @var array<string,mixed>|null */ private $pendingPreKey;

    /** @param array<string,SignalChain> $chains @param array<string,mixed>|null $pendingPreKey */
    public function __construct(int $registrationId, SignalRatchet $ratchet, SignalSessionIndex $index, array $chains = [], ?array $pendingPreKey = null)
    {
        if ($registrationId < 0 || $registrationId > 0xffffffff || count($chains) > 32) {
            throw new InvalidArgumentException('Signal session metadata is invalid.');
        }
        foreach ($chains as $key => $chain) {
            $decoded = base64_decode($key, true);
            if (!is_string($decoded) || base64_encode($decoded) !== $key || strlen($decoded) !== 33 || !$chain instanceof SignalChain) {
                throw new InvalidArgumentException('Signal session chain map is invalid.');
            }
        }
        self::assertPending($pendingPreKey);
        $this->registrationId = $registrationId;
        $this->ratchet = $ratchet;
        $this->index = $index;
        $this->chains = $chains;
        $this->pendingPreKey = $pendingPreKey;
    }

    public function registrationId(): int { return $this->registrationId; }
    public function ratchet(): SignalRatchet { return $this->ratchet; }
    public function index(): SignalSessionIndex { return $this->index; }
    /** @return array<string,SignalChain> */ public function chains(): array { return $this->chains; }
    /** @return array<string,mixed>|null */ public function pendingPreKey(): ?array { return $this->pendingPreKey; }
    public function setPendingPreKey(?array $pending): void { self::assertPending($pending); $this->pendingPreKey = $pending; }
    public function chain(string $publicKey): ?SignalChain { return $this->chains[base64_encode($publicKey)] ?? null; }
    public function putChain(string $publicKey, SignalChain $chain): void { SignalCurve::assertPublicKey($publicKey); $this->chains[base64_encode($publicKey)] = $chain; }
    public function removeChain(string $publicKey): void { unset($this->chains[base64_encode($publicKey)]); }

    private static function assertPending(?array $pending): void
    {
        if ($pending === null) { return; }
        if (!isset($pending['signed_pre_key_id'], $pending['base_key']) || !is_int($pending['signed_pre_key_id']) || !is_string($pending['base_key'])) {
            throw new InvalidArgumentException('Signal pending pre-key metadata is invalid.');
        }
        if ($pending['signed_pre_key_id'] < 0 || $pending['signed_pre_key_id'] > 0xffffff) { throw new InvalidArgumentException('Signal pending signed pre-key ID is invalid.'); }
        SignalCurve::assertPublicKey($pending['base_key']);
        if (array_key_exists('pre_key_id', $pending) && (!is_int($pending['pre_key_id']) || $pending['pre_key_id'] < 0 || $pending['pre_key_id'] > 0xffffff)) {
            throw new InvalidArgumentException('Signal pending one-time pre-key ID is invalid.');
        }
    }
}
