import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
const sourcePath = join(repoRoot, "index.html");
const templateRoot = join(repoRoot, "templates", "pages", "home");
const blockRoot = join(templateRoot, "blocks");
const contentPath = join(repoRoot, "content", "pages", "home.json");

let html = await readFile(sourcePath, "utf8");
html = html.replace(/\n*<script id="node-dc-static-config"(?: type="application\/json")?>[\s\S]*?<\/script>\n*/g, "\n");

const marker = {
  hero: '<header id="hero" class="s h-screen">',
  features: '<section id="features" class="s h-screen py mobile-flip">',
  cViz: '<div class="c-viz">',
  pricingIntro: '<section id="waitlist" class="s">',
  waitlistFiles: '<section id="waitlist" class="s h-screen">',
  demoVideoFolder: '<div data-module="folder-wrap" class="folder-w video-w">',
  footer: '<footer class="s footer">',
};

function findRequired(needle, from = 0) {
  const index = html.indexOf(needle, from);
  if (index === -1) {
    throw new Error(`Could not find marker: ${needle}`);
  }
  return index;
}

const heroStart = findRequired(marker.hero);
const featuresStart = findRequired(marker.features, heroStart);
const cVizStart = findRequired(marker.cViz, featuresStart);
const cVizOpenEnd = cVizStart + marker.cViz.length;
const pricingIntroStart = findRequired(marker.pricingIntro, cVizOpenEnd);
const waitlistFilesStart = findRequired(marker.waitlistFiles, pricingIntroStart);
const demoVideoFolderStart = findRequired(marker.demoVideoFolder, waitlistFilesStart);
const footerStart = findRequired(marker.footer, demoVideoFolderStart);
const footerEnd = findRequired("</footer>", footerStart) + "</footer>".length;
const productSystemHtml = html.slice(cVizOpenEnd, pricingIntroStart);
const productSectionStarts = [...productSystemHtml.matchAll(/<section\b/g)].map((match) => match.index);

if (productSectionStarts.length < 5) {
  throw new Error(`Expected at least 5 product-system sections, found ${productSectionStarts.length}`);
}

const productSections = productSectionStarts.map((start, index) => {
  const end = productSectionStarts[index + 1] ?? productSystemHtml.length;
  return productSystemHtml.slice(start, end);
});

let shell = [
  html.slice(0, heroStart),
  "{{NODEDC_BLOCKS_BEFORE_CVIZ}}",
  html.slice(cVizStart, cVizOpenEnd),
  "{{NODEDC_BLOCKS_CVIZ}}",
  html.slice(footerEnd),
].join("");

const blocks = [
  {
    region: "beforeCViz",
    id: "hero-waitlist",
    type: "staticHtml",
    template: "hero-waitlist.html",
    adminLabel: "Первый экран и заявка",
    anchor: "hero",
    html: html.slice(heroStart, featuresStart),
  },
  {
    region: "beforeCViz",
    id: "feature-video",
    type: "staticHtml",
    template: "feature-video.html",
    adminLabel: "Демо и базовая логика",
    anchor: "features",
    html: html.slice(featuresStart, cVizStart),
  },
  {
    region: "cViz",
    id: "component-library",
    type: "staticHtml",
    template: "component-library.html",
    adminLabel: "Компонентная библиотека и MCP",
    anchor: null,
    html: productSections[0],
  },
  {
    region: "cViz",
    id: "ai-modes",
    type: "staticHtml",
    template: "ai-modes.html",
    adminLabel: "Гибкие режимы ИИ",
    anchor: null,
    html: productSections[1],
  },
  {
    region: "cViz",
    id: "faq",
    type: "staticHtml",
    template: "faq.html",
    adminLabel: "Частые вопросы и ответы",
    anchor: null,
    html: productSections[2],
  },
  {
    region: "cViz",
    id: "publish-cta",
    type: "staticHtml",
    template: "publish-cta.html",
    adminLabel: "Финальный CTA перед тарифами",
    anchor: "hero",
    html: productSections[3],
  },
  {
    region: "cViz",
    id: "pricing-table",
    type: "staticHtml",
    template: "pricing-table.html",
    adminLabel: "Тарифная таблица",
    anchor: "pricing-wrap",
    html: productSections[4],
  },
  {
    region: "cViz",
    id: "pricing-intro",
    type: "staticHtml",
    template: "pricing-intro.html",
    adminLabel: "Интро тарифов",
    anchor: "waitlist",
    html: html.slice(pricingIntroStart, waitlistFilesStart),
  },
  {
    region: "cViz",
    id: "waitlist-files",
    type: "staticHtml",
    template: "waitlist-files.html",
    adminLabel: "Форма ожидания и txt-файлы",
    anchor: "waitlist",
    html: html.slice(waitlistFilesStart, demoVideoFolderStart),
  },
  {
    region: "cViz",
    id: "demo-video-folder",
    type: "staticHtml",
    template: "demo-video-folder.html",
    adminLabel: "Иконка демо-видео в доке",
    anchor: null,
    html: html.slice(demoVideoFolderStart, footerStart),
  },
  {
    region: "cViz",
    id: "footer",
    type: "staticHtml",
    template: "footer.html",
    adminLabel: "Футер",
    anchor: null,
    html: html.slice(footerStart, footerEnd),
  },
];

function getByPath(source, path) {
  return path.split(".").reduce((value, key) => value?.[key], source);
}

function inferFieldGroup(path) {
  if (path.startsWith("content.navigation.")) return "Шапка";
  if (path.startsWith("content.notifications.")) return "Уведомления";
  if (path.startsWith("content.dock.")) return "Dock";
  if (path.startsWith("content.assets.")) return "Модель и логотипы";
  if (path.startsWith("content.cards.")) return "Тезисы первого экрана";
  if (path.startsWith("content.app.")) return "Карточка раннего доступа";
  if (path.startsWith("content.form.")) return "Форма";
  if (path === "content.legalHtml") return "Юридический блок";
  if (path === "content.windowTitle" || path === "content.videoSrc") return "Видео-окно";
  if (path.startsWith("content.features.")) return "Пункты";
  if (path.startsWith("content.sliderItems.")) return "Слайдер";
  if (path.startsWith("content.oneClick.")) return "Один клик";
  if (path.startsWith("content.mcp.")) return "MCP";
  if (path.startsWith("content.faq.")) return "FAQ";
  if (path.startsWith("content.cta.")) return "CTA";
  if (path.startsWith("content.plans.") || path.startsWith("content.pricing.")) return "Тарифы";
  if (path.startsWith("content.heading.") || path === "content.introHtml") return "Заголовок";
  return "Контент";
}

function field(path, label, kind = "text", renderAs = kind === "html" ? "html" : "text", group = null, options = {}) {
  return { path, label, kind, renderAs, group: group || inferFieldGroup(path), ...options };
}

function tokenizeBlock(block, definition) {
  let html = block.html;

  for (const editableField of definition.editableFields) {
    const value = getByPath({ content: definition.content }, editableField.path);
    const token = `{{${editableField.renderAs}:${editableField.path}}}`;

    if (typeof value !== "string") {
      throw new Error(`Token field must be a string: ${block.id}.${editableField.path}`);
    }

    if (!html.includes(value)) {
      throw new Error(`Could not find "${value}" while tokenizing ${block.id}`);
    }

    html = editableField.replaceAll ? html.split(value).join(token) : html.replace(value, token);
  }

  return {
    ...block,
    type: definition.type,
    html,
    content: definition.content,
    editableFields: definition.editableFields,
  };
}

const demoSliderItems = [
  { videoSrc: "./assets/media/various-modes.mp4", title: "Переключение режимов ИИ" },
  { videoSrc: "./assets/media/project-flow.mp4", title: "Создание нового проекта" },
  { videoSrc: "./assets/media/ai-dock-mode.mp4", title: "ИИ-док" },
  { videoSrc: "./assets/media/account.mp4", title: "Аккаунт" },
  { videoSrc: "./assets/media/code-edits.mp4", title: "Редактирование кода с ИИ" },
  { videoSrc: "./assets/media/dev-server.mp4", title: "Dev-сервер с ИИ" },
  { videoSrc: "./assets/media/webflow-mcp.mp4", title: "Webflow MCP" },
  { videoSrc: "./assets/media/publish-flow.mp4", title: "Публикация" },
];

function sliderEditableFields(prefix = "content.sliderItems", labelPrefix = "Слайд") {
  return demoSliderItems.flatMap((_, index) => [
    field(`${prefix}.${index}.videoSrc`, `${labelPrefix} ${index + 1}: видео`, "media", "attr"),
    field(`${prefix}.${index}.title`, `${labelPrefix} ${index + 1}: подпись`),
  ]);
}

const typedBlockDefinitions = {
  "hero-waitlist": {
    type: "heroWaitlist",
    content: {
      heading: {
        muted: "Ваш Webflow ",
        main: "ИИ-инструменты",
      },
      introHtml:
        'Несколько агентов для проекта Webflow: от <span class="darker-70">Designer</span> до <span class="darker-70">кастомного кода.</span>',
      cards: [
        {
          muted: "Полный",
          main: "агентный контроль",
          bodyHtml:
            'Интеграция с <span class="darker-70">Webflow MCP,</span> мультиагентный режим позволяет работать с <span class="darker-70">несколькими экземплярами</span> параллельно.',
        },
        {
          muted: "Компонентная",
          main: "библиотека",
          bodyHtml:
            'Собирайте с учетом <span class="darker-70">ИИ-агентов</span> разные компонентные заготовки дают основу, а стиль и сложную функциональность можно дорабатывать промптами.',
        },
        {
          muted: "Деплой ",
          main: "пайплайн",
          bodyHtml:
            '<span class="darker-70">Деплой в один клик,</span> непрерывный пайплайн, мгновенный откат и быстрая <span class="darker-70">доставка через CDN.</span>',
        },
      ],
      app: {
        iconAlt: "Иконка NODE.DC",
        logoAlt: "Логотип NODE.DC",
        release: "Бета-релиз: июль 2026",
        heading: "Ранний доступ",
        description: "Кастомный код, MCP и ИИ-агенты для Webflow в одном рабочем контуре.",
      },
      form: {
        messagePlaceholder: "Сообщение (необязательно)",
        namePlaceholder: "Ваше имя*",
        emailPlaceholder: "Email для регистрации*",
        submitLabel: "Встать в лист ожидания",
        successHeading: "Заявка принята. Спасибо.",
        successTextHtml: "Мы скоро свяжемся. <br/>",
        betaCtaLabel: "Подать заявку в бету?",
        founderIntroHtml: "Или оформить ранний Founder-доступ<br/>",
        founderCtaHtml: 'Оформить Founder-доступ <span class="darker-70">350€</span>',
        spotsLabel: "До 200 мест",
        failureHeading: "Не получилось",
        failureTextHtml: "Что-то пошло не так. <br/>Попробуйте ещё раз чуть позже.<br/>",
      },
      legalHtml:
        '<a href="https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing" target="_blank">Политика конфиденциальности</a> • <a href="https://docs.google.com/document/d/1vME2w8ZB88HoH-JyvkkiL7exrcMfWhkERfFRlnIeZFA/edit?usp=sharing" target="_blank">Политика cookie</a><br/>© — Все права защищены<br/>2026',
    },
    editableFields: [
      field("content.heading.muted", "Hero: приглушённая строка"),
      field("content.heading.main", "Hero: основная строка"),
      field("content.introHtml", "Hero: вводный текст", "html"),
      field("content.cards.0.muted", "Карточка 1: приглушённая строка"),
      field("content.cards.0.main", "Карточка 1: основная строка"),
      field("content.cards.0.bodyHtml", "Карточка 1: текст", "html"),
      field("content.cards.1.muted", "Карточка 2: приглушённая строка"),
      field("content.cards.1.main", "Карточка 2: основная строка"),
      field("content.cards.1.bodyHtml", "Карточка 2: текст", "html"),
      field("content.cards.2.muted", "Карточка 3: приглушённая строка"),
      field("content.cards.2.main", "Карточка 3: основная строка"),
      field("content.cards.2.bodyHtml", "Карточка 3: текст", "html"),
      field("content.app.iconAlt", "App: alt иконки", "text", "attr"),
      field("content.app.logoAlt", "App: alt логотипа", "text", "attr"),
      field("content.app.release", "App: релиз"),
      field("content.app.heading", "App: заголовок"),
      field("content.app.description", "App: описание"),
      field("content.form.messagePlaceholder", "Форма: placeholder сообщения", "text", "attr"),
      field("content.form.namePlaceholder", "Форма: placeholder имени", "text", "attr"),
      field("content.form.emailPlaceholder", "Форма: placeholder email", "text", "attr"),
      field("content.form.submitLabel", "Форма: кнопка"),
      field("content.form.successHeading", "Форма: успех, заголовок"),
      field("content.form.successTextHtml", "Форма: успех, текст", "html"),
      field("content.form.betaCtaLabel", "Форма: CTA беты"),
      field("content.form.founderIntroHtml", "Форма: Founder intro", "html"),
      field("content.form.founderCtaHtml", "Форма: Founder CTA", "html"),
      field("content.form.spotsLabel", "Форма: лимит мест"),
      field("content.form.failureHeading", "Форма: ошибка, заголовок"),
      field("content.form.failureTextHtml", "Форма: ошибка, текст", "html"),
      field("content.legalHtml", "Юридический текст", "html"),
    ],
  },
  "feature-video": {
    type: "featureVideo",
    content: {
      windowTitle: "демо-видео.mp4",
      videoSrc: "./assets/media/publish-flow.mp4",
      heading: {
        muted: "Всё в одном",
        main: "приложение.",
      },
      introHtml:
        'Универсальный инструмент для <span class="darker-70">ИИ-агентов</span> в вашем Webflow-проекте.',
      features: [
        {
          eyebrow: "Контекст проекта",
          bodyHtml:
            'Подключитесь к <span class="darker-70">MCP</span> чтобы агенты получили полный контекст вашей сборки.',
        },
        {
          eyebrow: "Группы агентов",
          bodyHtml:
            'Используйте <span class="darker-70">несколько агентов параллельно</span> чтобы добавлять кастомный код, анимации и функциональность.',
        },
        {
          eyebrow: "Среда разработки",
          bodyHtml:
            'Используйте быстрые <span class="darker-70">Bun</span> сборки и автообновление для вашего <span class="darker-70">кастомного кода.</span>',
        },
        {
          eyebrow: "Деплой в один клик",
          bodyHtml:
            '<span class="darker-70">Нажмите и публикуйте</span> кастомный код, написанный ИИ, чтобы он стал доступен всем посетителям сайта.',
        },
      ],
    },
    editableFields: [
      field("content.windowTitle", "Окно: название файла"),
      field("content.videoSrc", "Окно: видео", "media", "attr"),
      field("content.heading.muted", "Заголовок: приглушённая строка"),
      field("content.heading.main", "Заголовок: основная строка"),
      field("content.introHtml", "Вводный текст", "html"),
      field("content.features.0.eyebrow", "Пункт 1: подпись"),
      field("content.features.0.bodyHtml", "Пункт 1: текст", "html"),
      field("content.features.1.eyebrow", "Пункт 2: подпись"),
      field("content.features.1.bodyHtml", "Пункт 2: текст", "html"),
      field("content.features.2.eyebrow", "Пункт 3: подпись"),
      field("content.features.2.bodyHtml", "Пункт 3: текст", "html"),
      field("content.features.3.eyebrow", "Пункт 4: подпись"),
      field("content.features.3.bodyHtml", "Пункт 4: текст", "html"),
    ],
  },
  "component-library": {
    type: "componentLibrary",
    content: {
      heading: {
        muted: "Агентная",
        middle: "Компонентная",
        main: "библиотека.",
      },
      introHtml:
        'Готовая к работе с агентами <span class="darker-70">компонентная</span> библиотека: добавление в один клик через приложение NODE.DC для Webflow и дальнейшая настройка под проект.',
      sliderItems: demoSliderItems,
      oneClick: {
        heading: {
          muted: "Один",
          middle: "клик",
          main: "добавления.",
        },
        introHtml: 'С помощью <span class="darker-70">расширения NODE.DC для Webflow.</span> ',
        items: [
          {
            eyebrow: "Понимание компонентов",
            bodyHtml:
              'Используйте <span class="darker-70">несколько агентов параллельно</span> чтобы добавлять кастомный код, анимации и функциональность.',
          },
          {
            eyebrow: "Быстрый старт",
            bodyHtml:
              'Используйте быстрые <span class="darker-70">Bun</span> сборки и автообновление для вашего <span class="darker-70">кастомного кода.</span>',
          },
        ],
      },
      mcp: {
        heading: {
          muted: "Получите",
          middle: "более сильный",
          main: "MCP.",
        },
        introHtml: "Полностью переработанный и более быстрый MCP-сервер. ",
        items: [
          {
            eyebrow: "Понимание Webflow",
            bodyHtml:
              'Используйте <span class="darker-70">несколько агентов параллельно</span> чтобы добавлять кастомный код, анимации и функциональность.',
          },
        ],
      },
    },
    editableFields: [
      field("content.heading.muted", "Заголовок: приглушённая строка"),
      field("content.heading.middle", "Заголовок: средняя строка"),
      field("content.heading.main", "Заголовок: основная строка"),
      field("content.introHtml", "Вводный текст", "html"),
      ...sliderEditableFields(),
      field("content.oneClick.heading.muted", "Один клик: приглушённая строка"),
      field("content.oneClick.heading.middle", "Один клик: средняя строка"),
      field("content.oneClick.heading.main", "Один клик: основная строка"),
      field("content.oneClick.introHtml", "Один клик: вводный текст", "html"),
      field("content.oneClick.items.0.eyebrow", "Один клик: пункт 1, подпись"),
      field("content.oneClick.items.0.bodyHtml", "Один клик: пункт 1, текст", "html"),
      field("content.oneClick.items.1.eyebrow", "Один клик: пункт 2, подпись"),
      field("content.oneClick.items.1.bodyHtml", "Один клик: пункт 2, текст", "html"),
      field("content.mcp.heading.muted", "MCP: приглушённая строка"),
      field("content.mcp.heading.middle", "MCP: средняя строка"),
      field("content.mcp.heading.main", "MCP: основная строка"),
      field("content.mcp.introHtml", "MCP: вводный текст", "html"),
      field("content.mcp.items.0.eyebrow", "MCP: пункт 1, подпись"),
      field("content.mcp.items.0.bodyHtml", "MCP: пункт 1, текст", "html"),
    ],
  },
  "ai-modes": {
    type: "aiModes",
    content: {
      heading: {
        muted: "Гибкие",
        main: "режимы ИИ.",
      },
      introHtml: 'Разные <span class="darker-70">режимы</span> и <span class="darker-70">модели</span> для разных задач.',
      sliderItems: demoSliderItems,
    },
    editableFields: [
      field("content.heading.muted", "Заголовок: приглушённая строка"),
      field("content.heading.main", "Заголовок: основная строка"),
      field("content.introHtml", "Вводный текст", "html"),
      ...sliderEditableFields(),
    ],
  },
  faq: {
    type: "faqSection",
    content: {
      heading: {
        muted: "Частые",
        middle: "вопросы",
        main: "и ответы",
      },
      introHtml: '<span class="darker-70">Подробный обзор</span> возможностей, логики работы и внутреннего устройства.',
      docsHref: "https://docs.ssscript.app/",
      docsLabel: "Полная документация",
      windowTitle: "FAQ",
      faq: [
        {
          question: "Это влияет на производительность?",
          answerHtml:
            "<p>Как и любой кастомный код, это добавляет только ту нагрузку, которую несёт сам код. </p><p>Все файлы собираются, минифицируются и сжимаются, чтобы минимально влиять на проект и не плодить внешние библиотеки и CDN-скрипты.</p>",
        },
        {
          question: "Где размещаются мои файлы?",
          answerHtml:
            "<p>После сборки файлы загружаются в Cloudflare R2 и отдаются через CDN-ссылку на выделенном домене проекта, чтобы получить быстрый и совместимый с Webflow результат.</p>",
        },
        {
          question: "Что делать, если я опубликовал не те файлы?",
          answerHtml:
            "<p>В панели деплоя есть мгновенный откат. </p><p>Нажмите откат, верните предыдущую рабочую версию, исправьте локально и опубликуйте заново.</p>",
        },
        {
          question: "Что такое ИИ-агент Webflow?",
          answerHtml:
            "<p>Помимо <strong>@plan</strong>, <strong>@build</strong> и <strong>@explore</strong> стандартных режимов агентов мы готовим <strong>@webflow</strong> режим, специально заточенный под Webflow: структуру проекта, взаимодействие с ним и эффективную разработку.</p>",
        },
        {
          question: "Что такое собственная база знаний?",
          answerHtml:
            "<p>Основано на многолетнем опыте написания кастомного кода для Webflow-анимаций. </p><p>WebGL, Three.js, GSAP и кастомная функциональность собраны в набор практик, пайплайнов и модульную систему, чтобы всё работало согласованно. </p><p>Также доступна библиотека компонентов: выберите компонент, добавьте в проект и попросите ИИ адаптировать его под задачу.</p>",
        },
        {
          question: "Как использовать это с Webflow?",
          answerHtml:
            "<p>При запуске <em>NODE.DC</em> доступна отдельная вкладка для встраивания. </p><p>В зависимости от задачи можно получить скрипты для конкретной страницы или единый app.js для всего проекта. </p><p>Сгенерированная ссылка содержит и продакшен-версию, следующую за URL деплоя, и локальную версию для предпросмотра без влияния на посетителей сайта.</p>",
        },
        {
          question: "Какие ИИ-модели доступны?",
          answerHtml:
            "<p>Мы используем основные доступные модели: <strong>OpenAI</strong> до <strong>Claude</strong> до <strong>Gemini</strong>. </p><p>Оркестратор выбирает модель под задачу автоматически, но вы можете вручную переключиться на нужную модель.</p>",
        },
        {
          question: "Как это работает?",
          answerHtml:
            "<p>Во время работы NODE.DC выполняет две задачи параллельно.</p><p>Поднимает локальную среду разработки на Bun, синхронизирует JavaScript и TypeScript, включает HMR и держит webhook для автоматического обновления сайта при изменениях. </p><p>Одновременно работает оркестратор ИИ-агентов: обрабатывает запросы, меняет код, проверяет состояние сборки и при необходимости обновляет проект Webflow.</p>",
        },
        {
          question: "Как работает публикация?",
          answerHtml:
            "<p>Процесс многоэтапный: сначала Bun собирает, сжимает, минифицирует и подготавливает проект вместе с библиотеками.</p><p>Затем файлы отправляются в CDN: <code>cdn.node.dc/{USERNAME}/{PROJECT-NAME}</code>. Ссылка уже содержит нужную структуру: вы видите локальную версию, а пользователи получают опубликованный скрипт.</p>",
        },
        {
          question: "Это работает с MCP?",
          answerHtml:
            "<p>Система изначально рассчитана на работу с Webflow MCP. </p><p>Подключите проект и авторизацию, чтобы ИИ-агенты получили доступ к Webflow-сборке. Подключение приложения в Webflow Designer даёт агентам доступ к контенту, элементам, стилям и CSS, а кастомный код получает полный контекст проекта.</p>",
        },
      ],
    },
    editableFields: [
      field("content.heading.muted", "Заголовок: приглушённая строка"),
      field("content.heading.middle", "Заголовок: средняя строка"),
      field("content.heading.main", "Заголовок: основная строка"),
      field("content.introHtml", "Вводный текст", "html"),
      field("content.docsHref", "Документация: ссылка", "url", "attr"),
      field("content.docsLabel", "Документация: кнопка"),
      field("content.windowTitle", "Окно: название"),
      ...Array.from({ length: 10 }, (_, index) => [
        field(`content.faq.${index}.question`, `FAQ ${index + 1}: вопрос`, "text", "text", "FAQ", {
          replaceAll: true,
        }),
        field(`content.faq.${index}.answerHtml`, `FAQ ${index + 1}: ответ`, "html", "html", "FAQ"),
      ]).flat(),
    ],
  },
  "publish-cta": {
    type: "publishCta",
    content: {
      cta: {
        heading: {
          muted: "Подключить.",
          middle: "Поставить задачу.",
          main: "Опубликовать.",
        },
        intro: "Единый путь от разработки до продакшена.",
        detailHtml:
          '<span class="darker-70">Настройка примерно в 12 кликов:</span> от скачивания приложения до запуска в проекте.',
        href: "index.html#hero",
        buttonLabel: "Встать в лист ожидания",
      },
    },
    editableFields: [
      field("content.cta.heading.muted", "CTA: приглушённая строка"),
      field("content.cta.heading.middle", "CTA: средняя строка"),
      field("content.cta.heading.main", "CTA: основная строка"),
      field("content.cta.intro", "CTA: вводный текст", "text", "text", "CTA"),
      field("content.cta.detailHtml", "CTA: пояснение", "html", "html", "CTA"),
      field("content.cta.href", "CTA: ссылка", "url", "attr", "CTA"),
      field("content.cta.buttonLabel", "CTA: кнопка", "text", "text", "CTA"),
    ],
  },
  "pricing-table": {
    type: "pricingTable",
    content: {
      pricing: {
        eyebrow: "Тарифы",
        heading: {
          muted: "Код",
          middle: "для No-Code,",
          main: "усиленный ИИ.",
        },
        introHtml: "Подписки на приложение с<br/>включённым объёмом в каждом тарифе. <br/>",
        tokenBillingHtml: '<span class="darker-70">Токенная</span> оплата сверх лимитов.',
        founderLimitHtml: '<span class="darker-70">200 Founder</span> мест с фиксированной ценой на первый год.',
        windowTitle: "Тарифы.app",
        discountLabel: "Скидка 50%",
        betaHref: "beta-apply.html",
        founderHref: "founders.html",
        betaButtonLabel: "Подать заявку в бету",
        founderButtonLabel: "Оформить Founder-доступ",
        basicButtonLabel: "Выбрать Basic",
        advancedButtonLabel: "Выбрать Advanced",
        noteHtml: "(*) Условия могут меняться по ходу тестирования, но мы постараемся сохранить заявленные обещания.",
        enterpriseNoteHtml:
          '<a href="enterprise.html" class="darker-40">Enterprise</a> BYOK-тарифы будут доступны с сентября 2026.',
      },
      plans: [
        {
          name: "Бета",
          priceHtml: "Бесплатно",
          period: "1 месяц",
        },
        {
          name: "Founder ",
          priceHtml: '<span class="darker-70 mr-xs lighter">€</span>310',
          period: "/ год, навсегда",
        },
        {
          name: "Basic",
          priceHtml: '<span class="darker-70 mr-xs lighter">€</span>20',
          period: "/ месяц",
        },
        {
          name: "Advanced",
          priceHtml: '<span class="darker-70 mr-xs lighter">€</span>50',
          period: "/ месяц",
        },
      ],
    },
    editableFields: [
      field("content.pricing.eyebrow", "Тарифы: надзаголовок"),
      field("content.pricing.heading.muted", "Тарифы: приглушённая строка"),
      field("content.pricing.heading.middle", "Тарифы: средняя строка"),
      field("content.pricing.heading.main", "Тарифы: основная строка"),
      field("content.pricing.introHtml", "Тарифы: вводный текст", "html"),
      field("content.pricing.tokenBillingHtml", "Тарифы: оплата сверх лимитов", "html"),
      field("content.pricing.founderLimitHtml", "Тарифы: Founder-лимит", "html"),
      field("content.pricing.windowTitle", "Окно: название"),
      field("content.pricing.discountLabel", "Founder: бейдж скидки"),
      field("content.plans.0.name", "Бета: название"),
      field("content.plans.0.priceHtml", "Бета: цена", "html"),
      field("content.plans.0.period", "Бета: период"),
      field("content.plans.1.name", "Founder: название"),
      field("content.plans.1.priceHtml", "Founder: цена", "html"),
      field("content.plans.1.period", "Founder: период"),
      field("content.plans.2.name", "Basic: название"),
      field("content.plans.2.priceHtml", "Basic: цена", "html"),
      field("content.plans.2.period", "Basic: период"),
      field("content.plans.3.name", "Advanced: название"),
      field("content.plans.3.priceHtml", "Advanced: цена", "html"),
      field("content.plans.3.period", "Advanced: период"),
      field("content.pricing.betaHref", "Бета: ссылка", "url", "attr"),
      field("content.pricing.founderHref", "Founder: ссылка", "url", "attr"),
      field("content.pricing.betaButtonLabel", "Бета: кнопка"),
      field("content.pricing.founderButtonLabel", "Founder: кнопка"),
      field("content.pricing.basicButtonLabel", "Basic: кнопка"),
      field("content.pricing.advancedButtonLabel", "Advanced: кнопка"),
      field("content.pricing.noteHtml", "Тарифы: примечание", "html"),
      field("content.pricing.enterpriseNoteHtml", "Тарифы: Enterprise-примечание", "html"),
    ],
  },
};

const staticElements = {
  id: "static-elements",
  type: "staticElements",
  adminLabel: "Статичные элементы",
  enabled: true,
  content: {
    navigation: {
      logoAlt: "главная",
      brandLabel: "NODE.DC",
      links: [
        {
          label: "О продукте",
          href: "index.html#features",
          target: "",
        },
        {
          label: "Лист ожидания",
          href: "index.html#hero",
          target: "",
        },
        {
          label: "Документация",
          href: "https://docs.ssscript.app/",
          target: "_blank",
        },
        {
          label: "Тарифы",
          href: "index.html#pricing-wrap",
          target: "",
        },
      ],
    },
    notifications: [
      {
        eyebrowHtml: '<span class="darker-70">ДАЛЕЕ</span> — ИЮЛЬ 2026',
        title: "Открытая бета",
        body: "Открываем доступ всем. Будем собирать и отлаживать вместе.",
      },
      {
        eyebrowHtml: '<span class="darker-70">ДАЛЕЕ</span> —<span class="darker-70"> </span>ИЮЛЬ 2026',
        title: "Альфа для Founder-доступа",
        body: "На второй фазе тестирования бета открыта для всех подписчиков Founder-тарифа.",
      },
      {
        eyebrowHtml: "СЕЙЧАС",
        title: "Инвайты в закрытую бету уже отправлены.",
        body: "Сейчас тестируем базовую функциональность и ИИ-интеграцию. ",
      },
    ],
    dock: {
      appTarget: "app-data",
      trashAlt: "Белая иконка корзины.",
      demoFileLabel: "демо-видео.txt",
    },
    assets: {
      webglModelSrc: "./assets/custom/icon.glb",
      logoPng: "./assets/custom/logo.png",
      logoPngAlt: "Логотип NODE.DC",
      appIconSrc: "./assets/webflow/images/69ad964d951f9d17cc5a919e_logo-app.svg",
      appIconAlt: "Иконка NODE.DC",
      appWordmarkSrc: "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc",
      appWordmarkAlt: "Логотип NODE.DC",
    },
  },
  editableFields: [
    field("content.navigation.logoAlt", "Логотип в шапке: alt"),
    field("content.navigation.brandLabel", "Бренд в шапке"),
    field("content.navigation.links.0.label", "Ссылка 1: текст"),
    field("content.navigation.links.0.href", "Ссылка 1: адрес", "url", "attr"),
    field("content.navigation.links.1.label", "Ссылка 2: текст"),
    field("content.navigation.links.1.href", "Ссылка 2: адрес", "url", "attr"),
    field("content.navigation.links.2.label", "Ссылка 3: текст"),
    field("content.navigation.links.2.href", "Ссылка 3: адрес", "url", "attr"),
    field("content.navigation.links.2.target", "Ссылка 3: target", "text", "attr"),
    field("content.navigation.links.3.label", "Ссылка 4: текст"),
    field("content.navigation.links.3.href", "Ссылка 4: адрес", "url", "attr"),
    field("content.notifications.0.eyebrowHtml", "Уведомление 1: дата/статус", "html"),
    field("content.notifications.0.title", "Уведомление 1: заголовок"),
    field("content.notifications.0.body", "Уведомление 1: текст", "textarea"),
    field("content.notifications.1.eyebrowHtml", "Уведомление 2: дата/статус", "html"),
    field("content.notifications.1.title", "Уведомление 2: заголовок"),
    field("content.notifications.1.body", "Уведомление 2: текст", "textarea"),
    field("content.notifications.2.eyebrowHtml", "Уведомление 3: дата/статус", "html"),
    field("content.notifications.2.title", "Уведомление 3: заголовок"),
    field("content.notifications.2.body", "Уведомление 3: текст", "textarea"),
    field("content.dock.appTarget", "Dock: ID приложения"),
    field("content.dock.trashAlt", "Dock: alt корзины"),
    field("content.dock.demoFileLabel", "Dock: подпись демо-файла"),
    field("content.assets.webglModelSrc", "WebGL-модель логотипа", "media", "attr"),
    field("content.assets.logoPng", "PNG-логотип", "media", "attr"),
    field("content.assets.logoPngAlt", "PNG-логотип: alt"),
    field("content.assets.appIconSrc", "Иконка приложения", "media", "attr"),
    field("content.assets.appIconAlt", "Иконка приложения: alt"),
    field("content.assets.appWordmarkSrc", "Wordmark приложения", "media", "attr"),
    field("content.assets.appWordmarkAlt", "Wordmark приложения: alt"),
  ],
};

function shellToken(mode, path) {
  return `{{${mode}:staticElements.${path}}}`;
}

function replaceShellLiteral(source, literal, replacement, label) {
  if (!source.includes(literal)) {
    throw new Error(`Could not find shell fragment while tokenizing static elements: ${label}`);
  }

  return source.replace(literal, replacement);
}

function tokenizeStaticShell(source, definition) {
  const { navigation, notifications, dock } = definition.content;

  let tokenized = source;

  tokenized = replaceShellLiteral(
    tokenized,
    `aria-label="${navigation.logoAlt}" aria-current`,
    `aria-label="${shellToken("attr", "content.navigation.logoAlt")}" aria-current`,
    "navigation.logoAlt",
  );
  tokenized = replaceShellLiteral(
    tokenized,
    `class="bar-txt medium w--current">${navigation.brandLabel}</a>`,
    `class="bar-txt medium w--current">${shellToken("text", "content.navigation.brandLabel")}</a>`,
    "navigation.brandLabel",
  );

  for (const [index, link] of navigation.links.entries()) {
    const targetPart = link.target ? ` target="${link.target}"` : "";
    tokenized = replaceShellLiteral(
      tokenized,
      `<a href="${link.href}"${targetPart} class="bar-txt">${link.label}</a>`,
      `<a href="${shellToken("attr", `content.navigation.links.${index}.href`)}"${
        link.target ? ` target="${shellToken("attr", `content.navigation.links.${index}.target`)}"` : ""
      } class="bar-txt">${shellToken("text", `content.navigation.links.${index}.label`)}</a>`,
      `navigation.links.${index}`,
    );
  }

  notifications.forEach((notification, index) => {
    tokenized = replaceShellLiteral(
      tokenized,
      notification.eyebrowHtml,
      shellToken("html", `content.notifications.${index}.eyebrowHtml`),
      `notifications.${index}.eyebrowHtml`,
    );
    tokenized = replaceShellLiteral(
      tokenized,
      `<div>${notification.title}</div>`,
      `<div>${shellToken("text", `content.notifications.${index}.title`)}</div>`,
      `notifications.${index}.title`,
    );
    tokenized = replaceShellLiteral(
      tokenized,
      `<div>${notification.body}</div>`,
      `<div>${shellToken("text", `content.notifications.${index}.body`)}</div>`,
      `notifications.${index}.body`,
    );
  });

  tokenized = replaceShellLiteral(
    tokenized,
    `data-target="${dock.appTarget}" class="dock-icon ssscript"`,
    `data-target="${shellToken("attr", "content.dock.appTarget")}" class="dock-icon ssscript"`,
    "dock.appTarget",
  );
  tokenized = replaceShellLiteral(
    tokenized,
    `alt="${dock.trashAlt}" class="dock-app-img trash"`,
    `alt="${shellToken("attr", "content.dock.trashAlt")}" class="dock-app-img trash"`,
    "dock.trashAlt",
  );

  tokenized = replaceShellLiteral(
    tokenized,
    "<script>\nwindow.__API_ORIGIN__=",
    `\n<script id="node-dc-static-config" type="application/json">{"assets":{"icon":"${shellToken(
      "js",
      "content.assets.webglModelSrc",
    )}","logo":"${shellToken("js", "content.assets.logoPng")}"}}</script>\n<script>\nwindow.__API_ORIGIN__=`,
    "static asset config",
  );

  return tokenized;
}

shell = tokenizeStaticShell(shell, staticElements);

const preparedBlocks = blocks.map((block) => {
  const definition = typedBlockDefinitions[block.id];
  return definition ? tokenizeBlock(block, definition) : block;
});

const page = {
  schemaVersion: 1,
  slug: "home",
  output: "index.html",
  title: "NODE.DC",
  staticElements,
  seo: {
    title: "NODE.DC — ИИ-платформа для Webflow",
    description:
      "Единая платформа для кастомного кода, MCP и ИИ-агентов в Webflow: контекст проекта, автоматизация, деплой и управляемая разработка.",
  },
  regions: [
    {
      id: "beforeCViz",
      label: "До c-viz wrapper",
      description: "Hero and first product section. These blocks live before the visual wrapper.",
      blocks: preparedBlocks
        .filter((block) => block.region === "beforeCViz")
        .map(({ html: _html, ...block }) => ({ ...block, enabled: true })),
    },
    {
      id: "cViz",
      label: "Inside c-viz wrapper",
      description: "Sections coupled to WebGL/color-flip shell. Keep these in this region for now.",
      blocks: preparedBlocks
        .filter((block) => block.region === "cViz")
        .map(({ html: _html, ...block }) => ({ ...block, enabled: true })),
    },
  ],
};

await mkdir(blockRoot, { recursive: true });
await mkdir(dirname(contentPath), { recursive: true });
await writeFile(join(templateRoot, "shell.html"), shell);
for (const block of preparedBlocks) {
  await writeFile(join(blockRoot, block.template), block.html);
}
await writeFile(contentPath, `${JSON.stringify(page, null, 2)}\n`);

console.log(`Extracted ${blocks.length} blocks from ${sourcePath}`);
console.log(`Wrote ${join(templateRoot, "shell.html")}`);
console.log(`Wrote ${contentPath}`);
