// ─── Pure conversion engine ───────────────────────────────────────── // No DOM dependencies — just math. Used by the Calculator component. import type { CalculatorDef } from './data/calculators'; export interface SolveResult { val1: string; val2: string; val3: string; } function fmt(n: number): string { return parseFloat(n.toFixed(6)).toString(); } function gcd(a: number, b: number): number { a = Math.abs(Math.round(a)); b = Math.abs(Math.round(b)); return b ? gcd(b, a % b) : a; } /** * Run the conversion for a given calculator definition. * `source` indicates which field the user is typing in (1, 2, or 3). * Returns new string values for all fields. */ export function solve( calc: CalculatorDef, source: 1 | 2 | 3, rawVal1: string, rawVal2: string, rawVal3: string ): SolveResult { const v1 = parseFloat(rawVal1); const v2 = parseFloat(rawVal2); const v3 = parseFloat(rawVal3); const factor = calc.factor ?? 1; const offset = calc.offset ?? 0; let out: SolveResult = { val1: rawVal1, val2: rawVal2, val3: rawVal3 }; switch (calc.type) { case 'standard': if (source === 1) { out.val2 = !isNaN(v1) ? fmt(v1 * factor + offset) : ''; } else { out.val1 = !isNaN(v2) ? fmt((v2 - offset) / factor) : ''; } break; case 'inverse': if (source === 1) { out.val2 = (!isNaN(v1) && v1 !== 0) ? fmt(factor / v1) : ''; } else { out.val1 = (!isNaN(v2) && v2 !== 0) ? fmt(factor / v2) : ''; } break; case '3col': if (source === 1 || source === 2) { out.val3 = (!isNaN(v1) && !isNaN(v2) && v2 !== 0) ? fmt(v1 / v2) : ''; } else { out.val1 = (!isNaN(v3) && !isNaN(v2)) ? fmt(v3 * v2) : ''; } break; case '3col-mul': if (source === 1 || source === 2) { out.val3 = (!isNaN(v1) && !isNaN(v2)) ? fmt(v1 * v2) : ''; } else { out.val1 = (!isNaN(v3) && !isNaN(v2) && v2 !== 0) ? fmt(v3 / v2) : ''; } break; case 'base': { const fromBase = calc.fromBase ?? 10; const toBase = calc.toBase ?? 2; if (source === 1) { const val = rawVal1.trim(); if (!val) { out.val2 = ''; break; } const dec = parseInt(val, fromBase); out.val2 = !isNaN(dec) ? dec.toString(toBase).toUpperCase() : 'Invalid'; } else { const val = rawVal2.trim(); if (!val) { out.val1 = ''; break; } const dec = parseInt(val, toBase); out.val1 = !isNaN(dec) ? dec.toString(fromBase).toUpperCase() : 'Invalid'; } break; } case 'text-bin': if (source === 1) { out.val2 = rawVal1.split('').map(c => c.charCodeAt(0).toString(2).padStart(8, '0')).join(' '); } else { try { out.val1 = rawVal2.split(' ').map(b => String.fromCharCode(parseInt(b, 2))).join(''); } catch { out.val1 = 'Error'; } } break; case 'bin-text': if (source === 1) { try { out.val2 = rawVal1.split(' ').map(b => String.fromCharCode(parseInt(b, 2))).join(''); } catch { out.val2 = 'Error'; } } else { out.val1 = rawVal2.split('').map(c => c.charCodeAt(0).toString(2).padStart(8, '0')).join(' '); } break; case 'dms-dd': if (source === 1) { if (!isNaN(v1)) { const d = Math.floor(v1); const md = (v1 - d) * 60; const m = Math.floor(md); const sec = ((md - m) * 60).toFixed(2); out.val2 = `${d}° ${m}' ${sec}"`; } else { out.val2 = ''; } } else { const match = rawVal2.match(/(?:([0-9.-]+)\s*°)?\s*(?:([0-9.-]+)\s*')?\s*(?:([0-9.-]+)\s*")?/); if (match && rawVal2.trim().length > 0) { const d = parseFloat(match[1]) || 0; const m = parseFloat(match[2]) || 0; const sec = parseFloat(match[3]) || 0; out.val1 = fmt(d + m / 60 + sec / 3600); } else { out.val1 = ''; } } break; case 'dec-frac': if (source === 1) { if (!isNaN(v1)) { const parts = v1.toString().split('.'); const len = parts[1] ? parts[1].length : 0; const den = Math.pow(10, len); const num = v1 * den; const div = gcd(num, den); out.val2 = `${num / div}/${den / div}`; } else { out.val2 = ''; } } else { const parts = rawVal2.split('/'); if (parts.length === 2 && !isNaN(Number(parts[0])) && !isNaN(Number(parts[1])) && Number(parts[1]) !== 0) { out.val1 = fmt(Number(parts[0]) / Number(parts[1])); } else { const f = parseFloat(parts[0]); out.val1 = !isNaN(f) ? f.toString() : ''; } } break; case 'db-int': if (source === 1) { out.val2 = !isNaN(v1) ? (1e-12 * Math.pow(10, v1 / 10)).toExponential(6) : ''; } else { out.val1 = (!isNaN(v2) && v2 > 0) ? fmt(10 * Math.log10(v2 / 1e-12)) : ''; } break; case 'db-spl': if (source === 1) { out.val2 = !isNaN(v1) ? fmt(20 * Math.pow(10, v1 / 20)) : ''; } else { out.val1 = (!isNaN(v2) && v2 > 0) ? fmt(20 * Math.log10(v2 / 20)) : ''; } break; case 'db-v': if (source === 1) { out.val2 = !isNaN(v1) ? fmt(Math.pow(10, v1 / 20)) : ''; } else { out.val1 = (!isNaN(v2) && v2 > 0) ? fmt(20 * Math.log10(v2)) : ''; } break; case 'db-w': if (source === 1) { out.val2 = !isNaN(v1) ? fmt(Math.pow(10, v1 / 10)) : ''; } else { out.val1 = (!isNaN(v2) && v2 > 0) ? fmt(10 * Math.log10(v2)) : ''; } break; } return out; }