import { MAX_PREVIEW_HEIGHT_SIDE_BY_SIDE, MAX_PREVIEW_HEIGHT_STACKED, MarkdownContentCache, NOTES_AFFORDANCE_OVERHEAD, } from "./markdown-content-cache.js";
import { BORDER_HORIZONTAL_OVERHEAD, BORDER_INNER_PADDING_HORIZONTAL, BORDER_VERTICAL_OVERHEAD, computeBoxDimensions, renderBorderedBox, } from "./preview-box-renderer.js";
/**
 * Affordance text shown below the bordered preview when focused on a preview-bearing option.
 * Re-exported by `preview-pane.ts` for the existing test surface.
 */
export const NOTES_AFFORDANCE_TEXT = "Notes: n add notes";
/**
 * Renders the bordered markdown preview block for a single question (one block per render call,
 * for the option at `optionIndex`). Owns a per-question `MarkdownContentCache`.
 *
 * NOT a `Component` — pure render-and-measure helper consumed by `PreviewPane`. The layout mode
 * is threaded as an explicit param (never re-derived from column width post-split).
 *
 * The affordance row is always emitted (visually empty when gated) so the preview block's row
 * count is height-stable across affordance-state transitions.
 */
export class PreviewBlockRenderer {
    constructor(config) {
        this.theme = config.theme;
        this.cache = new MarkdownContentCache(config.question, config.theme, config.markdownTheme);
    }
    hasAnyPreview() {
        return this.cache.hasAnyPreview();
    }
    has(optionIndex) {
        return this.cache.has(optionIndex);
    }
    invalidate() {
        this.cache.invalidate();
    }
    /**
     * Height contribution of the preview block: `BORDER_VERTICAL_OVERHEAD + contentRows +
     * NOTES_AFFORDANCE_OVERHEAD`. Always returns the same value as `renderBlock(...).length`
     * — the affordance overhead is constant, not gated by `focused`/`notesVisible`.
     */
    blockHeight(width, optionIndex, mode) {
        const cap = mode === "side-by-side" ? MAX_PREVIEW_HEIGHT_SIDE_BY_SIDE : MAX_PREVIEW_HEIGHT_STACKED;
        const contentBudget = Math.max(1, cap - BORDER_VERTICAL_OVERHEAD - NOTES_AFFORDANCE_OVERHEAD);
        const innerWidth = Math.max(1, width - BORDER_HORIZONTAL_OVERHEAD - 2 * BORDER_INNER_PADDING_HORIZONTAL);
        const rawRows = this.cache.bodyFor(optionIndex, innerWidth).length;
        const contentRows = Math.min(rawRows, contentBudget);
        return BORDER_VERTICAL_OVERHEAD + contentRows + NOTES_AFFORDANCE_OVERHEAD;
    }
    /**
     * Render the full preview block at `width`: bordered box + blank separator + affordance row.
     * `focused` and `notesVisible` together gate the affordance text (visible only when the
     * focused option carries a preview AND notes mode is inactive). The affordance row is ALWAYS
     * emitted (as an empty string when gated) so the row count is invariant.
     */
    renderBlock(width, optionIndex, mode, focused, notesVisible) {
        const cap = mode === "side-by-side" ? MAX_PREVIEW_HEIGHT_SIDE_BY_SIDE : MAX_PREVIEW_HEIGHT_STACKED;
        const contentBudget = Math.max(1, cap - BORDER_VERTICAL_OVERHEAD - NOTES_AFFORDANCE_OVERHEAD);
        const maxInnerWidth = Math.max(1, width - BORDER_HORIZONTAL_OVERHEAD - 2 * BORDER_INNER_PADDING_HORIZONTAL);
        const raw = this.cache.bodyFor(optionIndex, maxInnerWidth);
        const truncated = raw.length > contentBudget;
        const hidden = truncated ? raw.length - contentBudget : 0;
        const contentLines = truncated ? raw.slice(0, contentBudget) : raw;
        const { boxWidth } = computeBoxDimensions(contentLines, maxInnerWidth);
        const colorFn = (s) => this.theme.fg("accent", s);
        const boxedLines = renderBorderedBox(contentLines, boxWidth, colorFn, hidden);
        const showAffordance = focused && !notesVisible && this.cache.has(optionIndex);
        const affordance = showAffordance
            ? this.theme.fg("muted", NOTES_AFFORDANCE_TEXT)
            : "";
        return [...boxedLines, "", affordance];
    }
}
//# sourceMappingURL=preview-block-renderer.js.map