/**
 * Asset document text extraction.
 *
 * A pure helper that turns a document asset's bytes into searchable text. Under
 * asset-as-embed the extracted text is seeded straight into the imported
 * entry's BODY (in-memory), so there is no on-disk sidecar to persist — the
 * import path (`asset-import-service.ts`) is the only caller. (Image captions
 * are a separate async pass — `asset-index-sweep.ts` — and land in
 * `assets.db.caption`, not here.)
 *
 * The `writeAssetSidecar` persistence helper + the `.studiograph/assets-text/`
 * tree were removed with the entity-keyed caption pipeline; the `Sidecar` in
 * the name is historical.
 */
import { log } from '../utils/log.js';
/** Bytes above which we skip request-thread text extraction. The parsers
 *  (pdf-parse / mammoth / SheetJS) are CPU-bound and synchronous; a huge file
 *  would stall the event loop for every other in-flight request. The asset
 *  still uploads — it just isn't sidecar-searchable until a later re-extract. */
const SIDECAR_EXTRACT_BYTES_CAP = 25 * 1024 * 1024;
/**
 * Extract searchable text from a document asset for its index sidecar (P3 —
 * generalized from PDF-only). Driven by the format registry's `extractor`:
 * pdf → pdf-parse (also yields page count), docx → mammoth raw text, xlsx →
 * every non-empty sheet as CSV. `image-caption` and `null` produce no sidecar
 * here — image captioning is the async P3b pass, opaque assets stay
 * filename-only. Best-effort: extraction errors return empty text (the asset
 * is still stored + indexed by frontmatter).
 */
export async function extractSidecarText(buffer, extractor, entityId) {
    if (!extractor || extractor === 'image-caption')
        return { text: '' };
    if (buffer.length > SIDECAR_EXTRACT_BYTES_CAP) {
        log.warn(`[asset-upload] sidecar extraction skipped for ${entityId}: ${buffer.length} bytes > ${SIDECAR_EXTRACT_BYTES_CAP} cap`);
        return { text: '' };
    }
    try {
        if (extractor === 'pdf') {
            const { PDFParse } = await import('pdf-parse');
            const parser = new PDFParse({ data: buffer });
            try {
                // pageJoiner: '' suppresses pdf-parse's default `-- N of M --` page
                // markers, which otherwise litter both the search sidecar AND the
                // seeded document body (asset-import-service reuses this text).
                const result = await parser.getText({ pageJoiner: '' });
                return { text: result.text?.trim() ?? '', pages: result.total && result.total > 0 ? result.total : undefined };
            }
            finally {
                // Always release the parser — on an encrypted/malformed PDF getText()
                // throws, and without finally the handle/buffers would leak.
                await parser.destroy();
            }
        }
        if (extractor === 'docx') {
            const mammoth = await import('mammoth');
            const result = await mammoth.extractRawText({ buffer });
            return { text: result.value?.trim() ?? '' };
        }
        if (extractor === 'xlsx') {
            const XLSX = await import('xlsx');
            const wb = XLSX.read(buffer, { type: 'buffer' });
            const parts = [];
            for (const name of wb.SheetNames) {
                const sheet = wb.Sheets[name];
                if (!sheet || !sheet['!ref'])
                    continue;
                const csv = XLSX.utils.sheet_to_csv(sheet, { blankrows: false }).trim();
                if (csv)
                    parts.push(`# ${name}\n${csv}`);
            }
            return { text: parts.join('\n\n').trim() };
        }
    }
    catch (err) {
        log.warn(`[asset-extract] text extraction failed for ${entityId} (${extractor}): ${err instanceof Error ? err.message : err}`);
    }
    return { text: '' };
}
//# sourceMappingURL=asset-sidecar-service.js.map