<?php

declare(strict_types=1);

/*
 * This file is part of Oveleon ContaoMemberExtension Bundle.
 *
 * @package     contao-member-extension-bundle
 * @license     MIT
 * @author      Sebastian Zoglowek     <https://github.com/zoglo>
 * @author      Daniele Sciannimanica  <https://github.com/doishub>
 * @author      Fabian Ekert           <https://github.com/eki89>
 * @copyright   Oveleon                <https://www.oveleon.de/>
 */

namespace Oveleon\ContaoMemberExtensionBundle\Controller\FrontendModule;

use Contao\Config;
use Contao\Controller;
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
use Contao\CoreBundle\Exception\PageNotFoundException;
use Contao\Date;
use Contao\Environment;
use Contao\FrontendTemplate;
use Contao\Input;
use Contao\MemberModel;
use Contao\Model\Collection;
use Contao\ModuleModel;
use Contao\Pagination;
use Contao\StringUtil;
use Contao\System;
use Contao\Template;
use Contao\Widget;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

#[AsFrontendModule(MemberListController::TYPE, category: 'user', template: 'mod_memberList')]
class MemberListController extends MemberExtensionController
{
    const TYPE = 'memberList';
    private ModuleModel $model;
    private Template $template;

    private array $memberFilter = [];

    protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
    {
        $this->model = $model;
        $this->template = $template;

        $limit = null;
        $offset = 0;

        $arrGroups = StringUtil::deserialize($model->ext_groups);

        if (empty($arrGroups) || !\is_array($arrGroups))
        {
            $template->empty = $GLOBALS['TL_LANG']['MSC']['emptyMemberList'];
            $template->getResponse();
        }

        if ($this->model->ext_activateFilter)
        {
            $this->parseFilters();
        }

        $memberTemplate = new FrontendTemplate($model->memberListTpl ?: 'memberExtension_list_default');

        if (
            str_starts_with($this->template->getName(), 'mod_' . self::TYPE . '_table') &&
            str_starts_with($memberTemplate->getName(), 'memberExtension_list_row')
        ) {
            $this->isTable = true;
        }

        $intTotal = 0;
        $arrMembers = [];

        if (null !== ($objMembers = $this->getMembers()))
        {
            foreach ($objMembers as $objMember)
            {
                if (
                    !$this->checkMemberGroups($arrGroups, $objMember) ||
                    ($this->model->ext_activateFilter && $this->excludeMember($objMember))
                ) {
                    continue;
                }

                $intTotal += 1;

                $this->memberFields = StringUtil::deserialize($model->memberFields, true);
                $memberTemplate->setData($objMember->row());

                $arrMembers[] = $this->parseMemberTemplate($objMember, $memberTemplate, $model);
            }
        }

        $total = $intTotal - $offset;

        if ($model->numberOfItems > 0)
        {
            $limit = $model->numberOfItems;
        }

        if ($model->perPage > 0 && (!isset($limit) || $model->numberOfItems > $model->perPage) && !$this->isTable)
        {
            if (isset($limit))
            {
                $total = min($limit, $total);
            }

            $id = 'page_n' . $model->id;
            $page = Input::get($id) ?? 1;

            if ($page < 1 || $page > max(ceil($total/$model->perPage), 1))
            {
                throw new PageNotFoundException('Page not found: ' . Environment::get('uri'));
            }

            $limit = $model->perPage;
            $offset += (max($page, 1) - 1) * $model->perPage;
            $skip = 0;

            if ($offset + $limit > $total + $skip)
            {
                $limit = $total + $skip - $offset;
            }

            $arrMembers = \array_slice($arrMembers, $offset, ((int) $limit ?: $intTotal), true);

            $objPagination = new Pagination($total, $model->perPage, Config::get('maxPaginationLinks'), $id);
            $template->pagination = $objPagination->generate("\n  ");
        }

        if (empty($arrMembers))
        {
            $template->empty = $GLOBALS['TL_LANG']['MSC']['emptyMemberList'];
        }

        $template->hasDetailPage = !!$model->jumpTo;

        $template->total = $total;
        $template->labels = $this->labels;
        $template->members = $arrMembers;

        return $template->getResponse();
    }

    private function checkMemberGroups(array $arrGroups, MemberModel $objMember): bool
    {
        if (empty($arrGroups))
        {
            return false;
        }

        $arrMemberGroups = StringUtil::deserialize($objMember->groups);

        if (!\is_array($arrMemberGroups) || !\count(array_intersect($arrGroups, $arrMemberGroups)))
        {
            return false;
        }

        return true;
    }

    private function getMembers(): Collection|MemberModel|null
    {
        $t = MemberModel::getTable();
        $time = Date::floorToMinute();

        $arrColumns = ["$t.disable='' AND ($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'$time') "];
        $arrOptions = [];

        if (!!$orderField = $this->model->ext_orderField)
        {
            $arrOptions['order'] = "$t.$orderField ";
        }

        switch ($this->model->ext_order)
        {
            case 'order_random':
                $arrOptions['order'] = "RAND()";

                break;

            case 'order_desc':
                if (isset($arrOptions['order'])) {
                    $arrOptions['order'] .= "DESC ";
                }

                break;

            case 'order_asc':
            default:
                break;
        }

        // Hook modify the member results
        if (isset($GLOBALS['TL_HOOKS']['getMembers']) && \is_array($GLOBALS['TL_HOOKS']['getMembers']))
        {
            foreach ($GLOBALS['TL_HOOKS']['getMembers'] as $callback)
            {
                System::importStatic($callback[0])->{$callback[1]}($arrColumns, $arrOptions, $this);
            }
        }

        return MemberModel::findBy($arrColumns, null, $arrOptions);
    }

    private function excludeMember(MemberModel $member): bool
    {
        foreach ($this->memberFilter as $condition)
        {
            if ($member->$condition !== '1')
            {
                return true;
            }
        }

        return false;
    }

    private function parseFilters(): void
    {
        Controller::loadDataContainer('tl_member');
        System::loadLanguageFile('tl_member');

        $filters = [];

        foreach ($GLOBALS['TL_DCA']['tl_member']['fields'] ?? [] as $fieldName => $fieldConfig)
        {
            $type = $fieldConfig['inputType'] ?? null;
            $filterable = $fieldConfig['eval']['feFilterable'] ?? null;

            if ('checkbox' === $type && $filterable)
            {
                $filters[] = $fieldName;
            }
        }

        if (!empty($filters))
        {
            /** @var Widget $strClass */
            if (null === ($strClass = $GLOBALS['TL_FFL']['checkbox'] ?? null))
            {
                return;
            }

            $formId = 'memberListFilter_' . $this->model->id;

            $this->template->requestToken = System::getContainer()->get('contao.csrf.token_manager')->getDefaultTokenValue();
            $this->template->filterFormId = $formId;

            foreach ($filters as $key => $filter)
            {
                $objWidget = new $strClass([
                    'type'      => 'checkbox',
                    'name'      => $filter,
                    'id'        => $filter . '_'. $this->model->id,
                    'options'   => [[
                        'default'=> '',
                        'value' => '1',
                        'label' => $GLOBALS['TL_LANG']['tl_member'][$filter][0] ?? $filters
                    ]]
                ]);

                if (Input::post('FORM_SUBMIT') === $formId)
                {
                    $objWidget->validate();

                    if (!!$objWidget->value)
                    {
                        $this->memberFilter[] = $objWidget->name;
                    }
                }

                $filters[$key] = $objWidget->parse();
            }
        }

        $this->template->filters = $filters;
    }
}