import React, { useState, useEffect } from 'react'; import { Info, ClipboardIcon, Check, Star, Plus, Trash2, Layout, Clock, ChevronRight, HelpCircle, Terminal, RefreshCcw, User, Loader2, Sparkles } from 'lucide-react'; import { VisionScanner } from '../services/visionScanner'; import { AstroData, Profile } from '../types'; import { parseJHoraGrid, getPlacements, normalizeVargaType } from '../services/asciiParser'; import { generateCombinedTransitTable } from '../services/transitLogic'; import { INITIAL_DATA, VARGA_MEANINGS, VARGA_DUMMY_DATA } from '../constants'; import VedicDateTimePicker from './VedicDateTimePicker'; import { useAstroContext } from '../context/AstroContext'; import { supabase } from '../lib/supabase'; import { fetchProfileData, updateProfileData } from '../services/importService'; interface Props { onComplete: (data: AstroData) => void; initialData?: AstroData; } interface HelpTooltipProps { content: string; position?: 'top' | 'left' | 'right' | 'top-right'; } function HelpTooltip({ content, position = 'top' }: HelpTooltipProps) { const posClasses = { top: 'bottom-full left-1/2 -translate-x-1/2 mb-3', 'top-right': 'bottom-full right-0 mb-3', left: 'right-full top-1/2 -translate-y-1/2 mr-3', right: 'left-full top-1/2 -translate-y-1/2 ml-3' }; return (
{content}
); } function CopyButton({ content, label = "Copy Section" }: { content: string; label?: string }) { const [copied, setCopied] = useState(false); const handleCopy = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); navigator.clipboard.writeText(content.trim()); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return ( ); } export default function Step0Input({ onComplete, initialData }: Props) { const { state, setProfiles, setActiveProfileId } = useAstroContext(); const [data, setData] = useState(initialData || INITIAL_DATA); const [rasiPreview, setRasiPreview] = useState(''); const [copied, setCopied] = useState(false); const [showTransitNatalSelect, setShowTransitNatalSelect] = useState(false); const [isLoadingProfile, setIsLoadingProfile] = useState(false); const [savImage, setSavImage] = useState(null); const [isOcrLoading, setIsOcrLoading] = useState(false); const [originalData, setOriginalData] = useState(null); const [isSyncing, setIsSyncing] = useState(false); const [syncSuccess, setSyncSuccess] = useState(false); const [showBatchImporter, setShowBatchImporter] = useState(false); const [batchImportText, setBatchImportText] = useState(''); const VARGA_LIST = ['D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9', 'D10', 'D11', 'D12', 'D16', 'D20', 'D24', 'D27', 'D30', 'D40', 'D45', 'D60', 'D81', 'D108', 'D144']; // Fetch profiles on mount useEffect(() => { const getProfiles = async () => { const { data: pList } = await supabase.from('profiles').select('*').order('name'); if (pList) setProfiles(pList); }; getProfiles(); }, []); const handleProfileChange = async (id: string) => { if (!id) { setActiveProfileId(null); return; } setIsLoadingProfile(true); try { const profileData = await fetchProfileData(id); if (profileData) { setData(profileData); setOriginalData(JSON.parse(JSON.stringify(profileData))); // Deep clone for comparison setActiveProfileId(id); } } catch (err) { console.error('Failed to load profile:', err); alert('Error loading profile from database.'); } finally { setIsLoadingProfile(false); } }; // Update preview when rasiChartText changes useEffect(() => { if (data.rasiChartText.trim()) { const summary = parseJHoraGrid(data.rasiChartText); setRasiPreview(summary); } else { setRasiPreview(''); } }, [data.rasiChartText]); // Migration/Initialization: Ensure D1 has scores if legacy ashtakavarga exists useEffect(() => { if (data.ashtakavarga && Object.keys(data.ashtakavarga).length > 0) { const d1 = data.vargaAshtakavarga?.['D1']; if (!d1 || Object.keys(d1).length === 0) { setData(prev => ({ ...prev, vargaAshtakavarga: { ...prev.vargaAshtakavarga, 'D1': prev.ashtakavarga || {} } })); } } }, [data.ashtakavarga]); const updateDivisionalChart = (key: string, value: string) => { setData(prev => ({ ...prev, divisionalCharts: { ...prev.divisionalCharts, [key]: value } })); }; const removeDivisionalChart = (name: string) => { const newCharts = { ...data.divisionalCharts }; delete newCharts[name]; setData(prev => ({ ...prev, divisionalCharts: newCharts })); }; const zodOrder = ["ARIES", "TAURUS", "GEMINI", "CANCER", "LEO", "VIRGO", "LIBRA", "SCORPIO", "SAGITTARIUS", "CAPRICORN", "AQUARIUS", "PISCES"]; const lagnaSign = React.useMemo(() => { let signName = "ARIES"; // 1. Get D1 Lagna as a secondary fallback let d1Lagna = "ARIES"; if (data.rasiChartText) { const d1Result = getPlacements(data.rasiChartText); if (typeof d1Result !== 'string') { for (const [s, planets] of Object.entries(d1Result.placements)) { if (planets.some(p => p === "As" || p.startsWith("As(") || p.startsWith("As "))) { d1Lagna = s; break; } } } } // 2. Identify the target varga chart const currentRef = normalizeVargaType(data.savChartType || 'D1'); // Aggressive lookup: normalize ALL keys to find a match let vargaText = currentRef === 'D1' ? data.rasiChartText : undefined; if (!vargaText && data.divisionalCharts) { const entry = Object.entries(data.divisionalCharts).find(([k]) => normalizeVargaType(k) === currentRef); if (entry) { vargaText = entry[1]; } else { // Fallback to exact key if normalization failed vargaText = data.divisionalCharts[data.savChartType]; } } if (vargaText) { const result = getPlacements(vargaText); if (typeof result !== 'string') { const { placements } = result; let found = false; for (const [s, planets] of Object.entries(placements)) { // Robust check for Lagna token if (planets.some(p => p === "As" || p.toLowerCase().includes("lagna") || p.startsWith("As(") || p.startsWith("As "))) { signName = s; found = true; break; } } if (!found) signName = d1Lagna; } else { signName = d1Lagna; } } else { signName = d1Lagna; } console.log(`[SAV DEBUG] Chart: ${data.savChartType} (${currentRef}) -> Lagna: ${signName}`); return signName; }, [data.savChartType, data.rasiChartText, data.divisionalCharts]); // 3. Fallback SAV data if database columns are missing or empty const effectiveAshtakavarga = React.useMemo(() => { if (Object.keys(data.ashtakavarga || {}).length > 0) return data.ashtakavarga; // If empty, attempt to parse initialData or current context const currentRef = normalizeVargaType(data.savChartType || 'D1'); const chartText = currentRef === 'D1' ? data.rasiChartText : data.divisionalCharts[currentRef]; if (chartText) { // Future: add parseSAVFromText(chartText) here if needed. // For now, we rely on the user to 'Sync' or 'Refresh' until DB is fixed. } return data.ashtakavarga || {}; }, [data.ashtakavarga, data.savChartType, data.divisionalCharts, data.rasiChartText]); const lagnaIdx = zodOrder.indexOf(lagnaSign); const addDivisionalChart = (name: string) => { const uppercaseName = name.toUpperCase(); if (uppercaseName in data.divisionalCharts) { removeDivisionalChart(uppercaseName); } else { updateDivisionalChart(uppercaseName, VARGA_DUMMY_DATA[uppercaseName] || ''); } }; const parseBatchImport = (text: string) => { if (!text.trim()) return; try { const newData = { ...data }; const newDivisionalCharts = { ...data.divisionalCharts }; // Parse JHora ASCII charts directly based on the table borders const lines = text.split('\n'); let currentChart: string[] = []; let inChart = false; lines.forEach(line => { const trimmed = line.trim(); // Detect the top or bottom border of a JHora chart grid (e.g., +---------+) if (trimmed.match(/^\+[-]{30,}\+$/)) { if (!inChart) { inChart = true; currentChart = [line]; } else { currentChart.push(line); const chartText = currentChart.join('\n'); // Identify which chart this is by the text embedded in its center cells if (chartText.includes('Rasi') && !chartText.includes('D-')) { newData.rasiChartText = chartText; } else { const match = chartText.match(/D-(\d+)/); if (match) { const vKey = `D${match[1]}`; if (VARGA_LIST.includes(vKey)) { newDivisionalCharts[vKey] = chartText; } } } // Reset for the next chart currentChart = []; inChart = false; } } else if (inChart) { currentChart.push(line); } }); newData.divisionalCharts = newDivisionalCharts; setData(newData); setShowBatchImporter(false); setBatchImportText(''); } catch (err) { console.error('Batch Import Error:', err); alert('There was an error parsing the imported content. Please ensure it follows the format of JHora ASCII grids.'); } }; const handleToggleAllVargas = () => { const allVargasIn = VARGA_LIST.every(v => v in data.divisionalCharts); if (allVargasIn) { const coreCharts: Record = {}; if ('D9' in data.divisionalCharts) coreCharts['D9'] = data.divisionalCharts['D9']; if ('D10' in data.divisionalCharts) coreCharts['D10'] = data.divisionalCharts['D10']; setData(prev => ({ ...prev, divisionalCharts: coreCharts })); } else { const fullSet: Record = { ...data.divisionalCharts }; VARGA_LIST.forEach(v => { if (!(v in fullSet)) { fullSet[v] = VARGA_DUMMY_DATA[v] || ''; } }); setData(prev => ({ ...prev, divisionalCharts: fullSet })); } }; const handleCopyTransitAnalysis = () => { if (!data.transitChartText || !data.rasiChartText) return; const natalChart = (data.transitNatalSelection || 'D1') === 'D1' ? data.rasiChartText : data.divisionalCharts[data.transitNatalSelection as string] || ''; if (!natalChart) return; const result = generateCombinedTransitTable( natalChart, data.transitChartText, { natalName: data.transitNatalSelection || 'D1', transitDateTime: data.transitDateTime || '' } ); navigator.clipboard.writeText(result); }; const handleClear = () => { if (confirm('Are you sure you want to clear all data?')) { setData({ ...INITIAL_DATA, userName: '', dob: '', birthTime: '', longitudeTable: '', rasiChartText: '', dasha: '', additionalInfo: '', divisionalCharts: { 'D9': '', 'D10': '' }, isBirthTimeEstimate: false }); setRasiPreview(''); } }; const handleLoadDefault = () => { setData(INITIAL_DATA); }; const handlePasteImage = (e: React.ClipboardEvent) => { const items = e.clipboardData.items; for (let i = 0; i < items.length; i++) { if (items[i].type.indexOf('image') !== -1) { const blob = items[i].getAsFile(); if (blob) { const reader = new FileReader(); reader.onload = (event) => { setSavImage(event.target?.result as string); }; reader.readAsDataURL(blob); } } } }; const buildContextString = () => { const cleanJHoraSpaces = (text: string) => { if (!text) return ''; return text .split('\n') .map(line => line.trim().replace(/\s\s+/g, ' ')) .join('\n') .trim(); }; const getCompactTable = (name: string, ascii: string) => { if (!ascii.trim()) return ''; const summary = parseJHoraGrid(ascii); return `### ${name}\n${summary}`; }; const d1Table = getCompactTable('Rasi (D1)', data.rasiChartText); const vargas = Object.entries(data.divisionalCharts) .filter(([_, value]) => (value as string).trim().length > 0) .map(([key, value]) => getCompactTable(key, value as string)) .filter(t => t.length > 0) .join('\n\n'); let transitTable = ''; if (data.transitChartText && data.rasiChartText) { const natalChart = (data.transitNatalSelection || 'D1') === 'D1' ? data.rasiChartText : data.divisionalCharts[data.transitNatalSelection as string] || ''; if (natalChart) { transitTable = `## 🪐 Transit Synthesis\nReference: ${data.transitNatalSelection || 'D1'}, Time: ${data.transitDateTime || 'Now'}\n\n`; transitTable += generateCombinedTransitTable( natalChart, data.transitChartText, { natalName: data.transitNatalSelection || 'D1', transitDateTime: data.transitDateTime || '' } ); } } const getSAVContext = () => { const vAV = data.vargaAshtakavarga || {}; const vargaKeys = Object.keys(vAV).filter(k => Object.keys(vAV[k]).length > 0); if (vargaKeys.length === 0) return ''; let contextTables = `## 🕉️ Sarvastakavarga (SAV) Points\n\n`; vargaKeys.sort().forEach(varga => { contextTables += `### SAV - ${varga}\n\n| H | Sign | Score |\n| :--- | :--- | :--- |\n`; const scores = vAV[varga] || {}; const sortedData = zodOrder .map(sign => { const idx = zodOrder.indexOf(sign); const houseNum = ((idx - lagnaIdx + 12) % 12) + 1; return { houseNum, sign, score: scores[sign] || 0 }; }) .sort((a, b) => a.houseNum - b.houseNum); sortedData.forEach(item => { contextTables += `| H${item.houseNum < 10 ? '0' : ''}${item.houseNum} | ${item.sign} | ${item.score} |\n`; }); contextTables += '\n'; }); return contextTables; }; return ` Act as an expert Vedic Astrologer (@VedicGuru). --- TECHNICAL DATA SOURCE --- **User**: ${data.userName} **DOB**: ${data.dob} **Time**: ${data.birthTime} (Estimate: ${data.isBirthTimeEstimate ? 'Yes' : 'No'}) ## 📊 Longitude Table ${cleanJHoraSpaces(data.longitudeTable)} ${d1Table} ${vargas} ${getSAVContext()} ${transitTable ? transitTable + '\n\n' : ''}## ⏳ Dasha & Additional Info **Dasha**: ${cleanJHoraSpaces(data.dasha)} **Additional**: ${cleanJHoraSpaces(data.additionalInfo || 'N/A')} --- `.trim(); }; const generateMasterContext = () => { const context = buildContextString(); navigator.clipboard.writeText(context); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const hasChanges = React.useMemo(() => { if (!state.activeProfileId || !originalData) return false; // Compare essential fields const compareFields: (keyof AstroData)[] = [ 'userName', 'dob', 'birthTime', 'isBirthTimeEstimate', 'longitudeTable', 'rasiChartText', 'dasha', 'additionalInfo', 'divisionalCharts', 'ashtakavarga', 'vargaAshtakavarga', 'savChartType' ]; return compareFields.some(field => { const current = data[field]; const original = originalData[field]; return JSON.stringify(current) !== JSON.stringify(original); }); }, [data, originalData, state.activeProfileId]); const handleSyncProfile = async () => { if (!state.activeProfileId || !hasChanges || isSyncing) return; setIsSyncing(true); try { // 1. Sync to Supabase await updateProfileData(state.activeProfileId, data); // 2. Sync to local .md file in docs/ try { const timestamp = new Date().toLocaleString(); let context = buildContextString(); // Inject Updated Date context = context.replace( '--- TECHNICAL DATA SOURCE ---', `--- TECHNICAL DATA SOURCE ---\n**Updated Date**: ${timestamp}` ); await fetch('/api/save-local-md', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userName: data.userName, content: context }) }); } catch (localErr) { console.error('Failed to update local .md file:', localErr); } setOriginalData(JSON.parse(JSON.stringify(data))); setSyncSuccess(true); setTimeout(() => setSyncSuccess(false), 3000); } catch (err) { console.error('Failed to sync profile:', err); alert('Failed to sync data to Supabase.'); } finally { setIsSyncing(false); } }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onComplete(data); }; return (
{/* Profile Selector & Natal row */}

Step 1: Identity & Birth Data

{isLoadingProfile ? : }
setData(prev => ({ ...prev, userName: e.target.value }))} required />
setData(prev => ({ ...prev, dob: v }))} />
setData(prev => ({ ...prev, birthTime: v }))} />
setData(prev => ({ ...prev, isBirthTimeEstimate: !prev.isBirthTimeEstimate }))} > Est.
{/* Step 2: Longitude Table */}

Step 2: Planetary Longitudes

JHora Longitude Table (ASCII)