import path from 'node:path';
import serialize from 'serialize-javascript';
import type { Gez } from './gez';

/**
 * RenderContext 的配置选项接口
 *
 * @description
 * RenderContextOptions 用于配置 RenderContext 实例的行为，包括基础路径、入口名称、参数和导入映射模式等。
 *
 * @example
 * ```ts
 * // 1. 基础路径配置示例
 * // 支持将静态资源部署到不同的路径下
 * const rc = await gez.render({
 *   // 设置基础路径为 /gez，所有静态资源都会基于此路径加载
 *   base: '/gez',
 *   // 其他配置...
 * });
 *
 * // 2. 多语言站点部署示例
 * // 通过不同的基础路径支持多语言站点
 * const rc = await gez.render({
 *   base: '/cn',  // 中文站点
 *   params: { lang: 'zh-CN' }
 * });
 *
 * // 3. 导入映射模式配置示例
 * const rc = await gez.render({
 *   // 使用内联模式，适合小型应用
 *   importmapMode: 'inline',
 *   // 其他配置...
 * });
 * ```
 */
export interface RenderContextOptions {
    /**
     * 静态资源的基础路径
     * @description
     * - 默认为空字符串
     * - 所有静态资源（JS、CSS、图片等）都会基于此路径加载
     * - 支持运行时动态配置，无需重新构建
     * - 常用于多语言站点、微前端应用等场景
     */
    base?: string;

    /**
     * 服务端渲染入口名称
     * @description
     * - 默认为 'default'
     * - 用于指定服务端渲染时使用的入口函数
     * - 当一个模块导出多个渲染函数时使用
     */
    entryName?: string;

    /**
     * 渲染参数
     * @description
     * - 可以传递任意类型的参数给渲染函数
     * - 常用于传递请求信息（URL、query 参数等）
     * - 在服务端渲染过程中可以通过 rc.params 访问
     */
    params?: Record<string, any>;

    /**
     * 定义 importmap 的生成模式
     *
     * @description
     * ImportmapMode 用于控制 importmap 的生成方式，支持两种模式：
     * - `inline`: 将 importmap 内容直接内联到 HTML 中（默认值），适用于以下场景：
     *   - 需要减少 HTTP 请求数量
     *   - importmap 内容较小
     *   - 对首屏加载性能要求较高
     * - `js`: 将 importmap 内容生成为独立的 JS 文件，适用于以下场景：
     *   - importmap 内容较大
     *   - 需要利用浏览器缓存机制
     *   - 多个页面共享相同的 importmap
     *
     * 默认值选择 'inline' 的原因：
     * 1. 简单直接
     *    - 减少额外的 HTTP 请求
     *    - 无需额外的资源管理
     *    - 适合大多数应用场景
     * 2. 首屏性能
     *    - 避免额外的网络请求
     *    - 确保导入映射立即可用
     *    - 减少页面加载时间
     * 3. 易于调试
     *    - 导入映射直接可见
     *    - 便于问题诊断
     *    - 简化开发流程
     *
     * @example
     * ```ts
     * // 使用内联模式（默认）
     * const rc = await gez.render({
     *   params: { url: req.url }
     * });
     *
     * // 显式指定内联模式
     * const rc = await gez.render({
     *   importmapMode: 'inline',
     *   params: { url: req.url }
     * });
     *
     * // 使用 JS 文件模式
     * const rc = await gez.render({
     *   importmapMode: 'js',
     *   params: { url: req.url }
     * });
     * ```
     */
    importmapMode?: ImportmapMode;
}

/**
 * 服务端渲染函数
 */
export type ServerRenderHandle = (rc: RenderContext) => Promise<void>;

/**
 * 渲染资源文件列表接口
 * @description
 * RenderFiles 接口定义了服务端渲染过程中收集的各类静态资源：
 *
 * 1. **资源类型**
 *    - css: 样式表文件列表
 *    - modulepreload: 需要预加载的 ESM 模块列表
 *    - js: JavaScript 文件列表
 *    - resources: 其他资源文件列表
 *
 * 2. **使用场景**
 *    - 在 commit() 方法中自动收集
 *    - 通过 preload()、css() 等方法注入
 *    - 支持基础路径配置
 *
 * @example
 * ```ts
 * // 1. 资源收集
 * await rc.commit();
 *
 * // 2. 资源注入
 * rc.html = `
 *   <!DOCTYPE html>
 *   <html>
 *   <head>
 *     <!-- 预加载资源 -->
 *     ${rc.preload()}
 *     <!-- 注入样式表 -->
 *     ${rc.css()}
 *   </head>
 *   <body>
 *     ${html}
 *     <!-- 注入导入映射 -->
 *     ${rc.importmap()}
 *     <!-- 注入客户端入口 -->
 *     ${rc.moduleEntry()}
 *     <!-- 预加载模块 -->
 *     ${rc.modulePreload()}
 *   </body>
 *   </html>
 * `;
 * ```
 */
export interface RenderFiles {
    /**
     * JavaScript 文件列表
     */
    js: string[];
    /**
     * CSS 文件列表
     */
    css: string[];
    /**
     * 需要预加载的 ESM 模块列表
     */
    modulepreload: string[];
    /**
     * 其他资源文件列表（图片、字体等）
     */
    resources: string[];
}

/**
 * 定义 importmap 的生成模式
 *
 * @description
 * ImportmapMode 用于控制 importmap 的生成方式，支持两种模式：
 * - `inline`: 将 importmap 内容直接内联到 HTML 中（默认值），适用于以下场景：
 *   - 需要减少 HTTP 请求数量
 *   - importmap 内容较小
 *   - 对首屏加载性能要求较高
 * - `js`: 将 importmap 内容生成为独立的 JS 文件，适用于以下场景：
 *   - importmap 内容较大
 *   - 需要利用浏览器缓存机制
 *   - 多个页面共享相同的 importmap
 *
 * 默认值选择 'inline' 的原因：
 * 1. 简单直接
 *    - 减少额外的 HTTP 请求
 *    - 无需额外的资源管理
 *    - 适合大多数应用场景
 * 2. 首屏性能
 *    - 避免额外的网络请求
 *    - 确保导入映射立即可用
 *    - 减少页面加载时间
 * 3. 易于调试
 *    - 导入映射直接可见
 *    - 便于问题诊断
 *    - 简化开发流程
 *
 * @example
 * ```ts
 * // 使用内联模式（默认）
 * const rc = await gez.render({
 *   params: { url: req.url }
 * });
 *
 * // 显式指定内联模式
 * const rc = await gez.render({
 *   importmapMode: 'inline',
 *   params: { url: req.url }
 * });
 *
 * // 使用 JS 文件模式
 * const rc = await gez.render({
 *   importmapMode: 'js',
 *   params: { url: req.url }
 * });
 * ```
 */
export type ImportmapMode = 'inline' | 'js';

/**
 * RenderContext 是 Gez 框架中的核心类，负责服务端渲染（SSR）过程中的资源管理和 HTML 生成
 *
 * @description
 * RenderContext 具有以下核心特点：
 * 1. **基于 ESM 的模块系统**
 *    - 采用现代的 ECMAScript Modules 标准
 *    - 支持原生的模块导入导出
 *    - 实现了更好的代码分割和按需加载
 *
 * 2. **智能依赖收集**
 *    - 基于实际渲染路径动态收集依赖
 *    - 避免不必要的资源加载
 *    - 支持异步组件和动态导入
 *
 * 3. **精确的资源注入**
 *    - 严格控制资源加载顺序
 *    - 优化首屏加载性能
 *    - 确保客户端激活（Hydration）的可靠性
 *
 * 4. **灵活的配置机制**
 *    - 支持动态基础路径配置
 *    - 提供多种导入映射模式
 *    - 适应不同的部署场景
 *
 * @example
 * ```ts
 * export default async (rc: RenderContext) => {
 *     // 1. 渲染页面内容并收集依赖
 *     const app = createApp();
 *     const html = await renderToString(app, {
 *         importMetaSet: rc.importMetaSet
 *     });
 *
 *     // 2. 提交依赖收集
 *     await rc.commit();
 *
 *     // 3. 生成完整 HTML
 *     rc.html = `
 *         <!DOCTYPE html>
 *         <html>
 *         <head>
 *             <!-- 预加载 CSS 和 JS 资源，提前开始加载以优化性能 -->
 *             ${rc.preload()}
 *             <!-- 注入首屏样式表，避免页面闪烁 -->
 *             ${rc.css()}
 *         </head>
 *         <body>
 *             ${html}
 *             <!-- 注入模块导入映射，定义 ESM 模块的路径解析规则 -->
 *             ${rc.importmap()}
 *             <!-- 注入客户端入口模块，必须在 importmap 之后执行 -->
 *             ${rc.moduleEntry()}
 *             <!-- 预加载模块依赖，基于实际渲染收集的依赖进行优化加载 -->
 *             ${rc.modulePreload()}
 *         </body>
 *         </html>
 *     `;
 * };
 * ```
 */
export class RenderContext {
    public gez: Gez;
    /**
     * 重定向地址
     * @description
     * - 默认为 null，表示不进行重定向
     * - 设置后，服务端可以根据此值进行 HTTP 重定向
     * - 常用于登录验证、权限控制等场景
     *
     * @example
     * ```ts
     * // 1. 登录验证示例
     * export default async (rc: RenderContext) => {
     *   if (!isLoggedIn()) {
     *     rc.redirect = '/login';
     *     rc.status = 302;
     *     return;
     *   }
     *   // 继续渲染页面...
     * };
     *
     * // 2. 权限控制示例
     * export default async (rc: RenderContext) => {
     *   if (!hasPermission()) {
     *     rc.redirect = '/403';
     *     rc.status = 403;
     *     return;
     *   }
     *   // 继续渲染页面...
     * };
     *
     * // 3. 服务端处理示例
     * app.use(async (req, res) => {
     *   const rc = await gez.render({
     *     params: {
     *       url: req.url
     *     }
     *   });
     *
     *   // 处理重定向
     *   if (rc.redirect) {
     *     res.statusCode = rc.status || 302;
     *     res.setHeader('Location', rc.redirect);
     *     res.end();
     *     return;
     *   }
     *
     *   // 设置状态码
     *   if (rc.status) {
     *     res.statusCode = rc.status;
     *   }
     *
     *   // 响应 HTML 内容
     *   res.end(rc.html);
     * });
     * ```
     */
    public redirect: string | null = null;

    /**
     * HTTP 响应状态码
     * @description
     * - 默认为 null，表示使用 200 状态码
     * - 可以设置任意有效的 HTTP 状态码
     * - 常用于错误处理、重定向等场景
     * - 通常与 redirect 属性配合使用
     *
     * @example
     * ```ts
     * // 1. 404 错误处理示例
     * export default async (rc: RenderContext) => {
     *   const page = await findPage(rc.params.url);
     *   if (!page) {
     *     rc.status = 404;
     *     // 渲染 404 页面...
     *     return;
     *   }
     *   // 继续渲染页面...
     * };
     *
     * // 2. 临时重定向示例
     * export default async (rc: RenderContext) => {
     *   if (needMaintenance()) {
     *     rc.redirect = '/maintenance';
     *     rc.status = 307; // 临时重定向，保持请求方法不变
     *     return;
     *   }
     *   // 继续渲染页面...
     * };
     *
     * // 3. 服务端处理示例
     * app.use(async (req, res) => {
     *   const rc = await gez.render({
     *     params: {
     *       url: req.url
     *     }
     *   });
     *
     *   // 处理重定向
     *   if (rc.redirect) {
     *     res.statusCode = rc.status || 302;
     *     res.setHeader('Location', rc.redirect);
     *     res.end();
     *     return;
     *   }
     *
     *   // 设置状态码
     *   if (rc.status) {
     *     res.statusCode = rc.status;
     *   }
     *
     *   // 响应 HTML 内容
     *   res.end(rc.html);
     * });
     * ```
     */
    public status: number | null = null;
    private _html = '';
    /**
     * 静态资源的基础路径
     * @description
     * base 属性用于控制静态资源的加载路径，是 Gez 框架动态基础路径配置的核心：
     *
     * 1. **构建时处理**
     *    - 静态资源路径使用特殊占位符标记：`[[[___GEZ_DYNAMIC_BASE___]]]/your-app-name/`
     *    - 占位符会被注入到所有静态资源的引用路径中
     *    - 支持 CSS、JavaScript、图片等各类静态资源
     *
     * 2. **运行时替换**
     *    - 通过 `gez.render()` 的 `base` 参数设置实际基础路径
     *    - RenderContext 自动将 HTML 中的占位符替换为实际路径
     *
     * 3. **技术优势**
     *    - 部署灵活：同一套构建产物可部署到任意路径
     *    - 性能优化：保持静态资源的最佳缓存策略
     *    - 开发友好：简化多环境配置管理
     *
     * @example
     * ```ts
     * // 1. 基础用法
     * const rc = await gez.render({
     *   base: '/gez',  // 设置基础路径
     *   params: { url: req.url }
     * });
     *
     * // 2. 多语言站点示例
     * const rc = await gez.render({
     *   base: '/cn',  // 中文站点
     *   params: { lang: 'zh-CN' }
     * });
     *
     * // 3. 微前端应用示例
     * const rc = await gez.render({
     *   base: '/app1',  // 子应用1
     *   params: { appId: 1 }
     * });
     * ```
     */
    public readonly base: string;
    /**
     * 服务端渲染入口函数名称
     * @description
     * entryName 属性用于指定服务端渲染时使用的入口函数：
     *
     * 1. **基本用途**
     *    - 默认值为 'default'
     *    - 用于从 entry.server.ts 中选择要使用的渲染函数
     *    - 支持一个模块导出多个渲染函数的场景
     *
     * 2. **使用场景**
     *    - 多模板渲染：不同页面使用不同的渲染模板
     *    - A/B 测试：同一页面使用不同的渲染逻辑
     *    - 特殊渲染：某些页面需要自定义的渲染流程
     *
     * @example
     * ```ts
     * // 1. 默认入口函数
     * // entry.server.ts
     * export default async (rc: RenderContext) => {
     *   // 默认渲染逻辑
     * };
     *
     * // 2. 多个入口函数
     * // entry.server.ts
     * export const mobile = async (rc: RenderContext) => {
     *   // 移动端渲染逻辑
     * };
     *
     * export const desktop = async (rc: RenderContext) => {
     *   // 桌面端渲染逻辑
     * };
     *
     * // 3. 根据设备类型选择入口函数
     * const rc = await gez.render({
     *   entryName: isMobile ? 'mobile' : 'desktop',
     *   params: { url: req.url }
     * });
     * ```
     */
    public readonly entryName: string;

    /**
     * 渲染参数
     * @description
     * params 属性用于在服务端渲染过程中传递和访问参数：
     *
     * 1. **参数类型**
     *    - 支持任意类型的键值对
     *    - 通过 Record<string, any> 类型定义
     *    - 在整个渲染生命周期中保持不变
     *
     * 2. **常见使用场景**
     *    - 传递请求信息（URL、query 参数等）
     *    - 设置页面配置（语言、主题等）
     *    - 注入环境变量（API 地址、版本号等）
     *    - 共享服务端状态（用户信息、权限等）
     *
     * 3. **访问方式**
     *    - 在服务端渲染函数中通过 rc.params 访问
     *    - 可以解构获取特定参数
     *    - 支持设置默认值
     *
     * @example
     * ```ts
     * // 1. 基础用法 - 传递 URL 和语言设置
     * const rc = await gez.render({
     *   params: {
     *     url: req.url,
     *     lang: 'zh-CN'
     *   }
     * });
     *
     * // 2. 页面配置 - 设置主题和布局
     * const rc = await gez.render({
     *   params: {
     *     theme: 'dark',
     *     layout: 'sidebar'
     *   }
     * });
     *
     * // 3. 环境配置 - 注入 API 地址
     * const rc = await gez.render({
     *   params: {
     *     apiBaseUrl: process.env.API_BASE_URL,
     *     version: '1.0.0'
     *   }
     * });
     *
     * // 4. 在渲染函数中使用
     * export default async (rc: RenderContext) => {
     *   // 解构获取参数
     *   const { url, lang = 'en' } = rc.params;
     *
     *   // 根据参数执行不同逻辑
     *   if (lang === 'zh-CN') {
     *     // 中文版本处理...
     *   }
     *
     *   // 传递参数到组件
     *   const html = await renderToString(createApp({
     *     props: {
     *       currentUrl: url,
     *       language: lang
     *     }
     *   }));
     *
     *   // 设置 HTML
     *   rc.html = `
     *     <!DOCTYPE html>
     *     <html lang="${lang}">
     *       <body>${html}</body>
     *     </html>
     *   `;
     * };
     * ```
     */
    public readonly params: Record<string, any>;
    /**
     * 模块依赖收集集合
     * @description
     * importMetaSet 是 Gez 框架智能依赖收集机制的核心，用于在服务端渲染过程中追踪和记录模块依赖：
     *
     * 1. **按需收集**
     *    - 在组件实际渲染过程中自动追踪和记录模块依赖
     *    - 只收集当前页面渲染时真正使用到的资源
     *    - 精确记录每个组件的模块依赖关系
     *
     * 2. **性能优化**
     *    - 避免加载未使用的模块，显著减少首屏加载时间
     *    - 精确控制资源加载顺序，优化页面渲染性能
     *    - 自动生成最优的导入映射（Import Map）
     *
     * 3. **使用方式**
     *    - 在渲染函数中传递给 renderToString
     *    - 框架自动收集依赖，无需手动处理
     *    - 支持异步组件和动态导入的依赖收集
     *
     * @example
     * ```ts
     * // 1. 基础用法
     * const renderToString = (app: any, context: { importMetaSet: Set<ImportMeta> }) => {
     *   // 在渲染过程中自动收集模块依赖
     *   // 框架会在组件渲染时自动调用 context.importMetaSet.add(import.meta)
     *   // 开发者无需手动处理依赖收集
     *   return '<div id="app">Hello World</div>';
     * };
     *
     * // 使用示例
     * const app = createApp();
     * const html = await renderToString(app, {
     *   importMetaSet: rc.importMetaSet
     * });
     *
     * // 2. 提交依赖
     * await rc.commit();
     *
     * // 3. 生成 HTML
     * rc.html = `
     *   <!DOCTYPE html>
     *   <html>
     *   <head>
     *     <!-- 基于收集的依赖自动注入资源 -->
     *     ${rc.preload()}
     *     ${rc.css()}
     *   </head>
     *   <body>
     *     ${html}
     *     ${rc.importmap()}
     *     ${rc.moduleEntry()}
     *     ${rc.modulePreload()}
     *   </body>
     *   </html>
     * `;
     * ```
     */
    public importMetaSet = new Set<ImportMeta>();
    /**
     * 资源文件列表
     * @description
     * files 属性存储了在服务端渲染过程中收集到的所有静态资源文件路径：
     *
     * 1. **资源类型**
     *    - js: JavaScript 文件列表，包含所有脚本和模块
     *    - css: 样式表文件列表
     *    - modulepreload: 需要预加载的 ESM 模块列表
     *    - importmap: 导入映射文件列表
     *    - resources: 其他资源文件列表（图片、字体等）
     *
     * 2. **使用场景**
     *    - 在 commit() 方法中自动收集和分类资源
     *    - 通过 preload()、css() 等方法注入资源到 HTML
     *    - 支持基础路径配置，实现资源的动态加载
     *
     * @example
     * ```ts
     * // 1. 资源收集
     * await rc.commit();
     *
     * // 2. 资源注入
     * rc.html = `
     *   <!DOCTYPE html>
     *   <html>
     *   <head>
     *     <!-- 预加载资源 -->
     *     ${rc.preload()}
     *     <!-- 注入样式表 -->
     *     ${rc.css()}
     *   </head>
     *   <body>
     *     ${html}
     *     ${rc.importmap()}
     *     ${rc.moduleEntry()}
     *     ${rc.modulePreload()}
     *   </body>
     *   </html>
     * `;
     * ```
     */
    public files: RenderFiles = {
        js: [],
        css: [],
        modulepreload: [],
        resources: []
    };
    private _importMap: { src: string | null; code: string } = {
        src: '',
        code: ''
    };
    /**
     * 定义 importmap 的生成模式
     *
     * @description
     * ImportmapMode 用于控制 importmap 的生成方式，支持两种模式：
     * - `inline`: 将 importmap 内容直接内联到 HTML 中（默认值），适用于以下场景：
     *   - 需要减少 HTTP 请求数量
     *   - importmap 内容较小
     *   - 对首屏加载性能要求较高
     * - `js`: 将 importmap 内容生成为独立的 JS 文件，适用于以下场景：
     *   - importmap 内容较大
     *   - 需要利用浏览器缓存机制
     *   - 多个页面共享相同的 importmap
     *
     * 默认值选择 'inline' 的原因：
     * 1. 简单直接
     *    - 减少额外的 HTTP 请求
     *    - 无需额外的资源管理
     *    - 适合大多数应用场景
     * 2. 首屏性能
     *    - 避免额外的网络请求
     *    - 确保导入映射立即可用
     *    - 减少页面加载时间
     * 3. 易于调试
     *    - 导入映射直接可见
     *    - 便于问题诊断
     *    - 简化开发流程
     *
     * @example
     * ```ts
     * // 使用内联模式（默认）
     * const rc = await gez.render({
     *   params: { url: req.url }
     * });
     *
     * // 显式指定内联模式
     * const rc = await gez.render({
     *   importmapMode: 'inline',
     *   params: { url: req.url }
     * });
     *
     * // 使用 JS 文件模式
     * const rc = await gez.render({
     *   importmapMode: 'js',
     *   params: { url: req.url }
     * });
     * ```
     */
    public importmapMode: ImportmapMode;
    /**
     * HTML 内容
     * @description
     * html 属性用于设置和获取最终生成的 HTML 内容：
     *
     * 1. **基础路径替换**
     *    - 在设置 HTML 时自动处理基础路径占位符
     *    - 将 `[[[___GEZ_DYNAMIC_BASE___]]]/your-app-name/` 替换为实际的 base 路径
     *    - 确保所有静态资源的引用路径正确
     *
     * 2. **使用场景**
     *    - 设置服务端渲染生成的 HTML 内容
     *    - 支持动态基础路径配置
     *    - 自动处理静态资源的引用路径
     *
     * @example
     * ```ts
     * // 1. 基础用法
     * export default async (rc: RenderContext) => {
     *   // 设置 HTML 内容
     *   rc.html = `
     *     <!DOCTYPE html>
     *     <html>
     *       <head>
     *         ${rc.preload()}
     *         ${rc.css()}
     *       </head>
     *       <body>
     *         <div id="app">Hello World</div>
     *         ${rc.importmap()}
     *         ${rc.moduleEntry()}
     *         ${rc.modulePreload()}
     *       </body>
     *     </html>
     *   `;
     * };
     *
     * // 2. 动态基础路径
     * const rc = await gez.render({
     *   base: '/app',  // 设置基础路径
     *   params: { url: req.url }
     * });
     *
     * // HTML 中的占位符会被自动替换：
     * // [[[___GEZ_DYNAMIC_BASE___]]]/your-app-name/css/style.css
     * // 替换为：
     * // /app/your-app-name/css/style.css
     * ```
     */
    public get html() {
        return this._html;
    }
    public set html(html) {
        const varName = this.gez.basePathPlaceholder;
        this._html = varName
            ? html.replaceAll(this.gez.basePathPlaceholder, this.base)
            : html;
    }
    public constructor(gez: Gez, options: RenderContextOptions = {}) {
        this.gez = gez;
        this.base = options.base ?? '';
        this.params = options.params ?? {};
        this.entryName = options.entryName ?? 'default';
        this.importmapMode = options.importmapMode ?? 'inline';
    }
    /**
     * 将 JavaScript 对象序列化为字符串
     * @description
     * serialize 方法用于在服务端渲染过程中将状态数据序列化，以便传递到客户端：
     *
     * 1. **主要用途**
     *    - 序列化服务端状态数据
     *    - 确保数据可以安全地嵌入到 HTML 中
     *    - 支持复杂的数据结构（如 Date、RegExp 等）
     *
     * 2. **安全处理**
     *    - 自动转义特殊字符
     *    - 防止 XSS 攻击
     *    - 保持数据类型的完整性
     *
     * @example
     * ```ts
     * // 1. 基础用法 - 序列化状态数据
     * export default async (rc: RenderContext) => {
     *   const state = {
     *     user: { id: 1, name: 'Alice' },
     *     timestamp: new Date(),
     *     regex: /\d+/
     *   };
     *
     *   rc.html = `
     *     <!DOCTYPE html>
     *     <html>
     *     <head>
     *       <script>
     *         // 将序列化后的状态注入到全局变量
     *         window.__INITIAL_STATE__ = ${rc.serialize(state)};
     *       </script>
     *     </head>
     *     <body>${html}</body>
     *     </html>
     *   `;
     * };
     *
     * // 2. 自定义序列化选项
     * const state = { sensitive: 'data' };
     * const serialized = rc.serialize(state, {
     *   isJSON: true,  // 使用 JSON 兼容模式
     *   unsafe: false  // 禁用不安全的序列化
     * });
     * ```
     *
     * @param {any} input - 要序列化的输入数据
     * @param {serialize.SerializeJSOptions} [options] - 序列化选项
     * @returns {string} 序列化后的字符串
     */
    public serialize(
        input: any,
        options?: serialize.SerializeJSOptions
    ): string {
        return serialize(input, options);
    }
    /**
     * 将状态数据序列化并注入到 HTML 中
     * @description
     * state 方法用于在服务端渲染时将状态数据序列化并注入到 HTML 中，以便客户端可以在激活时恢复这些状态：
     *
     * 1. **序列化机制**
     *    - 使用安全的序列化方法处理数据
     *    - 支持复杂的数据结构（对象、数组等）
     *    - 自动处理特殊字符和 XSS 防护
     *
     * 2. **使用场景**
     *    - 服务端状态同步到客户端
     *    - 初始化客户端应用状态
     *    - 实现无缝的服务端渲染到客户端激活
     *
     * @param varName 全局变量名，用于在客户端访问注入的数据
     * @param data 需要序列化的数据对象
     * @returns 包含序列化数据的 script 标签字符串
     *
     * @example
     * ```ts
     * // 1. 基础用法 - 注入用户信息
     * export default async (rc: RenderContext) => {
     *   const userInfo = {
     *     id: 1,
     *     name: 'John',
     *     roles: ['admin']
     *   };
     *
     *   rc.html = `
     *     <!DOCTYPE html>
     *     <html>
     *     <head>
     *       ${rc.state('__USER__', userInfo)}
     *     </head>
     *     <body>
     *       <div id="app"></div>
     *     </body>
     *     </html>
     *   `;
     * };
     *
     * // 2. 客户端使用
     * // 在客户端可以直接访问注入的数据
     * const userInfo = window.__USER__;
     * console.log(userInfo.name); // 输出: 'John'
     *
     * // 3. 复杂数据结构
     * export default async (rc: RenderContext) => {
     *   const appState = {
     *     user: {
     *       id: 1,
     *       preferences: {
     *         theme: 'dark',
     *         language: 'zh-CN'
     *       }
     *     },
     *     settings: {
     *       notifications: true,
     *       timezone: 'Asia/Shanghai'
     *     }
     *   };
     *
     *   rc.html = `
     *     <!DOCTYPE html>
     *     <html>
     *     <head>
     *       ${rc.state('__APP_STATE__', appState)}
     *     </head>
     *     <body>
     *       <div id="app"></div>
     *     </body>
     *     </html>
     *   `;
     * };
     * ```
     */
    public state(varName: string, data: Record<string, any>): string {
        return `<script>window[${serialize(varName)}] = ${serialize(data, { isJSON: true })};</script>`;
    }
    /**
     * 提交依赖收集并更新资源列表
     * @description
     * commit 方法是 RenderContext 依赖收集机制的核心，负责处理所有收集到的模块依赖并更新文件资源列表：
     *
     * 1. **依赖处理流程**
     *    - 从 importMetaSet 中收集所有使用到的模块
     *    - 基于 manifest 文件解析每个模块的具体资源
     *    - 处理 JS、CSS、资源文件等不同类型的依赖
     *    - 自动处理模块预加载和导入映射
     *
     * 2. **资源分类**
     *    - js: JavaScript 文件，包含所有脚本和模块
     *    - css: 样式表文件
     *    - modulepreload: 需要预加载的 ESM 模块
     *    - importmap: 导入映射文件
     *    - resources: 其他资源文件（图片、字体等）
     *
     * 3. **路径处理**
     *    - 自动添加基础路径前缀
     *    - 确保资源路径的正确性
     *    - 支持多应用场景的资源隔离
     *
     * @example
     * ```ts
     * // 1. 基础用法
     * export default async (rc: RenderContext) => {
     *   // 渲染页面并收集依赖
     *   const app = createApp();
     *   const html = await renderToString(app, {
     *     importMetaSet: rc.importMetaSet
     *   });
     *
     *   // 提交依赖收集
     *   await rc.commit();
     *
     *   // 生成 HTML
     *   rc.html = `
     *     <!DOCTYPE html>
     *     <html>
     *     <head>
     *       ${rc.preload()}
     *       ${rc.css()}
     *     </head>
     *     <body>
     *       ${html}
     *       ${rc.importmap()}
     *       ${rc.moduleEntry()}
     *       ${rc.modulePreload()}
     *     </body>
     *     </html>
     *   `;
     * };
     *
     * // 2. 多应用场景
     * const rc = await gez.render({
     *   base: '/app1',  // 设置基础路径
     *   params: { appId: 1 }
     * });
     *
     * // 渲染并提交依赖
     * const html = await renderApp(rc);
     * await rc.commit();
     *
     * // 资源路径会自动添加基础路径前缀
     * // 例如：/app1/your-app-name/js/main.js
     * ```
     */
    public async commit() {
        const { gez } = this;
        const chunkSet = new Set([`${gez.name}@src/entry.client.ts`]);
        for (const item of this.importMetaSet) {
            if ('chunkName' in item && typeof item.chunkName === 'string') {
                chunkSet.add(item.chunkName);
            }
        }

        const files: {
            [K in keyof RenderFiles]: Set<string>;
        } = {
            js: new Set(),
            modulepreload: new Set(),
            css: new Set(),
            resources: new Set()
        };

        const getUrlPath = (...paths: string[]) =>
            path.posix.join('/', this.base, ...paths);

        const manifests = await this.gez.getManifestList('client');
        manifests.forEach((item) => {
            const addPath = (setName: keyof RenderFiles, filepath: string) =>
                files[setName].add(getUrlPath(item.name, filepath));
            const addPaths = (
                setName: keyof RenderFiles,
                filepaths: string[]
            ) => filepaths.forEach((filepath) => addPath(setName, filepath));
            Object.entries(item.chunks).forEach(([filepath, info]) => {
                if (chunkSet.has(filepath)) {
                    addPath('js', info.js);
                    addPaths('css', info.css);
                    addPaths('resources', info.resources);
                }
            });
        });

        const paths = await gez.getStaticImportPaths(
            'client',
            `${gez.name}/src/entry.client`
        );
        paths?.forEach((filepath) =>
            files.modulepreload.add(getUrlPath(filepath))
        );

        files.js = new Set([...files.js, ...files.modulepreload]);
        Object.keys(files).forEach(
            (key) => (this.files[key] = Array.from(files[key]))
        );
        this._importMap = await gez.getImportMapClientInfo(this.importmapMode);
    }
    /**
     * 生成资源预加载标签
     * @description
     * preload() 方法用于生成资源预加载标签，通过提前加载关键资源来优化页面性能：
     *
     * 1. **资源类型**
     *    - CSS 文件：使用 `as="style"` 预加载样式表
     *    - JS 文件：使用 `as="script"` 预加载导入映射脚本
     *
     * 2. **性能优化**
     *    - 提前发现并加载关键资源
     *    - 与 HTML 解析并行加载
     *    - 优化资源加载顺序
     *    - 减少页面渲染阻塞
     *
     * 3. **最佳实践**
     *    - 在 head 中尽早使用
     *    - 只预加载当前页面必需的资源
     *    - 与其他资源加载方法配合使用
     *
     * @returns 返回包含所有预加载标签的 HTML 字符串
     *
     * @example
     * ```ts
     * // 在 HTML head 中使用
     * rc.html = `
     *   <!DOCTYPE html>
     *   <html>
     *   <head>
     *     <!-- 预加载关键资源 -->
     *     ${rc.preload()}
     *     <!-- 注入样式表 -->
     *     ${rc.css()}
     *   </head>
     *   <body>
     *     ${html}
     *     ${rc.importmap()}
     *     ${rc.moduleEntry()}
     *     ${rc.modulePreload()}
     *   </body>
     *   </html>
     * `;
     * ```
     */
    public preload() {
        const { files, _importMap } = this;
        const list = files.css.map((url) => {
            return `<link rel="preload" href="${url}" as="style">`;
        });
        if (_importMap.src) {
            list.push(
                `<link rel="preload" href="${_importMap.src}" as="script">`
            );
        }
        return list.join('');
    }
    /**
     * 注入首屏样式表
     * @description
     * css() 方法用于注入页面所需的样式表资源：
     *
     * 1. **注入位置**
     *    - 必须在 head 标签中注入
     *    - 避免页面闪烁（FOUC）和重排
     *    - 确保样式在内容渲染时就位
     *
     * 2. **性能优化**
     *    - 支持关键 CSS 提取
     *    - 自动处理样式依赖关系
     *    - 利用浏览器并行加载能力
     *
     * 3. **使用场景**
     *    - 注入首屏必需的样式
     *    - 处理组件级别的样式
     *    - 支持主题切换和动态样式
     *
     * @example
     * ```ts
     * // 1. 基础用法
     * rc.html = `
     *   <!DOCTYPE html>
     *   <html>
     *   <head>
     *     ${rc.preload()}  <!-- 预加载资源 -->
     *     ${rc.css()}      <!-- 注入样式表 -->
     *   </head>
     *   <body>
     *     <div id="app">Hello World</div>
     *   </body>
     *   </html>
     * `;
     *
     * // 2. 与其他资源配合使用
     * rc.html = `
     *   <!DOCTYPE html>
     *   <html>
     *   <head>
     *     ${rc.preload()}  <!-- 预加载资源 -->
     *     ${rc.css()}      <!-- 注入样式表 -->
     *   </head>
     *   <body>
     *     ${html}
     *     ${rc.importmap()}
     *     ${rc.moduleEntry()}
     *     ${rc.modulePreload()}
     *   </body>
     *   </html>
     * `;
     * ```
     */
    public css() {
        return this.files.css
            .map((url) => `<link rel="stylesheet" href="${url}">`)
            .join('');
    }
    /**
     * 注入模块导入映射
     * @description
     * importmap() 方法用于注入 ESM 模块的路径解析规则：
     *
     * 1. **注入位置**
     *    - 必须在 body 中注入
     *    - 必须在 moduleEntry 之前执行
     *    - 避免阻塞页面首次渲染
     *
     * 2. **导入映射模式**
     *    - 内联模式（inline）：
     *      - 将映射内容直接内联到 HTML 中
     *      - 适合映射内容较小的场景
     *      - 减少 HTTP 请求数量
     *    - JS 文件模式（js）：
     *      - 生成独立的 JS 文件
     *      - 适合映射内容较大的场景
     *      - 可以利用浏览器缓存机制
     *
     * 3. **技术原因**
     *    - 定义了 ESM 模块的路径解析规则
     *    - 客户端入口模块和其依赖都需要使用这些映射
     *    - 确保在执行模块代码前已正确设置映射
     *
     * @example
     * ```ts
     * // 1. 基础用法 - 内联模式
     * const rc = await gez.render({
     *   importmapMode: 'inline'  // 默认模式
     * });
     *
     * rc.html = `
     *   <!DOCTYPE html>
     *   <html>
     *   <head>
     *     ${rc.preload()}
     *     ${rc.css()}
     *   </head>
     *   <body>
     *     ${html}
     *     ${rc.importmap()}    <!-- 注入导入映射 -->
     *     ${rc.moduleEntry()}  <!-- 在导入映射之后执行 -->
     *     ${rc.modulePreload()}
     *   </body>
     *   </html>
     * `;
     *
     * // 2. JS 文件模式 - 适合大型应用
     * const rc = await gez.render({
     *   importmapMode: 'js'  // 使用 JS 文件模式
     * });
     * ```
     */
    public importmap() {
        return this._importMap.code;
    }
    /**
     * 注入客户端入口模块
     * @description
     * moduleEntry() 方法用于注入客户端的入口模块：
     * 1. **注入位置**
     *    - 必须在 importmap 之后执行
     *    - 确保在执行模块代码前已正确设置导入映射
     *    - 控制客户端激活（Hydration）的开始时机
     *
     * 2. **技术原因**
     *    - 作为客户端代码的入口点
     *    - 需要等待基础设施（如导入映射）就绪
     *    - 确保正确的模块路径解析
     *
     * 3. **使用场景**
     *    - 启动客户端应用
     *    - 执行客户端激活
     *    - 初始化客户端状态
     *
     * @example
     * ```ts
     * // 1. 基础用法
     * rc.html = `
     *   <!DOCTYPE html>
     *   <html>
     *   <head>
     *     ${rc.preload()}
     *     ${rc.css()}
     *   </head>
     *   <body>
     *     ${html}
     *     ${rc.importmap()}    <!-- 先注入导入映射 -->
     *     ${rc.moduleEntry()}  <!-- 再注入入口模块 -->
     *     ${rc.modulePreload()}
     *   </body>
     *   </html>
     * `;
     *
     * // 2. 多入口配置
     * const rc = await gez.render({
     *   entryName: 'mobile',  // 指定入口名称
     *   params: { device: 'mobile' }
     * });
     * ```
     */
    public moduleEntry() {
        return `<script type="module">import "${this.gez.name}/src/entry.client";</script>`;
    }

    /**
     * 预加载模块依赖
     * @description
     * modulePreload() 方法用于预加载后续可能用到的模块：
     *
     * 1. **注入位置**
     *    - 必须在 importmap 和 moduleEntry 之后
     *    - 确保使用正确的模块路径映射
     *    - 避免与首屏渲染竞争资源
     *
     * 2. **性能优化**
     *    - 预加载后续可能用到的模块
     *    - 提升运行时性能
     *    - 优化按需加载体验
     *
     * 3. **技术原因**
     *    - 需要正确的路径解析规则
     *    - 避免重复加载
     *    - 控制加载优先级
     *
     * @example
     * ```ts
     * // 1. 基础用法
     * rc.html = `
     *   <!DOCTYPE html>
     *   <html>
     *   <head>
     *     ${rc.preload()}
     *     ${rc.css()}
     *   </head>
     *   <body>
     *     ${html}
     *     ${rc.importmap()}
     *     ${rc.moduleEntry()}
     *     ${rc.modulePreload()}  <!-- 预加载模块依赖 -->
     *   </body>
     *   </html>
     * `;
     *
     * // 2. 与异步组件配合使用
     * const AsyncComponent = defineAsyncComponent(() =>
     *   import('./components/AsyncComponent.vue')
     * );
     * // modulePreload 会自动收集并预加载异步组件的依赖
     * ```
     */
    public modulePreload() {
        return this.files.modulepreload
            .map((url) => `<link rel="modulepreload" href="${url}">`)
            .join('');
    }
}
