import type { Accessor, Element as JSXElement } from "solid-js";
import { useClock } from "./clock.ts";

/**
 * A per-frame color field over a region: given a glyph cell's local position
 * `(x, y)` within a `width`×`height` box, return its packed 24-bit fg, or
 * `undefined` to leave the painted color as-is.
 */
export type AnimationField = (
	x: number,
	y: number,
	width: number,
	height: number,
) => number | undefined;

/** What an {@link Effect} is handed each frame to build its field. */
export type EffectEnv = {
	/** Animation clock (ms since mount). */
	t: number;
	/** True while the group is paused. */
	paused: boolean;
};

/**
 * A reusable animation. Called every frame with the current {@link EffectEnv},
 * it returns the {@link AnimationField} sampled for each glyph cell. See
 * `shimmer`, `wave`, and `pulse` in `./effects.ts`.
 */
export type Effect = (env: EffectEnv) => AnimationField;

export type AnimatedProps = {
	children: JSXElement;
	/** The animation to run across the region (e.g. `shimmer(...)`). */
	effect: Effect;
	/** Clock tick in ms — the animation's temporal resolution. Default 50. */
	tick?: number;
	/** Freeze the animation (effects fall back to their resting color). */
	paused?: boolean;
	/** Share an external clock instead of creating one. */
	time?: Accessor<number>;
};

/**
 * Runs an {@link Effect} as a paint-time color filter over its children, so the
 * animation sweeps across whatever they paint — text, a spinner glyph, nested
 * boxes — purely by screen position. Children stay plain; nothing needs to be
 * animation-aware:
 *
 * ```tsx
 * <Animated effect={shimmer({ baseColor: "#666", highlightColor: "#fff" })}>
 *   <Spinner />
 *   <text>{` ${verb}…`}</text>
 * </Animated>
 * ```
 *
 * The named `Shimmer` / `Wave` / `Pulse` wrappers cover the common effects.
 */
export function Animated(props: AnimatedProps) {
	const time = props.time ?? useClock(props.tick ?? 50);
	// Rebuilds the field each tick; the changed prop re-paints the region.
	const colorField = () =>
		props.effect({ t: time(), paused: props.paused ?? false });
	return (
		<box flexDirection="row" colorField={colorField()}>
			{props.children}
		</box>
	);
}
