import React, { useState, useMemo, useEffect } from 'react'; import { Entity, EntityType, CharacterAttributes, EntityTemplate, CustomFieldDefinition, CustomFieldType } from '../types'; import { Plus, Trash2, Save, X, Sparkles, User, Activity, Brain, Ruler, Settings, Layout, List, ToggleLeft } from 'lucide-react'; import { ENTITY_ICONS, ENTITY_COLORS, HAIR_COLORS, EYE_COLORS, ARCHETYPES } from '../constants'; interface WorldBuilderProps { entities: Entity[]; onCreate: (entity: Omit) => Promise; onUpdate: (id: string, updates: Partial) => void; onDelete: (id: string) => void; templates: EntityTemplate[]; onUpdateTemplates: (templates: EntityTemplate[]) => void; initialSelectedId?: string | null; } const DEFAULT_CHAR_ATTRIBUTES: CharacterAttributes = { age: 30, height: 175, hair: 'Brun', eyes: 'Marron', archetype: 'Le Héros', role: 'support', personality: { spectrumIntrovertExtravert: 50, spectrumEmotionalRational: 50, spectrumChaoticLawful: 50, }, physicalQuirk: '', behavioralQuirk: '' }; const WorldBuilder: React.FC = ({ entities, onCreate, onUpdate, onDelete, templates, onUpdateTemplates, initialSelectedId }) => { const [editingId, setEditingId] = useState(null); const [tempEntity, setTempEntity] = useState(null); const [mode, setMode] = useState<'entities' | 'templates'>('entities'); // Template Editor State const [activeTemplateType, setActiveTemplateType] = useState(EntityType.CHARACTER); // Handle external navigation request (deep link) useEffect(() => { if (initialSelectedId) { const entity = entities.find(e => e.id === initialSelectedId); if (entity) { handleEdit(entity); setMode('entities'); } } }, [initialSelectedId, entities]); // Dynamic Archetypes List const allArchetypes = useMemo(() => { const existing = entities .filter(e => e.type === EntityType.CHARACTER && e.attributes?.archetype) .map(e => e.attributes!.archetype); return Array.from(new Set([...ARCHETYPES, ...existing])).sort(); }, [entities]); // --- ENTITY ACTIONS --- const handleAdd = (type: EntityType) => { const newEntity: Entity = { id: Date.now().toString(), // Helper ID for UI type, name: '', description: '', details: '', storyContext: '', attributes: type === EntityType.CHARACTER ? { ...DEFAULT_CHAR_ATTRIBUTES } : undefined, customValues: {} }; setTempEntity(newEntity); setEditingId('NEW'); }; const handleEdit = (entity: Entity) => { // Ensure attributes exist if it's a character (backward compatibility) const entityToEdit = { ...entity }; if (entity.type === EntityType.CHARACTER && !entity.attributes) { entityToEdit.attributes = { ...DEFAULT_CHAR_ATTRIBUTES }; } if (!entity.customValues) { entityToEdit.customValues = {}; } setTempEntity(entityToEdit); setEditingId(entity.id); }; const handleSave = async () => { if (!tempEntity || !tempEntity.name) return; if (editingId === 'NEW') { const { id, ...entityData } = tempEntity; await onCreate(entityData); } else { onUpdate(tempEntity.id, tempEntity); } setEditingId(null); setTempEntity(null); }; const handleDelete = (id: string) => { if (confirm('Supprimer cet élément ?')) { onDelete(id); if (editingId === id) { setEditingId(null); setTempEntity(null); } } }; const updateAttribute = (key: keyof CharacterAttributes, value: any) => { if (tempEntity && tempEntity.attributes) { setTempEntity({ ...tempEntity, attributes: { ...tempEntity.attributes, [key]: value } }); } }; const updatePersonality = (key: keyof CharacterAttributes['personality'], value: number) => { if (tempEntity && tempEntity.attributes) { setTempEntity({ ...tempEntity, attributes: { ...tempEntity.attributes, personality: { ...tempEntity.attributes.personality, [key]: value } } }); } }; const updateCustomValue = (fieldId: string, value: any) => { if (tempEntity) { setTempEntity({ ...tempEntity, customValues: { ...tempEntity.customValues, [fieldId]: value } }); } }; // --- TEMPLATE ACTIONS --- const addCustomField = (type: EntityType) => { const newField: CustomFieldDefinition = { id: `field-${Date.now()}`, label: 'Nouveau Champ', type: 'text', placeholder: '' }; // Correct immutable update const updatedTemplates = templates.map(t => { if (t.entityType === type) { return { ...t, fields: [...t.fields, newField] }; } return t; }); // If template didn't exist (unlikely given App.tsx init, but safe) if (!updatedTemplates.some(t => t.entityType === type)) { updatedTemplates.push({ entityType: type, fields: [newField] }); } onUpdateTemplates(updatedTemplates); }; const updateCustomField = (type: EntityType, fieldId: string, updates: Partial) => { const updatedTemplates = templates.map(t => { if (t.entityType !== type) return t; return { ...t, fields: t.fields.map(f => f.id === fieldId ? { ...f, ...updates } : f) }; }); onUpdateTemplates(updatedTemplates); }; const deleteCustomField = (type: EntityType, fieldId: string) => { const updatedTemplates = templates.map(t => { if (t.entityType !== type) return t; return { ...t, fields: t.fields.filter(f => f.id !== fieldId) }; }); onUpdateTemplates(updatedTemplates); }; const filterByType = (type: EntityType) => entities.filter(e => e.type === type); // --- RENDER HELPERS --- const renderCharacterEditor = () => { if (!tempEntity?.attributes) return null; const attrs = tempEntity.attributes; return (
{/* SECTION 1: ROLE & ARCHETYPE */}

Identité Narrative

updateAttribute('archetype', e.target.value)} className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-blue-500" placeholder="Ex: Le Héros, Le Sage..." /> {allArchetypes.map(a =>
{[ { val: 'protagonist', label: 'Protagoniste' }, { val: 'antagonist', label: 'Antagoniste' }, { val: 'support', label: 'Secondaire' }, { val: 'extra', label: 'Figurant' } ].map(opt => ( ))}
{/* SECTION 2: PHYSIQUE */}

Apparence Physique

updateAttribute('age', parseInt(e.target.value))} className="flex-1 h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-indigo-600" /> updateAttribute('age', parseInt(e.target.value))} className="w-20 p-1 text-right text-sm border border-slate-300 rounded font-mono text-indigo-700 bg-[#eef2ff] focus:border-indigo-500 outline-none" />
updateAttribute('height', parseInt(e.target.value))} className="flex-1 h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-indigo-600" /> updateAttribute('height', parseInt(e.target.value))} className="w-20 p-1 text-right text-sm border border-slate-300 rounded font-mono text-indigo-700 bg-[#eef2ff] focus:border-indigo-500 outline-none" />
updateAttribute('physicalQuirk', e.target.value)} placeholder="Cicatrice, tatouage..." className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400" />
{/* SECTION 3: PSYCHOLOGIE */}

Psychologie & Comportement

Introverti Extraverti
updatePersonality('spectrumIntrovertExtravert', parseInt(e.target.value))} className="w-full h-2 bg-gradient-to-r from-slate-300 via-indigo-200 to-slate-300 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
Émotionnel Rationnel
updatePersonality('spectrumEmotionalRational', parseInt(e.target.value))} className="w-full h-2 bg-gradient-to-r from-red-200 via-purple-200 to-blue-200 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
Chaotique Loyal
updatePersonality('spectrumChaoticLawful', parseInt(e.target.value))} className="w-full h-2 bg-gradient-to-r from-orange-200 via-yellow-100 to-green-200 rounded-lg appearance-none cursor-pointer accent-indigo-600" />
updateAttribute('behavioralQuirk', e.target.value)} placeholder="Joue avec sa bague, bégaie quand il ment..." className="w-full p-2 bg-[#eef2ff] border border-slate-300 rounded text-sm outline-none focus:border-indigo-400" />
); }; const renderCustomFieldsEditor = () => { const currentTemplate = templates.find(t => t.entityType === tempEntity?.type); if (!currentTemplate || currentTemplate.fields.length === 0) return null; return (

Champs Personnalisés

{currentTemplate.fields.map(field => { const value = tempEntity?.customValues?.[field.id] ?? ''; return (
{field.type === 'textarea' ? (