<?php
declare(strict_types=1);

/**
 * ks_markdown.php — a small, safe Markdown renderer (intentionally limited)
 * Supports:
 *  - Headings #..######
 *  - Paragraphs
 *  - --- / *** / ✦ as scene breaks
 *  - Blockquotes >
 *  - Ordered/Unordered lists
 *  - Inline: **bold**, *italic*, `code`
 *  - Code fences ```lang
 */

function ks_slugify(string $text, array &$used): string {
  $s = mb_strtolower($text, 'UTF-8');
  $s = preg_replace('/[^\p{L}\p{N}\s-]+/u', '', $s) ?? $s;
  $s = preg_replace('/\s+/u', '-', trim($s)) ?? $s;
  $s = $s === '' ? 'section' : $s;

  $base = $s;
  $i = 2;
  while (isset($used[$s])) {
    $s = $base . '-' . $i;
    $i++;
  }
  $used[$s] = true;
  return $s;
}

function ks_md_inline(string $text): string {
  $t = htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

  $t = preg_replace_callback('/`([^`]+)`/', fn($m) => '<code>'.$m[1].'</code>', $t) ?? $t;
  $t = preg_replace('/\*\*([^\*]+)\*\*/', '<strong>$1</strong>', $t) ?? $t;
  $t = preg_replace('/(?<!\*)\*([^\*]+)\*(?!\*)/', '<em>$1</em>', $t) ?? $t;

  return $t;
}

/**
 * @param string $md
 * @param array $toc output: [ ['level'=>2,'id'=>'x','title'=>'y'], ... ]
 */
function ks_render_markdown(string $md, array &$toc): string {
  $lines = explode("\n", $md);

  $html = [];
  $toc = [];
  $headingIds = [];

  $inCode = false;
  $codeLang = '';
  $codeBuf = [];

  $inUl = false;
  $inOl = false;
  $inQuote = false;

  $paraBuf = [];

  $flushParagraph = function() use (&$paraBuf, &$html) {
    if (!$paraBuf) return;
    $text = trim(implode(' ', $paraBuf));
    if ($text !== '') $html[] = '<p>' . ks_md_inline($text) . '</p>';
    $paraBuf = [];
  };

  $closeBlocks = function() use (&$html, &$inUl, &$inOl, &$inQuote) {
    if ($inUl) { $html[] = '</ul>'; $inUl = false; }
    if ($inOl) { $html[] = '</ol>'; $inOl = false; }
    if ($inQuote) { $html[] = '</blockquote>'; $inQuote = false; }
  };

  foreach ($lines as $line) {
    // Code fences
    if (preg_match('/^```(.*)\s*$/', $line, $m)) {
      if (!$inCode) {
        $flushParagraph();
        $closeBlocks();
        $inCode = true;
        $codeLang = trim($m[1] ?? '');
        $codeBuf = [];
      } else {
        $inCode = false;
        $code = htmlspecialchars(implode("\n", $codeBuf), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        $cls = $codeLang !== '' ? ' class="language-' . htmlspecialchars($codeLang, ENT_QUOTES, 'UTF-8') . '"' : '';
        $html[] = "<pre><code{$cls}>{$code}</code></pre>";
        $codeLang = '';
        $codeBuf = [];
      }
      continue;
    }
    if ($inCode) { $codeBuf[] = $line; continue; }

    $trim = rtrim($line);

    // Blank line
    if (trim($trim) === '') {
      $flushParagraph();
      $closeBlocks();
      continue;
    }

    // Scene break
    if (preg_match('/^\s*(---|\*\*\*|✦)\s*$/u', trim($trim))) {
      $flushParagraph();
      $closeBlocks();
      $html[] = '<hr class="scene-break">';
      continue;
    }

    // Headings
    if (preg_match('/^(#{1,6})\s+(.*)$/', $trim, $m)) {
      $flushParagraph();
      $closeBlocks();

      $level = strlen($m[1]);
      $title = trim($m[2]);
      $id = ks_slugify($title, $headingIds);

      if ($level >= 2 && $level <= 4) {
        $toc[] = ['level' => $level, 'id' => $id, 'title' => $title];
      }

      $html[] = sprintf(
        '<h%d id="%s">%s</h%d>',
        $level,
        htmlspecialchars($id, ENT_QUOTES, 'UTF-8'),
        ks_md_inline($title),
        $level
      );
      continue;
    }

    // Blockquote
    if (preg_match('/^\s*>\s?(.*)$/', $trim, $m)) {
      $flushParagraph();
      if (!$inQuote) {
        $closeBlocks();
        $html[] = '<blockquote>';
        $inQuote = true;
      }
      $html[] = '<p>' . ks_md_inline($m[1]) . '</p>';
      continue;
    }

    // Ordered list
    if (preg_match('/^\s*\d+\.\s+(.*)$/', $trim, $m)) {
      $flushParagraph();
      if ($inUl) { $html[] = '</ul>'; $inUl = false; }
      if ($inQuote) { $html[] = '</blockquote>'; $inQuote = false; }
      if (!$inOl) { $html[] = '<ol>'; $inOl = true; }
      $html[] = '<li>' . ks_md_inline($m[1]) . '</li>';
      continue;
    }

    // Unordered list
    if (preg_match('/^\s*[-\*]\s+(.*)$/', $trim, $m)) {
      $flushParagraph();
      if ($inOl) { $html[] = '</ol>'; $inOl = false; }
      if ($inQuote) { $html[] = '</blockquote>'; $inQuote = false; }
      if (!$inUl) { $html[] = '<ul>'; $inUl = true; }
      $html[] = '<li>' . ks_md_inline($m[1]) . '</li>';
      continue;
    }

    // Paragraph line
    $paraBuf[] = trim($trim);
  }

  $flushParagraph();
  if ($inUl) $html[] = '</ul>';
  if ($inOl) $html[] = '</ol>';
  if ($inQuote) $html[] = '</blockquote>';

  return implode("\n", $html);
}
