<commit_before><?php declare(strict_types=1);

namespace Room11\Jeeves\Chat\BuiltIn;

use Amp\Artax\HttpClient;
use Amp\Artax\Response as HttpResponse;
use Amp\Promise;
use Amp\Success;
use Room11\Jeeves\Chat\BuiltInCommand;
use Room11\Jeeves\Chat\Client\ChatClient;
use Room11\Jeeves\Chat\Message\Command as CommandMessage;
use Room11\Jeeves\Storage\Admin as AdminStorage;
use function Amp\all;
use function Amp\resolve;
use function Room11\DOMUtils\domdocument_process_html_docs;

class Admin implements BuiltInCommand
{
    const ACTIONS = ["add", "remove", "list"];

    private $chatClient;

    private $storage;
    /**
     * @var HttpClient
     */
    private $httpClient;

    public function __construct(ChatClient $chatClient, HttpClient $httpClient, AdminStorage $storage) {
        $this->chatClient = $chatClient;
        $this->storage    = $storage;
        $this->httpClient = $httpClient;
    }

    private function list(CommandMessage $command): Promise
    {
        return resolve(function() use($command) {
            $admins = yield $this->storage->getAll($command->getRoom());

            if ($admins['owners'] === [] && $admins['admins'] === []) {
                return $this->chatClient->postMessage($command->getRoom(), "There are no registered admins");
            }

            $userIds = array_merge($admins['owners'], $admins['admins']);
            $usernames = array_map(function($profile) {
                return $profile["username"];
            }, yield from $this->getUserData($userIds));

            sort($usernames, SORT_ASC);

            $list = implode(", ", $usernames);

            return $this->chatClient->postMessage($command->getRoom(), $list);
        });
    }

    private function add(CommandMessage $command, int $userId): Promise
    {
        return resolve(function() use($command, $userId) {
            yield $this->storage->add($command->getRoom(), $userId);

            return $this->chatClient->postMessage($command->getRoom(), "User added to the admin list.");
        });
    }

    private function remove(CommandMessage $command, int $userId): Promise
    {
        return resolve(function() use($command, $userId) {
            yield $this->storage->remove($command->getRoom(), $userId);

            return $this->chatClient->postMessage($command->getRoom(), "User removed from the admin list.");
        });
    }

    private function getUserData(array $userIds): \Generator {
        $profileURLs = array_map(function($userId) {
            return "http://stackoverflow.com/users/$userId"; // todo multi-site
        }, $userIds);

        $userProfiles = yield all($this->httpClient->requestMulti($profileURLs));

        return $this->parseUserProfiles(array_map(function(HttpResponse $response) {
            return $response->getBody();
        }, $userProfiles));
    }

    /**
     * @param string[] $userProfiles
     * @return array
     */
    private function parseUserProfiles(array $userProfiles): array {
        $userData = [];

        domdocument_process_html_docs($userProfiles, function(\DOMDocument $dom) use(&$userData) {
            $xpath = new \DOMXPath($dom);

            $usernameNodes = $xpath->query("//h2[@class='user-card-name']/text()");
            $profileNodes = $xpath->query("//link[@rel='canonical']");

            if ($usernameNodes->length > 0 && $profileNodes->length > 0) {
                /** @var \DOMText $usernameNode */
                /** @var \DOMElement $profileNode */

                $usernameNode = $usernameNodes->item(0);
                $profileNode = $profileNodes->item(0);

                $userData[] = [
                    'username' => trim($usernameNode->textContent),
                    'profile'  => $profileNode->getAttribute("href"),
                ];
            }
        });

        return $userData;
    }

    /**
     * Handle a command message
     *
     * @param CommandMessage $command
     * @return Promise
     */
    public function handleCommand(CommandMessage $command): Promise
    {
        if (!in_array($command->getParameter(0), self::ACTIONS, true)) {
            return new Success();<commit_msg>Show owners in italics in owners list<commit_after><?php declare(strict_types=1);

namespace Room11\Jeeves\Chat\BuiltIn;

use Amp\Artax\HttpClient;
use Amp\Artax\Response as HttpResponse;
use Amp\Promise;
use Amp\Success;
use Room11\Jeeves\Chat\BuiltInCommand;
use Room11\Jeeves\Chat\Client\ChatClient;
use Room11\Jeeves\Chat\Message\Command as CommandMessage;
use Room11\Jeeves\Storage\Admin as AdminStorage;
use function Amp\all;
use function Amp\resolve;
use function Room11\DOMUtils\domdocument_process_html_docs;

class Admin implements BuiltInCommand
{
    const ACTIONS = ["add", "remove", "list"];

    private $chatClient;

    private $storage;
    /**
     * @var HttpClient
     */
    private $httpClient;

    public function __construct(ChatClient $chatClient, HttpClient $httpClient, AdminStorage $storage) {
        $this->chatClient = $chatClient;
        $this->storage    = $storage;
        $this->httpClient = $httpClient;
    }

    private function list(CommandMessage $command): Promise
    {
        return resolve(function() use($command) {
            $admins = yield $this->storage->getAll($command->getRoom());

            if ($admins['owners'] === [] && $admins['admins'] === []) {
                return $this->chatClient->postMessage($command->getRoom(), "There are no registered admins");
            }

            $userIds = array_merge($admins['owners'], $admins['admins']);
            $userNames = array_map(function($profile) use($admins) {
                return in_array($profile['id'], $admins['owners'])
                    ? '*' . $profile['username'] . '*'
                    : $profile['username'];
            }, yield from $this->getUserData($userIds));

            sort($userNames, SORT_ASC);

            $list = implode(", ", $userNames);

            return $this->chatClient->postMessage($command->getRoom(), $list);
        });
    }

    private function add(CommandMessage $command, int $userId): Promise
    {
        return resolve(function() use($command, $userId) {
            yield $this->storage->add($command->getRoom(), $userId);

            return $this->chatClient->postMessage($command->getRoom(), "User added to the admin list.");
        });
    }

    private function remove(CommandMessage $command, int $userId): Promise
    {
        return resolve(function() use($command, $userId) {
            yield $this->storage->remove($command->getRoom(), $userId);

            return $this->chatClient->postMessage($command->getRoom(), "User removed from the admin list.");
        });
    }

    private function getUserData(array $userIds): \Generator {
        $profileURLs = array_map(function($userId) {
            return "http://stackoverflow.com/users/$userId"; // todo multi-site
        }, $userIds);

        $userProfiles = yield all($this->httpClient->requestMulti($profileURLs));

        return $this->parseUserProfiles(array_map(function(HttpResponse $response) {
            return $response->getBody();
        }, $userProfiles));
    }

    /**
     * @param string[] $userProfiles
     * @return array
     */
    private function parseUserProfiles(array $userProfiles): array {
        $userData = [];

        domdocument_process_html_docs($userProfiles, function(\DOMDocument $dom) use(&$userData) {
            $xpath = new \DOMXPath($dom);

            $usernameNodes = $xpath->query("//h2[@class='user-card-name']/text()");
            $profileNodes = $xpath->query("//link[@rel='canonical']");

            if ($usernameNodes->length > 0 && $profileNodes->length > 0) {
                /** @var \DOMText $usernameNode */
                /** @var \DOMElement $profileNode */

                $usernameNode = $usernameNodes->item(0);
                $profileNode = $profileNodes->item(0);
                preg_match('#/users/([0-9]+)#', $profileNode->getAttribute("href"), $match);

                $userData[] = [
                    'id'       => (int)$match[1],
                    'username' => trim($usernameNode->textContent),
                    'profile'  => $profileNode->getAttribute("href"),
                ];
            }
        });

        return $userData;
    }

    /**
     * Handle a command message
     *
     * @param CommandMessage $command
     * @return Promise
     */
    public function handleCommand(CommandMessage $command): Promise
    {
        if (!in_array($command->getParameter(0), self::ACTIONS, true)) {
            return new Success();
{'fraction_non_alphanumeric': 0.14891895016764944, 'fraction_numerical': 0.00474043241993294, 'mean_word_length': 2.5523613963039016, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5365407', 'n_tokens_mistral': 2451, 'n_tokens_neox': 2366, 'n_words': 484}