<?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\Policy;

use Exception;
use Piwik\Tracker\Cache;
use Piwik\Plugin\Manager;
use Piwik\Policy\Exceptions\CompliancePolicyNotFoundException;
use Piwik\Settings\Interfaces\PolicyComparisonInterface;
use Piwik\Settings\Interfaces\SettingValueInterface;
use Piwik\Settings\Interfaces\Traits\Getters\ConfigGetterTrait;
use Piwik\Settings\Interfaces\Traits\Getters\CustomGetterTrait;
use Piwik\Settings\Interfaces\Traits\Getters\MeasurableGetterTrait;
use Piwik\Settings\Interfaces\Traits\Getters\OptionGetterTrait;
use Piwik\Settings\Interfaces\Traits\Getters\SystemGetterTrait;
use Piwik\Settings\Measurable\MeasurableProperty;
use Piwik\Settings\Measurable\MeasurableSetting;
use Piwik\Settings\Plugin\SystemConfigSetting;
use Piwik\Settings\Plugin\SystemSetting;
use Piwik\Settings\Setting;
use Piwik\Tracker\Config\ThirdPartyCookies;
class PolicyManager
{
    public const SETTING_TYPE_SYSTEM = 'system';
    public const SETTING_TYPE_MEASURABLE = 'measurable';
    public const SETTING_TYPE_CUSTOM = 'custom';
    public const SETTING_TYPE_OPTION = 'option';
    public const SETTING_TYPE_CONFIG = 'config';
    // TODO: In Matomo 6, get*Name methods will change visibility from protected to public,
    //  so we will need to replace the method names here
    private static $settingTypesMap = [self::SETTING_TYPE_SYSTEM => ['trait' => SystemGetterTrait::class, 'method' => 'getSystemSettingShortName'], self::SETTING_TYPE_MEASURABLE => ['trait' => MeasurableGetterTrait::class, 'method' => 'getMeasurableSettingShortName'], self::SETTING_TYPE_CUSTOM => ['trait' => CustomGetterTrait::class, 'method' => 'getCustomSettingShortName'], self::SETTING_TYPE_OPTION => ['trait' => OptionGetterTrait::class, 'method' => 'getOptionSettingShortName'], self::SETTING_TYPE_CONFIG => ['trait' => ConfigGetterTrait::class, 'method' => 'getConfigSettingShortName']];
    /**
     * @return array<class-string<CompliancePolicy>>
     */
    public static function getAllPolicies() : array
    {
        return [\Piwik\Policy\CnilPolicy::class];
    }
    /**
     * @return array<array<string, string>>
     */
    public static function getAllPoliciesDetails() : array
    {
        $policies = static::getAllPolicies();
        return array_map(function ($policyClass) {
            return $policyClass::getDetails();
        }, $policies);
    }
    /**
     * @return class-string<CompliancePolicy>|null
     */
    public static function getPolicyByName(string $policyName) : ?string
    {
        $policies = static::getAllPolicies();
        foreach ($policies as $policyClass) {
            if ($policyName === $policyClass::getName()) {
                return $policyClass;
            }
        }
        return null;
    }
    /**
     * @return array<class-string<PolicyComparisonInterface<mixed>&SettingValueInterface<mixed>>>
     */
    protected static function getAllSettings(?string $settingType = null) : array
    {
        $settings = Manager::getInstance()->findMultipleComponents('Settings', SettingValueInterface::class);
        $underPolicy = [];
        // Add core specific settings
        $settings[] = ThirdPartyCookies::class;
        foreach ($settings as $setting) {
            if (!is_a($setting, PolicyComparisonInterface::class, \true)) {
                continue;
            }
            if ($settingType && !in_array(self::$settingTypesMap[$settingType]['trait'], class_uses($setting), \true)) {
                continue;
            }
            $underPolicy[] = $setting;
        }
        return $underPolicy;
    }
    /**
     * @param class-string<CompliancePolicy> $policyClass
     * @return array<class-string<PolicyComparisonInterface<mixed>&SettingValueInterface<mixed>>>
     */
    public static function getAllControlledSettings(string $policyClass, ?int $idSite = null, ?string $settingType = null) : array
    {
        $settings = static::getAllSettings($settingType);
        $underPolicy = [];
        foreach ($settings as $setting) {
            if (!$setting::isControlledBySpecificPolicy($policyClass, $idSite)) {
                continue;
            }
            $underPolicy[] = $setting;
        }
        return $underPolicy;
    }
    /**
     * @param class-string<CompliancePolicy> $policyClass
     * @throws CompliancePolicyNotFoundException when $policyClass is not a valid policy
     */
    public static function isPolicyActive(string $policyClass, ?int $idSite = null) : bool
    {
        self::checkPolicyIsValid($policyClass);
        return $policyClass::isActive($idSite);
    }
    /**
     * @param class-string<CompliancePolicy> $policyClass
     * @throws \Exception when $policyClass is not a valid policy
     */
    public static function isPolicyConfigControlled(string $policyClass) : bool
    {
        self::checkPolicyIsValid($policyClass);
        return $policyClass::isConfigControlled();
    }
    /**
     * @param class-string<CompliancePolicy> $policyClass
     * @return array<array<string>> of [['title' => (string) 'TITLE', 'note' => (string) 'NOTE']]
     * @throws CompliancePolicyNotFoundException when $policyClass is not a valid policy
     */
    public static function getAllUnknownSettings(string $policyClass) : array
    {
        self::checkPolicyIsValid($policyClass);
        return $policyClass::getUnknownSettings();
    }
    /**
     * @param class-string<CompliancePolicy> $policyClass
     * @throws CompliancePolicyNotFoundException when $policyClass is not a valid policy
     */
    private static function checkPolicyIsValid(string $policyClass) : void
    {
        if (!is_a($policyClass, \Piwik\Policy\CompliancePolicy::class, \true)) {
            throw new CompliancePolicyNotFoundException('Invalid compliance policy.');
        }
    }
    /**
     * @param class-string<CompliancePolicy> $policyClass
     * @throws CompliancePolicyNotFoundException when $policyClass is not a valid policy
     */
    public static function setPolicyActiveStatus(string $policyClass, bool $isActive, ?int $idSite = null) : void
    {
        self::checkPolicyIsValid($policyClass);
        $policyClass::setActiveStatus($idSite, $isActive);
        if (!is_null($idSite)) {
            Cache::deleteCacheWebsiteAttributes($idSite);
        }
        Cache::deleteTrackerCache();
    }
    public static function storePolicySettingValuesInTrackerCache(array &$cacheContent, int $idSite) : array
    {
        $settings = static::getAllSettings();
        foreach ($settings as $setting) {
            $cacheContent[$setting] = $setting::getInstance($idSite)->getValue();
        }
        return $cacheContent;
    }
    /**
     * Return setting type from a given Setting instance, including subclasses
     */
    public static function getSettingTypeFromSettingClass(Setting $setting) : ?string
    {
        $map = [MeasurableSetting::class => self::SETTING_TYPE_MEASURABLE, MeasurableProperty::class => self::SETTING_TYPE_MEASURABLE, SystemSetting::class => self::SETTING_TYPE_SYSTEM, SystemConfigSetting::class => self::SETTING_TYPE_CONFIG];
        foreach ($map as $class => $type) {
            if ($setting instanceof $class) {
                return $type;
            }
        }
        return null;
    }
    /**
     * For a given setting name, return an information on policies that may control the setting and its required value.
     *
     * @return array<string, array{
     *      requiredValue: mixed
     *  }>
     * @throws Exception
     */
    public static function getCompliancePoliciesControllingASetting(string $settingName, ?int $idSite = null, ?string $settingType = null) : array
    {
        if (!$settingType || !in_array($settingType, array_keys(self::$settingTypesMap), \true)) {
            return [];
        }
        $policies = static::getAllPolicies();
        $settings = [];
        foreach ($policies as $policyClass) {
            if (\false === $policyClass::isActive($idSite)) {
                continue;
            }
            $controlledSettings = self::getAllControlledSettings($policyClass, $idSite, $settingType);
            foreach ($controlledSettings as $controlledSetting) {
                if ($settingName === call_user_func([$controlledSetting, self::$settingTypesMap[$settingType]['method']])) {
                    $settings[$policyClass::getName()] = ['requiredValue' => $controlledSetting::getPolicyRequirements()[$policyClass]];
                }
            }
        }
        return $settings;
    }
}
