/* ═══════════════════════════════════════════════════════════════════
   WizardRunner.jsx — the config-driven step ENGINE (WPSB.Wizard.run).
   ───────────────────────────────────────────────────────────────────
   WizardShell.jsx is chrome only. ImageBgRemoveWizard inlines its own
   state machine. Per _FILE-MANAGER-DUAL-PATH-AND-WIZARD-SPEC.md the
   Convert + Organize paths must be CONFIGS of one container — so this
   file is the engine the spec assumed existed: state, validated nav,
   plan-aware step sets, locked-CTA-as-upsell, deferred atomic commit,
   per-step telemetry. Steps render into WizardShell.

   Load AFTER WizardIcons.jsx + WizardPrimitives.jsx + WizardShell.jsx.
   Registers: window.WPSB.Wizard.run(config)  ->  React element
              window.WPSB.Wizard.Runner       ->  <Runner config=.../>

   config = {
     id, eyebrow, title, subtitle,
     stepVariant: 'vertical'|'horizontal'|'numbered',   // default 'vertical'
     plan,                          // current plan key (for locked()/includeIf())
     initialData: {},               // the wizard data bag
     finishLabel,                   // default 'Finish'
     steps: [{
       key, title, subtitle,
       optional,                    // bool -> shows "Skip for now"
       includeIf: (ctx)=>bool,      // dynamic inclusion (plan-aware step sets)
       locked:    (ctx)=> falsy | { tag, reason, cta:{label,kind}, onCta },
       validate:  (ctx)=> true | 'error string',   // gate Next/Finish
       render:    (ctx)=> ReactNode,
       nextLabel, finishLabel,
     }],
     onCommit:    async (data, helpers)=> result,   // deferred atomic commit at Finish
     onStepChange:(fromIdx,toIdx,ctx)=>{},          // optional
     telemetry:   (event,payload)=>{},              // optional sink (defaults to analytics)
     onExit:      ()=>{},                           // cancel/close
     confirmExitIfDirty,                            // bool (default true)
   }

   ctx (passed to render/validate/locked/includeIf):
     { data, setData, patch, goNext, goBack, goTo, current, stepKey,
       steps, isLast, plan, error, setError, committing, commitError,
       result, close }

   Pattern: IIFE, React.createElement (pure ASCII), window.WPSB.* + globals.
   ═══════════════════════════════════════════════════════════════════ */
(function () {
  const { useState, useMemo, useCallback, useRef } = React;
  const h = React.createElement;
  const tx = (k, v, f) => (window.WPSB && window.WPSB.t) ? window.WPSB.t(k, v, f) : (f != null ? f : k);

  const WizardShell = window.WizardShell;
  const NoticeCard  = window.NoticeCard;
  const WIcon       = window.WIcon;

  /* fire-and-forget analytics default — never throws, never blocks */
  function defaultTelemetry(event, payload) {
    try {
      const RAILWAY = (window.WPSB_CONFIG && window.WPSB_CONFIG.RAILWAY_URL)
        || 'https://wpsitebeam-railway-api-production.up.railway.app';
      const body = JSON.stringify(Object.assign({ type: event }, payload || {}));
      const send = (url, init) => {
        if (window.WPSB_Auth && typeof window.WPSB_Auth.authedFetch === 'function') {
          window.WPSB_Auth.authedFetch(url, init).catch(function () {});
        } else {
          const t = (window.WPSB && window.WPSB.getToken && window.WPSB.getToken()) || null;
          const headers = { 'Content-Type': 'application/json' };
          if (t) headers['Authorization'] = 'Bearer ' + t;
          fetch(url, Object.assign({ headers }, init)).catch(function () {});
        }
      };
      send(RAILWAY + '/events/track', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body });
    } catch (_) { /* analytics is best-effort */ }
  }

  /* ── Locked-step body: upsell, never a dead wall ─────────────────── */
  function LockedStepBody({ lock }) {
    const cta = lock && lock.cta;
    return h(NoticeCard, {
      accent: (cta && cta.kind === 'addon') ? 'var(--wiz-upsell)' : 'var(--beam)',
      icon: 'lock',
      title: lock && lock.title ? lock.title : tx('page.wizardrunner.locked_title', null, 'Available on a higher plan'),
      body: (lock && lock.reason) || tx('page.wizardrunner.locked_body', null, 'This step is part of a plan you are not on yet. Everything else in this wizard still runs — this step is simply skipped.'),
      tag: lock && lock.tag,
      cta: cta ? { label: cta.label, icon: cta.kind === 'addon' ? 'rocket' : 'arrow-up-right', onClick: lock.onCta || (function () {}) } : undefined,
    });
  }

  /* ── Error banner (validation) ───────────────────────────────────── */
  function ErrorBanner({ message }) {
    if (!message) return null;
    return h('div', {
      role: 'alert',
      style: {
        display: 'flex', alignItems: 'flex-start', gap: 8, marginBottom: 'var(--sp-4)',
        padding: '10px 12px', borderRadius: 'var(--r)', fontSize: '.82rem', lineHeight: 1.45,
        background: 'color-mix(in oklch, var(--danger, var(--red)) 10%, var(--surface))',
        border: '1px solid color-mix(in oklch, var(--danger, var(--red)) 32%, transparent)',
        color: 'var(--text)',
      },
    },
      WIcon ? h(WIcon, { name: 'alert-triangle', size: 16, style: { color: 'var(--danger, var(--red))', flex: 'none', marginTop: 1 } }) : null,
      h('span', null, message)
    );
  }

  /* ── Committing veil ─────────────────────────────────────────────── */
  function CommitVeil({ label }) {
    return h('div', {
      style: {
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
        padding: 'var(--sp-6)', borderRadius: 'var(--r-lg)', marginBottom: 'var(--sp-4)',
        background: 'var(--surface-2)', border: '1px solid var(--border)', color: 'var(--text-2)', fontSize: '.86rem',
      },
    },
      h('span', { className: 'wpsb-spin', style: { width: 16, height: 16, borderRadius: '50%', border: '2px solid var(--border-2)', borderTopColor: 'var(--beam)', display: 'inline-block', animation: 'wpsb-spin 700ms linear infinite' } }),
      h('span', null, label || tx('page.wizardrunner.working', null, 'Working…'))
    );
  }

  /* ═══════════════════════════════════════════════════════════════
     The Runner
     ═══════════════════════════════════════════════════════════════ */
  function Runner({ config }) {
    const cfg = config || {};
    const telemetry = cfg.telemetry || defaultTelemetry;

    const [data, setDataState] = useState(cfg.initialData || {});
    const [currentKey, setCurrentKey] = useState(null);   // track by KEY (survives step-set changes)
    const [maxKey, setMaxKey] = useState(null);
    const [error, setError] = useState(null);
    const [committing, setCommitting] = useState(false);
    const [commitError, setCommitError] = useState(null);
    const [result, setResult] = useState(null);
    const dirtyRef = useRef(false);

    const setData = useCallback(function (updater) {
      dirtyRef.current = true;
      setDataState(function (prev) { return typeof updater === 'function' ? updater(prev) : updater; });
    }, []);
    const patch = useCallback(function (partial) {
      dirtyRef.current = true;
      setDataState(function (prev) { return Object.assign({}, prev, partial); });
    }, []);

    /* base ctx for predicate evaluation (no nav fns needed by predicates) */
    const predCtx = { data: data, plan: cfg.plan, result: result };

    /* filter steps by includeIf, in declared order */
    const steps = useMemo(function () {
      return (cfg.steps || []).filter(function (s) {
        if (typeof s.includeIf !== 'function') return true;
        try { return !!s.includeIf(predCtx); } catch (_) { return true; }
      });
      // eslint-disable-next-line
    }, [cfg.steps, data, cfg.plan, result]);

    /* resolve current index from currentKey, clamped to the live step list */
    const safeSteps = steps.length ? steps : [{ key: '__empty', title: '—', render: function () { return null; } }];
    let current = 0;
    if (currentKey != null) {
      const idx = safeSteps.findIndex(function (s) { return s.key === currentKey; });
      current = idx >= 0 ? idx : Math.min(safeSteps.length - 1, 0);
    }
    let maxReached = current;
    if (maxKey != null) {
      const mi = safeSteps.findIndex(function (s) { return s.key === maxKey; });
      maxReached = mi >= 0 ? Math.max(mi, current) : current;
    }

    const step = safeSteps[current] || safeSteps[0];
    const isLast = current === safeSteps.length - 1;

    const lock = useMemo(function () {
      if (typeof step.locked !== 'function') return null;
      try { const r = step.locked(predCtx); return r || null; } catch (_) { return null; }
    }, [step, data, cfg.plan, result]);

    /* nav primitives */
    const goTo = useCallback(function (i) {
      const target = safeSteps[i];
      if (!target) return;
      setError(null);
      const fromIdx = current;
      setCurrentKey(target.key);
      setMaxKey(function (mk) {
        const mi = mk == null ? -1 : safeSteps.findIndex(function (s) { return s.key === mk; });
        return i > mi ? target.key : mk;
      });
      try { cfg.onStepChange && cfg.onStepChange(fromIdx, i, predCtx); } catch (_) {}
      telemetry('wizard_step', { wizard_id: cfg.id, step_key: target.key, step_index: i });
      // eslint-disable-next-line
    }, [safeSteps, current, cfg.id]);

    const goBack = useCallback(function () { if (current > 0) goTo(current - 1); }, [current, goTo]);

    const validateCurrent = useCallback(function () {
      if (lock) return true;                 // locked steps never block (upsell, not wall)
      if (typeof step.validate !== 'function') return true;
      let v;
      try { v = step.validate({ data: data, plan: cfg.plan, result: result }); } catch (e) { v = (e && e.message) || tx('page.wizardrunner.validation_error', null, 'Validation error.'); }
      if (v === true || v === undefined || v === null) return true;
      setError(typeof v === 'string' ? v : tx('page.wizardrunner.validation_incomplete', null, 'Please complete this step before continuing.'));
      return false;
    }, [step, lock, data, cfg.plan, result]);

    const close = useCallback(function () {
      if (cfg.confirmExitIfDirty !== false && dirtyRef.current && committing === false) {
        if (!window.confirm(tx('page.wizardrunner.confirm_discard', null, 'Discard this wizard and any unsaved choices?'))) return;
      }
      telemetry('wizard_exit', { wizard_id: cfg.id, step_key: step.key, step_index: current });
      cfg.onExit && cfg.onExit();
      // eslint-disable-next-line
    }, [cfg, step, current, committing]);

    const runCommit = useCallback(async function () {
      if (!cfg.onCommit) { telemetry('wizard_commit', { wizard_id: cfg.id, ok: true }); return; }
      setCommitError(null); setCommitting(true);
      const helpers = {
        setData: setData, patch: patch, goTo: goTo, close: close,
        toast: function (m, t) { if (window.wpsbToast) window.wpsbToast(m, t || 'info'); },
      };
      try {
        const r = await cfg.onCommit(data, helpers);
        setResult(r != null ? r : true);
        dirtyRef.current = false;
        telemetry('wizard_commit', { wizard_id: cfg.id, ok: true });
      } catch (e) {
        setCommitError((e && e.message) || tx('page.wizardrunner.commit_error', null, 'Something went wrong while finishing. Please try again.'));
        telemetry('wizard_commit', { wizard_id: cfg.id, ok: false, error: (e && e.message) || 'error' });
      } finally {
        setCommitting(false);
      }
      // eslint-disable-next-line
    }, [cfg, data, setData, patch, goTo, close]);

    const goNext = useCallback(function () {
      if (committing) return;
      if (!validateCurrent()) return;
      if (isLast) { runCommit(); return; }
      goTo(current + 1);
    }, [committing, validateCurrent, isLast, runCommit, goTo, current]);

    const onSkip = useCallback(function () {
      if (committing) return;
      setError(null);
      if (isLast) { runCommit(); return; }
      goTo(current + 1);
    }, [committing, isLast, runCommit, goTo, current]);

    /* shared ctx handed to render() */
    const ctx = {
      data: data, setData: setData, patch: patch,
      goNext: goNext, goBack: goBack, goTo: goTo, close: close,
      current: current, stepKey: step.key, steps: safeSteps, isLast: isLast,
      plan: cfg.plan, error: error, setError: setError,
      committing: committing, commitError: commitError, result: result,
    };

    /* shell step descriptors */
    const shellSteps = safeSteps.map(function (s) {
      let lk = null;
      if (typeof s.locked === 'function') { try { const r = s.locked(predCtx); if (r) lk = r.tag || 'Upgrade'; } catch (_) {} }
      return { key: s.key, title: s.title, subtitle: s.subtitle, locked: lk };
    });

    /* footer state */
    const footer = {
      onBack: goBack, onNext: goNext, onSkip: onSkip,
      onFinish: goNext,
      canBack: current > 0 && !committing,
      canSkip: !!step.optional && !committing,
      isLast: isLast,
      nextLabel: committing ? tx('page.wizardrunner.working', null, 'Working…') : (step.nextLabel || tx('page.wizardrunner.next', null, 'Next')),
      finishLabel: committing ? tx('page.wizardrunner.finishing', null, 'Finishing…') : (step.finishLabel || cfg.finishLabel || tx('page.wizardrunner.finish', null, 'Finish')),
    };

    /* body */
    const body = lock
      ? LockedStepBody({ lock: lock })
      : h(React.Fragment, null,
          committing ? h(CommitVeil, { label: step.finishLabel || tx('page.wizardrunner.finishing', null, 'Finishing…') }) : null,
          h(ErrorBanner, { message: commitError || error }),
          (function () { try { return step.render(ctx); } catch (e) { return h(ErrorBanner, { message: tx('page.wizardrunner.step_render_failed', { msg: (e && e.message) || 'error' }, 'This step failed to render: {msg}') }); } })()
        );

    return h(WizardShell, {
      eyebrow: cfg.eyebrow, title: cfg.title, subtitle: cfg.subtitle,
      steps: shellSteps, current: current, maxReached: maxReached,
      stepVariant: cfg.stepVariant || 'vertical',
      onStepClick: function (i) { if (i <= maxReached && !committing) goTo(i); },
      footer: footer,
    }, body);
  }

  /* run(config) -> element (the headline API the spec/roadmap reference) */
  function run(config) { return h(Runner, { config: config }); }

  /* spin keyframes (idempotent) */
  (function injectSpin() {
    if (document.getElementById('wpsb-wiz-spin')) return;
    const st = document.createElement('style');
    st.id = 'wpsb-wiz-spin';
    st.textContent = '@keyframes wpsb-spin{to{transform:rotate(360deg)}}';
    document.head.appendChild(st);
  })();

  window.WPSB = window.WPSB || {};
  window.WPSB.Wizard = Object.assign(window.WPSB.Wizard || {}, { run: run, Runner: Runner });
  window.WizardRunner = Runner;
  console.log('[WPSB Wizard] Runner loaded (config-driven engine: WPSB.Wizard.run).');
})();
