import { type Interaction, ButtonInteraction, ModalSubmitInteraction, StringSelectMenuInteraction, ThreadChannel, TextChannel, EmbedBuilder } from "discord.js"; import { tradeService } from "@shared/modules/trade/trade.service"; import { inventoryService } from "@shared/modules/inventory/inventory.service"; import { createErrorEmbed, createWarningEmbed, createSuccessEmbed, createInfoEmbed } from "@lib/embeds"; import { UserError } from "@lib/errors"; import { getTradeDashboard, getTradeMoneyModal, getItemSelectMenu, getTradeCompletedEmbed } from "./trade.view"; export async function handleTradeInteraction(interaction: Interaction) { if (!interaction.isButton() && !interaction.isStringSelectMenu() && !interaction.isModalSubmit()) return; const { customId } = interaction; const threadId = interaction.channelId; if (!threadId) return; if (customId === 'trade_cancel') { await handleCancel(interaction, threadId); } else if (customId === 'trade_lock') { await handleLock(interaction, threadId); } else if (customId === 'trade_confirm') { // Confirm logic is handled implicitly by both locking or explicitly if needed. // For now, locking both triggers execution, so no separate confirm handler is actively used // unless we re-introduce a specific button. keeping basic handler stub if needed. } else if (customId === 'trade_add_money') { await handleAddMoneyClick(interaction); } else if (customId === 'trade_money_modal') { await handleMoneySubmit(interaction as ModalSubmitInteraction, threadId); } else if (customId === 'trade_add_item') { await handleAddItemClick(interaction as ButtonInteraction, threadId); } else if (customId === 'trade_select_item') { await handleItemSelect(interaction as StringSelectMenuInteraction, threadId); } else if (customId === 'trade_remove_item') { await handleRemoveItemClick(interaction as ButtonInteraction, threadId); } else if (customId === 'trade_remove_item_select') { await handleRemoveItemSelect(interaction as StringSelectMenuInteraction, threadId); } } async function handleCancel(interaction: ButtonInteraction | StringSelectMenuInteraction | ModalSubmitInteraction, threadId: string) { const session = tradeService.getSession(threadId); const user = interaction.user; tradeService.endSession(threadId); await interaction.deferUpdate(); if (interaction.channel?.isThread()) { const embed = createInfoEmbed(`Trade cancelled by ${user.username}.`, "Trade Cancelled"); await scheduleThreadCleanup(interaction.channel, "This thread will be deleted in 5 seconds.", 5000, embed); } } async function handleLock(interaction: ButtonInteraction | StringSelectMenuInteraction | ModalSubmitInteraction, threadId: string) { await interaction.deferUpdate(); const isLocked = tradeService.toggleLock(threadId, interaction.user.id); await updateTradeDashboard(interaction, threadId); // Check if trade executed (both locked) const session = tradeService.getSession(threadId); if (session && session.state === 'COMPLETED') { // Trade executed during updateTradeDashboard return; } await interaction.followUp({ content: isLocked ? "🔒 You locked your offer." : "� You unlocked your offer.", ephemeral: true }); } async function handleAddMoneyClick(interaction: Interaction) { if (!interaction.isButton()) return; const modal = getTradeMoneyModal(); await interaction.showModal(modal); } async function handleMoneySubmit(interaction: ModalSubmitInteraction, threadId: string) { const amountStr = interaction.fields.getTextInputValue('amount'); const amount = BigInt(amountStr); if (amount < 0n) throw new UserError("Amount must be positive"); tradeService.updateMoney(threadId, interaction.user.id, amount); await interaction.deferUpdate(); // Acknowledge modal await updateTradeDashboard(interaction, threadId); } async function handleAddItemClick(interaction: ButtonInteraction, threadId: string) { const inventory = await inventoryService.getInventory(interaction.user.id); if (inventory.length === 0) { await interaction.reply({ embeds: [createWarningEmbed("Your inventory is empty.")], ephemeral: true }); return; } // Slice top 25 for select menu const options = inventory.slice(0, 25).map((entry: any) => ({ label: `${entry.item.name} (${entry.quantity})`, value: entry.item.id.toString(), description: `Rarity: ${entry.item.rarity} ` })); const { components } = getItemSelectMenu(options, 'trade_select_item', 'Select an item to add'); await interaction.reply({ content: "Select an item to add:", components, ephemeral: true }); } async function handleItemSelect(interaction: StringSelectMenuInteraction, threadId: string) { const value = interaction.values[0]; if (!value) return; const itemId = parseInt(value); // Assuming implementation implies adding 1 item for now const item = await inventoryService.getItem(itemId); if (!item) throw new UserError("Item not found"); tradeService.addItem(threadId, interaction.user.id, { id: item.id, name: item.name }, 1n); await interaction.update({ content: `Added ${item.name} x1`, components: [] }); await updateTradeDashboard(interaction, threadId); } async function handleRemoveItemClick(interaction: ButtonInteraction, threadId: string) { const session = tradeService.getSession(threadId); if (!session) return; const participant = session.userA.id === interaction.user.id ? session.userA : session.userB; if (participant.offer.items.length === 0) { await interaction.reply({ embeds: [createWarningEmbed("No items in offer to remove.")], ephemeral: true }); return; } const options = participant.offer.items.slice(0, 25).map(i => ({ label: `${i.name} (${i.quantity})`, value: i.id.toString(), })); const { components } = getItemSelectMenu(options, 'trade_remove_item_select', 'Select an item to remove'); await interaction.reply({ content: "Select an item to remove:", components, ephemeral: true }); } async function handleRemoveItemSelect(interaction: StringSelectMenuInteraction, threadId: string) { const value = interaction.values[0]; if (!value) return; const itemId = parseInt(value); tradeService.removeItem(threadId, interaction.user.id, itemId); await interaction.update({ content: `Removed item.`, components: [] }); await updateTradeDashboard(interaction, threadId); } // --- DASHBOARD UPDATER --- export async function updateTradeDashboard(interaction: Interaction, threadId: string) { const session = tradeService.getSession(threadId); if (!session) return; // Check Auto-Execute (If both locked) if (session.userA.locked && session.userB.locked) { // Execute Trade try { await tradeService.executeTrade(threadId); const embed = getTradeCompletedEmbed(session); await updateDashboardMessage(interaction, { embeds: [embed], components: [] }); // Notify and Schedule Cleanup if (interaction.channel?.isThread()) { const successEmbed = createSuccessEmbed("Trade executed successfully. Items and funds have been transferred.", "Trade Complete"); await scheduleThreadCleanup( interaction.channel, `🎉 Trade successful! < @${session.userA.id}> <@${session.userB.id}>\nThis thread will be deleted in 10 seconds.`, 10000, successEmbed ); } return; } catch (e: any) { console.error("Trade Execution Error:", e); const errorEmbed = createErrorEmbed(e.message, "Trade Failed"); if (interaction.channel?.isThread()) { await scheduleThreadCleanup( interaction.channel, "❌ Trade failed due to an error. This thread will be deleted in 10 seconds.", 10000, errorEmbed ); } return; } } // Build Status Embed const { embeds, components } = getTradeDashboard(session); await updateDashboardMessage(interaction, { embeds, components }); } async function updateDashboardMessage(interaction: Interaction, payload: any) { if (interaction.isButton() && interaction.message) { // If interaction came from the dashboard itself, we can edit directly try { await interaction.message.edit(payload); } catch (e) { console.error("Failed to edit message directly", e); } } else { // Find dashboard in channel const channel = interaction.channel as ThreadChannel; if (channel && channel.isThread()) { try { const messages = await channel.messages.fetch({ limit: 10 }); const dashboardFn = messages.find(m => m.embeds[0]?.title === "🤝 Trading Session"); if (dashboardFn) { await dashboardFn.edit(payload); } } catch (e) { console.error("Failed to fetch/edit dashboard", e); } } } } async function scheduleThreadCleanup(channel: ThreadChannel | TextChannel, message: string, delayMs: number = 10000, embed?: EmbedBuilder) { try { const payload: any = { content: message }; if (embed) payload.embeds = [embed]; await channel.send(payload); setTimeout(async () => { try { if (channel.isThread()) { console.log(`Deleting thread: ${channel.id} `); await channel.delete("Trade Session Ended"); } } catch (e) { console.error("Failed to delete thread", e); } }, delayMs); } catch (e) { console.error("Failed to send cleanup notification", e); } }