/**
 * GENE Motion — Scroll Hero free-placement timeline: shared constants + pure
 * geometry. Imported by save.js and edit.js (which need the resolved defaults),
 * and by view.js (which needs only the fixed transition amounts — it reads each
 * beat's placement/window from the DOM data-* attributes save writes).
 *
 * The model: in timeline mode each heading is placed at a center-anchored
 * (x%, y%) over the pinned hero and lives inside a scroll window [in, out]
 * (fractions of the runway). It enters over a fixed lead-in, holds through the
 * window, then leaves over a fixed lead-out — all scrubbed by scroll position,
 * so scrolling back reverses it. Enter/exit length and travel are fixed here on
 * purpose (never exposed in the UI, so placements can't look janky).
 */

// Fraction of the runway spent on the enter / exit transition of each heading.
export const ENTER = 0.12;
export const EXIT = 0.12;
// Travel distance of the movement presets, in px.
export const RISE = 44;
export const SLIDE = 64;

export function num( v, d ) {
	return typeof v === 'number' && isFinite( v ) ? v : d;
}

/** Center-anchored placement for a beat (percent). Mobile falls back to desktop. */
export function beatPos( b, mobile ) {
	const x = mobile && typeof b.xM === 'number' ? b.xM : num( b.x, 50 );
	const y = mobile && typeof b.yM === 'number' ? b.yM : num( b.y, 50 );
	return { x, y };
}

/**
 * The beat's scroll window as fractions 0..1. When the author hasn't set in/out
 * we fall back to an even split by index, so a freshly placed heading already
 * has a sensible window and just works.
 */
export function beatWindow( i, n, b ) {
	const inD = n ? i / n : 0;
	const outD = n ? ( i + 1 ) / n : 1;
	let bi = typeof b.in === 'number' ? b.in / 100 : inD;
	let bo = typeof b.out === 'number' ? b.out / 100 : outD;
	bi = bi < 0 ? 0 : bi > 1 ? 1 : bi;
	bo = bo < 0 ? 0 : bo > 1 ? 1 : bo;
	if ( bo < bi ) bo = bi;
	return { in: bi, out: bo };
}

/**
 * Per-element type controls (timeline only): the heading and the sub-line each
 * choose their own alignment (left/center/right) and size (60–200%). `which` is
 * 'head' or 'sub'. Defaults (centered, 100%) keep the current look, and emit
 * nothing, so untouched beats save byte-identically.
 */
export function beatAlign( b, which ) {
	const v = b && b[ which === 'sub' ? 'subAlign' : 'headAlign' ];
	return v === 'left' || v === 'right' ? v : 'center';
}
export function beatScale( b, which ) {
	const s = b && Number( b[ which === 'sub' ? 'subScale' : 'headScale' ] );
	return isFinite( s ) && s >= 60 && s <= 200 ? s : 100;
}
// Inline style for the heading element: its own text-align + size var (the
// stylesheet multiplies --gene-hero-beat-scale into the font-size). Only
// non-defaults are written.
export function headStyle( b ) {
	const st = {};
	const a = beatAlign( b, 'head' );
	if ( a !== 'center' ) st.textAlign = a;
	const s = beatScale( b, 'head' );
	if ( s !== 100 ) st[ '--gene-hero-beat-scale' ] = String( s / 100 );
	return st;
}
// Inline style for the sub-line: its own text-align, block-margin vars for
// left/right, and its own size var.
export function subStyle( b ) {
	const st = {};
	const a = beatAlign( b, 'sub' );
	if ( a === 'left' ) { st.textAlign = 'left'; st[ '--gene-hero-sub-ml' ] = '0'; }
	else if ( a === 'right' ) { st.textAlign = 'right'; st[ '--gene-hero-sub-mr' ] = '0'; }
	const s = beatScale( b, 'sub' );
	if ( s !== 100 ) st[ '--gene-hero-beat-scale' ] = String( s / 100 );
	return st;
}
// Inline style on the beat so its CTA buttons follow the heading's alignment.
export function beatActionsStyle( b ) {
	const a = beatAlign( b, 'head' );
	if ( a === 'left' ) return { '--gene-hero-actions-justify': 'flex-start' };
	if ( a === 'right' ) return { '--gene-hero-actions-justify': 'flex-end' };
	return {};
}
