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';
/**
 * Playable 分析器 - 分析最终打包后的单 HTML 文件
 *
 * 职责：
 * 1. 解析单 HTML 文件中的所有内联资源
 * 2. 提取 data URL 并计算实际大小
 * 3. 生成准确的资源分布报告
 */
export class PlayableAnalyzer {
    constructor(htmlPath, outputDir, baseBuildReportPath) {
        this.htmlPath = htmlPath;
        this.outputDir = outputDir || path.dirname(htmlPath);
        this.baseBuildReportPath = baseBuildReportPath;
    }
    /**
     * 分析 Playable HTML 文件
     *
     * 策略：
     * 1. 优先尝试从 build-state.json 读取资源信息（更准确）
     * 2. 如果没有 state 文件，则从 HTML 中提取资源（fallback）
     */
    async analyze() {
        console.log('[PlayableAnalyzer] 开始分析 Playable HTML 文件...');
        // 读取 HTML 文件
        const htmlContent = await fs.readFile(this.htmlPath, 'utf-8');
        const htmlSize = Buffer.from(htmlContent, 'utf-8').length;
        // 尝试从 build-state.json 读取资源信息
        const stateManager = new BuildStateManager(this.outputDir, this.outputDir);
        const stateLoaded = await stateManager.loadState();
        let assets;
        if (stateLoaded) {
            console.log('[PlayableAnalyzer] 使用 build-state.json 的数据');
            assets = this.extractAssetsFromState(stateManager, htmlSize);
        }
        else {
            console.log('[PlayableAnalyzer] build-state.json 不存在，从 HTML 中提取资源');
            assets = this.extractAssets(htmlContent);
        }
        // 按大小排序
        assets.sort((a, b) => b.size - a.size);
        // 统计
        const byCategory = {};
        const byType = {};
        for (const asset of assets) {
            // 按分类
            if (!byCategory[asset.category]) {
                byCategory[asset.category] = { count: 0, size: 0 };
            }
            byCategory[asset.category].count++;
            byCategory[asset.category].size += asset.size;
            // 按类型
            if (!byType[asset.type]) {
                byType[asset.type] = { count: 0, size: 0 };
            }
            byType[asset.type].count++;
            byType[asset.type].size += asset.size;
        }
        // 格式化大小和百分比
        for (const category of Object.keys(byCategory)) {
            byCategory[category].sizeFormatted = this.formatSize(byCategory[category].size);
            byCategory[category].percentage = (byCategory[category].size / htmlSize) * 100;
        }
        for (const type of Object.keys(byType)) {
            byType[type].sizeFormatted = this.formatSize(byType[type].size);
            byType[type].percentage = (byType[type].size / htmlSize) * 100;
        }
        const report = {
            htmlPath: this.htmlPath,
            totalSize: htmlSize,
            totalSizeFormatted: this.formatSize(htmlSize),
            assets,
            byCategory,
            byType,
        };
        console.log(`[PlayableAnalyzer] 分析完成: ${assets.length} 个资源, 总大小 ${report.totalSizeFormatted}`);
        return report;
    }
    /**
     * 提取 HTML 中的所有资源
     *
     * 注意：不要重复计算！
     * - script/style 标签的大小已经包含了其中的 data URL
     * - 所以我们只统计标签本身，不单独提取 data URL
     */
    extractAssets(htmlContent) {
        const assets = [];
        const totalSize = Buffer.from(htmlContent, 'utf-8').length;
        // 1. 提取内联 <script> 标签
        const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
        let scriptMatch;
        let scriptIndex = 0;
        const engineScriptCount = { playcanvas: 0, physics: 0 };
        while ((scriptMatch = scriptRegex.exec(htmlContent)) !== null) {
            const scriptContent = scriptMatch[1];
            const size = Buffer.from(scriptContent, 'utf-8').length;
            // 跳过空脚本
            if (size < 10) {
                continue;
            }
            // 尝试识别脚本类型
            let name = `script-${scriptIndex}`;
            let category = 'script';
            if (scriptContent.includes('pc.Application') || scriptContent.includes('PlayCanvas') || scriptContent.includes('pc.Entity')) {
                engineScriptCount.playcanvas++;
                if (engineScriptCount.playcanvas === 1) {
                    name = 'playcanvas-engine.js';
                }
                else {
                    name = `playcanvas-engine-part${engineScriptCount.playcanvas}.js`;
                }
                category = 'engine';
            }
            else if (scriptContent.includes('__settings__') || scriptContent.includes('ASSET_PREFIX')) {
                name = '__settings__.js';
                category = 'config';
            }
            else if (scriptContent.includes('__start__') || scriptContent.includes('app.start()')) {
                name = '__start__.js';
                category = 'script';
            }
            else if (scriptContent.includes('__modules__')) {
                name = '__modules__.js';
                category = 'script';
            }
            else if (scriptContent.includes('__game-scripts')) {
                name = '__game-scripts__.js';
                category = 'script';
            }
            else if (scriptContent.includes('planck') || scriptContent.includes('p2') || scriptContent.includes('cannon')) {
                engineScriptCount.physics++;
                if (engineScriptCount.physics === 1) {
                    name = 'physics-engine.js';
                }
                else {
                    name = `physics-engine-part${engineScriptCount.physics}.js`;
                }
                category = 'engine';
            }
            assets.push({
                name,
                type: 'js',
                category,
                size,
                sizeFormatted: this.formatSize(size),
                percentage: (size / totalSize) * 100,
            });
            scriptIndex++;
        }
        // 2. 提取内联 <style> 标签
        const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
        let styleMatch;
        let styleIndex = 0;
        while ((styleMatch = styleRegex.exec(htmlContent)) !== null) {
            const styleContent = styleMatch[1];
            const size = Buffer.from(styleContent, 'utf-8').length;
            // 跳过空样式
            if (size < 10) {
                continue;
            }
            assets.push({
                name: `style-${styleIndex}.css`,
                type: 'css',
                category: 'asset',
                size,
                sizeFormatted: this.formatSize(size),
                percentage: (size / totalSize) * 100,
            });
            styleIndex++;
        }
        // 3. 计算 HTML 结构本身的大小（不包括 script 和 style 的内容）
        const scriptsAndStylesSize = assets.reduce((sum, asset) => sum + asset.size, 0);
        const htmlStructureSize = totalSize - scriptsAndStylesSize;
        if (htmlStructureSize > 0) {
            assets.push({
                name: 'html-structure',
                type: 'html',
                category: 'html',
                size: htmlStructureSize,
                sizeFormatted: this.formatSize(htmlStructureSize),
                percentage: (htmlStructureSize / totalSize) * 100,
            });
        }
        return assets;
    }
    /**
     * 从 build-state.json 提取资源信息
     *
     * 这个方法使用打包过程中记录的真实资源信息，比从 HTML 中提取更准确
     */
    extractAssetsFromState(stateManager, htmlSize) {
        const assets = [];
        const state = stateManager.getState();
        // 遍历所有资源
        for (const [assetId, asset] of Object.entries(state.assets)) {
            // 使用最终大小；名称为最后处理阶段的 name 或原始名称
            const size = asset.finalSize;
            const name = asset.processingHistory.length > 0
                ? asset.processingHistory[asset.processingHistory.length - 1].name
                : asset.originalName;
            // 确定分类
            let category = 'asset';
            if (asset.type === 'script') {
                if (name.includes('playcanvas') || name.includes('engine')) {
                    category = 'engine';
                }
                else {
                    category = 'script';
                }
            }
            else if (asset.type === 'json') {
                if (name.includes('config') || name.includes('settings')) {
                    category = 'config';
                }
                else if (name.includes('scene')) {
                    category = 'scene';
                }
                else {
                    category = 'asset';
                }
            }
            else if (asset.type === 'css') {
                category = 'asset';
            }
            else if (asset.type === 'html') {
                category = 'html';
            }
            assets.push({
                name,
                type: asset.type,
                category,
                size,
                sizeFormatted: this.formatSize(size),
                percentage: (size / htmlSize) * 100,
            });
        }
        // 添加 HTML 结构本身的大小
        const assetsSize = assets.reduce((sum, asset) => sum + asset.size, 0);
        const htmlStructureSize = htmlSize - assetsSize;
        if (htmlStructureSize > 0) {
            assets.push({
                name: 'html-structure',
                type: 'html',
                category: 'html',
                size: htmlStructureSize,
                sizeFormatted: this.formatSize(htmlStructureSize),
                percentage: (htmlStructureSize / htmlSize) * 100,
            });
        }
        return assets;
    }
    /**
     * 根据 MIME 类型获取文件信息
     */
    getMimeTypeInfo(mimeType) {
        const lowerMime = mimeType.toLowerCase();
        // 图片
        if (lowerMime.startsWith('image/')) {
            const ext = lowerMime.split('/')[1].split('+')[0];
            return { type: 'image', category: 'asset', extension: ext };
        }
        // 音频
        if (lowerMime.startsWith('audio/')) {
            const ext = lowerMime.split('/')[1];
            return { type: 'audio', category: 'asset', extension: ext };
        }
        // 字体
        if (lowerMime.startsWith('font/') || lowerMime.includes('woff')) {
            return { type: 'font', category: 'asset', extension: 'woff2' };
        }
        // JSON
        if (lowerMime.includes('json')) {
            return { type: 'json', category: 'config', extension: 'json' };
        }
        // 模型
        if (lowerMime.includes('gltf') || lowerMime.includes('glb')) {
            return { type: 'model', category: 'asset', extension: 'glb' };
        }
        // WASM
        if (lowerMime.includes('wasm')) {
            return { type: 'wasm', category: 'engine', extension: 'wasm' };
        }
        // 其他
        return { type: 'other', category: 'asset', extension: 'bin' };
    }
    /**
     * 生成 HTML 报告
     *
     * 策略：
     * 1. 如果提供了 baseBuildReportPath，则复用 Base Build 的 HTML 模板，只替换数据
     * 2. 否则，使用统一的模板生成新的 HTML
     */
    async generateHTMLReport(report) {
        const reportPath = path.join(this.outputDir, 'playable-analysis-report.html');
        // 尝试加载 build state 并生成增强版报告
        const stateManager = new BuildStateManager(this.outputDir, this.outputDir);
        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(`[PlayableAnalyzer] 增强版分析报告已生成: ${reportPath}`);
            console.log(`[PlayableAnalyzer] 发现 ${optimizationAnalysis.totalSuggestions} 条优化建议`);
            return reportPath;
        }
        // 转换为统一的报告数据格式
        const unifiedData = {
            reportType: 'playable',
            title: '📊 Playable 分析报告',
            buildPath: path.basename(report.htmlPath),
            totalSize: report.totalSize,
            totalSizeFormatted: report.totalSizeFormatted,
            items: report.assets.map(asset => ({
                name: asset.name,
                type: asset.type,
                category: asset.category,
                size: asset.size,
                sizeFormatted: asset.sizeFormatted,
                percentage: asset.percentage,
            })),
            byCategory: report.byCategory,
            byType: report.byType,
            optimizationInfo: report.optimizationInfo ? {
                baseBuildSize: report.optimizationInfo.baseBuildSize,
                baseBuildSizeFormatted: report.optimizationInfo.baseBuildSizeFormatted,
                actualSize: report.totalSize,
                actualSizeFormatted: report.totalSizeFormatted,
                optimizationRate: report.optimizationInfo.optimizationRate,
            } : undefined,
        };
        let html;
        // 如果提供了 Base Build 报告路径，则复用其 HTML 模板
        if (this.baseBuildReportPath) {
            try {
                // 读取 Base Build 的 HTML 文件
                const baseBuildHtml = await fs.readFile(this.baseBuildReportPath, 'utf-8');
                // 查找并替换 reportData
                // 匹配模式：const reportData = {...};
                const dataRegex = /const reportData = \{[\s\S]*?\};/;
                const newData = `const reportData = ${JSON.stringify(unifiedData, null, 2)};`;
                if (dataRegex.test(baseBuildHtml)) {
                    html = baseBuildHtml.replace(dataRegex, newData);
                    console.log(`[PlayableAnalyzer] 复用 Base Build HTML 模板: ${this.baseBuildReportPath}`);
                }
                else {
                    console.warn(`[PlayableAnalyzer] 未找到 reportData，使用统一模板`);
                    html = generateUnifiedReportHTML(unifiedData);
                }
            }
            catch (error) {
                console.warn(`[PlayableAnalyzer] 读取 Base Build HTML 失败，使用统一模板:`, error);
                html = generateUnifiedReportHTML(unifiedData);
            }
        }
        else {
            // 使用统一的模板
            html = generateUnifiedReportHTML(unifiedData);
        }
        await fs.writeFile(reportPath, html, 'utf-8');
        console.log(`[PlayableAnalyzer] 分析报告已生成: ${reportPath}`);
        return reportPath;
    }
    /**
     * 格式化文件大小
     */
    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 asset of report.assets) {
            if (!categories.has(asset.category)) {
                categories.set(asset.category, {
                    name: asset.category,
                    children: []
                });
            }
            categories.get(asset.category).children.push({
                name: asset.name,
                value: asset.size,
                type: asset.type,
                category: asset.category,
                sizeFormatted: asset.sizeFormatted,
                percentage: asset.percentage
            });
        }
        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.percentage.toFixed(2)}%</td>
          <td>
            <div class="bar" style="width: ${data.percentage.toFixed(1)}%"></div>
          </td>
        </tr>
      `).join('');
        const typeRows = Object.entries(report.byType)
            .sort((a, b) => b[1].size - a[1].size)
            .map(([type, data]) => `
        <tr>
          <td>${type}</td>
          <td>${data.count}</td>
          <td>${data.sizeFormatted}</td>
          <td>${data.percentage.toFixed(2)}%</td>
          <td>
            <div class="bar" style="width: ${data.percentage.toFixed(1)}%"></div>
          </td>
        </tr>
      `).join('');
        const topAssets = report.assets.slice(0, 20);
        const assetRows = topAssets.map(asset => `
      <tr>
        <td>${asset.name}</td>
        <td>${asset.type}</td>
        <td>${asset.category}</td>
        <td>${asset.sizeFormatted}</td>
        <td>${asset.percentage.toFixed(2)}%</td>
        <td>
          <div class="bar" style="width: ${asset.percentage.toFixed(1)}%"></div>
        </td>
      </tr>
    `).join('');
        const optimizationSection = report.optimizationInfo ? `
      <div class="optimization-info">
        <h2>🎯 优化效果</h2>
        <div class="optimization-stats">
          <div class="optimization-item">
            <div class="optimization-label">Base Build 预估大小</div>
            <div class="optimization-value">${report.optimizationInfo.baseBuildSizeFormatted}</div>
          </div>
          <div class="optimization-item">
            <div class="optimization-label">实际打包大小</div>
            <div class="optimization-value">${report.totalSizeFormatted}</div>
          </div>
          <div class="optimization-item">
            <div class="optimization-label">优化率</div>
            <div class="optimization-value optimization-rate">${report.optimizationInfo.optimizationRate?.toFixed(1)}%</div>
          </div>
        </div>
      </div>
    ` : '';
        return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Playable 分析报告</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;
      padding: 20px;
    }

    .container {
      max-width: 1400px;
      margin: 0 auto;
    }

    .header {
      background: #252526;
      padding: 30px;
      border-radius: 8px;
      margin-bottom: 20px;
      border: 1px solid #3e3e42;
    }

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

    .header-stats {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 20px;
    }

    .stat-item {
      background: #1e1e1e;
      padding: 15px;
      border-radius: 6px;
      border: 1px solid #3e3e42;
    }

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

    .stat-value {
      color: #4ec9b0;
      font-weight: 600;
      font-size: 20px;
    }

    .optimization-info {
      background: #252526;
      padding: 30px;
      border-radius: 8px;
      margin-bottom: 20px;
      border: 1px solid #3e3e42;
    }

    .optimization-info h2 {
      font-size: 18px;
      margin-bottom: 20px;
      color: #e0e0e0;
    }

    .optimization-stats {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
      gap: 20px;
    }

    .optimization-item {
      background: #1e1e1e;
      padding: 20px;
      border-radius: 6px;
      border: 1px solid #3e3e42;
      text-align: center;
    }

    .optimization-label {
      color: #888;
      font-size: 13px;
      margin-bottom: 10px;
    }

    .optimization-value {
      color: #4ec9b0;
      font-weight: 600;
      font-size: 24px;
    }

    .optimization-rate {
      color: #2ecc71;
    }

    .section {
      background: #252526;
      padding: 30px;
      border-radius: 8px;
      margin-bottom: 20px;
      border: 1px solid #3e3e42;
    }

    .section h2 {
      font-size: 18px;
      margin-bottom: 20px;
      color: #e0e0e0;
    }

    table {
      width: 100%;
      border-collapse: collapse;
    }

    th, td {
      padding: 12px;
      text-align: left;
      border-bottom: 1px solid #3e3e42;
    }

    th {
      background: #1e1e1e;
      color: #888;
      font-size: 12px;
      text-transform: uppercase;
      letter-spacing: 0.5px;
      font-weight: 600;
    }

    td {
      color: #e0e0e0;
      font-size: 13px;
    }

    tr:hover {
      background: #2a2a2a;
    }

    .bar {
      height: 20px;
      background: linear-gradient(90deg, #4ec9b0, #2ecc71);
      border-radius: 4px;
      min-width: 2px;
    }

    .treemap-container {
      background: #252526;
      padding: 30px;
      border-radius: 8px;
      margin-bottom: 20px;
      border: 1px solid #3e3e42;
    }

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

    .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;
    }

    .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;
    }

    .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;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>📊 Playable 分析报告</h1>
      <div class="header-stats">
        <div class="stat-item">
          <div class="stat-label">HTML 文件</div>
          <div class="stat-value">${path.basename(report.htmlPath)}</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">资源数量</div>
          <div class="stat-value">${report.assets.length}</div>
        </div>
      </div>
    </div>

    ${optimizationSection}

    <div class="treemap-container">
      <h2>🗺️ 资源分布图</h2>
      <div class="treemap" id="treemap"></div>
    </div>

    <div class="section">
      <h2>📦 按分类统计</h2>
      <table>
        <thead>
          <tr>
            <th>分类</th>
            <th>数量</th>
            <th>大小</th>
            <th>占比</th>
            <th>可视化</th>
          </tr>
        </thead>
        <tbody>
          ${categoryRows}
        </tbody>
      </table>
    </div>

    <div class="section">
      <h2>📄 按类型统计</h2>
      <table>
        <thead>
          <tr>
            <th>类型</th>
            <th>数量</th>
            <th>大小</th>
            <th>占比</th>
            <th>可视化</th>
          </tr>
        </thead>
        <tbody>
          ${typeRows}
        </tbody>
      </table>
    </div>

    <div class="section">
      <h2>🔝 Top 20 资源</h2>
      <table>
        <thead>
          <tr>
            <th>名称</th>
            <th>类型</th>
            <th>分类</th>
            <th>大小</th>
            <th>占比</th>
            <th>可视化</th>
          </tr>
        </thead>
        <tbody>
          ${assetRows}
        </tbody>
      </table>
    </div>
  </div>

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

  <script>
    const data = ${JSON.stringify(treemapData)};
    const totalSize = ${report.totalSize};

    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 flattenFiles = (node) => {
      const files = [];
      if (node.children) {
        node.children.forEach(category => {
          if (category.children) {
            files.push(...category.children);
          }
        });
      }
      return files;
    };

    const layoutTreemap = (node, x, y, width, height) => {
      const allFiles = flattenFiles(node);
      allFiles.sort((a, b) => b.value - a.value);
      
      const total = allFiles.reduce((sum, file) => sum + file.value, 0);
      if (total === 0) return [];
      
      const blocks = [];
      let currentY = y;
      let remainingHeight = height;
      let remainingSize = total;
      
      let rowFiles = [];
      let rowSize = 0;
      
      allFiles.forEach((file, index) => {
        rowFiles.push(file);
        rowSize += file.value;
        
        const shouldBreak = rowFiles.length >= 10 || index === allFiles.length - 1;
        
        if (shouldBreak) {
          const rowRatio = rowSize / total;
          const rowHeight = height * rowRatio;
          
          let offsetX = x;
          rowFiles.forEach(f => {
            const fWidth = (f.value / 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;
          remainingSize -= rowSize;
          
          rowFiles = [];
          rowSize = 0;
        }
      });
      
      return blocks;
    };

    const renderTreemap = () => {
      const container = document.getElementById('treemap');
      const width = container.clientWidth;
      const height = 600;
      
      container.innerHTML = '';
      container.style.height = height + 'px';

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

      blocks.forEach(block => {
        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 showLabel = block.width > 60 && block.height > 40;

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

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

        div.addEventListener('mouseenter', (e) => {
          const tooltip = document.getElementById('tooltip');
          tooltip.innerHTML = \`
            <div class="tooltip-title">\${block.name}</div>
            <div class="tooltip-row">
              <span>类型:</span>
              <span>\${block.type}</span>
            </div>
            <div class="tooltip-row">
              <span>分类:</span>
              <span>\${block.category}</span>
            </div>
            <div class="tooltip-row">
              <span>大小:</span>
              <span>\${block.sizeFormatted}</span>
            </div>
            <div class="tooltip-row">
              <span>占比:</span>
              <span>\${block.percentage.toFixed(2)}%</span>
            </div>
          \`;
          tooltip.style.display = 'block';
          tooltip.style.left = (e.clientX + 15) + 'px';
          tooltip.style.top = (e.clientY + 15) + 'px';
        });

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

        div.addEventListener('mouseleave', () => {
          document.getElementById('tooltip').style.display = 'none';
        });

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

    renderTreemap();

    let resizeTimeout;
    window.addEventListener('resize', () => {
      clearTimeout(resizeTimeout);
      resizeTimeout = setTimeout(renderTreemap, 300);
    });
  </script>
</body>
</html>`;
    }
}
