<?php

/**
 * @package Dotclear
 * @subpackage Backend
 *
 * @copyright Olivier Meunier & Association Dotclear
 * @copyright AGPL-3.0
 */
declare(strict_types=1);

namespace Dotclear\Process\Backend;

use Dotclear\App;
use Dotclear\Database\MetaRecord;
use Dotclear\Helper\Html\Form\Checkbox;
use Dotclear\Helper\Html\Form\Div;
use Dotclear\Helper\Html\Form\Form;
use Dotclear\Helper\Html\Form\Hidden;
use Dotclear\Helper\Html\Form\Label;
use Dotclear\Helper\Html\Form\Li;
use Dotclear\Helper\Html\Form\Link;
use Dotclear\Helper\Html\Form\None;
use Dotclear\Helper\Html\Form\Note;
use Dotclear\Helper\Html\Form\Option;
use Dotclear\Helper\Html\Form\Para;
use Dotclear\Helper\Html\Form\Select;
use Dotclear\Helper\Html\Form\Set;
use Dotclear\Helper\Html\Form\Span;
use Dotclear\Helper\Html\Form\Submit;
use Dotclear\Helper\Html\Form\Text;
use Dotclear\Helper\Html\Form\Ul;
use Dotclear\Helper\Html\Html;
use Dotclear\Helper\Process\TraitProcess;
use Exception;

/**
 * @since 2.27 Before as admin/categories.php
 */
class Categories
{
    use TraitProcess;

    /**
     * @var Option[] $categories_combo
     */
    protected static array $categories_combo;

    public static function init(): bool
    {
        App::backend()->page()->check(App::auth()->makePermissions([
            App::auth()::PERMISSION_CATEGORIES,
        ]));

        return self::status(true);
    }

    public static function process(): bool
    {
        if (!empty($_POST['delete'])
            && is_array($_POST['delete'])
        ) {
            // Remove a category
            $keys   = array_keys($_POST['delete']);
            $cat_id = (int) $keys[0];
            $name   = '';

            // Check if category to delete exists
            $rs = App::blog()->getCategory($cat_id);
            if ($rs->isEmpty()) {
                App::backend()->notices()->addErrorNotice(__('This category does not exist.'));
                App::backend()->url()->redirect('admin.categories');
            } else {
                $name = $rs->strField('cat_title');
            }

            try {
                // Delete category
                App::blog()->delCategory($cat_id);
                App::backend()->notices()->addSuccessNotice(sprintf(
                    __('The category "%s" has been successfully deleted.'),
                    Html::escapeHTML($name)
                ));
                App::backend()->url()->redirect('admin.categories');
            } catch (Exception $e) {
                App::error()->add($e->getMessage());
            }
        }

        if (!empty($_POST['mov'])
            && is_array($_POST['mov'])
            && !empty($_POST['mov_cat'])
            && is_array($_POST['mov_cat'])
        ) {
            // move post into a category
            try {
                // Check if category where to move posts exists
                $keys   = array_keys($_POST['mov']);
                $cat_id = (int) $keys[0];

                $mov_cat = is_numeric($mov_cat = $_POST['mov_cat'][$cat_id]) ? (int) $mov_cat : 0;
                $name    = '';

                if ($mov_cat !== 0) {
                    $rs = App::blog()->getCategory($mov_cat);
                    if ($rs->isEmpty()) {
                        throw new Exception(__('Category where to move entries does not exist.'));
                    }

                    $name = $rs->strField('cat_title');
                }

                // Move posts
                if ($mov_cat !== $cat_id) {
                    App::blog()->changePostsCategory($cat_id, $mov_cat);
                }

                App::backend()->notices()->addSuccessNotice(sprintf(
                    __('The entries have been successfully moved to category "%s"'),
                    Html::escapeHTML($name)
                ));
                App::backend()->url()->redirect('admin.categories');
            } catch (Exception $e) {
                App::error()->add($e->getMessage());
            }
        }

        if (!empty($_POST['save_feedback'])) {
            // Update feedback opening/closing for each category
            $no_feedbacks     = isset($_POST['cat_no_feedback']) && is_array($no_feedbacks = $_POST['cat_no_feedback']) ? $no_feedbacks : [];
            $cats_no_feedback = array_keys($no_feedbacks);
            App::blog()->settings()->get('system')->put('cats_no_feedback', $cats_no_feedback, App::blogWorkspace()::NS_ARRAY);

            App::backend()->notices()->addSuccessNotice(__('The opening/closing of feedbacks for categories have been successfully saved.'));
            App::backend()->url()->redirect('admin.categories');
        }

        if (!empty($_POST['save_order'])
            && !empty($_POST['categories_order'])
            && is_string($_POST['categories_order'])
        ) {
            // Update order
            $categories = json_decode($_POST['categories_order'], null, 512, JSON_THROW_ON_ERROR);
            if (is_array($categories)) {
                foreach ($categories as $category) {
                    if ($category instanceof \stdClass
                        && !empty($category->item_id)
                        && is_numeric($category->item_id)
                        && !empty($category->left)
                        && is_numeric($category->left)
                        && !empty($category->right)
                        && is_numeric($category->right)
                    ) {
                        App::blog()->updCategoryPosition((int) $category->item_id, (int) $category->left, (int) $category->right);
                    }
                }
            }

            App::backend()->notices()->addSuccessNotice(__('Categories have been successfully reordered.'));
            App::backend()->url()->redirect('admin.categories');
        }

        if (!empty($_POST['reset'])) {
            // Reset order
            try {
                App::blog()->resetCategoriesOrder();
                App::backend()->notices()->addSuccessNotice(__('Categories order has been successfully reset.'));
                App::backend()->url()->redirect('admin.categories');
            } catch (Exception $e) {
                App::error()->add($e->getMessage());
            }
        }

        return true;
    }

    public static function render(): void
    {
        $rs = App::blog()->getCategories();

        $starting_script = '';

        if (!App::auth()->prefs()->get('accessibility')->getBool('nodragdrop')
            && App::auth()->check(App::auth()->makePermissions([
                App::auth()::PERMISSION_CATEGORIES,
            ]), App::blog()->id())
            && $rs->count() > 1) {
            $starting_script .= App::backend()->page()->jsLoad('js/jquery/jquery-ui.custom.js');
            $starting_script .= App::backend()->page()->jsLoad('js/jquery/jquery.ui.touch-punch.js');
            $starting_script .= App::backend()->page()->jsLoad('js/jquery/jquery.mjs.nestedSortable.js');
        }

        $starting_script .= App::backend()->page()->jsConfirmClose('form-categories');
        $starting_script .= App::backend()->page()->jsLoad('js/_categories.js');

        App::backend()->page()->open(
            __('Categories'),
            $starting_script,
            App::backend()->page()->breadcrumb(
                [
                    Html::escapeHTML(App::blog()->name()) => '',
                    __('Categories')                      => '',
                ]
            )
        );

        if (!empty($_GET['del'])) {
            App::backend()->notices()->success(__('The category has been successfully removed.'));
        }

        if (!empty($_GET['reord'])) {
            App::backend()->notices()->success(__('Categories have been successfully reordered.'));
        }

        if (!empty($_GET['move'])) {
            App::backend()->notices()->success(__('Entries have been successfully moved to the category you choose.'));
        }

        self::$categories_combo = App::backend()->combos()->getCategoriesCombo($rs);

        echo (new Para())
            ->class('new-stuff')
            ->items([
                (new Link())
                    ->class(['button', 'add'])
                    ->href(App::backend()->url()->get('admin.category'))
                    ->text(__('New category')),
            ])
            ->render();

        $parts = [];

        if ($rs->isEmpty()) {
            $parts[] = (new Note())
                ->text(__('No category so far.'));
        } else {
            // List of categories
            $list = (new Div('categories'))
                ->items([
                    self::categorieList(1, $rs),
                ]);

            // Actions
            $actions = [];
            $message = (new None());

            if (App::auth()->check(App::auth()->makePermissions([
                App::auth()::PERMISSION_CATEGORIES,
            ]), App::blog()->id())) {
                $actions[] = (new Set())
                    ->items([
                        (new Submit('save_feedback', __('Save the opening/closing of feedbacks'))),
                        (new Hidden(['process'], 'Categories')),
                    ]);
            }

            if (App::auth()->check(App::auth()->makePermissions([
                App::auth()::PERMISSION_CATEGORIES,
            ]), App::blog()->id()) && $rs->count() > 1) {
                if (!App::auth()->prefs()->get('accessibility')->getBool('nodragdrop')) {
                    $message = (new Note())
                        ->class(['form-note', 'hidden-if-no-js'])
                        ->text(__('To rearrange categories order, move items by drag and drop, then click on “Save categories order” button.'));
                }

                $actions[] = (new Set())
                    ->items([
                        (new Span())
                            ->class('hidden-if-no-js')
                            ->items([
                                (new Hidden('categories_order', '')),
                                (new Submit(['save_order', 'save-set-order'], __('Save categories order'))),
                            ]),
                    ]);
            }

            if (App::auth()->check(App::auth()->makePermissions([
                App::auth()::PERMISSION_CATEGORIES,
            ]), App::blog()->id())) {
                $actions[] = (new Set())
                    ->items([
                        (new Submit(['reset'], __('Reorder all categories on the top level')))
                            ->class('reset'),
                        (new Hidden(['process'], 'Categories')),
                    ]);
            }

            $action = (new Div())
                ->items([
                    $message,
                    (new Para())
                        ->class('form-buttons')
                        ->items([
                            ...$actions,
                            App::nonce()->formNonce(),
                        ]),
                ]);

            $parts[] = (new Form('form-categories'))
                ->action(App::backend()->url()->get('admin.categories'))
                ->method('post')
                ->fields([
                    $list,
                    $action,
                ]);
        }

        echo (new Div())
            ->items($parts)
        ->render();

        App::backend()->page()->helpBlock('core_categories');
        App::backend()->page()->close();
    }

    /**
     * Return Ul with current level categories as LI or None if nothing to list
     *
     * @param      int                            $level             The level
     * @param      MetaRecord                     $rs                The recordset
     */
    private static function categorieList(int $level, MetaRecord $rs): Ul|None
    {
        $categories = [];

        if ($rs->isEnd() && $rs->count() === 1) {
            // Only one category
            if ($rs->intField('level') >= $level) {
                $categories[] = self::categorieLine($rs);
            }
        } else {
            while (!$rs->isEnd() && $rs->fetch()) {
                if ($rs->intField('level') < $level) {
                    // Back to upper level
                    // @phpstan-ignore if.alwaysFalse
                    if ($rs->isEnd()) {
                        // Clear end flag of recordset
                        $rs->moveEnd();
                    }

                    $rs->movePrev();

                    break;
                }

                $categories[] = self::categorieLine($rs);
            }
        }

        return count($categories) ?
            (new Ul())->items($categories) :
            (new None());
    }

    /**
     * Return an LI with the category, including sub-categories if any
     *
     * @param      MetaRecord                     $rs                The recordset
     */
    private static function categorieLine(MetaRecord $rs): Li
    {
        $cats_no_feedback = is_array($cats_no_feedback = app::blog()->settings()->get('system')->get('cats_no_feedback')) ? $cats_no_feedback : [];
        $cat_no_feedback  = in_array($rs->intField('cat_id'), $cats_no_feedback, true);

        // Category info
        $category = (new Set())
            ->items([
                (new Para())
                    ->class(['cat-title', 'form-buttons'])
                    ->items([
                        (new Link())
                            ->href(App::backend()->url()->get('admin.category', ['id' => $rs->intField('cat_id')]))
                            ->text(Html::escapeHTML($rs->strField('cat_title'))),
                    ]),
                (new Para())
                    ->class(['cat-feedback'])
                    ->items([
                        (new Checkbox(['cat_no_feedback[' . $rs->intField('cat_id') . ']'], $cat_no_feedback))
                            ->value(1)
                            ->label(
                                new Label(__('Disable feedbacks'), Label::IL_FT)
                            ),
                    ]),
                (new Para())
                    ->class(['cat-nb-posts'])
                    ->items([
                        (new Text(null, '(')),
                        (new Link())
                            ->href(App::backend()->url()->get('admin.posts', ['cat_id' => $rs->intField('cat_id')]))
                            ->text(sprintf(__('1 entry', '%d entries', $rs->intField('nb_post')), $rs->intField('nb_post'))),
                        (new Text(null, ', ' . __('total:') . ' ' . $rs->intField('nb_total') . ')')),
                    ]),
                (new Para())
                    ->class(['cat-url', 'form-buttons'])
                    ->items([
                        (new Text(null, __('URL:'))),
                        (new Text('code', Html::escapeHTML($rs->strField('cat_url')))),
                    ]),
            ]);

        // Move entries button
        $move = (new None());
        if ($rs->intField('nb_total') > 0) {
            $options = array_filter(self::$categories_combo, fn (Option $cat): bool => $cat->value !== ((string) $rs->intField('cat_id')));
            if ($options !== []) {
                $move = (new Set())
                    ->items([
                        (new Select(['mov_cat[' . $rs->intField('cat_id') . ']', 'mov_cat_' . $rs->intField('cat_id')]))
                            ->items($options)
                            ->label(new Label(__('Move entries to'), Label::IL_TF)),
                        (new Submit(['mov[' . $rs->intField('cat_id') . ']'], __('Ok'))),
                    ]);
            }
        }

        // Delete button
        $classes = ['delete'];
        $delete  = (new Submit(['delete[' . $rs->intField('cat_id') . ']'], __('Delete category')));
        if ($rs->intField('nb_total') > 0) {
            $delete
                ->disabled(true);
            $classes[] = 'disabled';
        }

        $delete
            ->class($classes);

        return (new Li('cat_' . $rs->intField('cat_id')))
            ->class(['cat-line', 'clearfix'])
            ->items([
                $category,
                (new Para())
                    ->class(['cat-buttons', 'form-buttons'])
                    ->items([
                        $move,
                        $delete,
                    ]),
                self::categorieList($rs->intField('level') + 1, $rs),
            ]);
    }
}
