<?php
/**
 * DokuWiki Plugin multimage (Syntax Component)
 */

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

class syntax_plugin_multimage extends DokuWiki_Syntax_Plugin
{
    public function getType(): string
    {
        // We capture a delimited block of raw text
        return 'container';
    }

    public function getPType(): string
    {
        // We emit block-level HTML (<div>)
        return 'block';
    }

    public function getSort(): int
    {
        return 200;
    }

    public function connectTo($mode): void
    {
        $this->Lexer->addEntryPattern(
            '<images>(?=.*?</images>)',
            $mode,
            'plugin_multimage'
        );
    }

    public function postConnect(): void
    {
        $this->Lexer->addExitPattern('</images>', 'plugin_multimage');
    }

    public function handle($match, $state, $pos, Doku_Handler $handler)
    {
        switch ($state) {
            case DOKU_LEXER_ENTER:
                return ['enter'];
            case DOKU_LEXER_UNMATCHED:
                return ['content', $match];
            case DOKU_LEXER_EXIT:
                return ['exit'];
        }
        return [];
    }

    public function render($mode, Doku_Renderer $renderer, $data): bool
    {
        if ($mode !== 'xhtml') {
            return false;
        }

        [$type, $content] = array_pad($data, 2, null);

        if ($type !== 'content') {
            return true;
        }

        $yaml = trim($content);
        if ($yaml === '') {
            return true;
        }

        if (!function_exists('yaml_parse')) {
            $renderer->doc .= '<!-- multimage: PHP YAML extension missing -->';
            return true;
        }

        $parsed = @yaml_parse($yaml);
        if (!is_array($parsed)) {
            $renderer->doc .= '<!-- multimage: invalid YAML -->';
            return true;
        }

        /*
         * Normalization:
         * - mapping with 'image' => single base image
         * - sequence => multiple base images
         */
        if (array_key_exists('image', $parsed)) {
            $baseImages = [$parsed];
        } elseif (array_values($parsed) === $parsed) {
            $baseImages = $parsed;
        } else {
            $renderer->doc .= '<!-- multimage: expected image or list of images -->';
            return true;
        }

        $renderer->doc .= '<div class="images">';

        foreach ($baseImages as $baseImage) {
            if (!is_array($baseImage)) {
                continue;
            }

            $this->renderBaseImage($renderer, $baseImage);


            if (!empty($baseImage['ontop']) && is_array($baseImage['ontop'])) {
                foreach ($baseImage['ontop'] as $ontopImage) {
                    if (is_array($ontopImage)) {
                        $this->renderOntopImage($renderer, $ontopImage);
                    }
                }
            }
        }

        $renderer->doc .= '</div>';

        return true;
    }

    /* ---------- rendering helpers ---------- */

    protected function renderBaseImage(Doku_Renderer $renderer, array $entry): void
    {
        $style = $this->filterStyles($entry['style'] ?? null);

        $renderer->doc .= '<div class="images_ontop">';

        if (empty($entry['image'])) {
            $renderer->doc .= '<div class="images_image"';
            if ($style !== '') {
                $renderer->doc .= ' style="' . hsc($style) . '"';
            }
            $renderer->doc .= '>&nbsp;</div>';
        } else {
          [$src, $alt] = $this->parseImage($entry['image']);
          $renderer->doc .= '<img class="images_image" src="' . hsc($src) . '"';
          if ($style !== '') {
              $renderer->doc .= ' style="' . hsc($style) . '"';
          }
          $renderer->doc .= ' alt="' . hsc($alt) . '"/>';
        }
        $renderer->doc .= '</div>';
    }

    protected function renderOntopImage(Doku_Renderer $renderer, array $entry): void
    {
        if (empty($entry['image'])) {
            return;
        }

        [$src, $alt] = $this->parseImage($entry['image']);
        $style = $this->filterStyles($entry['style'] ?? null);

        $renderer->doc .= '<div class="images_ontop">';
        $renderer->doc .= '<img class="images_ontop_image" src="' . hsc($src) . '"';
        if ($style !== '') {
            $renderer->doc .= ' style="' . hsc($style) . '"';
        }
        $renderer->doc .= ' alt="' . hsc($alt) . '"/>';
        $renderer->doc .= '</div>';
    }

    /* ---------- parsing & validation ---------- */

    protected function parseImage(string $spec): array
    {
        // Format: wiki:img.svg?400|alt text
        [$left, $alt] = array_pad(explode('|', $spec, 2), 2, '');
        [$id, $params] = array_pad(explode('?', $left, 2), 2, '');

        $opts = [];
        if ($params !== '') {
            parse_str($params, $opts);
        }

        $src = ml($id, $opts, true, '&');

        return [$src, $alt];
    }

protected function filterStyles($styles): string
{
    if (!is_array($styles)) {
        return '';
    }

    $allowed = $this->getAllowedStyles();
    $out = [];

    foreach ($styles as $key => $value) {
        if (!is_string($key) || !is_scalar($value)) {
            continue;
        }

        $key = trim($key);
        $value = trim((string)$value);

        if (!in_array($key, $allowed, true)) {
            continue;
        }

        // Rewrite url("...") or url('...')
        $value = preg_replace_callback(
            '/url\(\s*([\'"])([^\'"]+)\1\s*\)/i',
            function ($m) {
                $target = trim($m[2]);

                // Only rewrite DokuWiki media IDs
                if (preg_match('/^[a-zA-Z0-9_:-]+(\.[a-zA-Z0-9]+)?$/', $target)) {
                    $url = ml($target, [], true, '&');
                    return 'url("' . $url . '")';
                }

                // Leave untouched if not a media ID
                return $m[0];
            },
            $value
        );

        $out[] = $key . ': ' . $value;
    }

    return $out ? implode('; ', $out) . ';': '';
}

    protected function getAllowedStyles(): array
    {
        static $styles = null;

        if ($styles !== null) {
            return $styles;
        }

        $raw = (string)$this->getConf('allowed_styles');

        $styles = array_filter(
            array_map('trim', explode(',', $raw)),
            static fn($s) => $s !== ''
        );

        return $styles;
    }
}

