<?php

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

namespace Dotclear\Core\Backend;

use Dotclear\App;
use Dotclear\Database\MetaRecord;
use Dotclear\Helper\Date;
use Dotclear\Helper\Html\Form\Optgroup;
use Dotclear\Helper\Html\Form\Option;
use Dotclear\Helper\Html\Html;

/**
 * Admin combo library
 *
 * Dotclear utility class that provides reuseable combos across all admin
 */
class Combos
{
    /**
     * Returns an hierarchical categories combo from a category record
     *
     * @param      MetaRecord   $categories     The categories
     * @param      bool         $include_empty  Includes empty categories
     * @param      bool         $use_url        Use url or ID
     *
     * @return     array<Option>   The categories combo.
     */
    public static function getCategoriesCombo(MetaRecord $categories, bool $include_empty = true, bool $use_url = false): array
    {
        $categories_combo = [];
        if ($include_empty) {
            $categories_combo = [new Option(__('(No cat)'), '')];
        }

        while ($categories->fetch()) {
            $level = $categories->intField('level');
            if ($level < 1) {
                $level = 1;
            }

            $padding = str_repeat('&nbsp;', ($level - 1) * 4);
            $option  = new Option(
                $padding . Html::escapeHTML($categories->strField('cat_title')) . ' (' . $categories->intField('nb_post') . ')',
                ($use_url ? $categories->strField('cat_url') : (string) $categories->intField('cat_id'))
            );

            if ($level - 1 !== 0) {
                $option->class('sub-option' . ($level - 1));
            }

            $categories_combo[] = $option;
        }

        return $categories_combo;
    }

    /**
     * Returns available post status combo.
     *
     * @deprecated  since 2.33, use App::status()->post()->combo() instead
     *
     * @return     array<string, string>  The post statuses combo.
     */
    public static function getPostStatusesCombo(): array
    {
        return App::status()->post()->combo();
    }

    /**
     * Returns an users combo from a users record.
     *
     * @param      MetaRecord  $users  The users
     *
     * @return     array<string, string>   The users combo.
     */
    public static function getUsersCombo(MetaRecord $users): array
    {
        $users_combo = [];
        while ($users->fetch()) {
            $user_id = $users->strField('user_id');
            $user_cn = App::users()->getUserCN(
                $user_id,
                $users->strField('user_name', true),
                $users->strField('user_firstname', true),
                $users->strField('user_displayname', true)
            );

            if ($user_cn !== $user_id) {
                $user_cn .= ' (' . $user_id . ')';
            }

            $users_combo[$user_cn] = $user_id;
        }

        return $users_combo;
    }

    /**
     * Gets the dates combo.
     *
     * @param      MetaRecord  $dates  The dates
     * @param      bool        $as_component    If true, return array of OptGroup/Options rather than array of values
     *
     * @return     ($as_component is false ? array<string, string> : array<array-key, Option|Optgroup>)   The dates combo.
     */
    public static function getDatesCombo(MetaRecord $dates, bool $as_component = false): array
    {
        $dt_m_combo = [];

        if ($as_component) {
            // Collect years and months

            /**
             * Array of year (string) and for each year array of month (string) and timestamp (int)
             *
             * @var array<string, array<string, int>>
             */
            $years = [];
            while ($dates->fetch()) {
                $year  = $dates->year();
                $month = $dates->month();
                $ts    = $dates->ts();
                if ($year !== '' && $month !== '') {
                    $years[$year][$month] = $ts;
                }
            }

            foreach ($years as $year => $months) {
                $dt_m_combo[] = (new Optgroup((string) $year))
                    ->items([
                        ... array_map(
                            fn (string $month, int $ts): Option => (new Option(Date::str('%B %Y', $ts), $year . $month)),
                            array_keys($months),
                            array_values($months)
                        ),
                    ]);
            }

            return $dt_m_combo;
        }

        while ($dates->fetch()) {
            $ts    = $dates->ts();
            $year  = $dates->year();
            $month = $dates->month();

            $dt_m_combo[Date::str('%B %Y', $ts)] = $year . $month;
        }

        return $dt_m_combo;
    }

    /**
     * Gets the langs combo.
     *
     * @param      MetaRecord  $langs           The langs
     * @param      bool        $with_available  If false, only list items from record - if true, also list available languages
     * @param      bool        $as_component    If true, return array of OptGroup/Options rather than array of values
     *
     * @return     ($as_component is false ? array<string, mixed> : array<array-key, OptGroup|Option>)   The langs combo.
     */
    public static function getLangsCombo(MetaRecord $langs, bool $with_available = false, bool $as_component = false): array
    {
        /**
         * @var array<string, string> language code, language full label including code
         */
        $all_langs = App::lang()->getISOcodes(false, true);
        $rec_langs = [];
        while ($langs->fetch()) {
            $post_lang = $langs->strField('post_lang');
            if ($post_lang !== '') {
                $rec_langs[$post_lang] = $all_langs[$post_lang] ?? $post_lang;
            }
        }

        if ($with_available) {
            if ($as_component) {
                $main_langs  = array_map(fn (string $code, string $label): Option => (new Option($label, $code))->lang($code), array_keys($rec_langs), array_values($rec_langs));
                $other_langs = array_map(fn (string $code, string $label): Option => (new Option($label, $code))->lang($code), array_keys(array_diff_key($all_langs, $rec_langs)), array_values(array_diff_key($all_langs, $rec_langs)));
                $langs_combo = [
                    (new Option('', '')),
                    (new Optgroup(__('Most used')))
                        ->items($main_langs),
                    (new Optgroup(__('Available')))
                        ->items($other_langs),
                ];
            } else {
                $langs_combo = [
                    ''              => '',
                    __('Most used') => array_flip($rec_langs),
                    __('Available') => array_flip(array_diff_key($all_langs, $rec_langs)),
                ];
            }
        } elseif ($as_component) {
            $langs_combo = array_map(fn (string $code, string $label): Option => (new Option($label, $code))->lang($code), array_keys($rec_langs), array_values($rec_langs));
        } else {
            $langs_combo = array_flip($rec_langs);
        }

        return $langs_combo;
    }

    /**
     * Returns a combo containing all available and installed languages for administration pages.
     *
     * @return     array<array-key, Optgroup>  The admin langs combo.
     */
    public static function getAdminLangsCombo(): array
    {
        $lang_combo_avail     = [];
        $lang_combo_not_avail = [];
        $langs                = App::lang()->getISOcodes(true, true);
        foreach ($langs as $k => $v) {
            $lang_avail = $v == 'en' || is_dir(App::config()->l10nRoot() . '/' . $v);
            $option     = new Option($k, $v);
            $option->lang($v);
            if ($lang_avail) {
                $lang_combo_avail[] = $option;
            } else {
                $lang_combo_not_avail[] = $option;
            }
        }

        return [
            (new Optgroup(__('Available')))
                ->items($lang_combo_avail),
            (new Optgroup(__('Other')))
                ->items($lang_combo_not_avail),
        ];
    }

    /**
     * Returns a combo containing all available editors in admin.
     *
     * @return     array<string, string>  The editors combo.
     */
    public static function getEditorsCombo(): array
    {
        $editors_combo = [];

        foreach (App::formater()->getEditors() as $v) {
            $editors_combo[$v] = $v;
        }

        return $editors_combo;
    }

    /**
     * Returns a combo containing all available formaters by editor in admin.
     *
     * @param      string  $editor_id  The editor identifier (dcLegacyEditor, dcCKEditor, ...)
     *
     * @return     array<string, array<string, string>|string>   The formaters combo.
     */
    public static function getFormatersCombo(string $editor_id = ''): array
    {
        $formaters_combo = [];

        if ($editor_id !== '') {
            foreach (App::formater()->getFormater($editor_id) as $formater) {
                $formaters_combo[$formater] = $formater;
            }
        } else {
            foreach (App::formater()->getFormaters() as $editor => $formaters) {
                foreach ($formaters as $formater) {
                    $formaters_combo[$editor][$formater] = $formater;
                }
            }
        }

        return $formaters_combo;
    }

    /**
     * Gets the blog statuses combo.
     *
     * @deprecated  since 2.33, use App::status()->blog()->combo() instead
     *
     * @return     array<string, string>  The blog statuses combo.
     */
    public static function getBlogStatusesCombo(): array
    {
        return App::status()->blog()->combo();
    }

    /**
     * Gets the comment statuses combo.
     *
     * @deprecated  since 2.33, use App::status()->comment()->combo() instead
     *
     * @return     array<string, string>  The comment statuses combo.
     */
    public static function getCommentStatusesCombo(): array
    {
        return App::status()->comment()->combo();
    }

    /**
     * Gets the order combo.
     *
     * @return     array<string, string>  The order combo.
     */
    public static function getOrderCombo(): array
    {
        return [
            __('Descending') => 'desc',
            __('Ascending')  => 'asc',
        ];
    }

    /**
     * Gets the posts sortby combo.
     *
     * @return     array<string, string>  The posts sortby combo.
     */
    public static function getPostsSortbyCombo(): array
    {
        $sortby_combo = [
            __('Date')                 => 'post_dt',
            __('Title')                => 'post_title',
            __('Category')             => 'cat_title',
            __('Author')               => 'user_id',
            __('Status')               => 'post_status',
            __('Selected')             => 'post_selected',
            __('Number of comments')   => 'nb_comment',
            __('Number of trackbacks') => 'nb_trackback',
        ];
        # --BEHAVIOR-- adminPostsSortbyCombo -- array<int,array<string,string>>
        App::behavior()->callBehavior('adminPostsSortbyCombo', [&$sortby_combo]);

        return $sortby_combo;
    }

    /**
     * Gets the comments sortby combo.
     *
     * @return     array<string, string>  The comments sortby combo.
     */
    public static function getCommentsSortbyCombo(): array
    {
        $sortby_combo = [
            __('Date')        => 'comment_dt',
            __('Entry title') => 'post_title',
            __('Entry date')  => 'post_dt',
            __('Author')      => 'comment_author',
            __('Status')      => 'comment_status',
            __('Spam filter') => 'comment_spam_filter',
        ];

        // IP are available only for super-admin and admin
        $show_ip = App::auth()->check(
            App::auth()->makePermissions([
                App::auth()::PERMISSION_CONTENT_ADMIN,
            ]),
            App::blog()->id()
        );
        if ($show_ip) {
            $sortby_combo[__('IP')] = 'comment_ip';
        }

        # --BEHAVIOR-- adminCommentsSortbyCombo -- array<int,array<string,string>>
        App::behavior()->callBehavior('adminCommentsSortbyCombo', [&$sortby_combo]);

        return $sortby_combo;
    }

    /**
     * Gets the blogs sortby combo.
     *
     * @return     array<string, string>  The blogs sortby combo.
     */
    public static function getBlogsSortbyCombo(): array
    {
        $sortby_combo = [
            __('Last update') => 'blog_upddt',
            __('Blog name')   => 'UPPER(blog_name)',
            __('Blog ID')     => 'B.blog_id',
            __('Status')      => 'blog_status',
        ];
        # --BEHAVIOR-- adminBlogsSortbyCombo -- array<int,array<string,string>>
        App::behavior()->callBehavior('adminBlogsSortbyCombo', [&$sortby_combo]);

        return $sortby_combo;
    }

    /**
     * Gets the users sortby combo.
     *
     * @return     array<string, string>  The users sortby combo.
     */
    public static function getUsersSortbyCombo(): array
    {
        $sortby_combo = [];
        if (App::auth()->isSuperAdmin()) {
            $sortby_combo = [
                __('Username')          => 'user_id',
                __('Last Name')         => 'user_name',
                __('First Name')        => 'user_firstname',
                __('Display name')      => 'user_displayname',
                __('Number of entries') => 'nb_post',
                __('Creation date')     => 'user_creadt',
                __('Update date')       => 'user_upddt',
            ];
            # --BEHAVIOR-- adminUsersSortbyCombo -- array<int,array<string,string>>
            App::behavior()->callBehavior('adminUsersSortbyCombo', [&$sortby_combo]);
        }

        return $sortby_combo;
    }
}
