<!--
    Component: Line
    Renders one laid-out line as three regions: a fontA bulk run (past the
    slider), an N-char crossfade window straddling it, and a fontB bulk run (not
    yet past). Bulk runs are native shaped text (kerning, ligatures); only the
    window uses per-char DOM. `split` is a primitive so the render-model
    `$derived` skips recomputation on ticks that leave it unchanged.
-->
<script lang="ts">
import {
    type ComparisonLine,
    computeLineRenderModel,
    windowSizeForLine,
} from '$entities/Font';
import { getTypographySettingsStore } from '$features/AdjustTypography';
import { getComparisonStore } from '../../model';
import Character from '../Character/Character.svelte';

interface Props {
    /**
     * Laid-out line from `DualFontLayout.layout()`. Stable across slider movement.
     */
    line: ComparisonLine;
    /**
     * Count of chars the slider has passed, from `findSplitIndex`.
     */
    split: number;
}

let { line, split }: Props = $props();

const comparisonStore = getComparisonStore();

const windowSize = $derived(windowSizeForLine(line.chars.length));

const model = $derived(computeLineRenderModel(line, split, windowSize));

const typography = getTypographySettingsStore();
const fontA = $derived(comparisonStore.fontA);
const fontB = $derived(comparisonStore.fontB);

const fontSizePx = $derived(typography.renderedSize);
const lineHeightPx = $derived(typography.height * typography.renderedSize);
const letterSpacingPx = $derived(typography.spacing * typography.renderedSize);

/**
 * Class and style are single short bindings so the formatter keeps
 * `<span ...>{text}</span>` on one line. A wrapped text expression would leak
 * its indentation into the span content under `white-space: pre`.
 */
const BULK_LEFT_CLASS =
    'inline-block align-baseline leading-none text-swiss-black/75 dark:text-brand/75 transition-colors duration-300';
const BULK_RIGHT_CLASS =
    'inline-block align-baseline leading-none text-neutral-950 dark:text-white transition-colors duration-300';

const leftStyle = $derived(`font-family:${fontA?.name ?? ''};font-size:${fontSizePx}px`);
const rightStyle = $derived(`font-family:${fontB?.name ?? ''};font-size:${fontSizePx}px`);

/**
 * Stops the whole line from jumping up or down as the slider moves. The browser
 * pins a line box's baseline to its tallest inline box, so without a fixed
 * reference the baseline (and every glyph) shifts the moment a bulk run appears
 * or disappears, or the last window char morphs to a font with a taller ascent.
 * This invisible strut is always the tallest box — `overflow: hidden` puts its
 * baseline at its bottom edge — so it owns the line baseline and holds it still.
 * Its height also sets the text's vertical position (the container is block, so
 * nothing else centers it).
 *
 * Height factors are empirical: the first term centers the text, the `* 1.1`
 * floor keeps the strut above the fonts' ascent at tight line-heights.
 */
const strutHeightPx = $derived(Math.max(lineHeightPx / 2 + fontSizePx * 0.34, fontSizePx * 1.1));
const strutStyle = $derived(
    `display:inline-block;width:0;overflow:hidden;vertical-align:baseline;height:${strutHeightPx}px`,
);
</script>

<!--
    Children align on the baseline (`align-baseline`) so fontA/fontB share it
    despite differing metrics. `font-size: 0` drops inter-element whitespace that
    would show as gaps under `white-space: pre`; children restore their size.
    Letter-spacing is px because em would resolve against that zero.
-->
{#if fontA && fontB}
    <div
        class="relative block w-full text-center whitespace-pre"
        style:height="{lineHeightPx}px"
        style:line-height="{lineHeightPx}px"
        style:font-size="0"
        style:letter-spacing="{letterSpacingPx}px"
        style:font-weight={typography.weight}
    >
        <span style={strutStyle} aria-hidden="true"></span>
        {#if model.leftText}
            <span class={BULK_LEFT_CLASS} style={leftStyle}>{model.leftText}</span>
        {/if}
        {#each model.windowChars as wc (wc.key)}
            <Character char={wc.char} {fontA} {fontB} isPast={wc.isPast} fontSize={fontSizePx} />
        {/each}
        {#if model.rightText}
            <span class={BULK_RIGHT_CLASS} style={rightStyle}>{model.rightText}</span>
        {/if}
    </div>
{/if}
