<?php

namespace Drush\Drupal\Commands\core;

use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Render\RendererInterface;
use Drush\Commands\DrushCommands;
use Drupal\views\Views;
use Drush\Utils\StringUtils;

class ViewsCommands extends DrushCommands
{
    protected $configFactory;

    protected $moduleHandler;

    protected $entityTypeManager;

    protected $renderer;

    /**
     * ViewsCommands constructor.
     * @param $moduleHandler
     * @param $entityTypeManager
     * @param $renderer
     */
    public function __construct(ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, EntityTypeManagerInterface $entityTypeManager, RendererInterface $renderer)
    {
        $this->moduleHandler = $moduleHandler;
        $this->entityTypeManager = $entityTypeManager;
        $this->renderer = $renderer;
        $this->configFactory = $configFactory;
    }

    public function getConfigFactory(): ConfigFactoryInterface
    {
        return $this->configFactory;
    }

    public function getModuleHandler(): ModuleHandlerInterface
    {
        return $this->moduleHandler;
    }

    public function getEntityTypeManager(): EntityTypeManagerInterface
    {
        return $this->entityTypeManager;
    }

    public function getRenderer(): RendererInterface
    {
        return $this->renderer;
    }

    /**
     * Set several Views settings to more developer-oriented values.
     *
     * @command views:dev
     *
     * @validate-module-enabled views
     * @aliases vd,views-dev
     */
    public function dev(): void
    {
        $settings = [
            'ui.show.listing_filters' => true,
            'ui.show.master_display' => true,
            'ui.show.advanced_column' => true,
            'ui.always_live_preview' => false,
            'ui.always_live_preview_button' => true,
            'ui.show.preview_information' => true,
            'ui.show.sql_query.enabled' => true,
            'ui.show.sql_query.where' => 'above',
            'ui.show.performance_statistics' => true,
            'ui.show.additional_queries' => true,
        ];

        $config = $this->getConfigFactory()->getEditable('views.settings');

        foreach ($settings as $setting => $value) {
            $config->set($setting, $value);
            // Convert boolean values into a string to print.
            if (is_bool($value)) {
                $value = $value ? 'TRUE' : 'FALSE';
            } elseif (is_string($value)) {
                // Wrap string values in quotes.
                $value = "\"$value\"";
            }
            $this->logger()->success(dt('!setting set to !value', [
                '!setting' => $setting,
                '!value' => $value
            ]));
        }

        // Save the new config.
        $config->save();

        $this->logger()->success(dt('New views configuration saved.'));
    }

    /**
     * Get a list of all views in the system.
     *
     * @command views:list
     *
     * @option name A string contained in the view's name to filter the results with.
     * @option tags A comma-separated list of views tags by which to filter the results.
     * @option status Filter views by status. Choices: enabled, disabled.
     * @usage drush vl
     *   Show a list of all available views.
     * @usage drush vl --name=blog
     *   Show a list of views which names contain 'blog'.
     * @usage drush vl --tags=tag1,tag2
     *   Show a list of views tagged with 'tag1' or 'tag2'.
     * @usage drush vl --status=enabled
     *   Show a list of enabled views.
     * @table-style default
     * @field-labels
     *   machine-name: Machine name
     *   label: Name
     *   description: Description
     *   status: Status
     *   tag: Tag
     * @default-fields machine-name,label,description,status
     * @aliases vl,views-list
     * @validate-module-enabled views
     *
     * @filter-default-field machine-name
     * @return RowsOfFields
     */
    public function vlist($options = ['name' => self::REQ, 'tags' => self::REQ, 'status' => self::REQ, 'format' => 'table'])
    {
        $disabled_views = [];
        $enabled_views = [];

        $views = $this->getEntityTypeManager()->getStorage('view')->loadMultiple();

        // Get the --name option.
        $name = StringUtils::csvToArray($options['name']);
        $with_name = !empty($name);

        // Get the --tags option.
        $tags =  StringUtils::csvToArray($options['tags']);
        $with_tags = !empty($tags);

        // Get the --status option. Store user input apart to reuse it after.
        $status = $options['status'];

        // @todo See https://github.com/consolidation/annotated-command/issues/53
        if ($status && !in_array($status, ['enabled', 'disabled'])) {
            throw new \Exception(dt('Invalid status: @status. Available options are "enabled" or "disabled"', ['@status' => $status]));
        }

        // Setup a row for each view.
        foreach ($views as $view) {
            // If options were specified, check that first mismatch push the loop to the
            // next view.
            if ($with_name && !stristr($view->id(), $name[0])) {
                continue;
            }
            if ($with_tags && !in_array($view->get('tag'), $tags)) {
                continue;
            }

            $status_bool = $status == 'enabled';
            if ($status && ($view->status() !== $status_bool)) {
                continue;
            }

            $row = [
            'machine-name' => $view->id(),
            'label' => $view->label(),
            'description' =>  $view->get('description'),
            'status' =>  $view->status() ? dt('Enabled') : dt('Disabled'),
            'tag' =>  $view->get('tag'),
            ];

            // Place the row in the appropriate array, so we can have disabled views at
            // the bottom.
            if ($view->status()) {
                  $enabled_views[] = $row;
            } else {
                  $disabled_views[] = $row;
            }
        }

        // Sort alphabetically.
        asort($disabled_views);
        asort($enabled_views);

        if (count($enabled_views) || count($disabled_views)) {
            $rows = array_merge($enabled_views, $disabled_views);
            return new RowsOfFields($rows);
        } else {
            $this->logger()->notice(dt('No views found.'));
        }
    }

    /**
     * Execute a view and show a count of the results, or the rendered HTML.
     *
     * @command views:execute
     *
     * @param string $view_name The name of the view to execute.
     * @param string $display The display ID to execute. If none specified, the default display will be used.
     * @param string $view_args A comma delimited list of values, corresponding to contextual filters.
     * @option count Display a count of the results instead of each row.
     * @option show-admin-links Show contextual admin links in the rendered markup.
     * @usage drush views:execute my_view
     *   Show the rendered HTML for the default display for the my_view View.
     * @usage drush views:execute my_view page_1 3 --count
     *   Show a count of my_view:page_1 where the first contextual filter value is 3.
     * @usage drush views:execute my_view page_1 3,foo
     *   Show the rendered HTML of my_view:page_1 where the first two contextual filter values are 3 and 'foo' respectively.
     * @validate-entity-load view view_name
     * @aliases vex,views-execute
     * @validate-module-enabled views
     *
     * @return string
     */
    public function execute(string $view_name, $display = null, $view_args = null, $options = ['count' => 0, 'show-admin-links' => false])
    {

        $view = Views::getView($view_name);

        // Set the display and execute the view.
        $view->setDisplay($display);
        $view->preExecute(StringUtils::csvToArray($view_args));
        $view->execute();

        if (empty($view->result)) {
            $this->logger()->success(dt('No results returned for this View.'));
            return null;
        } elseif ($options['count']) {
            $this->io()->writeln(count($view->result));
            return null;
        } else {
            // Don't show admin links in markup by default.
            $view->hide_admin_links = !$options['show-admin-links'];
            $build = $view->preview();
            return (string) $this->getRenderer()->renderPlain($build);
        }
    }

    /**
     * Get a list of all Views and analyze warnings.
     *
     * @command views:analyze
     * @todo Command has not  been fully tested. How to generate a message?
     * @field-labels
     *   type: Type
     *   message: Message
     * @aliases va,views-analyze
     * @validate-module-enabled views
     *
     * @return RowsOfFields
     */
    public function analyze()
    {
        $messages = null;
        $messages_count = 0;
        $rows = [];

        $views = $this->getEntityTypeManager()->getStorage('view')->loadMultiple();

        if (!empty($views)) {
            $analyzer = \Drupal::service('views.analyzer');
            foreach ($views as $view_name => $view) {
                $view = $view->getExecutable();

                if ($messages = $analyzer->getMessages($view)) {
                    $rows[] = [$messages['type'], $messages['message']];
                }
            }

            $this->logger()->success(dt('A total of @total views were analyzed and @messages problems were found.', ['@total' => count($views), '@messages' => $messages_count]));
            return new RowsOfFields($rows);
        } else {
            $this->logger()->success(dt('There are no views to analyze'));
        }
    }

    /**
     * Enable the specified views.
     *
     * @command views:enable
     * @param string $views A comma delimited list of view names.
     * @validate-entity-load view views
     * @usage drush ven frontpage,taxonomy_term
     *   Enable the frontpage and taxonomy_term views.
     * @aliases ven,views-enable
     */
    public function enable(string $views): void
    {
        $view_names = StringUtils::csvToArray($views);
        if ($views = $this->getEntityTypeManager()->getStorage('view')->loadMultiple($view_names)) {
            foreach ($views as $view) {
                $view->enable();
                $view->save();
            }
        }
        $this->logger()->success(dt('!str enabled.', ['!str' => implode(', ', $view_names)]));
    }

    /**
     * Disable the specified views.
     *
     * @command views:disable
     * @validate-entity-load view views
     * @param string $views A comma delimited list of view names.
     * @usage drush vdis frontpage taxonomy_term
     *   Disable the frontpage and taxonomy_term views.
     * @aliases vdis,views-disable
     */
    public function disable(string $views): void
    {
        $view_names = StringUtils::csvToArray($views);
        if ($views = $this->getEntityTypeManager()->getStorage('view')->loadMultiple($view_names)) {
            foreach ($views as $view) {
                $view->disable();
                $view->save();
            }
        }
        $this->logger()->success(dt('!str disabled.', ['!str' => implode(', ', $view_names)]));
    }

    /**
     * Adds a cache clear option for views.
     *
     * @hook on-event cache-clear
     */
    public function cacheClear(&$types, $include_bootstrapped_types): void
    {
        if ($include_bootstrapped_types && $this->getModuleHandler()->moduleExists('views')) {
            $types['views'] = 'views_invalidate_cache';
        }
    }
}
