<?php

declare(strict_types=1);

namespace PhpWA\Client;

use PhpWA\Exception\ProtocolException;
use PhpWA\Protocol\Binary\BinaryNode;
use PhpWA\Protocol\Jid\Jid;
use PhpWA\Runtime\Deadline;

final class WhatsAppGroupDirectory implements GroupDirectoryInterface
{
    /** @var QueryExecutorInterface */ private $queries;
    /** @var MessageIdGeneratorInterface */ private $ids;
    public function __construct(QueryExecutorInterface $queries, ?MessageIdGeneratorInterface $ids = null)
    {
        $this->queries = $queries; $this->ids = $ids ?? new RandomMessageIdGenerator();
    }

    public function metadata(string $groupJid, Deadline $deadline): GroupMetadata
    {
        $group = Jid::parse($groupJid);
        if (!$group instanceof Jid || !$group->isGroup() || $group->device() !== null) {
            throw new \InvalidArgumentException('Group directory requires a group JID.');
        }
        $response = $this->queries->query(BinaryNode::children('iq', [
            'to' => (string) $group, 'type' => 'get', 'xmlns' => 'w:g2', 'id' => $this->ids->generate(),
        ], [new BinaryNode('query', ['request' => 'interactive'])]), $deadline);
        if ($response->tag() !== 'iq' || $response->attribute('type') !== 'result') {
            throw new ProtocolException('Group metadata query returned an invalid IQ result.');
        }
        $node = $response->child('group');
        if (!$node instanceof BinaryNode) { throw new ProtocolException('Group metadata result has no group.'); }
        $returnedId = $node->attribute('id');
        if ($returnedId !== null && $returnedId !== $group->user() && $returnedId !== (string) $group) {
            throw new ProtocolException('Group metadata belongs to another group.');
        }
        $mode = $node->attribute('addressing_mode');
        if ($mode !== null && $mode !== 'pn' && $mode !== 'lid') { throw new ProtocolException('Group addressing mode is invalid.'); }
        $participants = []; $mappings = [];
        foreach ($node->childrenNamed('participant') as $participantNode) {
            $jid = Jid::parse($participantNode->attribute('jid'));
            if (!$jid instanceof Jid || (!$jid->isPhoneNumber() && !$jid->isLid()
                && !$jid->isHostedPhoneNumber() && !$jid->isHostedLid())) {
                throw new ProtocolException('Group metadata contains an invalid participant JID.');
            }
            $base = new Jid($jid->user(), $jid->server(), null, $jid->domainType());
            $participants[(string) $base] = true;
            if ($jid->isPhoneNumber() || $jid->isHostedPhoneNumber()) {
                $lid = Jid::parse($participantNode->attribute('lid'));
                if ($lid instanceof Jid && ($lid->isLid() || $lid->isHostedLid())) {
                    $lid = new Jid($lid->user(), $lid->server(), null, $lid->domainType());
                    $mappings[(string) $base] = (string) $lid;
                }
            } else {
                $phone = Jid::parse($participantNode->attribute('phone_number'));
                if ($phone instanceof Jid && ($phone->isPhoneNumber() || $phone->isHostedPhoneNumber())) {
                    $phone = new Jid($phone->user(), $phone->server(), null, $phone->domainType());
                    $mappings[(string) $phone] = (string) $base;
                }
            }
            if (count($participants) > 5000) { throw new ProtocolException('Group participant list exceeds its limit.'); }
        }
        if ($participants === []) { throw new ProtocolException('Group metadata has no participants.'); }
        if ($mode === null) {
            $mode = true;
            foreach (array_keys($participants) as $participant) {
                $parsed = Jid::parse($participant);
                if (!$parsed instanceof Jid || (!$parsed->isLid() && !$parsed->isHostedLid())) { $mode = false; break; }
            }
            $mode = $mode ? 'lid' : 'pn';
        }
        return new GroupMetadata((string) $group, array_keys($participants), $mode, $mappings);
    }
}
