/**
* EqualizationChat Component
* Displays cached step explanations and quick action buttons for the equalization visualizer
*
* When a step is selected, automatically opens the chatbot and shows a cached explanation.
* Users can also ask follow-up questions via the suggested prompts.
*/
'use client';
import React, { useEffect, useRef } from 'react';
import { useLocale } from 'next-intl';
import { MessageSquare } from 'lucide-react';
import { useChatStore, useChatContext } from '@/lib/stores/chatStore';
import { useVisualizerStore } from '@/hooks/useVisualizerStore';
import { useAuth } from '@/contexts/AuthContext';
import { stepExplanations } from '@/lib/visualizer/equalizationExplanations';
import type { Message } from '@/lib/types/chat';
const stepQuestions: Record<number, { en: string[]; fr: string[] }> = {
1: {
en: [
'Why do we need equalization?',
'How does it differ from other transfers?',
'What happens if a province opts out?',
],
fr: [
'Pourquoi avons-nous besoin de la péréquation?',
'En quoi diffère-t-elle des autres transferts?',
'Que se passe-t-il si une province se retire?',
],
},
2: {
en: [
'Why is natural resources only 15%?',
'How are the weights determined?',
'What about user fees and licenses?',
],
fr: [
'Pourquoi les ressources naturelles ne sont-elles que 15%?',
'Comment les poids sont-ils déterminés?',
'Qu\'en est-il des frais d\'utilisation et licences?',
],
},
3: {
en: [
'Why is Alberta\'s capacity so high?',
'How is fiscal capacity calculated exactly?',
'What affects a province\'s capacity most?',
],
fr: [
'Pourquoi la capacité de l\'Alberta est-elle si élevée?',
'Comment la capacité fiscale est-elle calculée exactement?',
'Qu\'est-ce qui affecte le plus la capacité d\'une province?',
],
},
4: {
en: [
'Why exclude territories from the standard?',
'How often is the standard updated?',
'What if a province hits exactly 100%?',
],
fr: [
'Pourquoi exclure les territoires de la norme?',
'À quelle fréquence la norme est-elle mise à jour?',
'Et si une province atteint exactement 100%?',
],
},
5: {
en: [
'Why doesn\'t Ontario receive payments?',
'Could Alberta ever receive payments?',
'How does Newfoundland stay above average?',
],
fr: [
'Pourquoi l\'Ontario ne reçoit-il pas de paiements?',
'L\'Alberta pourrait-elle recevoir des paiements?',
'Comment Terre-Neuve reste-t-elle au-dessus de la moyenne?',
],
},
6: {
en: [
'Walk me through Quebec\'s calculation',
'Why is PEI\'s per-capita payment so high?',
'What happens if population changes mid-year?',
],
fr: [
'Expliquez-moi le calcul du Québec',
'Pourquoi le paiement par habitant de l\'Î.-P.-É. est-il si élevé?',
'Que se passe-t-il si la population change en cours d\'année?',
],
},
7: {
en: [
'Who actually pays for equalization?',
'Is equalization fair to Alberta?',
'What happens if oil prices crash?',
],
fr: [
'Qui paie réellement pour la péréquation?',
'La péréquation est-elle juste pour l\'Alberta?',
'Que se passe-t-il si les prix du pétrole s\'effondrent?',
],
},
};
export function EqualizationChat() {
const locale = useLocale() as 'en' | 'fr';
const { user } = useAuth();
const { equalizationStep, visualizationType } = useVisualizerStore();
const { setContext } = useChatContext();
const { isLoading, sendMessage, conversation, createConversation, toggleOpen, isOpen, addMessage, messages } = useChatStore();
// Track which steps have been explained in this session
const explainedStepsRef = useRef<Set<number>>(new Set());
const previousStepRef = useRef<number>(equalizationStep);
// Show cached explanation when step changes
useEffect(() => {
const showExplanation = async () => {
// Skip if step hasn't changed
if (previousStepRef.current === equalizationStep) {
return;
}
previousStepRef.current = equalizationStep;
// Skip if this step was already explained in this session
if (explainedStepsRef.current.has(equalizationStep)) {
// Still open chatbot if not open
if (!isOpen) {
toggleOpen();
}
return;
}
// Mark this step as explained
explainedStepsRef.current.add(equalizationStep);
// Set the visualizer context
setContext('visualizer', undefined, {
view: visualizationType,
step: equalizationStep,
});
// Open the chatbot if not already open
if (!isOpen) {
toggleOpen();
}
// Get the cached explanation for this step
const explanation = stepExplanations[equalizationStep];
if (!explanation) return;
// Add the explanation as an assistant message (cached, not from API)
const cachedMessage: Message = {
id: `cached-step-${equalizationStep}-${Date.now()}`,
conversation_id: conversation?.id || 'visualizer-cache',
role: 'assistant',
content: explanation[locale],
used_byo_key: false,
created_at: new Date().toISOString(),
};
addMessage(cachedMessage);
};
showExplanation();
}, [equalizationStep, visualizationType, setContext, toggleOpen, isOpen, addMessage, conversation?.id, locale]);
// Show initial explanation on mount (step 1)
useEffect(() => {
// Only run once on mount, and only if no messages exist yet
if (explainedStepsRef.current.size === 0 && messages.length === 0) {
explainedStepsRef.current.add(1);
const explanation = stepExplanations[1];
if (explanation) {
const cachedMessage: Message = {
id: `cached-step-1-${Date.now()}`,
conversation_id: conversation?.id || 'visualizer-cache',
role: 'assistant',
content: explanation[locale],
used_byo_key: false,
created_at: new Date().toISOString(),
};
addMessage(cachedMessage);
if (!isOpen) {
toggleOpen();
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Only run on mount
const handleQuestionClick = async (question: string) => {
if (isLoading) return;
// Set the visualizer context
setContext('visualizer', undefined, {
view: visualizationType,
step: equalizationStep,
});
// Create conversation if needed
if (!conversation) {
await createConversation({
type: 'visualizer',
data: {
view: visualizationType,
step: equalizationStep,
name: locale === 'en' ? 'Equalization Explainer' : 'Explicateur de péréquation',
},
});
}
// Open the floating chatbot if not already open
if (!isOpen) {
toggleOpen();
}
// Send the message to the floating chatbot
await sendMessage(question);
};
const currentQuestions = stepQuestions[equalizationStep]?.[locale] || [];
// Show sign-in prompt for non-authenticated users (for follow-up questions only)
// Cached explanations work without sign-in
return (
<div className="border border-border-subtle rounded-lg overflow-hidden bg-bg-elevated">
{/* Header */}
<div className="flex items-center gap-2 p-3">
<MessageSquare className="w-4 h-4 text-accent-red" />
<span className="text-sm font-medium text-text-primary">
{locale === 'en' ? 'Ask Gordie' : 'Demandez à Gordie'}
</span>
</div>
{/* Quick action buttons */}
<div className="p-3 pt-0 space-y-1.5">
{!user && (
<p className="text-xs text-text-tertiary mb-2">
{locale === 'en'
? 'Sign in to ask follow-up questions'
: 'Connectez-vous pour poser des questions de suivi'}
</p>
)}
{currentQuestions.map((q, i) => (
<button
key={i}
onClick={() => handleQuestionClick(q)}
disabled={isLoading || !user}
className="w-full text-left text-xs p-2 rounded bg-bg-secondary hover:bg-bg-primary text-text-secondary hover:text-text-primary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{q}
</button>
))}
{!user && (
<a
href={`/${locale}/signin`}
className="block w-full text-center py-2 px-3 mt-2 text-sm font-medium bg-accent-red text-white rounded-lg hover:bg-accent-red/90 transition-colors"
>
{locale === 'en' ? 'Sign In' : 'Connexion'}
</a>
)}
</div>
</div>
);
}