<?php

declare(strict_types=1);

namespace PhpWA\Crypto\Signal;

use InvalidArgumentException;

final class SignalChain
{
    public const SENDING = 1;
    public const RECEIVING = 2;

    /** @var int */ private $counter;
    /** @var string|null */ private $chainKey;
    /** @var int */ private $type;
    /** @var array<int,string> */ private $messageKeys;

    /** @param array<int,string> $messageKeys */
    public function __construct(int $counter, ?string $chainKey, int $type, array $messageKeys = [])
    {
        if ($counter < -1 || $counter > 0xffffffff || ($chainKey !== null && strlen($chainKey) !== 32) || ($type !== self::SENDING && $type !== self::RECEIVING) || count($messageKeys) > 2000) {
            throw new InvalidArgumentException('Signal chain state is invalid.');
        }
        foreach ($messageKeys as $iteration => $key) {
            if (!is_int($iteration) || $iteration < 0 || $iteration > 0xffffffff || !is_string($key) || strlen($key) !== 32) {
                throw new InvalidArgumentException('Signal cached message key is invalid.');
            }
        }
        $this->counter = $counter;
        $this->chainKey = $chainKey;
        $this->type = $type;
        $this->messageKeys = $messageKeys;
    }

    public function counter(): int { return $this->counter; }
    public function chainKey(): ?string { return $this->chainKey; }
    public function type(): int { return $this->type; }
    /** @return array<int,string> */ public function messageKeys(): array { return $this->messageKeys; }
    public function hasMessageKey(int $counter): bool { return array_key_exists($counter, $this->messageKeys); }
    public function messageKey(int $counter): ?string { return $this->messageKeys[$counter] ?? null; }
    public function putMessageKey(int $counter, string $key): void
    {
        if (strlen($key) !== 32 || $counter < 0 || count($this->messageKeys) >= 2000) { throw new InvalidArgumentException('Signal message key is invalid or cache is full.'); }
        $this->messageKeys[$counter] = $key;
    }
    public function removeMessageKey(int $counter): ?string
    {
        if (!array_key_exists($counter, $this->messageKeys)) { return null; }
        $key = $this->messageKeys[$counter];
        unset($this->messageKeys[$counter]);
        return $key;
    }
    public function advance(string $chainKey): void
    {
        if (strlen($chainKey) !== 32 || $this->counter === 0xffffffff) { throw new InvalidArgumentException('Signal chain advance is invalid.'); }
        $this->chainKey = $chainKey;
        $this->counter++;
    }
    public function close(): void { $this->chainKey = null; }
}
