import { useState } from 'react';
import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/button';
import { cn } from '@/utils/cn';
import { useAIAnalysis } from '@/hooks/useAIAnalysis';
import { X } from 'lucide-react';
/**
* Trade Analysis Modal
* Full-screen detailed analysis for evaluating individual trades
*/
export function TradeAnalysisModal({ row, isOpen, onClose }) {
const [showAIAnalysis, setShowAIAnalysis] = useState(false);
const { loading: aiLoading, error: aiError, analysis: aiAnalysis, fetchAnalysis } = useAIAnalysis();
if (!isOpen || !row) return null;
const signal = row.tradeSignal;
const symbol = row.Symbol || row.symbol_norm;
const score = row.rocketScore || row.rocket_score || 0;
const momentum = row.momentumScore || 0;
const netPremium = (row.bull_total || 0) - (row.bear_total || 0);
const bullTotal = row.bull_total || 0;
const bearTotal = row.bear_total || 0;
const premium = row.premium_num || 0;
const volume = row.Volume || row.vol_num || 0;
const oi = row.OI || row.oi_num || 0;
const currentPrice = parseFloat(row.u_close || row.spot_num || row.Price || 0) || 0;
// Ensure strike is always a number - handle string values like "150.00" or "$150"
const strikeValue = row.Strike || 0;
const strike = typeof strikeValue === 'string'
? parseFloat(strikeValue.replace(/[^0-9.-]/g, '')) || 0
: parseFloat(strikeValue) || 0;
const callPut = row.CallPut || row.cp_norm || '';
const expiration = row.ExpirationDate;
const moneyness = row.moneynessContext || {};
const isGamma = row.isGammaSqueezeSetup;
const tapeAlign = row.TapeAlign || row.tapeArrow || '—';
const trend = row.flowTrendRaw || row.flowTrend?.full;
const decay = row.flowDecayRaw || row.flowDecay?.full;
const catalyst = row.near_alert_type || 'None';
const badges = row.badges || row.badgesRaw || {};
// Extract prices
const extractPrice = (value) => {
if (!value) return null;
if (typeof value === 'string') {
const match = value.match(/\$?([\d.]+)/);
return match ? parseFloat(match[1]) : null;
}
if (typeof value === 'object') {
const primary = value.primary || value.aggressive || value.tight || value.wide || value.t1;
if (typeof primary === 'string') {
const match = primary.match(/\$?([\d.]+)/);
return match ? parseFloat(match[1]) : null;
}
return primary;
}
return null;
};
const entryPrice = signal?.entry ? extractPrice(signal.entry) : currentPrice;
const stopPrice = signal?.stop ? extractPrice(signal.stop) : null;
const target1Price = signal?.target?.t1 ? extractPrice(signal.target.t1) : null;
const target2Price = signal?.target?.t2 ? extractPrice(signal.target.t2) : null;
const target3Price = signal?.target?.t3 ? extractPrice(signal.target.t3) : null;
// Calculate R:R
const risk = entryPrice && stopPrice ? Math.abs(entryPrice - stopPrice) : null;
const reward1 = entryPrice && target1Price ? Math.abs(target1Price - entryPrice) : null;
const riskReward1 = risk && reward1 ? (reward1 / risk).toFixed(2) : null;
// Calculate win probability estimate
const winProb = signal?.confidence || 50;
// Get flow trend status
const getFlowTrend = (lastFlowMinutes) => {
if (lastFlowMinutes === null || lastFlowMinutes === undefined) {
return null;
}
if (lastFlowMinutes <= 5) {
return {
icon: '🔥',
label: 'SURGING',
color: 'red',
message: 'Institutions actively buying RIGHT NOW'
};
} else if (lastFlowMinutes <= 30) {
return {
icon: '🟡',
label: 'STABLE',
color: 'yellow',
message: 'Recent flow - institutions still positioned'
};
} else if (lastFlowMinutes <= 60) {
return {
icon: '⚠️',
label: 'FADING',
color: 'orange',
message: 'Flow slowing down - watch for reversal'
};
} else {
return {
icon: '💀',
label: 'DEAD',
color: 'red',
message: 'Flow too stale - institutions likely exited'
};
}
};
const lastFlowMinutesAgo = trend?.lastFlowMinutesAgo ?? decay?.lastFlowMinutesAgo;
const flowTrend = getFlowTrend(lastFlowMinutesAgo);
const hoursAgo = lastFlowMinutesAgo ? Math.floor(lastFlowMinutesAgo / 60) : 0;
return (
{/* Header */}
{symbol}
{callPut} ${typeof strike === 'number' ? strike.toFixed(2) : 'N/A'}
{isGamma && (
⚡ GAMMA SQUEEZE
)}
{signal?.type && (
{signal.type.replace(/_/g, ' ')}
)}
{/* Content - Scrollable */}
{/* Quick Stats Bar */}
🚀 Score
{score.toFixed(1)}
💰 Premium
${(premium / 1000000).toFixed(2)}M
{/* Trade Signal Card */}
{signal && signal.signal !== 'NEUTRAL' && signal.signal !== 'WAIT' && (
🎯 TRADE PLAN
{signal.signal === 'BUY' ? '🚀 BUY' : '🔴 SHORT'}
{entryPrice && typeof entryPrice === 'number' && (
Entry
${entryPrice.toFixed(2)}
{signal.entry?.notes && (
{signal.entry.notes}
)}
)}
{stopPrice && typeof stopPrice === 'number' && (
Stop Loss
${stopPrice.toFixed(2)}
{risk && typeof risk === 'number' && entryPrice && typeof entryPrice === 'number' && (
Risk: ${risk.toFixed(2)} ({((risk / entryPrice) * 100).toFixed(1)}%)
)}
)}
{target1Price && typeof target1Price === 'number' && (
Target 1
${target1Price.toFixed(2)}
{riskReward1 && `R:R ${riskReward1}:1`} • Take 50%
)}
{target2Price && typeof target2Price === 'number' && (
Target 2
${target2Price.toFixed(2)}
Take 30%
)}
{target3Price && typeof target3Price === 'number' && (
Target 3
${target3Price.toFixed(2)}
Runner 20%
)}
{signal.reasoning && (
Reasoning:
{signal.reasoning}
)}
{signal.invalidation && (
⚠️ Invalidation Rules:
{Array.isArray(signal.invalidation) ? (
{signal.invalidation.map((rule, idx) => (
- {rule}
))}
) : (
{signal.invalidation}
)}
)}
)}
{/* Flow Details */}
🔹 FLOW DETAILS
{/* Flow Trend Visual */}
{flowTrend && (
{flowTrend.icon}
{flowTrend.label} FLOW
Last activity:
{lastFlowMinutesAgo} minutes ago {hoursAgo > 0 && `(${hoursAgo} ${hoursAgo === 1 ? 'hour' : 'hours'})`}
⚠️ {flowTrend.message}
)}
Net Premium:
0 ? "text-green-400" : "text-red-400"
)}>
${(Math.abs(netPremium) / 1000000).toFixed(2)}M
Bull Premium:
${(bullTotal / 1000000).toFixed(2)}M
Bear Premium:
${(bearTotal / 1000000).toFixed(2)}M
Volume:
{volume.toLocaleString()}
Open Interest:
{oi.toLocaleString()}
Moneyness:
{row.moneynessLabel || `${moneyness.distancePct || 0}% ${moneyness.category || ''}`}
📊 PRICE CONTEXT
Current Price:
${typeof currentPrice === 'number' ? currentPrice.toFixed(2) : 'N/A'}
Strike:
${typeof strike === 'number' ? strike.toFixed(2) : 'N/A'}
Distance:
0 ? "text-red-400" : "text-green-400"
)}>
{moneyness.distancePct ? `${moneyness.distancePct > 0 ? '+' : ''}${moneyness.distancePct.toFixed(1)}%` : 'N/A'}
vs RTH Open:
0 ? "text-green-400" : "text-red-400"
)}>
{(row.PctVsRthOpen || 0) > 0 ? '+' : ''}{(row.PctVsRthOpen || 0).toFixed(2)}%
Flow Trend:
{trend?.status || 'STABLE'}
{/* Tape Alignment Visual */}
{tapeAlign !== '—' ? (
✅
TAPE ALIGNED
Price confirming flow direction ↑
) : (
⚠️
NO TAPE ALIGNMENT
Price not confirming flow direction
Current Price
${typeof currentPrice === 'number' ? currentPrice.toFixed(2) : 'N/A'}
Expected: Upward momentum
Reality: Flat/down
)}
{/* Badges & Catalyst */}
🏆 BADGES
{badges.round && (
{badges.round}
)}
{badges.more && badges.more.split(' ').map((badge, idx) => (
{badge}
))}
{badges.flash && (
{badges.flash}
)}
⚡ CATALYST
{catalyst !== 'None' ? (
{catalyst}
{trend?.lastFlowMinutesAgo !== null && (
Last flow: {trend.lastFlowMinutesAgo}min ago
)}
) : (
No alerts detected
)}
{/* AI Analysis */}
🤖 AI ANALYSIS
{!showAIAnalysis && (
)}
{showAIAnalysis && (
{aiLoading && (
Analyzing with Claude AI...
)}
{aiError && (
Error: {aiError}
)}
{aiAnalysis && aiAnalysis.analysis && (
Summary: {aiAnalysis.analysis.summary}
{/* Long Term */}
{aiAnalysis.analysis.longTerm && (
Long-Term Investor
{aiAnalysis.analysis.longTerm.assessment}
{aiAnalysis.analysis.longTerm.recommendation}
{aiAnalysis.analysis.longTerm.moatAndFundamentals}
)}
{/* Swing Trade */}
{aiAnalysis.analysis.swing && (
Swing Trader
{aiAnalysis.analysis.swing.assessment}
{aiAnalysis.analysis.swing.recommendation}
{aiAnalysis.analysis.swing.technicalSetup}
)}
{/* Day Trade */}
{aiAnalysis.analysis.dayTrade && (
Day Trader
{aiAnalysis.analysis.dayTrade.assessment}
{aiAnalysis.analysis.dayTrade.recommendation}
{aiAnalysis.analysis.dayTrade.volatilityAndLiquidity}
)}
)}
)}
{/* Footer Actions */}
Time Horizon: {signal?.timeHorizon || 'INTRADAY'} •
Expiration: {expiration ? new Date(expiration).toLocaleDateString() : 'N/A'}
);
}