/**
 * AnimatedStatus - Provides smooth, dynamic status indicators with animations
 * Integrates with the AnimationScheduler for real-time UI updates
 */
import { theme, spinnerFrames, progressChars } from './theme.js';
import { AnimationScheduler } from './animation/AnimationScheduler.js';
import { clampPercentage, getContextColor } from './uiConstants.js';
// Singleton scheduler for global animation coordination
let globalScheduler = null;
let schedulerDisposed = false; // Prevent recreation after disposal
let schedulerRefCount = 0; // Reference counting for proper cleanup
/**
 * Get or create the global animation scheduler
 * Uses lazy initialization and reference counting
 */
function getScheduler() {
    if (schedulerDisposed) {
        // Return a no-op scheduler if disposed to prevent recreation
        // This handles edge cases where animations try to register after cleanup
        if (!globalScheduler) {
            globalScheduler = createNoOpScheduler();
        }
        return globalScheduler;
    }
    if (!globalScheduler) {
        globalScheduler = new AnimationScheduler(30); // 30 FPS for smooth animations
    }
    schedulerRefCount++;
    return globalScheduler;
}
/**
 * Create a no-op scheduler for disposed state
 */
function createNoOpScheduler() {
    const scheduler = new AnimationScheduler(30);
    scheduler.dispose(); // Immediately dispose - it's a stub
    return scheduler;
}
/**
 * Release a reference to the scheduler
 */
function releaseScheduler() {
    if (schedulerRefCount > 0) {
        schedulerRefCount--;
    }
}
/**
 * Animated spinner that cycles through frames
 */
export class AnimatedSpinner {
    frameIndex = 0;
    frames;
    intervalId = null;
    message;
    color;
    currentFrame = '';
    onUpdate;
    constructor(message = '', style = 'dots', color = theme.info) {
        this.frames = spinnerFrames[style];
        this.message = message;
        this.color = color;
        this.currentFrame = this.frames[0] ?? '⠋';
    }
    start(onUpdate) {
        this.onUpdate = onUpdate;
        this.frameIndex = 0;
        this.intervalId = setInterval(() => {
            this.frameIndex = (this.frameIndex + 1) % this.frames.length;
            this.currentFrame = this.frames[this.frameIndex] ?? '⠋';
            if (this.onUpdate) {
                this.onUpdate(this.render());
            }
        }, 80); // ~12 FPS for smooth spinner animations
    }
    stop() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
        this.onUpdate = undefined;
    }
    setMessage(message) {
        this.message = message;
    }
    render() {
        const spinnerChar = this.color(this.currentFrame);
        return this.message ? `${spinnerChar} ${this.message}` : spinnerChar;
    }
    isRunning() {
        return this.intervalId !== null;
    }
    /**
     * Dispose of the spinner and clean up resources
     */
    dispose() {
        this.stop();
    }
}
/**
 * Animated progress bar with smooth transitions
 */
export class AnimatedProgressBar {
    current = 0;
    target = 0;
    total;
    width;
    animationId = null;
    // Store the bound listener so we can remove it to prevent memory leaks
    transitionHandler = null;
    constructor(total, width = 20) {
        this.total = total;
        this.width = width;
    }
    update(value) {
        this.target = Math.min(value, this.total);
        // Smooth transition to target
        const scheduler = getScheduler();
        // Remove old listener before creating new animation to prevent accumulation
        if (this.transitionHandler) {
            scheduler.removeListener('transition:update', this.transitionHandler);
            this.transitionHandler = null;
        }
        if (this.animationId) {
            scheduler.unregister(this.animationId);
        }
        this.animationId = `progress-${Date.now()}`;
        scheduler.createTransition(this.animationId, this.current, this.target, 'progress', 300, AnimationScheduler.Easing.easeOutQuad);
        // Create and store the listener for proper cleanup
        this.transitionHandler = (data) => {
            if (data.id === this.animationId && typeof data.value === 'number') {
                this.current = data.value;
            }
        };
        scheduler.on('transition:update', this.transitionHandler);
    }
    render() {
        const percentage = this.total > 0 ? this.current / this.total : 0;
        const filled = Math.floor(percentage * this.width);
        const partial = (percentage * this.width) % 1;
        const empty = this.width - filled - (partial > 0 ? 1 : 0);
        let bar = theme.progress.bar(progressChars.filled.repeat(filled));
        if (partial > 0 && filled < this.width) {
            const partialIndex = Math.floor(partial * progressChars.partial.length);
            const partialChar = progressChars.partial[partialIndex] ?? progressChars.partial[0];
            bar += theme.progress.bar(partialChar);
        }
        bar += theme.progress.empty(progressChars.empty.repeat(Math.max(0, empty)));
        const percentText = theme.progress.percentage(`${Math.round(percentage * 100)}%`);
        return `${bar} ${percentText}`;
    }
    dispose() {
        // Remove the transition listener to prevent memory leaks
        if (this.transitionHandler) {
            try {
                getScheduler().removeListener('transition:update', this.transitionHandler);
            }
            catch {
                // Ignore errors during cleanup
            }
            this.transitionHandler = null;
        }
        if (this.animationId) {
            try {
                getScheduler().unregister(this.animationId);
            }
            catch {
                // Ignore errors during cleanup
            }
            this.animationId = null;
        }
        releaseScheduler();
    }
}
/**
 * Animated elapsed time display
 */
export class AnimatedElapsedTime {
    startTime;
    intervalId = null;
    currentDisplay = '0s';
    onUpdate;
    constructor() {
        this.startTime = Date.now();
    }
    start(onUpdate) {
        this.startTime = Date.now();
        this.onUpdate = onUpdate;
        this.intervalId = setInterval(() => {
            this.currentDisplay = this.format();
            if (this.onUpdate) {
                this.onUpdate(this.currentDisplay);
            }
        }, 1000); // Update every second
    }
    stop() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
        this.onUpdate = undefined;
        return this.format();
    }
    format() {
        const elapsed = Date.now() - this.startTime;
        const seconds = Math.floor(elapsed / 1000);
        const minutes = Math.floor(seconds / 60);
        const hours = Math.floor(minutes / 60);
        if (hours > 0) {
            return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
        }
        else if (minutes > 0) {
            return `${minutes}:${(seconds % 60).toString().padStart(2, '0')}`;
        }
        else {
            return `${seconds}s`;
        }
    }
    render() {
        return theme.ui.muted(this.currentDisplay);
    }
    dispose() {
        this.stop();
    }
}
/**
 * Pulsing text effect for emphasis
 */
export class PulsingText {
    text;
    intensity = 0;
    increasing = true;
    intervalId = null;
    onUpdate;
    constructor(text) {
        this.text = text;
    }
    start(onUpdate) {
        this.onUpdate = onUpdate;
        this.intervalId = setInterval(() => {
            if (this.increasing) {
                this.intensity += 0.1;
                if (this.intensity >= 1) {
                    this.intensity = 1;
                    this.increasing = false;
                }
            }
            else {
                this.intensity -= 0.1;
                if (this.intensity <= 0.3) {
                    this.intensity = 0.3;
                    this.increasing = true;
                }
            }
            if (this.onUpdate) {
                this.onUpdate(this.render());
            }
        }, 50); // 20 FPS for pulse
    }
    stop() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
        this.onUpdate = undefined;
    }
    setText(text) {
        this.text = text;
    }
    render() {
        // Use intensity to vary between dim and bright
        if (this.intensity > 0.7) {
            return theme.ui.highlight(this.text);
        }
        else if (this.intensity > 0.4) {
            return theme.info(this.text);
        }
        else {
            return theme.ui.muted(this.text);
        }
    }
    dispose() {
        this.stop();
    }
}
/**
 * Thinking indicator with animated dots
 */
export class ThinkingIndicator {
    dotCount = 0;
    intervalId = null;
    label;
    onUpdate;
    constructor(label = 'Thinking') {
        this.label = label;
    }
    start(onUpdate) {
        this.onUpdate = onUpdate;
        this.dotCount = 0;
        this.intervalId = setInterval(() => {
            this.dotCount = (this.dotCount + 1) % 4;
            if (this.onUpdate) {
                this.onUpdate(this.render());
            }
        }, 400); // Slower animation for thinking
    }
    stop() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
        this.onUpdate = undefined;
    }
    setLabel(label) {
        this.label = label;
    }
    render() {
        const dots = '.'.repeat(this.dotCount);
        const padding = ' '.repeat(3 - this.dotCount);
        return `${theme.thinking.icon('◐')} ${theme.thinking.label(this.label)}${theme.thinking.text(dots)}${padding}`;
    }
    dispose() {
        this.stop();
    }
}
/**
 * Streaming status with spinner and message
 */
export class StreamingStatus {
    spinner;
    elapsed;
    isActive = false;
    constructor() {
        this.spinner = new AnimatedSpinner('', 'braille', theme.info);
        this.elapsed = new AnimatedElapsedTime();
    }
    start(message, onUpdate) {
        this.isActive = true;
        this.spinner.setMessage(message);
        const update = () => {
            if (this.isActive && onUpdate) {
                const spinnerPart = this.spinner.render();
                const elapsedPart = this.elapsed.render();
                onUpdate(`${spinnerPart} ${theme.ui.muted('·')} ${elapsedPart}`);
            }
        };
        this.spinner.start(update);
        this.elapsed.start(update);
    }
    updateMessage(message) {
        this.spinner.setMessage(message);
    }
    stop() {
        this.isActive = false;
        this.spinner.stop();
        const finalTime = this.elapsed.stop();
        return `${theme.success('✓')} ${theme.ui.muted('Completed')} ${theme.ui.muted('·')} ${theme.ui.muted(finalTime)}`;
    }
}
/**
 * Context usage meter with color transitions
 */
export class ContextMeter {
    percentage = 0;
    targetPercentage = 0;
    animationFrame = null;
    update(percentage) {
        this.targetPercentage = Math.min(100, Math.max(0, percentage));
        this.animate();
    }
    animate() {
        if (this.animationFrame) {
            clearTimeout(this.animationFrame);
        }
        const step = () => {
            const diff = this.targetPercentage - this.percentage;
            if (Math.abs(diff) < 0.5) {
                this.percentage = this.targetPercentage;
                return;
            }
            // Smooth easing
            this.percentage += diff * 0.15;
            this.animationFrame = setTimeout(step, 16); // ~60 FPS
        };
        step();
    }
    render() {
        const pct = Math.round(clampPercentage(this.percentage));
        const color = getContextColor(pct, {
            error: theme.error,
            warning: theme.warning,
            info: theme.info,
            success: theme.success,
        });
        return `${theme.ui.muted('ctx')} ${color(`${pct}%`)}`;
    }
    dispose() {
        if (this.animationFrame) {
            clearTimeout(this.animationFrame);
        }
    }
}
/**
 * Animated typing indicator (for showing AI is responding)
 */
export class TypingIndicator {
    frames = ['▌', '▐', '▌', ' '];
    frameIndex = 0;
    intervalId = null;
    onUpdate;
    start(onUpdate) {
        this.onUpdate = onUpdate;
        this.intervalId = setInterval(() => {
            this.frameIndex = (this.frameIndex + 1) % this.frames.length;
            if (this.onUpdate) {
                this.onUpdate(this.render());
            }
        }, 530); // Blink at cursor rate
    }
    stop() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
        this.onUpdate = undefined;
    }
    render() {
        return theme.info(this.frames[this.frameIndex] ?? '▌');
    }
    dispose() {
        this.stop();
    }
}
/**
 * Wave animation for processing states
 */
export class WaveIndicator {
    frames = ['∙∙∙∙∙', '●∙∙∙∙', '∙●∙∙∙', '∙∙●∙∙', '∙∙∙●∙', '∙∙∙∙●', '∙∙∙∙∙'];
    frameIndex = 0;
    intervalId = null;
    onUpdate;
    start(onUpdate) {
        this.onUpdate = onUpdate;
        this.intervalId = setInterval(() => {
            this.frameIndex = (this.frameIndex + 1) % this.frames.length;
            if (this.onUpdate) {
                this.onUpdate(this.render());
            }
        }, 120);
    }
    stop() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
        this.onUpdate = undefined;
    }
    render() {
        return theme.info(this.frames[this.frameIndex] ?? '∙∙∙∙∙');
    }
    dispose() {
        this.stop();
    }
}
// Export singleton cleanup
export function disposeAnimations() {
    schedulerDisposed = true; // Prevent recreation
    if (globalScheduler) {
        globalScheduler.dispose();
        globalScheduler = null;
    }
}
// Reset function for testing or re-initialization scenarios
export function resetAnimationScheduler() {
    if (globalScheduler) {
        globalScheduler.dispose();
        globalScheduler = null;
    }
    schedulerDisposed = false;
}
//# sourceMappingURL=animatedStatus.js.map