import { jsx as _jsx } from "react/jsx-runtime";
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import React from 'react';
import { render } from 'ink-testing-library';
import { Text } from 'ink';
import { LoadingIndicator } from './LoadingIndicator.js';
import { StreamingContext } from '../contexts/StreamingContext.js';
import { StreamingState } from '../types.js';
import { vi } from 'vitest';
import * as useTerminalSize from '../hooks/useTerminalSize.js';
// Mock HopCodeRespondingSpinner
vi.mock('./HopCodeRespondingSpinner.js', () => ({
    HopCodeRespondingSpinner: ({ nonRespondingDisplay, }) => {
        const streamingState = React.useContext(StreamingContext);
        if (streamingState === StreamingState.Responding) {
            return _jsx(Text, { children: "MockRespondingSpinner" });
        }
        else if (nonRespondingDisplay) {
            return _jsx(Text, { children: nonRespondingDisplay });
        }
        return null;
    },
}));
vi.mock('../hooks/useTerminalSize.js', () => ({
    useTerminalSize: vi.fn(),
}));
const useTerminalSizeMock = vi.mocked(useTerminalSize.useTerminalSize);
const renderWithContext = (ui, streamingStateValue, width = 120) => {
    useTerminalSizeMock.mockReturnValue({ columns: width, rows: 24 });
    const contextValue = streamingStateValue;
    return render(_jsx(StreamingContext.Provider, { value: contextValue, children: ui }));
};
describe('<LoadingIndicator />', () => {
    const defaultProps = {
        currentLoadingPhrase: 'Loading...',
        elapsedTime: 5,
    };
    it('should not render when streamingState is Idle', () => {
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps }), StreamingState.Idle);
        expect(lastFrame()).toBe('');
    });
    it('should render spinner, phrase, and time when streamingState is Responding', () => {
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps }), StreamingState.Responding);
        const output = lastFrame();
        expect(output).toContain('MockRespondingSpinner');
        expect(output).toContain('Loading...');
        expect(output).toContain('5s');
        expect(output).toContain('esc to cancel');
    });
    it('should render spinner (static), phrase but no time/cancel when streamingState is WaitingForConfirmation', () => {
        const props = {
            currentLoadingPhrase: 'Confirm action',
            elapsedTime: 10,
        };
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...props }), StreamingState.WaitingForConfirmation);
        const output = lastFrame();
        expect(output).toContain('⠏'); // Static char for WaitingForConfirmation
        expect(output).toContain('Confirm action');
        expect(output).not.toContain('(esc to cancel)');
        expect(output).not.toContain('10s');
    });
    it('should display the currentLoadingPhrase correctly', () => {
        const props = {
            currentLoadingPhrase: 'Processing data...',
            elapsedTime: 3,
        };
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...props }), StreamingState.Responding);
        expect(lastFrame()).toContain('Processing data...');
    });
    it('should display the elapsedTime correctly when Responding', () => {
        const props = {
            currentLoadingPhrase: 'Working...',
            elapsedTime: 60,
        };
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...props }), StreamingState.Responding);
        expect(lastFrame()).toContain('(1m · esc to cancel)');
    });
    it('should display the elapsedTime correctly in human-readable format', () => {
        const props = {
            currentLoadingPhrase: 'Working...',
            elapsedTime: 125,
        };
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...props }), StreamingState.Responding);
        expect(lastFrame()).toContain('(2m 5s · esc to cancel)');
    });
    it('should render rightContent when provided', () => {
        const rightContent = _jsx(Text, { children: "Extra Info" });
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, rightContent: rightContent }), StreamingState.Responding);
        expect(lastFrame()).toContain('Extra Info');
    });
    it('should transition correctly between states using rerender', () => {
        const { lastFrame, rerender } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps }), StreamingState.Idle);
        expect(lastFrame()).toBe(''); // Initial: Idle
        // Transition to Responding
        rerender(_jsx(StreamingContext.Provider, { value: StreamingState.Responding, children: _jsx(LoadingIndicator, { currentLoadingPhrase: "Now Responding", elapsedTime: 2 }) }));
        let output = lastFrame();
        expect(output).toContain('MockRespondingSpinner');
        expect(output).toContain('Now Responding');
        expect(output).toContain('(2s · esc to cancel)');
        // Transition to WaitingForConfirmation
        rerender(_jsx(StreamingContext.Provider, { value: StreamingState.WaitingForConfirmation, children: _jsx(LoadingIndicator, { currentLoadingPhrase: "Please Confirm", elapsedTime: 15 }) }));
        output = lastFrame();
        expect(output).toContain('⠏');
        expect(output).toContain('Please Confirm');
        expect(output).not.toContain('(esc to cancel)');
        expect(output).not.toContain('15s');
        // Transition back to Idle
        rerender(_jsx(StreamingContext.Provider, { value: StreamingState.Idle, children: _jsx(LoadingIndicator, { ...defaultProps }) }));
        expect(lastFrame()).toBe('');
    });
    it('should display fallback phrase if thought is empty', () => {
        const props = {
            thought: null,
            currentLoadingPhrase: 'Loading...',
            elapsedTime: 5,
        };
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...props }), StreamingState.Responding);
        const output = lastFrame();
        expect(output).toContain('Loading...');
    });
    it('should display the subject of a thought', () => {
        const props = {
            thought: {
                subject: 'Thinking about something...',
                description: 'and other stuff.',
            },
            elapsedTime: 5,
        };
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...props }), StreamingState.Responding);
        const output = lastFrame();
        expect(output).toBeDefined();
        if (output) {
            expect(output).toContain('Thinking about something...');
            expect(output).not.toContain('and other stuff.');
        }
    });
    it('should prioritize thought.subject over currentLoadingPhrase', () => {
        const props = {
            thought: {
                subject: 'This should be displayed',
                description: 'A description',
            },
            currentLoadingPhrase: 'This should not be displayed',
            elapsedTime: 5,
        };
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...props }), StreamingState.Responding);
        const output = lastFrame();
        expect(output).toContain('This should be displayed');
        expect(output).not.toContain('This should not be displayed');
    });
    it('should truncate long primary text instead of wrapping', () => {
        const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, currentLoadingPhrase: 'This is an extremely long loading phrase that should be truncated in the UI to keep the primary line concise.' }), StreamingState.Responding, 80);
        expect(lastFrame()).toMatchSnapshot();
    });
    describe('responsive layout', () => {
        it('should render on a single line on a wide terminal', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, rightContent: _jsx(Text, { children: "Right" }) }), StreamingState.Responding, 120);
            const output = lastFrame();
            // Check for single line output
            expect(output?.includes('\n')).toBe(false);
            expect(output).toContain('Loading...');
            expect(output).toContain('(5s · esc to cancel)');
            expect(output).toContain('Right');
        });
        it('should render on multiple lines on a narrow terminal', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, rightContent: _jsx(Text, { children: "Right" }) }), StreamingState.Responding, 79);
            const output = lastFrame();
            const lines = output?.split('\n');
            // Expecting 3 lines:
            // 1. Spinner + Primary Text
            // 2. Cancel + Timer
            // 3. Right Content
            expect(lines).toHaveLength(3);
            if (lines) {
                expect(lines[0]).toContain('Loading...');
                expect(lines[0]).not.toContain('5s');
                expect(lines[1]).toContain('5s');
                expect(lines[2]).toContain('Right');
            }
        });
        it('should use wide layout at 80 columns', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps }), StreamingState.Responding, 80);
            expect(lastFrame()?.includes('\n')).toBe(false);
        });
        it('should use narrow layout at 79 columns', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps }), StreamingState.Responding, 79);
            expect(lastFrame()?.includes('\n')).toBe(true);
        });
    });
    describe('token display', () => {
        it('should display output tokens inline with arrow notation', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, candidatesTokens: 847 }), StreamingState.Responding);
            const output = lastFrame();
            expect(output).toContain('↓ 847 tokens');
            expect(output).not.toContain('↑');
            expect(output).toContain('5s');
            expect(output).toContain('esc to cancel');
        });
        it('should not display tokens when output tokens is 0', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, candidatesTokens: 0 }), StreamingState.Responding);
            const output = lastFrame();
            expect(output).not.toContain('↓');
            expect(output).not.toContain('tokens');
        });
        it('should not display tokens when props are undefined', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps }), StreamingState.Responding);
            const output = lastFrame();
            expect(output).not.toContain('↓');
            expect(output).not.toContain('tokens');
        });
        it('should hide tokens in narrow terminal', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, candidatesTokens: 500 }), StreamingState.Responding, 79);
            const output = lastFrame();
            expect(output).not.toContain('↓');
            expect(output).not.toContain('tokens');
            expect(output).toContain('esc to cancel');
        });
        it('should show tokens in wide terminal with inline format', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, candidatesTokens: 5400 }), StreamingState.Responding, 80);
            const output = lastFrame();
            expect(output).toContain('↓ 5.4k tokens');
        });
        it('should format tokens inline with time and cancel', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, candidatesTokens: 5400 }), StreamingState.Responding, 120);
            const output = lastFrame();
            expect(output).toContain('(5s · ↓ 5.4k tokens · esc to cancel)');
        });
        it('should show ↑ arrow when waiting for API response', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, candidatesTokens: 500, isReceivingContent: false }), StreamingState.Responding);
            const output = lastFrame();
            expect(output).toContain('↑ 500 tokens');
            expect(output).not.toContain('↓');
        });
        it('should show ↓ arrow when receiving content (default)', () => {
            const { lastFrame } = renderWithContext(_jsx(LoadingIndicator, { ...defaultProps, candidatesTokens: 500 }), StreamingState.Responding);
            const output = lastFrame();
            expect(output).toContain('↓ 500 tokens');
            expect(output).not.toContain('↑');
        });
    });
});
//# sourceMappingURL=LoadingIndicator.test.js.map