// MaxAMove CRM — Payment Flow
// Shared payment components used by the On-Job view (Gavin's phone). The
// customer portal (portal.html) has its own inline copy of this flow because
// it's a separate self-contained HTML file.
//
// Components exported on window:
//   FinalPaymentOverview  — your-end review screen (line items, totals, edit)
//   PaymentElementCard    — embedded Stripe Payment Element wrapper
//   StarRating            — 1-5 star picker
//   TipSelector           — 10/15/20/Custom/Skip
//   AddFlatFeeModal       — pick from catalog or add custom flat fee
//   ConfirmResetClockModal — "Are you sure?" modal for clock reset

const { useState: _pfUseState, useEffect: _pfUseEffect, useRef: _pfUseRef, useMemo: _pfUseMemo } = React;

// ─── Stripe.js loader ──────────────────────────────────────────────
// Loads stripe.js once and returns a Stripe instance. We load lazily (when
// the Payment Element is first mounted) so casual tab visits don't pull in
// 100KB of Stripe code.
let _stripeInstance = null;
let _stripeLoadPromise = null;

function loadStripe(publishableKey) {
  if (_stripeInstance) return Promise.resolve(_stripeInstance);
  if (_stripeLoadPromise) return _stripeLoadPromise;
  _stripeLoadPromise = new Promise((resolve, reject) => {
    if (window.Stripe) {
      _stripeInstance = window.Stripe(publishableKey);
      return resolve(_stripeInstance);
    }
    const s = document.createElement('script');
    s.src = 'https://js.stripe.com/v3/';
    s.async = true;
    s.onload = () => {
      try { _stripeInstance = window.Stripe(publishableKey); resolve(_stripeInstance); }
      catch (e) { reject(e); }
    };
    s.onerror = () => reject(new Error('Failed to load Stripe.js'));
    document.head.appendChild(s);
  });
  return _stripeLoadPromise;
}
window.maxamoveLoadStripe = loadStripe;

// ─── FinalPaymentOverview ──────────────────────────────────────────
// Your-end review screen shown when Gavin hits "Finish Job & Collect Payment".
// Shows an editable invoice — line items, flat fees, deposit credit, total.
// Once he hits Confirm, fires onConfirm({ subtotal, breakdown }) which:
//   1. Stops both clocks
//   2. Snapshots breakdown to job.extra.finalBreakdown
//   3. Flips paymentLive=true so customer portal shows Pay Now
//   4. Opens PaymentCollectView on Gavin's phone
function FinalPaymentOverview({ job, billableHours, hourlyRate, baseFees, flatFees, depositPaid, onCancel, onConfirm }) {
  // NTE cap — when the job has a not-to-exceed in pricing, the DEFAULT
  // billable hours we charge the customer is capped at nteHours, even if the
  // actual billable clock ran past it. The "lost" amount is tracked but not
  // billed unless Gavin explicitly edits hours up to the actual.
  // (Override approval is logged separately on the job extra during the
  // move; this just respects the cap by default.)
  const ePricing = (job && job.estimatePricing) || (job && job.pricing) || {};
  const nteEnabled = !!ePricing.nteEnabled;
  // Fallback: legacy estimates with nteEnabled=true but nteHours=null (wizard
  // bug). Use the same auto-formula the wizard shows: hours + 1.
  const nteHoursRaw = Number(ePricing.nteHours) || null;
  const estimateHoursForCap = Number(ePricing.hours) || 0;
  const nteHoursCap = nteHoursRaw || (nteEnabled && estimateHoursForCap > 0 ? estimateHoursForCap + 1 : null);
  const hasOverride = !!(job && job.extra && job.extra.nteOverride && job.extra.nteOverride.approved);
  const actualHours = Number(billableHours) || 0;
  // 15-MINUTE ROUND-UP — billing policy: always round billable time up to
  // the next quarter-hour. 3.10 hr (3h 6min) → 3.25 hr (3h 15min).
  // 3.0 → 3.0 (no rounding when already on a quarter). Customer never
  // gets short-changed AND we never undercharge for partial increments.
  const roundedActualHours = Math.ceil(actualHours * 4) / 4;
  // Cap is a HARD ceiling — never bill above it (default). Even when rounding
  // up, NTE wins. e.g. NTE = 4 hr, actual = 4.05 hr → rounded = 4.25 → capped to 4.
  const cappedHours = (nteEnabled && nteHoursCap && roundedActualHours > nteHoursCap && !hasOverride)
    ? nteHoursCap : roundedActualHours;
  // "Money eaten" = labor we won't charge. Surface this so Gavin sees the
  // cost of running over without an override. Compares ROUNDED actual vs
  // cap (rounded actual is what we'd charge if there were no cap).
  const overtimeHoursLost = Math.max(0, roundedActualHours - cappedHours);
  const overtimeMoneyEaten = Math.round(overtimeHoursLost * (Number(hourlyRate) || 0) * 100) / 100;

  // Editable line items — default to the CAPPED value (so the price stays at
  // NTE), allow Gavin to tweak before locking. Tweaking back to actualHours
  // is one keystroke.
  const [hours, setHours] = _pfUseState(cappedHours.toFixed(2));
  const [rate, setRate] = _pfUseState(hourlyRate);
  const [fees, setFees] = _pfUseState(flatFees || []);
  const [extraDiscount, setExtraDiscount] = _pfUseState(0);
  const [extraDiscountReason, setExtraDiscountReason] = _pfUseState('');
  const [busy, setBusy] = _pfUseState(false);

  const laborCost = Math.round((Number(hours) || 0) * (Number(rate) || 0) * 100) / 100;
  const baseFeesTotal = (baseFees || []).reduce((s, f) => s + (Number(f.price) || 0), 0);
  const flatFeesTotal = fees.reduce((s, f) => s + (Number(f.price) || 0), 0);
  const subtotalBeforeDiscount = laborCost + baseFeesTotal + flatFeesTotal;
  const discountAmt = Math.min(Number(extraDiscount) || 0, subtotalBeforeDiscount);
  const subtotal = Math.max(0, subtotalBeforeDiscount - discountAmt);
  const balanceDue = Math.max(0, subtotal - (Number(depositPaid) || 0));

  const editFee = (id, patch) => setFees(prev => prev.map(f => f.id === id ? { ...f, ...patch } : f));
  const removeFee = (id) => setFees(prev => prev.filter(f => f.id !== id));

  const handleConfirm = async () => {
    setBusy(true);
    // Recompute overtime stats based on whatever Gavin actually set hours to.
    // If he kept the cap (default), overtimeHoursLostFinal == overtimeHoursLost.
    // If he edited up to actual, overtimeHoursLostFinal == 0.
    const finalChargedHours = Number(hours) || 0;
    const overtimeHoursLostFinal = Math.max(0, actualHours - finalChargedHours);
    const overtimeMoneyEatenFinal = Math.round(overtimeHoursLostFinal * (Number(rate) || 0) * 100) / 100;
    const breakdown = {
      hours: finalChargedHours,
      hourlyRate: Number(rate) || 0,
      laborCost,
      baseFees: baseFees || [],
      baseFeesTotal,
      flatFees: fees,
      flatFeesTotal,
      discount: discountAmt,
      discountReason: extraDiscountReason || null,
      subtotal,
      depositPaid: Number(depositPaid) || 0,
      balanceDue,
      // Overtime tracking — persisted on the job for accuracy reporting and
      // so Gavin can audit "how much did we eat this week" later.
      actualHours,
      cappedAtNte: nteEnabled && nteHoursCap && finalChargedHours <= nteHoursCap && actualHours > nteHoursCap,
      overtimeHoursLost: overtimeHoursLostFinal,
      overtimeMoneyEaten: overtimeMoneyEatenFinal,
      nteHoursCap,
      nteEnabled,
      lockedAt: new Date().toISOString(),
    };
    try {
      await onConfirm(breakdown);
    } catch (e) {
      alert('Failed to lock in totals: ' + (e.message || 'unknown'));
      setBusy(false);
    }
  };

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.45)', zIndex: 200, display: 'flex', alignItems: 'flex-start', justifyContent: 'center', overflowY: 'auto', padding: '16px 0' }}>
      <div style={{ background: 'var(--card)', borderRadius: 16, width: 'min(560px, 92vw)', maxHeight: 'calc(100vh - 32px)', overflowY: 'auto', boxShadow: '0 12px 48px rgba(0,0,0,0.25)' }}>
        {/* Header */}
        <div style={{ padding: '18px 20px', borderBottom: '1px solid var(--border)', position: 'sticky', top: 0, background: 'var(--card)', zIndex: 1 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
            <div>
              <div style={{ fontSize: 11, fontWeight: 800, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '.08em', marginBottom: 4 }}>Final Payment Overview</div>
              <h2 style={{ fontSize: 18, fontWeight: 900, fontFamily: 'Poppins' }}>{job.customer}</h2>
              <div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 2 }}>Job #{job.id} · Review and lock in before collecting payment</div>
            </div>
            <button onClick={onCancel} disabled={busy} style={{ background: 'transparent', border: 'none', fontSize: 24, color: 'var(--muted)', cursor: busy ? 'wait' : 'pointer', padding: 0, lineHeight: 1 }}>×</button>
          </div>
        </div>

        {/* Body — editable line items */}
        <div style={{ padding: '18px 20px' }}>
          {/* Labor */}
          <div style={{ marginBottom: 18 }}>
            <div style={{ fontSize: 10, fontWeight: 800, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '.08em', marginBottom: 8 }}>Labor</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 100px 120px', gap: 10, alignItems: 'center', padding: '10px 12px', background: 'var(--bg)', borderRadius: 10 }}>
              <div>
                <div style={{ fontSize: 13, fontWeight: 700 }}>Billable hours × rate</div>
                <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 1 }}>
                  {overtimeHoursLost > 0
                    ? <>Actual: {actualHours.toFixed(2)} hr · Capped at NTE</>
                    : actualHours > 0 && Math.abs(roundedActualHours - actualHours) > 0.001
                      ? <>Actual: {actualHours.toFixed(2)} hr · Rounded up to nearest 15 min</>
                      : <>From the clock you stopped</>}
                </div>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                <span style={{ fontSize: 9, color: 'var(--muted)', fontWeight: 700 }}>HOURS</span>
                <input type="number" step="0.25" min="0" value={hours} onChange={e => setHours(e.target.value)}
                  style={{ width: '100%', padding: '7px 9px', borderRadius: 7, border: '1.5px solid var(--border)', fontSize: 14, fontFamily: 'inherit', textAlign: 'right', background: '#fff' }}/>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                <span style={{ fontSize: 9, color: 'var(--muted)', fontWeight: 700 }}>$/HR</span>
                <input type="number" step="1" min="0" value={rate} onChange={e => setRate(Number(e.target.value) || 0)}
                  style={{ width: '100%', padding: '7px 9px', borderRadius: 7, border: '1.5px solid var(--border)', fontSize: 14, fontFamily: 'inherit', textAlign: 'right', background: '#fff' }}/>
              </div>
            </div>
            <div style={{ display: 'flex', justifyContent: 'flex-end', padding: '6px 4px 0', fontSize: 13, fontWeight: 800, color: 'var(--accent-dark)' }}>
              ${laborCost.toFixed(2)}
            </div>

            {/* OVERTIME / NTE-CAPPED CALLOUT — shows when actual hours exceeded
                NTE cap. By default the hours field is set to the cap so the
                customer is charged exactly NTE. Gavin can tap "Charge full"
                to override the cap and bill the actual time worked. */}
            {overtimeHoursLost > 0 && (
              <div style={{ marginTop: 8, padding: '10px 12px', background: '#fef2f2', borderRadius: 9, border: '1.5px solid #fecaca' }}>
                <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8 }}>
                  <Icon name="alert" size={16} color="#dc2626" style={{ flexShrink: 0, marginTop: 1 }}/>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 12, fontWeight: 800, color: '#b91c1c' }}>
                      Job ran <b>{overtimeHoursLost.toFixed(2)} hr</b> past NTE · You&rsquo;re eating <b>${overtimeMoneyEaten.toFixed(2)}</b>
                    </div>
                    <div style={{ fontSize: 11, color: '#7f1d1d', marginTop: 2, lineHeight: 1.4 }}>
                      Defaulting hours to the {nteHoursCap}-hour NTE cap.
                      {hasOverride
                        ? ' Override was approved earlier — tap below to charge full.'
                        : ' Tap below if customer agreed to pay full.'}
                    </div>
                    <div style={{ display: 'flex', gap: 6, marginTop: 8 }}>
                      <button type="button" onClick={() => setHours(roundedActualHours.toFixed(2))}
                        style={{ flex: 1, padding: '8px', borderRadius: 7, border: 'none', background: '#dc2626', color: '#fff', fontWeight: 800, fontSize: 11, fontFamily: 'inherit', cursor: 'pointer' }}>
                        Charge full ({roundedActualHours.toFixed(2)} hr)
                      </button>
                      <button type="button" onClick={() => setHours(cappedHours.toFixed(2))}
                        style={{ flex: 1, padding: '8px', borderRadius: 7, border: '1.5px solid #fecaca', background: '#fff', color: '#7f1d1d', fontWeight: 700, fontSize: 11, fontFamily: 'inherit', cursor: 'pointer' }}>
                        Keep cap ({cappedHours.toFixed(2)} hr)
                      </button>
                    </div>
                  </div>
                </div>
              </div>
            )}
          </div>

          {/* Base fees from estimate (trip fee, surcharges) */}
          {(baseFees && baseFees.length > 0) && (
            <div style={{ marginBottom: 18 }}>
              <div style={{ fontSize: 10, fontWeight: 800, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '.08em', marginBottom: 8 }}>From estimate</div>
              {baseFees.map((f, i) => (
                <div key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 12px', background: 'var(--bg)', borderRadius: 8, marginBottom: 6, fontSize: 13 }}>
                  <span style={{ fontWeight: 600 }}>{f.name}</span>
                  <span style={{ fontWeight: 700 }}>${(Number(f.price) || 0).toFixed(2)}</span>
                </div>
              ))}
            </div>
          )}

          {/* Flat fees added during job */}
          {fees.length > 0 && (
            <div style={{ marginBottom: 18 }}>
              <div style={{ fontSize: 10, fontWeight: 800, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '.08em', marginBottom: 8 }}>Flat fees added today</div>
              {fees.map(f => (
                <div key={f.id} style={{ display: 'grid', gridTemplateColumns: '1fr 110px 32px', gap: 8, alignItems: 'center', padding: '8px 12px', background: 'var(--bg)', borderRadius: 8, marginBottom: 6 }}>
                  <input type="text" value={f.name} onChange={e => editFee(f.id, { name: e.target.value })}
                    style={{ padding: '6px 9px', borderRadius: 6, border: '1.5px solid var(--border)', fontSize: 13, fontFamily: 'inherit', background: '#fff' }}/>
                  <input type="number" step="0.01" min="0" value={f.price} onChange={e => editFee(f.id, { price: Number(e.target.value) || 0 })}
                    style={{ padding: '6px 9px', borderRadius: 6, border: '1.5px solid var(--border)', fontSize: 13, fontFamily: 'inherit', textAlign: 'right', background: '#fff' }}/>
                  <button onClick={() => removeFee(f.id)} title="Remove" style={{ background: 'transparent', border: 'none', color: '#b91c1c', fontSize: 18, cursor: 'pointer', padding: 0 }}>×</button>
                </div>
              ))}
            </div>
          )}

          {/* Optional on-the-spot discount */}
          <div style={{ marginBottom: 18 }}>
            <div style={{ fontSize: 10, fontWeight: 800, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '.08em', marginBottom: 8 }}>Discount (optional)</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 110px', gap: 8, alignItems: 'center', padding: '8px 12px', background: 'var(--bg)', borderRadius: 8 }}>
              <input type="text" placeholder="Reason (optional)" value={extraDiscountReason} onChange={e => setExtraDiscountReason(e.target.value)}
                style={{ padding: '6px 9px', borderRadius: 6, border: '1.5px solid var(--border)', fontSize: 13, fontFamily: 'inherit', background: '#fff' }}/>
              <input type="number" step="0.01" min="0" placeholder="$0.00" value={extraDiscount} onChange={e => setExtraDiscount(Number(e.target.value) || 0)}
                style={{ padding: '6px 9px', borderRadius: 6, border: '1.5px solid var(--border)', fontSize: 13, fontFamily: 'inherit', textAlign: 'right', background: '#fff' }}/>
            </div>
          </div>

          {/* Totals */}
          <div style={{ padding: '14px 16px', background: 'linear-gradient(135deg, #f0fdfb, #ecfdf5)', borderRadius: 12, border: '1.5px solid var(--accent)' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, color: 'var(--muted)', marginBottom: 6 }}>
              <span>Subtotal (labor + fees)</span>
              <span style={{ fontWeight: 700, color: 'var(--text)' }}>${subtotal.toFixed(2)}</span>
            </div>
            {(Number(depositPaid) || 0) > 0 && (
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, color: 'var(--muted)', marginBottom: 6 }}>
                <span>− Deposit already paid</span>
                <span style={{ fontWeight: 700, color: '#16a34a' }}>−${(Number(depositPaid) || 0).toFixed(2)}</span>
              </div>
            )}
            <div style={{ height: 1, background: 'var(--border)', margin: '8px 0' }}/>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
              <span style={{ fontSize: 12, fontWeight: 800, color: 'var(--accent-dark)', textTransform: 'uppercase', letterSpacing: '.05em' }}>Balance due</span>
              <span style={{ fontSize: 26, fontWeight: 900, fontFamily: 'Poppins', color: 'var(--accent-dark)' }}>${balanceDue.toFixed(2)}</span>
            </div>
            <div style={{ fontSize: 10, color: 'var(--muted)', marginTop: 4, lineHeight: 1.4 }}>
              Tip is added by the customer when they pay — not included here.
            </div>
          </div>
        </div>

        {/* Footer — primary action */}
        <div style={{ padding: '14px 20px 18px', borderTop: '1px solid var(--border)', position: 'sticky', bottom: 0, background: 'var(--card)' }}>
          <button onClick={handleConfirm} disabled={busy || balanceDue <= 0}
            style={{ width: '100%', padding: '15px', borderRadius: 12, border: 'none',
              background: balanceDue <= 0 ? 'var(--bg)' : 'linear-gradient(135deg,#14C6BF,#3DCC7E)',
              color: balanceDue <= 0 ? 'var(--muted)' : '#fff',
              fontWeight: 900, fontFamily: 'inherit', fontSize: 15, cursor: (busy || balanceDue <= 0) ? 'not-allowed' : 'pointer',
              boxShadow: balanceDue > 0 ? '0 6px 20px rgba(20,198,191,.3)' : 'none' }}>
            {busy ? 'Locking in…' : balanceDue <= 0 ? 'No balance due' : `Confirm & Collect $${balanceDue.toFixed(2)}`}
          </button>
          <button onClick={onCancel} disabled={busy}
            style={{ width: '100%', marginTop: 8, padding: '11px', borderRadius: 10, border: '1.5px solid var(--border)', background: 'transparent', color: 'var(--muted)', fontWeight: 700, fontFamily: 'inherit', fontSize: 13, cursor: busy ? 'wait' : 'pointer' }}>
            Cancel — keep editing the job
          </button>
        </div>
      </div>
    </div>
  );
}

// ─── PaymentElementCard ────────────────────────────────────────────
// Embedded Stripe Payment Element that lives inside whatever container
// renders it. Mounts on first render, calls onSuccess when the PaymentIntent
// is confirmed. Used on Gavin's CRM (after Confirm & Collect) and on the
// customer portal (when paymentLive=true).
function PaymentElementCard({ clientSecret, returnUrl, onSuccess, onError, label = 'Pay now' }) {
  const containerRef = _pfUseRef(null);
  const [stripe, setStripe] = _pfUseState(null);
  const [elements, setElements] = _pfUseState(null);
  const [paying, setPaying] = _pfUseState(false);
  const [error, setError] = _pfUseState(null);
  const [ready, setReady] = _pfUseState(false);

  _pfUseEffect(() => {
    let cancelled = false;
    if (!clientSecret) return;
    loadStripe(window.STRIPE_PK).then(s => {
      if (cancelled) return;
      setStripe(s);
      const els = s.elements({
        clientSecret,
        appearance: {
          theme: 'stripe',
          variables: {
            colorPrimary: '#14C6BF',
            colorText: '#0f172a',
            colorBackground: '#ffffff',
            fontFamily: 'DM Sans, sans-serif',
            borderRadius: '10px',
          },
        },
      });
      const pe = els.create('payment', {
        layout: 'tabs',
        wallets: { applePay: 'auto', googlePay: 'auto' },
      });
      pe.mount(containerRef.current);
      pe.on('ready', () => !cancelled && setReady(true));
      setElements(els);
    }).catch(e => {
      console.error('[PaymentElementCard] stripe load failed', e);
      setError('Could not load payment form. Refresh and try again.');
    });
    return () => { cancelled = true; };
  }, [clientSecret]);

  const handleSubmit = async (e) => {
    e && e.preventDefault && e.preventDefault();
    if (!stripe || !elements || paying) return;
    setPaying(true);
    setError(null);
    try {
      const result = await stripe.confirmPayment({
        elements,
        confirmParams: returnUrl ? { return_url: returnUrl } : {},
        redirect: 'if_required',
      });
      if (result.error) {
        setError(result.error.message || 'Payment failed');
        if (onError) onError(result.error);
        setPaying(false);
        return;
      }
      // Success path — paymentIntent.status === 'succeeded' or 'processing'
      if (onSuccess) onSuccess(result.paymentIntent);
    } catch (err) {
      console.error('[PaymentElementCard] confirmPayment threw', err);
      setError(err.message || 'Payment failed');
      if (onError) onError(err);
      setPaying(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div ref={containerRef} style={{ minHeight: 200 }}/>
      {!ready && !error && (
        <div style={{ padding: 14, textAlign: 'center', color: 'var(--muted)', fontSize: 13 }}>Loading payment form…</div>
      )}
      {error && (
        <div style={{ padding: '10px 12px', background: '#fef2f2', border: '1px solid #fecaca', borderRadius: 9, color: '#b91c1c', fontSize: 13 }}>
          {error}
        </div>
      )}
      <button type="submit" disabled={!ready || paying}
        style={{ padding: '14px', borderRadius: 12, border: 'none',
          background: (!ready || paying) ? 'var(--bg)' : 'linear-gradient(135deg,#14C6BF,#3DCC7E)',
          color: (!ready || paying) ? 'var(--muted)' : '#fff',
          fontWeight: 900, fontFamily: 'inherit', fontSize: 15,
          cursor: (!ready || paying) ? 'not-allowed' : 'pointer',
          boxShadow: ready && !paying ? '0 6px 20px rgba(20,198,191,.3)' : 'none' }}>
        {paying ? 'Processing…' : label}
      </button>
    </form>
  );
}

// ─── StarRating ────────────────────────────────────────────────────
function StarRating({ value, onChange, size = 30 }) {
  const [hover, setHover] = _pfUseState(0);
  return (
    <div style={{ display: 'inline-flex', gap: 4 }}>
      {[1, 2, 3, 4, 5].map(n => {
        const filled = (hover || value) >= n;
        return (
          <button key={n} type="button"
            onMouseEnter={() => setHover(n)} onMouseLeave={() => setHover(0)}
            onClick={() => onChange(n)}
            aria-label={`${n} star${n === 1 ? '' : 's'}`}
            style={{ background: 'transparent', border: 'none', padding: 4, cursor: 'pointer', lineHeight: 1 }}>
            <svg width={size} height={size} viewBox="0 0 24 24" fill={filled ? '#f59e0b' : 'none'} stroke={filled ? '#f59e0b' : '#cbd5e1'} strokeWidth="1.75" strokeLinejoin="round">
              <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
            </svg>
          </button>
        );
      })}
    </div>
  );
}

// ─── TipSelector ───────────────────────────────────────────────────
// Three-tier moving-industry standard percentages: 10/15/20.
// Custom + Skip both visible (Skip prominent — no tip-pressure).
function TipSelector({ subtotal, value, onChange }) {
  const [customMode, setCustomMode] = _pfUseState(false);
  const [customAmt, setCustomAmt] = _pfUseState('');

  const tiers = [
    { pct: 0.10, label: '10%' },
    { pct: 0.15, label: '15%' },
    { pct: 0.20, label: '20%' },
  ];

  const setTier = (pct) => {
    setCustomMode(false);
    const amt = Math.round(Number(subtotal) * pct * 100) / 100;
    onChange({ amount: amt, label: `${Math.round(pct * 100)}%` });
  };
  const setCustom = (raw) => {
    setCustomAmt(raw);
    const amt = Math.max(0, Number(raw) || 0);
    onChange({ amount: amt, label: 'Custom' });
  };
  const skip = () => {
    setCustomMode(false);
    setCustomAmt('');
    onChange({ amount: 0, label: 'Skip' });
  };

  const activeTierPct = !customMode && value && value.amount > 0 && Math.abs(value.amount / subtotal - 0.10) < 0.001 ? 0.10
    : !customMode && value && value.amount > 0 && Math.abs(value.amount / subtotal - 0.15) < 0.001 ? 0.15
    : !customMode && value && value.amount > 0 && Math.abs(value.amount / subtotal - 0.20) < 0.001 ? 0.20
    : null;

  return (
    <div>
      <div style={{ fontSize: 11, fontWeight: 800, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '.08em', marginBottom: 8 }}>Add a tip (optional)</div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginBottom: 8 }}>
        {tiers.map(t => {
          const active = activeTierPct === t.pct;
          const amt = Math.round(Number(subtotal) * t.pct * 100) / 100;
          return (
            <button key={t.label} type="button" onClick={() => setTier(t.pct)}
              style={{ padding: '12px 8px', borderRadius: 10,
                background: active ? 'linear-gradient(135deg,#14C6BF,#3DCC7E)' : '#fff',
                color: active ? '#fff' : 'var(--text)',
                border: active ? 'none' : '1.5px solid var(--border)',
                fontWeight: 800, fontSize: 14, fontFamily: 'inherit', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
              <span>{t.label}</span>
              <span style={{ fontSize: 10, opacity: 0.85, fontWeight: 600 }}>${amt.toFixed(2)}</span>
            </button>
          );
        })}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
        <button type="button" onClick={() => { setCustomMode(true); }} style={{ padding: '10px 8px', borderRadius: 9, background: customMode ? 'var(--accent-light)' : '#fff', border: `1.5px solid ${customMode ? 'var(--accent)' : 'var(--border)'}`, color: customMode ? 'var(--accent-dark)' : 'var(--text)', fontWeight: 700, fontSize: 13, fontFamily: 'inherit', cursor: 'pointer' }}>
          Custom
        </button>
        <button type="button" onClick={skip} style={{ padding: '10px 8px', borderRadius: 9, background: !value || value.amount === 0 ? 'var(--bg)' : '#fff', border: '1.5px solid var(--border)', color: 'var(--muted)', fontWeight: 700, fontSize: 13, fontFamily: 'inherit', cursor: 'pointer' }}>
          Skip
        </button>
      </div>
      {customMode && (
        <div style={{ marginTop: 8 }}>
          <input type="number" min="0" step="0.01" placeholder="Tip amount $" value={customAmt} onChange={e => setCustom(e.target.value)} autoFocus
            style={{ width: '100%', padding: '11px 12px', borderRadius: 10, border: '1.5px solid var(--accent)', fontSize: 16, fontFamily: 'inherit', background: '#fff', outline: 'none' }}/>
        </div>
      )}
    </div>
  );
}

// ─── AddFlatFeeModal ───────────────────────────────────────────────
// Picks from the inventory catalog (assembly/disassembly fees, packing,
// piano, TV mount, etc.) OR lets Gavin type a custom name + price.
function AddFlatFeeModal({ onClose, onAdd, catalogItems }) {
  const [tab, setTab] = _pfUseState('catalog');
  const [search, setSearch] = _pfUseState('');
  const [customName, setCustomName] = _pfUseState('');
  const [customPrice, setCustomPrice] = _pfUseState('');

  // Default flat-rate menu — covers the common moving extras. Gavin can edit
  // price before adding so seasonal pricing or one-off discounts work.
  const defaultMenu = [
    { name: 'TV mount / unmount', price: 75 },
    { name: 'Piano (upright)', price: 200 },
    { name: 'Piano (baby grand)', price: 400 },
    { name: 'Pool table', price: 350 },
    { name: 'Safe (small/med)', price: 150 },
    { name: 'Safe (large)', price: 300 },
    { name: 'Crib disassembly/assembly', price: 50 },
    { name: 'Bed disassembly/assembly', price: 50 },
    { name: 'Treadmill', price: 100 },
    { name: 'Hot tub', price: 500 },
    { name: 'Long carry (extra)', price: 75 },
    { name: 'Stair carry (per flight)', price: 50 },
    { name: 'Fuel surcharge', price: 25 },
  ];
  const filtered = defaultMenu.filter(i => !search || i.name.toLowerCase().includes(search.toLowerCase()));

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 200, display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}>
      <div style={{ background: 'var(--card)', borderRadius: '16px 16px 0 0', width: '100%', maxWidth: 520, maxHeight: '85vh', overflowY: 'auto' }}>
        <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', position: 'sticky', top: 0, background: 'var(--card)', zIndex: 1 }}>
          <h3 style={{ fontSize: 16, fontWeight: 800, fontFamily: 'Poppins' }}>Add flat fee</h3>
          <button onClick={onClose} style={{ background: 'transparent', border: 'none', fontSize: 22, color: 'var(--muted)', cursor: 'pointer', padding: 0 }}>×</button>
        </div>

        <div style={{ display: 'flex', gap: 4, padding: '8px 14px', borderBottom: '1px solid var(--border)' }}>
          <button onClick={() => setTab('catalog')} style={{ flex: 1, padding: '8px', borderRadius: 8, background: tab === 'catalog' ? 'var(--accent-light)' : 'transparent', color: tab === 'catalog' ? 'var(--accent-dark)' : 'var(--muted)', border: 'none', fontWeight: 800, fontSize: 13, fontFamily: 'inherit', cursor: 'pointer' }}>From menu</button>
          <button onClick={() => setTab('custom')} style={{ flex: 1, padding: '8px', borderRadius: 8, background: tab === 'custom' ? 'var(--accent-light)' : 'transparent', color: tab === 'custom' ? 'var(--accent-dark)' : 'var(--muted)', border: 'none', fontWeight: 800, fontSize: 13, fontFamily: 'inherit', cursor: 'pointer' }}>Custom</button>
        </div>

        <div style={{ padding: 14 }}>
          {tab === 'catalog' && (
            <>
              <input type="text" placeholder="Search…" value={search} onChange={e => setSearch(e.target.value)}
                style={{ width: '100%', padding: '10px 12px', borderRadius: 9, border: '1.5px solid var(--border)', fontSize: 14, fontFamily: 'inherit', background: 'var(--bg)', outline: 'none', marginBottom: 10 }}/>
              {filtered.map((item, i) => (
                <CatalogFeeRow key={i} item={item} onAdd={onAdd}/>
              ))}
              {filtered.length === 0 && (
                <div style={{ padding: 20, textAlign: 'center', color: 'var(--muted)', fontSize: 13 }}>
                  No matches. Try the Custom tab.
                </div>
              )}
            </>
          )}
          {tab === 'custom' && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              <label>
                <span style={{ fontSize: 10, fontWeight: 800, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '.06em', display: 'block', marginBottom: 4 }}>Description</span>
                <input type="text" placeholder="e.g. Heavy item charge" value={customName} onChange={e => setCustomName(e.target.value)} autoFocus
                  style={{ width: '100%', padding: '11px 13px', borderRadius: 10, border: '1.5px solid var(--border)', fontSize: 15, fontFamily: 'inherit', background: 'var(--bg)', outline: 'none' }}/>
              </label>
              <label>
                <span style={{ fontSize: 10, fontWeight: 800, color: 'var(--muted)', textTransform: 'uppercase', letterSpacing: '.06em', display: 'block', marginBottom: 4 }}>Price ($)</span>
                <input type="number" step="0.01" min="0" placeholder="0.00" value={customPrice} onChange={e => setCustomPrice(e.target.value)}
                  style={{ width: '100%', padding: '11px 13px', borderRadius: 10, border: '1.5px solid var(--border)', fontSize: 15, fontFamily: 'inherit', background: 'var(--bg)', outline: 'none' }}/>
              </label>
              <button disabled={!customName.trim() || !(Number(customPrice) >= 0)}
                onClick={() => { onAdd({ name: customName.trim(), price: Number(customPrice) || 0, source: 'custom' }); }}
                style={{ marginTop: 4, padding: '13px', borderRadius: 11, border: 'none',
                  background: (customName.trim() && Number(customPrice) >= 0) ? 'linear-gradient(135deg,#14C6BF,#3DCC7E)' : 'var(--bg)',
                  color: (customName.trim() && Number(customPrice) >= 0) ? '#fff' : 'var(--muted)',
                  fontWeight: 900, fontSize: 14, fontFamily: 'inherit', cursor: (customName.trim() && Number(customPrice) >= 0) ? 'pointer' : 'not-allowed' }}>
                Add custom fee
              </button>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// Single row in the AddFlatFeeModal catalog list — editable price before add.
function CatalogFeeRow({ item, onAdd }) {
  const [price, setPrice] = _pfUseState(item.price);
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 100px 90px', gap: 8, alignItems: 'center', padding: '9px 0', borderBottom: '1px solid var(--border)' }}>
      <span style={{ fontSize: 13, fontWeight: 600 }}>{item.name}</span>
      <input type="number" step="0.01" min="0" value={price} onChange={e => setPrice(Number(e.target.value) || 0)}
        style={{ padding: '6px 9px', borderRadius: 6, border: '1.5px solid var(--border)', fontSize: 13, fontFamily: 'inherit', textAlign: 'right', background: '#fff' }}/>
      <button onClick={() => onAdd({ name: item.name, price: Number(price) || 0, source: 'catalog' })}
        style={{ padding: '7px', borderRadius: 7, border: 'none', background: 'var(--accent)', color: '#fff', fontWeight: 800, fontSize: 12, fontFamily: 'inherit', cursor: 'pointer' }}>
        + Add
      </button>
    </div>
  );
}

// ─── ConfirmResetClockModal ────────────────────────────────────────
function ConfirmResetClockModal({ which, onCancel, onConfirm }) {
  const label = which === 'job' ? 'Job Time' : which === 'bill' ? 'Billable Time' : 'both clocks';
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 250, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16 }}>
      <div style={{ background: 'var(--card)', borderRadius: 14, padding: 22, width: 'min(420px, 100%)', boxShadow: '0 12px 40px rgba(0,0,0,0.25)' }}>
        <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, marginBottom: 8, color: '#b45309' }}>
          <Icon name="alert" size={18}/>
          <h3 style={{ fontSize: 16, fontWeight: 900, fontFamily: 'Poppins' }}>Reset {label}?</h3>
        </div>
        <p style={{ fontSize: 13, color: 'var(--muted)', lineHeight: 1.5, marginBottom: 16 }}>
          This will erase the elapsed time and pause history for {which === 'both' ? 'both clocks' : 'this clock'}. You can&rsquo;t undo this.
        </p>
        <div style={{ display: 'flex', gap: 8 }}>
          <button onClick={onCancel}
            style={{ flex: 1, padding: '11px', borderRadius: 9, border: '1.5px solid var(--border)', background: 'transparent', color: 'var(--text)', fontWeight: 700, fontFamily: 'inherit', fontSize: 13, cursor: 'pointer' }}>
            Cancel
          </button>
          <button onClick={onConfirm}
            style={{ flex: 1, padding: '11px', borderRadius: 9, border: 'none', background: '#b91c1c', color: '#fff', fontWeight: 800, fontFamily: 'inherit', fontSize: 13, cursor: 'pointer' }}>
            Yes, reset
          </button>
        </div>
      </div>
    </div>
  );
}

// Expose to the rest of the app
Object.assign(window, {
  FinalPaymentOverview,
  PaymentElementCard,
  StarRating,
  TipSelector,
  AddFlatFeeModal,
  ConfirmResetClockModal,
});
