import { readFile } from 'node:fs/promises';
import path from 'node:path';
import JSZip from 'jszip';
import { z } from 'zod';
import { extractOfficeText, getExtension, PDF_LOG_SUPPRESSED_PREFIXES, suppressConsoleLogWhile, wrapDependencyLoadError, } from '../utils.js';
// Heavy converters are lazy-loaded (and memoized) so importing this module
// never pulls in mammoth / pdf-parse / xlsx / turndown.
let mammothModule;
async function loadMammoth() {
    mammothModule ??= import('mammoth')
        .then(mod => mod.default)
        .catch((error) => wrapDependencyLoadError('mammoth', error));
    return mammothModule;
}
let pdfParseModule;
async function loadPdfParse() {
    pdfParseModule ??= import('pdf-parse')
        .then(mod => mod.default)
        .catch((error) => wrapDependencyLoadError('pdf-parse', error));
    return pdfParseModule;
}
// CJS interop: prefer .default (full API object) and fall back to the namespace.
let xlsxModule;
async function loadXlsx() {
    xlsxModule ??= import('xlsx')
        .then(mod => mod.default ?? mod)
        .catch((error) => wrapDependencyLoadError('xlsx', error));
    return xlsxModule;
}
// Memoizes the configured TurndownService instance alongside the module load.
let turndownInstance;
async function getTurndown() {
    turndownInstance ??= import('turndown')
        .then(mod => new mod.default({ headingStyle: 'atx' }))
        .catch((error) => wrapDependencyLoadError('turndown', error));
    return turndownInstance;
}
const PdfTextContentSchema = z.object({ items: z.array(z.object({ str: z.string() }).passthrough()) });
const RAW_TEXT_EXTENSIONS = new Set([
    'md',
    'markdown',
    'mdx',
    'txt',
    'rst',
    'adoc',
    'asciidoc',
    'yaml',
    'yml',
    'json',
    'jsonc',
    'toml',
]);
/** Convert a DOCX file to markdown via mammoth → HTML → turndown */
async function docxToMarkdown(filePath) {
    const mammoth = await loadMammoth();
    const result = await mammoth.convertToHtml({ path: filePath });
    const html = result.value || '';
    if (!html.trim())
        return '';
    const turndown = await getTurndown();
    return turndown.turndown(html);
}
/** Convert a PDF to markdown with [Page N] markers */
async function pdfToMarkdown(filePath) {
    const buffer = await readFile(filePath);
    const pdf = await loadPdfParse();
    // Suppress pdf.js warnings (shared ref-counted helper — safe under concurrency)
    const data = await suppressConsoleLogWhile(PDF_LOG_SUPPRESSED_PREFIXES, () => pdf(buffer, {
        pagerender: async (pageData) => {
            const textContent = PdfTextContentSchema.parse(await pageData.getTextContent());
            const strings = textContent.items.map(item => item.str);
            return `[Page ${pageData.pageIndex + 1}]\n${strings.join(' ')}`;
        },
    }));
    return data.text;
}
/** Convert a PPTX to markdown with slide headers */
async function pptxToMarkdown(filePath) {
    const buffer = await readFile(filePath);
    const zip = await JSZip.loadAsync(buffer);
    // Get slide filenames sorted by number
    const slideFiles = Object.keys(zip.files)
        .filter(name => /^ppt\/slides\/slide\d+\.xml$/.test(name))
        .sort((a, b) => {
        const numA = parseInt(a.match(/slide(\d+)/)?.[1] || '0', 10);
        const numB = parseInt(b.match(/slide(\d+)/)?.[1] || '0', 10);
        return numA - numB;
    });
    // Get full text via officeparser
    const fullText = await extractOfficeText(buffer);
    const slideCount = slideFiles.length;
    if (slideCount <= 1) {
        return `# Slide 1\n\n${fullText}`;
    }
    // Split text roughly between slides
    const lines = fullText.split('\n');
    const linesPerSlide = Math.max(1, Math.ceil(lines.length / slideCount));
    const parts = [];
    for (let i = 0; i < slideCount; i++) {
        const start = i * linesPerSlide;
        const end = Math.min((i + 1) * linesPerSlide, lines.length);
        const slideText = lines.slice(start, end).join('\n').trim();
        parts.push(`# Slide ${i + 1}\n\n${slideText}`);
    }
    return parts.join('\n\n');
}
/** Convert an ODT file to markdown (best-effort heading detection) */
async function odtToMarkdown(filePath) {
    const text = await extractOfficeText(filePath);
    return text;
}
/** Convert an ODP file to markdown with slide headers */
async function odpToMarkdown(filePath) {
    const buffer = await readFile(filePath);
    const zip = await JSZip.loadAsync(buffer);
    const contentXml = await zip.file('content.xml')?.async('text');
    if (!contentXml)
        return '';
    const slides = (contentXml.match(/<draw:page /g) || []).length;
    const text = await extractOfficeText(buffer);
    if (slides <= 1) {
        return `# Slide 1\n\n${text}`;
    }
    const lines = text.split('\n');
    const linesPerSlide = Math.max(1, Math.ceil(lines.length / slides));
    const parts = [];
    for (let i = 0; i < slides; i++) {
        const start = i * linesPerSlide;
        const end = Math.min((i + 1) * linesPerSlide, lines.length);
        const slideText = lines.slice(start, end).join('\n').trim();
        parts.push(`# Slide ${i + 1}\n\n${slideText}`);
    }
    return parts.join('\n\n');
}
/** Convert an XLSX file to markdown with table formatting per sheet */
async function xlsxToMarkdown(filePath, maxRows = 20) {
    const buffer = await readFile(filePath);
    const XLSX = await loadXlsx();
    const workbook = XLSX.read(buffer, {
        type: 'buffer',
        cellDates: true,
        cellText: true,
    });
    const parts = [];
    for (const sheetName of workbook.SheetNames) {
        const sheet = workbook.Sheets[sheetName];
        if (!sheet?.['!ref'])
            continue;
        const range = XLSX.utils.decode_range(sheet['!ref']);
        const totalRows = range.e.r - range.s.r + 1;
        const totalCols = range.e.c - range.s.c + 1;
        parts.push(`## ${sheetName}`);
        parts.push('');
        if (totalCols === 0 || totalRows === 0) {
            parts.push('*Empty sheet*');
            parts.push('');
            continue;
        }
        // Read header row
        const headers = [];
        for (let col = range.s.c; col <= range.e.c; col++) {
            const cell = sheet[XLSX.utils.encode_cell({ r: range.s.r, c: col })];
            const raw = cell?.v != null ? String(cell.w ?? cell.v) : XLSX.utils.encode_col(col);
            headers.push(raw.replace(/\|/g, '\\|').replace(/\n/g, ' '));
        }
        // Build markdown table
        parts.push(`| ${headers.join(' | ')} |`);
        parts.push(`| ${headers.map(() => '---').join(' | ')} |`);
        const dataRows = Math.min(maxRows, totalRows - 1);
        for (let row = range.s.r + 1; row <= range.s.r + dataRows; row++) {
            const values = [];
            for (let col = range.s.c; col <= range.e.c; col++) {
                const cell = sheet[XLSX.utils.encode_cell({ r: row, c: col })];
                const val = cell?.v != null ? String(cell.w ?? cell.v) : '';
                values.push(val.replace(/\|/g, '\\|').replace(/\n/g, ' '));
            }
            parts.push(`| ${values.join(' | ')} |`);
        }
        if (totalRows - 1 > maxRows) {
            parts.push('');
            parts.push(`*...${totalRows - 1 - maxRows} more rows*`);
        }
        parts.push('');
    }
    return parts.join('\n');
}
/** Convert a document to markdown. Returns null for unsupported formats (ods). */
export async function documentToMarkdown(filePath) {
    const ext = getExtension(filePath);
    if (RAW_TEXT_EXTENSIONS.has(ext)) {
        // Strip a leading UTF-8 BOM so header/heading detection anchors on the
        // first real character (Node's utf8 decode keeps U+FEFF).
        return (await readFile(filePath, 'utf8')).replace(/^\uFEFF/, '');
    }
    switch (ext) {
        case 'docx':
            return docxToMarkdown(filePath);
        case 'pdf':
            return pdfToMarkdown(filePath);
        case 'pptx':
            return pptxToMarkdown(filePath);
        case 'odt':
            return odtToMarkdown(filePath);
        case 'odp':
            return odpToMarkdown(filePath);
        case 'xlsx':
            return xlsxToMarkdown(filePath);
        case 'ods':
            return null;
        default:
            return null;
    }
}
/**
 * A `documentToMarkdown` that coalesces conversions of the same file:
 * concurrent and repeated calls for one path share a single conversion
 * (including its failure, so a broken document is not re-parsed per caller).
 * Scope one converter per operation (e.g. one workspace bundle) — it holds
 * every converted document's markdown for its lifetime.
 */
export function createSharedMarkdownConverter() {
    const conversions = new Map();
    return filePath => {
        const key = path.resolve(filePath);
        const cached = conversions.get(key);
        if (cached)
            return cached;
        const conversion = documentToMarkdown(filePath);
        conversions.set(key, conversion);
        return conversion;
    };
}
//# sourceMappingURL=convert.js.map