import { useEffect, useRef, useState, useCallback } from 'react' import X from 'lucide-react/dist/esm/icons/x' import { DEG_TO_RAD, isOperatorTail, tokenizeExpression, formatResult, evaluateExpression } from '../../utils/calculatorEngine' const Btn = ({ label, onClick, type = 'default', wide = false }) => ( ) export default function Calculator({ onClose }) { const dialogRef = useRef(null) const expressionRef = useRef('') const [display, setDisplay] = useState('0') const [expression, setExpression] = useState('') const [memory, setMemory] = useState(0) const [isDeg, setIsDeg] = useState(true) const [waitingForOperand, setWaitingForOperand] = useState(false) const getAngle = (val) => isDeg ? val * DEG_TO_RAD : val const setExpressionText = useCallback((next) => { const value = typeof next === 'function' ? next(expressionRef.current) : next expressionRef.current = value setExpression(value) }, []) useEffect(() => { const firstButton = dialogRef.current?.querySelector('button') firstButton?.focus() }, []) const inputDigit = (digit) => { if (waitingForOperand) { setDisplay(String(digit)) setWaitingForOperand(false) } else { setDisplay(display === '0' ? String(digit) : display + digit) } } const inputDecimal = () => { if (waitingForOperand) { setDisplay('0.'); setWaitingForOperand(false); return } if (!display.includes('.')) setDisplay(display + '.') } const handleOperator = (op) => { setExpressionText(prev => { const trimmed = prev.trim() if (!trimmed) return `${display} ${op}` if (isOperatorTail(trimmed)) return `${trimmed.slice(0, trimmed.lastIndexOf(' ')).trim()} ${op}` if (trimmed.endsWith(')')) return `${trimmed} ${op}` return `${trimmed} ${display} ${op}` }) setWaitingForOperand(true) } const inputOpenParen = () => { setExpressionText(prev => { const trimmed = prev.trim() if (!trimmed) return '(' if (isOperatorTail(trimmed) || trimmed.endsWith('(')) return `${trimmed} (` return `${trimmed} ${display} × (` }) setDisplay('0') setWaitingForOperand(true) } const inputCloseParen = () => { const tokens = tokenizeExpression(expressionRef.current) const openCount = tokens.filter(token => token === '(').length const closeCount = tokens.filter(token => token === ')').length if (openCount <= closeCount) return setExpressionText(prev => { const trimmed = prev.trim() if (!trimmed || trimmed.endsWith('(')) return trimmed if (trimmed.endsWith(')')) return `${trimmed} )` return `${trimmed} ${display} )` }) setWaitingForOperand(false) } const calculate = () => { if (!expressionRef.current) return try { const trimmed = expressionRef.current.trim() const finalExpression = trimmed.endsWith(')') ? trimmed : `${trimmed} ${display}` setDisplay(formatResult(evaluateExpression(finalExpression))) setExpressionText('') setWaitingForOperand(true) } catch { setDisplay('Error') } } const sciFunc = (fn) => { const val = parseFloat(display) let result try { switch (fn) { case 'sin': result = Math.sin(getAngle(val)); break case 'cos': result = Math.cos(getAngle(val)); break case 'tan': result = Math.tan(getAngle(val)); break case 'sin⁻¹': result = isDeg ? Math.asin(val) / DEG_TO_RAD : Math.asin(val); break case 'cos⁻¹': result = isDeg ? Math.acos(val) / DEG_TO_RAD : Math.acos(val); break case 'tan⁻¹': result = isDeg ? Math.atan(val) / DEG_TO_RAD : Math.atan(val); break case 'log': result = Math.log10(val); break case 'ln': result = Math.log(val); break case 'log₂': result = Math.log2(val); break case '√': result = Math.sqrt(val); break case 'x²': result = val * val; break case 'x³': result = val * val * val; break case '1/x': result = 1 / val; break case 'n!': result = factorial(val); break case 'eˣ': result = Math.exp(val); break case '10ˣ': result = Math.pow(10, val); break case '|x|': result = Math.abs(val); break case 'π': setDisplay(String(Math.PI)); return case 'e': setDisplay(String(Math.E)); return default: return } setDisplay(formatResult(result)) setWaitingForOperand(true) } catch { setDisplay('Error') } } const factorial = (n) => { if (n < 0 || !Number.isInteger(n)) return NaN if (n === 0 || n === 1) return 1 let r = 1; for (let i = 2; i <= n; i++) r *= i; return r } const clear = () => { setDisplay('0'); setExpressionText(''); setWaitingForOperand(false) } const backspace = () => { setDisplay(display.length > 1 ? display.slice(0, -1) : '0') } const toggleSign = () => setDisplay(String(parseFloat(display) * -1)) const handleDialogKeyDown = useCallback((event) => { if (event.key === 'Escape') { event.preventDefault() onClose() return } if (event.key !== 'Tab') return const focusable = Array.from( dialogRef.current?.querySelectorAll('button:not([disabled])') || [] ) if (focusable.length === 0) return const first = focusable[0] const last = focusable.at(-1) if (event.shiftKey && document.activeElement === first) { event.preventDefault() last.focus() } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault() first.focus() } }, [onClose]) return (