<?php
/**
 * Публичный файловый сайт — проект «Внешние сайты».
 * Раздача файлов: сертификаты, дистрибутивы, установщики.
 *
 * Возможности:
 *   - папка = раздел; папки, начинающиеся с #, пропускаются
 *   - боковая навигация + якоря на разделы
 *   - теги: облако вверху, плашки в карточках, фильтр по клику
 *   - поиск по названию/описанию/имени файла/тегам/разделу
 *   - ссылки на внешние ресурсы (записи с url)
 */

$ROOT       = __DIR__;
$EMPTY_DESC = 'Добавьте описание, пожалуйста.';

// ---- description.yaml ---------------------------------------------------
$meta = [];
$yamlFile = $ROOT . '/description.yaml';
if (is_readable($yamlFile) && function_exists('yaml_parse_file')) {
    $parsed = @yaml_parse_file($yamlFile);
    if (is_array($parsed)) $meta = $parsed;
}
$sectionsMeta = $meta['sections'] ?? [];
$siteTitle    = $meta['title']    ?? 'Файлы';
$subtitle     = $meta['subtitle'] ?? '';
$badge        = $meta['badge']    ?? '';

function e($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }

function md_inline($s) {
    $s = e(trim((string)$s));
    return preg_replace(
        '~\[([^\]]+)\]\((https?://[^\s)]+|/[^\s)]*)\)~u',
        '<a class="inline-link" href="$2" target="_blank" rel="noopener">$1</a>',
        $s
    );
}

function humanSize($bytes) {
    if ($bytes <= 0) return '';
    $u = ['Б', 'КБ', 'МБ', 'ГБ'];
    $i = min((int)floor(log($bytes, 1024)), count($u) - 1);
    return round($bytes / pow(1024, $i), $i ? 1 : 0) . ' ' . $u[$i];
}

/** ID для якоря */
function slug($text) {
    $t = mb_strtolower(trim(strip_tags((string)$text)), 'UTF-8');
    $t = preg_replace('~[^\p{L}\p{N}]+~u', '-', $t);
    return trim($t, '-') ?: 'section';
}

/** Нормализует теги в массив строк */
function norm_tags($t) {
    if (empty($t)) return [];
    if (is_string($t)) $t = preg_split('~\s*,\s*~u', $t);
    return array_values(array_filter(array_map('trim', (array)$t)));
}

// ---- сбор дерева --------------------------------------------------------
$sections = [];   // [folder => ['name','desc','bg','anchor','items'=>[...]]]
$allTags  = [];   // тег => количество

foreach (scandir($ROOT) ?: [] as $folder) {
    if ($folder === '.' || $folder === '..') continue;
    if (!is_dir($ROOT . '/' . $folder)) continue;
    if (mb_substr($folder, 0, 1, 'UTF-8') === '#') continue;   // скрытая папка

    $sm    = $sectionsMeta[$folder] ?? [];
    $fMeta = $sm['files'] ?? [];
    $secTags = norm_tags($sm['tags'] ?? []);

    $items = [];

    // файлы на диске
    $files = [];
    foreach (scandir($ROOT . '/' . $folder) ?: [] as $f) {
        if ($f === '.' || $f === '..') continue;
        if (mb_substr($f, 0, 1, 'UTF-8') === '#') continue;     // скрытый файл
        if (is_file($ROOT . '/' . $folder . '/' . $f)) $files[] = $f;
    }
    sort($files, SORT_NATURAL | SORT_FLAG_CASE);

    foreach ($files as $fname) {
        $m    = $fMeta[$fname] ?? [];
        $tags = array_values(array_unique(array_merge($secTags, norm_tags($m['tags'] ?? []))));
        foreach ($tags as $t) $allTags[$t] = ($allTags[$t] ?? 0) + 1;

        $items[] = [
            'type'  => 'file',
            'key'   => $fname,
            'name'  => $m['name'] ?? $fname,
            'desc'  => $m['desc'] ?? '',
            'ext'   => strtolower(pathinfo($fname, PATHINFO_EXTENSION)),
            'size'  => humanSize(@filesize($ROOT . '/' . $folder . '/' . $fname)),
            'href'  => rawurlencode($folder) . '/' . rawurlencode($fname),
            'tags'  => $tags,
        ];
    }

    // ссылки из yaml (записи с url)
    foreach ($fMeta as $key => $m) {
        if (!is_array($m) || empty($m['url'])) continue;
        $tags = array_values(array_unique(array_merge($secTags, norm_tags($m['tags'] ?? []))));
        foreach ($tags as $t) $allTags[$t] = ($allTags[$t] ?? 0) + 1;

        $items[] = [
            'type' => 'link',
            'key'  => $key,
            'name' => $m['name'] ?? $key,
            'desc' => $m['desc'] ?? '',
            'href' => $m['url'],
            'tags' => $tags,
        ];
    }

    $sections[$folder] = [
        'name'   => $sm['name'] ?? $folder,
        'desc'   => $sm['desc'] ?? '',
        'bg'     => $sm['bg']   ?? '',
        'anchor' => slug($sm['name'] ?? $folder),
        'tags'   => $secTags,
        'items'  => $items,
    ];
}

// порядок: как в yaml, остальные по алфавиту
$ordered = [];
foreach (array_keys($sectionsMeta) as $k) {
    if (isset($sections[$k])) { $ordered[$k] = $sections[$k]; unset($sections[$k]); }
}
ksort($sections, SORT_NATURAL | SORT_FLAG_CASE);
$sections = $ordered + $sections;

ksort($allTags, SORT_NATURAL | SORT_FLAG_CASE);

// ---- фильтрация: поиск и тег -------------------------------------------
$query  = trim($_GET['q']   ?? '');
$tagSel = trim($_GET['tag'] ?? '');
$filtering = ($query !== '' || $tagSel !== '');

$matches = [];   // плоский список найденного
if ($filtering) {
    foreach ($sections as $folder => $sec) {
        foreach ($sec['items'] as $it) {

            if ($tagSel !== '' && !in_array($tagSel, $it['tags'], true)) continue;

            if ($query !== '') {
                $hay = implode(' ', [
                    $it['name'], $it['desc'], $it['key'],
                    implode(' ', $it['tags']),
                    $sec['name'], $sec['desc'],
                ]);
                if (mb_stripos($hay, $query, 0, 'UTF-8') === false) continue;
            }

            $it['section']  = $sec['name'];
            $it['anchor']   = $sec['anchor'];
            $matches[] = $it;
        }
    }
}

function highlight($text, $q) {
    $text = e($text);
    if ($q === '') return $text;
    return preg_replace('~(' . preg_quote(e($q), '~') . ')~iu', '<mark>$1</mark>', $text);
}

$ICON_SHIELD = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>';
$ICON_DL     = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>';
$ICON_EXT    = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>';
$ICON_LINK   = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>';
$ICON_SEARCH = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>';
$ICON_CLOSE  = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
?><!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= e($siteTitle) ?></title>
<style>
  :root{--ink:#12261f;--paper:#f6f4ee;--line:#d9d3c4;--seal:#1f5c46;
    --seal-soft:#eaf1ed;--muted:#5f6b62;--file:#8a5a2b;--tag-bg:#e6e2d6}
  *{box-sizing:border-box}
  html{scroll-behavior:smooth}
  body{margin:0;background:var(--paper);color:var(--ink);
    font-family:"Segoe UI",-apple-system,Roboto,Arial,sans-serif;
    line-height:1.5;-webkit-font-smoothing:antialiased}
  .shell{max-width:1080px;margin:0 auto;padding:48px 24px 80px;
    display:flex;gap:40px;align-items:flex-start}
  .main{flex:1;min-width:0}

  /* боковая навигация */
  .sidenav{flex:none;width:210px;position:sticky;top:24px;
    border-left:2px solid var(--line);padding-left:16px}
  .sidenav-title{font-size:11px;letter-spacing:.12em;text-transform:uppercase;
    color:var(--muted);font-weight:700;margin-bottom:10px}
  .sidenav a{display:block;font-size:13.5px;color:var(--muted);text-decoration:none;
    padding:5px 0;line-height:1.35;border-left:2px solid transparent;margin-left:-18px;
    padding-left:16px;transition:color .12s,border-color .12s}
  .sidenav a:hover{color:var(--seal)}
  .sidenav a.active{color:var(--seal);font-weight:600;border-left-color:var(--seal)}

  header{border-bottom:2px solid var(--ink);padding-bottom:20px;margin-bottom:18px}
  .badge{display:inline-flex;align-items:center;gap:8px;font-size:12px;
    letter-spacing:.14em;text-transform:uppercase;color:var(--seal);
    font-weight:600;margin-bottom:12px}
  .badge svg{width:15px;height:15px;flex:none}
  h1.site{font-family:Georgia,"Times New Roman",serif;font-weight:600;font-size:32px;
    line-height:1.15;margin:0 0 8px;letter-spacing:-.01em}
  h1.site a{color:inherit;text-decoration:none}
  .subtitle{color:var(--muted);font-size:15px;max-width:60ch;margin:0}

  /* поиск */
  .searchbar{display:flex;gap:10px;margin:20px 0 14px}
  .searchbar input{flex:1;padding:11px 14px;font-size:15px;border:1px solid var(--line);
    border-radius:9px;background:#fff;color:var(--ink);font-family:inherit}
  .searchbar input:focus{outline:2px solid var(--seal);outline-offset:-1px}
  .searchbar button{display:flex;align-items:center;gap:7px;padding:11px 18px;
    font-size:14px;font-weight:600;border:0;border-radius:9px;cursor:pointer;
    background:var(--seal);color:#fff;font-family:inherit}
  .searchbar button svg{width:16px;height:16px}
  .searchbar button:hover{background:#123c2d}

  /* облако тегов */
  .tagcloud{display:flex;flex-wrap:wrap;gap:7px;margin:0 0 6px}
  .tag{display:inline-flex;align-items:center;gap:5px;font-size:12.5px;
    padding:4px 10px;border-radius:20px;background:var(--tag-bg);color:var(--ink);
    text-decoration:none;border:1px solid transparent;transition:background .12s}
  .tag:hover{background:#dcd6c6}
  .tag .cnt{color:var(--muted);font-size:11.5px}
  .tag.active{background:var(--seal);color:#fff;border-color:var(--seal)}
  .tag.active .cnt{color:#cfe3d8}
  .tag svg{width:12px;height:12px}

  /* активный фильтр */
  .filterbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
    margin:14px 0 4px;color:var(--muted);font-size:14px}
  .clear{display:inline-flex;align-items:center;gap:5px;color:var(--seal);
    text-decoration:none;font-weight:600;font-size:13.5px}
  .clear svg{width:13px;height:13px}

  /* разделы */
  .section{margin-top:28px;padding:22px 24px;border-radius:12px;scroll-margin-top:20px}
  .section-head{display:flex;align-items:baseline;gap:12px;margin-bottom:4px}
  .section-num{font-family:Georgia,serif;font-size:13px;color:var(--seal);
    font-weight:700;min-width:26px}
  .section-name{font-family:Georgia,serif;font-size:21px;font-weight:600;margin:0}
  .section-name a{color:inherit;text-decoration:none}
  .section-name a:hover::after{content:' #';color:var(--line);font-size:16px}
  .section-desc{color:var(--muted);font-size:14.5px;margin:6px 0 14px 38px;max-width:64ch}

  .files{list-style:none;margin:0 0 0 38px;padding:0;border-top:1px solid var(--line)}
  .file{display:flex;align-items:center;gap:16px;padding:14px 4px;
    border-bottom:1px solid var(--line);text-decoration:none;color:inherit;
    transition:padding-left .15s,background .15s}
  .file:hover{background:var(--seal-soft);padding-left:12px}
  .file:focus-visible{outline:2px solid var(--seal);outline-offset:-2px}
  .ext{flex:none;width:46px;height:46px;border-radius:7px;background:#fff;
    border:1px solid var(--line);display:flex;align-items:center;justify-content:center;
    font-size:10px;font-weight:700;letter-spacing:.05em;color:var(--file);
    text-transform:uppercase;overflow:hidden}
  .ext svg{width:20px;height:20px;color:var(--seal)}
  .file-body{flex:1;min-width:0}
  .file-name{display:block;font-weight:600;font-size:15.5px}
  .file-desc{display:block;color:var(--muted);font-size:13.5px;margin-top:3px;max-width:56ch}
  .file-desc.empty{color:#b0a892;font-style:italic}
  .file-size{display:block;color:#a49b86;font-size:12px;margin-top:2px}
  .file-tags{display:flex;flex-wrap:wrap;gap:5px;margin-top:6px}
  .minitag{font-size:11.5px;padding:2px 8px;border-radius:20px;
    background:var(--tag-bg);color:var(--muted)}
  .inline-link{color:var(--seal);text-decoration:underline;text-underline-offset:2px}
  .dl{flex:none;display:flex;align-items:center;color:var(--seal)}
  .dl svg{width:16px;height:16px}
  .empty-sec{color:#b0a892;font-style:italic;font-size:13.5px;padding:12px 4px;margin-left:38px}

  /* результаты */
  .res-count{color:var(--muted);font-size:14px;margin:10px 0 2px}
  .res-list{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
  .res-sec{font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;
    color:var(--seal);font-weight:600;display:block;margin-bottom:2px}
  mark{background:#fdf0b8;color:inherit;padding:0 2px;border-radius:2px}

  footer{margin-top:56px;padding-top:20px;border-top:1px solid var(--line);
    color:#a49b86;font-size:12.5px;display:flex;justify-content:space-between;gap:8px}

  @media (max-width:860px){
    .sidenav{display:none}
    .shell{gap:0}
  }
  @media (max-width:560px){
    .shell{padding:32px 18px 60px}
    h1.site{font-size:26px}
    .section{padding:18px 16px}
    .section-desc,.files,.empty-sec{margin-left:0}
    .searchbar button span{display:none}
  }
  @media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{transition:none!important}}
</style>
</head>
<body>
<div class="shell">

  <div class="main">
    <header>
      <?php if ($badge): ?><div class="badge"><?= $ICON_SHIELD ?><?= e($badge) ?></div><?php endif; ?>
      <h1 class="site"><a href="?"><?= e($siteTitle) ?></a></h1>
      <?php if ($subtitle): ?><p class="subtitle"><?= e($subtitle) ?></p><?php endif; ?>
    </header>

    <form class="searchbar" method="get" action="">
      <input type="search" name="q" placeholder="Поиск по файлам, описаниям, тегам…"
             value="<?= e($query) ?>" autocomplete="off">
      <?php if ($tagSel !== ''): ?>
        <input type="hidden" name="tag" value="<?= e($tagSel) ?>">
      <?php endif; ?>
      <button type="submit"><?= $ICON_SEARCH ?><span>Найти</span></button>
    </form>

    <?php if ($allTags): ?>
    <div class="tagcloud">
      <?php foreach ($allTags as $t => $cnt): ?>
        <?php $isActive = ($t === $tagSel); ?>
        <a class="tag<?= $isActive ? ' active' : '' ?>"
           href="?<?= $isActive ? '' : 'tag=' . rawurlencode($t) ?>">
          <?= e($t) ?><span class="cnt"><?= $cnt ?></span>
          <?php if ($isActive) echo $ICON_CLOSE; ?>
        </a>
      <?php endforeach; ?>
    </div>
    <?php endif; ?>

<?php if ($filtering): ?>

    <div class="filterbar">
      <span>
        <?php if ($matches): ?>
          Найдено: <?= count($matches) ?>
        <?php else: ?>
          Ничего не найдено
        <?php endif; ?>
        <?php if ($query !== ''): ?> по запросу «<?= e($query) ?>»<?php endif; ?>
        <?php if ($tagSel !== ''): ?> с тегом «<?= e($tagSel) ?>»<?php endif; ?>
      </span>
      <a class="clear" href="?"><?= $ICON_CLOSE ?>Сбросить</a>
    </div>

    <ul class="res-list">
    <?php foreach ($matches as $it): ?>
      <?php $isLink = $it['type'] === 'link'; ?>
      <a class="file" href="<?= $isLink ? e($it['href']) : $it['href'] ?>"
         <?= $isLink ? 'target="_blank" rel="noopener"' : 'download' ?>>
        <span class="ext"><?= $isLink ? $ICON_LINK : e($it['ext']) ?></span>
        <span class="file-body">
          <span class="res-sec"><?= e($it['section']) ?></span>
          <span class="file-name"><?= highlight($it['name'], $query) ?></span>
          <?php if ($it['desc']): ?>
            <span class="file-desc"><?= highlight($it['desc'], $query) ?></span>
          <?php endif; ?>
          <?php if (!empty($it['size'])): ?>
            <span class="file-size"><?= e($it['size']) ?></span>
          <?php endif; ?>
          <?php if ($it['tags']): ?>
            <span class="file-tags">
              <?php foreach ($it['tags'] as $t): ?>
                <span class="minitag"><?= e($t) ?></span>
              <?php endforeach; ?>
            </span>
          <?php endif; ?>
        </span>
        <span class="dl"><?= $isLink ? $ICON_EXT : $ICON_DL ?></span>
      </a>
    <?php endforeach; ?>
    </ul>

<?php else: ?>

    <?php $i = 0; foreach ($sections as $folder => $sec): $i++;
          $bg = $sec['bg'] ? ' style="background:' . e($sec['bg']) . '"' : ''; ?>
      <section class="section" id="<?= e($sec['anchor']) ?>"<?= $bg ?>>
        <div class="section-head">
          <span class="section-num"><?= str_pad($i, 2, '0', STR_PAD_LEFT) ?></span>
          <h2 class="section-name">
            <a href="#<?= e($sec['anchor']) ?>"><?= e($sec['name']) ?></a>
          </h2>
        </div>
        <?php if ($sec['desc']): ?>
          <p class="section-desc"><?= md_inline($sec['desc']) ?></p>
        <?php endif; ?>

        <?php if ($sec['items']): ?>
        <ul class="files">
          <?php foreach ($sec['items'] as $it): $isLink = $it['type'] === 'link'; ?>
            <a class="file" href="<?= $isLink ? e($it['href']) : $it['href'] ?>"
               <?= $isLink ? 'target="_blank" rel="noopener"' : 'download' ?>>
              <span class="ext"><?= $isLink ? $ICON_LINK : e($it['ext']) ?></span>
              <span class="file-body">
                <span class="file-name"><?= e($it['name']) ?></span>
                <?php if ($it['desc']): ?>
                  <span class="file-desc"><?= md_inline($it['desc']) ?></span>
                <?php else: ?>
                  <span class="file-desc empty"><?= e($EMPTY_DESC) ?></span>
                <?php endif; ?>
                <?php if (!empty($it['size'])): ?>
                  <span class="file-size"><?= e($it['size']) ?></span>
                <?php endif; ?>
                <?php if ($it['tags']): ?>
                  <span class="file-tags">
                    <?php foreach ($it['tags'] as $t): ?>
                      <span class="minitag"><?= e($t) ?></span>
                    <?php endforeach; ?>
                  </span>
                <?php endif; ?>
              </span>
              <span class="dl"><?= $isLink ? $ICON_EXT : $ICON_DL ?></span>
            </a>
          <?php endforeach; ?>
        </ul>
        <?php else: ?>
          <div class="empty-sec">Раздел пуст.</div>
        <?php endif; ?>
      </section>
    <?php endforeach; ?>

    <?php if ($i === 0): ?>
      <p class="empty-sec">Разделы не найдены — создайте папки рядом с index.php.</p>
    <?php endif; ?>

<?php endif; ?>

    <footer>
      <span>sgbm.biz</span>
      <span><?= date('Y') ?></span>
    </footer>
  </div>

  <?php if (!$filtering && count($sections) > 1): ?>
  <nav class="sidenav">
    <div class="sidenav-title">Разделы</div>
    <?php foreach ($sections as $sec): ?>
      <a href="#<?= e($sec['anchor']) ?>" data-anchor="<?= e($sec['anchor']) ?>">
        <?= e($sec['name']) ?>
      </a>
    <?php endforeach; ?>
  </nav>
  <?php endif; ?>

</div>

<script>
// подсветка активного раздела в боковой навигации
(function(){
  const links = document.querySelectorAll('.sidenav a[data-anchor]');
  if (!links.length) return;
  const secs = [...links].map(a => document.getElementById(a.dataset.anchor)).filter(Boolean);

  const obs = new IntersectionObserver(entries => {
    entries.forEach(en => {
      if (!en.isIntersecting) return;
      links.forEach(a => a.classList.toggle('active', a.dataset.anchor === en.target.id));
    });
  }, { rootMargin: '-15% 0px -70% 0px' });

  secs.forEach(s => obs.observe(s));
})();
</script>
</body>
</html>
