import fs from 'fs/promises';
import path from 'path';
import { generateUnifiedReportHTML } from './report-template.js';
import { generateEnhancedReportHTML } from './enhanced-report-template.js';
import { OptimizationAnalyzer } from './optimization-analyzer.js';
import { BuildStateManager, StateToReportConverter } from '../state/index.js';
/**
 * 构建分析器 - 分析构建产物并生成详细报告
 */
export class BuildAnalyzer {
    constructor(buildDir, outputDir) {
        this.buildDir = buildDir;
        this.outputDir = outputDir;
    }
    /**
     * 分析构建产物
     */
    async analyze() {
        console.log('[BuildAnalyzer] 开始分析构建产物...');
        // 尝试从 build-state.json 读取数据
        const stateFilePath = path.join(this.buildDir, 'build-state.json');
        try {
            await fs.access(stateFilePath);
            console.log('[BuildAnalyzer] 发现 build-state.json，使用状态数据分析');
            return await this.analyzeFromState();
        }
        catch (error) {
            console.log('[BuildAnalyzer] 未找到 build-state.json，使用文件系统扫描');
            return await this.analyzeFromFileSystem();
        }
    }
    /**
     * 从 build-state.json 分析
     */
    async analyzeFromState() {
        const stateManager = new BuildStateManager(this.buildDir, this.buildDir);
        const loaded = await stateManager.loadState();
        if (!loaded) {
            console.warn('[BuildAnalyzer] 加载状态失败，回退到文件系统扫描');
            return await this.analyzeFromFileSystem();
        }
        const state = stateManager.getState();
        // 执行优化分析
        console.log('[BuildAnalyzer] 执行优化分析...');
        const optimizationAnalyzer = new OptimizationAnalyzer(state);
        const optimizationAnalysis = optimizationAnalyzer.analyze();
        console.log(`[BuildAnalyzer] 发现 ${optimizationAnalysis.totalSuggestions} 条优化建议`);
        const unifiedData = StateToReportConverter.convertToUnifiedReport(state);
        // 转换为 BuildAnalysisReport 格式
        const files = unifiedData.assets.map(asset => {
            // 计算 data URL 大小
            const dataUrlSize = this.estimateDataUrlSize(asset.finalSize, asset.type);
            return {
                path: asset.id,
                relativePath: asset.finalName,
                size: asset.finalSize,
                sizeFormatted: StateToReportConverter.formatBytes(asset.finalSize),
                dataUrlSize,
                dataUrlSizeFormatted: StateToReportConverter.formatBytes(dataUrlSize),
                type: this.mapAssetTypeToFileType(asset.type),
                category: this.mapAssetTypeToCategory(asset.type, asset.id),
            };
        });
        // 按大小排序
        files.sort((a, b) => b.size - a.size);
        const totalSize = unifiedData.totalFinalSize;
        const estimatedHtmlSize = files.reduce((sum, f) => sum + f.dataUrlSize, 0);
        // 按分类统计
        const byCategory = {};
        const byType = {};
        for (const file of files) {
            // 按分类
            if (!byCategory[file.category]) {
                byCategory[file.category] = { count: 0, size: 0, dataUrlSize: 0 };
            }
            byCategory[file.category].count++;
            byCategory[file.category].size += file.size;
            byCategory[file.category].dataUrlSize += file.dataUrlSize;
            // 按类型
            if (!byType[file.type]) {
                byType[file.type] = { count: 0, size: 0 };
            }
            byType[file.type].count++;
            byType[file.type].size += file.size;
        }
        // 格式化大小
        for (const category of Object.keys(byCategory)) {
            byCategory[category].sizeFormatted = this.formatSize(byCategory[category].size);
            byCategory[category].dataUrlSizeFormatted = this.formatSize(byCategory[category].dataUrlSize);
        }
        for (const type of Object.keys(byType)) {
            byType[type].sizeFormatted = this.formatSize(byType[type].size);
        }
        const report = {
            totalFiles: files.length,
            totalSize,
            totalSizeFormatted: this.formatSize(totalSize),
            estimatedHtmlSize,
            estimatedHtmlSizeFormatted: this.formatSize(estimatedHtmlSize),
            files,
            byCategory,
            byType,
        };
        console.log(`[BuildAnalyzer] 分析完成: ${report.totalFiles} 个文件, 总大小 ${report.totalSizeFormatted}`);
        console.log(`[BuildAnalyzer] 预估单 HTML 大小: ${report.estimatedHtmlSizeFormatted}`);
        return report;
    }
    /**
     * 从文件系统分析（回退方案）
     */
    async analyzeFromFileSystem() {
        const files = await this.scanDirectory(this.buildDir);
        const analyses = [];
        for (const file of files) {
            const analysis = await this.analyzeFile(file);
            analyses.push(analysis);
        }
        // 按大小排序
        analyses.sort((a, b) => b.size - a.size);
        // 统计
        const totalSize = analyses.reduce((sum, f) => sum + f.size, 0);
        const estimatedHtmlSize = analyses.reduce((sum, f) => sum + f.dataUrlSize, 0);
        // 按分类统计
        const byCategory = {};
        const byType = {};
        for (const file of analyses) {
            // 按分类
            if (!byCategory[file.category]) {
                byCategory[file.category] = { count: 0, size: 0, dataUrlSize: 0 };
            }
            byCategory[file.category].count++;
            byCategory[file.category].size += file.size;
            byCategory[file.category].dataUrlSize += file.dataUrlSize;
            // 按类型
            if (!byType[file.type]) {
                byType[file.type] = { count: 0, size: 0 };
            }
            byType[file.type].count++;
            byType[file.type].size += file.size;
        }
        // 格式化大小
        for (const category of Object.keys(byCategory)) {
            byCategory[category].sizeFormatted = this.formatSize(byCategory[category].size);
            byCategory[category].dataUrlSizeFormatted = this.formatSize(byCategory[category].dataUrlSize);
        }
        for (const type of Object.keys(byType)) {
            byType[type].sizeFormatted = this.formatSize(byType[type].size);
        }
        const report = {
            totalFiles: analyses.length,
            totalSize,
            totalSizeFormatted: this.formatSize(totalSize),
            estimatedHtmlSize,
            estimatedHtmlSizeFormatted: this.formatSize(estimatedHtmlSize),
            files: analyses,
            byCategory,
            byType,
        };
        console.log(`[BuildAnalyzer] 分析完成: ${report.totalFiles} 个文件, 总大小 ${report.totalSizeFormatted}`);
        console.log(`[BuildAnalyzer] 预估单 HTML 大小: ${report.estimatedHtmlSizeFormatted}`);
        return report;
    }
    /**
     * 估算 data URL 大小
     */
    estimateDataUrlSize(size, type) {
        const dataUrlPrefix = this.getDataUrlPrefix(type);
        const base64Size = Math.ceil(size * 4 / 3);
        return dataUrlPrefix.length + base64Size;
    }
    /**
     * 映射资产类型到文件类型
     */
    mapAssetTypeToFileType(assetType) {
        const typeMap = {
            'texture': 'image',
            'audio': 'audio',
            'model': 'other',
            'script': 'js',
            'json': 'json',
            'css': 'css',
            'html': 'html',
            'font': 'font',
            'binary': 'other',
            'other': 'other',
        };
        return typeMap[assetType] || 'other';
    }
    /**
     * 映射资产类型到分类
     */
    mapAssetTypeToCategory(assetType, assetId) {
        if (assetId.includes('engine') || assetId.includes('playcanvas')) {
            return 'engine';
        }
        if (assetId.includes('config')) {
            return 'config';
        }
        if (assetId.includes('scene')) {
            return 'scene';
        }
        if (assetType === 'script') {
            return 'script';
        }
        if (assetType === 'html') {
            return 'html';
        }
        return 'asset';
    }
    /**
     * 生成 HTML 报告
     */
    async generateHTMLReport(report) {
        const reportPath = path.join(this.outputDir, 'build-analysis-report.html');
        // 尝试加载 build state 并生成增强版报告
        const stateManager = new BuildStateManager(this.buildDir, this.buildDir);
        const loaded = await stateManager.loadState();
        if (loaded) {
            const state = stateManager.getState();
            const unifiedData = StateToReportConverter.convertToUnifiedReport(state);
            // 执行优化分析
            const optimizationAnalyzer = new OptimizationAnalyzer(state);
            const optimizationAnalysis = optimizationAnalyzer.analyze();
            // 生成增强版报告
            const enhancedData = {
                ...unifiedData,
                optimizationAnalysis,
            };
            const html = generateEnhancedReportHTML(enhancedData);
            await fs.writeFile(reportPath, html, 'utf-8');
            console.log(`[BuildAnalyzer] 增强版分析报告已生成: ${reportPath}`);
            console.log(`[BuildAnalyzer] 发现 ${optimizationAnalysis.totalSuggestions} 条优化建议`);
            return reportPath;
        }
        // 回退到旧版报告
        const unifiedData = {
            reportType: 'base-build',
            title: '📊 Base Build 分析报告',
            buildPath: path.basename(this.buildDir),
            totalSize: report.totalSize,
            totalSizeFormatted: report.totalSizeFormatted,
            estimatedHtmlSize: report.estimatedHtmlSize,
            estimatedHtmlSizeFormatted: report.estimatedHtmlSizeFormatted,
            items: report.files.map(file => ({
                name: file.relativePath,
                path: file.path,
                type: file.type,
                category: file.category,
                size: file.size,
                sizeFormatted: file.sizeFormatted,
                percentage: (file.size / report.totalSize) * 100,
                dataUrlSize: file.dataUrlSize,
                dataUrlSizeFormatted: file.dataUrlSizeFormatted,
            })),
            byCategory: Object.fromEntries(Object.entries(report.byCategory).map(([key, value]) => [
                key,
                {
                    ...value,
                    percentage: (value.size / report.totalSize) * 100,
                },
            ])),
            byType: Object.fromEntries(Object.entries(report.byType).map(([key, value]) => [
                key,
                {
                    ...value,
                    percentage: (value.size / report.totalSize) * 100,
                },
            ])),
        };
        const html = generateUnifiedReportHTML(unifiedData);
        await fs.writeFile(reportPath, html, 'utf-8');
        console.log(`[BuildAnalyzer] 分析报告已生成: ${reportPath}`);
        return reportPath;
    }
    /**
     * 扫描目录获取所有文件
     */
    async scanDirectory(dir, baseDir = dir) {
        const files = [];
        const entries = await fs.readdir(dir, { withFileTypes: true });
        for (const entry of entries) {
            const fullPath = path.join(dir, entry.name);
            const relativePath = path.relative(baseDir, fullPath);
            // 跳过隐藏文件和临时目录
            if (entry.name.startsWith('.') || entry.name.startsWith('__')) {
                continue;
            }
            // 跳过分析报告文件
            if (entry.name === 'base-bundle-report.html' || entry.name === 'build-analysis-report.html') {
                continue;
            }
            if (entry.isDirectory()) {
                const subFiles = await this.scanDirectory(fullPath, baseDir);
                files.push(...subFiles);
            }
            else {
                files.push(fullPath);
            }
        }
        return files;
    }
    /**
     * 分析单个文件
     */
    async analyzeFile(filePath) {
        const stats = await fs.stat(filePath);
        const size = stats.size;
        const relativePath = path.relative(this.buildDir, filePath);
        const ext = path.extname(filePath).toLowerCase();
        // 确定文件类型
        const type = this.getFileType(ext);
        // 确定分类
        const category = this.getFileCategory(relativePath, type);
        // 计算 data URL 大小
        // 注意：config.json 和 scenes.json 中已经包含了资源的 data URL，
        // 所以这些文件本身已经是 Base64 编码后的大小，不需要再次计算增长
        let dataUrlSize;
        if (relativePath.includes('config.json') || relativePath.includes('scenes.json') || relativePath.includes('__settings__.js')) {
            // 这些文件中已经包含了 data URL，只需要再次 Base64 编码文件本身
            const dataUrlPrefix = this.getDataUrlPrefix(type);
            const base64Size = Math.ceil(size * 4 / 3);
            dataUrlSize = dataUrlPrefix.length + base64Size;
        }
        else if (type === 'js' || type === 'css' || type === 'html') {
            // 纯文本文件，需要 Base64 编码
            const dataUrlPrefix = this.getDataUrlPrefix(type);
            const base64Size = Math.ceil(size * 4 / 3);
            dataUrlSize = dataUrlPrefix.length + base64Size;
        }
        else {
            // 其他资源文件（图片、音频等）在 base 构建时已经被转换为 data URL
            // 并存储在 config.json 中，所以这里不应该出现这些文件
            // 如果出现了，说明它们没有被正确处理，按原始大小计算
            dataUrlSize = size;
        }
        return {
            path: filePath,
            relativePath,
            size,
            sizeFormatted: this.formatSize(size),
            dataUrlSize,
            dataUrlSizeFormatted: this.formatSize(dataUrlSize),
            type,
            category,
        };
    }
    /**
     * 获取文件类型
     */
    getFileType(ext) {
        const typeMap = {
            '.js': 'js',
            '.mjs': 'js',
            '.css': 'css',
            '.json': 'json',
            '.png': 'image',
            '.jpg': 'image',
            '.jpeg': 'image',
            '.gif': 'image',
            '.webp': 'image',
            '.svg': 'image',
            '.mp3': 'audio',
            '.wav': 'audio',
            '.ogg': 'audio',
            '.m4a': 'audio',
            '.woff': 'font',
            '.woff2': 'font',
            '.ttf': 'font',
            '.otf': 'font',
            '.wasm': 'wasm',
            '.html': 'html',
        };
        return typeMap[ext] || 'other';
    }
    /**
     * 获取文件分类
     */
    getFileCategory(relativePath, type) {
        const lowerPath = relativePath.toLowerCase();
        if (lowerPath.includes('playcanvas') || lowerPath.includes('engine')) {
            return 'engine';
        }
        if (lowerPath === 'config.json') {
            return 'config';
        }
        if (lowerPath.endsWith('.json') && /^\d+\.json$/.test(path.basename(lowerPath))) {
            return 'scene';
        }
        if (type === 'js' || type === 'mjs') {
            return 'script';
        }
        if (type === 'html') {
            return 'html';
        }
        return 'asset';
    }
    /**
     * 获取 data URL 前缀
     */
    getDataUrlPrefix(type) {
        const prefixMap = {
            'js': 'data:text/javascript;base64,',
            'css': 'data:text/css;base64,',
            'json': 'data:application/json;base64,',
            'image': 'data:image/png;base64,', // 平均值
            'audio': 'data:audio/mpeg;base64,',
            'font': 'data:font/woff2;base64,',
            'wasm': 'data:application/wasm;base64,',
            'html': 'data:text/html;base64,',
        };
        return prefixMap[type] || 'data:application/octet-stream;base64,';
    }
    /**
     * 格式化文件大小
     */
    formatSize(bytes) {
        if (bytes === 0)
            return '0 B';
        const k = 1024;
        const sizes = ['B', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
    }
    /**
     * 生成树状图数据结构
     */
    generateTreemapData(report) {
        const root = {
            name: 'root',
            children: []
        };
        // 按分类分组
        const categories = new Map();
        for (const file of report.files) {
            if (!categories.has(file.category)) {
                categories.set(file.category, {
                    name: file.category,
                    children: []
                });
            }
            categories.get(file.category).children.push({
                name: file.relativePath,
                value: file.size,
                type: file.type,
                category: file.category,
                sizeFormatted: file.sizeFormatted,
                dataUrlSize: file.dataUrlSize,
                dataUrlSizeFormatted: file.dataUrlSizeFormatted
            });
        }
        root.children = Array.from(categories.values());
        return root;
    }
    /**
     * 生成 HTML 报告内容
     */
    generateReportHTML(report) {
        // 生成树状图数据
        const treemapData = this.generateTreemapData(report);
        const categoryRows = Object.entries(report.byCategory)
            .sort((a, b) => b[1].size - a[1].size)
            .map(([category, data]) => `
        <tr>
          <td>${category}</td>
          <td>${data.count}</td>
          <td>${data.sizeFormatted}</td>
          <td>${data.dataUrlSizeFormatted}</td>
          <td>
            <div class="bar" style="width: ${(data.size / report.totalSize * 100).toFixed(1)}%"></div>
          </td>
        </tr>
      `).join('');
        return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>构建分析报告 - Treemap 可视化</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
      background: #1e1e1e;
      color: #e0e0e0;
      overflow-x: hidden;
    }

    .header {
      background: #252526;
      padding: 20px 30px;
      border-bottom: 1px solid #3e3e42;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .header h1 {
      font-size: 20px;
      font-weight: 600;
      color: #e0e0e0;
    }

    .header-stats {
      display: flex;
      gap: 30px;
      font-size: 13px;
    }

    .stat-item {
      display: flex;
      flex-direction: column;
      align-items: flex-end;
    }

    .stat-label {
      color: #888;
      font-size: 11px;
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    .stat-value {
      color: #4ec9b0;
      font-weight: 600;
      font-size: 16px;
      margin-top: 2px;
    }

    .controls {
      background: #252526;
      padding: 15px 30px;
      border-bottom: 1px solid #3e3e42;
      display: flex;
      gap: 20px;
      align-items: center;
      flex-wrap: wrap;
    }

    .control-group {
      display: flex;
      gap: 10px;
      align-items: center;
      flex-wrap: wrap;
    }

    .control-label {
      color: #888;
      font-size: 13px;
      font-weight: 600;
    }

    .filter-divider {
      width: 1px;
      height: 24px;
      background: #3e3e42;
    }

    .control-button {
      background: #3e3e42;
      border: 1px solid #555;
      color: #e0e0e0;
      padding: 6px 12px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 12px;
      transition: all 0.2s;
      white-space: nowrap;
    }

    .control-button:hover {
      background: #4e4e52;
      border-color: #666;
    }

    .control-button.active {
      background: #0e639c;
      border-color: #1177bb;
      color: white;
    }

    .filter-divider {
      width: 1px;
      height: 24px;
      background: #3e3e42;
    }

    .main-container {
      display: flex;
      height: calc(100vh - 120px);
    }

    .treemap-container {
      flex: 1;
      padding: 20px;
      overflow: auto;
    }

    .treemap {
      width: 100%;
      min-height: 600px;
      position: relative;
      background: #1e1e1e;
    }

    .treemap-block {
      position: absolute;
      border: 2px solid #1e1e1e;
      cursor: pointer;
      transition: all 0.2s;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      padding: 8px;
    }

    .treemap-block:hover {
      border-color: #fff;
      z-index: 10;
      transform: scale(1.02);
      box-shadow: 0 4px 12px rgba(0,0,0,0.5);
    }

    .treemap-block-label {
      font-size: 11px;
      font-weight: 600;
      color: white;
      text-align: center;
      text-shadow: 0 1px 2px rgba(0,0,0,0.8);
      word-break: break-word;
      line-height: 1.3;
      max-width: 100%;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    .treemap-block-size {
      font-size: 10px;
      color: rgba(255,255,255,0.8);
      text-shadow: 0 1px 2px rgba(0,0,0,0.8);
      margin-top: 4px;
    }

    .sidebar {
      width: 350px;
      background: #252526;
      border-left: 1px solid #3e3e42;
      overflow-y: auto;
      padding: 20px;
    }

    .sidebar h2 {
      font-size: 14px;
      color: #e0e0e0;
      margin-bottom: 15px;
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    .info-item {
      background: #1e1e1e;
      padding: 12px;
      border-radius: 4px;
      margin-bottom: 10px;
      border: 1px solid #3e3e42;
    }

    .info-label {
      font-size: 11px;
      color: #888;
      text-transform: uppercase;
      letter-spacing: 0.5px;
      margin-bottom: 4px;
    }

    .info-value {
      font-size: 13px;
      color: #e0e0e0;
      font-weight: 500;
      word-break: break-all;
    }

    .legend {
      margin-top: 20px;
    }

    .legend-item {
      display: flex;
      align-items: center;
      margin-bottom: 8px;
      font-size: 12px;
    }

    .legend-color {
      width: 20px;
      height: 20px;
      border-radius: 3px;
      margin-right: 10px;
      border: 1px solid #3e3e42;
    }

    .warning {
      background: #3a2a1a;
      border-left: 3px solid #d4a72c;
      padding: 12px;
      margin: 20px;
      border-radius: 4px;
      font-size: 13px;
      color: #d4a72c;
    }

    .tooltip {
      position: fixed;
      background: rgba(0, 0, 0, 0.95);
      color: white;
      padding: 12px;
      border-radius: 6px;
      font-size: 12px;
      pointer-events: none;
      z-index: 1000;
      display: none;
      border: 1px solid #555;
      box-shadow: 0 4px 12px rgba(0,0,0,0.5);
      max-width: 300px;
    }

    .tooltip-title {
      font-weight: 600;
      margin-bottom: 6px;
      color: #4ec9b0;
    }

    .tooltip-row {
      display: flex;
      justify-content: space-between;
      margin: 4px 0;
      gap: 20px;
    }

    .tooltip-label {
      color: #888;
    }

    .tooltip-value {
      color: #e0e0e0;
      font-weight: 500;
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>📊 构建分析报告 - Treemap 可视化</h1>
    <div class="header-stats">
      <div class="stat-item">
        <div class="stat-label">文件总数</div>
        <div class="stat-value">${report.totalFiles}</div>
      </div>
      <div class="stat-item">
        <div class="stat-label">当前大小</div>
        <div class="stat-value">${report.totalSizeFormatted}</div>
      </div>
      <div class="stat-item">
        <div class="stat-label">预估 HTML 大小</div>
        <div class="stat-value">${report.estimatedHtmlSizeFormatted}</div>
      </div>
    </div>
  </div>

  <div class="controls">
    <div class="control-group">
      <span class="control-label">显示大小:</span>
      <button class="control-button active" data-size-type="current">当前大小</button>
      <button class="control-button" data-size-type="dataurl">Data URL 大小</button>
    </div>
    
    <div class="filter-divider"></div>
    
    <div class="control-group">
      <span class="control-label">文件类型:</span>
      <button class="control-button active" data-type-filter="all">全部</button>
      <button class="control-button" data-type-filter="js">JS</button>
      <button class="control-button" data-type-filter="image">图片</button>
      <button class="control-button" data-type-filter="json">JSON</button>
      <button class="control-button" data-type-filter="css">CSS</button>
      <button class="control-button" data-type-filter="audio">音频</button>
      <button class="control-button" data-type-filter="other">其他</button>
    </div>
  </div>

  ${report.estimatedHtmlSize > 5 * 1024 * 1024 ? `
  <div class="warning">
    ⚠️ 预估的单 HTML 大小超过 5 MB，可能会影响加载性能。建议优化资源大小。
  </div>
  ` : ''}

  <div class="main-container">
    <div class="treemap-container">
      <div class="treemap" id="treemap"></div>
    </div>
    
    <div class="sidebar">
      <h2>📦 选中文件信息</h2>
      <div id="file-info">
        <div class="info-item">
          <div class="info-label">提示</div>
          <div class="info-value">点击或悬停在方块上查看详细信息</div>
        </div>
      </div>

      <div class="legend">
        <h2>🎨 分类图例</h2>
        ${Object.keys(report.byCategory).map((category, index) => {
            const colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c'];
            const color = colors[index % colors.length];
            return `
            <div class="legend-item">
              <div class="legend-color" style="background: ${color}"></div>
              <span>${category}</span>
            </div>
          `;
        }).join('')}
      </div>
    </div>
  </div>

  <div class="tooltip" id="tooltip"></div>

  <script>
    const data = ${JSON.stringify(treemapData)};
    const totalSize = ${report.totalSize};
    let currentSizeType = 'current';
    let currentTypeFilter = 'all';

    // 颜色映射
    const categoryColors = {
      'engine': '#e74c3c',
      'config': '#3498db',
      'scene': '#2ecc71',
      'script': '#f39c12',
      'asset': '#9b59b6',
      'html': '#1abc9c'
    };

    // 格式化大小
    const formatSize = (bytes) => {
      if (bytes === 0) return '0 B';
      const k = 1024;
      const sizes = ['B', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
    };

    // 获取节点的大小
    const getNodeSize = (node) => {
      if (node.value) {
        return currentSizeType === 'current' ? node.value : node.dataUrlSize;
      }
      if (node.children) {
        return node.children.reduce((sum, child) => sum + getNodeSize(child), 0);
      }
      return 0;
    };

    // 扁平化所有文件节点（应用类型过滤）
    const flattenFiles = (node) => {
      const files = [];
      if (node.children) {
        node.children.forEach(category => {
          if (category.children) {
            category.children.forEach(file => {
              // 应用类型过滤
              if (currentTypeFilter === 'all' || file.type === currentTypeFilter) {
                files.push(file);
              }
            });
          }
        });
      }
      return files;
    };

    // 简单的 treemap 布局算法
    const layoutTreemap = (node, x, y, width, height) => {
      // 获取所有文件
      const allFiles = flattenFiles(node);
      
      // 按大小排序
      allFiles.sort((a, b) => getNodeSize(b) - getNodeSize(a));
      
      // 计算总大小
      const total = allFiles.reduce((sum, file) => sum + getNodeSize(file), 0);
      
      if (total === 0) return [];
      
      const blocks = [];
      let currentX = x;
      let currentY = y;
      let remainingWidth = width;
      let remainingHeight = height;
      let remainingArea = width * height;
      let remainingSize = total;
      
      // 简单的行布局算法
      let rowFiles = [];
      let rowSize = 0;
      
      allFiles.forEach((file, index) => {
        const fileSize = getNodeSize(file);
        const fileRatio = fileSize / remainingSize;
        const fileArea = remainingArea * fileRatio;
        
        // 添加到当前行
        rowFiles.push(file);
        rowSize += fileSize;
        
        // 判断是否需要换行（每行最多10个文件，或者是最后一个文件）
        const shouldBreak = rowFiles.length >= 10 || index === allFiles.length - 1;
        
        if (shouldBreak) {
          // 计算行高度
          const rowRatio = rowSize / total;
          const rowHeight = height * rowRatio;
          
          // 布局当前行的文件
          let offsetX = currentX;
          rowFiles.forEach(f => {
            const fSize = getNodeSize(f);
            const fWidth = (fSize / rowSize) * width;
            
            blocks.push({
              ...f,
              x: offsetX,
              y: currentY,
              width: Math.max(fWidth, 1),
              height: Math.max(rowHeight, 1)
            });
            
            offsetX += fWidth;
          });
          
          // 更新位置
          currentY += rowHeight;
          remainingHeight -= rowHeight;
          remainingArea = width * remainingHeight;
          remainingSize -= rowSize;
          
          // 重置行
          rowFiles = [];
          rowSize = 0;
        }
      });
      
      return blocks;
    };

    // 渲染 treemap
    const renderTreemap = () => {
      const container = document.getElementById('treemap');
      const width = container.clientWidth;
      const height = Math.max(600, container.clientHeight);
      
      container.innerHTML = '';
      container.style.height = height + 'px';

      const blocks = layoutTreemap(data, 0, 0, width, height);

      blocks.forEach(block => {
        if (!block.value) return;

        const div = document.createElement('div');
        div.className = 'treemap-block';
        div.style.left = block.x + 'px';
        div.style.top = block.y + 'px';
        div.style.width = block.width + 'px';
        div.style.height = block.height + 'px';
        div.style.background = categoryColors[block.category] || '#95a5a6';

        const fileName = block.name.split('/').pop();
        const showLabel = block.width > 60 && block.height > 40;

        if (showLabel) {
          const label = document.createElement('div');
          label.className = 'treemap-block-label';
          label.textContent = fileName;
          div.appendChild(label);

          const size = document.createElement('div');
          size.className = 'treemap-block-size';
          size.textContent = block.sizeFormatted;
          div.appendChild(size);
        }

        // 鼠标事件
        div.addEventListener('mouseenter', (e) => {
          showTooltip(e, block);
          updateFileInfo(block);
        });

        div.addEventListener('mousemove', (e) => {
          updateTooltipPosition(e);
        });

        div.addEventListener('mouseleave', () => {
          hideTooltip();
        });

        div.addEventListener('click', () => {
          updateFileInfo(block);
        });

        container.appendChild(div);
      });
    };

    // 显示 tooltip
    const showTooltip = (e, block) => {
      const tooltip = document.getElementById('tooltip');
      tooltip.innerHTML = \`
        <div class="tooltip-title">\${block.name}</div>
        <div class="tooltip-row">
          <span class="tooltip-label">类型:</span>
          <span class="tooltip-value">\${block.type}</span>
        </div>
        <div class="tooltip-row">
          <span class="tooltip-label">分类:</span>
          <span class="tooltip-value">\${block.category}</span>
        </div>
        <div class="tooltip-row">
          <span class="tooltip-label">当前大小:</span>
          <span class="tooltip-value">\${block.sizeFormatted}</span>
        </div>
        <div class="tooltip-row">
          <span class="tooltip-label">Data URL 大小:</span>
          <span class="tooltip-value">\${block.dataUrlSizeFormatted}</span>
        </div>
        <div class="tooltip-row">
          <span class="tooltip-label">占比:</span>
          <span class="tooltip-value">\${((block.value / totalSize) * 100).toFixed(2)}%</span>
        </div>
      \`;
      tooltip.style.display = 'block';
      updateTooltipPosition(e);
    };

    const updateTooltipPosition = (e) => {
      const tooltip = document.getElementById('tooltip');
      tooltip.style.left = (e.clientX + 15) + 'px';
      tooltip.style.top = (e.clientY + 15) + 'px';
    };

    const hideTooltip = () => {
      const tooltip = document.getElementById('tooltip');
      tooltip.style.display = 'none';
    };

    // 更新文件信息
    const updateFileInfo = (block) => {
      const infoContainer = document.getElementById('file-info');
      infoContainer.innerHTML = \`
        <div class="info-item">
          <div class="info-label">文件路径</div>
          <div class="info-value">\${block.name}</div>
        </div>
        <div class="info-item">
          <div class="info-label">文件类型</div>
          <div class="info-value">\${block.type}</div>
        </div>
        <div class="info-item">
          <div class="info-label">分类</div>
          <div class="info-value">\${block.category}</div>
        </div>
        <div class="info-item">
          <div class="info-label">当前大小</div>
          <div class="info-value">\${block.sizeFormatted}</div>
        </div>
        <div class="info-item">
          <div class="info-label">Data URL 大小</div>
          <div class="info-value">\${block.dataUrlSizeFormatted}</div>
        </div>
        <div class="info-item">
          <div class="info-label">占总大小比例</div>
          <div class="info-value">\${((block.value / totalSize) * 100).toFixed(2)}%</div>
        </div>
      \`;
    };

    // 切换大小类型
    document.querySelectorAll('[data-size-type]').forEach(button => {
      button.addEventListener('click', () => {
        document.querySelectorAll('[data-size-type]').forEach(b => b.classList.remove('active'));
        button.classList.add('active');
        currentSizeType = button.dataset.sizeType;
        renderTreemap();
      });
    });

    // 切换类型过滤
    document.querySelectorAll('[data-type-filter]').forEach(button => {
      button.addEventListener('click', () => {
        document.querySelectorAll('[data-type-filter]').forEach(b => b.classList.remove('active'));
        button.classList.add('active');
        currentTypeFilter = button.dataset.typeFilter;
        updateFilteredStats();
        renderTreemap();
      });
    });

    // 更新过滤后的统计信息
    const updateFilteredStats = () => {
      const allFiles = flattenFiles(data);
      const filteredSize = allFiles.reduce((sum, file) => sum + getNodeSize(file), 0);
      const filteredCount = allFiles.length;
      
      // 更新顶部统计
      const statItems = document.querySelectorAll('.stat-item');
      if (statItems[0]) {
        const label = statItems[0].querySelector('.stat-label');
        const value = statItems[0].querySelector('.stat-value');
        if (currentTypeFilter === 'all') {
          label.textContent = '文件总数';
          value.textContent = ${report.totalFiles};
        } else {
          label.textContent = '过滤后文件数';
          value.textContent = filteredCount;
        }
      }
      
      if (statItems[1]) {
        const label = statItems[1].querySelector('.stat-label');
        const value = statItems[1].querySelector('.stat-value');
        if (currentTypeFilter === 'all') {
          label.textContent = '当前大小';
          value.textContent = '${report.totalSizeFormatted}';
        } else {
          label.textContent = '过滤后大小';
          value.textContent = formatSize(filteredSize);
        }
      }
    };

    // 初始渲染
    renderTreemap();

    // 窗口大小改变时重新渲染
    let resizeTimeout;
    window.addEventListener('resize', () => {
      clearTimeout(resizeTimeout);
      resizeTimeout = setTimeout(renderTreemap, 300);
    });
  </script>
</body>
</html>`;
    }
}
