<?php

namespace Concrete\Block\Search;

use CollectionAttributeKey;
use Concrete\Core\Attribute\Key\CollectionKey;
use Concrete\Core\Block\BlockController;
use Concrete\Core\Feature\Features;
use Concrete\Core\Feature\UsesFeatureInterface;
use Concrete\Core\Page\PageList;
use Core;
use Database;
use Page;
use Request;
use Concrete\Core\Support\Facade\Config;
use Concrete\Core\Url\SeoCanonical;

class Controller extends BlockController implements UsesFeatureInterface
{
    /**
     * Search title.
     *
     * @var string
     */
    public $title = '';

    /**
     * Button text.
     *
     * @var string
     */
    public $buttonText = '>';

    /**
     * The base search path.
     *
     * @var string
     */
    public $baseSearchPath = '';

    /**
     * Destination page (another page).
     *
     * @var int|null
     */
    public $postTo_cID = null;

    /**
     * Destination page (external URL).
     *
     * @var string
     */
    public $resultsURL = '';

    /**
     * Unused?
     *
     * @var string[]
     */
    public $reservedParams = ['page=', 'query=', 'search_paths[]=', 'submit=', 'search_paths%5B%5D='];

    /**
     * The name of the database that holds the block data.
     *
     * @var string
     */
    protected $btTable = 'btSearch';

    /**
     * Set this to true if the data sent to the save/performSave methods can contain NULL values that should be persisted.
     *
     * @var bool
     */
    protected $supportSavingNullValues = true;

    /**
     * Add/Edit dialog width (in pixels).
     *
     * @var int
     */
    protected $btInterfaceWidth = 400;

    /**
     * Add/Edit dialog height (in pixels).
     *
     * @var int
     */
    protected $btInterfaceHeight = 420;

    /**
     * The CSS class of the block wrapper (unused?).
     *
     * @var string
     */
    protected $btWrapperClass = 'ccm-ui';

    /**
     * List of database table fields that contains fields with collection identifiers.
     *
     * @var string[]
     */
    protected $btExportPageColumns = ['postTo_cID'];

    /**
     * Should the database record be cached?
     *
     * @var bool
     */
    protected $btCacheBlockRecord = true;

    /**
     * Should the block output be cached?
     *
     * @var bool
     */
    protected $btCacheBlockOutput = null;

    /**
     * The color to be used to highlight results.
     *
     * @var string
     */
    protected $hColor = '#EFE795';

    /**
     * Whether or not to search all sites.
     *
     * @var bool
     */
    protected $search_all;

    /**
     * Whether or not to users can search all sites from the frontend.
     *
     * @var bool
     */
    protected $allow_user_options;

    /**
     * {@inheritdoc}
     */
    public function getBlockTypeName()
    {
        return t('Search');
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockTypeDescription()
    {
        return t('Add a search box to your site.');
    }

    /**
     * Highlight parts of a text.
     *
     * @param string $fulltext the whole text
     * @param string $highlight The text to be highlighted
     *
     * @return string
     */
    public function highlightedMarkup($fulltext, $highlight)
    {
        if (!$highlight) {
            return $fulltext;
        }

        $this->hText = h($fulltext);
        $this->hHighlight = h($highlight);
        $this->hText = @preg_replace('#' . preg_quote($this->hHighlight, '#') . '#ui', '<span style="background-color:' . $this->hColor . ';">$0</span>', $this->hText);

        return $this->hText;
    }

    public function getRequiredFeatures(): array
    {
        return [
            Features::SEARCH
        ];
    }

    /**
     * Highlight parts of a text (extended version).
     *
     * @param string $fulltext the whole text
     * @param string $highlight The text to be highlighted
     *
     * @return string|null
     */
    public function highlightedExtendedMarkup($fulltext, $highlight)
    {
        $text = @preg_replace("#\n|\r#", ' ', $fulltext);

        $matches = [];
        $highlight = str_replace(['"', "'", '&quot;'], '', $highlight); // strip the quotes as they mess the regex

        if (!$highlight) {
            $text = $this->app->make('helper/text')->shorten($fulltext, 180);
            if (strlen($fulltext) > 180) {
                $text .= '&hellip;<wbr>';
            }
            $result = $text;
        } else {
            $result = null;
            $regex = '([[:alnum:]|\'|\.|_|\s]{0,45})' . preg_quote($highlight, '#') . '([[:alnum:]|\.|_|\s]{0,45})';
            preg_match_all("#$regex#ui", $text, $matches);

            if (!empty($matches[0])) {
                $body_length = 0;
                $body_string = [];
                foreach ($matches[0] as $line) {
                    $body_length += strlen($line);

                    $r = $this->highlightedMarkup($line, $highlight);
                    if ($r) {
                        $body_string[] = $r;
                    }
                    if ($body_length > 150) {
                        break;
                    }
                }
                if (!empty($body_string)) {
                    $result = (string) @implode('&hellip;<wbr>', $body_string);
                }
            }
        }

        return $result;
    }

    /**
     * Set the color to be used to highlight results.
     *
     * @param string $color
     */
    public function setHighlightColor($color)
    {
        $this->hColor = $color;
    }

    /**
     * Is there some page in the PageSearchIndex database table?
     *
     * @return bool
     */
    public function indexExists()
    {
        $db = Database::connection();

        return (bool) $db->fetchColumn('select cID from PageSearchIndex limit 1');
    }

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Block\BlockController::cacheBlockOutput()
     */
    public function cacheBlockOutput()
    {
        if ($this->btCacheBlockOutput === null) {
            $this->btCacheBlockOutput = false;
            if ($this->postTo_cID || (string) $this->resultsURL !== '') {
                if ($this->request->get('query') === null) {
                    $this->btCacheBlockOutput = true;
                }
            }
            $this->btCacheBlockOutput = (($this->postTo_cID || $this->resultsURL) && Request::request('query') === null);
        }

        return $this->btCacheBlockOutput;
    }

    /**
     * Default on_start().
     */
    public function on_start()
    {
        $seoCanonical = $this->app->make(SeoCanonical::class);
        $includedQueryParams = $seoCanonical->getIncludedQuerystringParameters();
        $paging = $this->request->request(Config::get('concrete.seo.paging_string'));
        if ($paging && $paging >= 2) {
            // ccm_paging_p hardwired in config // Seemingly no effect if removed // concrete/site.php
            if (!in_array(Config::get('concrete.seo.paging_string'), $includedQueryParams)) {
                $seoCanonical->addIncludedQuerystringParameter(Config::get('concrete.seo.paging_string'));
            }
            $this->addHeaderItem('<meta name="robots" content="noindex">');
        }

        if (!empty($this->request->request('query'))) {
            if (!in_array('query', $includedQueryParams)) {
                $seoCanonical->addIncludedQuerystringParameter('query');
            }
        }
    }

    /**
     * Default view method.
     */
    public function view()
    {
        $this->set('title', $this->title);
        $this->set('buttonText', $this->buttonText);
        $this->set('baseSearchPath', $this->baseSearchPath);
        $this->set('postTo_cID', $this->postTo_cID);
        $this->set('allowUserOptions', $this->allow_user_options);
        $this->set('searchAll', $this->search_all);

        if ((string) $this->resultsURL !== '') {
            $resultsPage = null;
            $resultsURL = $this->resultsURL;
        } else {
            $resultsPage = $this->postTo_cID ? Page::getById($this->postTo_cID) : null;
            if (is_object($resultsPage) && !$resultsPage->isError()) {
                $resultsURL = $resultsPage->getCollectionPath();
            } else {
                $resultsPage = null;
                $c = Page::getCurrentPage();
                if (is_object($c)) {
                    $resultsURL = $c->getCollectionPath();
                } else {
                    $resultsURL = '/';
                }
            }
        }

        $resultsURL = $this->app->make('helper/text')->encodePath($resultsURL);

        $this->set('resultTargetURL', $resultsURL);
        if ($resultsPage !== null) {
            $this->set('resultTarget', $resultsPage);
        } else {
            $this->set('resultTarget', $resultsURL);
        }

        //run query if display results elsewhere not set, or the cID of this page is set
        if ($resultsPage === null && (string) $this->resultsURL === '') {
            $query = $this->request->request('query');
            if (!is_string($query)) {
                $query = '';
            }
            if ($query !== '' || $this->request->request('akID') || $this->request->request('month')) {
                $this->do_search();
            }
        }
    }

    /**
     * Method called when the "add block" dialog is going to be shown.
     */
    public function add()
    {
        $this->edit();
    }

    /**
     * Method called when the "edit block" dialog is going to be shown.
     */
    public function edit()
    {
        $this->set('pageSelector', $this->app->make('helper/form/page_selector'));
        $this->set('searchAll', $this->search_all);
        $this->set('allowUserOptions', $this->allow_user_options);
    }

    /**
     * {@inheritdoc}
     */
    public function save($data)
    {
        $data += [
            'title' => '',
            'buttonText' => '',
            'baseSearchPath' => '',
            'searchUnderCID' => 0,
            'postTo_cID' => 0,
            'resultsURL' => '',
            'resultsPageKind' => '',
            'allowUserOptions' => '',
        ];
        $args = [
            'title' => (string) $data['title'],
            'buttonText' => (string) $data['buttonText'],
            'baseSearchPath' => '',
            'postTo_cID' => null,
            'resultsURL' => '',
            'search_all' => 0,
            'allow_users_options' => 0,
        ];
        switch ($data['baseSearchPath']) {
            case 'THIS':
                $c = Page::getCurrentPage();
                if (is_object($c) && !$c->isError()) {
                    $args['baseSearchPath'] = (string) $c->getCollectionPath();
                }
                break;
            case 'OTHER':
                if ($data['searchUnderCID']) {
                    $searchUnderCID = (int) $data['searchUnderCID'];
                    $searchUnderC = Page::getByID($searchUnderCID);
                    if (is_object($searchUnderC) && !$searchUnderC->isError()) {
                        $args['baseSearchPath'] = (string) $searchUnderC->getCollectionPath();
                    }
                }
                break;
            case 'ALL':
                $args['search_all'] = true;
                break;
        }
        if ($args['baseSearchPath'] === '/') {
            $args['baseSearchPath'] = '';
        }

        switch ($data['resultsPageKind']) {
            case 'CID':
                if ($data['postTo_cID']) {
                    $postTo_cID = (int) $data['postTo_cID'];
                    $postTo_c = Page::getByID($postTo_cID);
                    if (is_object($postTo_c) && !$postTo_c->isError()) {
                        $args['postTo_cID'] = $postTo_cID;
                    }
                }
                break;
            case 'URL':
                $args['resultsURL'] = (string) $data['resultsURL'];
                break;
        }

        if ($data['allowUserOptions'] === 'ALLOW') {
            $args['allow_user_options'] = true;
        } else {
            $args['allow_user_options'] = 0;
        }

        parent::save($args);
    }

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Block\BlockController::getImportData()
     */
    protected function getImportData($blockNode, $page)
    {
        $args = parent::getImportData($blockNode, $page);
        $baseSearchPath = (string) $args['baseSearchPath'];
        $args['baseSearchPath'] = empty($args['search_all']) ? 'EVERYWHERE' : 'ALL';
        if ($baseSearchPath !== '') {
            $c = Page::getByPath($baseSearchPath);
            if ($c && !$c->isError()) {
                $args['baseSearchPath'] = 'OTHER';
                $args['searchUnderCID'] = $c->getCollectionID();
            }
        }
        $postTo_cID = (int) ($args['postTo_cID'] ?? 0);
        if ($postTo_cID !== 0) {
            $args['resultsPageKind'] = 'CID'; 
        } elseif ((string) ($args['resultsURL'] ?? '') !== '') {
            $args['resultsPageKind'] = 'URL';
        }
        $args['allowUserOptions'] = empty($args['allowUserOptions']) ? 0 : 'ALLOW';

        return $args;
    }

    /**
     * Perform the search.
     *
     * @return null|false
     */
    public function do_search()
    {
        $query = (string) $this->request->request('query');

        $ipl = new PageList();

        $options = $this->request->request('options');
        if ($options) {
            //Overrides search all settings with user submitted option
            if ($options === 'ALL') {
                $ipl->setSiteTreeToAll();
            }
        } else {
            //If options are not send by user then use default options from the block settings.
            //If Search All is enabled set search site tree to all.
            if ((int) $this->search_all === 1) {
                $ipl->setSiteTreeToAll();
            }
        }

        $aksearch = false;
        $akIDs = $this->request->request('akID');
        if (is_array($akIDs)) {
            foreach ($akIDs as $akID => $req) {
                $fak = CollectionAttributeKey::getByID($akID);
                if (is_object($fak)) {
                    $type = $fak->getAttributeType();
                    $cnt = $type->getController();
                    $cnt->setAttributeKey($fak);
                    $cnt->searchForm($ipl);
                    $aksearch = true;
                }
            }
        }

        if ($this->request->request('month') !== null && $this->request->request('year') !== null) {
            $year = @(int) ($this->request->request('year'));
            $month = abs(@(int) ($this->request->request('month')));
            if (strlen(abs($year)) < 4) {
                $year = (($year < 0) ? '-' : '') . str_pad(abs($year), 4, '0', STR_PAD_LEFT);
            }
            if ($month < 12) {
                $month = str_pad($month, 2, '0', STR_PAD_LEFT);
            }
            $daysInMonth = date('t', strtotime("$year-$month-01"));
            $dh = Core::make('helper/date');
            /* @var $dh \Concrete\Core\Localization\Service\Date */
            $start = $dh->toDB("$year-$month-01 00:00:00", 'user');
            $end = $dh->toDB("$year-$month-$daysInMonth 23:59:59", 'user');
            $ipl->filterByPublicDate($start, '>=');
            $ipl->filterByPublicDate($end, '<=');
            $aksearch = true;
        }

        if ($query === '' && $aksearch === false) {
            return false;
        }

        if ($query !== '') {
            $ipl->filterByKeywords($query);
        }

        $search_paths = $this->request->request('search_paths');
        if (is_array($search_paths)) {
            foreach ($search_paths as $path) {
                if (!is_string($path)) {
                    continue;
                }
                if ($path === '') {
                    continue;
                }
                $ipl->filterByPath($path);
            }
        } elseif ($this->baseSearchPath != '') {
            $ipl->filterByPath($this->baseSearchPath);
        }

        $cak = CollectionKey::getByHandle('exclude_search_index');
        if (is_object($cak)) {
            $ipl->filterByExcludeSearchIndex(false);
        }

        $pagination = $ipl->getPagination();
        $results = $pagination->getCurrentPageResults();

        $this->set('query', $query);
        $this->set('results', $results);
        $this->set('do_search', true);
        $this->set('searchList', $ipl);
        $this->set('pagination', $pagination);
        $this->set('search_paths', $search_paths);
    }
}
