<?php

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

namespace Dotclear\Core\Backend;

use ArrayObject;
use Dotclear\App;
use Dotclear\Helper\Html\Form\Optgroup;
use Dotclear\Helper\Html\Form\Option;

/**
 * Admin user preference library
 *
 * Dotclear utility class that provides reuseable user preference
 * across all admin page with lists and filters
 *
 * @phpstan-type TUserPrefFilterProperties array{
 *             null|string,
 *             null|array<Optgroup|Option|string>,
 *             null|string,
 *             null|string,
 *             array{null|string, null|int}
 * }
 *
 * @phpstan-type TUserPrefFilters array<string, TUserPrefFilterProperties>
 */
class UserPref
{
    /**
     * Sort filters preferences
     *
     * @var     UserPrefFilter[]       $filters
     */
    protected static array $filters;

    /**
     * Gets the default columns.
     *
     * @return     ArrayObject<string, array{string, array<string, array{bool, string}>}>
     */
    public static function getDefaultColumns(): ArrayObject
    {
        $cols = [
            'posts' => [
                __('Posts'),
                [
                    'date'       => [true, __('Date')],
                    'category'   => [true, __('Category')],
                    'author'     => [true, __('Author')],
                    'comments'   => [true, __('Comments')],
                    'trackbacks' => [true, __('Trackbacks')],
                ],
            ],
            'comments' => [
                __('Comments'),
                [
                    'date'        => [true, __('Date')],
                    'author'      => [true, __('Author')],
                    'status'      => [true, __('Status')],
                    'ip'          => [true, __('IP')],
                    'spam_filter' => [true, __('Spam filter')],
                    'entry'       => [true, __('Entry')],
                ],
            ],
        ];

        if (App::auth()->isSuperAdmin()) {
            $cols['users'] = [
                __('Users'),
                [
                    'first_name'   => [true, __('First Name')],
                    'last_name'    => [true, __('Last Name')],
                    'display_name' => [true, __('Display name')],
                    'entries'      => [true, __('Entries (all types)')],
                    'status'       => [true, __('Status')],
                    'user_creadt'  => [false, __('Creation date')],
                    'user_upddt'   => [false, __('Update date')],
                ],
            ];
            $cols['blogs'] = [
                __('Blogs'),
                [
                    'name'   => [true, __('Blog name')],
                    'url'    => [true, __('URL')],
                    'posts'  => [true, __('Entries (all types)')],
                    'upddt'  => [true, __('Last update')],
                    'status' => [true, __('Status')],
                ],
            ];
        }

        $cols = new ArrayObject($cols);

        # --BEHAVIOR-- adminColumnsLists -- ArrayObject
        App::behavior()->callBehavior('adminColumnsListsDefault', $cols);

        // @phpstan-ignore return.type (cannot be specific as behavior may change the content of $cols)
        return $cols;
    }

    /**
     * Gets the user columns.
     *
     * @param      null|string                                              $type     The type
     * @param      null|array<string, mixed>|ArrayObject<string, mixed>     $columns  The columns
     *
     * @return     ($type is null
     *              ? ArrayObject<string, array{string, array<string, array{bool, string}>}>
     *              : ArrayObject<int, array<string, array{bool, string}>|string>)
     */
    public static function getUserColumns(?string $type = null, $columns = null): ArrayObject
    {
        # Get default colums (admin lists)
        $cols = self::getDefaultColumns();

        # --BEHAVIOR-- adminColumnsLists -- ArrayObject
        App::behavior()->callBehavior('adminColumnsListsV2', $cols);

        // Load user settings
        $cols_user = @App::auth()->prefs()->get('interface')->get('cols');
        if (is_array($cols_user)) {
            /*
             * $ct = type (blogs, users, posts, …)
             * $cv = columns for this type
            */
            foreach ($cols_user as $ct => $cv) {
                if (is_array($cv)) {
                    // Sort corresponding $cols columns
                    $order = array_keys($cv);
                    if (isset($cols[$ct][1])) {
                        uksort($cols[$ct][1], fn ($key1, $key2): int => array_search($key1, $order, true) <=> array_search($key2, $order, true));
                    }

                    if ($type !== null && $type !== '' && !empty($columns) && $ct == $type) {
                        // Use ArrayObject in all cases
                        $columns = $columns instanceof ArrayObject ? $columns : new ArrayObject($columns);
                        // Sort also corresponding $columns columns
                        $columns->uksort(fn ($key1, $key2): int => array_search($key1, $order, true) <=> array_search($key2, $order, true));
                    }

                    /*
                     * $cn = column id
                     * $cd = column flag (false/true)
                     */
                    foreach ($cv as $cn => $cd) {
                        if (isset($cols[$ct][1][$cn])) {
                            $cols[$ct][1][$cn][0] = $cd;

                            // remove unselected columns if type is given
                            if (!$cd && $type !== null && $type !== '' && !empty($columns) && $ct == $type && isset($columns[$cn])) {
                                unset($columns[$cn]);
                            }
                        }
                    }
                }
            }
        }

        if ($columns !== null) {
            // @phpstan-ignore return.type
            return $columns instanceof ArrayObject ? $columns : new ArrayObject($columns);
        }

        if ($type !== null) {
            return new ArrayObject($cols[$type] ?? []);
        }

        return $cols;
    }

    /**
     * Get all user columns
     *
     * @return array<string, array{string, array<string, array{bool, string}>}>
     */
    public static function getAllUserColumns(): array
    {
        return self::getUserColumns()->getArrayCopy();
    }

    /**
     * Get sort filters or typed filter or field value
     *
     * @param      null|string  $type    The type
     * @param      null|string  $option  The option
     *
     * @return     ($type is null ? UserPrefFilter[] : ($option is null ? null|UserPrefFilter : string|int|null))
     */
    public static function getUserFilters(
        ?string $type = null,
        ?string $option = null
    ): null|array|string|int|UserPrefFilter {
        self::initUserFilters();

        if (null === $type) {
            return self::$filters;
        }

        if ($option === null) {
            return self::getUserFilter($type);
        }

        $filter = self::getUserFilter($type);
        if ($filter instanceof UserPrefFilter) {
            switch ($option) {
                case 'sortby':
                    return $filter->getSortBy();

                case 'order':
                    return $filter->getOrder();

                case 'nb':
                    return $filter->getNb();
            }
        }

        return null;
    }

    /**
     * Get sorts filters users preference for a given type
     *
     * @param  string $type     Filter type
     */
    public static function getUserFilter(string $type): null|UserPrefFilter
    {
        self::initUserFilters();

        foreach (self::$filters as $filter) {
            if ($filter->getType() === $type) {
                return $filter;
            }
        }

        return null;
    }

    /**
     * Get user preferences sort by for a given type
     *
     * @param  string $type Filter type
     */
    public static function getUserFilterSortBy(string $type): ?string
    {
        self::initUserFilters();

        $filter = self::getUserFilter($type);
        if ($filter instanceof UserPrefFilter) {
            return $filter->getSortBy();
        }

        return null;
    }

    /**
     * Get user preferences sort order for a given type
     *
     * @param  string $type Filter type
     */
    public static function getUserFilterOrder(string $type): ?string
    {
        self::initUserFilters();

        $filter = self::getUserFilter($type);
        if ($filter instanceof UserPrefFilter) {
            return $filter->getOrder();
        }

        return null;
    }

    /**
     * Get user preferences number for a given type
     *
     * @param  string $type Filter type
     */
    public static function getUserFilterNb(string $type): ?int
    {
        self::initUserFilters();

        $filter = self::getUserFilter($type);
        if ($filter instanceof UserPrefFilter) {
            return $filter->getNb();
        }

        return null;
    }

    /**
     * Gets the default filters.
     *
     * @return UserPrefFilter[]    The default filters
     */
    protected static function getDefaultFilters(): array
    {
        // Helper for nb of element per page, use setting if set and > 0, else use default value
        $nb_per_page = fn (string $name, int $default = 30): int => ($value = App::auth()->prefs()->get('interface')->getInt($name, false)) > 0
            ? $value
            : $default;

        return [
            new UserPrefFilter(
                'posts',
                __('Posts'),
                App::backend()->combos()->getPostsSortbyCombo(),
                'post_dt',
                'desc',
                __('posts per page'),
                $nb_per_page('nb_posts_per_page')
            ),
            new UserPrefFilter(
                'comments',
                __('Comments'),
                App::backend()->combos()->getCommentsSortbyCombo(),
                'comment_dt',
                'desc',
                __('comments per page'),
                $nb_per_page('nb_comments_per_page')
            ),
            new UserPrefFilter(
                'blogs',
                __('Blogs'),
                App::backend()->combos()->getBlogsSortbyCombo(),
                'blog_upddt',
                'desc',
                __('blogs per page'),
                $nb_per_page('nb_blogs_per_page')
            ),
            App::auth()->isSuperAdmin()
                ? new UserPrefFilter(
                    'users',
                    __('Users'),
                    App::backend()->combos()->getUsersSortbyCombo(),
                    'user_id',
                    'asc',
                    __('users per page'),
                    $nb_per_page('nb_users_per_page')
                )
                : new UserPrefFilter('users', null, null, null, null, null, null),
            new UserPrefFilter(
                'media',
                __('Media manager'),
                [
                    __('Name')  => 'name',
                    __('Date')  => 'date',
                    __('Size')  => 'size',
                    __('Title') => 'title',
                ],
                'name',
                'asc',
                __('media per page'),
                $nb_per_page('media_by_page')
            ),
            new UserPrefFilter(
                'search',
                __('Search'),
                null,
                null,
                null,
                __('results per page'),
                $nb_per_page('nb_searchresults_per_page', 20)
            ),
        ];
    }

    /**
     * Populate sorts from user preferences if not already done
     */
    protected static function initUserFilters(): void
    {
        if (!isset(self::$filters)) {
            // Get default filters
            $filters = self::getDefaultFilters();

            // 3rd party filters
            $others = new ArrayObject($filters);

            # --BEHAVIOR-- adminFiltersLists -- ArrayObject<array-key, UserPrefFilter>
            App::behavior()->callBehavior('adminFiltersListsV3', $others);

            $filters = $others->getArrayCopy();

            // Cope with old behavior - START
            $sorts = [];
            foreach ($filters as $filter) {
                $sorts[$filter->getType()] = [
                    $filter->getLabel(),
                    $filter->getOptions(),
                    $filter->getSortBy(),
                    $filter->getOrder(),
                    [
                        $filter->getNbLabel(),
                        $filter->getNb(),
                    ],
                ];
            }

            $sorts_def = new ArrayObject($sorts);

            # --BEHAVIOR-- adminFiltersLists -- ArrayObject
            App::behavior()->callBehavior('adminFiltersListsV2', $sorts_def);

            /**
             * @var TUserPrefFilters $sorts
             */
            $sorts = $sorts_def->getArrayCopy();

            $filters = [];
            foreach ($sorts as $type => $properties) {
                $filters[] = (new UserPrefFilter(
                    $type,
                    $properties[0],
                    $properties[1],
                    $properties[2],
                    $properties[3],
                    $properties[4][0],
                    $properties[4][1]
                ));
            }

            // Cope with old behavior - END

            // Get user preferences and apply them to filters
            $customs = App::auth()->prefs()->get('interface')->get('sorts');
            if (is_array($customs)) {
                foreach ($filters as $filter) {
                    $type = $filter->getType();
                    if (!isset($customs[$type])) {
                        // Keep default for this type
                        continue;
                    }

                    $data = $customs[$type];

                    if ($filter->getSortBy() !== null
                        && is_array($data)
                        && isset($data[0])
                        && is_string($data[0])
                        && $filter->findOption($data[0])
                    ) {
                        $filter->setSortBy($data[0]);
                    }

                    if ($filter->getOrder() !== null
                        && is_array($data)
                        && isset($data[1])
                        && is_string($data[1])
                        && in_array($data[1], ['asc', 'desc'], true)
                    ) {
                        $filter->setOrder($data[1]);
                    }

                    if ($filter->getNb() !== null
                        && is_array($data)
                        && isset($data[2])
                        && is_numeric($data[2])
                        && (int) $data[2] > 0
                    ) {
                        $filter->setNb((int) $data[2]);
                    }
                }
            }

            // Store filters
            self::$filters = $filters;
        }
    }
}
