<?php
/**
 * Action Plugin for SphinxSearch - Full DOM Generation Version
 */

if(!defined('DOKU_INC')) die();

require_once(__DIR__ . '/sphinxapi.php');
require_once(__DIR__ . '/PageMapper.php');
require_once(__DIR__ . '/SphinxSearch.php');
require_once(__DIR__ . '/functions.php');

class action_plugin_sphinxsearchwas extends DokuWiki_Action_Plugin {

    private $_sObj = null;
    private $_dom = null;

    public function register(\dokuwiki\Extension\EventHandler $controller) {
        $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'handle_act_unknown');
    }

    public function handle_act_unknown(\dokuwiki\Extension\Event $event, $param) {
        global $ACT, $QUERY;
        if ($ACT !== 'search') return;
        $event->stopPropagation(); $event->preventDefault();
        $this->_search($QUERY, (int)($_REQUEST['start'] ?? 0));
    }

    private function _search($query, $start) {
        $search = new SphinxSearch($this->getConf('host'), $this->getConf('port'), $this->getConf('index'));
        $maxResults = (int)$this->getConf('maxresults') ?: 10;
        $search->setSnippetSize((int)$this->getConf('snippetsize') ?: 512);

        $keywords = $this->_getKeywords($query);
        $cats = $this->_getCategories($query);
        if (empty($keywords)) { echo "No keywords."; return; }

        if (empty($cats)) $search->setSearchAllQuery($keywords, '');
        else $search->setSearchAllQueryWithCategoryFilter($keywords, $cats);

        $res = $search->search($start, $maxResults);
        $this->_sObj = $search;

        if ($search->getError()) { echo "Sphinx Error: " . hsc($search->getError()); return; }

        $pages = $search->getPages($keywords);
        $total = $search->getTotalFound();
        if (!$res || empty($pages) || $total == 0) { echo "No results."; return; }

        $this->_dom = new DOMDocument('1.0', 'UTF-8');
        $root = $this->_dom->createElement('div');
        $this->_dom->appendChild($root);

        $h1 = $this->_dom->createElement('h1', "Found $total matches for \"" . hsc($query) . "\"");
        $root->appendChild($h1);

        $container = $this->_dom->createElement('div');
        $container->setAttribute('class', 'sphinx_search_container');
        $root->appendChild($container);
        
        $sidebar = $this->_dom->createElement('div');
        $sidebar->setAttribute('class', 'search_sidebar');
        ob_start();
        if (function_exists('printNamespacesNew')) printNamespacesNew($this->_getMatchingPagenames($keywords, $cats));
        $this->_appendRawHTML($sidebar, ob_get_clean());
        $container->appendChild($sidebar);

        $h2 = $this->_dom->createElement('h2', "Matching keywords");
        $container->appendChild($h2);

        $list = $this->_dom->createElement('div');
        $list->setAttribute('class', 'search_result_list');
        $container->appendChild($list);

        foreach ($pages as $row) {
            $list->appendChild($this->_createResultNode($row, $keywords));
        }

        $this->_addNumberedPagination($container, $query, $start, $total, $maxResults);
        echo $this->_dom->saveHTML($root);
    }

    private function _createResultNode($row, $keywords) {
        $div = $this->_dom->createElement('div');
        $div->setAttribute('class', 'search_result_row');
        $a = $this->_dom->createElement('a', hsc($row['titleTextExcerpt']));
        $a->setAttribute('class', 'title');
        $a->setAttribute('href', wl($row['page']));
        $div->appendChild($a);
        
        $snippet = $this->_dom->createElement('div');
        $snippet->setAttribute('class', 'search_snippet');
        $this->_appendRawHTML($snippet, $row['bodyExcerpt']);
        $div->appendChild($snippet);
        
        $nmsp = $this->_dom->createElement('span');
        $nmsp->setAttribute('class', 'search_nmsp');
        foreach (getNsLinks($row['page'], $keywords, $this->_sObj) as $i => $n) {
            if ($i > 0) $nmsp->appendChild($this->_dom->createTextNode(' : '));
            $na = $this->_dom->createElement('a', hsc($n['title']));
            $na->setAttribute('href', wl($n['link']));
            $nmsp->appendChild($na);
        }
        $div->appendChild($nmsp);
        return $div;
    }

    private function _addNumberedPagination($parent, $query, $start, $total, $perPage) {
        $nav = $this->_dom->createElement('div');
        $nav->setAttribute('class', 'sphinxsearch_pagination');
        $totalPages = (int)ceil($total / $perPage);
        $currentPage = (int)floor($start / $perPage) + 1;
        $range = 4;
        $pageStart = max(1, $currentPage - $range);
        $pageEnd   = min($totalPages, $currentPage + $range);

        if ($currentPage > 1) $this->_addPageLink($nav, $query, ($currentPage - 2) * $perPage, '« Prev', 'page_box prev');
        for ($i = $pageStart; $i <= $pageEnd; $i++) {
            $class = ($i == $currentPage) ? 'page_box active' : 'page_box';
            $this->_addPageLink($nav, $query, ($i - 1) * $perPage, $i, $class);
        }
        if ($currentPage < $totalPages) $this->_addPageLink($nav, $query, $currentPage * $perPage, 'Next »', 'page_box next');
        $parent->appendChild($nav);
    }

    private function _addPageLink($parent, $query, $startValue, $label, $class) {
        $a = $this->_dom->createElement('a', $label);
        $a->setAttribute('href', wl('', ['do'=>'search', 'id'=>$query, 'start'=>$startValue]));
        $a->setAttribute('class', $class);
        $parent->appendChild($a);
    }

    private function _appendRawHTML($node, $html) {
        if (empty($html)) return;
        $tmp = new DOMDocument();
        @$tmp->loadHTML('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $html);
        $body = $tmp->getElementsByTagName('body')->item(0);
        if ($body) {
            foreach ($body->childNodes as $child) {
                if ($child->nodeName === 'meta') continue;
                $node->appendChild($this->_dom->importNode($child, true));
            }
        }
    }

    private function _getCategories($q) { preg_match('/@ns\s+([^\s]+)/i', urldecode($q), $m); return $m[1] ?? ''; }
    private function _getKeywords($q) { return trim(preg_replace('/@ns\s+[^\s]+/i', '', urldecode($q))); }
    private function _getMatchingPagenames($kw, $cat) {
        $this->_sObj->setSearchOnlyPagename();
        if (!$this->_sObj->search(0, 50)) return false;
        $m = [];
        foreach ($this->_sObj->getPagesIds() as $p) $m[$p['page']] = $p['hid'];
        return $m;
    }
}
