<?php
declare(strict_types=1);

/**
 * ks_util.php — shared utilities + auto-discovery
 * Loaded by index.php and read.php via require_once.
 */

function ks_h(string $s): string {
  return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function ks_base64url_encode(string $s): string {
  return rtrim(strtr(base64_encode($s), '+/', '-_'), '=');
}

function ks_base64url_decode(string $s): string {
  $s = strtr($s, '-_', '+/');
  $pad = strlen($s) % 4;
  if ($pad) $s .= str_repeat('=', 4 - $pad);
  $out = base64_decode($s, true);
  return $out === false ? '' : $out;
}

/** @return never */
function ks_abort(int $code, string $msg): void {
  http_response_code($code);
  header('Content-Type: text/plain; charset=utf-8');
  echo $msg;
  exit;
}

/**
 * List top-level directories in site root that start with "kepler".
 * @return string[] roots like ["kepler_drift", "kepler_xyz"]
 */
function ks_list_kepler_roots(string $siteRoot, int $maxRoots = 50): array {
  $roots = [];
  $items = @scandir($siteRoot);
  if ($items === false) return $roots;

  foreach ($items as $name) {
    if ($name === '.' || $name === '..') continue;
    if (!str_starts_with($name, 'kepler')) continue;

    $path = $siteRoot . DIRECTORY_SEPARATOR . $name;
    if (is_dir($path)) $roots[] = $name;
    if (count($roots) >= $maxRoots) break;
  }

  sort($roots);
  return $roots;
}

/**
 * Discover Book1-Full.md / Book2-Full.md / Book3-Full.md anywhere under kepler* roots.
 *
 * Return structure:
 * [
 *   'projects' => [
 *     'kepler_drift' => [
 *        'label' => 'kepler_drift',
 *        'best'  => [1=>entry|null, 2=>..., 3=>...],
 *        'all'   => [1=>[entry...], 2=>..., 3=>...]
 *     ],
 *     ...
 *   ],
 *   'featured' => 'kepler_drift'|null
 * ]
 *
 * entry:
 *  ['book'=>1,'dir'=>'kepler_drift/book1/chapter','file'=>'/abs/.../Book1-Full.md','mtime'=>1234567890,'root'=>'kepler_drift']
 */
function ks_discover_library(
  string $siteRoot,
  int $maxDepth = 10,
  int $maxKeplerRoots = 50,
  int $maxMatchesPerBook = 200
): array {
  $roots = ks_list_kepler_roots($siteRoot, $maxKeplerRoots);

  $projects = [];
  foreach ($roots as $root) {
    $projects[$root] = [
      'label' => $root,
      'best'  => [1 => null, 2 => null, 3 => null],
      'all'   => [1 => [],   2 => [],   3 => []],
    ];

    $rootAbs = $siteRoot . DIRECTORY_SEPARATOR . $root;

    $it = new RecursiveIteratorIterator(
      new RecursiveDirectoryIterator($rootAbs, FilesystemIterator::SKIP_DOTS),
      RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($it as $path => $info) {
      if ($it->getDepth() > $maxDepth) continue;
      /** @var SplFileInfo $info */
      if (!$info->isFile()) continue;

      $fn = $info->getFilename();
      if (!preg_match('/^Book([1-3])-Full\.md$/', $fn, $m)) continue;

      $book = (int)$m[1];
      $abs = $info->getPathname();
      if (!is_readable($abs)) continue;

      $mtime = (int)$info->getMTime();
      $dirAbs = $info->getPath();
      $dirRel = str_replace('\\', '/', substr($dirAbs, strlen($siteRoot) + 1));

      $entry = [
        'book' => $book,
        'dir'  => $dirRel,
        'file' => $abs,
        'mtime'=> $mtime,
        'root' => $root,
      ];

      if (count($projects[$root]['all'][$book]) < $maxMatchesPerBook) {
        $projects[$root]['all'][$book][] = $entry;
      }

      $curr = $projects[$root]['best'][$book];
      if ($curr === null || $mtime > (int)$curr['mtime']) {
        $projects[$root]['best'][$book] = $entry;
      }
    }

    for ($b=1; $b<=3; $b++) {
      usort($projects[$root]['all'][$b], fn($a,$c) => (int)$c['mtime'] <=> (int)$a['mtime']);
    }
  }

  // Choose featured project:
  // prefer project with B1+B2+B3, else newest B1, else any with anything.
  $featured = null;
  $bestScore = -1;

  foreach ($projects as $root => $p) {
    $has1 = $p['best'][1] !== null;
    $has2 = $p['best'][2] !== null;
    $has3 = $p['best'][3] !== null;

    $score = 0;
    if ($has1) $score += 1000;
    if ($has2) $score += 600;
    if ($has3) $score += 600;

    // tie-break: latest Book1 mtime
    $score += (int)($p['best'][1]['mtime'] ?? 0);

    if ($score > $bestScore) {
      $bestScore = $score;
      $featured = $root;
    }
  }

  return ['projects' => $projects, 'featured' => $featured];
}

/**
 * Choose default project root.
 */
function ks_choose_default_project(array $projects, ?string $featured): ?string {
  if ($featured && isset($projects[$featured])) return $featured;

  // fallback: first project that has any book
  foreach ($projects as $root => $p) {
    if ($p['best'][1] || $p['best'][2] || $p['best'][3]) return $root;
  }
  // else: any project at all
  return empty($projects) ? null : array_key_first($projects);
}

/**
 * Choose a book within a project:
 * - If requested exists, use it; else B1->B2->B3.
 */
function ks_choose_book(array $project, int $requested): int {
  if (isset($project['best'][$requested]) && $project['best'][$requested] !== null) return $requested;
  foreach ([1,2,3] as $b) {
    if (($project['best'][$b] ?? null) !== null) return $b;
  }
  return 1;
}

/**
 * Build a read.php link given a project root (kepler_drift) and book #.
 */
function ks_read_link(string $projectRoot, int $book): string {
  $pick = ks_base64url_encode($projectRoot);
  $q = http_build_query(['pick' => $pick, 'book' => $book]);
  return "read.php?{$q}";
}

function ks_human_date(int $ts): string {
  return date('F j, Y', $ts);
}
