<?php

namespace pictpostpersonal\renderer;

use pictpostpersonal\controller\ControllerBase;
use pictpostpersonal\service\ThemeService;
use pictpostpersonal\Environment;
use pictpostpersonal\Request;
use pictpostpersonal\Result;
use pictpostpersonal\FlashMessage;
use pictpostpersonal\model\Article;
use pictpostpersonal\model\Account;
use pictpostpersonal\TagProcessor;
use pictpostpersonal\Utility;
use pictpostpersonal\CsrfUtility;

/**
 * 
 * Aritcleテーブルのデータをもとに記事ページをレンダリングするサービス。
 * AritlcePageやIndexPageから利用される。
 * 
 */
class ArticleRenderer  extends ControllerBase
{
    //記事スタイル群
    private const STYLE_NORMAL = 0;     //標準。本文の後に添付画像が一覧表示される
    private const STYLE_DETAIL = 1;     //詳細。本文内に添付画像が表示される。
    private const STYLE_NOVEL_H = 2;    //小説。詳細ベースで、本文が自動で{ht}～{/ht}で囲まれる。
    private const STYLE_NOVEL_V = 3;    //小説。詳細ベースで、本文が自動で{vt}～{/vt}で囲まれる。
    private const STYLE_MANGA_L = 4;    //漫画。標準ベースで、{mangaview l}が自動で挿入される。
    private const STYLE_MANGA_R = 5;    //漫画。標準ベースで、{mangaview r}が自動で挿入される。
    private const STYLE_MANGA_LW = 6;   //漫画。標準ベースで、{mangaview lw}が自動で挿入される。
    private const STYLE_MANGA_RW = 7;   //漫画。標準ベースで、{mangaview rw}が自動で挿入される。
    private const STYLE_FIX_LOG = 98;   //固定ページ。詳細ベースで、テンプレートとしてtemplate2_pageが使用される。
    private const STYLE_FIX = 99;       //固定ページ。詳細ベースで、テンプレートとしてtemplate2_logが使用される。Indexページにメニューとして表示される。
    private const STYLE_INDEX = 9999;   //トップページ。template2_indexが使用される。

    private $show_indexes = ['0' => '1'];
    private $article_id = -1;
    public $site_url = '';
    public $base_url = '';
    public $page_url = '';

    private $section_data = [];

    private $log_catche = [];

    public function setUrl(array $article)
    {
        $this->base_url = BASE_URL;
        $this->site_url = SITE_URL;
        $this->page_url = $this->site_url . Environment::getLogUrl($article);
    }

    public function show(Request $req, array $article_data, array $page_indexes): Result
    {

        if ($article_data === null) {
            //記事データが渡されていない場合は404を返す
            $result = new Result();
            $result->status = 404;
            return $result;
        }

        //表示ページを保存
        $this->show_indexes = $page_indexes;

        //記事のIDを保存
        $this->article_id = $article_data['id'];

        //NSFW同意状態(Cookie)チェック
        $nsfw_ans = false;
        if ($this->setting->getValue('always_nsfw_check', false)) {
            Utility::SessionStart();
            //常時NSFWチェックの場合、引数で同意が渡されていなければ警告
            $nsfw_ans = $req->getArgs('nsfw_consent', false);
            if ($nsfw_ans) {
                //引数に同意データがあればセッションに同意記録
                $_SESSION['nsfw_ans'] = $this->article_id;
            } else {
                //セッションに本記事のIDがあれば同意済み
                if (($_SESSION['nsfw_ans'] ?? -1) == $this->article_id) {
                    $nsfw_ans = true;
                }
            }
        } else {
            //通常チェック
            $nsfw_ans = ($req->getCookie('nsfw_consent', false));
        }

        //NSFW指定の記事の場合の処理分岐
        if ($article_data['nsfw']) {
            if ($nsfw_ans == false && !$req->getArgs('nsfw', false)) {
                //同意状態でない場合、NSFW警告レンダリング
                FlashMessage::setMessage('nsfw', ['title' => $article_data['title'], 'url' => $this->page_url, 'return_url' => SITE_URL, 'type' => 1]);
                $result = new Result();
                $result->redirect_action = 'middleware\NSFWMiddleware:show';
                return $result;
            }
        }

        //前後記事のデータ取得
        $model = new Article($this->db, $this->setting);
        $prev_data = $model->getPrevArticleByData($article_data, $this->setting->GetValue('move_link_type', 0));
        $next_data = $model->getNextArticleByData($article_data, $this->setting->GetValue('move_link_type', 0));

        //本文データを取得
        $comment = $article_data['contents'];

        //スタイルを決定する
        $simple_mode = true;
        $style = $article_data['style'] ?? self::STYLE_NORMAL;
        switch ($style) {
            case self::STYLE_DETAIL:
                //詳細モード
                $simple_mode = false;
                break;
            case self::STYLE_NOVEL_H:
                //小説用
                $comment = '{ht}' . $comment . '{/ht}';
                $simple_mode = false;
                break;
            case self::STYLE_NOVEL_V:
                //小説用
                $comment = '{vt}' . $comment . '{/vt}';
                $simple_mode = false;
                break;
            case self::STYLE_MANGA_L:
                //漫画VIEW使用
                $comment = $comment . '{mangaview}';
                break;
            case self::STYLE_MANGA_R:
                //漫画VIEW使用
                $comment = $comment . '{mangaview r}';
                break;
            case self::STYLE_MANGA_LW:
                //漫画VIEW使用
                $comment = $comment . '{mangaview dp}';
                break;
            case self::STYLE_MANGA_RW:
                //漫画VIEW使用
                $comment = $comment . '{mangaview dp r}';
                break;
        }

        //漫画ビュアー使用判定
        if (stristr($comment, '{mangaview') !== false) {
            $manga_mode = true;
        } else {
            $manga_mode = false;
        }

        //ページ分割準備
        $comment = preg_replace('/{([vh])t}/i', '{$1t}{paging}', $comment); //vt,htはpagingで囲む
        $comment = preg_replace('/{\/([vh])t}/i', '{/paging}{/$1t}', $comment); //vt,htはpagingで囲む

        //前後メニューの構築
        [$next_link, $comment] = $this->createNextLink($next_data, $comment);
        [$prev_link, $comment] = $this->createPrevLink($prev_data, $comment);

        //TAGリンクの構築
        $tags = explode(' ', trim($article_data['tags']));
        $tag_links = $this->createTagLinks($tags);
        $move_tag = $tags[0] ?? '';


        $data = [
            'current' => &$article_data,
            'next' => &$next_data,
            'prev' => &$prev_data,
            'next_link' => $next_link,
            'prev_link' => $prev_link,
            'manga_mode' => $manga_mode,
            'nsfw_ans' => $nsfw_ans
        ];

        $comment = str_replace('{{', '&#060;&#060;', $comment); //{{ 表記をエスケープ
        $comment = str_replace('}}', '&#062;&#062;', $comment); //}} 表記をエスケープ
        $comment = str_replace('<<', '&lt;', $comment); //<< 表記を変換
        $comment = str_replace('>>', '&gt;', $comment); //>> 表記を変換

        //パスワードモード
        [$password_mode, $comment] = $this->passwordProc($req, $comment);

        //特殊記述の置換 
        $comment = $this->createContents($data, $comment, false, $page_indexes);

        //表示用テンプレート名の取り出し
        [$template_name, $comment] = $this->getTemplateName($comment);

        //キャプション取り出し
        [$caption, $comment] = $this->getCaption($comment);

        //ページ分割
        $comment = $this->pageBreak($comment, $page_indexes[0]);

        $comment = str_replace('&#060;&#060;', '{', $comment); //{{ 表記のエスケープを復元
        $comment = str_replace('&#062;&#062;', '}', $comment); //}} 表記のエスケープを復元

        //画像拡大処理用のスクリプトを指定する
        if ($manga_mode) {
            //漫画ビュアーを仕様
            $version = Environment::getVersion();
            $comment .= "<script src=\"{$this->site_url}/system_resource/script/manga_viewer2.js?version=$version'\"></script><script src=\"{$this->site_url}/system_resource/script/manga_viewer2_use.js?version=$version'\"></script><link rel=\"stylesheet\" href=\"{$this->site_url}/system_resource/css/manga_viewer.css?version=$version\"><img class=\"hide p-manga-view-loader-src\" src=\"{$this->site_url}/system_resource/loading.png\">";
        } else if ($this->setting->getValue('use_lightbox', false)) {
            //ライトボックスを使用
            $comment .= "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js\"></script><link rel=\"stylesheet\" href=\"{$this->site_url}/system_resource/vender/lightbox/css/lightbox.min.css\"><script src=\"{$this->site_url}/system_resource/vender/lightbox/js/lightbox.min.js\"></script>";
        } else {
            //標準の画像ビュアーを使用
            $comment .= "<script src=\"{$this->site_url}/system_resource/script/image_viewer2.js\"></script>";
        }

        //標準・マンガスタイルの場合は本文をキャプションに設定する
        if (
            $style == self::STYLE_NORMAL || $style == self::STYLE_INDEX ||
            ($style >= self::STYLE_MANGA_L && $style <= self::STYLE_MANGA_RW)
        ) {
            $simple_mode = true;

            //画像がある場合、上部に本文・下部に画像一覧になる
            if ($caption == '') {
                $caption = $comment;
                $comment = '';
            }
        }

        if ($password_mode) {
            //パスワード未回答かつシンプルの場合は画像表示しない
            $article_data['media'] = [];
            $article_data['media_count'] = 0;
        }

        //シンプル表示用 添付メディア処理
        if ($simple_mode && $article_data['media_count'] != 0) {
            if ($article_data['thumbnail_type'] == 1) {
                //1枚目をサムネイル扱いとして読み捨てる
                array_shift($article_data['media']);
            }

            foreach ($article_data['media'] as $index => $media) {
                //成形済みサムネイルHTMLを設定する(シンプル表示用)
                $article_data['media'][$index]['thumbnail_html'] = $this->createMediaHtml($media, true);
            }
        }

        //ホームメニューの構築
        $home_menu = $this->createHomeMenu($model);

        //本文中でテンプレートが指定されていない場合は規定テンプレートを使う
        if ($template_name == '') {
            if ($style == self::STYLE_FIX || $style == self::STYLE_FIX_LOG) {
                $template_name = 'template2_page.php';
            } else {
                $template_name = 'template2_article.php';
            }
        }

        //SNSリンク       
        $account_model = new Account($this->db);
        $accounts = $account_model->findAll();

        //レンダリング開始
        $template = ThemeService::getThemeTemplate($template_name, $this->db, $this->setting);

        $page_type = ($style == self::STYLE_FIX || $style == self::STYLE_FIX_LOG) ? 'page' : 'article';

        self::setSiteData($template); //サイト関連情報を設定
        self::setSystemArea($template, $article_data['status'] == 0 ? "" : "<span style=\"#922\">この記事は現在下書きです。訪問者には表示されません。</span>", ["id" => $article_data['id'], "page_type" => $page_type, "views" => $article_data['views'], "likes" => $article_data['like'],]); //管理系情報を設定

        $csrf = CsrfUtility::createCSRFTokenHtml();
        $template->set_data('csrf', $csrf);

        //ページ関連情報を設定
        $template->set_data_h('page_description', Utility::createCaption($caption));
        $template->set_data_h('page_keywords', str_replace(' ', ',', trim(str_replace(' -', ' ', $article_data['tags']))));
        $template->set_data_h('page_author', $this->setting->GetValue('display_name', ''));
        $template->set_data_h('page_title', "{$article_data['title']} - {$this->setting->GetValue('site_title', '新しいサイト')}");
        $template->set_data_h('page_type', $page_type);
        $template->set_data('page_url', $this->page_url);
        $template->set_data('page_og_image', $article_data['media_count'] != 0 ? $article_data['media'][0]['thumb_name'] : '');
        $template->set_data('home_menu', $home_menu);

        //コンテンツ情報を設定
        $template->set_data('accounts', $accounts);
        $template->set_data('id', $article_data['id']);
        $template->set_data_h('title', $article_data['title']);
        $template->set_data_h('created_at', $article_data['created_at']);
        $template->set_data_h('created_at_date', substr($article_data['created_at'] ?? '', 0, 10));
        $template->set_data_h('updated_at', $article_data['updated_at']);
        $template->set_data_h('updated_at_date', substr($article_data['updated_at'] ?? '', 0, 10));
        $template->set_data('contents', $comment);
        $template->set_data('tag_links_html', $tag_links);
        $template->set_data('tags', $article_data['tags']);
        $template->set_data_h('primary_tag', $move_tag);
        $template->set_data('nsfw', $article_data['nsfw']);
        $template->set_data('use_password', $password_mode);

        $template->set_data('caption', $caption);

        $template->set_data('media', $article_data['media'] ?? []);

        $template->set_data('likes', $article_data['like']);
        $template->set_data('views', $article_data['views']);
        $template->set_data('likes_html', $this->setting->getValue('show_likes', '0') ? "<i class=\"fa-regular fa-heart\"></i> {$article_data['like']}"  : "");
        $template->set_data('views_html', $this->setting->getValue('show_views', '0') ? "<i class=\"fa-regular fa-eye\"></i>  {$article_data['views']}"  : "");


        $template->set_data('section_data', $this->section_data);

        $template->set_data('next_link_html', $next_link);
        $template->set_data('prev_link_html', $prev_link);
        $template->set_data('next_url', Environment::getLogUrl($next_data));
        $template->set_data('prev_url', Environment::getLogUrl($prev_data));

        $template->set_data('style', $style);
        $template->set_data('simple_mode', $simple_mode);
        $template->set_data('manga_mode', $manga_mode);



        $result = new Result();
        $result->output = $template->getString();
        return $result;
    }

    //メディア用HTMLタグ生成
    function createMediaHtml(array $media, bool $show_comment, $thumb_size = 0, bool $border = false, $container_position = 0, $add_comment = '', $url = '')
    {
        //リンクタグ
        $site_url = $this->site_url;
        if ($url == '') {
            $url = "{$site_url}/images/{$media['sub_dir']}{$media['file_name']}";
        }
        $link_a = "<a class=\"lighbox-link\" data-lightbox=\"images\" data-thumbnail href=\"$url\">";
        $link_b = '</a>';

        //コンテナ
        switch ($container_position) {
            case self::THUMB_POS_CENTER:
            default:
                $container_position_class = "p-center";
                break;
            case self::THUMB_POS_LEFT:
                $container_position_class = "p-left";
                break;
            case self::THUMB_POS_RIGHT:
                $container_position_class = "p-right";
                break;
        }
        $container = "<figure class=\"p-thumb {$container_position_class}\">";

        //画像コメント
        $img_comment = '';
        if ($show_comment) {
            $img_comment = $media['comment'] ?? '' . $add_comment;
        } else {
            $img_comment = '';
        }
        if ($img_comment != '') {
            $img_comment = Utility::convertLineBreaks($img_comment);
            $img_comment = '<span class=\'p-image-comment\'>' . $img_comment . '</span>';
        }

        //ボーダー
        $border_class = '';
        if ($border) {
            $border_class = "p-thumb-border";
        }

        //画像サイズ
        $thumb_size_name = $thumb_size == self::THUMB_SIZE_L ? "thumb_l" : "thumb";
        $thumb_size_class = $thumb_size == self::THUMB_SIZE_L ? "p-thumb-large" : "p-thumb-small";

        //タグを構築
        if ($media['type'] == 'video') {
            $img = $container . '<video controls class="' . $border_class . '" src="' . $site_url . '/images/' . h($media['sub_dir']) . h($media['file_name'])  . '"' .
                ' data-src="' . $site_url  . '/images/' . h($media['sub_dir']) .  h($media['file_name'])  . '" width=' .  h($media[$thumb_size_name . '_width']) . ' height=' .  h($media[$thumb_size_name . '_height']) .
                ' data-width="' . h($media['width']) . '" data-height="' . h($media['height']) . '"></video>' . $img_comment . '</figure>';
        } else {
            $img = $container . $link_a . '<img class="' . $thumb_size_class . ' ' . $border_class . '" src="' . $site_url  . '/images/' . h($media['sub_dir']) . h($media[$thumb_size_name . '_name'])  . '"' .
                ' data-src="' . $site_url  . '/images/' . h($media['sub_dir']) .  h($media['file_name'])  . '" width=' .  h($media[$thumb_size_name . '_width']) . ' height=' .  h($media[$thumb_size_name . '_height']) .
                ' data-width="' . h($media['width']) . '" data-height="' . h($media['height']) . '">' . $link_b .  $img_comment . '</figure>';
        }
        $img = "<a name=\"{$media['sort']}\"></a>$img";

        return $img;
    }

    //ページ内記事分割
    function pageBreak(string $comment, $index)
    {
        if (strpos($comment, '{pagebreak}') === false) {
            $comment = preg_replace('/{\/?paging}(?:<br>)?/i', '', $comment);
            return $comment;
        }
        $site_url = SITE_URL;

        $comment = preg_replace('/{pagebreak}(?:<br>)?/i', '{pagebreak}', $comment); //改行コード除去

        $header = '';
        $footer = '';
        preg_match('/(.*){paging}/s', $comment, $match);
        if ($match) {
            $header = $match[1];
            $comment = preg_replace("/.*{paging}(?:<br>)?/s", '', $comment);
        }
        preg_match('/{\/paging}(.*)/s', $comment, $match);
        if ($match) {
            $footer = $match[1];
            $comment = preg_replace("/(?:<br>)?{\/paging}.*/s", '', $comment);
        }

        $delimiter = '{pagebreak}';
        $result_array = explode($delimiter, $comment);
        $result = $result_array[$index - 1];

        if (count($result_array) > 1) {
            //ページインデックス
            $pages =  count($result_array);
            $template_file = 'template2_parts_pagination.php';
            $template = ThemeService::getThemeTemplate($template_file, $this->db, $this->setting);
            //$template = new NoSixTemplate2($template_file, ["./env/templates", "./env/themes/" . $this->setting->getValue("theme", ""), "./core/templates", "./core/templates_sys"], "./env/template_cache", ["db" => $this->db, "setting" => $this->setting]);
            $template->set_data('base_url', $this->base_url);
            $template->set_data('site_url', $this->site_url);
            $template->set_data('pagination', $this->createPagination($index, $pages));
            $pagination = $template->getString();
        } else {
            $result = $comment;
            $pagination = '';
        }
        return $header . $result . $footer . $pagination;
    }

    //テンプレート指定の取り出し
    private function getTemplateName(string $comment): array
    {
        $template_name = '';
        preg_match('/{template ([^}]+)}/i', $comment, $match);
        if ($match) {
            $comment = preg_replace('/{template ([^}]+)}(?:<br>)?/i', '', $comment);

            if (strpos($match[1], '.') !== false) {
                //ディレクトリトラバーサル防止
                die('指定されたテンプレート名が不正です');
            }
            $template_name = "template2_user_{$match[1]}.php";
        } else {
            $template_name = '';
        }
        return [$template_name, $comment];
    }

    //本文からキャプション（{caption}～{/caption}）を取りだす
    private function getCaption(string $comment): array
    {
        $caption = '';
        $comment = preg_replace('/{\/caption}<br>/i', '{/caption}', $comment);
        preg_match('/{caption}(.*?){\/caption}/si', $comment, $match);
        if ($match) {
            $caption = $match[1];
            $comment = preg_replace('/{caption}(.*?){\/caption}(?:\r?\n)?/si', '', $comment);
        }
        return [$caption, $comment];
    }

    //記事のタグから検索用HTMLを作成する
    private function createTagLinks(array $tags): string
    {
        $base_url = $this->base_url;
        $tag_links = '';
        foreach ($tags as $tag) {
            if ($tag != '' && $tag[0] != '-') {
                $tag = h($tag);
                $tag_links .= "<a href=\"{$base_url}/search?q=%23{$tag}\" class=\"p-tag-link\">#{$tag}</a> ";
            }
        }
        return $tag_links;
    }

    private function createNextLink(?array $next_data, $comment): array
    {
        $site_url = $this->site_url;
        preg_match('/{next ([^\s\}]+) ?([^}]*)?}/si', $comment, $match);
        if ($match) {
            $next_id = $match[1];
            $move_caption = '次へ';
            if (count($match) == 3 && $match[2] != "") {
                $move_caption = $match[2];
            }
            if ($next_id == '-') {
                $next_link = '-';
            } else {
                $next_link = "<a href=\"{$site_url}" . Environment::getLogUrl(s($next_id ?? '')) . "\">$move_caption</a>";
            }
            $comment = r('/{next ([^\s\}]+) ?([^}]*)?}(?:\r?\n)?/si', '', $comment);
        } else if ($next_data != null) {
            $next_url = Environment::getLogUrl($next_data);
            if ($this->setting->getValue('move_link_title', 0) == 1) {
                $next_title = '次へ';
            } else {
                $next_title = $next_data['title'];
            }
            $next_link = "<a href=\"{$site_url}{$next_url}\">$next_title</a>";
        } else {
            $next_link = '-';
        }
        return [$next_link, $comment];
    }

    private function createPrevLink(?array $prev_data, $comment): array
    {
        $site_url = $this->site_url;
        preg_match('/{prev ([^\s\}]+)([^}]*)?}/si', $comment, $match);
        if ($match) {
            $prev_id = $match[1];
            $move_caption = '前へ';
            if (count($match) == 3 && $match[2] != "") {
                $move_caption = $match[2];
            }
            if ($prev_id == '-') {
                $prev_link = '-';
            } else {
                $prev_link = "<a href=\"{$site_url}" . Environment::getLogUrl(s($prev_id ?? '')) . "\">$move_caption</a>";
            }
            $comment = r('/{prev ([^\s\}]+)([^}]*)?}(?:\r?\n)?/si', '', $comment);
        } else if ($prev_data != null) {
            $prev_url = Environment::getLogUrl($prev_data);
            if ($this->setting->getValue('move_link_title', 0) == 1) {
                $prev_title = '前へ';
            } else {
                $prev_title = $prev_data['title'];
            }
            $prev_link = "<a href=\"{$site_url}{$prev_url}\">$prev_title</a>";
        } else {
            $prev_link = '-';
        }
        return [$prev_link, $comment];
    }
    public function createHomeMenu(Article $article): array
    {
        $home_menu = [];
        $menu_items = $article->search('[type] = 1 and [status] = 0', [], ['sort desc', 'created_at desc']);
        $menu_count = count($menu_items);
        if ($menu_count > 0) {
            $home_menu[] = [
                'id' => -1,
                'url' => $this->site_url,
                'title' => 'HOME',
                'is_current' => $this->article_id == -1,
                'is_first' => true,
                'is_last' => false,
                'class' => 'home_menu-item-first ' . ($this->article_id == -1 ? 'p-home-menu-item-current' : '')
            ];
            foreach ($menu_items as $index => $item) {
                $url = Environment::getLogUrl($item);
                $home_menu[] = [
                    'id' => $item['id'],
                    'url' => SITE_URL . $url,
                    'title' => s($item['title'] ?? ''),
                    'is_current' => $this->article_id == $item['id'],
                    'is_first' => false,
                    'is_last' => ($index == $menu_count - 1),
                    'class' => ($this->article_id == $item['id'] ? 'p-home-menu-item-current ' : '') .   ($index == ($menu_count - 1) ? 'p-home-menu-item-last ' : '')
                ];
            }
        }
        return $home_menu;
    }

    private function linkProcess(string $comment): string
    {
        if (strpos($comment, '{link') !== false) {
            //外部リンク（http～）の処理 - 配列形式で一括置換
            $comment = preg_replace(
                [
                    '/{link[ :]http([^}]*)}(.*?){\/link}/i',    // {link http...}caption{/link}
                    '/{link[ :]http([^\s}]+) ([^}]+)}/i',       // {link:http... caption}
                    '/{link[ :]http([^}]+)}/i',                 // {link:http...}
                ],
                [
                    '<a href="http$1" target="_blank">$2</a>',
                    '<a href="http$1" target="_blank">$2</a>',
                    '<a href="http$1" target="_blank">http$1</a>',
                ],
                $comment
            );

            //内部リンク（記事ID）の処理 - 配列形式で一括置換
            $link_base = Environment::getLogUrlBase();
            $comment = preg_replace(
                [
                    '/{link[ :]([^}]+)}(.*?){\/link}/i',        // {link id}caption{/link}
                    '/{link[ :]([0-9]+) ([^}]+)}/i',            // {link:id caption} (数字)
                    '/{link[ :]([^\s}]+) ([^}]+)}/i',           // {link:id caption}
                ],
                [
                    "<a data-log-link href=\"{$link_base}$1\">$2</a>",
                    "<a data-log-link href=\"{$link_base}$1\">$2</a>",
                    "<a data-log-link href=\"{$link_base}$1\">$2</a>",
                ],
                $comment
            );
        }
        return $comment;
    }
    private function markdownProcess(string $comment): string
    {
        //Parsedownインスタンスの準備（必要な場合のみ）
        $parsedown = null;
        if (strpos($comment, '{md}') !== false || $this->setting->getValue('use_markdown', false)) {
            require_once('./system_resource/vender/Parsedown.php');
            require_once('./system_resource/vender/ParsedownEx.php');
            $parsedown = new \ParsedownEx();
        }
        if ($parsedown !== null && strpos($comment, '{md}') !== false) {
            $comment = preg_replace_callback(
                '/\{md\}(.*?)\{\/md\}/s',
                function ($matches) use ($parsedown) {
                    return $parsedown->text($matches[1]);
                },
                $comment
            );
        }
        if ($parsedown !== null && $this->setting->getValue('use_markdown', false)) {
            $comment = $parsedown->text($comment);
        } elseif ($this->setting->getValue('use_mini_markdown', true)) {
            // 一部マークダウンだけ対応する

            //コードブロック
            $comment = $this->markdownPPPCodeBlockToHTML($comment);
            $comment = $this->markdownHTMLCodeBlockToHTML($comment);
            $comment = $this->markdownCodeBlockToHTML($comment);

            $pattern = '/^(\r)?\n/m';
            $replace = "<br>\r\n";
            $comment = preg_replace($pattern, $replace, $comment);

            // 見出し
            $pattern = '/^(#+)\s*(.*)/m';
            $comment = preg_replace_callback(
                $pattern,
                function ($matches) {
                    $level = strlen($matches[1]);
                    return "<h{$level}  class=\"p-md\">{$matches[2]}</h{$level}>";
                },
                $comment
            );

            // 強調
            $pattern = '/\*\*(.*?)\*\*/';
            $replace = '<strong class="p-md">\\1</strong>';
            $comment = preg_replace($pattern, $replace, $comment);

            // 斜体
            $pattern = '/\*(.*?)\*/';
            $replace = '<em class="p-md">\\1</em>';
            $comment = preg_replace($pattern, $replace, $comment);

            //テーブル
            $comment = $this->markdownTableToHtml($comment);
        }


        return $comment;
    }


    public function createContents(array $data, string $comment, bool $for_Index, array $show_indexes)
    {
        $this->show_indexes = $show_indexes;
        $base_url = $this->base_url;

        //記事共通ヘッダー/フッターの付与
        if (!$for_Index) {
            $comment = $this->setting->getValue('contents_header', '') . "\r\n" . $comment;
            $comment = $comment . $this->setting->getValue('contents_footer', '') . "\r\n";
        }


        $version = Environment::getVersion();
        $novel_ctl_vt = '<span class=\'p-vt-controler-ctl\'><i class=\'fa-solid fa-sliders\' ></i></span><div class=\'p-vt-cover hide\'></div><div class=\'p-vt-controler hide\'><div><span class=\'p-vt-title-1\'>文字サイズ</span><span class=\'p-vt-controler-1 p-vt-controler-active\'>小</span><span class=\'p-vt-controler-2\'>中</span><span class=\'p-vt-controler-3\'>大</span></div><div><span class=\'p-vt-title-2\'>文字組</span><span class=\'p-vt-controler-v p-vt-controler-active\'>縦</span><span class=\'p-vt-controler-h\'>横</span></div><div><span class=\'p-vt-title-3\'>フォント</span><span class=\'p-vt-controler-fss p-vt-controler-active\'>ゴシック体</span><span class=\'p-vt-controler-fs\'>明朝体</span></div></div>';
        $novel_ctl_ht = '<span class=\'p-vt-controler-ctl\'><i class=\'fa-solid fa-sliders\' ></i></span><div class=\'p-vt-cover hide\'></div><div class=\'p-vt-controler hide\'><div><span class=\'p-vt-title-1\'>文字サイズ</span><span class=\'p-vt-controler-1 p-vt-controler-active\'>小</span><span class=\'p-vt-controler-2\'>中</span><span class=\'p-vt-controler-3\'>大</span></div><div><span class=\'p-vt-title-2\'>文字組</span><span class=\'p-vt-controler-v\'>縦</span><span class=\'p-vt-controler-h p-vt-controler-active\'>横</span></div><div><span class=\'p-vt-title-3\'>フォント</span><span class=\'p-vt-controler-fss p-vt-controler-active\'>ゴシック体</span><span class=\'p-vt-controler-fs\'>明朝体</span></div></div>';

        //■ Step 1. Markdownで誤処理されてしまう可能性のあるタグを置換する----------------------------------------------
        //linkタグを変換
        $comment = $this->linkProcess($comment);


        //■ Step 2. Markdown 処理------------------------------------------------------------------------------------
        $comment = $this->markdownProcess($comment);

        //■ Step 3. 改行を<BR>に変換------------------------------------------------------------------------------------
        $comment = Utility::convertLineBreaks($comment);


        //■ Step 4. ページ全体に影響する系統のタグを処理
        //alertタグ 
        $comment = preg_replace(
            [
                '/{alert (.*?)\|(.*?)}(?:<br>)?(.*?){\/alert}(?:<br>)?/s',
                '/{alert}(?:<br>)?(.*?){\/alert}(?:<br>)?/s',
            ],
            [
                "<div class='p-alert'><div>$3<div class='p-alert-control'><button data-alert-close class='p-alert-close p-button'>$1</button> <button class='p-return_button p-alert-return p-button-normal p-button '>$2</button></div><div class='p-alert-control p-move_control p-alert-move_control'>{$data['next_link']} | {$data['prev_link']}</div></div></div>\r\n",
                "<div class='p-alert'><div>$1<div class='p-alert-control'><button data-alert-close class='p-alert-close p-button'>表示</button> <button class='p-return_button p-alert-return p-button-normal p-button '>戻る</button></div><div class='p-alert-control p-move_control p-alert-move_control'>{$data['next_link']} | {$data['prev_link']}</div></div></div>\r\n",
            ],
            $comment
        );

        //vt/htタグ - $novel_ctl_vt/$novel_ctl_htを使用するため個別処理
        $comment = preg_replace(
            [
                '/{vt}(?:<br>)?/i',
                '/{\/vt}(?:<br>)?/i',
                '/{ht}(?:<br>)?/i',
                '/{\/ht}(?:<br>)?/i',
            ],
            [
                "$novel_ctl_vt<div class='p-novel-body-outer'><div class='p-novel-body vertical-text'><div class='p-novel-body-in'>",
                '</div></div></div>',
                "$novel_ctl_ht<div class='p-novel-body-outer'><div class='p-novel-body horizontal-text'><div class='p-novel-body-in'>",
                '</div></div></div>',
            ],
            $comment
        );


        //mangaviewタグ - 配列形式で一括置換
        $comment = preg_replace(
            [
                '/{mangaview dp2 r}(?:<br>)?/i',          // 長いパターンを先に
                '/{mangaview dp2}(?:<br>)?/i',
                '/{mangaview dp r}(?:<br>)?/i',
                '/{mangaview dp}(?:<br>)?/i',
                '/{mangaview r}(?:<br>)?/i',
                '/{mangaview}(?:<br>)?/i',
            ],
            [
                '<script>r_opening = true;dp_spred = 2;</script>',
                '<script>dp_spred = 2;</script>',
                '<script>r_opening = true;dp_spred = 1;</script>',
                '<script>dp_spred = 1;</script>',
                '<script>r_opening = true;</script>',
                '',
            ],
            $comment
        );

        //メールフォームタグ - テンプレートを読み込んで展開する（事前チェック付き）
        if (strpos($comment, '{mailform}') !== false) {
            $comment = preg_replace_callback('/{mailform}(.*?){\/mailform}(?:<br>)?/s', function ($matches) {
                $template_file = 'template2_parts_mailform.php';
                $template = ThemeService::getThemeTemplate($template_file, $this->db, $this->setting);
                //$template = new NoSixTemplate2($template_file, ["./env/templates", "./env/themes/" . $this->setting->getValue("theme", ""), "./core/templates", "./core/templates_sys"], "./env/template_cache", ["db" => $this->db, "setting" => $this->setting]);
                $template->set_data('base_url', $this->base_url);
                $template->set_data('site_url', $this->site_url);
                $template->set_data('append_form', $matches[0]);
                $file_contents = $template->getString();
                return $file_contents;
            }, $comment);

            $comment = preg_replace_callback('/{mailform}(?:<br>)?/i', function ($matches) {
                $template_file = 'template2_parts_mailform.php';
                $template = ThemeService::getThemeTemplate($template_file, $this->db, $this->setting);
                //$template = new NoSixTemplate2($template_file, ["./env/templates", "./env/themes/" . $this->setting->getValue("theme", ""), "./core/templates", "./core/templates_sys"], "./env/template_cache", ["db" => $this->db, "setting" => $this->setting]);
                $template->set_data('base_url', $this->base_url);
                $template->set_data('site_url', $this->site_url);
                $file_contents = $template->getString();
                return $file_contents;
            }, $comment);
        }

        //コメントフォームタグ - テンプレートを読み込んで展開する（事前チェック付き）
        if (strpos($comment, '{messageform}') !== false) {
            $comment = preg_replace_callback('/{messageform}(?:<br>)?/i', function ($matches) {
                $template_file = 'template2_parts_messageform.php';
                $template = ThemeService::getThemeTemplate($template_file, $this->db, $this->setting);
                //$template = new NoSixTemplate2($template_file, ["./env/templates", "./env/themes/" . $this->setting->getValue("theme", ""), "./core/templates", "./core/templates_sys"], "./env/template_cache", ["db" => $this->db, "setting" => $this->setting]);
                $template->set_data('base_url', $this->base_url);
                $template->set_data('site_url', $this->site_url);
                $template->set_data('csrf', CsrfUtility::createCSRFTokenHtml());
                $file_contents = $template->getString();
                return $file_contents;
            }, $comment);
        }


        //■ Step 4. 複雑なタグ処理を実施する --------------------------------------------------------------------------------------------------------------------
        $tagproc = new TagProcessor();
        $tagproc->data = &$data;
        $tagproc->add('article_list', [$this, 'createArticleList']);
        $tagproc->add('[0-9]+-[0-9]+', [$this, 'createRefImageTag']);
        $tagproc->add('[0-9]+', [$this, 'createImageTagBySort']);
        $tagproc->add('img', [$this, 'createSimpleImageTag']);
        $tagproc->add('new-thumb', [$this, 'createNewThumbImageTag']);
        $tagproc->add('new-thumb-s', [$this, 'createNewThumbImageTag']);
        $comment = $tagproc->process($comment);



        //■ Step 5. 置換系 タグを処理する（配列形式で最適化）--------------------------------------------------------------------------------

        //search/tagタグ - 配列形式で一括置換
        $comment = preg_replace(
            [
                '/{search[ :](.*?)}(.*?){\/search}/i',       // {search keyword}caption{/search}
                '/{search[ :]([^}]*?) ([^}]*?)}/i',          // {search keyword caption}
                '/{search[ :]([^}]+)}/i',                    // {search keyword}
                '/{tag[ :]([^}]+)}/i',                       // {tag tagname}
            ],
            [
                "<a href=\"{$base_url}/search?q=$1\">$2</a>",
                "<a href=\"{$base_url}/search?q=$1\">$2</a>",
                "<a href=\"{$base_url}/search?q=$1\">$1</a>",
                "<a href=\"{$base_url}/search?q=%23$1\" class=\"p-tag\">#$1</a>",
            ],
            $comment
        );

        // {.UserCSS}～{/.}、{..UserBlockCSS}～{/..}を処理する
        $comment = preg_replace_callback('/\{ ?\.\.([^\}]+)\}/', function ($matches) {
            $values = explode(' ', $matches[1]);
            $class = 'user_' . implode(' user_', $values);
            return '<div class="' . $class . '">';
        }, $comment);
        $comment = preg_replace_callback('/\{ ?\.([^\}]+)\}/', function ($matches) {
            $values = explode(' ', $matches[1]);
            $class = 'user_' . implode(' user_', $values);
            return '<span class="' . $class . '">';
        }, $comment);
        $comment = preg_replace(['/{\/\.\.}/i', '/{\/\.}/i'], ['</div>', '</span>'], $comment);

        //単純置換系タグ - 配列形式で一括置換（変数不要なもの）
        $comment = preg_replace(
            [
                '/{# ?([^}]+)}(.*?){\/#}/s',              // {#color}text{/#}
                '/{tc}/i',                                // {tc}
                '/{\/tc}/i',                              // {/tc}
                '/\{rb\}(.*?)\|(.*?)\{\/rb\}/',           // {rb}text|ruby{/rb}
                '/{c}(?:<br>)?/i',                        // {c}
                '/{clear}(?:<br>)?/i',                    // {clear}
                '/{border ([^}]+)}(?:<br>)?/i',           // {border color}
                '/{border}(?:<br>)?/i',                   // {border}
                '/{hidden[ :]([^}]+)}(?:<br>)?/i',           // {hidden:id}
            ],
            [
                "<span style='color:#$1'>$2</span>",
                "<span class='p-text-combine'>",
                '</span>',
                '<ruby>$1<rp>(</rp><rt>$2</rt><rp>)</rp></ruby>',
                "<div style='clear:both'></div>\r\n",
                "<div style='clear:both'></div>\r\n",
                '<style>.view-image{border:1px solid $1;}</style>',
                '<style>.view-image{border:1px solid #555;}</style>',
                '<style>#$1{display:none}</style>',
            ],
            $comment
        );

        //moreタグ - $dataを使用するため個別処理
        $comment = preg_replace(
            [
                '/{more}(?:<br>)?(.*?){\/more}(?:<br>)?/s',
                '/{more (.*?)\|(.*?)}(?:<br>)?(.*?){\/more}(?:<br>)?/s',
            ],
            [
                "<div class='p-more-area hide' data-more-area>$1</div><button data-more data-label='続きを読む...' data-closelabel='閉じる' class='p-more-button p-more-button-active'>続きを読む...</button>\r\n",
                "<div class='p-more-area hide' data-more-area>$3</div><button data-more data-label='$1' data-closelabel='$2' class='p-more-button p-more-button-active'>$1</button>\r\n",
            ],
            $comment
        );

        //tocタグ(ページ内目次)
        $comment = $this->CreateToc($comment);

        //■ Step 6. タグ置換以外の特殊処理-----------------------------------------------------------------------------------------------------------------------

        //埋め込みyoutube対応 画面サイズにリサイズするために<iframe>にyoutubeクラスを追加し、さらにyotube_wrapperクラスでラップする（事前チェック付き）
        if (stripos($comment, 'youtube.com') !== false) {
            $comment = preg_replace_callback('#<iframe([^>]*)src="([^"]*youtube\.com[^"]*)"[^>]*>#i', function ($matches) {
                $attrs = $matches[1];
                $src = $matches[2];

                // class="youtube"がない場合は追加
                if (!preg_match('#\bclass=#i', $attrs)) {
                    $attrs .= ' class="p-youtube"';
                } else {
                    $attrs = preg_replace_callback('#\bclass="([^"]*)"#i', function ($class_matches) {
                        if (strpos($class_matches[1], 'youtube') === false) {
                            return 'class="' . $class_matches[1] . ' p-youtube"';
                        } else {
                            return $class_matches[0];
                        }
                    }, $attrs);
                }

                // iframeをラップ
                return '<div class="p-youtube-wrapper"><iframe' . $attrs . ' src="' . $src . '"></iframe></div>';
            }, $comment);
        }

        //iOS向け特例処理（最適化版：preg_replaceを配列形式で統合）
        $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        if (strpos($user_agent, 'iPhone') !== false || strpos($user_agent, 'iPad') !== false || strpos($user_agent, 'Macintosh') !== false) {
            $comment = preg_replace(
                ['/―/u', '/(─+)/u', '/(…+)/u'],
                ['─', "<span style='text-orientation:mixed;'>$1</span>", "<span style='text-orientation:mixed;'>$1</span>"],
                $comment
            );
        }

        // Step 7. セクションデータの分離
        $section_proc = new TagProcessor();
        $section_proc->data = $data;
        $section_proc->add('section', [$this, 'createSectionData']);
        $comment = $section_proc->process($comment);



        return $comment;
    }

    public function createNewThumbImageTag(string $command, array $args, string $contents, array $data): string
    {
        //最新記事を取得
        if ($this->log_catche['new'] ?? false) {
            $log = $this->log_catche['new'];
        } else {
            $article = new Article($this->db, $this->setting);
            $new_log = $article->search('hidden_at_list != 1 and status = 0 and [type] = 0', [], ['id desc'], 1); //最新記事を取得
            if ($new_log == null || count($new_log) == 0) {
                return '';
            }
            $this->log_catche['new'] = $new_log[0];
            $log = $new_log[0];
        }

        if ($log != null && $log['media_count'] > 0) {
            if ($log['nsfw'] && $data['nsfw_ans'] == false) {
                return '/system_resource/nsfw.png';
            } else {
                $data['current'] = $log;
                return $this->createImageTag($log['media'][0], $args, Environment::getLogUrl($log[0]));
            }
        } else {
            return '';
        }
    }



    const THUMB_SIZE_S = 1;
    const THUMB_SIZE_L = 0;
    const THUMB_POS_CENTER = 0;
    const THUMB_POS_LEFT = 1;
    const THUMB_POS_RIGHT = 2;

    private function createImageTag(array $media, array $args, string $url = '')
    {
        $border = false;
        $thumb_size = self::THUMB_SIZE_L;
        $thumb_position = self::THUMB_POS_CENTER;

        if (isset($args['s']) || isset($args['small'])) {
            $thumb_size = self::THUMB_SIZE_S;
        }
        if (isset($args['b']) || isset($args['border'])) {
            $border = true;
        }
        if (isset($args['c']) || isset($args['center'])) {
            $thumb_position = self::THUMB_POS_CENTER;
        }
        if (isset($args['l']) || isset($args['left'])) {
            $thumb_position = self::THUMB_POS_LEFT;
        }
        if (isset($args['r']) || isset($args['right'])) {
            $thumb_position = self::THUMB_POS_RIGHT;
        }

        return $this->createMediaHtml($media, true, $thumb_size, $border, $thumb_position, $url);
    }
    public function createImageTagBySort(string $command, array $args, string $contents, array $data): string
    {
        $media = null;
        $num = $command;
        foreach ($data['current']['media'] as $item) {
            if ($item['sort'] == $num) {
                $media = $item;
            }
        }
        if ($media == null) {
            return '[ERROR IMG(' . $num . ')]<br>';
        }

        return $this->createImageTag($media, $args);
    }

    public function createImageTagById(string $command, array $args, string $contents, array $data): string
    {
        $id = $command;
        if (!isset($data['current']['media'])) {
            return '[ERROR IMG_ID(' . $id . ')]<br>';
        }
        $media = null;
        foreach ($data['current']['media'] as $search_media) {
            if ($search_media['id'] == $id) {
                $media = $search_media;
                break;
            }
        }
        if ($media == null) {
            return '[ERROR IMG_ID(' . $id . ')]<br>';
        }

        return $this->createImageTag($media, $args);
    }

    public function createRefImageTag(string $command, array $args, string $contents, array $data): string
    {
        $commands = explode('-', $command);
        if ($this->log_catche[$commands[0]] ?? false) {
            $data['current'] = $this->log_catche[$commands[0]];
            return $this->createImageTagBySort($commands[1], $args, $contents, $data);
        }
        $article = new Article($this->db, $this->setting);
        $log = $article->GetById($commands[0]);
        $this->log_catche[$commands[0]] = $log;
        if ($log != null && isset($log['media'])) {
            $data['current'] = $log;
            return $this->createImageTagBySort($commands[1], $args, $contents, $data);
        } else {
            return '';
        }
    }

    public function createSimpleImageTag(string $command, array $args, string $contents, array $data): string
    {
        $media = null;
        $num = $args[0];
        foreach ($data['current']['media'] as $item) {
            if ($item['sort'] == $num) {
                $media = $item;
            }
        }
        if ($media == null) {
            return '[ERROR IMG(' . $num . ')]<br>';
        }

        $width = '';
        $height = '';
        if (isset($args['width'])) {
            $width = "width=\"{$args['width']}\"";
        }
        if (isset($args['height'])) {
            $height = "height=\"{$args['height']}\"";
        }
        if ($width == '' && $height == '') {
            $width = "width=\"{$media['width']}\"";
            $height = "height=\"{$media['height']}\"";
        }
        $style = '';
        if (isset($args['style'])) {
            $style = "style=\"{$args['style']}\"";
        }
        $class = '';
        if (isset($args['class'])) {
            $class = "class=\"{$args['class']}\"";
        }

        $site_url = $this->site_url;
        $url = "{$site_url}/images/{$media['sub_dir']}{$media['file_name']}";

        //タグを構築
        if ($media['type'] == 'video') {
            $img =  "<video controls  src=\"$url\"></video>";
        } else {
            $img = "<img src=\"$url\" $width $height $class $style>";
        }
        return $img;
    }

    private $article_list_count = 0;
    public function createArticleList(string $command, array $args, string $contents, array $data): string
    {
        $list_id = $this->article_list_count + 1;
        if (isset($this->show_indexes[$list_id])) {
            $index = $this->show_indexes[$list_id];
        } else {
            $index = 1;
        }

        if (!isset($args[0])) {
            return '<span style=\'color:red\'>ERROR! {artilce_list}の記述が正しくありません</span>';
        }

        //パラメータ解析  0:tags, 1:sort, 2:style, 3:page_per

        $tags = str_replace('&&', ' ', $args[0]);

        if (isset($args[1])) {
            $sort = strtolower($args[1]);
        } else {
            $sort = '';
        }
        if (isset($args[2])) {
            $style = $args[2];
        } else {
            $style = 'normal';
        }
        $style = s(str_replace('\\', '', $style ?? ''));
        if (isset($args[3]) && is_numeric($args[3])) {
            $records_per_page = $args[3];
        } else {
            $records_per_page = 20;
        }
        $model = new Article($this->db, $this->setting);

        if ($sort == 'asc' || $sort == 'desc') {
            $sort_query = "created_at $sort";
        } else if ($sort == 'title' || $sort == 'title_asc') {
            $sort_query = 'title asc, created_at asc';
        } else if ($sort == 'title_desc') {
            $sort_query = 'title desc, created_at desc';
        } else if ($sort == 'like') {
            $sort_query = 'like desc';
        } else {
            $sort_query = 'created_at asc';
        }
        if ($tags != 'ALL') {
            ['articles' => $articles, 'count' => $total_count] = $model->searchByTagWithCount($tags, false, [$sort_query], $records_per_page, $index);
        } else {
            ['articles' => $articles, 'count' => $total_count] = $model->searchWithTotalCount('hidden_at_list = false and status = 0 and [type] = 0', [], [$sort_query], $records_per_page, $index);
        }
        $remove_idx = -1;
        foreach ($articles as $key => $item) {
            if ($item['id'] == $this->article_id) {
                //この記事自体は一覧に載せない
                $remove_idx = $key;
            } else {
                $articles[$key]['url'] = SITE_URL . Environment::getLogUrl($item);
                $articles[$key]['caption'] = Utility::createCaption($item['contents']);;
                $articles[$key]['created_at'] = s($item['created_at'] ?? '');
                $articles[$key]['updated_at'] = s($item['updated_at'] ?? '');
                $articles[$key]['created_at_day'] = s(substr($item['created_at'] ?? '', 0, 10));
                $articles[$key]['updated_at'] = s($item['updated_at'] ?? '');
                $articles[$key]['updated_at_day'] = s(substr($item['updated_at'] ?? '', 0, 10));
                $title = s($item['title']);
                $articles[$key]['title'] = $title;
                $articles[$key]['nsfw_type'] = $item['nsfw'] ? '0' : '1';
                $articles[$key]['nsfw_status'] = $data['nsfw_ans'] ? '0' : '1';
                $articles[$key]['views'] = $item['views'];
                $articles[$key]['likes'] = $item['like'];
                $articles[$key]['media_count'] = $item['media_count'];

                $comment = $articles[$key]['contents'];
                $comment = str_replace('{{', '&#060;&#060;', $comment); //{{ 表記をエスケープ
                $comment = str_replace('}}', '&#062;&#062;', $comment); //}} 表記をエスケープ
                $comment = str_replace('<<', '&lt;', $comment); //<< 表記を変換
                $comment = str_replace('>>', '&gt;', $comment); //>> 表記を変換
                $comment = preg_replace('/\{([0-9]+)\}/si', "{{$item['id']}-$1}", $comment);
                $comment = preg_replace('/\{([0-9]+) ([^}]+)\}/si', "{{$item['id']}-$1 $2}", $comment);
                $comment = str_replace("\n", '', $comment);
                $comment = $this->linkProcess($comment);
                $comment = $this->markdownProcess($comment);
                $articles[$key]['contents'] = $comment;

                $tags = explode(' ', trim($item['tags']));
                $tag_links = $this->createTagLinks($tags);
                $articles[$key]['tag_links'] = $tag_links;

                $thumbnail_class = '';
                if ($item['nsfw']) {
                    $thumbnail_class .= ' thumb_nsfw';
                }
                if ($data['nsfw_ans']) {
                    $thumbnail_class .= ' thumb_nsfw_consent';
                }
                if ($item['media_count'] == 0) {
                    $thumbnail_class .= ' thumb_text';
                    $articles[$key]['thumbnail_title'] = $title;
                } else {
                    $articles[$key]['thumbnail_title'] = '';
                }
                $articles[$key]['thumbnail_class'] = $thumbnail_class;

                $thumbnail = Utility::getThumbnailUrl($item, $data['nsfw_ans']);
                $articles[$key]['thumbnail_path'] =  $thumbnail; //NSFW同意を考慮したサムネイル

                if ($item['media_count'] == 0) {
                    $thumbnail = Utility::getThumbnailUrl($item, $data['nsfw_ans']);
                    $articles[$key]['raw_thumbnail_path'] = $thumbnail;
                    $articles[$key]['media'] = [];
                } else {
                    $thumbnail = Utility::getThumbnailUrl($item, true);
                    $articles[$key]['raw_thumbnail_path'] = $thumbnail;
                    if ($item['thumbnail_type'] == 1) {
                        //1枚目はサムネイルのため切り捨て
                        array_shift($item['media']);
                    }

                    $articles[$key]['media'] = $item['media'];
                }
            }
        }
        if ($remove_idx != -1) {
            array_splice($articles, $remove_idx, 1);
        }


        //リスト本体作成
        $template_file = "template2_list_$style.php";
        $template = ThemeService::getThemeTemplate($template_file, $this->db, $this->setting);
        //$template = new NoSixTemplate2($template_file, ["./env/templates", "./env/themes/" . $this->setting->getValue("theme", ""), "./core/templates", "./core/templates_sys"], "./env/template_cache", ["db" => $this->db, "setting" => $this->setting]);
        if (!$template->template_exists()) {
            return "[NO SUCH LIST STYLE TEMPLATE($style)]";
        }
        //ページネーション
        $pages = ceil($total_count / $records_per_page);
        if ($pages >= 2) {
            $template->set_data('pagination', $this->createListPagination($list_id, $index, $pages));
        } else {
            $template->set_data('pagination', []);
        }

        $template->set_data('base_url', $this->base_url);
        $template->set_data('site_url', $this->site_url);
        $template->set_data('articles', $articles);
        $list = "<a name=\"list_{$list_id}\"></a>" . $template->getString();

        $this->article_list_count++;

        return $list;
    }

    public function createExtractToc(string $command, array $args, string $contents, array $data): string
    {
        if (!isset($args[0])) {
            return '<span style=\'color:red\'>ERROR! {artilce_list}の記述が正しくありません</span>';
        }

        //パラメータ解析  0:tags, 1:sort, 2:style
        $tags = str_replace('&&', ' ', $args[0]);

        if (isset($args[1])) {
            $sort = strtolower($args[1]);
        } else {
            $sort = '';
        }
        if (isset($args[2])) {
            $style = $args[2];
        } else {
            $style = 'text';
        }
        $style = s(str_replace('\\', '', $style ?? ''));

        $model = new Article($this->db, $this->setting);

        if ($sort == 'asc' || $sort == 'desc') {
            $sort_query = "created_at $sort";
        } else if ($sort == 'title' || $sort == 'title_asc') {
            $sort_query = 'title asc, created_at asc';
        } else if ($sort == 'title_desc') {
            $sort_query = 'title desc, created_at desc';
        } else if ($sort == 'like') {
            $sort_query = 'like desc';
        } else {
            $sort_query = 'created_at asc';
        }
        if ($tags != 'ALL') {
            ['articles' => $articles, 'count' => $total_count] = $model->searchByTagWithCount($tags, false, [$sort_query]);
        } else {
            ['articles' => $articles, 'count' => $total_count] = $model->searchWithTotalCount('hidden_at_list = false and status = 0 and [type] = 0', [], [$sort_query]);
        }

        foreach ($articles as $key => $item) {
            if ($item['id'] == $this->article_id) {
                $articles[$key]['current'] = true;
            } else {
                $articles[$key]['current'] = true;
            }

            $articles[$key]['url'] = SITE_URL . Environment::getLogUrl($item);
            $articles[$key]['caption'] = Utility::createCaption($item['contents']);;
            $articles[$key]['created_at'] = s($item['created_at'] ?? '');
            $articles[$key]['updated_at'] = s($item['updated_at'] ?? '');
            $articles[$key]['created_at_day'] = s(substr($item['created_at'] ?? '', 0, 10));
            $articles[$key]['updated_at'] = s($item['updated_at'] ?? '');
            $articles[$key]['updated_at_day'] = s(substr($item['updated_at'] ?? '', 0, 10));
            $title = s($item['title']);
            $articles[$key]['title'] = $title;
            $articles[$key]['nsfw_type'] = $item['nsfw'] ? '0' : '1';
            $articles[$key]['nsfw_status'] = $data['nsfw_ans'] ? '0' : '1';
            $articles[$key]['views'] = $item['views'];
            $articles[$key]['likes'] = $item['like'];
            $articles[$key]['media_count'] = $item['media_count'];

            $tags = explode(' ', trim($item['tags']));
            $tag_links = $this->createTagLinks($tags);
            $articles[$key]['tag_links'] = $tag_links;

            $thumbnail_class = '';
            if ($item['nsfw']) {
                $thumbnail_class .= ' thumb_nsfw';
            }
            if ($data['nsfw_ans']) {
                $thumbnail_class .= ' thumb_nsfw_consent';
            }
            if ($item['media_count'] == 0) {
                $thumbnail_class .= ' thumb_text';
                $articles[$key]['thumbnail_title'] = $title;
            } else {
                $articles[$key]['thumbnail_title'] = '';
            }
            $articles[$key]['thumbnail_class'] = $thumbnail_class;

            $thumbnail = Utility::getThumbnailUrl($item, $data['nsfw_ans']);
            $articles[$key]['thumbnail_path'] =  $thumbnail; //NSFW同意を考慮したサムネイル

            if ($item['media_count'] == 0) {
                $thumbnail = Utility::getThumbnailUrl($item, $data['nsfw_ans']);
                $articles[$key]['raw_thumbnail_path'] = $thumbnail;
                $articles[$key]['media'] = [];
            } else {
                $thumbnail = Utility::getThumbnailUrl($item, true);
                $articles[$key]['raw_thumbnail_path'] = $thumbnail;
                if ($item['thumbnail_type'] == 1) {
                    //1枚目はサムネイルのため切り捨て
                    array_shift($item['media']);
                }

                $articles[$key]['media'] = $item['media'];
            }
        }

        //リスト本体作成
        $template_file = "template2_list_$style.php";
        $template = ThemeService::getThemeTemplate($template_file, $this->db, $this->setting);
        if (!$template->template_exists()) {
            return "[NO SUCH LIST STYLE TEMPLATE($style)]";
        }

        $template->set_data('pagination', []);

        $template->set_data('base_url', $this->base_url);
        $template->set_data('site_url', $this->site_url);
        $template->set_data('articles', $articles);

        $list = $template->getString();

        return $list;
    }
    public function createSectionData(string $command, array $args, string $contents, array $data): string
    {
        $this->section_data[$args[0]] = $contents;
        return '';
    }

    private function createPagination($current_index, $pages)
    {
        $pagination = [];
        $min_page = max(1, $current_index < 5 ? 1 : $current_index - 5);
        $max_page = min($pages, $min_page + 9);

        if ($current_index >= 5) {
            $max_page = min($pages, $current_index + 5);
            $min_page = max(1, $max_page - 10);
        }

        for ($i = $min_page; $i <= $max_page; $i++) {
            $is_first =  $i == 1;
            $is_last = $i == $pages;
            $is_page_first =  ($i == $min_page && $i != 1);
            $is_page_last = ($i == $max_page && $i != $pages);
            $is_current = $i == $current_index;

            //スタイルクラス
            $class = '';
            $class .= $is_current ? 'p-pagination-current ' : '';
            $class .= $is_first ? 'p-pagination-first ' : '';
            $class .= $is_last ? 'p-pagination-last ' : '';
            $class .= $is_page_first ? 'p-pagination-page-first ' : '';
            $class .= $is_page_last ? 'p-pagination-page-last ' : '';
            $class = rtrim($class);

            //リンク先URL決定
            if ($i == $current_index) {
                $url = '';
            } else {
                if (environment::userewrite() || $this->article_id == -1) {
                    $url = "?page={$i}";
                } else {
                    $url = "?q={$this->article_id}&page={$i}";
                }
            }
            $pagination[] = ['index' => $i, 'is_current' => $is_current, 'is_first' => $is_first, 'is_last' => $is_last, 'is_page_first' => $is_page_first, 'is_page_last' => $is_page_last, 'url' => $url, 'class' => $class];
        }
        return $pagination;
    }

    //リスト用ページネーション構築
    private function createListPagination($list_id, $current_index, $pages)
    {
        //ページ指定
        $indexes = $this->show_indexes;
        for ($i = 0; $i < $list_id; $i++) {
            if (isset($indexes[$i]) === false) {
                $indexes[$i] = '1';
            }
        }

        $pagination = [];
        $min_page = max(1, $current_index < 5 ? 1 : $current_index - 5);
        $max_page = min($pages, $min_page + 9);

        if ($current_index >= 5) {
            $max_page = min($pages, $current_index + 5);
            $min_page = max(1, $max_page - 10);
        }

        for ($i = $min_page; $i <= $max_page; $i++) {
            $article_list_base = $indexes;
            $article_list_base[$list_id] = $i;
            $index_list = implode(',', $article_list_base);
            if ($i == $current_index) {
                $pagination[] = ['index' => $i, 'is_current' => true, 'is_first' => $i == 1, 'is_last' => $i == $pages, 'is_page_first' => ($i == $min_page && $i != 1), 'is_page_last' => ($i == $max_page && $i != $pages), 'url' => ''];
            } else {
                if (environment::userewrite() || $this->article_id == -1) {
                    $pagination[] = ['index' => $i, 'is_current' => false, 'is_first' => $i == 1, 'is_last' => $i == $pages, 'is_page_first' => ($i == $min_page && $i != 1), 'is_page_last' => ($i == $max_page && $i != $pages), 'url' => "?page={$index_list}#list_{$list_id}"];
                } else {

                    $pagination[] = ['index' => $i, 'is_current' => false, 'is_first' => $i == 1, 'is_last' => $i == $pages, 'is_page_first' => ($i == $min_page && $i != 1), 'is_page_last' => ($i == $max_page && $i != $pages), 'url' => "?q={$this->article_id}&page={$index_list}#list_$list_id"];
                }
            }
        }
        return $pagination;
    }

    //パスワード処理
    private function passwordProc(Request $req, string $contents): array
    {
        //パスワード確認
        $password_mode = false;
        $input_password = $req->getArgs('password', '');
        preg_match('/{password ([^\s\}]+)([^}]*)}(.*?){\/password}/si', $contents, $match);
        if ($match) {
            Utility::SessionStart();
            $password = $match[1];
            $password_caption = $match[2];
            if (($input_password == '' || ($input_password != '' && $input_password != $password)) && (($_SESSION['log_password'] ?? '') != $password)) {
                $password_mode = true;
                $template_file = 'template2_parts_password.php';
                $template = ThemeService::getThemeTemplate($template_file, $this->db, $this->setting);
                //$template = new NoSixTemplate2("template2_parts_password.php", ["./env/templates", "./env/themes/" . $this->setting->getValue("theme", ""), "./core/templates", "./core/templates_sys"], "./env/template_cache", ["db" => $this->db, "setting" => $this->setting]);
                $template->set_data('error', ($input_password != '' && $input_password != $password));
                $template->set_data('hint', $password_caption);
                $form = $template->getString();
                $contents = r('/{password ([^\s\}]+)([^}]*)}(.*?){\/password}(?:\r?\n)?/si',  $form, $contents);
            } else {
                $_SESSION['log_password'] = $input_password;
                $password_mode = false;
                //パスワード認証完了
                $contents = r('/{password ([^\s\}]+)([^}]*)}\s*(.*?){\/password}(?:\r?\n)?/si', '<a name="passwrod_area"></a>$3', $contents);
            }
        }
        return [$password_mode, $contents];
    }


    //MarkDownによるテーブル表記をHTMLに変換
    function markdownTableToHtml($text)
    {
        // テーブルの行を抽出
        $pattern = '/^\|(.*?)\|\n\|(.*?)\|\n((\|(.*?)\|\n)*)/im';
        $text = preg_replace_callback($pattern, function ($matches) {

            // ヘッダー行の処理
            $header_cells = explode('|', trim($matches[1]));
            $header = '<thead><tr>';
            foreach ($header_cells as $cell) {
                $header .= '<th>' . trim($cell) . '</th>';
            }
            $header .= '</tr></thead>';

            // データ行の処理
            $rows = explode("\n", trim($matches[3]));
            $body = '<tbody>';
            foreach ($rows as $row) {
                $body .= '<tr>';
                $cells = explode('|', trim(substr($row, 1, strlen($row) - 2)));
                foreach ($cells as $cell) {
                    $body .= '<td>' . trim($cell) . '</td>';
                }
                $body .= '</tr>';
            }
            $body .= '</tbody>';

            // HTMLテーブルを作成
            $table = '<table class="p-md">' . $header . $body . '</table>';
            return $table;
        }, $text);

        return $text;
    }

    //コードエリア(PPP)変換
    function markdownPPPCodeBlockToHTML(string $text): string
    {
        if (strstr($text, '```ppp') === false) {
            return $text;
        }
        return preg_replace_callback(
            '/```ppp\s*(.*?)\s*```/s',
            function ($matches) {
                $code = $matches[1];
                $escaped = htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
                $colored = preg_replace(
                    '/({.*?})/',
                    '<span class="p-md-highlight-2">$1</span>',
                    $escaped
                );
                $colored = preg_replace(
                    '/(&lt;.*?&gt;)/',
                    '<span class="p-md-highlight-1">$1</span>',
                    $colored
                );
                return '<pre class="p-md-code"><code>' . $colored . '</code></pre>';
            },
            $text
        );
    }

    //コードエリア(HTML)変換
    function markdownHTMLCodeBlockToHTML(string $text): string
    {
        if (strstr($text, '```html') === false) {
            return $text;
        }
        return preg_replace_callback(
            '/```html\s*(.*?)\s*```/s',
            function ($matches) {
                $code = $matches[1];
                $escaped = htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
                $colored = preg_replace(
                    '/(&lt;.*?&gt;)/',
                    '<span class="p-md-highlight-1">$1</span>',
                    $escaped
                );

                return '<pre class="p-md-code"><code>' . $colored . '</code></pre>';
            },
            $text
        );
    }

    function markdownCodeBlockToHTML(string $text): string
    {
        if (strstr($text, '```') === false) {
            return $text;
        }
        return preg_replace_callback(
            '/```\s*(.*?)\s*```/s',
            function ($matches) {
                $code = $matches[1];
                $escaped = htmlspecialchars($code, ENT_QUOTES, 'UTF-8');


                return '<pre class="p-md-code"><code>' . $escaped . '</code></pre>';
            },
            $text
        );
    }

    public function formatToList(array $article): string
    {
        $s = $article['content'];
        $id = $article['id'];
        $s = preg_replace('/{([0-9]+)}/si', '{' . $id . '-$1}', $s);
        return $s;
    }

    // h2, h3の目次を作成する
    function CreateToc(string $html): string
    {
        if (strpos($html, '{toc') === false) {
            return $html;
        }
        $title = "";
        if (preg_match('/{toc ?([^}]*)}/is', $html, $matches)) {
            $title = $matches[1];
        }

        $pattern = '/<(h[23])([^>]*)>(.*?)<\/\1>/is';

        $toc = [];
        $index = 1;

        $html = preg_replace_callback($pattern, function ($matches) use (&$toc, &$index) {
            $tag   = $matches[1]; // h2 or h3
            $attrs = $matches[2];
            $text  = trim(s($matches[3] ?? ''));

            $anchor = 'toc-' . $index++;

            $toc[] = [
                'level' => (int)substr($tag, 1),
                'text'  => $text,
                'id'    => $anchor,
            ];

            // <a name=""> を見出し直前に埋め込み
            return sprintf(
                '<a name="%s"></a><%s%s>%s</%s>',
                $anchor,
                $tag,
                $attrs,
                $matches[3],
                $tag
            );
        }, $html);

        $html = preg_replace('/{toc ?([^}]*)}/is', $this->buildToc($title, $toc), $html);
        return $html;
    }

    function buildToc(string $title, array $toc): string
    {
        if ($title != "") {
            $title = htmlspecialchars($title);
            $title_tag = "<span class=\"p-toc-title\">$title</span>";
        } else {
            $title_tag = "";
        }
        $html = "<div class=\"p-toc\">$title_tag<ul>";
        $currentLevel = 2;

        foreach ($toc as $item) {
            $level = $item['level'];

            if ($level > $currentLevel) {
                $html .= '<ul>';
            } elseif ($level < $currentLevel) {
                $html .= str_repeat('</ul>', $currentLevel - $level);
            }

            $html .= sprintf(
                '<li class="p-toc-lv' . $level . '"><a href="#%s">%s</a></li>',
                h($item['id'], ENT_QUOTES),
                h($item['text'], ENT_QUOTES)
            );

            $currentLevel = $level;
        }

        $html .= str_repeat('</ul>', $currentLevel - 2);
        $html .= '</ul></div>';

        return $html;
    }
}


//preg_replaceのショート入力用関数
function r(string $pattern, string $replacement, $subject): string
{
    return preg_replace($pattern, $replacement, $subject);
}
