<?php

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

namespace Dotclear\Plugin\aboutConfig;

use Dotclear\App;
use Dotclear\Helper\Process\TraitProcess;
use Dotclear\Helper\Html\Form\Button;
use Dotclear\Helper\Html\Form\Decimal;
use Dotclear\Helper\Html\Form\Details;
use Dotclear\Helper\Html\Form\Div;
use Dotclear\Helper\Html\Form\Form;
use Dotclear\Helper\Html\Form\Hidden;
use Dotclear\Helper\Html\Form\Input;
use Dotclear\Helper\Html\Form\Label;
use Dotclear\Helper\Html\Form\Number;
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\Submit;
use Dotclear\Helper\Html\Form\Summary;
use Dotclear\Helper\Html\Form\Table;
use Dotclear\Helper\Html\Form\Tbody;
use Dotclear\Helper\Html\Form\Td;
use Dotclear\Helper\Html\Form\Text;
use Dotclear\Helper\Html\Form\Th;
use Dotclear\Helper\Html\Form\Thead;
use Dotclear\Helper\Html\Form\Tr;
use Dotclear\Helper\Html\Html;
use Exception;

/**
 * @brief   The module backend manage process.
 * @ingroup aboutConfig
 */
class Manage
{
    use TraitProcess;

    /**
     * Initializes the page.
     */
    public static function init(): bool
    {
        if (self::status(My::checkContext(My::MANAGE))) {
            App::backend()->part = !empty($_GET['part']) && $_GET['part'] === 'global' ? 'global' : 'local';
        }

        return self::status();
    }

    /**
     * Processes the request(s).
     */
    public static function process(): bool
    {
        if (!self::status()) {
            return false;
        }

        // Local navigation
        $gs_nav = is_string($gs_nav = $_POST['gs_nav'] ?? '') ? $gs_nav : '';
        if ($gs_nav !== '') {
            My::redirect([], $gs_nav);
        }
        $ls_nav = is_string($ls_nav = $_POST['ls_nav'] ?? '') ? $ls_nav : '';
        if ($ls_nav !== '') {
            My::redirect([], $ls_nav);
        }

        // Local settings update
        if (!empty($_POST['s']) && is_array($_POST['s'])) {
            try {
                /**
                 * @var string $ns
                 */
                foreach ($_POST['s'] as $ns => $s) {
                    /**
                     * @var \Dotclear\Interface\Core\BlogWorkspaceInterface $blogws
                     */
                    $blogws = App::blog()->settings()->$ns;
                    if (is_array($s)) {
                        foreach ($s as $k => $v) {
                            $type = '';
                            if (is_array($_POST['s_type']) && is_array($_POST['s_type'][$ns])) {
                                $type = $_POST['s_type'][$ns][$k] ?? '';
                            }
                            if ($type === App::blogWorkspace()::NS_ARRAY && is_string($v)) {
                                $v = json_decode($v, true, 512, JSON_THROW_ON_ERROR);
                            }
                            $blogws->put((string) $k, $v);
                        }
                        App::blog()->triggerBlog();
                    }
                }

                App::backend()->notices()->addSuccessNotice(__('Configuration successfully updated'));
                My::redirect();
            } catch (Exception $e) {
                App::error()->add($e->getMessage());
            }
        }

        // Global settings update
        if (!empty($_POST['gs']) && is_array($_POST['gs'])) {
            try {
                foreach ($_POST['gs'] as $ns => $s) {
                    /**
                     * @var \Dotclear\Interface\Core\BlogWorkspaceInterface $blogws
                     */
                    $blogws = App::blog()->settings()->$ns;
                    if (is_array($s)) {
                        foreach ($s as $k => $v) {
                            $type = '';
                            if (is_array($_POST['gs_type']) && is_array($_POST['gs_type'][$ns])) {
                                $type = $_POST['gs_type'][$ns][$k] ?? '';
                            }
                            if ($type === App::blogWorkspace()::NS_ARRAY && is_string($v)) {
                                $v = json_decode($v, true, 512, JSON_THROW_ON_ERROR);
                            }
                            $blogws->put((string) $k, $v, null, null, true, true);
                        }
                        App::blog()->triggerBlog();
                    }
                }

                App::backend()->notices()->addSuccessNotice(__('Configuration successfully updated'));
                My::redirect([
                    'part' => 'global',
                ]);
            } catch (Exception $e) {
                App::error()->add($e->getMessage());
            }
        }

        return true;
    }

    /**
     * Renders the page.
     */
    public static function render(): void
    {
        if (!self::status()) {
            return;
        }

        App::backend()->page()->openModule(
            My::name(),
            App::backend()->page()->jsPageTabs(App::backend()->part) .
            My::jsLoad('index')
        );

        echo
        App::backend()->page()->breadcrumb(
            [
                __('System')                          => '',
                Html::escapeHTML(App::blog()->name()) => '',
                My::name()                            => '',
            ]
        ) .
        App::backend()->notices()->getNotices();

        echo
        (new Div('local'))
            ->class('multi-part')
            ->title(sprintf(__('Settings for %s'), Html::escapeHTML(App::blog()->name())))
            ->items([
                (new Text('h3', sprintf(__('Settings for %s'), Html::escapeHTML(App::blog()->name()))))->class('out-of-screen-if-js'),
                ... self::settingsTable(false),
            ])
        ->render();

        echo
        (new Div('global'))
            ->class('multi-part')
            ->title(__('Global settings'))
            ->items([
                (new Text('h3', __('Global settings')))->class('out-of-screen-if-js'),
                ... self::settingsTable(true),
            ])
        ->render();

        App::backend()->page()->helpBlock(My::id());

        App::backend()->page()->closeModule();
    }

    /**
     * Get local or global settings forms (menu + settings)
     *
     * @param      bool  $global  True if global settings
     *
     * @return     array<Set|Form>
     */
    protected static function settingsTable(bool $global = false): array
    {
        $namespaces = App::blog()->settings()->dumpWorkspaces();
        $settings   = [];
        if ($global) {
            $prefix     = 'g_';
            $prefix_id  = '#' . $prefix;
            $field_name = 'gs';
            $nav_id     = 'gs_nav';
            $submit_id  = 'gs_submit';

            foreach ($namespaces as $ns => $namespace) {
                foreach ($namespace->dumpGlobalSettings() as $k => $v) {
                    $settings[$ns][$k] = $v;
                }
            }
        } else {
            $prefix     = 'l_';
            $prefix_id  = '#' . $prefix;
            $field_name = 's';
            $nav_id     = 'ls_nav';
            $submit_id  = 'ls_submit';

            foreach ($namespaces as $ns => $namespace) {
                foreach ($namespace->dumpSettings() as $k => $v) {
                    $settings[$ns][$k] = $v;
                }
            }
        }

        $elements = [];

        ksort($settings, SORT_FLAG_CASE | SORT_STRING);
        if ($settings !== []) {
            $ns_combo = array_map(fn ($key): Option => new Option($key, $prefix_id . $key), array_keys($settings));

            $elements[] = (new Form('frm_' . $nav_id))
                ->action(App::backend()->url()->get('admin.plugin'))
                ->method('post')
                ->class('anchor-nav-sticky')
                ->fields([
                    (new Para())
                        ->class('anchor-nav')
                        ->items([
                            (new Select($nav_id))
                                ->label(new Label(__('Goto:'), Label::INSIDE_LABEL_BEFORE))
                                ->class('navigation')
                                ->items($ns_combo),
                            (new Submit($submit_id, __('Ok'))),
                            ...My::hiddenFields(),
                        ]),
                ]);
        }

        $tables = [];
        foreach ($settings as $ns => $s) {
            ksort($s);

            $rows = [];
            foreach ($s as $k => $v) {
                $strong = $global ? false : !$v['global'];
                $rows[] = self::settingLine($k, $v, $ns, $field_name, $strong);
            }
            $table = (new Div())
                ->class('table-outer')
                ->items([
                    (new Table())
                        ->class('settings')
                        ->thead(
                            (new Thead())
                                ->rows([
                                    (new Tr())->cols([
                                        (new Th())->class('nowrap')->scope('col')->text(__('Setting ID')),
                                        (new Th())->scope('col')->text(__('Value')),
                                        (new Th())->scope('col')->text(__('Type')),
                                        (new Th())->scope('col')->text(__('Description')),
                                    ]),
                                ])
                        )
                        ->tbody(
                            (new Tbody())
                                ->rows($rows)
                        ),
                ]);

            $tables[] = (new Form([$submit_id . '_' . $ns . '_form']))
                ->action(App::backend()->url()->get('admin.plugin') . '#' . ($global ? 'global' : 'local') . '.' . $prefix . $ns)
                ->method('post')
                ->fields([
                    (new Details([$prefix . 'setting_details', $prefix . $ns]))
                        ->summary(new Summary($ns))
                        ->items([
                            $table,
                            (new Para())
                                ->class('form-buttons')
                                ->items([
                                    (new Submit([$submit_id . '_' . $ns . '_post'], __('Save'))),
                                    ...My::hiddenFields(),
                                ]),
                        ]),
                ]);
        }

        $elements[] = (new Set())
            ->items([
                ... $tables,
                (new Para())
                    ->class('form-buttons')
                    ->items([
                        (new Button([$submit_id . '_back'], __('Back')))->class(['go-back','reset','hidden-if-no-js']),
                    ]),
            ]);

        return $elements;
    }

    /**
     * Return table line (tr) for a setting
     *
     * @param      string                   $id            The identifier
     * @param      array<string, mixed>     $s             The setting
     * @param      string                   $ns            The namespace
     * @param      string                   $field_name    The field name
     * @param      bool                     $strong_label  The strong label
     */
    protected static function settingLine(string $id, array $s, string $ns, string $field_name, bool $strong_label): Tr
    {
        $nid = [$field_name . '[' . $ns . '][' . $id . ']', $field_name . '_' . $ns . '_' . $id];

        $setting_type  = is_string($setting_type = $s['type'] ?? '') ? $setting_type : '';
        $setting_label = is_string($setting_label = $s['label'] ?? '') ? $setting_label : '';

        $field = match ($setting_type) {
            // Boolean
            App::blogWorkspace()::NS_BOOL => (new Select($nid))
                ->default($s['value'] ? '1' : '0')
                ->items([
                    new Option(__('yes'), '1'),
                    new Option(__('no'), '0'),
                ]),

            // Array (JSON encoded)
            App::blogWorkspace()::NS_ARRAY => (new Input($nid))
                ->value(Html::escapeHTML(json_encode($s['value'], JSON_THROW_ON_ERROR)))
                ->size(40),

            // Int
            App::blogWorkspace()::NS_INT => (new Number(
                $nid,
                null,
                null,
                is_numeric($s['value']) ? (int) $s['value'] : null
            )),

            // Float
            App::blogWorkspace()::NS_FLOAT => (new Decimal(
                $nid,
                null,
                null,
                is_numeric($s['value']) ? (float) $s['value'] : null
            )),

            // String, Text
            App::blogWorkspace()::NS_STRING => (new Input($nid))
                ->value(Html::escapeHTML(is_string($s['value']) || is_numeric($s['value']) ? (string) $s['value'] : ''))
                ->size(40),

            // Default = String
            default => (new Input($nid))
                ->value(Html::escapeHTML(is_string($s['value']) || is_numeric($s['value']) ? (string) $s['value'] : ''))
                ->size(40),
        };

        $type = (new Hidden(
            [$field_name . '_type' . '[' . $ns . '][' . $id . ']', $field_name . '_' . $ns . '_' . $id . '_type'],
            Html::escapeHTML($setting_type)
        ));

        $label = (new Label(
            sprintf($strong_label ? '<strong>%s</strong>' : '%s', Html::escapeHTML($id)),
            Label::OUTSIDE_LABEL_BEFORE,
            $field_name . '_' . $ns . '_' . $id
        ));

        return (new Tr())
            ->class('line')
            ->items([
                (new Td())->items([$label]),
                (new Td())->items([$field]),
                (new Td())->text(Html::escapeHTML($setting_type))->items([$type]),
                (new Td())->text(Html::escapeHTML($setting_label)),
            ]);
    }
}
