<?php

/**
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */

namespace Piwik\Plugins\Referrers;

use Piwik\Cache;
use Piwik\Common;
use Piwik\Config;
use Piwik\Option;
use Piwik\Piwik;
use Piwik\SettingsPiwik;
use Piwik\Singleton;

/**
 * Contains methods to access AI assistant definition data.
 */
class AIAssistant extends Singleton
{
    public const OPTION_STORAGE_NAME = 'AIAssistantDefinitions';

    /** @var string location of definition file (relative to PIWIK_INCLUDE_PATH) */
    public const DEFINITION_FILE = '/vendor/matomo/searchengine-and-social-list/AIAssistants.yml';

    /** @var null|array<string, string> */
    protected $definitionList = null;

    /**
     * Returns list of AI assistants by URL
     *
     * @return array<string, string>
     */
    public function getDefinitions(): array
    {
        $cache = Cache::getEagerCache();
        $cacheId = 'AIAssistant-' . self::OPTION_STORAGE_NAME;

        if ($cache->contains($cacheId)) {
            /** @var array<string, string> $list */
            $list = $cache->fetch($cacheId);
        } else {
            $list = $this->loadDefinitions();
            $cache->save($cacheId, $list);
        }

        return $list;
    }

    /**
     * @return array<string, string>
     */
    private function loadDefinitions(): array
    {
        if ($this->definitionList === null) {
            $referrerDefinitionSyncOpt = Config::getInstance()->General['enable_referrer_definition_syncs'];

            if ($referrerDefinitionSyncOpt == 1) {
                $this->loadRemoteDefinitions();
            } else {
                $this->loadLocalYmlData();
            }
        }

        Piwik::postEvent('Referrer.addAIAssistantUrls', [&$this->definitionList]);

        return $this->definitionList ?? [];
    }

    /**
     * Loads definitions sourced from remote yaml with a local fallback
     */
    private function loadRemoteDefinitions(): void
    {
        // Read first from the auto-updated list in database
        $list = Option::get(self::OPTION_STORAGE_NAME);

        if ($list && SettingsPiwik::isInternetEnabled()) {
            $list = Common::safe_unserialize(base64_decode($list));
            if (!empty($list) && is_array($list)) {
                $this->definitionList = $list;
            }
        } else {
            // Fallback to reading the bundled list
            $this->loadLocalYmlData();
            Option::set(self::OPTION_STORAGE_NAME, base64_encode(serialize($this->definitionList)));
        }
    }

    /**
     * Loads the definition data from the local definitions file
     */
    private function loadLocalYmlData(): void
    {
        $yml = file_get_contents(PIWIK_INCLUDE_PATH . self::DEFINITION_FILE);
        if ($yml !== false) {
            $this->definitionList = $this->loadYmlData($yml);
        }
    }

    /**
     * Parses the given YML string and caches the resulting definitions
     *
     * @return null|array<string, string>
     */
    public function loadYmlData(string $yml): ?array
    {
        $ais = \Spyc::YAMLLoadString($yml);

        if (is_array($ais)) {
            $this->definitionList = $this->transformData($ais);
        }

        return $this->definitionList;
    }

    /**
     * @param array<string, string[]> $ais
     * @return array<string, string>
     */
    protected function transformData(array $ais): array
    {
        $urlToName = [];
        foreach ($ais as $name => $urls) {
            if (empty($urls) || !is_array($urls)) {
                continue;
            }

            foreach ($urls as $url) {
                $urlToName[$url] = $name;
            }
        }
        return $urlToName;
    }

    /**
     * Returns true if a URL belongs to an AI assistant, false if otherwise.
     *
     * @param string $url The URL to check.
     * @param string|null $aiAssistantName The name of the AI assistant to check for, or false to check for any.
     */
    public function isAIAssistantUrl(string $url, ?string $aiAssistantName = null): bool
    {
        foreach ($this->getDefinitions() as $domain => $name) {
            if (preg_match('#(^|[\.\/])' . preg_quote($domain) . '(\/|$)#', $url) && ($aiAssistantName === null || $name === $aiAssistantName)) {
                return true;
            }
        }

        return false;
    }


    /**
     * Gets AI assistant name from URL.
     */
    public function getAIAssistantFromDomain(string $url): string
    {
        foreach ($this->getDefinitions() as $domain => $name) {
            if (preg_match('#(^|[\.\/])' . preg_quote($domain) . '(\/|$)#', $url)) {
                return $name;
            }
        }

        return Piwik::translate('General_Unknown');
    }

    /**
     * Returns the main url of the AI assistant the given url matches
     */
    public function getMainUrl(string $url): string
    {
        $ai  = $this->getAIAssistantFromDomain($url);
        foreach ($this->getDefinitions() as $domain => $name) {
            if ($name === $ai) {
                return $domain;
            }
        }
        return $url;
    }

    /**
     * Returns the main url of the given AI assistant
     */
    public function getMainUrlFromName(string $aiAssistant): ?string
    {
        foreach ($this->getDefinitions() as $domain => $name) {
            if ($name === $aiAssistant) {
                return $domain;
            }
        }
        return null;
    }


    /**
     * Return AI assistant logo path by URL
     *
     * @see plugins/Morpheus/icons/dist/aiAssistants/
     */
    public function getLogoFromUrl(string $url): string
    {
        $ai = $this->getAIAssistantFromDomain($url);
        $ais = $this->getDefinitions();

        $filePattern = 'plugins/Morpheus/icons/dist/aiAssistants/%s.png';

        $aiDomains = array_keys($ais, $ai);
        foreach ($aiDomains as $domain) {
            if (file_exists(PIWIK_INCLUDE_PATH . '/' . sprintf($filePattern, $domain))) {
                return sprintf($filePattern, $domain);
            }
        }

        return sprintf($filePattern, 'xx');
    }
}
