import type { TUI } from "../tui";
import { sliceByColumn, visibleWidth } from "../utils";
import { Text } from "./text";

/**
 * Loader component that drives display refresh at ~60fps so callers whose
 * message colorizer is time-dependent (e.g. shimmer/KITT) animate smoothly.
 *
 * Two cadences are interleaved on a single timer:
 *   - **Render tick** (every `RENDER_INTERVAL_MS`) → asks the TUI to redraw.
 *     The TUI already throttles at 16ms (`MIN_RENDER_INTERVAL_MS`), so this
 *     is the natural upper bound; static messageColorFns produce identical
 *     output and the differ drops the no-op redraw at ~zero cost.
 *   - **Spinner advance** (every `SPINNER_ADVANCE_MS`) → bumps the spinner
 *     frame index. Decoupled from the render cadence so the spinner keeps
 *     its classic ~12.5fps step pace regardless of shimmer state.
 */
const RENDER_INTERVAL_MS = 16;
const SPINNER_ADVANCE_MS = 80;

export class Loader extends Text {
	#frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
	#currentFrame = 0;
	#intervalId?: NodeJS.Timeout;
	#ui: TUI | null = null;
	#lastSpinnerTick = 0;

	constructor(
		ui: TUI,
		private spinnerColorFn: (str: string) => string,
		private messageColorFn: (str: string) => string,
		private message: string = "Loading...",
		spinnerFrames?: string[],
	) {
		super("", 1, 0);
		this.#ui = ui;
		if (spinnerFrames && spinnerFrames.length > 0) {
			this.#frames = spinnerFrames;
		}
		this.start();
	}

	render(width: number): string[] {
		const lines = ["", ...super.render(width)];
		for (let i = 0; i < lines.length; i++) {
			const line = lines[i];
			if (visibleWidth(line) > width) {
				lines[i] = sliceByColumn(line, 0, width, true);
			}
		}
		return lines;
	}

	start() {
		this.#lastSpinnerTick = performance.now();
		this.#updateDisplay();
		this.#intervalId = setInterval(() => {
			const now = performance.now();
			if (now - this.#lastSpinnerTick >= SPINNER_ADVANCE_MS) {
				this.#currentFrame = (this.#currentFrame + 1) % this.#frames.length;
				this.#lastSpinnerTick = now;
			}
			this.#updateDisplay();
		}, RENDER_INTERVAL_MS);
	}

	stop() {
		if (this.#intervalId) {
			clearInterval(this.#intervalId);
			this.#intervalId = undefined;
		}
	}

	setMessage(message: string) {
		this.message = message;
		this.#updateDisplay();
	}

	#updateDisplay() {
		const frame = this.#frames[this.#currentFrame];
		this.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);
		if (this.#ui) {
			this.#ui.requestRender();
		}
	}
}