<?php
/**
 * @package     BSN_Wertungsbox
 * @copyright   (C) 2026 brettspiel-news.de
 * @license     GPL-2.0-or-later
 *
 * System-Plugin: Rendert Infobox + Wertungsbox aus Joomla Custom Fields.
 * Astroid-kompatibel via onAfterRender. Kategorie-gesteuert.
 *
 * REVIEW-ARTIKEL-AUFBAU:
 *   [Einleitung]
 *   {bsn_infobox}            ← Shortcode, manuell gesetzt
 *   [Haupttext]
 *                              ← Wertungsbox erscheint automatisch hier
 */

defined('_JEXEC') or die;

use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
use Joomla\CMS\Uri\Uri;

class PlgSystemBsn_Wertungsbox extends CMSPlugin
{
    protected $app;
    protected $autoloadLanguage = true;

    // Custom Field definitions
    private const FIELDS = [
        // Infobox
        'bsn-spiel-id'        => ['label' => 'BGG-Spiel-ID',       'type' => 'text',     'group' => 'infobox'],
        'bsn-verlag'          => ['label' => 'Verlag',             'type' => 'text',     'group' => 'infobox'],
        'bsn-designer'        => ['label' => 'Designer',           'type' => 'text',     'group' => 'infobox'],
        'bsn-autor'           => ['label' => 'Autor',              'type' => 'text',     'group' => 'infobox'],
        'bsn-illustrator'     => ['label' => 'Illustrator',        'type' => 'text',     'group' => 'infobox'],
        'bsn-spieler'         => ['label' => 'Spieleranzahl',      'type' => 'text',     'group' => 'infobox'],
        'bsn-alter'           => ['label' => 'Alter',              'type' => 'text',     'group' => 'infobox'],
        'bsn-dauer'           => ['label' => 'Spieldauer',         'type' => 'text',     'group' => 'infobox'],
        'bsn-bgg-rating'      => ['label' => 'BGG-Rating',         'type' => 'text',     'group' => 'infobox'],
        'bsn-bgg-weight'      => ['label' => 'BGG-Schwierigkeit',  'type' => 'text',     'group' => 'infobox'],
        // Wertungsbox
        'bsn-wertung-titel'   => ['label' => 'Wertung – Titel',    'type' => 'text',     'group' => 'wertung'],
        'bsn-wertung-untertitel' => ['label' => 'Wertung – Untertitel', 'type' => 'text', 'group' => 'wertung'],
        'bsn-wertung-komplex' => ['label' => 'Wertung – Komplexität', 'type' => 'text',   'group' => 'wertung'],
        'bsn-wertung-sprache' => ['label' => 'Wertung – Sprache',   'type' => 'text',     'group' => 'wertung'],
        'bsn-wertung-mechanik' => ['label' => 'Wertung – Mechanismen', 'type' => 'textarea', 'group' => 'wertung'],
        'bsn-wertung-pro'     => ['label' => 'Wertung – Pro',       'type' => 'textarea', 'group' => 'wertung'],
        'bsn-wertung-kontra'  => ['label' => 'Wertung – Kontra',    'type' => 'textarea', 'group' => 'wertung'],
        'bsn-wertung-autor'   => ['label' => 'Wertung – Rezensent', 'type' => 'text',     'group' => 'wertung'],
        'bsn-wertung-punkte'  => ['label' => 'Wertung – Punkte',    'type' => 'integer',  'group' => 'wertung'],
    ];

    /**
     * onAfterRender — the Astroid-safe injection point
     */
    public function onAfterRender(): void
    {
        try {
            // Safety: only frontend, only HTML
            if ($this->app->isClient('administrator')) return;
            
            $body = $this->app->getBody();
            $contentType = $this->app->getDocument()->getType();
            if ($contentType !== 'html') return;

            // Only process if we're on an article page (heuristic: com_content article container)
            $hasArticle = preg_match('/com-content-article.*itemscope/', $body);
            if (!$hasArticle) return;

            // 1) Replace {bsn_infobox} shortcodes
            $body = $this->processInfoboxShortcodes($body);

            // 2) Inject wertungsbox if category matches
            $body = $this->processWertungsbox($body);

            $this->app->setBody($body);

        } catch (\Throwable $e) {
            // Silent fail — never break the site
        }
    }

    /**
     * Find and replace {bsn_infobox} shortcodes with styled HTML
     */
    private function processInfoboxShortcodes(string $body): string
    {
        if (!preg_match_all('/\{bsn_infobox\}/', $body, $matches, PREG_OFFSET_CAPTURE)) {
            return $body;
        }

        // Find article IDs for each shortcode occurrence
        $replacements = [];
        foreach ($matches[0] as $match) {
            $pos = $match[1];
            // Find the enclosing article container
            $articleId = $this->findArticleId($body, $pos);
            if (!$articleId) continue;

            $fields = $this->getFieldValues($articleId);
            if (!$fields) continue;

            $box = $this->buildInfobox($fields);
            if ($box) {
                $replacements[] = ['pos' => $pos, 'len' => 14, 'html' => $box];
            }
        }

        // Apply in reverse order
        foreach (array_reverse($replacements) as $r) {
            $body = substr_replace($body, $r['html'], $r['pos'], $r['len']);
        }

        return $body;
    }

    /**
     * Auto-inject wertungsbox at end of article content
     */
    private function processWertungsbox(string $body): string
    {
        // Find article containers
        if (!preg_match_all(
            '/<div\s+class="[^"]*com-content-article[^"]*"[^>]*>/',
            $body, $matches, PREG_OFFSET_CAPTURE
        )) {
            return $body;
        }

        // Get authorized categories from plugin params
        $allowedCategories = $this->params->get('categories_wertung', []);
        if (empty($allowedCategories) || (is_array($allowedCategories) && count($allowedCategories) === 1 && empty($allowedCategories[0]))) {
            return $body; // No categories selected → don't inject
        }

        $injections = [];
        foreach ($matches[0] as $match) {
            $containerStart = $match[1];
            $articleId = $this->findArticleId($body, $containerStart);
            if (!$articleId) continue;

            // Check category
            $catId = $this->getArticleCategory($articleId);
            if (!$catId || !in_array((string)$catId, (array)$allowedCategories)) {
                continue;
            }

            $fields = $this->getFieldValues($articleId);
            if (!$fields) continue;

            $box = $this->buildWertungsbox($fields);
            if (!$box) continue;

            // Find </article> or </div> that closes the article content
            // Inject just before the closing of the article container
            $closePos = $this->findArticleContentEnd($body, $containerStart);
            if ($closePos) {
                $injections[] = ['pos' => $closePos, 'html' => $box];
            }
        }

        foreach (array_reverse($injections) as $inj) {
            $body = substr_replace($body, $inj['html'], $inj['pos'], 0);
        }

        return $body;
    }

    /**
     * Find article ID from HTML context near a position
     */
    private function findArticleId(string $body, int $nearPos): ?int
    {
        $chunk = substr($body, max(0, $nearPos - 500), 3000);

        $patterns = [
            '/data-id="(\d+)"/',
            '/itemprop="identifier"[^>]*content="(\d+)"/',
            '/<meta\s+itemprop="identifier"\s+content="(\d+)"/',
        ];

        foreach ($patterns as $p) {
            if (preg_match($p, $chunk, $m)) {
                return (int)$m[1];
            }
        }

        return null;
    }

    /**
     * Find the closing position of article content (before the kasten/quellen div)
     */
    private function findArticleContentEnd(string $body, int $containerStart): ?int
    {
        // Look for </article> tag, or the </div> before "Quellen" kasten
        $searchArea = substr($body, $containerStart, 20000);

        // Try </article> first
        $articleEnd = strpos($searchArea, '</article>');
        if ($articleEnd !== false) {
            return $containerStart + $articleEnd;
        }

        // Try the last </div> before "Quellen" heading
        if (preg_match('/<h2[^>]*>\s*Quellen\s*<\/h2>/', $searchArea, $m, PREG_OFFSET_CAPTURE)) {
            $quellenPos = $m[0][1];
            // Find the closing </div> just before Quellen
            $before = substr($searchArea, 0, $quellenPos);
            $lastDiv = strrpos($before, '</div>');
            if ($lastDiv !== false) {
                return $containerStart + $lastDiv;
            }
        }

        return null;
    }

    /**
     * Get article's category ID
     */
    private function getArticleCategory(int $articleId): ?int
    {
        try {
            $db = Factory::getContainer()->get(DatabaseInterface::class);
            $query = $db->getQuery(true)
                ->select('catid')
                ->from('#__content')
                ->where('id = ' . (int)$articleId)
                ->setLimit(1);
            $db->setQuery($query);
            return (int)$db->loadResult() ?: null;
        } catch (\Throwable $e) {
            return null;
        }
    }

    /**
     * Read custom field values for an article
     */
    private function getFieldValues(int $articleId): ?array
    {
        try {
            $db = Factory::getContainer()->get(DatabaseInterface::class);
            
            $names = array_keys(self::FIELDS);
            $quotedNames = array_map(function($n) use ($db) {
                return $db->quote($n);
            }, $names);
            
            $query = $db->getQuery(true)
                ->select(['f.name', 'v.value'])
                ->from($db->quoteName('#__fields', 'f'))
                ->join('LEFT', $db->quoteName('#__fields_values', 'v'),
                    'f.id = v.field_id AND v.item_id = ' . (int)$articleId)
                ->where('f.context = ' . $db->quote('com_content.article'))
                ->where('f.name IN (' . implode(',', $quotedNames) . ')')
                ->where('f.state = 1');

            $db->setQuery($query);
            $rows = $db->loadAssocList('name');

            if (empty($rows)) return null;

            $fields = [];
            $hasAny = false;
            foreach (self::FIELDS as $name => $def) {
                $val = isset($rows[$name]) && $rows[$name]['value'] !== null
                    ? trim($rows[$name]['value']) : '';
                $fields[$name] = $val;
                if ($val !== '') $hasAny = true;
            }

            return $hasAny ? $fields : null;
        } catch (\Throwable $e) {
            return null;
        }
    }

    /**
     * Build the infobox HTML
     */
    private function buildInfobox(array $f): string
    {
        $needle = ['bsn-verlag','bsn-designer','bsn-autor','bsn-illustrator','bsn-spieler','bsn-alter','bsn-dauer'];
        $hasAny = false;
        foreach ($needle as $n) {
            if (!empty($f[$n])) { $hasAny = true; break; }
        }
        if (!$hasAny) return '';

        $items = [];
        $labels = [
            'bsn-verlag'      => 'Verlag',
            'bsn-designer'    => 'Designer',
            'bsn-autor'       => 'Autor',
            'bsn-illustrator' => 'Illustrator',
            'bsn-spieler'     => 'Spieler',
            'bsn-alter'       => 'Alter',
            'bsn-dauer'       => 'Dauer',
            'bsn-bgg-rating'  => 'BGG-Rating',
            'bsn-bgg-weight'  => 'Schwierigkeit',
        ];

        foreach ($labels as $key => $label) {
            if (!empty($f[$key])) {
                $items[] = '<strong>' . htmlspecialchars($label) . ':</strong> '
                         . htmlspecialchars($f[$key]);
            }
        }

        $rows = '';
        foreach ($items as $item) {
            $rows .= '<div>' . $item . '</div>';
        }

        return <<<HTML
<div class="bsn-infobox" style="background:#f5f0eb;border:1px solid #d4c5b2;border-radius:8px;padding:1rem 1.3rem;margin:1.5rem 0;font-size:0.9rem;line-height:1.8;">
{$rows}
</div>
HTML;
    }

    /**
     * Build the wertungsbox HTML
     */
    private function buildWertungsbox(array $f): string
    {
        if (empty($f['bsn-wertung-titel'])) return '';

        $titel    = htmlspecialchars($f['bsn-wertung-titel']);
        $subtitel = htmlspecialchars($f['bsn-wertung-untertitel']);
        $komplex  = htmlspecialchars($f['bsn-wertung-komplex']);
        $sprache  = htmlspecialchars($f['bsn-wertung-sprache']);
        $mechanik = $this->parseLines($f['bsn-wertung-mechanik']);
        $pro      = $this->parseLines($f['bsn-wertung-pro']);
        $kontra   = $this->parseLines($f['bsn-wertung-kontra']);
        $rezensent = htmlspecialchars($f['bsn-wertung-autor']);
        $punkte   = (int)$f['bsn-wertung-punkte'];

        $meta = '';
        if ($komplex) $meta .= 'Komplexität: <strong>' . $komplex . '</strong>';
        if ($sprache) {
            if ($meta) $meta .= '<br>';
            $meta .= 'Sprachen: <strong>' . $sprache . '</strong>';
        }

        return <<<HTML
<style>
.bsn-wertungsbox{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;max-width:720px;margin:1.5rem 0;line-height:1.4}
.bsn-wertungsbox *{box-sizing:border-box}
.bsn-wb-header{background:#1a1e6b;color:#fff;padding:1.2rem 1.5rem;display:flex;justify-content:space-between;align-items:center;gap:1rem;flex-wrap:wrap}
.bsn-wb-header .bsn-wb-titel-block{flex:1;min-width:200px}
.bsn-wb-header .bsn-wb-titel-block .bsn-wb-titel{margin:0;font-size:1.4rem;font-weight:700}
.bsn-wb-header .bsn-wb-titel-block .bsn-wb-untertitel{font-size:.9rem;opacity:.8;margin-top:2px}
.bsn-wb-header .bsn-wb-meta{text-align:right;font-size:.85rem;line-height:1.6;min-width:150px}
.bsn-wb-header .bsn-wb-logo{font-weight:700;font-size:.9rem;letter-spacing:.05em;text-align:right;line-height:1.3}
.bsn-wb-content{background:#fff;display:flex;border:1px solid #ddd;border-top:none;flex-wrap:wrap}
.bsn-wb-col{flex:1;min-width:200px;padding:1rem 1.2rem 1.2rem}
.bsn-wb-col h4{margin:0 0 .6rem;font-size:.8rem;letter-spacing:.08em;color:#1a1e6b;text-align:center;text-transform:uppercase}
.bsn-wb-col ul{list-style:none;padding:0;margin:0}
.bsn-wb-col li{padding:.5rem 0;border-bottom:1px dotted #ccc;font-size:.85rem}
.bsn-wb-col li:last-child{border-bottom:none}
.bsn-wb-footer{background:#1a1e6b;color:#fff;padding:.9rem 1.5rem;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem}
.bsn-wb-footer .bsn-wb-rezensent{font-size:.85rem}
.bsn-wb-footer .bsn-wb-rezensent .bsn-wb-name{font-size:1.1rem;font-style:italic}
.bsn-wb-footer .bsn-wb-score{text-align:right}
.bsn-wb-footer .bsn-wb-score .bsn-wb-score-label{font-size:.75rem;opacity:.8;text-transform:uppercase}
.bsn-wb-footer .bsn-wb-score .bsn-wb-score-zahl{font-size:2rem;font-weight:700}
@media(max-width:600px){.bsn-wb-col{min-width:100%}.bsn-wb-header{flex-direction:column;text-align:center}.bsn-wb-header .bsn-wb-meta{text-align:center}.bsn-wb-header .bsn-wb-logo{text-align:center}}
</style>
<div class="bsn-wertungsbox">
  <div class="bsn-wb-header">
    <div class="bsn-wb-titel-block">
      <div class="bsn-wb-titel">{$titel}</div>
      <div class="bsn-wb-untertitel">{$subtitel}</div>
    </div>
    <div class="bsn-wb-meta">{$meta}</div>
    <div class="bsn-wb-logo">BRETT<br>SPIEL<br>NEWS</div>
  </div>
  <div class="bsn-wb-content">
    {$this->renderWbCol('MECHANISMEN', $mechanik)}
    {$this->renderWbCol('PRO', $pro)}
    {$this->renderWbCol('KONTRA', $kontra)}
  </div>
  <div class="bsn-wb-footer">
    <div class="bsn-wb-rezensent">
      Bewertung von:<br>
      <span class="bsn-wb-name">{$rezensent}</span>
    </div>
    <div class="bsn-wb-score">
      <div class="bsn-wb-score-label">MEINE WERTUNG</div>
      <div class="bsn-wb-score-zahl">{$punkte} / 100</div>
    </div>
  </div>
</div>
HTML;
    }

    private function renderWbCol(string $title, array $items): string
    {
        $h = htmlspecialchars($title);
        $lis = '';
        foreach ($items as $item) {
            $lis .= '<li>' . htmlspecialchars($item) . '</li>';
        }
        return <<<HTML
    <div class="bsn-wb-col">
      <h4>{$h}</h4>
      <ul>{$lis}</ul>
    </div>
HTML;
    }

    private function parseLines(string $text): array
    {
        $text = trim($text);
        if ($text === '') return [];
        $lines = preg_split('/\r?\n/', $text);
        return array_values(array_filter(array_map('trim', $lines), fn($l) => $l !== ''));
    }

    // ─── INSTALL / UNINSTALL ───────────────────────────────

    public function installFields(): bool
    {
        try {
            $db = Factory::getContainer()->get(DatabaseInterface::class);

            // Find or create field group
            $query = $db->getQuery(true)
                ->select('id')->from('#__fields_groups')
                ->where('context = ' . $db->quote('com_content.article'))
                ->where('title = ' . $db->quote('BSN Wertungsbox'))
                ->setLimit(1);
            $db->setQuery($query);
            $groupId = (int)$db->loadResult();

            if (!$groupId) {
                $db->insertObject('#__fields_groups', (object)[
                    'id' => 0, 'asset_id' => 0,
                    'context' => 'com_content.article',
                    'title' => 'BSN Wertungsbox',
                    'state' => 1, 'access' => 1,
                    'language' => '*', 'params' => '{}',
                    'created' => date('Y-m-d H:i:s'), 'created_by' => 0,
                ], 'id');
                $groupId = (int)$db->insertid();
            }

            $order = 1;
            foreach (self::FIELDS as $name => $def) {
                $query = $db->getQuery(true)
                    ->select('id')->from('#__fields')
                    ->where('name = ' . $db->quote($name))
                    ->where('context = ' . $db->quote('com_content.article'));
                $db->setQuery($query);
                if ($db->loadResult()) { $order++; continue; }

                $params = match($def['type']) {
                    'integer' => '{"class":"","default":"","first":"0","last":"100","step":"1","showlabel":"1"}',
                    'textarea' => '{"class":"","rows":"4","cols":"20","filter":"raw","showlabel":"1"}',
                    default => '{"class":"","showlabel":"1"}',
                };

                $db->insertObject('#__fields', (object)[
                    'id' => 0, 'asset_id' => 0,
                    'context' => 'com_content.article',
                    'group_id' => $groupId,
                    'title' => $def['label'], 'name' => $name,
                    'label' => $def['label'], 'type' => $def['type'],
                    'state' => 1, 'required' => 0,
                    'default_value' => '', 'params' => $params,
                    'fieldparams' => '{}', 'access' => 1,
                    'language' => '*', 'ordering' => $order++,
                    'created' => date('Y-m-d H:i:s'), 'created_by' => 0,
                ]);
            }
            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }

    public function uninstallFields(): bool
    {
        try {
            $db = Factory::getContainer()->get(DatabaseInterface::class);
            $names = array_keys(self::FIELDS);
            $quoted = array_map(fn($n) => $db->quote($n), $names);
            
            // Delete values first
            $query = $db->getQuery(true)
                ->delete('#__fields_values')
                ->where('field_id IN (SELECT id FROM ' . $db->quoteName('#__fields') 
                    . ' WHERE context = ' . $db->quote('com_content.article')
                    . ' AND name IN (' . implode(',', $quoted) . '))');
            $db->setQuery($query);
            $db->execute();

            // Delete fields
            $query = $db->getQuery(true)
                ->delete('#__fields')
                ->where('context = ' . $db->quote('com_content.article'))
                ->where('name IN (' . implode(',', $quoted) . ')');
            $db->setQuery($query);
            $db->execute();

            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }
}
