/**
* Spelling and character access tools
*/
import { createIndexedList } from '../utils/visualization.js';
// ============================================================================
// spell_word - Break a word into individual characters
// ============================================================================
export interface SpellWordInput {
text: string;
include_indices: boolean;
}
export interface SpellWordOutput {
text: string;
length: number;
characters: string[];
indexed_characters: Array<{ index: number; char: string }>;
spelled_out: string;
}
export function spellWord(input: SpellWordInput): SpellWordOutput {
const { text, include_indices } = input;
const characters = [...text];
const indexed_characters = createIndexedList(text);
const spelled_out = include_indices
? indexed_characters.map(ic => `${ic.index}:'${ic.char}'`).join(', ')
: characters.map(c => `'${c}'`).join(', ');
return {
text,
length: characters.length,
characters,
indexed_characters,
spelled_out,
};
}
// ============================================================================
// char_at - Get character at a specific index
// ============================================================================
export interface CharAtInput {
text: string;
index: number;
}
export interface CharAtOutput {
text: string;
index: number;
character: string | null;
valid: boolean;
text_length: number;
error?: string;
}
export function charAt(input: CharAtInput): CharAtOutput {
const { text, index } = input;
const characters = [...text];
// Handle negative indices (Python-style)
const normalizedIndex = index < 0 ? characters.length + index : index;
if (normalizedIndex < 0 || normalizedIndex >= characters.length) {
return {
text,
index,
character: null,
valid: false,
text_length: characters.length,
error: `Index ${index} is out of bounds. Valid range: 0 to ${characters.length - 1} (or -${characters.length} to -1 for negative indices).`,
};
}
return {
text,
index,
character: characters[normalizedIndex],
valid: true,
text_length: characters.length,
};
}
// ============================================================================
// nth_character - Get the nth character (1-indexed, human-friendly)
// ============================================================================
export interface NthCharacterInput {
text: string;
position: number;
from_end: boolean;
}
export interface NthCharacterOutput {
text: string;
position: number;
from_end: boolean;
character: string | null;
valid: boolean;
text_length: number;
description: string;
error?: string;
}
export function nthCharacter(input: NthCharacterInput): NthCharacterOutput {
const { text, position, from_end } = input;
const characters = [...text];
if (position < 1) {
return {
text,
position,
from_end,
character: null,
valid: false,
text_length: characters.length,
description: '',
error: `Position must be at least 1. Use position=1 for the ${from_end ? 'last' : 'first'} character.`,
};
}
const index = from_end ? characters.length - position : position - 1;
if (index < 0 || index >= characters.length) {
return {
text,
position,
from_end,
character: null,
valid: false,
text_length: characters.length,
description: '',
error: `Position ${position} is out of bounds. The text only has ${characters.length} characters.`,
};
}
const ordinal = getOrdinal(position);
const description = from_end
? `The ${ordinal} character from the end of "${text}" is '${characters[index]}'.`
: `The ${ordinal} character of "${text}" is '${characters[index]}'.`;
return {
text,
position,
from_end,
character: characters[index],
valid: true,
text_length: characters.length,
description,
};
}
function getOrdinal(n: number): string {
const s = ['th', 'st', 'nd', 'rd'];
const v = n % 100;
return n + (s[(v - 20) % 10] || s[v] || s[0]);
}
// ============================================================================
// word_length - Get the exact length of text
// ============================================================================
export interface WordLengthInput {
text: string;
count_spaces: boolean;
}
export interface WordLengthOutput {
text: string;
length: number;
length_without_spaces: number;
space_count: number;
word_count: number;
description: string;
}
export function wordLength(input: WordLengthInput): WordLengthOutput {
const { text, count_spaces } = input;
const characters = [...text];
const spaceCount = characters.filter(c => c === ' ').length;
const words = text.split(/\s+/).filter(w => w.length > 0);
const length = count_spaces ? characters.length : characters.length - spaceCount;
return {
text,
length,
length_without_spaces: characters.length - spaceCount,
space_count: spaceCount,
word_count: words.length,
description: `"${text}" has ${characters.length} total characters (${characters.length - spaceCount} letters, ${spaceCount} spaces).`,
};
}
// ============================================================================
// reverse_text - Reverse text character by character
// ============================================================================
export interface ReverseTextInput {
text: string;
reverse_words_only: boolean;
}
export interface ReverseTextOutput {
original: string;
reversed: string;
reverse_words_only: boolean;
is_palindrome: boolean;
description: string;
}
export function reverseText(input: ReverseTextInput): ReverseTextOutput {
const { text, reverse_words_only } = input;
let reversed: string;
if (reverse_words_only) {
// Reverse order of words but keep each word's characters in order
reversed = text.split(/(\s+)/).reverse().join('');
} else {
// Reverse all characters
reversed = [...text].reverse().join('');
}
const normalizedOriginal = text.toLowerCase().replace(/[^a-z0-9]/g, '');
const normalizedReversed = [...normalizedOriginal].reverse().join('');
const is_palindrome = normalizedOriginal === normalizedReversed && normalizedOriginal.length > 0;
const description = reverse_words_only
? `Words reversed: "${text}" → "${reversed}"`
: `Characters reversed: "${text}" → "${reversed}"${is_palindrome ? ' (This is a palindrome!)' : ''}`;
return {
original: text,
reversed,
reverse_words_only,
is_palindrome,
description,
};
}