import type { MountedVisualizationHandle } from '@astrato-io/visualizations'; /** Orders mounting against visibility changes; readiness must never block that ordering. */ export function createPanelLifecycle(options: { mount: () => Promise; onState: (state: string, message: string) => void; }) { let visible = true; let destroyed = false; let empty = false; let state = ''; const setState = (value: string, message: string) => { state = value; options.onState(value, message); }; let handle: MountedVisualizationHandle | undefined; let lifecycle: Promise = Promise.resolve(); function reconcile(force = false) { lifecycle = lifecycle .catch(() => undefined) .then(async () => { if (force || !visible || destroyed) { handle?.unmount(); handle = undefined; } if (!visible || destroyed) { setState('hidden', 'Chart is hidden and unmounted.'); return; } if (handle) return; setState('loading', 'Loading live data…'); try { const mounted = await options.mount(); if (!visible || destroyed) { mounted.unmount(); return; } handle = mounted; void mounted.ready .then((result) => { if (handle !== mounted || destroyed || !visible) return; if (result.status === 'rendered') setState(empty ? 'empty' : 'ready', empty ? 'No data for the applied filters.' : ''); else if (result.status === 'timeout') setState('timeout', 'Rendering is taking longer than expected.'); else if (result.status === 'error') setState('error', 'This chart could not render.'); }) .catch(() => { if (handle === mounted && visible && !destroyed) setState('error', 'This chart could not render.'); }); } catch (error) { if (visible && !destroyed) setState('error', error instanceof Error ? error.message : 'Mount failed.'); } }); return lifecycle; } return { start: () => reconcile(), show() { if (destroyed) return lifecycle; visible = true; return reconcile(); }, hide() { visible = false; return reconcile(); }, retry: () => reconcile(true), setEmpty(value: boolean) { empty = value; if (handle && visible && !destroyed && ['ready', 'empty'].includes(state)) setState(empty ? 'empty' : 'ready', empty ? 'No data for the applied filters.' : ''); }, destroy() { destroyed = true; return reconcile(); }, }; }