/**
 * HTML export implementation — reads a session JSONL file and produces
 * a self-contained HTML page with embedded CSS, JS, and session data.
 *
 * The export pipeline: JSONL -> SessionEntry[] -> view model + LLM messages
 * -> base64-encoded JSON injected into an HTML template with theme variables.
 */
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { basename, join } from 'path';
import { convertToLlm } from '../../app/messages.js';
import { openSessionManager } from '../../app/session/session-factory.js';
import { APP_NAME, getExportTemplateDir } from '../../config.js';
import { buildSessionContext } from '../../core/session-schema.js';
import { BUILT_IN_TOOL_NAMES } from '../../tool-names.js';
import { getResolvedThemeColors, getThemeExportColors } from '../theme/runtime.js';
import { asEntry, buildSessionExportViewModel } from './session-view-model.js';
/** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */
function parseColor(color) {
    const hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
    if (hexMatch) {
        return {
            r: Number.parseInt(hexMatch[1], 16),
            g: Number.parseInt(hexMatch[2], 16),
            b: Number.parseInt(hexMatch[3], 16)
        };
    }
    const rgbMatch = color.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
    if (rgbMatch) {
        return {
            r: Number.parseInt(rgbMatch[1], 10),
            g: Number.parseInt(rgbMatch[2], 10),
            b: Number.parseInt(rgbMatch[3], 10)
        };
    }
    return undefined;
}
/** Calculate relative luminance of a color (0-1, higher = lighter). */
function getLuminance(r, g, b) {
    const toLinear = (c) => {
        const s = c / 255;
        return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
    };
    return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
}
/** Adjust color brightness. Factor > 1 lightens, < 1 darkens. */
function adjustBrightness(color, factor) {
    const parsed = parseColor(color);
    if (!parsed)
        return color;
    const adjust = (c) => Math.min(255, Math.max(0, Math.round(c * factor)));
    return `rgb(${adjust(parsed.r)}, ${adjust(parsed.g)}, ${adjust(parsed.b)})`;
}
/** Derive export background colors from a base color (e.g., userMessageBg). */
function deriveExportColors(baseColor) {
    const parsed = parseColor(baseColor);
    if (!parsed) {
        return {
            pageBg: 'rgb(24, 24, 30)',
            cardBg: 'rgb(30, 30, 36)',
            infoBg: 'rgb(60, 55, 40)'
        };
    }
    const luminance = getLuminance(parsed.r, parsed.g, parsed.b);
    const isLight = luminance > 0.5;
    if (isLight) {
        return {
            pageBg: adjustBrightness(baseColor, 0.96),
            cardBg: baseColor,
            infoBg: `rgb(${Math.min(255, parsed.r + 10)}, ${Math.min(255, parsed.g + 5)}, ${Math.max(0, parsed.b - 20)})`
        };
    }
    return {
        pageBg: adjustBrightness(baseColor, 0.7),
        cardBg: adjustBrightness(baseColor, 0.85),
        infoBg: `rgb(${Math.min(255, parsed.r + 20)}, ${Math.min(255, parsed.g + 15)}, ${parsed.b})`
    };
}
/**
 * Generate CSS custom property declarations from theme colors.
 */
function generateThemeVars(themeName) {
    const colors = getResolvedThemeColors(themeName);
    const lines = [];
    for (const [key, value] of Object.entries(colors)) {
        lines.push(`--${key}: ${value};`);
    }
    // Use explicit theme export colors if available, otherwise derive from userMessageBg
    const themeExport = getThemeExportColors(themeName);
    const userMessageBg = colors.userMessageBg || '#343541';
    const derivedColors = deriveExportColors(userMessageBg);
    lines.push(`--exportPageBg: ${themeExport.pageBg ?? derivedColors.pageBg};`);
    lines.push(`--exportCardBg: ${themeExport.cardBg ?? derivedColors.cardBg};`);
    lines.push(`--exportInfoBg: ${themeExport.infoBg ?? derivedColors.infoBg};`);
    return lines.join('\n      ');
}
/**
 * Build the LLM message array for export, annotated with source info.
 * Runs buildSessionContext → convertToLlm (the real pipeline) and tags each
 * output message with the original AgentMessage role so the HTML can show
 * where it came from (for example custom product context that became a user message).
 */
function buildAnnotatedLlmMessages(entries, leafId) {
    const { messages: agentMessages } = buildSessionContext(entries, leafId ?? undefined);
    const llmMessages = convertToLlm(agentMessages);
    const annotated = [];
    let agentIdx = 0;
    for (const msg of llmMessages) {
        if (agentIdx < agentMessages.length) {
            const agentMsg = agentMessages[agentIdx];
            const source = agentMsg.role === 'custom'
                ? 'custom:' + (agentMsg.customType || '')
                : agentMsg.role;
            annotated.push({ ...msg, _source: source });
            agentIdx++;
        }
        else {
            annotated.push({ ...msg, _source: 'synthetic' });
        }
    }
    return annotated;
}
function escapeInlineScript(script) {
    return script.replace(/<\/script/gi, '<\\/script');
}
function encodeBase64(value) {
    return Buffer.from(value, 'utf-8').toString('base64');
}
function buildBootstrapScript(scripts) {
    const encodedScripts = JSON.stringify(scripts.map((script) => encodeBase64(script)));
    return `
(() => {
  const scripts = ${encodedScripts};
  const decode = (base64) => {
    const binary = atob(base64);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
    return new TextDecoder('utf-8').decode(bytes);
  };
  for (const encoded of scripts) {
    const script = document.createElement('script');
    script.text = decode(encoded);
    document.body.appendChild(script);
  }
})();
`.trim();
}
/**
 * Core HTML generation logic shared by both export functions.
 */
function generateHtml(sessionData, themeName, title) {
    const templateDir = getExportTemplateDir();
    const template = readFileSync(join(templateDir, 'template.html'), 'utf-8');
    const templateCss = readFileSync(join(templateDir, 'template.css'), 'utf-8');
    const templateJs = readFileSync(join(templateDir, 'template.js'), 'utf-8');
    const markedJs = readFileSync(join(templateDir, 'vendor', 'marked.min.js'), 'utf-8');
    const hljsJs = readFileSync(join(templateDir, 'vendor', 'highlight.min.js'), 'utf-8');
    const themeVars = generateThemeVars(themeName);
    const colors = getResolvedThemeColors(themeName);
    const exportColors = deriveExportColors(colors.userMessageBg || '#343541');
    const bodyBg = exportColors.pageBg;
    const containerBg = exportColors.cardBg;
    const infoBg = exportColors.infoBg;
    const resolvedCss = templateCss
        .replace('{{THEME_VARS}}', themeVars)
        .replace('{{BODY_BG}}', bodyBg)
        .replace('{{CONTAINER_BG}}', containerBg)
        .replace('{{INFO_BG}}', infoBg);
    const bootstrapJs = buildBootstrapScript([markedJs, hljsJs, templateJs]);
    const sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toString('base64');
    return template
        .replace('{{TITLE}}', title || `${APP_NAME} Session Export`)
        .replace('{{CSS}}', resolvedCss)
        .replace('{{SESSION_DATA}}', sessionDataBase64)
        .replace('{{BOOTSTRAP_JS}}', escapeInlineScript(bootstrapJs));
}
/**
 * Pre-render custom extension tools to HTML.
 */
function preRenderCustomTools(entries, toolRenderer) {
    const rendered = {};
    for (const entry of entries) {
        if (entry.type !== 'message')
            continue;
        const { message } = entry;
        if (message.role === 'assistant') {
            for (const content of message.content) {
                if (content.type !== 'toolCall')
                    continue;
                if (BUILT_IN_TOOL_NAMES.has(content.name))
                    continue;
                const callHtml = toolRenderer.renderCall(content.name, content.arguments);
                if (callHtml) {
                    rendered[content.id] = { ...(rendered[content.id] || {}), callHtml };
                }
            }
        }
        else if (message.role === 'toolResult') {
            if (BUILT_IN_TOOL_NAMES.has(message.toolName))
                continue;
            const resultHtml = toolRenderer.renderResult(message.toolName, message.content, message.details, message.isError);
            if (resultHtml) {
                rendered[message.toolCallId] = { ...(rendered[message.toolCallId] || {}), resultHtml };
            }
        }
    }
    return rendered;
}
function buildSessionData(header, entries, leafId, toolRenderer) {
    const viewModel = buildSessionExportViewModel(entries.map((entry) => asEntry(entry)));
    const renderedTools = toolRenderer ? preRenderCustomTools(entries, toolRenderer) : undefined;
    const llmMessages = buildAnnotatedLlmMessages(entries, leafId);
    return {
        header,
        entries,
        leafId,
        viewModel,
        renderedTools,
        llmMessages
    };
}
/** Read a JSONL session file and return the raw header, entries, and leaf id. */
export function loadSessionExportSourceFromFile(inputPath) {
    if (!existsSync(inputPath)) {
        throw new Error(`File not found: ${inputPath}`);
    }
    const sessionManager = openSessionManager(inputPath);
    return {
        entries: sessionManager.getEntries(),
        header: sessionManager.getHeader(),
        leafId: sessionManager.getLeafId()
    };
}
/** Render an already-loaded session source to a styled HTML document. */
export function renderSessionExportHtml(source, options = {}) {
    const sessionData = buildSessionData(source.header, source.entries, source.leafId, options.toolRenderer);
    return generateHtml(sessionData, options.themeName, options.title);
}
export async function exportFromFile(inputPath, options) {
    const opts = typeof options === 'string' ? { outputPath: options } : options || {};
    const html = renderSessionExportHtml(loadSessionExportSourceFromFile(inputPath), opts);
    let outputPath = opts.outputPath;
    if (!outputPath) {
        const inputBasename = basename(inputPath, '.jsonl');
        outputPath = `${APP_NAME}-session-${inputBasename}.html`;
    }
    writeFileSync(outputPath, html, 'utf-8');
    return outputPath;
}
//# sourceMappingURL=export-from-file.js.map