// Global UI helpers: toast, dropdown, modals, i18n.
const { useState: useStateE, useEffect: useEffectE, createContext, useContext, useRef } = React;

// ---------- i18n ----------
const TRANSLATIONS = {
  fr: {
    search:"Rechercher…",nav_section_espace:"Espace",nav_section_ma_classe:"Ma classe",nav_section_gestion:"Gestion",nav_section_communication:"Communication",nav_section_mon_enfant:"Mon enfant",nav_pilotage:"Pilotage",nav_classes:"Classes",nav_equipe:"Équipe",nav_ecole:"École",nav_mes_classes:"Mes classes",nav_inscriptions:"Inscriptions",nav_annonces:"Annonces",nav_conversations:"Conversations",nav_agenda:"Agenda",nav_parametres:"Paramètres",
    login_subtitle:"Qui es-tu aujourd'hui ?",login_dir_label:"Directeur · Directrice",login_dir_desc:"Vue globale de l'école — KPIs, facturation, équipe pédagogique et inscriptions.",login_prof_label:"Professeur",login_prof_desc:"Mes classes, emploi du temps, suivi des élèves et communication parents.",login_parent_label:"Parent d'élève",login_parent_desc:"Fiche de mon enfant, conversations avec l'école et agenda de classe.",login_continue:"Continuer",login_footer:"École Tournesol · Prototype Scolia",
    save:"Enregistrer",cancel:"Annuler",close:"Fermer",delete:"Supprimer",confirm_lbl:"Confirmer",today:"Aujourd'hui",pinned:"Épinglée",see_all:"Tout voir",export:"Exporter",message_btn:"Message",rdv_btn:"RDV",open_btn:"Ouvrir",write_btn:"Écrire",add:"Ajouter",history_btn:"Historique",
    pilotage_title:"Mon école aujourd'hui",pilotage_new_annonce:"Nouvelle annonce",kpi_presents:"Présents",kpi_presents_sub:"aujourd'hui",kpi_inscriptions:"Inscriptions 2026",kpi_inscriptions_sub:"dossiers en cours",kpi_payments:"Paiements à jour",kpi_payments_sub:"4 relances envoyées",weekly_title:"Présence hebdomadaire",legend_present:"Présent",legend_late:"Retard",legend_absent:"Absent",team_title:"Activité de l'équipe",team_teacher_col:"Enseignant",team_class_col:"Classe",team_obs_col:"Obs.",team_conv_col:"Conv.",team_presence_col:"Présence",actions_title:"Mes actions",task_ph:"Nom de la tâche…",task_urgent:"Urgent",task_normal:"Normal",task_low:"Faible",announcements_title:"Annonces",inscriptions_preview_title:"Inscriptions 2026",insc_status_new:"Nouveau",insc_status_review:"À relire",insc_status_wait:"En attente",insc_status_validated:"Validée",
    classes_eyebrow:"Toutes les classes",classes_search_ph:"Rechercher une classe…",classes_filters:"Filtres",classes_new:"Nouvelle classe",stat_classes:"Classes",stat_eleves:"Élèves",stat_presents_today:"Présents aujourd'hui",stat_obs_7j:"Observations · 7j",tab_all:"Toutes",tab_maternelle:"Maternelle",tab_cycle2:"Cycle 2",tab_cycle3:"Cycle 3",sort_by:"Trier par",sort_niveau:"Niveau",sort_effectif:"Effectif",sort_presence:"Présence",sort_nom:"Nom de classe",presence_du_jour:"Présence du jour",chip_presents:"présents",chip_retards:"retards",chip_absents:"absents",obs_7j_tiny:"Obs. 7j",conv_tiny:"Conv.",
    back_classes:"Classes",teacher_role:"Enseignant·e",export_list:"Exporter la liste",print_presence:"Imprimer la présence",hist_presence:"Historique présences",archive_class:"Archiver la classe",add_obs:"Ajouter une observation",today_btn:"Aujourd'hui",students_label:"Élèves",filter_ph:"Filtrer…",filter_all_students:"Tous les élèves",filter_present_only:"Présents uniquement",filter_absent_only:"Absents uniquement",filter_watch:"À surveiller",status_present:"Présent",status_late:"En retard",status_absent:"Absent",status_btn_p:"P",status_btn_r:"R",status_btn_a:"A",tab_suivi:"Suivi",tab_journal:"Journal",tab_parents:"Parents",tab_sante:"Santé",tab_docs:"Docs",skills_title:"Compétences",last_obs:"Dernière observation",ia_summary:"Synthèse IA",see_history:"Voir l'historique",journal_title:"Journal de l'élève",type_obs:"Observation",type_rdv:"RDV parents",type_note:"Note",contacts_title:"Contacts familiaux",main_contact:"Contact principal",pickup_auth:"Autorisation de sortie",health_title:"Fiche santé",allergies_label:"Allergies & intolérances",pai_label:"PAI (Projet d'Accueil Individualisé)",doctor_label:"Médecin traitant",last_visit_label:"Dernière visite médicale",export_health:"Exporter la fiche santé",docs_title:"Documents",scan_btn:"Scanner",missing_doc:"Manquant",to_provide:"À fournir",edit_file:"Modifier la fiche",export_pdf:"Exporter en PDF",program_rdv:"Programmer un RDV parents",archive_student:"Archiver l'élève",
    equipe_eyebrow:"Direction",equipe_title:"Mon équipe",inscriptions_tile:"Inscriptions",inscriptions_tile_sub:"Dossiers à traiter",annonces_tile:"Annonces",annonces_tile_sub:"Communications école",conv_tile:"Conversations",conv_tile_sub:"Parents · équipe",teachers_title:"Enseignants",tab_tous:"Tous",tab_actifs:"Actifs",tab_conge:"En congé",activity_col:"Dernière activité",to_process_rail:"À traiter",latest_annonces:"Dernières annonces",
    ecole_eyebrow:"Établissement",ecole_view_public:"Voir la fiche publique",identity_title:"Identité",identity_sub:"Visible par les familles et l'équipe.",change_logo:"Changer le logo",logo_hint:"PNG, SVG · 512×512 minimum",field_school_name:"Nom de l'école",field_address:"Adresse",field_phone:"Téléphone",field_email:"Email",school_year_title:"Année scolaire",field_current_year:"Année en cours",field_enrolled:"Élèves inscrits",field_insc_2026:"Inscriptions 2026",periods_title:"Périodes",active_period_chip:"En cours",edit_dates:"Modifier les dates",set_current:"Définir comme actuelle",reset_period:"Réinitialiser",direction_title:"Direction",integrations_title:"Intégrations",integrations_sub:"Connecté à l'écosystème de l'école.",active_chip:"Actif",ia_weekly_title:"Synthèse IA hebdomadaire",ia_activate:"Activer pour l'école",permissions_btn:"Permissions",
    conv_eyebrow:"Communication",conv_title:"Conversations",new_conv:"Nouvelle conversation",filter_all_conv:"Tous",filter_unread:"Non lus",filter_parents_conv:"Parents",filter_equipe_conv:"Équipe",online_label:"en ligne",write_msg_ph:"Écrire un message…",send_btn:"Envoyer",profile_btn:"Profil",pin_conv:"Épingler",mute_conv:"Mettre en silence",delete_conv:"Supprimer",shared_files:"Fichiers partagés",linked_to:"Lié à",no_conv:"Aucune conversation dans ce filtre.",
    insc_eyebrow:"Gestion · Année 2026",insc_title:"Inscriptions",insc_received:"Dossiers reçus",insc_to_process:"À traiter",insc_validated_count:"Validés",insc_rate:"Taux d'acceptation",tab_all_insc:"Tous · 7",tab_new_insc:"Nouveaux",tab_review_insc:"À relire",tab_wait_insc:"En attente",tab_validated_insc:"Validés",new_file:"Nouveau dossier",family_label:"Famille",file_label:"Dossier",doc_received:"Reçu",doc_missing:"Manquant",propose_rdv:"Proposer un RDV",refuse_btn:"Refuser",validate_insc:"Valider l'inscription",
    agenda_eyebrow:"Mai 2026 · Semaine 20",agenda_title:"Agenda",agenda_today:"Aujourd'hui",agenda_new:"Nouvel élément",view_day:"Jour",view_week:"Semaine",view_month:"Mois",view_year:"Année",event_title_label:"Titre *",event_day_label:"Jour",event_hour_label:"Heure",event_dur_label:"Durée",event_who_label:"Participants / lieu",event_desc_label:"Description (optionnelle)",event_type_label:"Type",event_general:"Général",event_important:"Important",add_to_cal:"Ajouter au calendrier",description_label:"Description",delete_event:"Supprimer",edit_event:"Modifier",
    settings_eyebrow:"Réglages",settings_title:"Paramètres",param_compte:"Compte",param_notifs:"Notifications",param_secu:"Sécurité",param_billing:"Facturation",param_team:"Membres & rôles",param_ia:"Scolia IA",account_title:"Compte",change_photo_btn:"Changer la photo",photo_hint:"JPG ou PNG · 2 Mo maxi",firstname_label:"Prénom",lastname_label:"Nom",email_pro_label:"Email professionnel",phone_pro_label:"Téléphone",title_input_label:"Titre",lang_label:"Langue de l'interface",notifs_title:"Notifications",notifs_sub:"Choisis ce que Scolia doit te signaler.",secu_title:"Sécurité",two_factor_label:"Authentification à deux facteurs",two_factor_sub:"Activée · application Authy",active_chip_secu:"Active",change_pw:"Changer le mot de passe",disconnect_others:"Déconnecter les autres sessions",active_sessions:"Sessions actives",this_session:"Cette session",disconnect_btn:"Déconnecter",billing_title:"Facturation",current_plan:"Plan actuel",per_month:"/ mois HT",change_plan:"Changer de plan",see_usage:"Voir la consommation",last_invoices:"Dernières factures",paid_status:"Payée",download_btn:"Télécharger",team_title_param:"Membres & rôles",invite_btn:"Inviter",permissions_btn_param:"Permissions",ia_param_title:"Scolia IA",teacher_role_chip:"Enseignant·e",direction_chip:"Direction",
    annonces_eyebrow:"Communication",annonces_title:"Annonces",new_annonce_btn:"Nouvelle annonce",tab_all_ann:"Toutes",tab_pinned_ann:"Épinglées",pinned_chip:"Épinglée",epingler:"Épingler",desepingler:"Désépingler",export_ann:"Exporter",no_annonce:"Aucune annonce",no_annonce_sub:"Publiez une annonce pour toute l'équipe ou les parents.",delete_ann:"Supprimer",
    parent_eyebrow:"Mon enfant",parent_contact_btn:"Contacter l'école",child_since:"Scolarisé depuis",present_today_label:"Présence aujourd'hui",present_status:"Présent",send_msg_btn:"Envoyer un message",request_rdv_btn:"Demander un RDV",tab_suivi_parent:"Suivi",tab_annonces_parent:"Annonces",tab_agenda_parent:"Agenda",last_obs_parent:"Dernières observations",school_annonces_parent:"Annonces de l'école",week_agenda_parent:"Agenda de la semaine",no_event:"Aucun événement",
    annonce_modal_eyebrow:"Nouvelle annonce",annonce_modal_sub:"Visible par toute l'équipe pédagogique",annonce_question:"Quoi communiquer à l'école ?",annonce_ph:"Ex: Réunion équipe jeudi 12h. On parle des bilans de période…",attach_file:"Joindre un fichier",schedule_btn:"Programmer",audience_btn:"Audience · Équipe",write_ia_btn:"Rédiger avec l'IA",pin_label:"Épingler",pin_sub:"L'annonce restera en haut du fil de l'école.",draft_btn:"Brouillon",publish_btn:"Publier",cal_past:"Terminé",cal_back_to_school:"Rentrée scolaire",cal_xmas:"Vacances de Noël",
  },
  en: {
    search:"Search…",nav_section_espace:"Space",nav_section_ma_classe:"My class",nav_section_gestion:"Management",nav_section_communication:"Communication",nav_section_mon_enfant:"My child",nav_pilotage:"Dashboard",nav_classes:"Classes",nav_equipe:"Team",nav_ecole:"School",nav_mes_classes:"My classes",nav_inscriptions:"Registrations",nav_annonces:"Announcements",nav_conversations:"Conversations",nav_agenda:"Calendar",nav_parametres:"Settings",
    login_subtitle:"Who are you today?",login_dir_label:"Head Teacher",login_dir_desc:"Full school overview — KPIs, billing, teaching team and registrations.",login_prof_label:"Teacher",login_prof_desc:"My classes, timetable, student tracking and parent communication.",login_parent_label:"Parent",login_parent_desc:"My child's file, conversations with school and class calendar.",login_continue:"Continue",login_footer:"École Tournesol · Scolia Prototype",
    save:"Save",cancel:"Cancel",close:"Close",delete:"Delete",confirm_lbl:"Confirm",today:"Today",pinned:"Pinned",see_all:"See all",export:"Export",message_btn:"Message",rdv_btn:"Meeting",open_btn:"Open",write_btn:"Write",add:"Add",history_btn:"History",
    pilotage_title:"My school today",pilotage_new_annonce:"New announcement",kpi_presents:"Present",kpi_presents_sub:"today",kpi_inscriptions:"Registrations 2026",kpi_inscriptions_sub:"files in progress",kpi_payments:"Payments up to date",kpi_payments_sub:"4 reminders sent",weekly_title:"Weekly attendance",legend_present:"Present",legend_late:"Late",legend_absent:"Absent",team_title:"Team activity",team_teacher_col:"Teacher",team_class_col:"Class",team_obs_col:"Obs.",team_conv_col:"Conv.",team_presence_col:"Attendance",actions_title:"My tasks",task_ph:"Task name…",task_urgent:"Urgent",task_normal:"Normal",task_low:"Low",announcements_title:"Announcements",inscriptions_preview_title:"Registrations 2026",insc_status_new:"New",insc_status_review:"To review",insc_status_wait:"Pending",insc_status_validated:"Validated",
    classes_eyebrow:"All classes",classes_search_ph:"Search a class…",classes_filters:"Filters",classes_new:"New class",stat_classes:"Classes",stat_eleves:"Students",stat_presents_today:"Present today",stat_obs_7j:"Observations · 7d",tab_all:"All",tab_maternelle:"Nursery",tab_cycle2:"Cycle 2",tab_cycle3:"Cycle 3",sort_by:"Sort by",sort_niveau:"Level",sort_effectif:"Headcount",sort_presence:"Attendance",sort_nom:"Class name",presence_du_jour:"Today's attendance",chip_presents:"present",chip_retards:"late",chip_absents:"absent",obs_7j_tiny:"Obs. 7d",conv_tiny:"Conv.",
    back_classes:"Classes",teacher_role:"Teacher",export_list:"Export list",print_presence:"Print attendance",hist_presence:"Attendance history",archive_class:"Archive class",add_obs:"Add observation",today_btn:"Today",students_label:"Students",filter_ph:"Filter…",filter_all_students:"All students",filter_present_only:"Present only",filter_absent_only:"Absent only",filter_watch:"To monitor",status_present:"Present",status_late:"Late",status_absent:"Absent",status_btn_p:"P",status_btn_r:"L",status_btn_a:"A",tab_suivi:"Track",tab_journal:"Journal",tab_parents:"Parents",tab_sante:"Health",tab_docs:"Docs",skills_title:"Skills",last_obs:"Last observation",ia_summary:"AI summary",see_history:"See history",journal_title:"Student journal",type_obs:"Observation",type_rdv:"Parent meeting",type_note:"Note",contacts_title:"Family contacts",main_contact:"Main contact",pickup_auth:"Pickup authorization",health_title:"Health file",allergies_label:"Allergies & intolerances",pai_label:"PAI (Individual care plan)",doctor_label:"Doctor",last_visit_label:"Last medical visit",export_health:"Export health file",docs_title:"Documents",scan_btn:"Scan",missing_doc:"Missing",to_provide:"To provide",edit_file:"Edit file",export_pdf:"Export as PDF",program_rdv:"Schedule parent meeting",archive_student:"Archive student",
    equipe_eyebrow:"Management",equipe_title:"My team",inscriptions_tile:"Registrations",inscriptions_tile_sub:"Files to process",annonces_tile:"Announcements",annonces_tile_sub:"School communications",conv_tile:"Conversations",conv_tile_sub:"Parents · team",teachers_title:"Teachers",tab_tous:"All",tab_actifs:"Active",tab_conge:"On leave",activity_col:"Last activity",to_process_rail:"To process",latest_annonces:"Latest announcements",
    ecole_eyebrow:"School",ecole_view_public:"View public profile",identity_title:"Identity",identity_sub:"Visible to families and the team.",change_logo:"Change logo",logo_hint:"PNG, SVG · min 512×512",field_school_name:"School name",field_address:"Address",field_phone:"Phone",field_email:"Email",school_year_title:"School year",field_current_year:"Current year",field_enrolled:"Enrolled students",field_insc_2026:"Registrations 2026",periods_title:"Periods",active_period_chip:"Current",edit_dates:"Edit dates",set_current:"Set as current",reset_period:"Reset",direction_title:"Management",integrations_title:"Integrations",integrations_sub:"Connected to school ecosystem.",active_chip:"Active",ia_weekly_title:"Weekly AI summary",ia_activate:"Activate for school",permissions_btn:"Permissions",
    conv_eyebrow:"Communication",conv_title:"Conversations",new_conv:"New conversation",filter_all_conv:"All",filter_unread:"Unread",filter_parents_conv:"Parents",filter_equipe_conv:"Team",online_label:"online",write_msg_ph:"Write a message…",send_btn:"Send",profile_btn:"Profile",pin_conv:"Pin",mute_conv:"Mute",delete_conv:"Delete",shared_files:"Shared files",linked_to:"Linked to",no_conv:"No conversations in this filter.",
    insc_eyebrow:"Management · 2026",insc_title:"Registrations",insc_received:"Files received",insc_to_process:"To process",insc_validated_count:"Validated",insc_rate:"Acceptance rate",tab_all_insc:"All · 7",tab_new_insc:"New",tab_review_insc:"To review",tab_wait_insc:"Pending",tab_validated_insc:"Validated",new_file:"New file",family_label:"Family",file_label:"File",doc_received:"Received",doc_missing:"Missing",propose_rdv:"Propose a meeting",refuse_btn:"Decline",validate_insc:"Validate registration",
    agenda_eyebrow:"May 2026 · Week 20",agenda_title:"Calendar",agenda_today:"Today",agenda_new:"New event",view_day:"Day",view_week:"Week",view_month:"Month",view_year:"Year",event_title_label:"Title *",event_day_label:"Day",event_hour_label:"Time",event_dur_label:"Duration",event_who_label:"Participants / venue",event_desc_label:"Description (optional)",event_type_label:"Type",event_general:"General",event_important:"Important",add_to_cal:"Add to calendar",description_label:"Description",delete_event:"Delete",edit_event:"Edit",
    settings_eyebrow:"Settings",settings_title:"Settings",param_compte:"Account",param_notifs:"Notifications",param_secu:"Security",param_billing:"Billing",param_team:"Members & roles",param_ia:"Scolia AI",account_title:"Account",change_photo_btn:"Change photo",photo_hint:"JPG or PNG · 2 MB max",firstname_label:"First name",lastname_label:"Last name",email_pro_label:"Professional email",phone_pro_label:"Phone",title_input_label:"Title",lang_label:"Interface language",notifs_title:"Notifications",notifs_sub:"Choose what Scolia should notify you about.",secu_title:"Security",two_factor_label:"Two-factor authentication",two_factor_sub:"Enabled · Authy app",active_chip_secu:"Active",change_pw:"Change password",disconnect_others:"Sign out other sessions",active_sessions:"Active sessions",this_session:"This session",disconnect_btn:"Sign out",billing_title:"Billing",current_plan:"Current plan",per_month:"/ month excl. VAT",change_plan:"Change plan",see_usage:"View usage",last_invoices:"Recent invoices",paid_status:"Paid",download_btn:"Download",team_title_param:"Members & roles",invite_btn:"Invite",permissions_btn_param:"Permissions",ia_param_title:"Scolia AI",teacher_role_chip:"Teacher",direction_chip:"Management",
    annonces_eyebrow:"Communication",annonces_title:"Announcements",new_annonce_btn:"New announcement",tab_all_ann:"All",tab_pinned_ann:"Pinned",pinned_chip:"Pinned",epingler:"Pin",desepingler:"Unpin",export_ann:"Export",no_annonce:"No announcements",no_annonce_sub:"Post an announcement for the whole team or parents.",delete_ann:"Delete",
    parent_eyebrow:"My child",parent_contact_btn:"Contact school",child_since:"Enrolled since",present_today_label:"Today's attendance",present_status:"Present",send_msg_btn:"Send a message",request_rdv_btn:"Request a meeting",tab_suivi_parent:"Track",tab_annonces_parent:"Announcements",tab_agenda_parent:"Calendar",last_obs_parent:"Recent observations",school_annonces_parent:"School announcements",week_agenda_parent:"Week calendar",no_event:"No events",
    annonce_modal_eyebrow:"New announcement",annonce_modal_sub:"Visible to all teaching staff",annonce_question:"What to communicate to school?",annonce_ph:"Ex: Team meeting Thursday 12pm. Discussing period reports…",attach_file:"Attach a file",schedule_btn:"Schedule",audience_btn:"Audience · Team",write_ia_btn:"Write with AI",pin_label:"Pin",pin_sub:"The announcement will stay at the top.",draft_btn:"Draft",publish_btn:"Publish",cal_past:"Done",cal_back_to_school:"Back to school",cal_xmas:"Christmas break",
  },
  es: {
    search:"Buscar…",nav_section_espace:"Espacio",nav_section_ma_classe:"Mi clase",nav_section_gestion:"Gestión",nav_section_communication:"Comunicación",nav_section_mon_enfant:"Mi hijo/a",nav_pilotage:"Panel",nav_classes:"Clases",nav_equipe:"Equipo",nav_ecole:"Escuela",nav_mes_classes:"Mis clases",nav_inscriptions:"Inscripciones",nav_annonces:"Anuncios",nav_conversations:"Conversaciones",nav_agenda:"Agenda",nav_parametres:"Ajustes",
    login_subtitle:"¿Quién eres hoy?",login_dir_label:"Director/a",login_dir_desc:"Vista global del colegio — KPIs, facturación, equipo docente e inscripciones.",login_prof_label:"Profesor/a",login_prof_desc:"Mis clases, horario, seguimiento de alumnos y comunicación con padres.",login_parent_label:"Padre/Madre",login_parent_desc:"Ficha de mi hijo/a, conversaciones con el colegio y agenda de clase.",login_continue:"Continuar",login_footer:"École Tournesol · Prototipo Scolia",
    save:"Guardar",cancel:"Cancelar",close:"Cerrar",delete:"Eliminar",confirm_lbl:"Confirmar",today:"Hoy",pinned:"Anclado",see_all:"Ver todo",export:"Exportar",message_btn:"Mensaje",rdv_btn:"Reunión",open_btn:"Abrir",write_btn:"Escribir",add:"Añadir",history_btn:"Historial",
    pilotage_title:"Mi colegio hoy",pilotage_new_annonce:"Nuevo anuncio",kpi_presents:"Presentes",kpi_presents_sub:"hoy",kpi_inscriptions:"Inscripciones 2026",kpi_inscriptions_sub:"expedientes en curso",kpi_payments:"Pagos al día",kpi_payments_sub:"4 recordatorios enviados",weekly_title:"Asistencia semanal",legend_present:"Presente",legend_late:"Tarde",legend_absent:"Ausente",team_title:"Actividad del equipo",team_teacher_col:"Docente",team_class_col:"Clase",team_obs_col:"Obs.",team_conv_col:"Conv.",team_presence_col:"Asistencia",actions_title:"Mis tareas",task_ph:"Nombre de la tarea…",task_urgent:"Urgente",task_normal:"Normal",task_low:"Baja",announcements_title:"Anuncios",inscriptions_preview_title:"Inscripciones 2026",insc_status_new:"Nuevo",insc_status_review:"A revisar",insc_status_wait:"En espera",insc_status_validated:"Validado",
    classes_eyebrow:"Todas las clases",classes_search_ph:"Buscar una clase…",classes_filters:"Filtros",classes_new:"Nueva clase",stat_classes:"Clases",stat_eleves:"Alumnos",stat_presents_today:"Presentes hoy",stat_obs_7j:"Observaciones · 7d",tab_all:"Todas",tab_maternelle:"Infantil",tab_cycle2:"Ciclo 2",tab_cycle3:"Ciclo 3",sort_by:"Ordenar por",sort_niveau:"Nivel",sort_effectif:"Efectivo",sort_presence:"Asistencia",sort_nom:"Nombre de clase",presence_du_jour:"Asistencia del día",chip_presents:"presentes",chip_retards:"tarde",chip_absents:"ausentes",obs_7j_tiny:"Obs. 7d",conv_tiny:"Conv.",
    back_classes:"Clases",teacher_role:"Docente",export_list:"Exportar lista",print_presence:"Imprimir asistencia",hist_presence:"Historial asistencia",archive_class:"Archivar clase",add_obs:"Añadir observación",today_btn:"Hoy",students_label:"Alumnos",filter_ph:"Filtrar…",filter_all_students:"Todos los alumnos",filter_present_only:"Solo presentes",filter_absent_only:"Solo ausentes",filter_watch:"A vigilar",status_present:"Presente",status_late:"Tarde",status_absent:"Ausente",status_btn_p:"P",status_btn_r:"T",status_btn_a:"A",tab_suivi:"Seguimiento",tab_journal:"Diario",tab_parents:"Padres",tab_sante:"Salud",tab_docs:"Docs",skills_title:"Competencias",last_obs:"Última observación",ia_summary:"Resumen IA",see_history:"Ver historial",journal_title:"Diario del alumno",type_obs:"Observación",type_rdv:"Reunión padres",type_note:"Nota",contacts_title:"Contactos familiares",main_contact:"Contacto principal",pickup_auth:"Autorización de salida",health_title:"Ficha de salud",allergies_label:"Alergias e intolerancias",pai_label:"PAI (Plan de acogida individualizado)",doctor_label:"Médico",last_visit_label:"Última visita médica",export_health:"Exportar ficha de salud",docs_title:"Documentos",scan_btn:"Escanear",missing_doc:"Faltante",to_provide:"A entregar",edit_file:"Editar ficha",export_pdf:"Exportar como PDF",program_rdv:"Programar reunión de padres",archive_student:"Archivar alumno",
    equipe_eyebrow:"Dirección",equipe_title:"Mi equipo",inscriptions_tile:"Inscripciones",inscriptions_tile_sub:"Expedientes a tramitar",annonces_tile:"Anuncios",annonces_tile_sub:"Comunicaciones escolares",conv_tile:"Conversaciones",conv_tile_sub:"Padres · equipo",teachers_title:"Docentes",tab_tous:"Todos",tab_actifs:"Activos",tab_conge:"De baja",activity_col:"Última actividad",to_process_rail:"A tramitar",latest_annonces:"Últimos anuncios",
    ecole_eyebrow:"Establecimiento",ecole_view_public:"Ver perfil público",identity_title:"Identidad",identity_sub:"Visible para familias y el equipo.",change_logo:"Cambiar logo",logo_hint:"PNG, SVG · mín. 512×512",field_school_name:"Nombre del colegio",field_address:"Dirección",field_phone:"Teléfono",field_email:"Email",school_year_title:"Año escolar",field_current_year:"Año en curso",field_enrolled:"Alumnos inscritos",field_insc_2026:"Inscripciones 2026",periods_title:"Períodos",active_period_chip:"En curso",edit_dates:"Editar fechas",set_current:"Establecer como actual",reset_period:"Restablecer",direction_title:"Dirección",integrations_title:"Integraciones",integrations_sub:"Conectado al ecosistema escolar.",active_chip:"Activo",ia_weekly_title:"Resumen IA semanal",ia_activate:"Activar para el colegio",permissions_btn:"Permisos",
    conv_eyebrow:"Comunicación",conv_title:"Conversaciones",new_conv:"Nueva conversación",filter_all_conv:"Todos",filter_unread:"Sin leer",filter_parents_conv:"Padres",filter_equipe_conv:"Equipo",online_label:"en línea",write_msg_ph:"Escribe un mensaje…",send_btn:"Enviar",profile_btn:"Perfil",pin_conv:"Fijar",mute_conv:"Silenciar",delete_conv:"Eliminar",shared_files:"Archivos compartidos",linked_to:"Vinculado a",no_conv:"Sin conversaciones en este filtro.",
    insc_eyebrow:"Gestión · 2026",insc_title:"Inscripciones",insc_received:"Expedientes recibidos",insc_to_process:"A tramitar",insc_validated_count:"Validados",insc_rate:"Tasa de aceptación",tab_all_insc:"Todos · 7",tab_new_insc:"Nuevos",tab_review_insc:"A revisar",tab_wait_insc:"En espera",tab_validated_insc:"Validados",new_file:"Nuevo expediente",family_label:"Familia",file_label:"Expediente",doc_received:"Recibido",doc_missing:"Faltante",propose_rdv:"Proponer reunión",refuse_btn:"Rechazar",validate_insc:"Validar inscripción",
    agenda_eyebrow:"Mayo 2026 · Semana 20",agenda_title:"Agenda",agenda_today:"Hoy",agenda_new:"Nuevo evento",view_day:"Día",view_week:"Semana",view_month:"Mes",view_year:"Año",event_title_label:"Título *",event_day_label:"Día",event_hour_label:"Hora",event_dur_label:"Duración",event_who_label:"Participantes / lugar",event_desc_label:"Descripción (opcional)",event_type_label:"Tipo",event_general:"General",event_important:"Importante",add_to_cal:"Añadir al calendario",description_label:"Descripción",delete_event:"Eliminar",edit_event:"Editar",
    settings_eyebrow:"Ajustes",settings_title:"Ajustes",param_compte:"Cuenta",param_notifs:"Notificaciones",param_secu:"Seguridad",param_billing:"Facturación",param_team:"Miembros y roles",param_ia:"Scolia IA",account_title:"Cuenta",change_photo_btn:"Cambiar foto",photo_hint:"JPG o PNG · 2 MB máx.",firstname_label:"Nombre",lastname_label:"Apellido",email_pro_label:"Email profesional",phone_pro_label:"Teléfono",title_input_label:"Título",lang_label:"Idioma de la interfaz",notifs_title:"Notificaciones",notifs_sub:"Elige qué debe avisarte Scolia.",secu_title:"Seguridad",two_factor_label:"Autenticación en dos pasos",two_factor_sub:"Activada · app Authy",active_chip_secu:"Activa",change_pw:"Cambiar contraseña",disconnect_others:"Cerrar otras sesiones",active_sessions:"Sesiones activas",this_session:"Esta sesión",disconnect_btn:"Cerrar sesión",billing_title:"Facturación",current_plan:"Plan actual",per_month:"/ mes sin IVA",change_plan:"Cambiar plan",see_usage:"Ver consumo",last_invoices:"Últimas facturas",paid_status:"Pagada",download_btn:"Descargar",team_title_param:"Miembros y roles",invite_btn:"Invitar",permissions_btn_param:"Permisos",ia_param_title:"Scolia IA",teacher_role_chip:"Docente",direction_chip:"Dirección",
    annonces_eyebrow:"Comunicación",annonces_title:"Anuncios",new_annonce_btn:"Nuevo anuncio",tab_all_ann:"Todos",tab_pinned_ann:"Anclados",pinned_chip:"Anclado",epingler:"Anclar",desepingler:"Desanclar",export_ann:"Exportar",no_annonce:"Sin anuncios",no_annonce_sub:"Publica un anuncio para el equipo o los padres.",delete_ann:"Eliminar",
    parent_eyebrow:"Mi hijo/a",parent_contact_btn:"Contactar el colegio",child_since:"Escolarizado desde",present_today_label:"Asistencia hoy",present_status:"Presente",send_msg_btn:"Enviar un mensaje",request_rdv_btn:"Solicitar una reunión",tab_suivi_parent:"Seguimiento",tab_annonces_parent:"Anuncios",tab_agenda_parent:"Agenda",last_obs_parent:"Últimas observaciones",school_annonces_parent:"Anuncios del colegio",week_agenda_parent:"Agenda semanal",no_event:"Sin eventos",
    annonce_modal_eyebrow:"Nuevo anuncio",annonce_modal_sub:"Visible para todo el equipo docente",annonce_question:"¿Qué comunicar al colegio?",annonce_ph:"Ej: Reunión equipo jueves 12h. Hablamos de los balances del período…",attach_file:"Adjuntar archivo",schedule_btn:"Programar",audience_btn:"Audiencia · Equipo",write_ia_btn:"Redactar con IA",pin_label:"Fijar",pin_sub:"El anuncio permanecerá en la parte superior.",draft_btn:"Borrador",publish_btn:"Publicar",cal_past:"Terminado",cal_back_to_school:"Vuelta al colegio",cal_xmas:"Vacaciones de Navidad",
  },
};

// ---------- App context ----------
const AppCtx = createContext(null);
const useApp = () => useContext(AppCtx);

function AppProvider({ children }) {
  const [toasts, setToasts] = useStateE([]);
  const [editEleve, setEditEleve] = useStateE(null);
  const [editTeacher, setEditTeacher] = useStateE(null);
  const [confirm, setConfirm] = useStateE(null);
  const [lang, setLangState] = useStateE(() => localStorage.getItem("scolia_lang") || "fr");
  const idRef = useRef(0);

  const setLang = (l) => { localStorage.setItem("scolia_lang", l); setLangState(l); };
  const t = (key) => (TRANSLATIONS[lang] || TRANSLATIONS.fr)[key] ?? TRANSLATIONS.fr[key] ?? key;

  const toast = (msg, kind = "ok") => {
    const id = ++idRef.current;
    setToasts(prev => [...prev, { id, msg, kind }]);
    setTimeout(() => setToasts(prev => prev.filter(x => x.id !== id)), 3000);
  };

  const api = {
    toast,
    editEleve:   (s) => setEditEleve(s),
    editTeacher: (tc) => setEditTeacher(tc),
    confirm:     (opts) => setConfirm(opts),
    lang, setLang, t,
  };

  return (
    <AppCtx.Provider value={api}>
      {children}
      <div style={{
        position: "fixed", bottom: 24, right: 24, zIndex: 100,
        display: "flex", flexDirection: "column", gap: 8,
        pointerEvents: "none",
      }}>
        {toasts.map(toast => (
          <div key={toast.id} style={{
            background: "var(--ink)", color: "white",
            padding: "12px 16px", borderRadius: 12,
            boxShadow: "var(--shadow-pop)",
            fontSize: 13.5, fontWeight: 500,
            display: "flex", alignItems: "center", gap: 10,
            minWidth: 260, animation: "fade .15s ease",
          }}>
            <span style={{
              width: 18, height: 18, borderRadius: 99,
              background: toast.kind === "warn" ? "var(--warn)" : "var(--brand)",
              display: "grid", placeItems: "center", flexShrink: 0,
            }}>
              <Icons.Check size={12} sw={2.4}/>
            </span>
            <span style={{flex: 1}}>{toast.msg}</span>
          </div>
        ))}
      </div>
      {editEleve   && <EditEleveModal   student={editEleve} onClose={() => setEditEleve(null)}/>}
      {editTeacher && <EditTeacherModal teacher={editTeacher} onClose={() => setEditTeacher(null)}/>}
      {confirm     && <ConfirmModal {...confirm} onClose={() => setConfirm(null)}/>}
    </AppCtx.Provider>
  );
}

// ---------- Dropdown menu ----------
function DropMenu({ trigger, items, align = "right" }) {
  const [open, setOpen] = useStateE(false);
  const wrap = useRef(null);
  useEffectE(() => {
    if (!open) return;
    const h = (e) => { if (wrap.current && !wrap.current.contains(e.target)) setOpen(false); };
    window.addEventListener("mousedown", h);
    return () => window.removeEventListener("mousedown", h);
  }, [open]);

  return (
    <div ref={wrap} style={{position: "relative", display: "inline-block"}}>
      <div onClick={() => setOpen(o => !o)}>{trigger}</div>
      {open && (
        <div style={{
          position: "absolute", top: "calc(100% + 6px)", [align]: 0,
          background: "var(--surface)", border: "1px solid var(--border)",
          borderRadius: 12, boxShadow: "var(--shadow-pop)",
          minWidth: 220, padding: 6, zIndex: 50,
        }}>
          {items.map((it, i) => it === "-" ? (
            <div key={i} style={{height: 1, background: "var(--border)", margin: "4px 6px"}}/>
          ) : (
            <button key={i} onClick={() => { setOpen(false); it.onClick?.(); }}
                    style={{
                      width: "100%", textAlign: "left", padding: "9px 12px",
                      borderRadius: 8, font: "inherit", fontSize: 13.5,
                      color: it.danger ? "var(--danger)" : "var(--ink-2)",
                      display: "flex", alignItems: "center", gap: 10, fontWeight: 500,
                    }}
                    onMouseEnter={e => e.currentTarget.style.background = "var(--surface-2)"}
                    onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
              {it.icon && <it.icon size={15}/>}
              <span style={{flex: 1}}>{it.label}</span>
              {it.kbd && <span className="kbd">{it.kbd}</span>}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ---------- Confirm dialog ----------
function ConfirmModal({ title, body, confirmLabel, danger, onConfirm, onClose }) {
  const { t } = useApp();
  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" style={{width: 440}} onClick={e => e.stopPropagation()}>
        <div style={{padding: 24}}>
          <div className="h2" style={{marginBottom: 8}}>{title}</div>
          <div style={{color: "var(--ink-3)", fontSize: 14, lineHeight: 1.55}}>{body}</div>
        </div>
        <div className="row" style={{padding: "14px 20px", borderTop: "1px solid var(--border)", justifyContent: "flex-end", gap: 8, background: "var(--surface-2)"}}>
          <button className="btn" onClick={onClose}>{t("cancel")}</button>
          <button className={`btn ${danger ? "" : "btn-primary"}`}
                  style={danger ? {background: "var(--danger)", color: "white", borderColor: "var(--danger)"} : {}}
                  onClick={() => { onConfirm?.(); onClose(); }}>
            {confirmLabel || t("confirm_lbl")}
          </button>
        </div>
      </div>
    </div>
  );
}

// ---------- Edit Élève ----------
function EditEleveModal({ student, onClose }) {
  const { toast, t } = useApp();
  const [name, setName] = useStateE(student.name);
  const [age, setAge]   = useStateE(student.age);
  const [status, setStatus] = useStateE(student.status);
  const [note, setNote] = useStateE(student.note);

  const save = () => {
    Object.assign(student, { name, age: +age, status, note });
    toast(`Fiche de ${name} mise à jour`);
    onClose();
  };

  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" style={{width: 640}} onClick={e => e.stopPropagation()}>
        <div className="row" style={{padding: "16px 20px", justifyContent: "space-between", alignItems: "center", borderBottom: "1px solid var(--border)"}}>
          <div className="row gap-3" style={{alignItems: "center"}}>
            <Sketch id={student.id} size={42}/>
            <div>
              <div className="eyebrow">{t("edit_file")}</div>
              <div className="h3">{student.name}</div>
            </div>
          </div>
          <button className="icon-btn" onClick={onClose}><Icons.X size={16}/></button>
        </div>
        <div style={{padding: "22px 24px"}}>
          <div className="row gap-4">
            <div style={{flex: 2}}><Input label="Prénom et nom" value={name} onChange={setName}/></div>
            <div style={{flex: 1}}><Input label="Âge" value={age} onChange={setAge} type="number"/></div>
          </div>
          <div className="row gap-4">
            <div style={{flex: 1}}><Input label="Classe" value="CE1 Camélia" disabled/></div>
            <div style={{flex: 1}}><Input label="Depuis" value="septembre 2023"/></div>
          </div>
          <Field2 label="Statut du jour">
            <div className="row gap-2">
              {[
                {v: "ok",   l: t("status_present"), cls: "ok"},
                {v: "warn", l: t("status_late"),     cls: "warn"},
                {v: "bad",  l: t("status_absent"),   cls: "bad"},
              ].map(o => (
                <button key={o.v} onClick={() => setStatus(o.v)}
                        className="row gap-2"
                        style={{
                          padding: "10px 14px", borderRadius: 10,
                          border: `1.5px solid ${status===o.v ? "var(--ink)" : "var(--border)"}`,
                          background: status===o.v ? "var(--surface-2)" : "var(--surface)",
                          fontWeight: 600, alignItems: "center",
                        }}>
                  <span className={`status ${o.cls}`}><span className="dot"/></span>
                  {o.l}
                </button>
              ))}
            </div>
          </Field2>
          <Field2 label="Note de suivi">
            <textarea value={note} onChange={e => setNote(e.target.value)}
                      style={inputStyle({textarea: true})} rows={3}/>
          </Field2>
        </div>
        <div className="row" style={{padding: "14px 20px", borderTop: "1px solid var(--border)", justifyContent: "space-between", background: "var(--surface-2)"}}>
          <button className="btn btn-ghost" style={{color: "var(--danger)"}}>{t("archive_student")}</button>
          <div className="row gap-2">
            <button className="btn" onClick={onClose}>{t("cancel")}</button>
            <button className="btn btn-primary" onClick={save}><Icons.Check size={14}/>{t("save")}</button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------- Edit Enseignant ----------
function EditTeacherModal({ teacher, onClose }) {
  const { toast, t } = useApp();
  const [name, setName] = useStateE(teacher.name);
  const [classe, setClasse] = useStateE(teacher.classe);
  const [perms, setPerms] = useStateE({ obs: true, parents: true, paie: false, dir: false });

  const save = () => {
    teacher.name = name; teacher.classe = classe;
    toast(`Profil de ${name} mis à jour`);
    onClose();
  };

  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" style={{width: 680}} onClick={e => e.stopPropagation()}>
        <div className="row" style={{padding: "16px 20px", justifyContent: "space-between", alignItems: "center", borderBottom: "1px solid var(--border)"}}>
          <div className="row gap-3" style={{alignItems: "center"}}>
            <Photo src={teacher.img} size={42}/>
            <div>
              <div className="eyebrow">Modifier le profil</div>
              <div className="h3">{teacher.name}</div>
            </div>
          </div>
          <button className="icon-btn" onClick={onClose}><Icons.X size={16}/></button>
        </div>
        <div style={{padding: "22px 24px"}}>
          <div className="row gap-4">
            <div style={{flex: 2}}><Input label="Prénom et nom" value={name} onChange={setName}/></div>
            <div style={{flex: 1}}><Input label="Classe" value={classe} onChange={setClasse}/></div>
          </div>
          <div className="row gap-4">
            <div style={{flex: 1}}><Input label="Email" value={`${teacher.first.toLowerCase()}@tournesol.fr`}/></div>
            <div style={{flex: 1}}><Input label="Téléphone" value="06 12 34 56 78"/></div>
          </div>
          <Field2 label="Rôle dans l'école">
            <div className="row gap-2">
              {["Enseignant·e", "Direction", "ATSEM", "Remplaçant·e"].map(r => (
                <button key={r}
                        className={r === "Enseignant·e" ? "chip brand" : "chip"}
                        style={{padding: "8px 12px"}}>{r}</button>
              ))}
            </div>
          </Field2>
          <Field2 label={t("permissions_btn")}>
            <div className="col gap-2">
              {[
                {k: "obs",     l: "Saisir des observations",     d: "Sur les élèves de sa classe"},
                {k: "parents", l: "Communiquer avec les parents", d: "Conversations + annonces"},
                {k: "paie",    l: "Voir les paiements",           d: "Statut + relances"},
                {k: "dir",     l: "Accès direction",              d: "Pilotage + équipe + école"},
              ].map(p => (
                <div key={p.k} className="row gap-3" style={{
                  padding: "12px 14px", border: "1px solid var(--border)", borderRadius: 10,
                  alignItems: "center",
                }}>
                  <div style={{flex: 1}}>
                    <div style={{fontWeight: 600, color: "var(--ink)", fontSize: 13.5}}>{p.l}</div>
                    <div className="meta" style={{marginTop: 2}}>{p.d}</div>
                  </div>
                  <Toggle on={perms[p.k]} onClick={() => setPerms(s => ({...s, [p.k]: !s[p.k]}))}/>
                </div>
              ))}
            </div>
          </Field2>
        </div>
        <div className="row" style={{padding: "14px 20px", borderTop: "1px solid var(--border)", justifyContent: "space-between", background: "var(--surface-2)"}}>
          <button className="btn btn-ghost" style={{color: "var(--danger)"}}>Suspendre l'accès</button>
          <div className="row gap-2">
            <button className="btn" onClick={onClose}>{t("cancel")}</button>
            <button className="btn btn-primary" onClick={save}><Icons.Check size={14}/>{t("save")}</button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------- Form atoms ----------
const inputStyle = ({textarea} = {}) => ({
  width: "100%", padding: "11px 14px",
  border: "1px solid var(--border)", borderRadius: 10,
  background: "var(--surface)", color: "var(--ink)",
  font: "inherit", fontSize: 14, fontWeight: 500,
  outline: "none", resize: textarea ? "vertical" : undefined,
  fontFamily: "inherit",
});
function Input({ label, value, onChange, type = "text", disabled }) {
  return (
    <Field2 label={label}>
      <input type={type} value={value} disabled={disabled}
             onChange={e => onChange?.(e.target.value)}
             style={{...inputStyle(), opacity: disabled ? .6 : 1}}/>
    </Field2>
  );
}
function Field2({ label, children }) {
  return (
    <div style={{marginBottom: 14}}>
      <div className="eyebrow" style={{marginBottom: 6}}>{label}</div>
      {children}
    </div>
  );
}

window.AppProvider = AppProvider;
window.useApp = useApp;
window.DropMenu = DropMenu;
window.Input = Input;
window.Field2 = Field2;
window.inputStyle = inputStyle;
