/**
* ShareConversationCard Component
*
* Simple card shown after chatbot uses tools.
* Clicking "Post to Discussion" navigates to the bill/forum discussion
* with the conversation pre-filled in the text box for the user to post.
*/
'use client';
import { useState, useEffect } from 'react';
import { MessageSquare, Send, Eye, X } from 'lucide-react';
import { useRouter, usePathname } from '@/i18n/navigation';
import { getCategories } from '@/actions/forum';
import type { ContextType } from '@/lib/types/chat';
/**
* Maps conversation context/content to forum category slugs.
* Returns the most appropriate category slug based on keywords and context.
*/
function detectForumCategory(
userQuestion: string,
aiResponse: string,
contextType?: ContextType
): string {
const combinedText = `${userQuestion} ${aiResponse}`.toLowerCase();
// Topic keyword mappings to category slugs
// These map to actual forum_categories in the database
const topicMappings: { keywords: string[]; slug: string }[] = [
// MPs and politicians
{
keywords: ['mp', 'member of parliament', 'politician', 'minister', 'prime minister', 'cabinet', 'riding', 'constituency', 'elected'],
slug: 'mps-politicians',
},
// Healthcare
{
keywords: ['health', 'healthcare', 'hospital', 'doctor', 'nurse', 'medical', 'patient', 'pharmaceutical', 'drug', 'medicine', 'mental health'],
slug: 'healthcare',
},
// Economy and Finance
{
keywords: ['economy', 'economic', 'finance', 'budget', 'tax', 'taxes', 'inflation', 'interest rate', 'bank', 'investment', 'gdp', 'deficit', 'debt', 'spending'],
slug: 'economy-finance',
},
// Environment
{
keywords: ['environment', 'climate', 'carbon', 'emission', 'pollution', 'green', 'renewable', 'energy', 'sustainability', 'conservation'],
slug: 'environment',
},
// Immigration
{
keywords: ['immigration', 'immigrant', 'refugee', 'visa', 'citizenship', 'border', 'asylum', 'newcomer'],
slug: 'immigration',
},
// Justice and Law
{
keywords: ['justice', 'law', 'legal', 'court', 'crime', 'criminal', 'police', 'prosecution', 'judge', 'charter', 'rights'],
slug: 'justice-law',
},
// Indigenous Affairs
{
keywords: ['indigenous', 'first nations', 'aboriginal', 'métis', 'inuit', 'reconciliation', 'treaty', 'reserve'],
slug: 'indigenous-affairs',
},
// Foreign Affairs
{
keywords: ['foreign', 'international', 'diplomacy', 'trade agreement', 'nato', 'united nations', 'embassy', 'sanctions'],
slug: 'foreign-affairs',
},
// Defence
{
keywords: ['defence', 'defense', 'military', 'armed forces', 'veteran', 'national security', 'norad'],
slug: 'defence',
},
// Housing
{
keywords: ['housing', 'rent', 'mortgage', 'affordable housing', 'homeless', 'real estate', 'property'],
slug: 'housing',
},
// Education
{
keywords: ['education', 'school', 'university', 'college', 'student', 'tuition', 'teacher', 'professor'],
slug: 'education',
},
// Transportation
{
keywords: ['transportation', 'transport', 'highway', 'rail', 'transit', 'airline', 'airport', 'infrastructure'],
slug: 'transportation',
},
// Lobbying - based on context type
{
keywords: ['lobby', 'lobbying', 'lobbyist', 'government relations', 'influence'],
slug: 'lobbying-ethics',
},
// Government spending - based on context type
{
keywords: ['contract', 'procurement', 'grant', 'spending', 'expenditure', 'government contract'],
slug: 'government-spending',
},
];
// First, check if context type gives us a strong hint
if (contextType === 'mp') {
return 'mps-politicians';
}
if (contextType === 'lobbying') {
return 'lobbying-ethics';
}
if (contextType === 'spending') {
return 'government-spending';
}
// Score each category based on keyword matches
let bestMatch = { slug: 'general', score: 0 };
for (const mapping of topicMappings) {
let score = 0;
for (const keyword of mapping.keywords) {
// Count occurrences and weight longer phrases higher
const regex = new RegExp(keyword, 'gi');
const matches = combinedText.match(regex);
if (matches) {
score += matches.length * (keyword.split(' ').length); // Weight multi-word phrases higher
}
}
if (score > bestMatch.score) {
bestMatch = { slug: mapping.slug, score };
}
}
// Return best match if score is above threshold, otherwise general
return bestMatch.score >= 2 ? bestMatch.slug : 'general';
}
interface ShareConversationCardProps {
userQuestion: string;
aiResponse: string;
conversationId: string;
billContext?: {
number: string;
session: string;
title?: string;
};
/** Optional context type from the conversation for better category detection */
contextType?: ContextType;
navigationUrl?: string;
onDismiss: () => void;
}
export function ShareConversationCard({
userQuestion,
aiResponse,
conversationId,
billContext,
contextType,
navigationUrl,
onDismiss,
}: ShareConversationCardProps) {
const router = useRouter();
const pathname = usePathname();
// State for detected category
const [detectedCategory, setDetectedCategory] = useState<{ slug: string; name: string } | null>(null);
// Detect and validate forum category on mount (for non-bill conversations)
useEffect(() => {
if (billContext) return; // Bill context takes priority
const detectCategory = async () => {
// Detect the best category based on content
const detectedSlug = detectForumCategory(userQuestion, aiResponse, contextType);
// Fetch available categories to validate and get the name
const result = await getCategories();
if (result.success && result.data) {
// Find the category by slug
const category = result.data.find((c) => c.slug === detectedSlug);
if (category) {
setDetectedCategory({ slug: category.slug, name: category.name });
} else {
// Fallback to General Discussion if detected category doesn't exist
const generalCategory = result.data.find((c) => c.slug === 'general');
if (generalCategory) {
setDetectedCategory({ slug: generalCategory.slug, name: generalCategory.name });
} else {
// Ultimate fallback - use first category or just 'general'
const fallback = result.data[0];
setDetectedCategory(fallback ? { slug: fallback.slug, name: fallback.name } : { slug: 'general', name: 'General Discussion' });
}
}
} else {
// If categories fetch fails, default to general
setDetectedCategory({ slug: 'general', name: 'General Discussion' });
}
};
detectCategory();
}, [billContext, userQuestion, aiResponse, contextType]);
// Truncate content for preview
const truncate = (text: string, maxLength: number) => {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength).trim() + '...';
};
const handlePostToDiscussion = () => {
// Store conversation data in sessionStorage for the discussion page to pick up
const conversationData = {
userQuestion,
aiResponse,
conversationId,
billContext,
categoryContext: detectedCategory,
timestamp: Date.now(),
};
sessionStorage.setItem('share_conversation_draft', JSON.stringify(conversationData));
// Determine the target URL
let targetPath: string;
if (billContext) {
// Bill discussion
targetPath = `/bills/${billContext.session}/${billContext.number}`;
} else if (detectedCategory) {
// Forum category discussion
targetPath = `/forum/${detectedCategory.slug}`;
} else {
// Fallback to general forum
targetPath = '/forum/general';
}
// Check if we're already on the target page
const isOnBillPage = billContext && pathname?.includes(`/bills/${billContext.session}/${billContext.number}`);
const isOnForumCategoryPage = !billContext && detectedCategory && pathname?.includes(`/forum/${detectedCategory.slug}`);
if (isOnBillPage || isOnForumCategoryPage) {
// Already on the target page - dispatch event to open discussion with pre-fill
window.dispatchEvent(new CustomEvent('openDiscussionWithDraft', {
detail: conversationData
}));
// Trigger hash change to load the draft
window.location.hash = '';
setTimeout(() => {
window.location.hash = 'discussions';
}, 10);
// Scroll to the discussion form after a short delay
setTimeout(() => {
const form = document.querySelector('[data-discussion-form]') || document.querySelector('form');
if (form) {
form.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, 150);
} else {
// Navigate to the bill/forum page with hash to open fulltext/discussions
router.push(`${targetPath}#discussions` as any);
}
};
const handleViewResults = () => {
if (navigationUrl) {
window.open(navigationUrl, '_blank', 'noopener,noreferrer');
}
};
// Determine the subtitle based on context
const getSubtitle = () => {
if (billContext) {
return `Bill ${billContext.number} (${billContext.session})${billContext.title ? ` • ${truncate(billContext.title, 35)}` : ''}`;
}
if (detectedCategory) {
return `Forum • ${detectedCategory.name}`;
}
return 'AI-powered analysis';
};
return (
<div className="mt-4 p-4 bg-gray-800/50 border border-gray-700 rounded-lg">
{/* Header */}
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
<MessageSquare className="w-5 h-5 text-accent-red" />
<div>
<h3 className="text-sm font-semibold text-white">
Share This Conversation
</h3>
<p className="text-xs text-gray-400 mt-0.5">
{getSubtitle()}
</p>
</div>
</div>
<button
onClick={onDismiss}
className="p-1 hover:bg-gray-700 rounded transition-colors"
title="Dismiss"
>
<X className="w-4 h-4 text-gray-400" />
</button>
</div>
{/* Exchange Preview */}
<div className="mb-3 p-3 bg-gray-900/50 border border-gray-700 rounded-md text-xs">
<div className="mb-2">
<span className="text-gray-500">You:</span>
<p className="text-gray-300 mt-0.5">{truncate(userQuestion, 100)}</p>
</div>
<div>
<span className="text-gray-500">Gordie:</span>
<p className="text-gray-300 mt-0.5">{truncate(aiResponse, 150)}</p>
</div>
</div>
{/* Action Buttons */}
<div className="flex flex-wrap gap-2">
{/* Primary: Post to Discussion */}
<button
onClick={handlePostToDiscussion}
className="inline-flex items-center gap-2 px-4 py-2 bg-accent-red hover:bg-accent-red/90 text-white rounded-lg text-sm font-medium transition-colors"
>
<Send className="w-4 h-4" />
<span>Post to Discussion</span>
</button>
{/* Secondary: View Results (if navigation URL provided) - opens in new tab */}
{navigationUrl && (
<button
onClick={handleViewResults}
className="inline-flex items-center gap-2 px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg text-sm font-medium transition-colors"
>
<Eye className="w-4 h-4" />
<span>View Results</span>
</button>
)}
</div>
</div>
);
}