/* global React, Icon */
const { useEffect, useState } = React;

const BILLING_PLAN_TIERS = {
  starter: [{ websites: 1, price: 29, messages: 2000 }],
  growth: [{ websites: 1, price: 79, messages: 5000 }],
  business: [{ websites: 1, price: 199, messages: 15000 }],
};

const BILLING_PLAN_NAMES = {
  trial: 'Free Trial',
  start: 'Starter Plan',
  starter: 'Starter Plan',
  standard: 'Growth Plan',
  growth: 'Growth Plan',
  business: 'Business Plan',
  legacy: 'Existing Access',
};

const PRICING_URL = 'https://pluginchatbot.com/pricing';

function formatBillingDate(value) {
  const raw = String(value || '').trim();
  if (!raw) return '';
  const normalized = /[zZ]|[+-]\d{2}:?\d{2}$/.test(raw) ? raw : `${raw.replace(' ', 'T')}Z`;
  const date = new Date(normalized);
  if (Number.isNaN(date.getTime())) return '';
  return date.toLocaleDateString(undefined, { day: 'numeric', month: 'long', year: 'numeric' });
}

function BillingPage() {
  const [overview, setOverview] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [busy, setBusy] = useState('');
  const [notice, setNotice] = useState(null);

  // Change-plan selector state
  const [selPlan, setSelPlan] = useState('starter');
  const [selWebsites, setSelWebsites] = useState(1);
  const [preview, setPreview] = useState(null);
  const [previewLoading, setPreviewLoading] = useState(false);
  const [invoices, setInvoices] = useState([]);
  const [invoicesLoading, setInvoicesLoading] = useState(false);

  // Account-level "Bring Your Own" OpenAI key.
  const [byo, setByo] = useState({ configured: false, last4: '' });
  const [byoInput, setByoInput] = useState('');

  // OpenAI usage (estimated cost / tokens over the last 30 days).
  const [usage, setUsage] = useState(null);

  const loadOverview = async () => {
    setLoading(true);
    setError('');
    try {
      const response = await fetch('/api/billing/overview', {
        credentials: 'include',
        cache: 'no-store',
        headers: { 'cache-control': 'no-cache' },
      });
      const data = await response.json().catch(() => ({}));
      if (response.status === 401) {
        window.location.href = (['localhost','127.0.0.1'].includes(location.hostname)?'http://localhost:8788':'https://pluginchatbot.com')+'/login.html?redirect=' + encodeURIComponent(window.location.href);
        return;
      }
      if (!response.ok || !data.ok) {
        throw new Error(data.error || 'Unable to load billing details.');
      }
      setOverview(data.overview);
      const planCode = ['starter', 'growth', 'business'].includes(data.overview.planCode) ? data.overview.planCode : data.overview.planCode === 'standard' ? 'growth' : 'starter';
      setSelPlan(planCode);
      setSelWebsites(1);
    } catch (loadError) {
      setError(loadError.message || 'Unable to load billing details.');
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    loadOverview();
  }, []);

  // Payment history (Stripe invoices) shown directly on the page.
  useEffect(() => {
    let active = true;
    setInvoicesLoading(true);
    fetch('/api/billing/invoices', {
      credentials: 'include',
      cache: 'no-store',
      headers: { 'cache-control': 'no-cache' },
    })
      .then(response => response.json().catch(() => ({})))
      .then(data => { if (active && data && data.ok) setInvoices(Array.isArray(data.invoices) ? data.invoices : []); })
      .catch(() => {})
      .finally(() => { if (active) setInvoicesLoading(false); });
    return () => { active = false; };
  }, []);

  // Load the account's BYO OpenAI key status (last4 only).
  useEffect(() => {
    let active = true;
    fetch('/api/account/openai-key', { credentials: 'include', cache: 'no-store' })
      .then(response => response.json().catch(() => ({})))
      .then(data => { if (active && data && data.ok) setByo({ configured: Boolean(data.configured), last4: String(data.last4 || '') }); })
      .catch(() => {});
    return () => { active = false; };
  }, []);

  // Load the account's OpenAI usage summary (cost/tokens over 30 days).
  useEffect(() => {
    let active = true;
    fetch('/api/account/openai-usage?days=30', { credentials: 'include', cache: 'no-store' })
      .then(response => response.json().catch(() => ({})))
      .then(data => { if (active && data && data.ok) setUsage(data.usage); })
      .catch(() => {});
    return () => { active = false; };
  }, []);

  // Live proration preview whenever the selection changes for an existing sub.
  useEffect(() => {
    if (!overview || !overview.hasStripeSubscription) {
      setPreview(null);
      return undefined;
    }

    const sameAsCurrent = selPlan === overview.planCode && Number(selWebsites) === Number(overview.websites);
    if (sameAsCurrent) {
      setPreview({ unchanged: true });
      return undefined;
    }

    let active = true;
    setPreviewLoading(true);
    postJson('/api/billing/preview-plan-change', { planCode: selPlan, websites: selWebsites })
      .then(data => { if (active) setPreview(data); })
      .catch(() => { if (active) setPreview(null); })
      .finally(() => { if (active) setPreviewLoading(false); });

    return () => { active = false; };
  }, [selPlan, selWebsites, overview]);

  const postJson = async (path, body) => {
    const response = await fetch(path, {
      method: 'POST',
      credentials: 'include',
      cache: 'no-store',
      headers: { 'content-type': 'application/json', 'cache-control': 'no-cache' },
      body: JSON.stringify(body || {}),
    });
    const data = await response.json().catch(() => ({}));
    if (!response.ok || !data.ok) {
      throw new Error(data.error || 'Request failed.');
    }
    return data;
  };

  const saveByo = async () => {
    const key = byoInput.trim();
    if (!key) { setNotice({ type: 'error', text: 'Please paste your OpenAI API key.' }); return; }
    setBusy('byo-save');
    setNotice(null);
    try {
      const res = await fetch('/api/account/openai-key', {
        method: 'POST',
        credentials: 'include',
        cache: 'no-store',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ apiKey: key }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok || !data.ok) throw new Error(data.error || 'Could not save the key.');
      setByo({ configured: Boolean(data.configured), last4: String(data.last4 || '') });
      setByoInput('');
      setNotice({ type: 'success', text: 'Your OpenAI API key has been saved. Your chatbots will now use it.' });
    } catch (saveError) {
      setNotice({ type: 'error', text: saveError.message || 'Could not save the key.' });
    } finally {
      setBusy('');
    }
  };

  const removeByo = async () => {
    setBusy('byo-remove');
    setNotice(null);
    try {
      const res = await fetch('/api/account/openai-key', { method: 'DELETE', credentials: 'include', cache: 'no-store' });
      const data = await res.json().catch(() => ({}));
      if (!res.ok || !data.ok) throw new Error(data.error || 'Could not remove the key.');
      setByo({ configured: false, last4: '' });
      setNotice({ type: 'success', text: "Your OpenAI API key was removed. Chatbots now use PluginChatBot's managed key." });
    } catch (removeError) {
      setNotice({ type: 'error', text: removeError.message || 'Could not remove the key.' });
    } finally {
      setBusy('');
    }
  };

  const openPortal = async () => {
    setBusy('portal');
    setNotice(null);
    try {
      const data = await postJson('/api/billing/create-portal-session');
      if (data.portalUrl) {
        window.location.assign(data.portalUrl);
        return;
      }
      throw new Error('Portal link was not returned.');
    } catch (portalError) {
      setNotice({ type: 'error', text: portalError.message });
      setBusy('');
    }
  };

  const toggleAutoRenew = async () => {
    if (!overview) return;
    const next = !overview.autoRenew;
    setBusy('autoRenew');
    setNotice(null);
    try {
      const data = await postJson('/api/billing/auto-renew', { autoRenew: next });
      setOverview(current => ({ ...current, autoRenew: data.autoRenew }));
      setNotice({
        type: 'success',
        text: data.autoRenew
          ? 'Auto-renew is on. Your plan will renew automatically.'
          : 'Auto-renew is off. Access continues until the period ends, then stops.',
      });
    } catch (renewError) {
      setNotice({ type: 'error', text: renewError.message });
    } finally {
      setBusy('');
    }
  };

  const updatePlan = async () => {
    setBusy('update');
    setNotice(null);
    try {
      const data = await postJson('/api/billing/update-plan', { planCode: selPlan, websites: selWebsites });

      // The whole payment happens on Stripe: active subscription -> hosted
      // "confirm your update" page (prorated); new / expired account -> Stripe
      // Checkout. Either way we hand the customer off to Stripe.
      const redirectUrl = data.portalUrl || data.checkoutUrl;
      if (redirectUrl) {
        setNotice({ type: 'success', text: 'Redirecting you to Stripe to confirm and pay…' });
        window.location.assign(redirectUrl);
        return;
      }

      if (data.unchanged) {
        setNotice({ type: 'success', text: 'You are already on this plan.' });
        setBusy('');
        return;
      }

      setNotice({ type: 'success', text: 'Plan updated. Refreshing your account…' });
      setTimeout(() => window.location.reload(), 1400);
    } catch (updateError) {
      setNotice({ type: 'error', text: updateError.message });
      setBusy('');
    }
  };

  const buyAddon = async (addonCode) => {
    setBusy(`addon-${addonCode}`);
    setNotice(null);
    try {
      const data = await postJson('/api/billing/create-addon-checkout-session', { addonCode });
      if (data.checkoutUrl) {
        window.location.assign(data.checkoutUrl);
        return;
      }
      setNotice({ type: 'error', text: 'Could not open checkout. Please try again.' });
      setBusy('');
    } catch (addonError) {
      setNotice({ type: 'error', text: addonError.message });
      setBusy('');
    }
  };

  const ADDONS = [
    { code: 'messages', title: '+1,000 AI Messages', price: '$15', unit: '/month', desc: 'Add 1,000 extra AI messages every month on top of your current plan. Your base plan stays the same.' },
    { code: 'website', title: '+1 Website', price: '$20', unit: '/month', desc: 'Connect one more website to your account on top of your current plan.' },
  ];

  const card = {
    background: '#fff',
    border: '1px solid #e6e8ee',
    borderRadius: 16,
    padding: 24,
    marginBottom: 20,
    boxShadow: '0 1px 2px rgba(16,24,40,.04)',
  };
  const rowLabel = { color: '#667085', fontSize: 13, fontWeight: 500 };
  const rowValue = { color: '#101828', fontSize: 15, fontWeight: 600 };

  if (loading) {
    return (
      <div className="view">
        <div style={{ ...card, textAlign: 'center', color: '#667085' }}>Loading your billing details…</div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="view">
        <div style={{ ...card, borderColor: '#fda29b', background: '#fffbfa' }}>
          <strong style={{ color: '#b42318' }}>Unable to load billing</strong>
          <p style={{ color: '#667085', margin: '8px 0 16px' }}>{error}</p>
          <button className="btn btn-secondary" type="button" onClick={loadOverview}>Try again</button>
        </div>
      </div>
    );
  }

  const planName = overview.planName || BILLING_PLAN_NAMES[overview.planCode] || 'Current Plan';
  const isExpired = String(overview.status || '').toLowerCase() === 'expired';
  const isTrial = String(overview.status || '').toLowerCase() === 'trialing';
  const statusLabel = isExpired ? 'Expired' : 'Active';
  const hasSub = overview.hasStripeSubscription;
  const renewalDate = formatBillingDate(overview.currentPeriodEnd || overview.expiresAt);
  const isTrialPlan = isTrial || String(overview.planCode || '').toLowerCase() === 'trial';
  // When Stripe hasn't reported an amount (e.g. a manually-set plan), derive the
  // price from the plan/website tier catalogue so it never shows a bare dash.
  const overviewPlanCode = String(overview.planCode || '').toLowerCase();
  const pricingPlanCode = overviewPlanCode === 'start' ? 'starter' : overviewPlanCode === 'standard' ? 'growth' : overviewPlanCode;
  const planTiers = BILLING_PLAN_TIERS[pricingPlanCode];
  const tierPrice = planTiers
    ? (planTiers.find(t => t.websites === Number(overview.websites)) || planTiers[0] || {}).price
    : null;
  const priceLabel = (overview.amount !== null && overview.amount !== undefined)
    ? `$${Number(overview.amount).toLocaleString('en-US')}/${overview.interval || 'month'}`
    : isTrialPlan ? 'Free'
    : (tierPrice != null ? `$${tierPrice}/month` : '—');
  const statusColor = isExpired ? { bg: '#fef3f2', fg: '#b42318' } : { bg: '#ecfdf3', fg: '#027a48' };
  const selectedTier = (BILLING_PLAN_TIERS[selPlan] || []).find(tier => tier.websites === selWebsites)
    || (BILLING_PLAN_TIERS[selPlan] || [])[0];

  return (
    <div className="view">
      <div style={{ marginBottom: 20 }}>
        <h1 style={{ fontSize: 22, fontWeight: 700, color: '#101828', margin: 0 }}>Billing &amp; Subscription</h1>
        <p style={{ color: '#667085', margin: '6px 0 0' }}>View your plan, manage payments and change your subscription.</p>
      </div>

      {notice && (
        <div
          style={{
            ...card,
            marginBottom: 16,
            padding: '14px 18px',
            borderColor: notice.type === 'error' ? '#fda29b' : '#a6f4c5',
            background: notice.type === 'error' ? '#fffbfa' : '#f6fef9',
            color: notice.type === 'error' ? '#b42318' : '#027a48',
            fontWeight: 500,
          }}
        >
          {notice.text}
        </div>
      )}

      {/* Current plan */}
      <div style={card}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16 }}>
          <div>
            <div style={rowLabel}>Current plan</div>
            <div style={{ fontSize: 20, fontWeight: 700, color: '#101828', marginTop: 4 }}>{planName}</div>
            <div style={{ fontSize: 13, color: '#667085', marginTop: 4 }}>
              Status: <strong style={{ color: isExpired ? '#b42318' : '#027a48' }}>{statusLabel}</strong>
            </div>
          </div>
          <span style={{
            background: statusColor.bg,
            color: statusColor.fg,
            fontSize: 12,
            fontWeight: 600,
            padding: '4px 10px',
            borderRadius: 999,
            textTransform: 'capitalize',
          }}>
            {statusLabel}
          </span>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 18, marginTop: 20 }}>
          <div>
            <div style={rowLabel}>Price</div>
            <div style={rowValue}>{priceLabel}</div>
          </div>
          {overview.websites ? (
            <div>
              <div style={rowLabel}>Websites</div>
              <div style={rowValue}>{overview.websites}</div>
            </div>
          ) : null}
          <div>
            <div style={rowLabel}>{isExpired ? 'Ended on' : (isTrial ? 'Trial Ends' : (overview.autoRenew ? 'Renews on' : 'Access until'))}</div>
            <div style={rowValue}>{renewalDate || '—'}</div>
          </div>
        </div>
      </div>

      {/* OpenAI usage (estimated cost over 30 days) */}
      {usage && usage.requests > 0 && (
        <div style={card}>
          <div style={{ fontSize: 16, fontWeight: 700, color: '#101828' }}>OpenAI Usage (last 30 days)</div>
          <p style={{ color: '#667085', margin: '6px 0 16px', maxWidth: 560 }}>
            Estimated spend across all your chatbots, based on recorded token usage.
          </p>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))', gap: 16 }}>
            {usage.actual && (
              <div>
                <div style={rowLabel}>Actual (OpenAI)</div>
                <div style={{ ...rowValue, color: '#027a48' }}>${Number(usage.actual.costUsd).toFixed(2)}</div>
              </div>
            )}
            <div><div style={rowLabel}>Estimated cost</div><div style={rowValue}>${usage.costUsd.toFixed(2)}</div></div>
            <div><div style={rowLabel}>Total tokens</div><div style={rowValue}>{usage.totalTokens.toLocaleString()}</div></div>
            <div><div style={rowLabel}>Requests</div><div style={rowValue}>{usage.requests.toLocaleString()}</div></div>
          </div>
          {usage.actual && (
            <p style={{ color: '#98a2b3', fontSize: 12, margin: '12px 0 0' }}>
              "Actual" is billed spend from OpenAI for this account's dedicated project. "Estimated" is our own token-based estimate.
            </p>
          )}
          {usage.byModel && usage.byModel.length > 0 && (
            <div style={{ marginTop: 18, borderTop: '1px solid #eef0f4', paddingTop: 14 }}>
              <div style={{ ...rowLabel, marginBottom: 8 }}>By model</div>
              {usage.byModel.map(m => (
                <div key={m.model} style={{ display: 'flex', justifyContent: 'space-between', gap: 12, fontSize: 13.5, color: '#475467', padding: '4px 0' }}>
                  <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.model}</span>
                  <span style={{ whiteSpace: 'nowrap', color: '#101828', fontWeight: 600 }}>${m.costUsd.toFixed(2)} · {m.tokens.toLocaleString()} tok</span>
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {/* Your OpenAI API key (Bring Your Own) */}
      <div style={card}>
        <div style={{ fontSize: 16, fontWeight: 700, color: '#101828' }}>Your OpenAI API Key</div>
        <p style={{ color: '#667085', margin: '6px 0 16px', maxWidth: 560 }}>
          Optional. Add your own OpenAI API key and every chatbot on your account will use it, billed directly to your OpenAI account. Leave this empty to use PluginChatBot&apos;s managed key that comes with your plan.
        </p>

        {byo.configured && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
            <span style={{ background: '#ecfdf3', color: '#027a48', fontSize: 12, fontWeight: 700, padding: '4px 10px', borderRadius: 999 }}>
              Your key is active
            </span>
            <span style={{ color: '#667085', fontSize: 13 }}>Ending in ••••{byo.last4}</span>
          </div>
        )}

        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center' }}>
          <input
            type="password"
            value={byoInput}
            onChange={e => setByoInput(e.target.value)}
            placeholder={byo.configured ? 'Paste a new key to replace it' : 'sk-...'}
            autoComplete="off"
            spellCheck={false}
            style={{ flex: '1 1 320px', minWidth: 0, border: '1px solid #d0d5dd', borderRadius: 10, padding: '10px 12px', fontSize: 14, fontFamily: 'inherit', color: '#101828', outline: 'none' }}
          />
          <button className="btn btn-primary" type="button" disabled={busy === 'byo-save'} onClick={saveByo}>
            {busy === 'byo-save' ? 'Saving…' : (byo.configured ? 'Replace key' : 'Save key')}
          </button>
          {byo.configured && (
            <button className="btn btn-secondary" type="button" disabled={busy === 'byo-remove'} onClick={removeByo}>
              {busy === 'byo-remove' ? 'Removing…' : 'Remove'}
            </button>
          )}
        </div>
        <p style={{ color: '#98a2b3', fontSize: 12, margin: '12px 0 0' }}>
          Stored encrypted. We only ever show the last 4 characters.
        </p>
      </div>

      {/* Add capacity to your plan (add-ons) */}
      {!isExpired && (
        <div style={card}>
          <div style={{ fontSize: 16, fontWeight: 700, color: '#101828' }}>Add capacity to your plan</div>
          <p style={{ color: '#667085', margin: '6px 0 18px', maxWidth: 540 }}>
            Need more room? Add extra AI messages or websites on top of your current plan. Your base plan stays exactly the same, and this is billed separately.
          </p>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: 16 }}>
            {ADDONS.map(addon => (
              <div key={addon.code} style={{ border: '1px solid #e6e8ee', borderRadius: 14, padding: 20, display: 'flex', flexDirection: 'column' }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
                    <span style={{ fontSize: 16, fontWeight: 700, color: '#101828' }}>{addon.title}</span>
                    {Number((overview.addons || {})[addon.code] || 0) > 0 && (
                      <span style={{ background: '#ecfdf3', color: '#027a48', fontSize: 11, fontWeight: 700, padding: '3px 9px', borderRadius: 999, textTransform: 'uppercase', letterSpacing: '.03em', whiteSpace: 'nowrap' }}>Active</span>
                    )}
                  </div>
                  <div style={{ fontWeight: 700, color: '#101828', whiteSpace: 'nowrap' }}>
                    {addon.price}<span style={{ color: '#667085', fontWeight: 500, fontSize: 13 }}>{addon.unit}</span>
                  </div>
                </div>
                <p style={{ color: '#667085', fontSize: 13.5, lineHeight: 1.55, margin: '8px 0 16px', flex: 1 }}>{addon.desc}</p>
                <button
                  className="btn btn-primary"
                  type="button"
                  disabled={Boolean(busy)}
                  onClick={() => buyAddon(addon.code)}
                  style={{ alignSelf: 'flex-start' }}
                >
                  {busy === `addon-${addon.code}` ? 'Opening…' : (Number((overview.addons || {})[addon.code] || 0) > 0 ? 'Add more' : 'Add to plan')}
                </button>
              </div>
            ))}
          </div>
        </div>
      )}

      {!hasSub && (
        <div style={card}>
          <strong style={{ color: '#101828' }}>No active paid subscription</strong>
          <p style={{ color: '#667085', margin: '8px 0 16px' }}>
            Choose a plan to unlock full access, integrations and higher limits.
          </p>
          <a className="btn btn-primary" href={PRICING_URL}>
            <Icon name="arrowRight" size={15}/> Choose a plan
          </a>
        </div>
      )}

      {hasSub && (
        <React.Fragment>
          {/* Auto-renew */}
          <div style={card}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 16 }}>
              <div>
                <div style={{ fontSize: 16, fontWeight: 700, color: '#101828' }}>Automatic renewal</div>
                <p style={{ color: '#667085', margin: '6px 0 0', maxWidth: 460 }}>
                  {overview.autoRenew
                    ? 'Your plan renews automatically and payment is taken at the end of each period.'
                    : 'Auto-renew is off. Your access continues until the current period ends, then stops.'}
                </p>
              </div>
              <button
                type="button"
                onClick={toggleAutoRenew}
                disabled={busy === 'autoRenew'}
                aria-pressed={overview.autoRenew}
                style={{
                  flexShrink: 0,
                  width: 52,
                  height: 30,
                  borderRadius: 999,
                  border: 'none',
                  cursor: busy === 'autoRenew' ? 'wait' : 'pointer',
                  background: overview.autoRenew ? '#12b76a' : '#d0d5dd',
                  position: 'relative',
                  transition: 'background .15s ease',
                }}
              >
                <span style={{
                  position: 'absolute',
                  top: 3,
                  left: overview.autoRenew ? 25 : 3,
                  width: 24,
                  height: 24,
                  borderRadius: '50%',
                  background: '#fff',
                  transition: 'left .15s ease',
                  boxShadow: '0 1px 2px rgba(16,24,40,.2)',
                }}/>
              </button>
            </div>
          </div>

          {/* Change plan */}
          <div style={card}>
            <div style={{ fontSize: 16, fontWeight: 700, color: '#101828' }}>Change plan</div>
            <p style={{ color: '#667085', margin: '6px 0 16px' }}>
              Choose a plan below. You only pay the prorated difference today. Unused balance from your current plan is credited automatically.
            </p>

            <style>{`
              .pc-plan-grid { display: grid; gap: 18px; margin-bottom: 18px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
              .pc-plan-tiers { display: grid; gap: 10px; grid-template-columns: 1fr; }
              @media (max-width: 760px) { .pc-plan-grid { grid-template-columns: 1fr; } }
            `}</style>
            <div className="pc-plan-grid">
              {['starter', 'growth', 'business'].map(plan => (
                <div key={plan} className={`pc-plan pc-plan--${plan}`}>
                  <div style={{ fontSize: 14, fontWeight: 700, color: '#101828', marginBottom: 10 }}>{BILLING_PLAN_NAMES[plan]}</div>
                  <div className="pc-plan-tiers">
                    {(BILLING_PLAN_TIERS[plan] || []).map(tier => {
                      const isCurrent = (overview.planCode === plan || (overview.planCode === 'start' && plan === 'starter') || (overview.planCode === 'standard' && plan === 'growth'));
                      const isSelected = selPlan === plan && Number(selWebsites) === tier.websites;
                      return (
                        <button
                          key={tier.websites}
                          type="button"
                          onClick={() => { setSelPlan(plan); setSelWebsites(tier.websites); }}
                          style={{
                            textAlign: 'left',
                            border: isSelected ? '2px solid #d92d20' : '1px solid #e6e8ee',
                            background: isSelected ? '#fef3f2' : '#fff',
                            borderRadius: 12,
                            padding: '12px 14px',
                            cursor: 'pointer',
                            transition: 'border-color .12s ease, background .12s ease',
                          }}
                        >
                          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
                            <strong style={{ fontSize: 16, color: '#101828' }}>
                              ${tier.price.toLocaleString('en-US')}<span style={{ fontSize: 12, color: '#667085', fontWeight: 500 }}>/mo</span>
                            </strong>
                            {isCurrent && (
                              <span style={{ fontSize: 10, fontWeight: 700, color: '#027a48', background: '#ecfdf3', padding: '2px 7px', borderRadius: 999 }}>
                                CURRENT
                              </span>
                            )}
                          </div>
                          <div style={{ fontSize: 12, color: '#667085', marginTop: 3 }}>
                            1 website / {tier.messages.toLocaleString('en-US')} msgs
                          </div>
                        </button>
                      );
                    })}
                  </div>
                </div>
              ))}
            </div>

            {selectedTier && (
              <div style={{
                background: '#f9fafb', border: '1px solid #eaecf0', borderRadius: 12, padding: '18px 20px', marginBottom: 16,
              }}>
                {/* Header: plan name + capacity */}
                <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 16, paddingBottom: 14, borderBottom: '1px solid #eaecf0' }}>
                  <div style={{ fontSize: 17, fontWeight: 700, color: '#101828' }}>{BILLING_PLAN_NAMES[selPlan]}</div>
                  <div style={{ color: '#667085', fontSize: 13 }}>
                    1 website / {selectedTier.messages.toLocaleString('en-US')} msgs/mo
                  </div>
                </div>

                {previewLoading && (
                  <div style={{ paddingTop: 14, color: '#667085', fontSize: 13 }}>Calculating what you owe today…</div>
                )}

                {!previewLoading && preview && preview.unchanged && (
                  <div style={{ paddingTop: 14, color: '#667085', fontSize: 13 }}>This is your current plan.</div>
                )}

                {!previewLoading && preview && !preview.unchanged && (
                  <div style={{ paddingTop: 14 }}>
                    {/* Monthly going forward */}
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, marginBottom: 10 }}>
                      <span style={{ color: '#475467', fontSize: 14 }}>
                        Monthly{renewalDate ? ` from ${renewalDate}` : ''}
                      </span>
                      <span style={{ color: '#344054', fontSize: 15, fontWeight: 600 }}>
                        ${selectedTier.price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
                      </span>
                    </div>

                    {/* Amount due today (prorated) */}
                    {preview.amountDue !== null && preview.amountDue !== undefined && (
                      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, paddingTop: 10, borderTop: '1px dashed #e4e7ec' }}>
                        <span style={{ color: '#101828', fontSize: 15, fontWeight: 700 }}>
                          {preview.amountDue < 0 ? 'Credit to your account' : 'Amount due today'}
                          {preview.amountDue !== 0 && <span style={{ color: '#98a2b3', fontSize: 12, fontWeight: 500 }}> (prorated)</span>}
                        </span>
                        <span style={{ fontSize: 20, fontWeight: 700, color: preview.amountDue > 0 ? '#101828' : '#027a48' }}>
                          ${Math.abs(preview.amountDue).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
                        </span>
                      </div>
                    )}

                    <p style={{ margin: '10px 0 0', color: '#98a2b3', fontSize: 12 }}>
                      Unused balance from your current plan is credited automatically. You confirm and pay securely on Stripe.
                    </p>
                  </div>
                )}
              </div>
            )}

            <button
              className="btn btn-primary"
              type="button"
              onClick={updatePlan}
              disabled={busy === 'update' || (preview && preview.unchanged)}
            >
              {busy === 'update' ? 'Opening Stripe…' : 'Continue to Stripe'}
            </button>
          </div>

          {/* Payment history + portal */}
          <div style={card}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16, flexWrap: 'wrap', marginBottom: 16 }}>
              <div>
                <div style={{ fontSize: 16, fontWeight: 700, color: '#101828' }}>Payment &amp; invoices</div>
                <p style={{ color: '#667085', margin: '6px 0 0', maxWidth: 560 }}>
                  Your payment history. Download or view any invoice as a PDF, or open the Stripe portal to update your card.
                </p>
              </div>
              <button className="btn btn-secondary" type="button" onClick={openPortal} disabled={busy === 'portal'} style={{ flexShrink: 0 }}>
                <Icon name="creditCard" size={15}/> {busy === 'portal' ? 'Opening…' : 'Manage billing'}
              </button>
            </div>

            {invoicesLoading && (
              <div style={{ color: '#667085', fontSize: 13, marginBottom: 16 }}>Loading payment history…</div>
            )}

            {!invoicesLoading && invoices.length === 0 && (
              <div style={{ color: '#667085', fontSize: 13, marginBottom: 16 }}>No invoices yet.</div>
            )}

            {!invoicesLoading && invoices.length > 0 && (
              <div style={{ marginBottom: 18 }}>
                {invoices.map(inv => (
                  <div key={inv.id} style={{ padding: '14px 0', borderTop: '1px solid #eaecf0' }}>
                    {/* Top row: date + plan on the left, amount + status on the right */}
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, flexWrap: 'wrap' }}>
                      <div style={{ minWidth: 0 }}>
                        <div style={{ color: '#101828', fontWeight: 600, fontSize: 14 }}>{formatBillingDate(inv.date) || '—'}</div>
                        <div style={{ color: '#667085', fontSize: 13 }}>{inv.description}</div>
                      </div>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexShrink: 0 }}>
                        <span style={{ color: '#101828', fontWeight: 700, fontSize: 15 }}>
                          ${Number(inv.amount).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
                        </span>
                        <span style={{
                          fontSize: 11, fontWeight: 700, textTransform: 'capitalize',
                          padding: '2px 8px', borderRadius: 999,
                          background: inv.status === 'paid' ? '#ecfdf3' : '#fef3f2',
                          color: inv.status === 'paid' ? '#027a48' : '#b42318',
                        }}>
                          {inv.status || 'open'}
                        </span>
                      </div>
                    </div>

                    {/* Actions row: always visible, wraps on mobile */}
                    {(inv.pdfUrl || inv.hostedUrl) && (
                      <div style={{ display: 'flex', gap: 18, marginTop: 10, flexWrap: 'wrap' }}>
                        {inv.pdfUrl && (
                          <a href={inv.pdfUrl} target="_blank" rel="noopener noreferrer"
                            style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: '#d92d20', fontWeight: 600, fontSize: 14, textDecoration: 'none' }}>
                            <Icon name="download" size={14}/> Download PDF
                          </a>
                        )}
                        {inv.hostedUrl && (
                          <a href={inv.hostedUrl} target="_blank" rel="noopener noreferrer"
                            style={{ display: 'inline-flex', alignItems: 'center', gap: 6, color: '#475467', fontWeight: 600, fontSize: 14, textDecoration: 'none' }}>
                            <Icon name="externalLink" size={14}/> View
                          </a>
                        )}
                      </div>
                    )}
                  </div>
                ))}
              </div>
            )}
          </div>
        </React.Fragment>
      )}
    </div>
  );
}

window.BillingPage = BillingPage;
