"use client"; import type React from "react"; import { useState, useEffect } from "react"; import { useUser } from "@/context/user-context"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Plus, Trash2, Sparkles, Loader2 } from "lucide-react"; import { APIKeyModal } from "@/components/api-key-modal"; import { hasAPIKey, generateQuestData, getAIProvider, } from "@/utils/ai-service"; import { useToast } from "@/hooks/use-toast"; import { predefinedConsumables } from "@/data/items"; export function AddQuestForm() { const { addCustomQuest } = useUser(); const { toast } = useToast(); const [open, setOpen] = useState(false); const [apiKeyModalOpen, setApiKeyModalOpen] = useState(false); const [isGeneratingWithAI, setIsGeneratingWithAI] = useState(false); const [hasAIKey, setHasAIKey] = useState(false); const [formData, setFormData] = useState({ title: "", description: "", difficulty: "C" as "S" | "A" | "B" | "C" | "D" | "E", priority: "Medium" as "High" | "Medium" | "Low", expiry: "One-time", // Changed default to One-time expReward: 30, statPointsReward: 1, goldReward: 0, strReward: 0, agiReward: 0, perReward: 0, intReward: 0, vitReward: 0, itemRewards: [] as { id?: string; name: string; type: string; description: string; }[], }); // Check if AI API key exists on component mount useEffect(() => { setHasAIKey(hasAPIKey()); }, []); const handleChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: name.includes("Reward") && name !== "expReward" ? Number.parseInt(value) || 0 : value, })); }; const handleSelectChange = (name: string, value: string) => { setFormData((prev) => ({ ...prev, [name]: value, })); }; const addItemReward = () => { setFormData((prev) => ({ ...prev, itemRewards: [ ...prev.itemRewards, { name: "", type: "Material", description: "", }, ], })); }; const removeItemReward = (index: number) => { setFormData((prev) => ({ ...prev, itemRewards: prev.itemRewards.filter((_, i) => i !== index), })); }; const updateItemReward = (index: number, field: string, value: string) => { setFormData((prev) => ({ ...prev, itemRewards: prev.itemRewards.map((item, i) => i === index ? { ...item, [field]: value } : item ), })); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // Calculate reward string based on stat rewards const rewardParts = []; if (formData.goldReward > 0) rewardParts.push(`${formData.goldReward} Gold`); if (formData.strReward > 0) rewardParts.push(`+${formData.strReward} Strength`); if (formData.agiReward > 0) rewardParts.push(`+${formData.agiReward} Agility`); if (formData.perReward > 0) rewardParts.push(`+${formData.perReward} Perception`); if (formData.intReward > 0) rewardParts.push(`+${formData.intReward} Intelligence`); if (formData.vitReward > 0) rewardParts.push(`+${formData.vitReward} Vitality`); if (formData.itemRewards.length > 0) { formData.itemRewards.forEach((item) => { rewardParts.push(`${item.name} (${item.type})`); }); } const rewardString = rewardParts.length > 0 ? rewardParts.join(", ") : "Experience only"; // Create the quest object const newQuest = { title: formData.title, description: formData.description, reward: rewardString, difficulty: formData.difficulty, priority: formData.priority, expiry: formData.expiry, expReward: Number.parseInt(formData.expReward.toString()), statPointsReward: formData.statPointsReward, goldReward: formData.goldReward, statRewards: { str: formData.strReward, agi: formData.agiReward, per: formData.perReward, int: formData.intReward, vit: formData.vitReward, }, itemRewards: formData.itemRewards.map((item, index) => { // Check if it's a predefined consumable (has an id property) if (item.id && predefinedConsumables.some((p) => p.id === item.id)) { // Find the predefined consumable to get all its properties const predefined = predefinedConsumables.find( (p) => p.id === item.id ); if (predefined) { return { id: predefined.id, name: predefined.name, type: predefined.type, rarity: predefined.rarity as | "Common" | "Uncommon" | "Rare" | "Epic" | "Legendary", description: predefined.description, quantity: 1, }; } } // Default for custom items return { id: `custom-item-${Date.now()}-${index}`, name: item.name, type: item.type as any, rarity: "Common" as | "Common" | "Uncommon" | "Rare" | "Epic" | "Legendary", description: item.description, quantity: 1, }; }), }; // Add the quest addCustomQuest(newQuest); // Reset form and close dialog setFormData({ title: "", description: "", difficulty: "C", priority: "Medium", expiry: "One-time", // Reset to One-time expReward: 30, statPointsReward: 1, goldReward: 0, strReward: 0, agiReward: 0, perReward: 0, intReward: 0, vitReward: 0, itemRewards: [], }); setOpen(false); }; const processAIGeneratedItems = (items: any[]) => { return items.map((item, index) => { // First, check if the AI provided an ID for a consumable if (item.type === "Consumable" && item.id) { // Check if the ID matches any predefined consumable const predefinedById = predefinedConsumables.find( (p) => p.id === item.id ); if (predefinedById) { // Use the predefined consumable properties with matching ID return { id: predefinedById.id, name: predefinedById.name, type: predefinedById.type, description: predefinedById.description, rarity: predefinedById.rarity, }; } } // If no ID match or ID not provided, fall back to name matching for consumables if (item.type === "Consumable") { // Try to find a matching predefined consumable by name (case-insensitive) const predefined = predefinedConsumables.find( (p) => p.name.toLowerCase() === item.name.toLowerCase() ); if (predefined) { // Use the predefined consumable properties return { id: predefined.id, name: predefined.name, type: predefined.type, description: predefined.description, rarity: predefined.rarity, }; } // Also try partial name matching for potions const potionMatch = predefinedConsumables.find( (p) => (item.name.toLowerCase().includes("health") && p.name.toLowerCase().includes("health")) || (item.name.toLowerCase().includes("mana") && p.name.toLowerCase().includes("mana")) || (item.name.toLowerCase().includes("focus") && p.name.toLowerCase().includes("focus")) ); if (potionMatch) { return { id: potionMatch.id, name: potionMatch.name, type: potionMatch.type, description: potionMatch.description, rarity: potionMatch.rarity, }; } } // For non-consumables or consumables that don't match predefined ones return { id: `custom-item-${Date.now()}-${index}`, name: item.name, type: item.type, description: item.description || "", rarity: "Common", }; }); }; const handleGenerateWithAI = async () => { // Check if description is empty if (!formData.description.trim()) { toast({ title: "Description Required", description: "Please enter a quest description before using AI generation.", variant: "destructive", }); return; } // Check if API key exists if (!hasAIKey) { setApiKeyModalOpen(true); return; } // Generate quest data with AI setIsGeneratingWithAI(true); try { const questData = await generateQuestData(formData.description); const provider = getAIProvider() || "AI"; // Update form data with AI-generated data, but keep the current expiry setFormData((prev) => ({ ...prev, title: questData.title || prev.title, description: questData.description || prev.description, difficulty: questData.difficulty || prev.difficulty, priority: questData.priority || prev.priority, // Don't update expiry from AI expReward: questData.expReward || prev.expReward, statPointsReward: questData.statPointsReward || prev.statPointsReward, goldReward: questData.goldReward || prev.goldReward, strReward: questData.statRewards?.str || 0, agiReward: questData.statRewards?.agi || 0, perReward: questData.statRewards?.per || 0, intReward: questData.statRewards?.int || 0, vitReward: questData.statRewards?.vit || 0, itemRewards: processAIGeneratedItems(questData.itemRewards || []), })); toast({ title: `${provider} Generation Complete`, description: "Quest details have been generated. Feel free to make any adjustments.", }); } catch (error) { toast({ title: "AI Generation Failed", description: error instanceof Error ? error.message : "An unknown error occurred", variant: "destructive", }); } finally { setIsGeneratingWithAI(false); } }; const handleAPIKeySubmit = (provider: string) => { setHasAIKey(true); // Automatically trigger AI generation after API key is submitted setTimeout(() => { handleGenerateWithAI(); }, 500); }; return ( <> Create New Quest Add a new quest to track your real-life progress