<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Policy;

use Composer\Semver\VersionParser;

/**
 * @internal
 * @final
 * @readonly
 */
class MalwarePolicyConfig extends ListPolicyConfig
{
    public const NAME = 'malware';

    /**
     * @var self::BLOCK_SCOPE_*
     */
    public $blockScope;

    /**
     * @var list<string>
     */
    public $ignoreSource;

    /**
     * Malware is the one built-in list with install-time pool filtering wired up,
     * so it opts into BLOCK_SCOPE_INSTALL via the base-class hook. The per-list
     * `block-scope` config field narrows the decision further inside shouldBlock().
     */
    protected function supportsInstallBlockScope(): bool
    {
        return true;
    }

    /**
     * @param array<string, list<IgnorePackageRule>> $ignore
     * @param list<string> $ignoreSource
     * @param self::AUDIT_* $audit
     * @param self::BLOCK_SCOPE_* $blockScope
     */
    public function __construct(
        bool $block,
        string $audit,
        string $blockScope,
        array $ignore,
        array $ignoreSource
    ) {
        parent::__construct(
            self::NAME,
            $block,
            $audit,
            $ignore
        );

        $this->blockScope = $blockScope;
        $this->ignoreSource = $ignoreSource;
    }

    /**
     * @param self::BLOCK_SCOPE_* $blockScope
     */
    public function shouldBlock(string $blockScope): bool
    {
        // Defer the "is this list enabled at all + supports this scope?" question
        // to the base class; only the per-list `block-scope` narrowing is malware-specific.
        if (!parent::shouldBlock($blockScope)) {
            return false;
        }

        return $this->blockScope === self::BLOCK_SCOPE_ALL || $this->blockScope === $blockScope;
    }

    public function withBlockingDisabled()
    {
        return new static(
            false,
            $this->audit,
            $this->blockScope,
            $this->ignore,
            $this->ignoreSource
        );
    }

    public function withAudit(string $audit)
    {
        return new static(
            $this->block,
            $audit,
            $this->blockScope,
            $this->ignore,
            $this->ignoreSource
        );
    }

    /**
     * @param array<string, mixed> $policyConfig
     */
    public static function fromRawConfig(array $policyConfig, VersionParser $parser):  self
    {
        $malwareConfig = $policyConfig['malware'] ?? [];
        if ($malwareConfig === false) {
            return self::disabled();
        }

        if (!is_array($malwareConfig)) {
            $malwareConfig = [];
        }

        return new self(
            (bool) ($malwareConfig['block'] ?? true),
            $malwareConfig['audit'] ?? self::AUDIT_FAIL,
            $malwareConfig['block-scope'] ?? self::BLOCK_SCOPE_ALL,
            IgnorePackageRule::parseIgnoreMap($malwareConfig['ignore'] ?? [], $parser),
            $malwareConfig['ignore-source'] ?? []
        );
    }

    public static function disabled(): self
    {
        return new self(
            false,
            self::AUDIT_IGNORE,
            self::BLOCK_SCOPE_ALL,
            [],
            []
        );
    }
}
