import { feature } from 'bun:bundle'
import * as React from 'react'
import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js'
import { useTerminalSize } from '../hooks/useTerminalSize.js'
import { Box, Text, usePreviewTheme, useTheme, useThemeSetting } from '@anthropic/ink'
import { useRegisterKeybindingContext } from '../keybindings/KeybindingContext.js'
import { useKeybinding } from '../keybindings/useKeybinding.js'
import { useShortcutDisplay } from '../keybindings/useShortcutDisplay.js'
import { useAppState, useSetAppState } from '../state/AppState.js'
import { gracefulShutdown } from '../utils/gracefulShutdown.js'
import { updateSettingsForSource } from '../utils/settings/settings.js'
import type { ThemeSetting } from '../utils/theme.js'
import { Select } from './CustomSelect/index.js'
import { Byline, KeyboardShortcutHint } from '@anthropic/ink'
import {
getColorModuleUnavailableReason,
getSyntaxTheme,
} from './StructuredDiff/colorDiff.js'
import { StructuredDiff } from './StructuredDiff.js'
export type ThemePickerProps = {
onThemeSelect: (setting: ThemeSetting) => void
showIntroText?: boolean
helpText?: string
showHelpTextBelow?: boolean
hideEscToCancel?: boolean
/** Skip exit handling when running in a context that already has it (e.g., onboarding) */
skipExitHandling?: boolean
/** Called when the user cancels (presses Escape). If skipExitHandling is true and this is provided, it will be called instead of just saving the preview. */
onCancel?: () => void
}
export function ThemePicker({
onThemeSelect,
showIntroText = false,
helpText = '',
showHelpTextBelow = false,
hideEscToCancel = false,
skipExitHandling = false,
onCancel: onCancelProp,
}: ThemePickerProps): React.ReactNode {
const [theme] = useTheme()
const themeSetting = useThemeSetting()
const { columns } = useTerminalSize()
const colorModuleUnavailableReason = getColorModuleUnavailableReason()
const syntaxTheme =
colorModuleUnavailableReason === null ? getSyntaxTheme(theme) : null
const { setPreviewTheme, savePreview, cancelPreview } = usePreviewTheme()
const syntaxHighlightingDisabled =
useAppState(s => s.settings.syntaxHighlightingDisabled) ?? false
const setAppState = useSetAppState()
// Register ThemePicker context so its keybindings take precedence over Global
useRegisterKeybindingContext('ThemePicker')
const syntaxToggleShortcut = useShortcutDisplay(
'theme:toggleSyntaxHighlighting',
'ThemePicker',
'ctrl+t',
)
useKeybinding(
'theme:toggleSyntaxHighlighting',
() => {
if (colorModuleUnavailableReason === null) {
const newValue = !syntaxHighlightingDisabled
updateSettingsForSource('userSettings', {
syntaxHighlightingDisabled: newValue,
})
setAppState(prev => ({
...prev,
settings: { ...prev.settings, syntaxHighlightingDisabled: newValue },
}))
}
},
{ context: 'ThemePicker' },
)
// Always call the hook to follow React rules, but conditionally assign the exit handler
const exitState = useExitOnCtrlCDWithKeybindings(
skipExitHandling ? () => {} : undefined,
)
const themeOptions: { label: string; value: ThemeSetting }[] = [
...(feature('AUTO_THEME')
? [{ label: 'Auto (match terminal)', value: 'auto' as const }]
: []),
{ label: 'Dark mode', value: 'dark' },
{ label: 'Light mode', value: 'light' },
{
label: 'Dark mode (colorblind-friendly)',
value: 'dark-daltonized',
},
{
label: 'Light mode (colorblind-friendly)',
value: 'light-daltonized',
},
{
label: 'Dark mode (ANSI colors only)',
value: 'dark-ansi',
},
{
label: 'Light mode (ANSI colors only)',
value: 'light-ansi',
},
]
const content = (
{showIntroText ? (
Let's get started.
) : (
Theme
)}
Choose the text style that looks best with your terminal
{helpText && !showHelpTextBelow && {helpText}}
{' '}
{colorModuleUnavailableReason === 'env'
? `Syntax highlighting disabled (via CLAUDE_CODE_SYNTAX_HIGHLIGHT=${process.env.CLAUDE_CODE_SYNTAX_HIGHLIGHT})`
: syntaxHighlightingDisabled
? `Syntax highlighting disabled (${syntaxToggleShortcut} to enable)`
: syntaxTheme
? `Syntax theme: ${syntaxTheme.theme}${syntaxTheme.source ? ` (from ${syntaxTheme.source})` : ''} (${syntaxToggleShortcut} to disable)`
: `Syntax highlighting enabled (${syntaxToggleShortcut} to disable)`}
)
// Only wrap in a box when not in onboarding
if (!showIntroText) {
return (
<>
{content}
{showHelpTextBelow && helpText && (
{helpText}
)}
{!hideEscToCancel && (
{exitState.pending ? (
<>Press {exitState.keyName} again to exit>
) : (
)}
)}
>
)
}
return content
}