#!/usr/bin/env node
/*
 * Генератор сайта «Ремонт квартиры».
 *
 * КАК ЭТО РАБОТАЕТ (чтобы было понятно):
 *   • Каждая заметка *.md в папке (кроме README.md и Планировки) = один раздел сайта.
 *   • Номер комнаты и площадь берутся из таблицы в «Планировка.md» по совпадению
 *     названия заметки с названием комнаты. Ничего в конфиге прописывать не нужно —
 *     добавил заметку → появился раздел.
 *   • Оформление раздела задаётся необязательным frontmatter в начале заметки:
 *         ---
 *         navShort: Кухня                 # короткая подпись в меню (для узких экранов)
 *         sub: кухня-студия с зоной отдыха # подпись под комнатой в списке планировки
 *         swatches: Дуб #C7A579, Графит #4B4E52   # кружки-образцы материалов
 *         ---
 *   • Тело заметки рендерится по правилам:
 *         - подряд идущие ![[картинка]] → галерея рендеров (1 / 2 / 3 / 4 в ряд);
 *         - таблица с колонкой «Фото» и без «Комментарий» → карточки (диваны, столы);
 *         - таблица с «Комментарий» → сравнительная таблица (техника), фото как миниатюры;
 *         - картинка сразу перед списком → блок «вариант отделки»;
 *         - обычные списки → нумерованные варианты.
 *   • Пустая заметка → раздел «в работе», комната в планировке помечается соответственно.
 *
 * Общие настройки (шапка, палитра, вводный текст) — в site.config.json.
 */
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const sharp = require('sharp');

const HERE = __dirname;
const ROOT = path.resolve(HERE, '..');            // папка «Ремонт квартира»
const IMG_SRC = path.join(ROOT, 'images');
const OUT = process.env.OUT || path.join(HERE, 'out');
const OUT_IMG = path.join(OUT, 'images');
const cfg = JSON.parse(fs.readFileSync(path.join(HERE, 'site.config.json'), 'utf8'));

// хэш содержимого ассета → ?v=… чтобы браузер сбрасывал кэш при изменении CSS/JS
function assetVer(rel) {
  const buf = fs.readFileSync(path.join(HERE, 'assets', rel));
  return crypto.createHash('md5').update(buf).digest('hex').slice(0, 8);
}
const CSS_VER = assetVer('style.css');
const JS_VER = assetVer('app.js');

// ─── утилиты ──────────────────────────────────────────────
const esc = (s) => String(s == null ? '' : s)
  .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');

function inline(t) {
  t = esc(t);
  t = t.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
  t = t.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
  t = t.replace(/&lt;br\s*\/?&gt;/gi, '<br>');   // разрешаем перенос строки <br> в тексте заметки
  return t;
}

const TR = { а:'a',б:'b',в:'v',г:'g',д:'d',е:'e',ё:'e',ж:'zh',з:'z',и:'i',й:'y',к:'k',л:'l',м:'m',н:'n',о:'o',п:'p',р:'r',с:'s',т:'t',у:'u',ф:'f',х:'h',ц:'ts',ч:'ch',ш:'sh',щ:'sch',ъ:'',ы:'y',ь:'',э:'e',ю:'yu',я:'ya' };
function slug(s) {
  return s.toLowerCase().split('').map((c) => (c in TR ? TR[c] : c)).join('')
    .replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'section';
}
const norm = (s) => String(s).toLowerCase().replace(/\s+/g, ' ').trim();

const usedImages = new Set();
function imgSrc(name) {
  name = String(name).trim();
  usedImages.add(name);
  return 'images/' + name.replace(/\.[a-z0-9]+$/i, '') + '.webp';
}
function parseWiki(line) {
  const m = line.trim().match(/^!\[\[([^\]|]+?)(?:\|[^\]]*)?\]\]$/);
  return m ? m[1].trim() : null;
}
function plural(n, one, few, many) {
  const m10 = n % 10, m100 = n % 100;
  if (m10 === 1 && m100 !== 11) return one;
  if (m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14)) return few;
  return many;
}
function domainLabel(url) {
  try { return new URL(url).hostname.replace(/^www\./, ''); } catch { return 'сайт'; }
}
function extractUrl(cell) {
  const md = cell.match(/\]\(([^)]+)\)/);
  if (md) return md[1].trim();
  const bare = cell.match(/https?:\/\/\S+/);
  return bare ? bare[0].trim() : '';
}

// ─── frontmatter ──────────────────────────────────────────
function parseFrontmatter(md) {
  const m = md.match(/^﻿?---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
  if (!m) return { data: {}, body: md };
  const data = {};
  m[1].split(/\r?\n/).forEach((line) => {
    const mm = line.match(/^([\wА-Яа-яЁё-]+):\s*(.*)$/);
    if (mm) data[mm[1].trim()] = mm[2].trim();
  });
  return { data, body: md.slice(m[0].length) };
}
function parseSwatches(str) {
  if (!str) return [];
  return str.split(',').map((s) => {
    const mm = s.trim().match(/^(.+?)\s+(#[0-9a-fA-F]{3,8})$/);
    return mm ? { name: mm[1].trim(), hex: mm[2] } : null;
  }).filter(Boolean);
}

// ─── парсер markdown в блоки ─────────────────────────────
function parseBlocks(md) {
  const lines = md.replace(/\r/g, '').split('\n');
  const blocks = [];
  let i = 0;
  const isTable = (l) => l.trim().startsWith('|');
  const isList = (l) => /^\s*(\d+\.|[-*])\s+/.test(l);
  const isQuote = (l) => /^\s*>/.test(l);
  const isHeading = (l) => /^#{1,6}\s/.test(l);

  while (i < lines.length) {
    const line = lines[i];
    if (!line.trim()) { i++; continue; }

    const h = line.match(/^(#{1,6})\s+(.*)$/);
    if (h) { blocks.push({ t: 'h', lvl: h[1].length, text: h[2].trim() }); i++; continue; }

    if (isTable(line)) {
      const rows = [];
      while (i < lines.length && isTable(lines[i])) { rows.push(lines[i]); i++; }
      blocks.push(parseTable(rows)); continue;
    }

    if (parseWiki(line)) {
      const imgs = [];
      while (i < lines.length) {
        if (!lines[i].trim()) { i++; continue; }
        const w = parseWiki(lines[i]);
        if (w) { imgs.push(w); i++; } else break;
      }
      blocks.push({ t: 'images', imgs }); continue;
    }

    if (isList(line)) {
      const items = [];
      while (i < lines.length && isList(lines[i])) {
        items.push(lines[i].replace(/^\s*(\d+\.|[-*])\s+/, '').trim()); i++;
      }
      blocks.push({ t: 'list', items }); continue;
    }

    if (isQuote(line)) {
      const q = [];
      while (i < lines.length && isQuote(lines[i])) { q.push(lines[i].replace(/^\s*>\s?/, '')); i++; }
      blocks.push({ t: 'quote', text: q.join(' ') }); continue;
    }

    const para = [];
    while (i < lines.length && lines[i].trim() && !isHeading(lines[i]) &&
           !isTable(lines[i]) && !isList(lines[i]) && !isQuote(lines[i]) && !parseWiki(lines[i])) {
      para.push(lines[i].trim()); i++;
    }
    blocks.push({ t: 'p', text: para.join(' ') });
  }
  return blocks;
}

function parseTable(rows) {
  const cells = (r) => r.trim().replace(/^\|/, '').replace(/\|$/, '')
    .split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, '|').trim());
  const headers = cells(rows[0]);
  const body = rows.slice(2).map(cells).filter((r) => r.some((c) => c !== ''));
  return { t: 'table', headers, rows: body };
}

// ─── типы таблиц ─────────────────────────────────────────
function tableIsPriced(tbl) { return tbl.headers.some((h) => /цена|price|₽|руб/i.test(h)); }
function tableHasPhoto(tbl) {
  return tbl.headers.some((h) => /фото|photo|картин|image/i.test(h))
    || tbl.rows.some((r) => r.some((c) => parseWiki(c)));
}
function tableHasComment(tbl) { return tbl.headers.some((h) => /коммент|comment|примеч|описан/i.test(h)); }
function tableIsCards(tbl) { return tableIsPriced(tbl) && tableHasPhoto(tbl) && !tableHasComment(tbl); }
function colIndex(headers, re) { return headers.findIndex((h) => re.test(h)); }

// ─── рендер блоков ───────────────────────────────────────
function renderCards(tbl) {
  const H = tbl.headers;
  const iNo = colIndex(H, /^№|^no$|^#$/i);
  const iPrice = colIndex(H, /цена|price|₽|руб/i);
  const iLink = colIndex(H, /ссылк|link|url/i);
  const iPhoto = colIndex(H, /фото|photo|картин|image/i);
  const iCat = colIndex(H, /^(тип|категор|группа|техника|раздел)$/i);
  const iName = H.findIndex((h, idx) => idx !== iNo && idx !== iPrice && idx !== iLink && idx !== iPhoto && idx !== iCat && h);

  // одна карточка. no — подпись «Вариант N» (в пределах группы)
  const card = (r, no) => {
    const nameRaw = iName >= 0 ? (r[iName] || '') : '';
    // «Модель<br>описание» → жирная модель + приглушённое описание
    const [head, ...rest] = nameRaw.split(/<br\s*\/?>/i);
    const desc = rest.join('<br>').trim();
    const priceRaw = iPrice >= 0 ? r[iPrice] : '';
    const priceNum = (priceRaw.match(/[\d\s .]+/) || [''])[0].trim();
    const unit = /₽|руб/i.test(priceRaw) ? '₽' : '';
    const photo = iPhoto >= 0 ? parseWiki(r[iPhoto]) : null;
    const url = iLink >= 0 ? extractUrl(r[iLink]) : '';
    const media = photo
      ? `<div class="opt__media" data-zoom-wrap><img src="${imgSrc(photo)}" alt="${esc(head)}" data-zoom loading="lazy"></div>` : '';
    const price = priceNum
      ? `<span class="opt__price">${esc(priceNum)}${unit ? ` <span>${unit}</span>` : ''}</span>` : '';
    const link = url
      ? `<a class="opt__link" href="${esc(url)}" target="_blank" rel="noopener">Открыть на ${esc(domainLabel(url))} <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 17 17 7M8 7h9v9"/></svg></a>` : '';
    return `<article class="opt reveal">${media}<div class="opt__body">
      <span class="opt__no">Вариант ${esc(no)}</span>
      <span class="opt__name">${inline(head)}</span>
      ${desc ? `<span class="opt__desc">${inline(desc)}</span>` : ''}
      ${price}${link}</div></article>`;
  };

  // без колонки типа — простая сетка карточек
  if (iCat < 0) {
    const cards = tbl.rows.map((r, idx) => card(r, iNo >= 0 && r[iNo] ? r[iNo] : idx + 1)).join('\n');
    return `<div class="options">${cards}</div>`;
  }

  // с колонкой типа — группируем по типу (пустая ячейка наследует тип сверху)
  const groups = [];
  let cur = null, lastCat = '';
  tbl.rows.forEach((r) => {
    const cat = (r[iCat] && r[iCat].trim()) || lastCat;
    lastCat = cat;
    if (!cur || cur.cat !== cat) { cur = { cat, rows: [] }; groups.push(cur); }
    cur.rows.push(r);
  });
  return groups.map((g) => {
    const cards = g.rows.map((r, i) => card(r, i + 1)).join('\n');
    return `<div class="opt-group reveal">
      <h4 class="opt-group__title">${inline(g.cat)}<span>${g.rows.length} ${plural(g.rows.length, 'вариант', 'варианта', 'вариантов')}</span></h4>
      <div class="options">${cards}</div></div>`;
  }).join('\n');
}

function tableCell(c) {
  const w = parseWiki(c);
  if (w) return `<img src="${imgSrc(w)}" alt="" loading="lazy">`;
  if (/^https?:\/\/\S+$/.test(c)) return `<a href="${esc(c)}" target="_blank" rel="noopener" class="tlink">Открыть <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 17 17 7M8 7h9v9"/></svg></a>`;
  return inline(c);
}
function renderTable(tbl) {
  const head = tbl.headers.map((h) => `<th>${inline(h)}</th>`).join('');
  const body = tbl.rows.map((r) => `<tr>${r.map((c) => `<td>${tableCell(c)}</td>`).join('')}</tr>`).join('\n');
  return `<div class="table-wrap reveal"><table><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table></div>`;
}

function renderImages(imgs) {
  if (imgs.length === 1) {
    return `<div class="renders renders--hero reveal"><div class="render" data-zoom-wrap><img src="${imgSrc(imgs[0])}" alt="" data-zoom loading="lazy"></div></div>`;
  }
  const cls = imgs.length === 2 ? 'renders--duo' : imgs.length === 4 ? 'renders--quad' : 'renders--trio';
  const cells = imgs.map((im) => `<div class="render" data-zoom-wrap><img src="${imgSrc(im)}" alt="" data-zoom loading="lazy"></div>`).join('');
  return `<div class="renders ${cls} reveal">${cells}</div>`;
}

function variantItems(items) {
  return items.map((it) => {
    const parts = it.split(/\s+[—–-]\s+/);
    const b = parts.shift();
    const small = parts.join(' — ');
    return `<li><span><b>${inline(b)}</b>${small ? `<small>${inline(small)}</small>` : ''}</span></li>`;
  }).join('\n');
}
function renderVariants(items) { return `<ol class="variants reveal">${variantItems(items)}</ol>`; }
function renderFinish(img, items) {
  return `<div class="finish reveal">
    <div class="finish__img" data-zoom-wrap><img src="${imgSrc(img)}" alt="" data-zoom loading="lazy"></div>
    <ol class="variants">${variantItems(items)}</ol></div>`;
}
function subhead(text, count) {
  const c = count != null
    ? `<span class="count">${count} ${plural(count, 'вариант', 'варианта', 'вариантов')}</span>` : '';
  return `<div class="subhead reveal"><h3>${inline(text)}</h3>${c}</div>`;
}

// тело раздела (список блоков → html)
function renderBody(body) {
  const out = [];
  for (let i = 0; i < body.length; i++) {
    const b = body[i];
    const next = body[i + 1];
    if (b.t === 'h') {
      let count = null;
      if (next) {
        if (next.t === 'table' && tableIsCards(next)) count = next.rows.length;
        else if (next.t === 'list') count = next.items.length;
        else if (next.t === 'images' && body[i + 2] && body[i + 2].t === 'list') count = body[i + 2].items.length;
      }
      out.push(subhead(b.text, count));
    } else if (b.t === 'images') {
      if (next && next.t === 'list' && b.imgs.length === 1) { out.push(renderFinish(b.imgs[0], next.items)); i++; }
      else out.push(renderImages(b.imgs));
    } else if (b.t === 'table') {
      out.push(tableIsCards(b) ? renderCards(b) : renderTable(b));
    } else if (b.t === 'list') {
      out.push(renderVariants(b.items));
    } else if (b.t === 'quote') {
      out.push(`<p class="note reveal">${inline(b.text)}</p>`);
    } else if (b.t === 'p') {
      out.push(`<p class="prose reveal">${inline(b.text)}</p>`);
    }
  }
  return out.join('\n    ');
}

// ─── планировка: разбор таблицы комнат ───────────────────
function parsePlanRooms() {
  const md = fs.readFileSync(path.join(ROOT, cfg.plan.file), 'utf8');
  const tbl = parseFrontmatter(md).body;
  const t = parseBlocks(tbl).find((b) => b.t === 'table');
  if (!t) return [];
  const iNo = colIndex(t.headers, /^№|^no$|^#$/i);
  const iArea = colIndex(t.headers, /площад|м²|area/i);
  const iName = t.headers.findIndex((h, idx) => idx !== iNo && idx !== iArea && h);
  const iSub = t.headers.findIndex((h, idx) => idx !== iNo && idx !== iArea && idx !== iName && h);
  return t.rows.map((r) => ({
    no: r[iNo] || '', name: r[iName] || '', area: r[iArea] || '', sub: iSub >= 0 ? r[iSub] : '',
  }));
}

// ─── сбор раздела из заметки ─────────────────────────────
function buildSection(file, rooms) {
  const md = fs.readFileSync(path.join(ROOT, file), 'utf8');
  const { data, body } = parseFrontmatter(md);
  const blocks = parseBlocks(body);

  const h1 = blocks.find((b) => b.t === 'h' && b.lvl === 1);
  const title = data.title || (h1 ? h1.text : file.replace(/\.md$/i, ''));
  const room = rooms.find((r) => norm(r.name) === norm(title));
  const order = data.order != null ? Number(data.order)
    : (room ? rooms.indexOf(room) : 100 + file.charCodeAt(0));

  const leadBlock = blocks.find((b) => b.t === 'p');
  const contentBlocks = blocks.filter((b) => b !== h1 && b !== leadBlock);
  const hasContent = contentBlocks.some((b) => b.t === 'images' || b.t === 'table' || b.t === 'list');

  return {
    file, title, room, order, hasContent,
    id: slug(title),
    nav: data.nav || title,
    navShort: data.navShort || data.nav || title,
    sub: data.sub || (room ? room.sub : ''),
    swatches: parseSwatches(data.swatches),
    eyebrow: data.eyebrow || (room ? `Комната ${room.no} · ${room.area}` : ''),
    lead: leadBlock ? leadBlock.text : '',
    contentBlocks,
  };
}

function renderSection(sec) {
  const sw = sec.swatches.map((s) => `<span class="swatch"><i style="background:${esc(s.hex)}"></i>${esc(s.name)}</span>`).join('');
  const bodyHtml = sec.hasContent
    ? renderBody(sec.contentBlocks)
    : `<p class="section-empty reveal">Раздел в работе — скоро добавим рендеры и варианты мебели.</p>`;
  return `<section class="section" id="${esc(sec.id)}">
  <div class="wrap">
    <div class="section__head reveal">
      <div>
        <span class="eyebrow">${esc(sec.eyebrow)}</span>
        <h2>${esc(sec.title)}</h2>
        ${sec.lead ? `<p class="section__lead">${inline(sec.lead)}</p>` : ''}
        ${sw ? `<div class="swatches">${sw}</div>` : ''}
      </div>
      <div class="section__index" aria-hidden="true">${esc(sec.index)}</div>
    </div>
    ${bodyHtml}
  </div>
</section>`;
}

function renderPlan(rooms, sections) {
  const byRoom = new Map();
  sections.forEach((s) => { if (s.room) byRoom.set(s.room.no, s); });
  const items = rooms.map((r) => {
    const sec = byRoom.get(r.no);
    const nameHtml = `<span class="room-name">${esc(r.name)}${r.sub || (sec && sec.sub) ? `<small>${esc(r.sub || sec.sub)}</small>` : ''}</span>`;
    const nameCell = sec ? `<a class="rooms-link" href="#${esc(sec.id)}">${nameHtml}</a>` : nameHtml;
    const tag = sec ? (sec.hasContent
      ? '<span class="tag tag--done">концепт готов</span>'
      : '<span class="tag tag--wip">в работе</span>') : '';
    return `<li><span class="room-no">${esc(r.no)}</span>${nameCell}${tag}<span class="room-area">${esc(r.area)}</span></li>`;
  }).join('\n');
  return `<section class="section" id="${esc(cfg.plan.id)}">
  <div class="wrap">
    <div class="section__head reveal">
      <div>
        <span class="eyebrow">${esc(cfg.plan.eyebrow)}</span>
        <h2>${esc(cfg.plan.nav)}</h2>
        <p class="section__lead">${esc(cfg.plan.lead)}</p>
      </div>
      <div class="section__index" aria-hidden="true">01</div>
    </div>
    <div class="plan-layout">
      <figure class="plan-figure reveal"><img src="${imgSrc(cfg.hero.planImage)}" alt="Схема планировки" data-zoom></figure>
      <ul class="rooms reveal">${items}</ul>
    </div>
  </div>
</section>`;
}

// ─── страница целиком ─────────────────────────────────────
function buildHtml(rooms, sections) {
  const h = cfg.hero;
  const doneCount = sections.filter((s) => s.hasContent).length;
  const nav = [{ id: cfg.plan.id, nav: cfg.plan.nav, navShort: cfg.plan.navShort }, ...sections]
    .map((s) => `<a href="#${s.id}"><span class="lg">${esc(s.nav)}</span><span class="sm">${esc(s.navShort || s.nav)}</span></a>`).join('\n      ');
  const materials = h.materials.map((m) => `<span class="swatch"><i style="background:${esc(m.hex)}"></i>${esc(m.name)}</span>`).join('\n          ');
  const stats = h.stats.map((s) => {
    const num = /концепт/i.test(s.lbl) ? String(doneCount) : s.num;
    return `<div><span class="num">${esc(num)}</span><span class="lbl">${esc(s.lbl)}</span></div>`;
  }).join('\n          ');
  const sectionsHtml = sections.map(renderSection).join('\n\n');

  return `<!DOCTYPE html>
<html lang="ru" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${esc(cfg.title)}</title>
<meta name="description" content="${esc(cfg.description)}">
<meta name="theme-color" content="#EDE8DE">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Onest:wght@400;500;600&display=swap&subset=cyrillic,cyrillic-ext,latin" rel="stylesheet">
<link rel="stylesheet" href="css/style.css?v=${CSS_VER}">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='20' fill='%237C8A66'/><path d='M25 70V42l25-18 25 18v28H56V52H44v18z' fill='%23F7F3EA'/></svg>">
</head>
<body>

<header class="topbar" id="topbar">
  <div class="topbar__inner">
    <div class="brand"><b>${esc(cfg.brand)}</b> <span>${esc(cfg.brandMeta)}</span></div>
    <nav class="nav" id="nav">
      ${nav}
    </nav>
    <button class="theme-toggle" id="themeBtn" aria-label="Сменить тему">
      <svg class="moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"/></svg>
      <svg class="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="12" cy="12" r="4.2"/><path d="M12 2v2.5M12 19.5V22M4.9 4.9l1.8 1.8M17.3 17.3l1.8 1.8M2 12h2.5M19.5 12H22M4.9 19.1l1.8-1.8M17.3 6.7l1.8-1.8"/></svg>
    </button>
  </div>
</header>

<section class="hero">
  <div class="wrap">
    <div class="hero__grid">
      <div class="reveal">
        <span class="eyebrow">${esc(h.eyebrow)}</span>
        <h1 class="hero__title">${h.title}</h1>
        <p class="hero__lead">${esc(h.lead)}</p>
        <div class="swatches" aria-label="Материалы квартиры">
          ${materials}
        </div>
        <div class="hero__meta">
          ${stats}
        </div>
      </div>
      <div class="hero__plan reveal">
        <span class="cap">Планировка</span>
        <img src="${imgSrc(h.planImage)}" alt="Планировка квартиры" data-zoom>
      </div>
    </div>
  </div>
</section>

${renderPlan(rooms, sections)}

${sectionsHtml}

<footer class="footer">
  <div class="wrap footer__inner">
    <span><b>${esc(cfg.brand)}</b> · концепт ремонта 2026</span>
    <span>Собрано из заметок Obsidian · 60 м²</span>
  </div>
</footer>

<div class="lightbox" id="lightbox" aria-hidden="true">
  <button class="lightbox__close" id="lbClose" aria-label="Закрыть">×</button>
  <img id="lbImg" src="" alt="">
</div>

<script src="js/app.js?v=${JS_VER}"></script>
</body>
</html>`;
}

// ─── оптимизация картинок ─────────────────────────────────
async function optimizeImages() {
  fs.mkdirSync(OUT_IMG, { recursive: true });
  for (const name of usedImages) {
    const src = path.join(IMG_SRC, name);
    const dst = path.join(OUT_IMG, name.replace(/\.[a-z0-9]+$/i, '') + '.webp');
    if (!fs.existsSync(src)) { console.warn('⚠ нет картинки:', name); continue; }
    await sharp(src).resize({ width: 1600, withoutEnlargement: true }).webp({ quality: 82 }).toFile(dst);
  }
}

// ─── main ─────────────────────────────────────────────────
(async () => {
  fs.rmSync(OUT, { recursive: true, force: true });
  fs.mkdirSync(path.join(OUT, 'css'), { recursive: true });
  fs.mkdirSync(path.join(OUT, 'js'), { recursive: true });

  const rooms = parsePlanRooms();
  const skip = new Set(['README.md', cfg.plan.file]);
  const files = fs.readdirSync(ROOT).filter((f) => /\.md$/i.test(f) && !skip.has(f));
  const sections = files.map((f) => buildSection(f, rooms)).sort((a, b) => a.order - b.order);
  sections.forEach((s, i) => { s.index = String(i + 2).padStart(2, '0'); });

  const html = buildHtml(rooms, sections);      // наполняет usedImages
  fs.writeFileSync(path.join(OUT, 'index.html'), html);
  fs.copyFileSync(path.join(HERE, 'assets/style.css'), path.join(OUT, 'css/style.css'));
  fs.copyFileSync(path.join(HERE, 'assets/app.js'), path.join(OUT, 'js/app.js'));
  await optimizeImages();

  console.log(`✅ Сайт собран: ${OUT}`);
  console.log(`   разделов: ${sections.length} (${sections.map((s) => s.title + (s.hasContent ? '' : '·в работе')).join(', ')})`);
  console.log(`   картинок: ${usedImages.size}`);
})().catch((e) => { console.error('❌', e); process.exit(1); });
