<?php

declare(strict_types=1);

namespace PhpWA\Config;

use InvalidArgumentException;
use PhpWA\Auth\CompanionDeviceConfig;
use PhpWA\Runtime\AccountLockProviderInterface;
use PhpWA\Runtime\RunGuardInterface;
use PhpWA\Storage\AccountStoreInterface;
use PhpWA\Transport\WebSocket\WebSocketClientOptions;

final class ClientOptions
{
    /** @var string */ private $accountId;
    /** @var string */ private $workDirectory;
    /** @var string|null */ private $storageKey;
    /** @var AccountStoreInterface|null */ private $accountStore;
    /** @var AccountLockProviderInterface|null */ private $lockProvider;
    /** @var CompanionDeviceConfig */ private $companion;
    /** @var WebSocketClientOptions */ private $webSocket;
    /** @var float */ private $operationTimeout;
    /** @var int */ private $maximumMediaBytes;
    /** @var callable|null */ private $connectionFactory;
    /** @var RunGuardInterface|null */ private $runGuard;
    /** @var float */ private $runGuardInterval;

    /**
     * $storageKey is a raw 32-byte key. It is required only when PhpWA creates
     * its encrypted file store; injected AccountStoreInterface adapters own
     * their own encryption policy.
     *
     * @param callable():\PhpWA\Client\ProtocolConnectionInterface|null $connectionFactory
     */
    public function __construct(
        string $accountId,
        string $workDirectory,
        ?string $storageKey = null,
        ?AccountStoreInterface $accountStore = null,
        ?AccountLockProviderInterface $lockProvider = null,
        ?CompanionDeviceConfig $companion = null,
        ?WebSocketClientOptions $webSocket = null,
        float $operationTimeout = 15.0,
        int $maximumMediaBytes = 268435456,
        ?callable $connectionFactory = null,
        ?RunGuardInterface $runGuard = null,
        float $runGuardInterval = 1.0
    ) {
        if ($accountId === '' || strlen($accountId) > 512 || preg_match('//u', $accountId) !== 1
            || preg_match('/[\x00-\x1f\x7f]/', $accountId) === 1) {
            throw new InvalidArgumentException('PhpWA account ID is invalid.');
        }
        if ($workDirectory === '' || $workDirectory[0] !== "\x2f" || strpos($workDirectory, "\0") !== false) {
            throw new InvalidArgumentException('PhpWA work directory must be an absolute path.');
        }
        if ($accountStore === null && (!is_string($storageKey) || strlen($storageKey) !== 32)) {
            throw new InvalidArgumentException('The encrypted file store requires a raw 32-byte storage key.');
        }
        if ($storageKey !== null && strlen($storageKey) !== 32) {
            throw new InvalidArgumentException('PhpWA storage key must contain exactly 32 bytes.');
        }
        if (!is_finite($operationTimeout) || $operationTimeout < 0.1 || $operationTimeout > 300.0
            || $maximumMediaBytes < 1 || $maximumMediaBytes > 2147483647
            || !is_finite($runGuardInterval) || $runGuardInterval < 0.1 || $runGuardInterval > 5.0) {
            throw new InvalidArgumentException('PhpWA operation limits are invalid.');
        }
        $this->accountId = $accountId; $this->workDirectory = rtrim($workDirectory, DIRECTORY_SEPARATOR);
        $this->storageKey = $storageKey; $this->accountStore = $accountStore; $this->lockProvider = $lockProvider;
        $this->companion = $companion ?? new CompanionDeviceConfig();
        $this->webSocket = $webSocket ?? new WebSocketClientOptions();
        $this->operationTimeout = $operationTimeout; $this->maximumMediaBytes = $maximumMediaBytes;
        $this->connectionFactory = $connectionFactory;
        $this->runGuard = $runGuard; $this->runGuardInterval = $runGuardInterval;
    }

    public function accountId(): string { return $this->accountId; }
    public function workDirectory(): string { return $this->workDirectory; }
    public function storageKey(): ?string { return $this->storageKey; }
    public function accountStore(): ?AccountStoreInterface { return $this->accountStore; }
    public function lockProvider(): ?AccountLockProviderInterface { return $this->lockProvider; }
    public function companion(): CompanionDeviceConfig { return $this->companion; }
    public function webSocket(): WebSocketClientOptions { return $this->webSocket; }
    public function operationTimeout(): float { return $this->operationTimeout; }
    public function maximumMediaBytes(): int { return $this->maximumMediaBytes; }
    public function connectionFactory(): ?callable { return $this->connectionFactory; }
    public function runGuard(): ?RunGuardInterface { return $this->runGuard; }
    public function runGuardInterval(): float { return $this->runGuardInterval; }

    /** Returns an immutable copy guarded by an application generation/lease. */
    public function withRunGuard(RunGuardInterface $runGuard, float $interval = 1.0): self
    {
        if (!is_finite($interval) || $interval < 0.1 || $interval > 5.0) {
            throw new InvalidArgumentException('Run guard interval must be between 0.1 and 5 seconds.');
        }
        $copy = clone $this;
        $copy->runGuard = $runGuard;
        $copy->runGuardInterval = $interval;
        return $copy;
    }
}
