<?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\CoreConsole\Commands;

use Piwik\AssetManager;
use Piwik\AssetManager\UIAssetFetcher\PluginUmdAssetFetcher;
use Piwik\Development;
use Piwik\Metrics\Formatter;
use Piwik\Plugin;
use Piwik\Plugin\ConsoleCommand;
use Piwik\Plugin\Manager;
use Piwik\ProxyHttp;
use Piwik\SettingsPiwik;
use Piwik\Theme;
/**
 * @phpstan-type TotalsType array{merged: int, gzip: int}
 */
class ComputeJsAssetSize extends ConsoleCommand
{
    /**
     * @var TotalsType
     */
    private $totals = ['merged' => 0, 'gzip' => 0];
    protected function configure()
    {
        $this->setName('development:compute-js-asset-size');
        $this->setDescription('Generates production assets and computes the size of the resulting code.');
        $this->addNoValueOption('no-delete', null, 'Do not delete files after creating them.');
        $this->addRequiredValueOption('plugin', null, 'For submodule plugins and 3rd party plugins.');
    }
    public function isEnabled()
    {
        return SettingsPiwik::isGitDeployment();
    }
    protected function doExecute() : int
    {
        $input = $this->getInput();
        $output = $this->getOutput();
        $noDelete = $input->getOption('no-delete');
        /** @var null|string $plugin */
        $plugin = $input->getOption('plugin');
        if (!empty($plugin) && !Manager::getInstance()->isValidPluginName($plugin)) {
            throw new \Exception('Invalid plugin name: ' . $plugin);
        }
        $this->checkDevelopmentModeDisabled();
        $this->ensureThirdPartyPluginsActivated($plugin);
        $output->writeln("Building and printing sizes of built JS assets...");
        $fetcher = $this->makeUmdFetcher();
        $this->deleteMergedAssets();
        $this->buildAssets($fetcher);
        $output->writeln('');
        $this->printCurrentGitHashAndBranch($plugin);
        $output->writeln('');
        $this->printFilesizes($fetcher);
        if (!$noDelete) {
            $this->deleteMergedAssets();
        }
        return self::SUCCESS;
    }
    private function ensureThirdPartyPluginsActivated(?string $plugin = null) : void
    {
        $expectedPluginsLoadedAndActivated = ["CorePluginsAdmin", "CoreAdminHome", "CoreHome", "WebsiteMeasurable", "IntranetMeasurable", "Diagnostics", "CoreVisualizations", "Proxy", "API", "Widgetize", "Transitions", "LanguagesManager", "Actions", "Dashboard", "MultiSites", "Referrers", "UserLanguage", "DevicesDetection", "Goals", "Ecommerce", "SEO", "Events", "UserCountry", "GeoIp2", "VisitsSummary", "VisitFrequency", "VisitTime", "VisitorInterest", "RssWidget", "Feedback", "Monolog", "Login", "TwoFactorAuth", "UsersManager", "SitesManager", "Installation", "CoreUpdater", "CoreConsole", "ScheduledReports", "UserCountryMap", "Live", "PrivacyManager", "ImageGraph", "Annotations", "MobileMessaging", "Overlay", "SegmentEditor", "Insights", "Morpheus", "Contents", "TestRunner", "BulkTracking", "Resolution", "DevicePlugins", "Heartbeat", "Intl", "UserId", "CustomJsTracker", "Tour", "PagePerformance", "CustomDimensions", "TagManager", "AbTesting", "ActivityLog", "Bandwidth", "Cohorts", "CustomAlerts", "CustomReports", "CustomVariables", "DeviceDetectorCache", "FormAnalytics", "Funnels", "GoogleAnalyticsImporter", "InvalidateReports", "MarketingCampaignsReporting", "MediaAnalytics", "MultiChannelConversionAttribution", "QueuedTracking", "RollUpReporting", "SearchEngineKeywordsPerformance", "UsersFlow", "VisitorGenerator", "WhiteLabel", "WooCommerceAnalytics", "AdvertisingConversionExport", "AnonymousPiwikUsageMeasurement"];
        if ($plugin) {
            $expectedPluginsLoadedAndActivated[] = $plugin;
        }
        if (is_file(PIWIK_INCLUDE_PATH . '/plugins/CoreVue/plugin.json')) {
            $expectedPluginsLoadedAndActivated[] = "CoreVue";
        }
        $expectedPluginsLoadedAndActivated = array_unique($expectedPluginsLoadedAndActivated);
        $pluginsLoadedAndActivated = Manager::getInstance()->getPluginsLoadedAndActivated();
        $pluginsLoadedAndActivated = array_map(function (Plugin $p) {
            return $p->getPluginName();
        }, $pluginsLoadedAndActivated);
        $missingPlugins = array_diff($expectedPluginsLoadedAndActivated, $pluginsLoadedAndActivated);
        if (!empty($missingPlugins)) {
            throw new \Exception("Activate the following plugins before running this command: " . implode(", ", $missingPlugins));
        }
    }
    private function buildAssets(PluginUmdAssetFetcher $fetcher) : void
    {
        AssetManager::getInstance()->getMergedCoreJavaScript();
        AssetManager::getInstance()->getMergedNonCoreJavaScript();
        $chunks = $fetcher->getChunkFiles();
        foreach ($chunks as $chunk) {
            AssetManager::getInstance()->getMergedJavaScriptChunk($chunk->getChunkName());
        }
    }
    private function deleteMergedAssets() : void
    {
        AssetManager::getInstance()->removeMergedAssets();
    }
    private function printFilesizes(PluginUmdAssetFetcher $fetcher) : void
    {
        $fileSizes = [];
        $mergedCore = AssetManager::getInstance()->getMergedCoreJavaScript();
        $fileSizes[] = $this->getFileSizeRow($mergedCore);
        $mergedNonCore = AssetManager::getInstance()->getMergedNonCoreJavaScript();
        $fileSizes[] = $this->getFileSizeRow($mergedNonCore);
        $chunks = $fetcher->getChunkFiles();
        foreach ($chunks as $chunk) {
            $chunkAsset = AssetManager::getInstance()->getMergedJavaScriptChunk($chunk->getChunkName());
            $fileSizes[] = $this->getFileSizeRow($chunkAsset);
        }
        $fileSizes[] = [];
        $fileSizes[] = ['Total', $this->getFormattedSize($this->totals['merged']), $this->getFormattedSize($this->totals['gzip'])];
        $this->renderTable(['File', 'Size', 'Size (gzipped)'], $fileSizes);
    }
    /**
     * @param key-of<TotalsType> $type
     */
    private function getFileSize(string $fileLocation, string $type) : string
    {
        $size = (int) filesize($fileLocation);
        $this->totals[$type] += $size;
        return $this->getFormattedSize($size);
    }
    private function getFormattedSize(int $size) : string
    {
        $formatter = new Formatter();
        return $formatter->getPrettySizeFromBytes($size, 'K', 2);
    }
    private function checkDevelopmentModeDisabled() : void
    {
        if (Development::isEnabled()) {
            throw new \Exception("This command is to estimate production build sizes, so development mode must be disabled for it.");
        }
    }
    private function getGzippedFileSize(string $path) : string
    {
        $data = file_get_contents($path);
        $data = ProxyHttp::gzencode($data);
        if (\false === $data) {
            throw new \Exception('compressing file ' . $path . ' failed');
        }
        $compressedPath = dirname($path) . '/' . basename($path) . '.gz';
        file_put_contents($compressedPath, $data);
        return $this->getFileSize($compressedPath, 'gzip');
    }
    private function printCurrentGitHashAndBranch(?string $plugin = null) : void
    {
        $branchName = trim(shell_exec("git rev-parse --abbrev-ref HEAD") ?: '');
        $lastCommit = trim(shell_exec("git log --pretty=format:'%h' -n 1") ?: '');
        $pluginSuffix = '';
        if ($plugin) {
            $prefix = 'cd ' . escapeshellarg(PIWIK_INCLUDE_PATH . '/plugins/' . $plugin) . '; ';
            $pluginBranchName = trim(shell_exec($prefix . 'git rev-parse --abbrev-ref HEAD') ?: '');
            $pluginLastCommit = trim(shell_exec($prefix . 'git log --pretty=format:\'%h\' -n 1') ?: '');
            $pluginSuffix = " [{$plugin}: {$pluginBranchName} ({$pluginLastCommit})]";
        }
        $this->getOutput()->writeln("<info>{$branchName} ({$lastCommit}){$pluginSuffix}</info>");
    }
    private function makeUmdFetcher() : PluginUmdAssetFetcher
    {
        $plugins = Manager::getInstance()->getPluginsLoadedAndActivated();
        $pluginNames = array_map(function ($p) {
            return $p->getPluginName();
        }, $plugins);
        $theme = Manager::getInstance()->getThemeEnabled();
        if (!empty($theme)) {
            $theme = new Theme();
        }
        return new PluginUmdAssetFetcher($pluginNames, $theme, null);
    }
    /**
     * @return array{0: string, 1: string, 2: string}
     */
    private function getFileSizeRow(AssetManager\UIAsset $asset) : array
    {
        return [$asset->getRelativeLocation(), $this->getFileSize($asset->getAbsoluteLocation(), 'merged'), $this->getGzippedFileSize($asset->getAbsoluteLocation())];
    }
}
