<?php
/**
 * AI 分步生成模板 API
 * 工作流：header → footer → homepage → article → archive → css
 * 每步生成一个文件，自动保存，返回摘要供下一步上下文使用
 */
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_DEPRECATED);
ini_set('display_errors', '0');
ob_start();

require __DIR__ . '/../../app/bootstrap.php';
require __DIR__ . '/../../app/database.php';
require __DIR__ . '/_ai-helpers.php';
require_login();

// 清除之前可能输出的任何内容（PHP warnings等）
ob_end_clean();
header('Content-Type: application/json; charset=utf-8');

$packCode    = isset($_POST['pack_code']) ? trim((string)$_POST['pack_code']) : '';
$fileKey     = isset($_POST['file_key']) ? trim((string)$_POST['file_key']) : '';
$instruction = isset($_POST['instruction']) ? trim((string)$_POST['instruction']) : '';
$prevContext = isset($_POST['prev_context']) ? trim((string)$_POST['prev_context']) : '';
$autoSave    = !empty($_POST['auto_save']);
$modelId     = isset($_POST['model_id']) ? (int)$_POST['model_id'] : 0;

if ($packCode === '' || $fileKey === '') {
    echo json_encode(array('success' => false, 'error' => '缺少必要参数。'));
    exit;
}

$model = null;
if ($modelId > 0) {
    $model = find_ai_model_by_id($modelId);
    if (!$model || $model['status'] !== 'enabled') {
        $model = null;
    }
}
if (!$model) {
    $model = find_first_enabled_ai_model();
}
if (!$model) {
    echo json_encode(array('success' => false, 'error' => '未配置启用的AI模型，请先在AI模型管理中配置。'));
    exit;
}

$apiUrl = isset($model['api_url']) ? trim((string)$model['api_url']) : '';
$apiKey = isset($model['api_key']) ? trim((string)$model['api_key']) : '';
if ($apiUrl === '' || $apiKey === '') {
    echo json_encode(array('success' => false, 'error' => 'AI模型API地址或Key未配置。'));
    exit;
}

$manifest = load_template_pack_manifest($packCode);
if (!$manifest) {
    echo json_encode(array('success' => false, 'error' => '未找到模板套件: ' . $packCode));
    exit;
}
$packName = isset($manifest['name']) ? (string)$manifest['name'] : $packCode;
$packType = isset($manifest['pack_type']) ? (string)$manifest['pack_type'] : 'variation';

$vars = template_variables_for_file_key($fileKey);
$fileKeys = template_pack_file_keys();
$fileLabel = isset($fileKeys[$fileKey]) ? $fileKeys[$fileKey] : $fileKey;

// ---------- Build prompts ----------

$cssPrefix = preg_replace('/[^a-z0-9]/', '-', strtolower($packCode));

$systemPrompt = <<<PROMPT
你是一个资深前端模板工程师，正在为"蜜蜂AI建站"平台的模板套件「{$packName}」({$packCode}) 生成模板文件。

【铁律——必须100%遵守，违反任何一条视为失败】
1. 只输出纯代码，绝对不要输出任何解释文字、markdown代码块标记（```）
2. 自定义CSS class统一使用前缀 .{$cssPrefix}-  避免冲突
3. 代码必须语义化、响应式、移动端友好、SEO友好（h1-h6层级合理、alt属性等）
4. 禁止添加文档之外的模板变量，只能使用下方提供的变量列表中存在的变量
5. 每个变量只输出一次，严禁同一个变量在页面中出现两次（特别是广告位变量）
6. 系统只支持简单的 {{变量名}} 占位符替换，严禁使用 Mustache/Handlebars 条件语法如 {{#var}}...{{/var}}、{{^var}}等

【header.html / footer.html 是片段，不是完整页面】
- header.html 和 footer.html 通过 {{header}} / {{footer}} 注入到 homepage/article/archive 页面中
- 因此 header.html 和 footer.html 绝对不能包含 <!DOCTYPE>、<html>、<head>、<body> 等文档级标签
- header.html 只输出 <header>...</header> 片段（含内联 <style>）
- footer.html 只输出 <footer>...</footer> 片段（含内联 <style>）
- 全局CSS变量（:root）、CSS Reset、body样式 必须写在 style.css 中（会自动注入），不要写在 header/footer 中

【style.css 和 script.js 自动注入机制——极重要】
- 系统会自动将 style.css 的内容以 <style> 标签注入到每个页面的 <head> 中
- 系统会自动将 script.js 的内容以 <script> 标签注入到每个页面的 </body> 前
- 因此：CSS Reset、:root变量、body基础样式、系统类名样式（.article-list-card/.article-card/.random-article-link/.header-dark系列）必须写在 style.css 中，不要在页面文件中重复
- 页面内 <style> 只写该页面独有的布局和区块样式（如hero、sidebar布局等）
- 页面内不要写 <script>，共享JS功能放在 script.js 中

【homepage.html / article.html / archive.html 是完整页面】
- 必须包含完整的 <!DOCTYPE html> 结构（含 <head>、<body>）
- 必须包含 {{header}} 和 {{footer}} 占位符
- <head> 中可写 <style> 但只放本页独有的CSS（不要重复 style.css 已有的内容）

【变量使用规则——严格遵守】
- 每个变量最多使用一次，不要重复输出
- 广告位变量（article_ad_top/middle/bottom/sidebar）直接用 <div> 包裹输出即可，变量本身就是完整HTML
- {{archive_content}} 已包含文章卡片列表+分页，使用它后不要再单独使用 {{archive_cards}} 或 {{archive_pagination}}；如果想自定义卡片列表样式，则只用 {{archive_cards}} + {{archive_pagination}}，不用 {{archive_content}}
- {{search_box}} 是系统生成的完整搜索框HTML，使用后不要再手动编写搜索框HTML
- 不要臆造 mod_* 变量名（如 mod_address、mod_phone 等），除非用户明确要求

【JS 防 WAF 拦截——绝对禁止的写法】
- 禁止 class/constructor（用普通 function 替代）
- 禁止模板字面量（用 '...' + var 拼接替代）
- 禁止 window.location.href = url（用动态创建 form 提交替代）
- 禁止 ...args 展开运算符（用 arguments 或显式参数替代）
- 禁止 eval()/Function()/document.write()/document.cookie
- 整个 script 用 IIFE 包裹：(function(){ 'use strict'; ... })();

【CSS 系统类名——这些是系统自动生成的HTML所使用的固定类名，不加自定义前缀】
系统渲染 {{archive_cards}}、{{related_articles}}、{{random_articles}} 时会输出固定类名的HTML，你的CSS必须直接为这些无前缀的系统类名编写样式：
栏目页文章卡片：.article-list-card / .article-list-card-link（a标签，必须 text-decoration:none !important; color:inherit !important;）/ .article-list-thumb / .article-list-thumb img / .article-list-time / .article-list-body / .article-list-title / .article-list-desc
文章页相关文章：.article-card（a标签，必须 display:inline-block !important; text-decoration:none !important; 并设置百分比宽度实现3~4列）/ .article-card-thumb / .article-card-thumb img / .article-card-title
随机推荐：.random-article-link（a标签，必须 display:flex !important; text-decoration:none !important; 每条独占一行）/ .random-article-index / .random-article-text
导航深色模式（用于栏目页/文章页无Hero时导航可读）：
  .header-dark 只做占位撑高（padding-top:70px），不设背景色
  .header-dark .{$cssPrefix}-header 设置深色背景（因为header是position:fixed脱离文档流，背景必须加在fixed元素上而非包裹div上）
  .header-dark .menu-nav a 白色文字
注意：以上系统类名不要加 .{$cssPrefix}- 前缀！它们是系统固定输出的，必须原样匹配。（.header-dark 是系统类名；其内部 .{$cssPrefix}-header 是自定义类名）

【侧边栏/推荐区标题规则】
系统生成的 {{archive_sidebar}}、{{related_articles}}、{{random_articles}} 的HTML不含标题，模板中必须在变量前手动添加 <h3> 标题，例如：
<h3>相关文章</h3>
{{related_articles}}

【多样性要求】
- 每个模板必须在布局结构、视觉风格、信息层次上具备明显差异
- 首屏Hero形态必须多变：全屏沉浸/左右分栏/斜切分割/纯文字极简/网格背景/带统计条等
- 卡片列数必须变化：1列/2列/3列/4列/不规则/瀑布流
- 文章页布局多变：全宽无侧栏/左内容右侧栏/窄栏居中（Medium风格）等
- 栏目页列表多变：卡片网格/时间线/纯文字列表/大图+摘要/左图右文交替等

【CSS Grid 防溢出——极重要】
- 所有使用 display:grid 的容器必须加 overflow:hidden
- 所有 grid 子项（卡片）必须加 min-width:0; overflow:hidden; 防止内容撑破网格列宽导致卡片偏移或溢出
- 容器使用 max-width + margin:0 auto 居中，所有卡片区块必须水平居中对齐
- grid-template-columns 使用 repeat(N, 1fr) 时，子元素的文字内容不能撑破列宽

【颜色对比度——极重要】
- 所有文本与背景必须有明显的颜色反差：深色背景用浅色/白色文字，浅色背景用深色文字
- 导航栏：如果导航背景是深色/暗色，导航链接文字必须是白色或浅色（#fff 或接近白色）；如果导航背景是浅色/透明，链接文字用深色
- 按钮：主要按钮必须有醒目的背景色和对比鲜明的文字颜色（如深色按钮+白色文字，或亮色按钮+深色文字），不要让按钮和背景混为一体
- 侧边栏标题（h3）和正文要有足够的颜色区分度
- 禁止出现文字颜色与背景颜色相近（如深灰文字在深色背景上、浅色文字在白色背景上）的情况
PROMPT;

// ---------- 根据 pack_type 追加 CSS 变量约束 ----------
if ($packType === 'variation') {
    $systemPrompt .= <<<'VARIATION_RULES'

【变异引擎适配——本模板为变体类型，必须严格遵守】
本模板参与变异引擎，同一套模板在不同域名下会呈现不同的配色、字体、圆角和间距。因此：

1. :root 中的主色、强调色、背景色、圆角变量必须使用 var(--v-xxx, 默认值) 语法：
   --primary-color: var(--v-primary, #3b82f6);
   --primary-dark: var(--v-primary-dark, #2563eb);
   --primary-light: var(--v-primary-light, #dbeafe);
   --accent-color: var(--v-accent, #8b5cf6);
   --bg-subtle: var(--v-bg-subtle, #f8fafc);
   --dark-bg: var(--v-dark-bg, #0a1628);
   --radius-sm: var(--v-radius-sm, 0.375rem);
   --radius-md: var(--v-radius-md, 0.5rem);
   --radius-lg: var(--v-radius-lg, 0.75rem);
   --radius-xl: var(--v-radius-xl, 1rem);

2. CSS 中所有与主色相关的颜色值禁止硬编码 RGB/HEX，必须引用上述变量：
   ❌ background-color: #3b82f6;
   ✅ background-color: var(--primary-color);
   ❌ background-color: rgba(59, 130, 246, 0.1);
   ✅ background-color: var(--primary-light);

3. font-family 禁止使用 !important（变异引擎通过 !important 覆盖字体）

4. 交替背景区块必须使用 var(--bg-subtle)，不要硬编码 #f8fafc

5. 深色区域背景使用 var(--dark-bg)，不要硬编码色值
VARIATION_RULES;
} else {
    $systemPrompt .= <<<'STANDARD_RULES'

【固定样式模板——本模板为常规类型，CSS直接写死】
本模板不参与变异引擎，所有域名使用相同外观。因此：

1. :root 中的颜色、字体、圆角、间距全部直接写死（不需要 var(--v-xxx) 语法）：
   --primary: #2563eb;  /* 直接写具体色值 */
   --primary-dark: #1d4ed8;
   --accent: #f59e0b;
   等等，变量名可自由命名，不需要兼容变异引擎

2. 每个模板应有独特的色彩方案，可从以下风格中选择：
   冷色系蓝紫 / 暖色系橙红 / 自然系绿棕 / 商务系深蓝灰 / 科技系青蓝 / 高对比黑白 / 柔和莫兰迪色 / 暗黑主题

3. 字体可自由使用 !important，无变异引擎冲突

4. 重点追求模板自身的视觉完整性和独特性，每个模板是一套完整的、固定的视觉方案
STANDARD_RULES;
}

// File-type specific guides
$fileGuides = array(
    'header' => "生成【全局页头模板】（header.html）——这是HTML片段，不是完整页面：
- 禁止输出 <!DOCTYPE>、<html>、<head>、<body> 标签
- 只输出一个 <header> 标签及其内联 <style>，结构：<style>...</style><header>...</header>
- 必须包含 {{site_name}} 和 {{menu_nav}}
- 生成一个响应式导航栏，含logo/站名区域 + 导航菜单
- 默认样式为 fixed 定位、透明/半透明背景
- 导航链接文字颜色必须与导航背景形成强对比（深色背景→白色文字，浅色背景→深色文字），确保在任何页面上都清晰可读
- 移动端需有汉堡菜单切换（JS用IIFE包裹写在<script>标签中）
- 不要在 header 中写 CSS Reset、body 样式或 :root 变量（这些属于页面级CSS）",

    'footer' => "生成【全局页脚模板】（footer.html）——这是HTML片段，不是完整页面：
- 禁止输出 <!DOCTYPE>、<html>、<head>、<body> 标签
- 只输出 <style>...</style><footer>...</footer> 片段
- 必须包含 {{year}}、{{site_name}}、{{footer_text}}、{{friend_links}}
- 禁止在 footer 中放置 {{cross_site_articles}}（跨站文章列表只在首页主体区域输出，不能在页脚重复）
- 不要在 footer 中写 CSS Reset 或 body 样式
- 不要使用臆造的 mod_* 变量（如 mod_address/mod_phone/mod_email），只用提供的变量列表中的变量
- 禁止在页脚中硬编码导航链接（如'首页、关于我们、产品中心'等），导航已在 header 中通过 {{menu_nav}} 输出

根据用户指令判断页脚风格：
【企业营销型】多栏布局（站点信息+快速链接+联系方式），可稍复杂
【资讯/新闻/博客型】（当用户要求新闻/资讯/博客/门户时使用）极简单行页脚：一行版权信息（{{site_name}} © {{year}} {{footer_text}}）+ 友链区 {{friend_links}}，不要多栏布局、不要快速导航、不要站内信息栏目，保持轻量干净",

    'homepage' => "生成【首页模板】（homepage.html）——完整HTML页面：
- 必须包含完整的 <!DOCTYPE html> 结构（含 <head> 和 <body>）
- <body> 开头放 {{header}}，结尾放 {{footer}}
- <style> 中只写首页独有的区块CSS
- 不要在 <style> 中写 CSS Reset、body基础样式或系统类名样式（这些已在 style.css 中，会自动注入）
- 首页可定义自己的 :root 变量（如 --ghp-* 等页面级配色变量），但不要重复 style.css 中的通用变量

根据用户指令判断首页类型，分两种模式：

【A 企业营销型首页】（默认）
- 需要用到尽可能多的首页专属变量（hero_*、services_*、feature_*、trust_*、testimonial_*、cta_* 等）
- 板块顺序可自由组合，建议从以下选取5~8个：hero首屏 / 统计数字 / 信任背书 / 服务展示 / 核心亮点 / 客户评价 / CTA转化 / 最新文章
- 底部包含 {{latest_articles}} 和 {{cross_site_articles}}（注意：{{cross_site_articles}} 只能在首页主体出现一次，页脚中禁止重复放置）

【B 资讯门户型首页】（当用户要求新闻/资讯/博客/门户/文章列表型首页时使用）
- 首页核心内容是 {{menu_articles_sections}}（系统自动生成：按每个菜单栏目分区展示最新文章卡片列表，含栏目标题和'查看更多'链接）
- 首屏可以有一个简洁的banner/搜索区（用少量hero_*变量），但不要占太多空间
- 页面主体直接放 {{menu_articles_sections}}，系统会自动按菜单分栏渲染文章列表
- 可以在主体区两侧加侧边栏（推荐文章、热门标签等），但不是必须
- 底部包含 {{cross_site_articles}} 和 {{friend_links}}（注意：{{cross_site_articles}} 只在首页主体出现一次，页脚中禁止重复放置）
- 不需要 services_*、feature_*、testimonial_*、cta_* 等营销型变量
- 参考风格：新闻门户、博客首页、CMS资讯站，以内容展示为主",

    'article' => "生成【文章详情页模板】（article.html）——完整HTML页面：
- 必须包含完整的 <!DOCTYPE html> 结构
- <body> 开头放 {{header}}（建议用 <div class=\"header-dark\">{{header}}</div> 包裹实现深色导航）
- <body> 结尾放 {{footer}}
- <style> 中只写文章页独有的布局CSS（如两栏布局、文章卡片容器、侧边栏等）
- 不要在 <style> 中重复写系统类名样式（.article-card/.random-article-link/.header-dark），这些已在 style.css 中
- 布局：左侧主内容区 + 右侧侧边栏
- 主内容区顺序：{{breadcrumb}} → <h1>{{title}}</h1> → {{article_date}} → {{article_ad_top}} → {{content}} → {{article_ad_middle}} → {{keyword_links}} → {{article_ad_bottom}}
- 禁止使用 {{cover_image}} 变量！封面图已包含在 {{content}} 正文HTML中，单独输出会导致封面图重复显示
- 每个广告位变量只输出一次，用div包裹即可（变量本身是完整HTML）
- 侧边栏：<h3>相关文章</h3>{{related_articles}} + <h3>随机推荐</h3>{{random_articles}} + {{article_ad_sidebar}}
- 不要使用 {{#var}}...{{/var}} 条件语法，系统不支持",

    'archive' => "生成【栏目列表页模板】（archive.html）——完整HTML页面：
- 必须包含完整的 <!DOCTYPE html> 结构
- <body> 开头放 {{header}}（建议用 <div class=\"header-dark\">{{header}}</div> 包裹）
- <body> 结尾放 {{footer}}
- <style> 中只写栏目页独有的布局CSS（两栏布局、面包屑、侧边栏样式等）
- 不要在 <style> 中重复写系统类名样式（.article-list-card/.random-article-link/.header-dark），这些已在 style.css 中
- 布局：左侧主内容区 + 右侧侧边栏
- 主内容区：{{breadcrumb}} → {{archive_content}}
- 重要：{{archive_content}} 已包含文章卡片+分页，使用后不要再用 {{archive_cards}} 或 {{archive_pagination}}，否则内容会重复
- 侧边栏：<h3>侧边栏标题</h3>{{archive_sidebar}}（必须手动加h3标题，因为系统输出不含标题）
- 可在侧边栏添加 {{cross_site_articles}} 和 {{friend_links}}",

    'css' => "生成【共享CSS样式表】（style.css）——这是全局共享CSS，会自动注入到每个页面的<head>中：
- 只输出纯CSS代码，不要HTML
- 这是所有页面共享的唯一CSS文件，内容会被系统自动注入到homepage/article/archive的<head>中
- 因此必须包含：CSS Reset/Normalize、:root变量定义、body基础样式、通用工具类
- 自定义class前缀用 .{$cssPrefix}-
" . ($packType === 'variation'
    ? "- 【变体模板】:root 变量必须使用 var(--v-xxx, 默认值) 写法，例如：
  :root { --primary-color: var(--v-primary, #3b82f6); --primary-dark: var(--v-primary-dark, #2563eb); --primary-light: var(--v-primary-light, #dbeafe); --accent-color: var(--v-accent, #8b5cf6); --bg-subtle: var(--v-bg-subtle, #f8fafc); --dark-bg: var(--v-dark-bg, #0a1628); --radius-sm: var(--v-radius-sm, 0.375rem); --radius-md: var(--v-radius-md, 0.5rem); --radius-lg: var(--v-radius-lg, 0.75rem); --radius-xl: var(--v-radius-xl, 1rem); }
  CSS中所有主色引用必须用变量，禁止硬编码HEX/RGB。font-family禁止!important。
"
    : "- 【常规模板】:root 变量直接写死具体色值，不需要 var(--v-xxx) 语法，例如：
  :root { --primary: #2563eb; --primary-dark: #1d4ed8; --primary-light: #dbeafe; --accent: #f59e0b; --bg-subtle: #f8fafc; --bg-dark: #0f172a; }
  变量名可自由命名，追求模板自身独特配色方案。
") . "- 必须包含以下系统类名的完整样式（不加自定义前缀，因为这些是系统固定输出的HTML类名）：
  * .article-list-card / .article-list-card-link（text-decoration:none !important; color:inherit !important;）/ .article-list-thumb / .article-list-thumb img / .article-list-time / .article-list-body / .article-list-title / .article-list-desc
  * .article-card（display:inline-block !important; text-decoration:none !important;）/ .article-card-thumb / .article-card-thumb img / .article-card-title
  * .random-article-link（display:flex !important; text-decoration:none !important;）/ .random-article-index / .random-article-text
  * .header-dark — 只做 padding-top 占位撑高（不加背景色！因为内部header是fixed定位脱离文档流，加背景色会导致空白绿条）
  * .header-dark .{$cssPrefix}-header — 给固定定位的header设置深色背景
  * .header-dark .menu-nav a — 白色文字
  * @media(max-width:900px) 调整 .header-dark 的 padding-top
- 包含：响应式断点、过渡动画、暗色模式媒体查询（可选）、打印样式（可选）
- 重点：各页面的 <style> 不会再重复这些内容，所以 style.css 必须完整覆盖所有共享样式",

    'js' => "生成【共享JS脚本】（script.js）——全局共享，系统会自动注入到每个页面的</body>前：
- 只输出纯JS代码
- 整个脚本用IIFE包裹：(function(){ 'use strict'; ... })();
- 包含共享功能：滚动渐显动画（Intersection Observer）、平滑锚点滚动、回到顶部按钮逻辑等
- 严格WAF安全：禁止 class/constructor、禁止模板字面量、禁止 window.location.href 赋值、禁止展开运算符
- 用 var 声明变量，用 function 关键字定义函数，用字符串拼接替代模板字面量
- 页面级JS（如header的汉堡菜单）可以写在header.html的内联<script>中，但通用功能必须放这里"
);

$guide = isset($fileGuides[$fileKey]) ? $fileGuides[$fileKey] : '';

// Variable list for user message
$varListStr = '';
if (!empty($vars)) {
    foreach ($vars as $var => $label) {
        $varListStr .= "  " . $var . " — " . $label . "\n";
    }
}

// Build user message
$userMessage = "";

// Previous context from earlier steps
if ($prevContext !== '') {
    $userMessage .= "## 已生成的文件摘要（请保持风格一致）\n\n" . $prevContext . "\n\n";
}

$userMessage .= "## 当前任务\n\n" . $guide . "\n\n";

if ($varListStr !== '') {
    $userMessage .= "## 当前文件可用的模板变量（必须使用）\n\n" . $varListStr . "\n";
}

if ($instruction !== '') {
    $userMessage .= "## 用户的整体风格要求\n\n" . $instruction . "\n\n";
}

$userMessage .= "请立即输出代码，不要任何解释。";

// ---------- Call AI ----------

$requestConfig = array();
if (isset($model['request_config']) && trim((string)$model['request_config']) !== '') {
    $decoded = json_decode($model['request_config'], true);
    if (is_array($decoded)) {
        $requestConfig = $decoded;
    }
}

$modelName = isset($requestConfig['model']) ? $requestConfig['model'] : (isset($model['model_name']) ? $model['model_name'] : '');

// 收集额外请求头
$extraHeaders = array();
if (isset($model['request_headers']) && trim((string)$model['request_headers']) !== '') {
    $parsedHeaders = json_decode($model['request_headers'], true);
    if (is_array($parsedHeaders)) {
        foreach ($parsedHeaders as $hk => $hv) {
            $extraHeaders[] = $hk . ': ' . $hv;
        }
    }
}

// 使用带自动续写的AI调用（自动检测模板完整性，截断时自动续写最多3次）
$aiResult = call_ai_with_continuation(
    $apiUrl, $apiKey, $extraHeaders, $modelName,
    $systemPrompt, $userMessage, $fileKey,
    0, // 0 = 使用 get_file_max_tokens() 默认值
    0.7
);

if (!$aiResult['success']) {
    echo json_encode(array('success' => false, 'error' => $aiResult['error']));
    exit;
}

$assistantText = $aiResult['content'];
$continuations = isset($aiResult['continuations']) ? (int)$aiResult['continuations'] : 0;
$aiWarning = isset($aiResult['warning']) ? $aiResult['warning'] : '';

if ($assistantText === '') {
    echo json_encode(array('success' => false, 'error' => 'AI未返回有效内容。'));
    exit;
}

// ---------- Auto-save ----------

$saved = false;
if ($autoSave && $assistantText !== '') {
    try {
        save_template_pack_file($packCode, $fileKey, $assistantText);
        $saved = true;
    } catch (Exception $e) {
        // Don't fail the whole step, just report save failed
    }
}

// ---------- Build context summary for next step ----------

$summaryLines = array();
$summaryLines[] = "【" . $fileLabel . "】已生成";

// Extract CSS classes used
if (preg_match_all('/class="([^"]+)"/', $assistantText, $cm)) {
    $classes = array();
    foreach ($cm[1] as $cls) {
        foreach (explode(' ', $cls) as $c) {
            $c = trim($c);
            if ($c !== '' && strpos($c, $cssPrefix) === 0) {
                $classes[$c] = true;
            }
        }
    }
    if (!empty($classes)) {
        $top = array_slice(array_keys($classes), 0, 15);
        $summaryLines[] = "使用的CSS类名: " . implode(', ', $top);
    }
}

// Extract variables used
if (preg_match_all('/\{\{([a-z_]+)\}\}/', $assistantText, $vm)) {
    $usedVars = array_unique($vm[0]);
    if (!empty($usedVars)) {
        $summaryLines[] = "使用的变量: " . implode(', ', array_slice($usedVars, 0, 20));
    }
}

// Color scheme hint
if (preg_match_all('/#([0-9a-fA-F]{3,8})/', $assistantText, $colorMatches)) {
    $colors = array_unique($colorMatches[0]);
    if (!empty($colors)) {
        $summaryLines[] = "配色: " . implode(', ', array_slice($colors, 0, 8));
    }
}

$summary = implode("\n", $summaryLines);

$response = array(
    'success'       => true,
    'html'          => $assistantText,
    'file_key'      => $fileKey,
    'label'         => $fileLabel,
    'saved'         => $saved,
    'summary'       => $summary,
    'model'         => $modelName,
    'continuations' => $continuations
);
if ($aiWarning !== '') {
    $response['warning'] = $aiWarning;
}
$jsonOutput = json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
if ($jsonOutput === false) {
    $response['html'] = mb_convert_encoding($assistantText, 'UTF-8', 'UTF-8');
    $response['summary'] = mb_convert_encoding($summary, 'UTF-8', 'UTF-8');
    $jsonOutput = json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
    if ($jsonOutput === false) {
        echo json_encode(array('success' => false, 'error' => 'JSON编码失败: ' . json_last_error_msg()));
        exit;
    }
}
echo $jsonOutput;
