<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Magento\Framework\View\Asset;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\State;
use Magento\Framework\ObjectManager\ResetAfterRequestInterface;

/**
 * Helper class for static files minification related processes.
 * @api
 * @since 100.0.2
 */
class Minification implements ResetAfterRequestInterface
{
    /**
     * XML path for asset minification configuration
     */
    public const XML_PATH_MINIFICATION_ENABLED = 'dev/%s/minify_files';

    public const XML_PATH_MINIFICATION_EXCLUDES = 'dev/%s/minify_exclude';

    /**
     * @var ScopeConfigInterface
     */
    private $scopeConfig;

    /**
     * @var State
     */
    private $appState;

    /**
     * @var string
     */
    private $scope;

    /**
     * @var array
     */
    private $configCache = [];

    /**
     * @param ScopeConfigInterface $scopeConfig
     * @param State $appState
     * @param string $scope
     */
    public function __construct(ScopeConfigInterface $scopeConfig, State $appState, $scope = 'store')
    {
        $this->scopeConfig = $scopeConfig;
        $this->appState = $appState;
        $this->scope = $scope;
    }

    /**
     * Check whether asset minification is on for specified content type
     *
     * @param string $contentType
     * @return bool
     */
    public function isEnabled($contentType)
    {
        if (!isset($this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$contentType])) {
            $this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$contentType] =
                $this->appState->getMode() != State::MODE_DEVELOPER &&
                $this->scopeConfig->isSetFlag(
                    sprintf(self::XML_PATH_MINIFICATION_ENABLED, $contentType),
                    $this->scope
                );
        }

        return $this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$contentType];
    }

    /**
     * Add 'min' suffix if minification is enabled and $filename has no one.
     *
     * @param string $filename
     * @return string
     */
    public function addMinifiedSign($filename)
    {
        $extension = pathinfo($filename, PATHINFO_EXTENSION);

        if ($this->isEnabledForArea($filename) &&
            !$this->isExcluded($filename) &&
            !$this->isMinifiedFilename($filename)
        ) {
            $filename = $filename !== null ? substr($filename, 0, -strlen($extension)) : '';
            $filename = $filename . 'min.' . $extension;
        }
        return $filename;
    }

    /**
     * Remove 'min' suffix if exists and minification is enabled
     *
     * @param string $filename
     * @return string
     */
    public function removeMinifiedSign($filename)
    {
        $extension = pathinfo($filename, PATHINFO_EXTENSION);

        if ($this->isEnabledForArea($filename) &&
            !$this->isExcluded($filename) &&
            $this->isMinifiedFilename($filename)
        ) {
            $filename = $filename !== null ? substr($filename, 0, -strlen($extension) - 4) : '';
            $filename = $filename . $extension;
        }
        return $filename;
    }

    /**
     * Is Minified Filename
     *
     * @param string $filename
     * @return bool
     */
    public function isMinifiedFilename($filename)
    {
        return $filename && substr($filename, strrpos($filename, '.') - 4, 5) == '.min.';
    }

    /**
     * Is Excluded
     *
     * @param string $filename
     * @return boolean
     */
    public function isExcluded($filename)
    {
        foreach ($this->getExcludes(pathinfo($filename, PATHINFO_EXTENSION)) as $exclude) {
            if ($filename && preg_match('/' . str_replace('/', '\/', $exclude) . '/', $filename)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get Excludes
     *
     * @param string $contentType
     * @return string[]
     */
    public function getExcludes($contentType)
    {
        if (!isset($this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType])) {
            $this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType] = [];
            $key = sprintf(self::XML_PATH_MINIFICATION_EXCLUDES, $contentType);
            $excludeValues = $this->getMinificationExcludeValues($key);
            foreach ($excludeValues as $exclude) {
                if ($exclude && trim($exclude) != '') {
                    $this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType][] = trim($exclude);
                }
            }
        }
        return $this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType];
    }

    /**
     * Get minification exclude values from configuration
     *
     * @param string $key
     * @return string[]
     */
    private function getMinificationExcludeValues($key)
    {
        $configValues = $this->scopeConfig->getValue($key, $this->scope) ?? [];
        //value used to be a string separated by 'newline' separator so we need to convert it to array
        if (!is_array($configValues)) {
            $configValuesFromString = [];
            foreach (explode("\n", $configValues) as $exclude) {
                if ($exclude && trim($exclude) != '') {
                    $configValuesFromString[] = trim($exclude);
                }
            }
            $configValues = $configValuesFromString;
        }
        return array_values($configValues);
    }

    /**
     * Check whether asset minification is on for specified content type and for area
     *
     * @param string $filename
     * @return bool
     */
    private function isEnabledForArea(string $filename): bool
    {
        $area = $this->getAreaFromPath($filename);
        $extension = pathinfo($filename, PATHINFO_EXTENSION);

        if ($area !== 'adminhtml') {
            $result = $this->isEnabled($extension);
        } else {
            $cacheConfigKey = $area . '_' . $extension;
            if (!isset($this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$cacheConfigKey])) {
                $this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$cacheConfigKey] =
                    $this->appState->getMode() != State::MODE_DEVELOPER &&
                    $this->scopeConfig->isSetFlag(
                        sprintf(self::XML_PATH_MINIFICATION_ENABLED, $extension),
                        'default'
                    );
            }

            $result = $this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$cacheConfigKey];
        }
        return $result;
    }

    /**
     * Get area from the path
     *
     * @param string $filename
     * @return string
     */
    private function getAreaFromPath(string $filename): string
    {
        $area = '';
        $pathParts = explode('/', $filename);
        if (isset($pathParts[0])) {
            $area = $pathParts[0];
        }
        return $area;
    }

    /**
     * @inheritDoc
     */
    public function _resetState(): void
    {
        $this->configCache = [];
    }
}
