/* global React, ReactDOM */
const { useState, useMemo } = React;

// === Math ===
const calcPanamaTax = (income) => {
  if (income <= 11000) return 0;
  if (income <= 50000) return (income - 11000) * 0.15;
  return (50000 - 11000) * 0.15 + (income - 50000) * 0.25;
};
const calcNPV = (cashflows, rate) =>
  cashflows.reduce((acc, cf, t) => acc + cf / Math.pow(1 + rate, t), 0);
const calcIRR = (cashflows) => {
  const hasNeg = cashflows.some(cf => cf < 0);
  const hasPos = cashflows.some(cf => cf > 0);
  if (!hasNeg || !hasPos) return null;
  let rate = 0.1;
  for (let i = 0; i < 100; i++) {
    let npv = 0, dnpv = 0;
    for (let t = 0; t < cashflows.length; t++) {
      const denom = Math.pow(1 + rate, t);
      npv += cashflows[t] / denom;
      if (t > 0) dnpv -= (t * cashflows[t]) / Math.pow(1 + rate, t + 1);
    }
    if (Math.abs(npv) < 1e-6) return rate;
    if (Math.abs(dnpv) < 1e-12) break;
    const newRate = rate - npv / dnpv;
    if (!isFinite(newRate)) break;
    if (Math.abs(newRate - rate) < 1e-8) return newRate;
    rate = newRate;
    if (rate < -0.99) rate = -0.99;
  }
  let low = -0.99, high = 10;
  let npvLow = calcNPV(cashflows, low);
  let npvHigh = calcNPV(cashflows, high);
  if (npvLow * npvHigh > 0) return null;
  for (let i = 0; i < 200; i++) {
    const mid = (low + high) / 2;
    const npvMid = calcNPV(cashflows, mid);
    if (Math.abs(npvMid) < 1e-6) return mid;
    if (npvMid * npvLow < 0) { high = mid; npvHigh = npvMid; }
    else { low = mid; npvLow = npvMid; }
  }
  return (low + high) / 2;
};

const formatUSD = (n) => {
  if (n === null || n === undefined || !isFinite(n)) return '—';
  return new Intl.NumberFormat('es-PA', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(n);
};
const formatPct = (n) => {
  if (n === null || n === undefined || !isFinite(n)) return '—';
  return `${(n * 100).toFixed(2)}%`;
};

// === Icons ===
const InfoIcon = () => (
  <svg className="cmp-tt-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
);
const AlertIcon = () => (
  <svg className="cmp-banner-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
);
const Chev = ({ up }) => (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
    {up ? <polyline points="18 15 12 9 6 15"/> : <polyline points="6 9 12 15 18 9"/>}
  </svg>
);
const BookIcon = () => (
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
);

// === Inputs ===
const Tooltip = ({ text }) => (
  <span className="cmp-tt">
    <InfoIcon />
    <span className="cmp-tt-body">{text}</span>
  </span>
);
const NumberInput = ({ value, onChange, prefix, suffix, step, min }) => (
  <div className="cmp-input-wrap">
    {prefix && <span className="cmp-input-prefix">{prefix}</span>}
    <input
      type="number"
      className={`cmp-input ${prefix ? 'has-prefix' : ''} ${suffix ? 'has-suffix' : ''}`}
      value={value}
      onChange={(e) => onChange(e.target.value === '' ? '' : Number(e.target.value))}
      step={step || 'any'}
      min={min}
    />
    {suffix && <span className="cmp-input-suffix">{suffix}</span>}
  </div>
);
const TextInput = ({ value, onChange, placeholder }) => (
  <input type="text" className="cmp-input" value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} />
);
const Toggle = ({ checked, onChange, label }) => (
  <button type="button" className={`cmp-toggle ${checked ? 'is-on' : ''}`} onClick={() => onChange(!checked)}>
    <span className="cmp-toggle-track"><span className="cmp-toggle-thumb"/></span>
    <span className="cmp-toggle-label">{label}</span>
  </button>
);

const defaultPolicy = (i) => ({
  name: `Póliza ${i + 1}`,
  premium: 1200,
  withdrawal: 30000,
});

function App() {
  // Global required
  const [discountRate, setDiscountRate] = useState(3);
  const [numPolicies, setNumPolicies] = useState(2);
  const [years, setYears] = useState(20);

  // Global optional
  const [showOptional, setShowOptional] = useState(false);
  const [taxEnabled, setTaxEnabled] = useState(false);
  const [income, setIncome] = useState(30000);
  const [filingCost, setFilingCost] = useState(125);
  const [replacementEnabled, setReplacementEnabled] = useState(false);
  const [replacement, setReplacement] = useState(0);
  const [tranquilidadEnabled, setTranquilidadEnabled] = useState(false);
  const [tranquilidad, setTranquilidad] = useState(0);

  // Per-policy
  const [policies, setPolicies] = useState([defaultPolicy(0), defaultPolicy(1), defaultPolicy(2), defaultPolicy(3)]);
  const [showInfo, setShowInfo] = useState(false);

  const updatePolicy = (idx, field, value) => {
    setPolicies(prev => prev.map((p, i) => i === idx ? { ...p, [field]: value } : p));
  };

  const activePolicies = policies.slice(0, numPolicies);

  const results = useMemo(() => {
    const rate = (Number(discountRate) || 0) / 100;
    const yrs = Math.max(0, Math.floor(Number(years) || 0));
    return activePolicies.map((p, idx) => {
      const premium = Number(p.premium) || 0;
      const withdrawal = Number(p.withdrawal) || 0;
      let taxSaving = 0;
      if (taxEnabled) {
        const inc = Number(income) || 0;
        const fc = Number(filingCost) || 0;
        const taxWithout = calcPanamaTax(inc);
        const taxWith = calcPanamaTax(Math.max(0, inc - premium));
        taxSaving = (taxWithout - taxWith) - fc;
      }
      const rep = replacementEnabled ? (Number(replacement) || 0) : 0;
      const tranq = tranquilidadEnabled ? (Number(tranquilidad) || 0) : 0;
      const annualInflow = taxSaving + rep + tranq;
      const netAnnual = -premium + annualInflow;
      const cashflows = [];
      for (let t = 0; t < yrs; t++) cashflows.push(netAnnual);
      cashflows.push(withdrawal);
      const npv = calcNPV(cashflows, rate);
      const npc = -npv;
      const irr = calcIRR(cashflows);
      const totalCost = premium * yrs;
      const totalInflows = annualInflow * yrs;
      return { idx, name: p.name || `Póliza ${idx + 1}`, premium, years: yrs, withdrawal, npc, irr, totalCost, totalInflows, annualInflow, taxSaving };
    });
  }, [activePolicies, discountRate, years, taxEnabled, income, filingCost, replacementEnabled, replacement, tranquilidadEnabled, tranquilidad]);

  const ranked = useMemo(() => [...results].sort((a, b) => a.npc - b.npc), [results]);
  const verdictMap = useMemo(() => {
    const map = {};
    ranked.forEach((r, rank) => {
      let label, cls;
      if (rank === 0) { label = 'Más eficiente'; cls = 'cmp-badge-best'; }
      else if (rank === ranked.length - 1 && ranked.length > 2) { label = 'Menos eficiente'; cls = 'cmp-badge-worst'; }
      else { label = 'Alternativa'; cls = 'cmp-badge-alt'; }
      map[r.idx] = { label, cls, rank: rank + 1 };
    });
    return map;
  }, [ranked]);

  const minNPC = ranked.length ? ranked[0].npc : 0;
  const maxNPC = ranked.length ? ranked[ranked.length - 1].npc : 0;
  const colsClass = `cols-${numPolicies}`;

  return (
    <div className="cmp-wrap">
      {/* Banners */}
      <div className="cmp-banner cmp-banner-blue">
        <AlertIcon />
        <div><strong>Requisito de comparación:</strong> Esta herramienta asume que todas las pólizas tienen la <strong>misma suma asegurada</strong>. La comparación es estrictamente sobre eficiencia de costo. Si las sumas aseguradas difieren, los resultados favorecerán artificialmente a la póliza más barata sin reflejar la diferencia en cobertura.</div>
      </div>
      <div className="cmp-banner cmp-banner-gold">
        <AlertIcon />
        <div><strong>Importante:</strong> Una póliza de vida no es primariamente un instrumento de inversión — es protección. Es normal que el componente de retorno, aislado, no supere el costo de oportunidad del capital. Use esta herramienta para comparar eficiencia financiera entre opciones, no para decidir si tener seguro de vida.</div>
      </div>

      <button className="cmp-info-toggle" onClick={() => setShowInfo(!showInfo)}>
        <BookIcon /> {showInfo ? 'Ocultar' : '¿Qué significan NPC y TIR?'} <Chev up={showInfo} />
      </button>
      {showInfo && (
        <div className="cmp-info-card">
          <h3>Costo Presente Neto (NPC)</h3>
          <p>Es el costo total de la póliza expresado en dólares de hoy, después de descontar todos los flujos futuros (primas, retiro recibido y beneficios anuales) a la tasa de descuento elegida.</p>
          <p><strong>Interpretación:</strong> "¿Cuánto me cuesta realmente esta póliza, en valor presente, después de considerar todo lo que recibo de vuelta?" <strong>Menor NPC = más eficiente.</strong></p>
          <div className="cmp-info-note"><strong>Nota técnica:</strong> NPC es matemáticamente el opuesto del NPV. Un NPC positivo significa que el costo presente de las primas supera el beneficio presente del retiro — esperado en pólizas de vida, ya que su valor principal está en la cobertura.</div>
          <div className="cmp-info-divider"></div>
          <h3>Tasa Interna de Retorno (TIR)</h3>
          <p>Es el rendimiento anualizado efectivo que genera la póliza vista como inversión pura. Es la tasa de descuento que haría el NPC igual a cero.</p>
          <ul>
            <li><strong>TIR &gt; tasa de descuento:</strong> El componente de inversión supera el costo de oportunidad.</li>
            <li><strong>TIR &lt; tasa de descuento:</strong> El cliente obtendría más en su alternativa de inversión.</li>
            <li><strong>TIR no calculable:</strong> Los flujos no permiten un cálculo.</li>
          </ul>
        </div>
      )}

      {/* Global parameters */}
      <div className="cmp-card">
        <div className="cmp-card-pad">
          <h2 className="cmp-section-title">Parámetros globales</h2>
          <div className="cmp-global-grid">
            <div>
              <label className="cmp-label">Tasa de descuento <Tooltip text="Costo de oportunidad del dinero. Representa lo que ganarías si invirtieras ese dinero en otra parte. 3% es conservador. Si inviertes activamente: 5-7%." /></label>
              <NumberInput value={discountRate} onChange={setDiscountRate} suffix="%" step="0.1" min="0" />
            </div>
            <div>
              <label className="cmp-label">Número de pólizas a comparar</label>
              <select className="cmp-input" value={numPolicies} onChange={(e) => setNumPolicies(Number(e.target.value))}>
                <option value={2}>2 pólizas</option>
                <option value={3}>3 pólizas</option>
                <option value={4}>4 pólizas</option>
              </select>
            </div>
            <div>
              <label className="cmp-label">Años para retiro <Tooltip text="Cantidad de años que pagarás la prima antes de hacer el retiro del valor acumulado. Debe ser igual para todas las pólizas para una comparación justa." /></label>
              <NumberInput value={years} onChange={setYears} suffix="años" min="1" step="1" />
            </div>
          </div>

          {/* Optional adjustments */}
          <div className="cmp-optional">
            <button className="cmp-optional-toggle" onClick={() => setShowOptional(!showOptional)}>
              <span>Ajustes opcionales</span>
              <Chev up={showOptional} />
            </button>
            {showOptional && (
              <div className="cmp-optional-body">
                <div className="cmp-opt-block">
                  <Toggle checked={taxEnabled} onChange={setTaxEnabled} label="Beneficio fiscal" />
                  <p className="cmp-opt-desc">Las primas de seguro de vida son deducibles del impuesto sobre la renta en Panamá, lo que reduce el costo real de la póliza.</p>
                  {taxEnabled && (
                    <div className="cmp-opt-fields">
                      <div>
                        <label className="cmp-label-sm">Ingreso anual del cliente <Tooltip text="Ingreso bruto anual antes de deducciones. Se usa para calcular cuánto te ahorras en impuestos al deducir la prima." /></label>
                        <NumberInput value={income} onChange={setIncome} prefix="$" min="0" />
                      </div>
                      <div>
                        <label className="cmp-label-sm">Costo de declaración <Tooltip text="Lo que cobra tu contador por preparar y presentar la declaración de renta donde deduces la prima. Si ya declaras, pon $0." /></label>
                        <NumberInput value={filingCost} onChange={setFilingCost} prefix="$" min="0" />
                      </div>
                    </div>
                  )}
                </div>
                <div className="cmp-opt-block">
                  <Toggle checked={replacementEnabled} onChange={setReplacementEnabled} label="Reemplazo de otras pólizas" />
                  <p className="cmp-opt-desc">Si la nueva póliza reemplaza coberturas existentes que ya pagas, ese ahorro anual se suma como beneficio en el análisis.</p>
                  {replacementEnabled && (
                    <div className="cmp-opt-fields">
                      <div>
                        <label className="cmp-label-sm">Ahorro anual <Tooltip text="Monto total anual que dejarás de pagar por las pólizas que esta nueva cobertura reemplaza." /></label>
                        <NumberInput value={replacement} onChange={setReplacement} prefix="$" min="0" />
                      </div>
                    </div>
                  )}
                </div>
                <div className="cmp-opt-block">
                  <Toggle checked={tranquilidadEnabled} onChange={setTranquilidadEnabled} label="Valor de tranquilidad" />
                  <p className="cmp-opt-desc">Valor subjetivo que le asignas a la cobertura. Útil para desempatar pólizas con NPC similar.</p>
                  {tranquilidadEnabled && (
                    <div className="cmp-opt-fields">
                      <div>
                        <label className="cmp-label-sm">Valor anual <Tooltip text="¿Cuánto pagarías al año por la tranquilidad de saber que tus beneficiarios recibirían esa suma si te pasara algo? No hay respuesta correcta, es personal." /></label>
                        <NumberInput value={tranquilidad} onChange={setTranquilidad} prefix="$" min="0" />
                      </div>
                    </div>
                  )}
                </div>
              </div>
            )}
          </div>
        </div>
      </div>

      {/* Policies */}
      <div className={`cmp-policy-grid ${colsClass}`}>
        {activePolicies.map((p, idx) => (
          <div key={idx} className="cmp-policy">
            <div className="cmp-policy-head">Póliza {idx + 1}</div>
            <div className="cmp-policy-body">
              <div className="cmp-field">
                <label className="cmp-label-sm">Aseguradora / Plan</label>
                <TextInput value={p.name} onChange={(v) => updatePolicy(idx, 'name', v)} placeholder="Ej: ASSA Optimista" />
              </div>
              <div className="cmp-field">
                <label className="cmp-label-sm">Prima anual <Tooltip text="Monto que pagas cada año por esta póliza. Lo encuentras en la cotización que te dio la aseguradora." /></label>
                <NumberInput value={p.premium} onChange={(v) => updatePolicy(idx, 'premium', v)} prefix="$" min="0" />
              </div>
              <div className="cmp-field">
                <label className="cmp-label-sm">Monto de retiro <Tooltip text="Valor acumulado que recibirás al momento del retiro. Aparece en la tabla de valores de la cotización, en el año que seleccionaste arriba." /></label>
                <NumberInput value={p.withdrawal} onChange={(v) => updatePolicy(idx, 'withdrawal', v)} prefix="$" min="0" />
              </div>
            </div>
          </div>
        ))}
      </div>

      {/* Results */}
      <h2 className="cmp-results-title">Resultados</h2>

      <div className={`cmp-score-grid ${colsClass}`}>
        {results.map((r) => {
          const v = verdictMap[r.idx];
          const isLowest = r.npc === minNPC;
          const isHighest = r.npc === maxNPC && minNPC !== maxNPC;
          const irrBeatsRate = r.irr !== null && r.irr > (Number(discountRate) / 100);
          return (
            <div key={r.idx} className="cmp-score">
              <div className="cmp-score-top">
                <div className="cmp-score-name">{r.name}</div>
                <span className={`cmp-badge ${v.cls}`}>{v.label}</span>
              </div>
              <div className="cmp-score-body">
                <div className="cmp-score-row">
                  <div style={{ flex: 1 }}>
                    <div className="cmp-score-label">Costo Presente Neto</div>
                    <div className={`cmp-npc ${isLowest && ranked.length > 1 ? 'is-best' : ''} ${isHighest ? 'is-worst' : ''}`}>{formatUSD(r.npc)}</div>
                    <div className="cmp-npc-note">
                      {isLowest && ranked.length > 1 && '✓ Menor costo presente'}
                      {isHighest && ranked.length > 1 && '✗ Mayor costo presente'}
                    </div>
                  </div>
                </div>
                <div className="cmp-score-row">
                  <div className="cmp-score-label">Tasa Interna de Retorno</div>
                  <div className={`cmp-score-val ${irrBeatsRate ? 'is-good' : ''}`}>{formatPct(r.irr)}</div>
                </div>
                <div className="cmp-totals">
                  <div className="cmp-totals-row"><span>Total primas pagadas</span><span>{formatUSD(r.totalCost)}</span></div>
                  <div className="cmp-totals-row"><span>Retiro al vencimiento</span><span>{formatUSD(r.withdrawal)}</span></div>
                  {r.totalInflows > 0 && <div className="cmp-totals-row"><span>Ahorro acumulado</span><span>{formatUSD(r.totalInflows)}</span></div>}
                </div>
              </div>
            </div>
          );
        })}
      </div>

      <div className="cmp-card">
        <div className="cmp-card-head">Ranking por NPC (menor es mejor)</div>
        <div className="cmp-table-wrap">
          <table className="cmp-table">
            <thead>
              <tr>
                <th>#</th><th>Plan</th>
                <th className="t-right">Prima Anual</th><th className="t-right">Años</th>
                <th className="t-right">Monto Retiro</th><th className="t-right">NPC</th>
                <th className="t-right">TIR</th><th className="t-center">Veredicto</th>
              </tr>
            </thead>
            <tbody>
              {ranked.map((r, rank) => {
                const v = verdictMap[r.idx];
                const isFirst = rank === 0;
                const isLast = rank === ranked.length - 1 && minNPC !== maxNPC;
                return (
                  <tr key={r.idx} className={isFirst ? 'is-first' : ''}>
                    <td><span className={`cmp-rank-pill ${isFirst ? 'is-first' : ''}`}>{rank + 1}</span></td>
                    <td className="cmp-cell-name">{r.name}</td>
                    <td className="t-right">{formatUSD(r.premium)}</td>
                    <td className="t-right">{r.years}</td>
                    <td className="t-right">{formatUSD(r.withdrawal)}</td>
                    <td className={`t-right ${isFirst ? 'cmp-cell-npc-best' : (isLast ? 'cmp-cell-npc-worst' : 'cmp-cell-npc')}`}>{formatUSD(r.npc)}</td>
                    <td className="t-right">{formatPct(r.irr)}</td>
                    <td className="t-center"><span className={`cmp-badge ${v.cls}`}>{v.label}</span></td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

      <div className="cmp-card">
        <div className="cmp-card-pad">
          <h3 className="cmp-section-title">Guía de decisión</h3>
          <div className="cmp-guide">
            <div className="cmp-guide-item cmp-guide-green"><strong>Menor NPC</strong>La póliza más eficiente en términos de costo presente neto — la opción a recomendar si la cobertura es idéntica.</div>
            <div className="cmp-guide-item cmp-guide-slate"><strong>NPC similares (&lt; 5%)</strong>Considerar factores no financieros: solidez del asegurador, calidad del servicio, exclusiones del contrato.</div>
            <div className="cmp-guide-item cmp-guide-gold"><strong>TIR &gt; tasa de descuento</strong>El componente de retorno supera el costo de oportunidad del cliente.</div>
            <div className="cmp-guide-item cmp-guide-red"><strong>TIR muy baja o negativa</strong>El cliente paga principalmente por cobertura, no por retorno. Normal y aceptable si necesita la protección.</div>
          </div>
        </div>
      </div>

      <div className="cmp-tool-foot">Análisis financiero comparativo · Suma asegurada constante entre pólizas</div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('comparador-root')).render(<App />);
