<?php
/**
 * @link https://craftcms.com/
 * @copyright Copyright (c) Pixel & Tonic, Inc.
 * @license https://craftcms.github.io/license/
 */

namespace craft\controllers;

use Craft;
use craft\base\ElementInterface;
use craft\base\PreviewableFieldInterface;
use craft\elements\conditions\ElementConditionInterface;
use craft\helpers\ArrayHelper;
use craft\helpers\Cp;
use craft\models\UserGroup;
use craft\services\ElementSources;
use craft\services\ProjectConfig;
use Illuminate\Support\Collection;
use yii\web\Response;

/**
 * The ElementIndexSettingsController class is a controller that handles various element index settings-related actions.
 * Note that all actions in the controller require an authenticated Craft session via [[allowAnonymous]].
 *
 * @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
 * @since 3.0.0
 */
class ElementIndexSettingsController extends BaseElementsController
{
    /**
     * @inheritdoc
     */
    public function beforeAction($action): bool
    {
        if (!parent::beforeAction($action)) {
            return false;
        }

        $this->requireAcceptsJson();
        $this->requireAdmin();

        return true;
    }

    /**
     * Returns all the info needed by the Customize Sources modal.
     *
     * @return Response
     */
    public function actionGetCustomizeSourcesModalData(): Response
    {
        /** @var class-string<ElementInterface> $elementType */
        $elementType = $this->elementType();
        $conditionsService = Craft::$app->getConditions();
        $view = Craft::$app->getView();

        // Global sort options
        $baseSortOptions = Collection::make($elementType::sortOptions())
            ->map(fn($option, $key) => [
                'label' => $option['label'] ?? $option,
                'attr' => $option['attribute'] ?? $option['orderBy'] ?? $key,
                'defaultDir' => $option['defaultDir'] ?? 'asc',
            ])
            ->values()
            ->all();

        // Get the source info
        $sourcesService = Craft::$app->getElementSources();
        $sources = $sourcesService->getSources($elementType, ElementSources::CONTEXT_INDEX, true);
        $multiPage = $elementType::multiPageSources();

        foreach ($sources as &$source) {
            if ($multiPage) {
                // ensure we're using the EN translation here
                $language = Craft::$app->language;
                Craft::$app->language = Craft::$app->sourceLanguage;
                $source['page'] ??= $elementType::pluralDisplayName();
                Craft::$app->language = $language;
            }

            if ($source['type'] === ElementSources::TYPE_HEADING) {
                continue;
            }

            // Sort options
            $source['sortOptions'] = array_merge(
                array_filter([
                    ($source['structureId'] ?? false)
                        ? [
                            'label' => Craft::t('app', 'Structure'),
                            'attr' => 'structure',
                            'defaultDir' => 'asc',
                        ]
                        : null,
                ]),
                $baseSortOptions,
                Collection::make($sourcesService->getSourceSortOptions($elementType, $source['key']))
                    ->map(fn($option) => [
                        'label' => $option['label'],
                        'attr' => $option['attribute'] ?? $option['orderBy'],
                        'defaultDir' => $option['defaultDir'] ?? 'asc',
                    ])
                    ->values()
                    ->all()
            );

            $defaultSortOption = null;
            $defaultSortDir = null;

            if (isset($source['defaultSort'])) {
                if (is_string($source['defaultSort'])) {
                    $defaultSortOption = ArrayHelper::firstWhere($source['sortOptions'], 'attr', $source['defaultSort']);
                } elseif (is_array($source['defaultSort']) && isset($source['defaultSort'][0])) {
                    $defaultSortOption = ArrayHelper::firstWhere($source['sortOptions'], 'attr', $source['defaultSort'][0]);
                    if ($defaultSortOption && isset($source['defaultSort'][1])) {
                        $defaultSortDir = $source['defaultSort'][1];
                    }
                }
            }

            if (!$defaultSortOption) {
                $defaultSortOption = reset($source['sortOptions']);
            }

            $source['defaultSort'] = [
                $defaultSortOption['attr'],
                $defaultSortDir ?? $defaultSortOption['defaultDir'],
            ];

            // Available custom field attributes
            $source['availableTableAttributes'] = [];
            foreach ($sourcesService->getSourceTableAttributes($elementType, $source['key']) as $key => $labelInfo) {
                $source['availableTableAttributes'][] = [$key, $labelInfo['label']];
            }

            // Selected table attributes
            $tableAttributes = $sourcesService->getTableAttributes($elementType, $source['key']);
            array_shift($tableAttributes);
            $source['tableAttributes'] = array_map(fn($a) => [$a[0], $a[1]['label']], $tableAttributes);

            if ($source['type'] === ElementSources::TYPE_CUSTOM) {
                if (isset($source['condition'])) {
                    /** @var ElementConditionInterface $condition */
                    $condition = $conditionsService->createCondition(ArrayHelper::remove($source, 'condition'));
                    $condition->mainTag = 'div';
                    $condition->name = "sources[{$source['key']}][condition]";
                    $condition->forProjectConfig = true;
                    $condition->queryParams = ['site', 'status'];
                    $condition->addRuleLabel = Craft::t('app', 'Add a filter');

                    $view->startJsBuffer();
                    $conditionBuilderHtml = $condition->getBuilderHtml();
                    $conditionBuilderJs = $view->clearJsBuffer();
                    $source += compact('conditionBuilderHtml', 'conditionBuilderJs');
                }

                if (isset($source['sites'])) {
                    $sitesService = Craft::$app->getSites();
                    $source['sites'] = array_values(array_filter(array_map(
                        fn(int $siteId) => $sitesService->getSiteById($siteId)?->uid,
                        $source['sites'] ?: [],
                    )));
                }
                if (isset($source['sites']) && $source['sites'] === false) {
                    $source['sites'] = [];
                }

                if (isset($source['userGroups']) && $source['userGroups'] === false) {
                    $source['userGroups'] = [];
                }
            }
        }
        unset($source);

        $viewModes = array_map(fn(array $viewMode) => array_merge($viewMode, [
            'iconSvg' => Cp::iconSvg($viewMode['icon'] ?? 'table'),
        ]), $elementType::indexViewModes());

        // Get the default sort options for custom sources
        $defaultSortOptions = Collection::make($sourcesService->getSourceSortOptions($elementType, 'custom:x'))
            ->map(fn(array $option) => [
                'label' => $option['label'],
                'attr' => $option['attribute'] ?? $option['orderBy'],
                'defaultDir' => $option['defaultDir'] ?? 'asc',
            ])
            ->values()
            ->all();

        // Get the available table attributes
        $availableTableAttributes = [];

        foreach ($sourcesService->getAvailableTableAttributes($elementType) as $key => $labelInfo) {
            $availableTableAttributes[] = [$key, $labelInfo['label']];
        }

        // Get previewable custom fields that should be available for all custom sources
        $customFieldAttributes = [];

        foreach (Craft::$app->getFields()->getLayoutsByType($elementType) as $fieldLayout) {
            foreach ($fieldLayout->getCustomFields() as $field) {
                if ($field instanceof PreviewableFieldInterface) {
                    $customFieldAttributes[] = ["field:$field->uid", Craft::t('site', $field->name)];
                }
            }
        }

        $condition = $elementType::createCondition();
        $condition->id = '__ID__';
        $condition->name = 'sources[__SOURCE_KEY__][condition]';
        $condition->mainTag = 'div';
        $condition->forProjectConfig = true;
        $condition->queryParams = ['site', 'status'];
        $condition->addRuleLabel = Craft::t('app', 'Add a filter');

        $view->startJsBuffer();
        $conditionBuilderHtml = $condition->getBuilderHtml();
        $conditionBuilderJs = $view->clearJsBuffer();

        $userGroups = Collection::make(Craft::$app->getUserGroups()->getAllGroups())
            ->map(fn(UserGroup $group) => [
                'label' => Craft::t('site', $group->name),
                'value' => $group->uid,
            ])
            ->all();

        $pageSettings = $sourcesService->getPageSettings($elementType);

        return $this->asJson([
            'multiPage' => $multiPage,
            'sources' => $sources,
            'pageSettings' => $pageSettings,
            'viewModes' => $viewModes,
            'baseSortOptions' => $baseSortOptions,
            'defaultSortOptions' => $defaultSortOptions,
            'availableTableAttributes' => $availableTableAttributes,
            'customFieldAttributes' => $customFieldAttributes,
            'elementTypeName' => $elementType::displayName(),
            'conditionBuilderHtml' => $conditionBuilderHtml,
            'conditionBuilderJs' => $conditionBuilderJs,
            'userGroups' => $userGroups,
            'headHtml' => $view->getHeadHtml(),
            'bodyHtml' => $view->getBodyHtml(),
        ]);
    }

    /**
     * Saves the Customize Sources modal settings.
     *
     * @return Response
     */
    public function actionSaveCustomizeSourcesModalSettings(): Response
    {
        $elementType = $this->elementType();
        $multiPage = $elementType::multiPageSources();

        // Get the old source configs
        $projectConfig = Craft::$app->getProjectConfig();
        $oldSourceConfigs = $projectConfig->get(ProjectConfig::PATH_ELEMENT_SOURCES . ".$elementType") ?? [];
        $oldSourceConfigs = ArrayHelper::index($oldSourceConfigs, 'key');

        $conditionsService = Craft::$app->getConditions();

        $sourceOrder = $this->request->getBodyParam('sourceOrder', []);
        $sourceSettings = $this->request->getBodyParam('sources', []);
        $newSourceConfigs = [];
        $disabledSourceKeys = [];

        if ($multiPage) {
            $sourcePages = $this->request->getBodyParam('sourcePages', []);
            $pageSettings = $this->request->getBodyParam('pageSettings', []);
            $sourcePageIndexes = [];
        }

        // Normalize to the way it's stored in the DB
        foreach ($sourceOrder as $key) {
            $type = match (true) {
                str_starts_with($key, 'custom:') => ElementSources::TYPE_CUSTOM,
                str_starts_with($key, 'heading:') => ElementSources::TYPE_HEADING,
                default => ElementSources::TYPE_NATIVE,
            };

            $isCustom = $type === ElementSources::TYPE_CUSTOM;
            $sourceConfig = [
                'type' => $type,
                'key' => $key,
            ];

            if (isset($sourcePages[$key])) {
                $sourceConfig['page'] = $sourcePages[$key];
            }

            // Were new settings posted?
            if (isset($sourceSettings[$key])) {
                $postedSettings = $sourceSettings[$key];

                if ($type !== ElementSources::TYPE_HEADING) {
                    $sourceConfig['tableAttributes'] = array_values(array_filter($postedSettings['tableAttributes'] ?? [])) ?: '-';
                }

                if (isset($postedSettings['defaultSort'])) {
                    $sourceConfig['defaultSort'] = $postedSettings['defaultSort'];
                }

                if (isset($postedSettings['defaultViewMode'])) {
                    $sourceConfig['defaultViewMode'] = $postedSettings['defaultViewMode'];
                }

                if ($isCustom) {
                    $sourceConfig += [
                        'label' => $postedSettings['label'],
                        'condition' => $conditionsService->createCondition($postedSettings['condition'])->getConfig(),
                    ];

                    if (isset($postedSettings['sites']) && $postedSettings['sites'] !== '*') {
                        $sourceConfig['sites'] = is_array($postedSettings['sites']) ? $postedSettings['sites'] : false;
                    }

                    if (isset($postedSettings['userGroups']) && $postedSettings['userGroups'] !== '*') {
                        $sourceConfig['userGroups'] = is_array($postedSettings['userGroups']) ? $postedSettings['userGroups'] : false;
                    }
                } elseif ($type === ElementSources::TYPE_HEADING) {
                    $sourceConfig['heading'] = $postedSettings['heading'];
                } elseif (isset($postedSettings['enabled'])) {
                    $sourceConfig['disabled'] = !$postedSettings['enabled'];
                    if ($sourceConfig['disabled']) {
                        $disabledSourceKeys[] = $key;
                    }
                }
            } elseif (isset($oldSourceConfigs[$key])) {
                $sourceConfig += $oldSourceConfigs[$key];
                if (!empty($sourceConfig['disabled'])) {
                    $disabledSourceKeys[] = $key;
                }
            } elseif ($isCustom) {
                // Ignore it
                continue;
            }

            $newSourceConfigs[] = $sourceConfig;

            if ($multiPage) {
                $sourcePageIndexes[] = array_search($sourceConfig['page'], array_keys($pageSettings));
            }
        }

        if ($multiPage) {
            /** @phpstan-ignore-next-line */
            array_multisort($sourcePageIndexes, SORT_NUMERIC, range(1, count($newSourceConfigs)), SORT_NUMERIC, $newSourceConfigs);
        }

        $sourcesService = Craft::$app->getElementSources();
        $sourcesService->saveSources($elementType, $newSourceConfigs);
        if ($multiPage) {
            $sourcesService->savePageSettings($elementType, array_map(
                fn(array $settings) => array_filter($settings, fn($setting) => $setting !== null && $setting !== ''),
                $pageSettings,
            ));
        }

        Craft::$app->getSession()->setSuccess(Craft::t('app', 'Source settings saved'));

        return $this->asSuccess(data: [
            'disabledSourceKeys' => $disabledSourceKeys,
        ]);
    }
}
