import type { AnilistCharacterDictionaryCollapsibleSectionKey } from '../../types'; import { formatCharacterStats, parseCharacterDescription } from './description'; import type { CharacterDictionaryGlossaryEntry, CharacterDictionaryRole, CharacterRecord, VoiceActorRecord, } from './types'; function roleLabel(role: CharacterDictionaryRole): string { if (role === 'main') return 'Protagonist'; if (role === 'primary') return 'Main Character'; if (role === 'side') return 'Side Character'; return 'Minor Role'; } function roleBadgeStyle(role: CharacterDictionaryRole): Record { const base = { borderRadius: '4px', padding: '0.15em 0.5em', fontSize: '0.8em', fontWeight: 'bold', color: '#fff', }; if (role === 'main') return { ...base, backgroundColor: '#4CAF50' }; if (role === 'primary') return { ...base, backgroundColor: '#2196F3' }; if (role === 'side') return { ...base, backgroundColor: '#FF9800' }; return { ...base, backgroundColor: '#9E9E9E' }; } function buildCollapsibleSection( title: string, open: boolean, body: Array> | string | Record, ): Record { return { tag: 'details', open, style: { marginTop: '0.4em' }, content: [ { tag: 'summary', style: { fontWeight: 'bold', fontSize: '0.95em', cursor: 'pointer' }, content: title, }, { tag: 'div', style: { padding: '0.25em 0 0 0.4em', fontSize: '0.9em' }, content: body, }, ], }; } function buildVoicedByContent( voiceActors: VoiceActorRecord[], vaImagePaths: Map, ): Record { if (voiceActors.length === 1) { const va = voiceActors[0]!; const vaImgPath = vaImagePaths.get(va.id); const vaLabel = va.nativeName ? va.fullName ? `${va.nativeName} (${va.fullName})` : va.nativeName : va.fullName; if (vaImgPath) { return { tag: 'table', content: { tag: 'tr', content: [ { tag: 'td', style: { verticalAlign: 'top', padding: '0', paddingRight: '0.4em', borderWidth: '0', }, content: { tag: 'img', path: vaImgPath, width: 3, height: 3, sizeUnits: 'em', title: vaLabel, alt: vaLabel, collapsed: false, collapsible: false, background: true, }, }, { tag: 'td', style: { verticalAlign: 'middle', padding: '0', borderWidth: '0' }, content: vaLabel, }, ], }, }; } return { tag: 'div', content: vaLabel }; } const items: Array> = []; for (const va of voiceActors) { const vaLabel = va.nativeName ? va.fullName ? `${va.nativeName} (${va.fullName})` : va.nativeName : va.fullName; items.push({ tag: 'li', content: vaLabel }); } return { tag: 'ul', style: { marginTop: '0.15em' }, content: items }; } function buildKnownNamesBlock(nameTerms: string[]): Record | null { const visibleTerms = [...new Set(nameTerms.map((term) => term.trim()).filter(Boolean))]; if (visibleTerms.length <= 1) { return null; } return { tag: 'div', style: { fontSize: '0.85em', marginBottom: '0.25em' }, content: [ { tag: 'div', style: { fontWeight: 'bold', color: '#d0d0d0', marginBottom: '0.1em' }, content: 'Known names', }, { tag: 'ul', style: { marginTop: '0', marginBottom: '0', paddingLeft: '1.2em' }, content: visibleTerms.map((term) => ({ tag: 'li', content: term, })), }, ], }; } export function createDefinitionGlossary( character: CharacterRecord, mediaId: number, mediaTitle: string, imagePath: string | null, vaImagePaths: Map, nameTerms: string[], getCollapsibleSectionOpenState: ( section: AnilistCharacterDictionaryCollapsibleSectionKey, ) => boolean, ): CharacterDictionaryGlossaryEntry[] { const displayName = character.nativeName || character.fullName || `Character ${character.id}`; const { fields, text: descriptionText } = parseCharacterDescription(character.description); const content: Array> = [ { tag: 'div', style: { fontWeight: 'bold', fontSize: '1.1em', marginBottom: '0.1em' }, content: displayName, }, ]; const knownNamesBlock = buildKnownNamesBlock(nameTerms); if (knownNamesBlock) { content.push(knownNamesBlock); } if (imagePath) { content.push({ tag: 'div', style: { marginTop: '0.3em', marginBottom: '0.3em' }, content: { tag: 'img', path: imagePath, width: 8, height: 11, sizeUnits: 'em', title: displayName, alt: displayName, description: `${displayName} ยท ${mediaTitle}`, collapsed: false, collapsible: false, background: true, }, }); } content.push({ tag: 'div', style: { fontSize: '0.8em', color: '#999', marginBottom: '0.2em' }, content: `From: ${mediaTitle}`, }); content.push({ tag: 'div', style: { marginBottom: '0.15em' }, content: { tag: 'span', style: roleBadgeStyle(character.role), content: roleLabel(character.role), }, }); const statsLine = formatCharacterStats(character); if (descriptionText) { content.push( buildCollapsibleSection( 'Description', getCollapsibleSectionOpenState('description'), descriptionText, ), ); } const fieldItems: Array> = []; if (statsLine) { fieldItems.push({ tag: 'li', style: { fontWeight: 'bold' }, content: statsLine, }); } fieldItems.push( ...fields.map((field) => ({ tag: 'li', content: `${field.key}: ${field.value}`, })), ); if (fieldItems.length > 0) { content.push( buildCollapsibleSection( 'Character Information', getCollapsibleSectionOpenState('characterInformation'), { tag: 'ul', style: { marginTop: '0.15em' }, content: fieldItems, }, ), ); } if (character.voiceActors.length > 0) { content.push( buildCollapsibleSection( 'Voiced by', getCollapsibleSectionOpenState('voicedBy'), buildVoicedByContent(character.voiceActors, vaImagePaths), ), ); } return [ { type: 'structured-content', content: { tag: 'div', data: { subminerMediaId: String(mediaId) }, content }, }, ]; }