<?php
$pageTitle   = 'View Source — dispelled.ca';
$currentPage = 'source';

require_once __DIR__ . '/includes/config.php';

// ── Viewable files ────────────────────────────────────────────────────────────
// Each entry: display label => path relative to this file's directory.
// The downloadable package contains the complete site. The browser viewer
// intentionally shows one representative article rather than the full
// article library.
$files = [
    'pages' => [
        'index.php'           => ['label' => 'index.php',            'path' => __DIR__ . '/index.php'],
        'cori.php'            => ['label' => 'cori.php',             'path' => __DIR__ . '/cori.php'],
        'j.php'               => ['label' => 'j.php',                'path' => __DIR__ . '/j.php'],
        'weather.php'         => ['label' => 'weather.php',          'path' => __DIR__ . '/weather.php'],
        'community.php'       => ['label' => 'community.php',        'path' => __DIR__ . '/community.php'],
        'github.php'          => ['label' => 'github.php',           'path' => __DIR__ . '/github.php'],
        'stocks.php'          => ['label' => 'stocks.php',           'path' => __DIR__ . '/stocks.php'],
        'links.php'           => ['label' => 'links.php',            'path' => __DIR__ . '/links.php'],
        'about.php'           => ['label' => 'about.php',            'path' => __DIR__ . '/about.php'],
        'agent.php'           => ['label' => 'agent.php',            'path' => __DIR__ . '/agent.php'],
        'categories.php'      => ['label' => 'categories.php',       'path' => __DIR__ . '/categories.php'],
        'source.php'          => ['label' => 'source.php',           'path' => __FILE__],
    ],
    'includes' => [
        'includes/config.php'  => ['label' => 'config.php',          'path' => __DIR__ . '/includes/config.php'],
        'includes/header.php'  => ['label' => 'header.php',          'path' => __DIR__ . '/includes/header.php'],
        'includes/footer.php'  => ['label' => 'footer.php',          'path' => __DIR__ . '/includes/footer.php'],
        'includes/rss.php'     => ['label' => 'rss.php',             'path' => __DIR__ . '/includes/rss.php'],
        'includes/weather-full.php' => ['label' => 'weather-full.php', 'path' => __DIR__ . '/includes/weather-full.php'],
        'includes/reddit.php'  => ['label' => 'reddit.php',          'path' => __DIR__ . '/includes/reddit.php'],
    ],
    'assets' => [
        'css/style.css'        => ['label' => 'style.css',           'path' => __DIR__ . '/css/style.css'],
        'router.php'           => ['label' => 'router.php',          'path' => __DIR__ . '/../router.php'],
    ],
    'example article' => [
        'categories/Code Notes/How this website was made.php' => [
            'label' => 'How this website was made.php',
            'path'  => __DIR__ . '/categories/Code Notes/How this website was made.php',
        ],
    ],
];

// Build a flat lookup for validation
$allFiles = array_merge(...array_values($files));

$requested = $_GET['file'] ?? 'index.php';
if (!array_key_exists($requested, $allFiles)) {
    $requested = 'index.php';
}

$fileInfo = $allFiles[$requested];
$source   = file_get_contents($fileInfo['path']);
$ext      = pathinfo($fileInfo['path'], PATHINFO_EXTENSION);

// Tune PHP's built-in highlighter to match the dark theme
ini_set('highlight.comment', '#6d95b0');
ini_set('highlight.default', '#ccdde8');
ini_set('highlight.html',    '#5ab8e8');
ini_set('highlight.keyword', '#79c0ff');
ini_set('highlight.string',  '#e8a87c');

/**
 * Split highlighted PHP HTML into per-line strings, keeping spans balanced.
 *
 * highlight_string() wraps tokens in <span> tags that can straddle <br />
 * boundaries (e.g. a multi-line string). We track every span opened on
 * previous lines and prepend them (unclosed) to the next line, closing them
 * at the end of that line so each line is valid HTML on its own.
 */
function splitHighlightedLines(string $html): array
{
    // PHP versions differ on outer wrapper (<code> vs <span>) and line-break
    // representation (<br /> in older versions, real \n in PHP 8.3+).
    // Strip whichever outer tag is present.
    $inner = preg_replace('#^<(?:code|span)[^>]*>(.*)</(?:code|span)>$#s', '$1', $html);

    // Normalise every line-break variant to a plain \n so we split once.
    $inner = str_replace(['<br />', '<br/>', '<br>'], "\n", $inner);

    $rawLines = explode("\n", $inner);
    $lines    = [];
    $openTags = [];   // stack of currently-open <span …> strings

    foreach ($rawLines as $raw) {
        // Prepend any spans that were still open from the previous line
        $line = implode('', $openTags) . $raw;

        // Find all <span …> and </span> tags in this line to update the stack
        preg_match_all('#<span\s[^>]*>|</span>#i', $raw, $m);
        foreach ($m[0] as $tag) {
            if (stripos($tag, '</span') === 0) {
                array_pop($openTags);
            } else {
                $openTags[] = $tag;
            }
        }

        // Close any spans that are still open so the line is valid HTML
        $line .= str_repeat('</span>', count($openTags));

        $lines[] = $line;
    }

    return $lines;
}

// Build the numbered lines array
if ($ext === 'php') {
    $highlighted  = highlight_string($source, true);
    $numberedLines = splitHighlightedLines($highlighted);
} else {
    $numberedLines = array_map('htmlspecialchars', explode("\n", $source));
}

require_once __DIR__ . '/includes/header.php';
?>

<div class="source-header">
  <div style="display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:1rem;">
    <div>
      <h1 class="page-title" style="margin-bottom:.5rem;">View Source</h1>
      <p class="source-subtitle">The actual code and article source behind this site. No minification. No transpilation. What you see is what runs.</p>
    </div>
    <a href="<?= BASE_PATH ?>/download.php" download="dispelled-ca.zip" class="dl-btn">&#8595; Download PHP edition</a>
  </div>
</div>

<div class="source-layout">

  <!-- File browser sidebar -->
  <nav class="source-nav" aria-label="Source files">
    <?php foreach ($files as $groupName => $group): ?>
      <div class="source-nav-group">
        <span class="source-nav-label"><?= htmlspecialchars($groupName) ?></span>
        <?php foreach ($group as $key => $info): ?>
          <a href="?file=<?= urlencode($key) ?>"
             class="source-nav-link<?= $key === $requested ? ' active' : '' ?>">
            <?= htmlspecialchars($info['label']) ?>
          </a>
        <?php endforeach; ?>
      </div>
    <?php endforeach; ?>
  </nav>

  <!-- Source panel -->
  <div class="source-panel">
    <div class="source-toolbar">
      <span class="source-filename"><?= htmlspecialchars($fileInfo['label']) ?></span>
      <span class="source-lines"><?= count($numberedLines) ?> lines</span>
    </div>
    <div class="source-code-wrap">
      <table class="source-table" aria-label="Source code">
        <tbody>
          <?php foreach ($numberedLines as $i => $line): ?>
            <tr>
              <td class="source-ln" aria-hidden="true"><?= $i + 1 ?></td>
              <td class="source-line"><?= $line !== '' ? $line : '&nbsp;' ?></td>
            </tr>
          <?php endforeach; ?>
        </tbody>
      </table>
    </div>
  </div>

</div>

<?php require_once __DIR__ . '/includes/footer.php'; ?>
