<?php

/**
 * @package    Grav\Console\Gpm
 *
 * @copyright  Copyright (c) 2015 - 2026 Trilby Media, LLC. All rights reserved.
 * @license    MIT License; see LICENSE file for details.
 */

namespace Grav\Console\Gpm;

use Grav\Common\GPM\Remote\AbstractPackageCollection;
use Grav\Common\GPM\Remote\Package;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Remote\Packages;
use Grav\Common\GPM\Remote\Plugins;
use Grav\Common\GPM\Remote\Themes;
use Grav\Common\Utils;
use Grav\Console\GpmCommand;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputOption;
use function count;

/**
 * Class IndexCommand
 * @package Grav\Console\Gpm
 */
class IndexCommand extends GpmCommand
{
    /** @var Packages */
    protected $data;
    /** @var GPM */
    protected $gpm;
    /** @var array */
    protected $options;

    /**
     * @return void
     */
    protected function configure(): void
    {
        $this
            ->setName('index')
            ->addOption(
                'force',
                'f',
                InputOption::VALUE_NONE,
                'Force re-fetching the data from remote'
            )
            ->addOption(
                'filter',
                'F',
                InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
                'Allows to limit the results based on one or multiple filters input. This can be either portion of a name/slug or a regex'
            )
            ->addOption(
                'themes-only',
                'T',
                InputOption::VALUE_NONE,
                'Filters the results to only Themes'
            )
            ->addOption(
                'plugins-only',
                'P',
                InputOption::VALUE_NONE,
                'Filters the results to only Plugins'
            )
            ->addOption(
                'updates-only',
                'U',
                InputOption::VALUE_NONE,
                'Filters the results to Updatable Themes and Plugins only'
            )
            ->addOption(
                'installed-only',
                'I',
                InputOption::VALUE_NONE,
                'Filters the results to only the Themes and Plugins you have installed'
            )
            ->addOption(
                'sort',
                's',
                InputOption::VALUE_REQUIRED,
                'Allows to sort (ASC) the results. SORT can be either "name", "slug", "author", "date"',
                'date'
            )
            ->addOption(
                'desc',
                'D',
                InputOption::VALUE_NONE,
                'Reverses the order of the output.'
            )
            ->addOption(
                'enabled',
                'e',
                InputOption::VALUE_NONE,
                'Filters the results to only enabled Themes and Plugins.'
            )
            ->addOption(
                'disabled',
                'd',
                InputOption::VALUE_NONE,
                'Filters the results to only disabled Themes and Plugins.'
            )
            ->setDescription('Lists the plugins and themes available for installation')
            ->setHelp('The <info>index</info> command lists the plugins and themes available for installation')
        ;
    }

    /**
     * @return int
     */
    protected function serve(): int
    {
        $input = $this->getInput();
        $this->options = $input->getOptions();
        $this->gpm = new GPM($this->options['force']);
        $this->displayGPMRelease();
        $this->data = $this->gpm->getRepository();

        $data = $this->filter($this->data);

        $io = $this->getIO();

        if (count($data) === 0) {
            $io->writeln('No data was found in the GPM repository stored locally.');
            $io->writeln('Please try clearing cache and running the <green>bin/gpm index -f</green> command again');
            $io->writeln('If this doesn\'t work try tweaking your GPM system settings.');
            $io->newLine();
            $io->writeln('For more help go to:');
            $io->writeln(' -> <yellow>https://learn.getgrav.org/troubleshooting/common-problems#cannot-connect-to-the-gpm</yellow>');

            return 1;
        }

        foreach ($data as $type => $packages) {
            $io->writeln('<green>' . strtoupper((string) $type) . '</green> [ ' . count($packages) . ' ]');

            $packages = $this->sort($packages);

            if (!empty($packages)) {
                $io->section('Packages table');
                $table = new Table($io);
                $table->setHeaders(['Count', 'Name', 'Slug', 'Version', 'Compat', 'Installed', 'Enabled']);

                $index = 0;
                foreach ($packages as $slug => $package) {
                    $row = [
                        'Count' => $index++ + 1,
                        'Name' => '<cyan>' . Utils::truncate($package->name, 20, false, ' ', '...') . '</cyan> ',
                        'Slug' => $slug,
                        'Version'=> $this->version($package),
                        'Compat' => $this->compatBadges($package),
                        'Installed' => $this->installed($package),
                        'Enabled' => $this->enabled($package),
                    ];

                    $table->addRow($row);
                }

                $table->render();
            }

            $io->newLine();
        }

        $io->writeln('You can either get more informations about a package by typing:');
        $io->writeln("    <green>{$this->argv} info <cyan><package></cyan></green>");
        $io->newLine();
        $io->writeln('Or you can install a package by typing:');
        $io->writeln("    <green>{$this->argv} install <cyan><package></cyan></green>");
        $io->newLine();

        return 0;
    }

    /**
     * @param Package $package
     * @return string
     */
    private function version(Package $package): string
    {
        $list      = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}();
        $package   = $list[$package->slug] ?? $package;
        $type      = ucfirst(preg_replace('/s$/', '', (string) $package->package_type));
        $updatable = $this->gpm->{'is' . $type . 'Updatable'}($package->slug);
        $installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug);
        $local     = $this->gpm->{'getInstalled' . $type}($package->slug);

        if (!$installed || !$updatable) {
            $version   = $installed ? $local->version : $package->version;
            return "v<green>{$version}</green>";
        }

        return "v<red>{$package->version}</red> <cyan>-></cyan> v<green>{$package->available}</green>";
    }

    /**
     * @param Package $package
     * @return string
     */
    private function installed(Package $package): string
    {
        $type      = ucfirst(preg_replace('/s$/', '', $package->package_type));
        $method = 'is' . $type . 'Installed';
        $installed = $this->gpm->{$method}($package->slug);

        return !$installed ? '<magenta>not installed</magenta>' : '<cyan>installed</cyan>';
    }

    /**
     * @param Package $package
     * @return string
     */
    private function enabled(Package $package): string
    {
        $type      = ucfirst(preg_replace('/s$/', '', $package->package_type));
        $method = 'is' . $type . 'Installed';
        $installed = $this->gpm->{$method}($package->slug);

        $result = '';
        if ($installed) {
            $method = 'is' . $type . 'Enabled';
            $enabled = $this->gpm->{$method}($package->slug);
            if ($enabled === true) {
                $result = '<cyan>enabled</cyan>';
            } elseif ($enabled === false) {
                $result = '<red>disabled</red>';
            }
        }

        return $result;
    }

    /**
     * @param Package $package
     * @return string
     */
    private function compatBadges(Package $package): string
    {
        $type = ucfirst(preg_replace('/s$/', '', $package->package_type));
        $method = 'is' . $type . 'Installed';
        $installed = $this->gpm->{$method}($package->slug);

        if ($installed) {
            $local = $this->gpm->{'getInstalled' . $type}($package->slug);
            $compat = $local->compatibility ?? null;
        } else {
            $compat = $package->compatibility ?? null;
        }

        if (!is_array($compat) || empty($compat['grav'])) {
            return '<blue>1.7</blue>';
        }

        $badges = [];
        if (in_array('1.7', $compat['grav'], true)) {
            $badges[] = '<blue>1.7</blue>';
        }
        if (in_array('1.8', $compat['grav'], true)) {
            $badges[] = '<green>1.8</green>';
        }
        if (in_array('2.0', $compat['grav'], true)) {
            $badges[] = '<magenta>2.0</magenta>';
        }

        return implode(' ', $badges);
    }

    /**
     * @param Packages $data
     * @return Packages
     */
    public function filter(Packages $data): Packages
    {
        // filtering and sorting
        if ($this->options['plugins-only']) {
            unset($data['themes']);
        }
        if ($this->options['themes-only']) {
            unset($data['plugins']);
        }

        $filter = [
            $this->options['desc'],
            $this->options['disabled'],
            $this->options['enabled'],
            $this->options['filter'],
            $this->options['installed-only'],
            $this->options['updates-only'],
        ];

        if (count(array_filter($filter))) {
            foreach ($data as $type => $packages) {
                foreach ($packages as $slug => $package) {
                    $filter = true;

                    // Filtering by string
                    if ($this->options['filter']) {
                        $filter = preg_grep('/(' . implode('|', $this->options['filter']) . ')/i', [$slug, $package->name]);
                    }

                    // Filtering updatables only
                    if ($filter && ($this->options['installed-only'] || $this->options['enabled'] || $this->options['disabled'])) {
                        $method = ucfirst(preg_replace('/s$/', '', (string) $package->package_type));
                        $function = 'is' . $method . 'Installed';
                        $filter = $this->gpm->{$function}($package->slug);
                    }

                    // Filtering updatables only
                    if ($filter && $this->options['updates-only']) {
                        $method = ucfirst(preg_replace('/s$/', '', (string) $package->package_type));
                        $function = 'is' . $method . 'Updatable';
                        $filter = $this->gpm->{$function}($package->slug);
                    }

                    // Filtering enabled only
                    if ($filter && $this->options['enabled']) {
                        $method = ucfirst(preg_replace('/s$/', '', (string) $package->package_type));

                        // Check if packaged is enabled.
                        $function = 'is' . $method . 'Enabled';
                        $filter = $this->gpm->{$function}($package->slug);
                    }

                    // Filtering disabled only
                    if ($filter && $this->options['disabled']) {
                        $method = ucfirst(preg_replace('/s$/', '', (string) $package->package_type));

                        // Check if package is disabled.
                        $function = 'is' . $method . 'Enabled';
                        $enabled_filter = $this->gpm->{$function}($package->slug);

                        // Apply filtering results.
                        if (!( $enabled_filter === false)) {
                            $filter = false;
                        }
                    }

                    if (!$filter) {
                        unset($data[$type][$slug]);
                    }
                }
            }
        }

        return $data;
    }

    /**
     * @param AbstractPackageCollection|Plugins|Themes $packages
     * @return array
     */
    public function sort(AbstractPackageCollection $packages): array
    {
        $key = $this->options['sort'];

        // Sorting only works once.
        return $packages->sort(
            fn($a, $b) => match ($key) {
                'author' => strcmp((string) $a->{$key}['name'], (string) $b->{$key}['name']),
                default => strcmp((string) $a->$key, (string) $b->$key),
            },
            $this->options['desc'] ? true : false
        );
    }
}
