<?php

declare(strict_types=1);

namespace PhpWA\Protocol\Jid;

use InvalidArgumentException;

final class Jid
{
    /** @var string */
    private $user;
    /** @var string */
    private $server;
    /** @var int|null */
    private $device;
    /** @var int */
    private $domainType;

    public function __construct(string $user, string $server, ?int $device = null, int $domainType = JidDomain::WHATSAPP)
    {
        if ($server === '' || strlen($user) > 256 || strlen($server) > 128
            || strpos($user, '@') !== false || strpos($server, '@') !== false
            || preg_match('//u', $user . $server) !== 1
            || preg_match('/[\x00-\x20\x7f]/', $user . $server) === 1
            || preg_match('/^[A-Za-z0-9.-]+$/D', $server) !== 1) {
            throw new InvalidArgumentException('Invalid JID parts.');
        }
        if ($device !== null && ($device < 0 || $device > 255)) {
            throw new InvalidArgumentException('AD JID device must fit one byte.');
        }
        if ($domainType < 0 || $domainType > 255) {
            throw new InvalidArgumentException('JID domain type must fit one byte.');
        }
        $this->user = $user;
        $this->server = $server;
        $this->device = $device;
        $this->domainType = $domainType;
    }

    public static function parse(?string $jid): ?self
    {
        if (!is_string($jid) || $jid === '' || strlen($jid) > 512 || substr_count($jid, '@') !== 1
            || preg_match('//u', $jid) !== 1 || preg_match('/[\x00-\x20\x7f]/', $jid) === 1) {
            return null;
        }
        $separator = strpos($jid, '@');
        if ($separator === false) {
            return null;
        }
        $server = substr($jid, $separator + 1);
        if ($server === '') {
            return null;
        }
        $combined = substr($jid, 0, $separator);
        if (substr_count($combined, ':') > 1 || substr_count($combined, '_') > 1) { return null; }
        $deviceParts = explode(':', $combined, 2);
        $userAgent = $deviceParts[0];
        $device = null;
        if (isset($deviceParts[1])) {
            if (preg_match('/^(0|[1-9][0-9]{0,2})$/D', $deviceParts[1]) !== 1 || (int) $deviceParts[1] > 255) {
                return null;
            }
            $device = (int) $deviceParts[1];
        }
        $agentParts = explode('_', $userAgent, 2);
        $user = $agentParts[0];
        $agent = $agentParts[1] ?? '';

        $domainType = JidDomain::WHATSAPP;
        if ($server === 'lid') {
            $domainType = JidDomain::LID;
        } elseif ($server === 'hosted') {
            $domainType = JidDomain::HOSTED;
        } elseif ($server === 'hosted.lid') {
            $domainType = JidDomain::HOSTED_LID;
        } elseif ($agent !== '') {
            if (preg_match('/^(0|[1-9][0-9]{0,2})$/D', $agent) !== 1 || (int) $agent > 255) { return null; }
            $domainType = (int) $agent;
        }
        try {
            return new self($user, $server, $device, $domainType);
        } catch (InvalidArgumentException $error) {
            return null;
        }
    }

    /** Builds a direct-chat JID from a human-formatted E.164 phone number. */
    public static function fromPhoneNumber(string $phoneNumber): self
    {
        if (preg_match('/\A[+0-9 ()-]{7,40}\z/D', $phoneNumber) !== 1) {
            throw new InvalidArgumentException('WhatsApp phone number contains unsupported characters.');
        }
        $digits = preg_replace('/[^0-9]/', '', $phoneNumber);
        if (!is_string($digits) || preg_match('/\A[1-9][0-9]{6,14}\z/D', $digits) !== 1) {
            throw new InvalidArgumentException('WhatsApp phone number must be a valid international number without a trunk prefix.');
        }
        return new self($digits, 's.whatsapp.net');
    }

    public static function encode($user, string $server, ?int $device = null, ?int $agent = null): string
    {
        $userPart = $user === null ? '' : (string) $user;
        $agentPart = $agent !== null && $agent !== 0 ? '_' . $agent : '';
        $devicePart = $device !== null && $device !== 0 ? ':' . $device : '';
        return $userPart . $agentPart . $devicePart . '@' . $server;
    }

    public function user(): string { return $this->user; }
    public function server(): string { return $this->server; }
    public function device(): ?int { return $this->device; }
    public function domainType(): int { return $this->domainType; }

    public function normalized(): string
    {
        return self::encode($this->user, $this->server === 'c.us' ? 's.whatsapp.net' : $this->server);
    }

    public function withDeviceFrom(self $source): self
    {
        return new self($this->user, $this->server, $source->device, $this->domainType);
    }

    public function isGroup(): bool { return $this->server === 'g.us'; }
    public function isBroadcast(): bool { return $this->server === 'broadcast'; }
    public function isStatusBroadcast(): bool { return $this->user === 'status' && $this->server === 'broadcast'; }
    public function isNewsletter(): bool { return $this->server === 'newsletter'; }
    public function isLid(): bool { return $this->server === 'lid'; }
    public function isPhoneNumber(): bool { return $this->server === 's.whatsapp.net'; }
    public function isHostedPhoneNumber(): bool { return $this->server === 'hosted'; }
    public function isHostedLid(): bool { return $this->server === 'hosted.lid'; }
    public function isMetaAi(): bool { return $this->server === 'bot'; }

    public function sameUser(self $other): bool
    {
        return hash_equals($this->user, $other->user);
    }

    public function __toString(): string
    {
        return self::encode($this->user, $this->server, $this->device);
    }
}
