import { useEffect, useMemo, useState } from 'react' import { createPortal } from 'react-dom' import { Sparkles, X } from 'lucide-react' import { useLocale } from '@meshkee/dashboard-ui' import { ApiError } from '../lib/api' import { useT } from '../i18n/useT' import { updateStoreItem } from '../services/storeItemService' import { matchStoreSpecialListWithAi, updateStoreSpecial, type SpecialItemsAiMatchRow, } from '../services/storeSpecialService' import type { SpecialProductsSource } from '../services/settingsService' import type { StoreSpecial } from '../types/storeSpecial' import { formatIrtPrice } from '../utils/irtPrice' import modalStyles from './VariationsModal.module.css' import aiStyles from '../styles/ai.module.css' import styles from './SpecialItemsAiModal.module.css' type Step = 'input' | 'review' const ANIMATION_MS = 220 export interface SpecialItemsAiApplyResult { special: StoreSpecial added: number priced: number cleared: boolean } interface SpecialItemsAiModalProps { open: boolean special: StoreSpecial | null source: SpecialProductsSource onClose: () => void onApplied: (result: SpecialItemsAiApplyResult) => void } export function SpecialItemsAiModal({ open, special, source, onClose, onApplied, }: SpecialItemsAiModalProps) { const t = useT() const { locale } = useLocale() const isFa = locale === 'fa' const fromProducts = source === 'product' const [mounted, setMounted] = useState(open) const [closing, setClosing] = useState(false) const [step, setStep] = useState('input') const [listText, setListText] = useState('') const [replaceExisting, setReplaceExisting] = useState(false) const [rows, setRows] = useState([]) const [error, setError] = useState('') const [busy, setBusy] = useState(false) useEffect(() => { if (open) { setMounted(true) setClosing(false) setStep('input') setListText('') setReplaceExisting(false) setRows([]) setError('') setBusy(false) } else if (mounted) { setClosing(true) const timer = setTimeout(() => { setMounted(false) setClosing(false) }, ANIMATION_MS) return () => clearTimeout(timer) } }, [open, mounted]) useEffect(() => { if (!mounted || closing) return const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape' && !busy) onClose() } document.addEventListener('keydown', onKey) return () => document.removeEventListener('keydown', onKey) }, [mounted, closing, busy, onClose]) const selectedCount = useMemo( () => rows.filter((row) => row.selected && row.candidate).length, [rows], ) async function handleMatch() { if (!special) return if (listText.trim().length < 3) { setError(t('website.specialItems.ai.listEmpty')) return } setBusy(true) setError('') try { const result = await matchStoreSpecialListWithAi(listText) setRows(result.matches) setStep('review') } catch (err) { if (err instanceof ApiError) { setError(err.message) } else { setError(t('website.specialItems.ai.errorMatch')) } } finally { setBusy(false) } } async function handleApply() { if (!special) return const accepted = rows.filter((row) => row.selected && row.candidate) if (accepted.length === 0) { setError(t('website.specialItems.ai.noneSelected')) return } setBusy(true) setError('') try { let priced = 0 if (!fromProducts) { for (const row of accepted) { if (!row.updatePrice || row.price == null || !row.candidate) continue await updateStoreItem(row.candidate.variantId, { price: row.price }) priced += 1 } } const existingIds = special.items.map((item) => fromProducts ? item.productId : item.id, ) const matchedIds = accepted.map((row) => fromProducts ? row.candidate!.productId : row.candidate!.storeItemId, ) const nextIds = replaceExisting ? [...new Set(matchedIds)] : [...new Set([...existingIds, ...matchedIds])] const result = await updateStoreSpecial( special.id, fromProducts ? { productIds: nextIds } : { storeItemIds: nextIds }, ) onApplied({ special: result.special, added: matchedIds.length, priced, cleared: replaceExisting, }) onClose() } catch (err) { if (err instanceof ApiError) { setError(err.message) } else { setError(t('website.specialItems.ai.errorApply')) } } finally { setBusy(false) } } function toggleRow(index: number) { setRows((prev) => prev.map((row, i) => i === index && row.candidate ? { ...row, selected: !row.selected } : row, ), ) } function togglePrice(index: number) { setRows((prev) => prev.map((row, i) => (i === index ? { ...row, updatePrice: !row.updatePrice } : row)), ) } if (!mounted || !special) return null return createPortal(
e.target === e.currentTarget && !busy && onClose()} role="presentation" >
e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} >

{t('website.specialItems.ai.title')}

{t('website.specialItems.ai.subtitle', { title: special.title })}

{error &&

{error}

} {step === 'input' ? ( <>