// DISCOUNT TOAST NOTIFICATIONS
// Fires a toast (same chrome as the existing waitlist toast, blue accent
// instead of orange) when the user activates any discount. Six discount
// types are watched:
//   1. Multi-service (2+ services in cart, tiered 5/15/25/35/45%)
//   2. Commitment (6mo = -20%, 12mo = -40% per service)
//   3. Pay upfront (-10% per service when toggled on)
//   4. Bring Your Own List addon (-15% retainer per service)
//   5. Promo code (variable %, applied via the discount-code input)
//   6. Agency partner intent (-15% own-agency / -40% white-label, all tiers)
//
// Detection: maintains a useRef of "previous discount state" and compares on
// each render. A toast fires when a discount transitions from inactive to
// active OR upgrades to a higher band (e.g. 6mo to 12mo).
//
// Hydration guard: silently snapshots state during the first 1500ms after
// mount. Without this, every page refresh fires toasts for ALL currently-
// active discounts (because the initial render looks like a "fresh
// activation" against the default-empty prev state).
//
// Toasts are stackable and dismiss on X-click only.

(function () {
  function MsdTier(serviceCount) {
    // 2026-05-30: aligned to the live two-tier rule (2 services = 5%, 3+ = 10%).
    if (serviceCount >= 3) return { n: 3, pct: 10, label: '3 or more services' };
    if (serviceCount === 2) return { n: 2, pct: 5, label: '2 services' };
    return { n: 0, pct: 0, label: '' };
  }

  // 2026-06-03 (Loom 18 7:06): count only services that actually qualify for the
  // accountability discount, so the toast never fires for Dedicated Resources
  // (discountExcluded staffing) or one-off services.
  // 2026-06-18 (Nicole): count from the live quote lines so the toast matches the
  // sidebar exactly. Waitlisted services (GBP0, no discount applied) are excluded,
  // just like the accountability-discount math, so the toast never claims a
  // discount the sidebar does not apply.
  function qualifyingServiceCount() {
    const q = (typeof window !== 'undefined' && window.__currentQuote) || {};
    const lines = Array.isArray(q.lines) ? q.lines : [];
    const sids = new Set();
    lines.forEach(l => {
      if (!l || l.addon || l.setupFee || l.waitlist || l.oneTime) return;
      if (l.custom && !l.role) return;
      if (!l.sid) return;
      sids.add(l.sid);
    });
    return sids.size;
  }
  // Distinct services on the waiting list (would qualify once onboarded).
  function waitlistedServiceCount() {
    const q = (typeof window !== 'undefined' && window.__currentQuote) || {};
    const lines = Array.isArray(q.lines) ? q.lines : [];
    const sids = new Set();
    lines.forEach(l => {
      if (!l || !l.waitlist || l.addon || l.setupFee || l.oneTime || !l.sid) return;
      sids.add(l.sid);
    });
    return sids.size;
  }

  function svcName(svcId) {
    const svc = (window.SERVICES || []).find(s => s.id === svcId);
    return svc ? svc.name : svcId;
  }

  function commitPctFor(commitMonths) {
    if (commitMonths === 12) return 40;
    if (commitMonths === 6) return 20;
    return 0;
  }

  function DiscountToastManager({ state }) {
    const { useRef, useState, useEffect } = React;
    const [toasts, setToasts] = useState([]);
    const prev = useRef({
      msdTier: 0,
      commitByService: {},
      payUpfrontByService: {},
      byolByService: {},
      promoCode: null,
      agencyMult: 1,
      intentId: null,
      ready: false,
      mountedAt: Date.now(),
    });
    /* 2026-05-29: track auto-dismiss timers so we can clear them on
       unmount (avoids "setState on unmounted component" warnings if the
       host page navigates away within 3s of a toast firing). */
    const dismissTimers = useRef([]);

    /* 2026-05-29 v2: two-step dismiss. Mark `leaving: true` first so the
       CSS exit animation (opacity → 0, slide right) can play; then 320ms
       later actually remove the toast from the array. Reused by both
       the auto-dismiss timer and the X-click handler. */
    const beginLeaving = (id) => {
      setToasts(t => t.map(x => x.id === id ? { ...x, leaving: true } : x));
      const removeTimer = setTimeout(() => {
        setToasts(t => t.filter(x => x.id !== id));
      }, 320);
      dismissTimers.current.push(removeTimer);
    };

    /* Public dismiss: alias for beginLeaving (kept as `dismiss` so the
       existing close-button onClick keeps working unchanged). */
    const dismiss = (id) => beginLeaving(id);

    const push = (title, body, meta) => {
      const id = Date.now() + Math.random();
      setToasts(t => {
        // De-dupe identical title within the current visible stack
        if (t.some(x => x.title === title)) return t;
        return [...t, { id, title, body, meta, leaving: false }];
      });
      /* 2026-05-29 v2: auto-dismiss after 5s (was 3s). Scheduled
         unconditionally — if the dedup guard rejected the push, the
         timer fires a harmless no-op since no toast matches the new id. */
      const timer = setTimeout(() => beginLeaving(id), 5000);
      dismissTimers.current.push(timer);
    };

    /* Cleanup any pending auto-dismiss + remove timers on unmount. */
    useEffect(() => {
      return () => {
        dismissTimers.current.forEach(clearTimeout);
        dismissTimers.current = [];
      };
    }, []);

    /* Referral-applied toast: fired by the referral modal via a window event
       window.dispatchEvent(new CustomEvent('gg:referral-applied', { detail:{title,body} })).
       Reuses the same toast chrome as the discount toasts. */
    useEffect(() => {
      const onReferral = (e) => {
        const det = (e && e.detail) || {};
        push(det.title || 'Referral applied', det.body || 'Your discount will be waiting at checkout.');
      };
      window.addEventListener('gg:referral-applied', onReferral);
      return () => window.removeEventListener('gg:referral-applied', onReferral);
    }, []);

    useEffect(() => {
      if (!state || !state.selections) return;
      const selections = state.selections || {};
      const svcIds = Object.keys(selections);
      const p = prev.current;

      // Hydration guard: any state change within the first 1500ms after mount
      // is treated as initial hydration burst (localStorage rehydration +
      // StrictMode double-effects) and snapshots silently without firing.
      const elapsedSinceMount = Date.now() - p.mountedAt;
      if (!p.ready || elapsedSinceMount < 1500) {
        p.msdTier = MsdTier(qualifyingServiceCount()).n;
        p.msdPendingShown = MsdTier(qualifyingServiceCount()).pct === 0 && MsdTier(qualifyingServiceCount() + waitlistedServiceCount()).pct > 0;
        for (const sid of svcIds) {
          const sel = selections[sid];
          if (!sel) continue;
          p.commitByService[sid] = sel.commitMonths || 3;
          p.payUpfrontByService[sid] = !!sel.payUpfront;
          p.byolByService[sid] = Array.isArray(sel.addons) && sel.addons.includes('byol');
        }
        p.promoCode = state.promoApplied?.code || null;
        p.agencyMult = window.getAgencyMultiplier ? window.getAgencyMultiplier(state) : 1;
        p.ready = true;
        return;
      }

      // 1. Multi-service tier (counts only services actually on the total)
      const _paidSvc = qualifyingServiceCount();
      const msd = MsdTier(_paidSvc);
      if (msd.n > p.msdTier && msd.pct > 0) {
        push(
          'Accountability discount unlocked',
          msd.pct + '% off all service retainers for ' + msd.label + ' in your plan.',
          'Auto-applied to your monthly total.'
        );
      }
      p.msdTier = msd.n;
      // 1b. 2026-06-18 (Nicole): if the discount does not apply yet only because a
      // qualifying service is on the waiting list, tell them it will apply once
      // onboarded - instead of claiming a discount that is not on the total.
      const _wlSvc = waitlistedServiceCount();
      const _potential = MsdTier(_paidSvc + _wlSvc);
      const _msdPending = msd.pct === 0 && _potential.pct > 0;
      if (_msdPending && !p.msdPendingShown) {
        push(
          'Accountability discount on the way',
          _potential.pct + '% off your service retainers will apply once your waiting-list ' + (_wlSvc > 1 ? 'services are' : 'service is') + ' onboarded.',
          'Nothing to do now, we will add it automatically.'
        );
      }
      p.msdPendingShown = _msdPending;

      // 2. Commitment per service
      for (const sid of svcIds) {
        const sel = selections[sid];
        if (!sel) continue;
        const cur = sel.commitMonths || 3;
        const prevCommit = p.commitByService[sid] || 3;
        const curPct = commitPctFor(cur);
        const prevPct = commitPctFor(prevCommit);
        if (curPct > prevPct && curPct > 0) {
          push(
            cur + '-month commitment locked in',
            curPct + '% off ' + svcName(sid) + ' for the length of your contract.',
            'Applies until the commitment ends.'
          );
        }
        p.commitByService[sid] = cur;
      }

      // 3. Pay upfront per service
      for (const sid of svcIds) {
        const sel = selections[sid];
        if (!sel) continue;
        const cur = !!sel.payUpfront;
        const wasPrev = !!p.payUpfrontByService[sid];
        if (cur && !wasPrev) {
          push(
            'Pay upfront discount applied',
            '10% off ' + svcName(sid) + ' when you pay your contract upfront.',
            'Toggle off in the service card to revert.'
          );
        }
        p.payUpfrontByService[sid] = cur;
      }

      // 4. BYOL addon per service
      for (const sid of svcIds) {
        const sel = selections[sid];
        if (!sel || !Array.isArray(sel.addons)) continue;
        const hasByol = sel.addons.includes('byol');
        const hadByol = !!p.byolByService[sid];
        if (hasByol && !hadByol) {
          push(
            'Bring Your Own List discount applied',
            '15% off the ' + svcName(sid) + ' retainer when you supply a verified prospect list.',
            'We still enrich, copywrite, and run the cadence.'
          );
        }
        p.byolByService[sid] = hasByol;
      }

      // 5. Promo code
      const curCode = state.promoApplied?.code || null;
      const curTopUp = state.promoApplied?.topUpAgencyTo || 0;
      const curPct = state.promoApplied?.pct || 0;
      if (curCode && curCode !== p.promoCode) {
        push(
          'Promo code applied',
          (curTopUp
            ? 'Your ' + ((window.isFreelancerMode && window.isFreelancerMode()) ? 'partner' : 'agency') + ' rate lifts to ' + curTopUp + '% off for this order with promo ' + curCode + '.'
            : curPct + '% off your monthly total with promo ' + curCode + '.'),
          'Remove with the X next to the promo chip in your summary.'
        );
      }
      p.promoCode = curCode;

      // 6. Agency multiplier
      const agyMult = window.getAgencyMultiplier ? window.getAgencyMultiplier(state) : 1;
      if (agyMult < 1 && agyMult !== p.agencyMult) {
        const off = Math.round((1 - agyMult) * 100);
        const isWl = state.intentId === 'agency-whitelabel';
        // 2026-06-12 (Loom 44 10:48): investors were seeing the agency label,
        // the toast now names the client type behind the multiplier (15%).
        const _isInv = state.clientTypeId === 'investor';
        push(
          (isWl ? 'White-label' : (_isInv ? 'Investor' : ((window.isFreelancerMode && window.isFreelancerMode()) ? 'Partner' : 'Agency partner'))) + ' discount applied',
          off + '% off every service tier, automatically.',
          'Reflected on every line in your summary.'
        );
      }
      p.agencyMult = agyMult;

      // 7. Growing My Agency path selected
      const curIntent = state.intentId || null;
      if (curIntent === 'agency-own' && p.intentId !== 'agency-own') {
        push(
          (window.isFreelancerMode && window.isFreelancerMode()) ? 'Growing my business path selected' : 'Growing My Agency path selected',
          (window.isFreelancerMode && window.isFreelancerMode()) ? 'On this path, we offer Sales & Demand Generation and Talent Solutions. These are the services that work best for growing your business.' : 'On this path, we offer Sales & Demand Generation and Talent Solutions. These are the services that work best for agency growth.',
          null
        );
      }
      p.intentId = curIntent;
    }, [
      state?.selections,
      state?.promoApplied,
      state?.intentId,
      state?.clientTypeId,
    ]);

    if (!toasts.length) return null;
    return React.createElement(
      'div',
      { className: 'discount-toast-stack', 'aria-live': 'polite' },
      toasts.map(t =>
        React.createElement(
          'div',
          {
            key: t.id,
            className: 'discount-toast' + (t.leaving ? ' discount-toast--leaving' : ''),
            role: 'status',
          },
          React.createElement(
            'span',
            { className: 'discount-toast__icon', 'aria-hidden': 'true' },
            React.createElement(
              'svg',
              { viewBox: '0 0 24 24', width: 18, height: 18, fill: 'none', stroke: 'currentColor',
                strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
              React.createElement('path', { d: 'M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z' }),
              React.createElement('line', { x1: 7, y1: 7, x2: 7.01, y2: 7 })
            )
          ),
          React.createElement(
            'div',
            { className: 'discount-toast__body' },
            React.createElement('div', { className: 'discount-toast__title' }, t.title),
            React.createElement('div', { className: 'discount-toast__copy' }, t.body),
            t.meta && React.createElement('div', { className: 'discount-toast__meta' }, t.meta)
          ),
          React.createElement(
            'button',
            {
              type: 'button',
              className: 'discount-toast__close',
              onClick: () => dismiss(t.id),
              'aria-label': 'Dismiss',
            },
            React.createElement(
              'svg',
              { viewBox: '0 0 24 24', width: 14, height: 14, fill: 'none', stroke: 'currentColor',
                strokeWidth: 2.4, strokeLinecap: 'round', strokeLinejoin: 'round' },
              React.createElement('line', { x1: 18, y1: 6, x2: 6, y2: 18 }),
              React.createElement('line', { x1: 6, y1: 6, x2: 18, y2: 18 })
            )
          )
        )
      )
    );
  }

  window.DiscountToastManager = DiscountToastManager;
})();
