(function () {
  function $(id) {
    return document.getElementById(id);
  }

  async function postAjax(action, data) {
    const form = new FormData();
    form.append('action', action);
    Object.keys(data || {}).forEach((k) => {
      const v = data[k];
      if (Array.isArray(v)) {
        v.forEach((item) => form.append(k + '[]', item));
      } else {
        form.append(k, v);
      }
    });

    const res = await fetch((window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.ajaxUrl) || '', {
      method: 'POST',
      credentials: 'same-origin',
      body: form,
    });

    const json = await res.json().catch(() => null);
    if (!json) throw new Error('Bad JSON response');
    return json;
  }

  function readForm() {
    const form = document.getElementById('wpsn-schema-scan-form');
    if (!form) return { post_types: [], include_terms: false, taxonomies: [] };

    const postTypes = [];
    form.querySelectorAll('input[name="post_types[]"]:checked').forEach((el) => {
      postTypes.push(el.value);
    });

    const includeTerms = !!form.querySelector('input[name="include_terms"]:checked');

    const taxonomies = [];
    if (includeTerms) {
      form.querySelectorAll('input[name="taxonomies[]"]:checked').forEach((el) => {
        taxonomies.push(el.value);
      });
    }

    return { post_types: postTypes, include_terms: includeTerms, taxonomies: taxonomies };
  }

  function setProgress(pct) {
    const bar = $("wpsn-schema-scan-bar");
    if (!bar) return;
    const v = Math.max(0, Math.min(100, pct));
    bar.style.width = String(v) + '%';
  }

  function setStatus(msg) {
    const el = $("wpsn-schema-scan-status");
    if (el) el.textContent = msg || '';
  }

  function showProgress(show) {
    const wrap = $("wpsn-schema-scan-progress");
    const cancel = $("wpsn-schema-scan-cancel");
    if (wrap) wrap.style.display = show ? 'block' : 'none';
    if (cancel) cancel.style.display = show ? 'inline-block' : 'none';
  }

  let running = false;
  let cancelled = false;
  let session = '';
  let scanId = 0;
  let total = 0;
  let scanned = 0;

  async function startScan() {
    if (running) return;

    const btn = $("wpsn-schema-scan-start");
    const nonce = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.nonce) || '';
    if (!btn || !nonce) return;

    const payload = readForm();

    running = true;
    cancelled = false;
    session = '';
    scanId = 0;
    total = 0;
    scanned = 0;

    btn.disabled = true;
    showProgress(true);
    setProgress(0);
    setStatus((window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.starting) || 'Starting…');

    try {
      const resp = await postAjax('wpsn_schema_scan_start', {
        nonce,
        post_types: payload.post_types,
        include_terms: payload.include_terms ? '1' : '0',
        taxonomies: payload.taxonomies,
      });

      if (!resp || !resp.success) {
        throw new Error((resp && resp.data && (resp.data.message || resp.data)) || 'Start failed');
      }

      session = resp.data.session || '';
      scanId = resp.data.scan_id || 0;
      total = resp.data.total || 0;
      scanned = 0;

      if (!session || !scanId) {
        throw new Error('Missing session');
      }

      setStatus((window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.processing) || 'Processing…');
      await step();
    } catch (err) {
      running = false;
      btn.disabled = false;
      showProgress(false);
      setStatus(((window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.error) || 'Error') + ' ' + (err && err.message ? err.message : ''));
    }
  }

  async function step() {
    if (cancelled || !session) {
      finish(false, (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.cancelled) || 'Cancelled.');
      return;
    }

    const nonce = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.nonce) || '';
    const btn = $("wpsn-schema-scan-start");

    try {
      const resp = await postAjax('wpsn_schema_scan_step', {
        nonce,
        session,
        chunk: '20',
      });

      if (!resp || !resp.success) {
        throw new Error((resp && resp.data && (resp.data.message || resp.data)) || 'Step failed');
      }

      scanned = resp.data.scanned || scanned;
      total = resp.data.total || total;

      const pct = total > 0 ? Math.round((scanned / total) * 100) : 0;
      setProgress(pct);

      const stage = resp.data.stage || '';
      const issuesAdded = resp.data.issues_added || 0;
      const msg = 'Scanned ' + scanned + ' / ' + total + (stage ? ' • ' + stage : '') + (issuesAdded ? ' • +' + issuesAdded + ' issues' : '');
      setStatus(msg);

      if (resp.data.done) {
        finish(true, (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.done) || 'Done.');
        return;
      }

      setTimeout(step, 150);
    } catch (err) {
      running = false;
      if (btn) btn.disabled = false;
      showProgress(false);
      setStatus(((window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.error) || 'Error') + ' ' + (err && err.message ? err.message : ''));
    }
  }

  function finish(ok, message) {
    const btn = $("wpsn-schema-scan-start");
    running = false;
    if (btn) btn.disabled = false;

    if (message) {
      setStatus(message);
    }

    if (ok && scanId) {
      setTimeout(function () {
        const url = new URL(window.location.href);
        url.searchParams.set('scan_id', String(scanId));
        window.location.href = url.toString();
      }, 350);
      return;
    }

    showProgress(false);
  }

  async function cancelScan() {
    if (!running) return;
    cancelled = true;

    const nonce = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.nonce) || '';
    try {
      await postAjax('wpsn_schema_scan_cancel', { nonce });
    } catch (e) {
      // ignore
    }
  }

  async function clearAll() {
    const nonce = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.nonce) || '';
    const confirmText = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.confirmClear) || 'Clear all?';
    if (!nonce) return;
    // eslint-disable-next-line no-alert
    if (!confirm(confirmText)) return;

    try {
      const resp = await postAjax('wpsn_schema_clear_all', { nonce });
      if (!resp || !resp.success) {
        throw new Error('Clear failed');
      }
      window.location.reload();
    } catch (err) {
      // eslint-disable-next-line no-alert
      alert((err && err.message) || 'Error');
    }
  }

  function setupDetailsModal() {
    const modal = $("wpsn-schema-details-modal");
    if (!modal) return;

    const pre = modal.querySelector('pre');
    const closeBtn = modal.querySelector('.wpsn-sv-modal-close');

    function close() {
      modal.style.display = 'none';
      if (pre) pre.textContent = '';
    }

    if (closeBtn) {
      closeBtn.addEventListener('click', close);
    }

    modal.addEventListener('click', function (e) {
      if (e.target === modal) close();
    });

    document.addEventListener('click', function (e) {
      const btn = e.target && e.target.closest ? e.target.closest('.wpsn-sv-details-toggle') : null;
      if (!btn) return;

      e.preventDefault();

      const raw = btn.getAttribute('data-details') || '';
      let txt = raw;
      try {
        const json = JSON.parse(raw);
        txt = JSON.stringify(json, null, 2);
      } catch (err) {
        // keep raw
      }

      if (pre) pre.textContent = txt;
      modal.style.display = 'block';
    });
  }

  function flashNotice(type, msg) {
    const wrap = document.querySelector('.wrap');
    if (!wrap) return;

    const notice = document.createElement('div');
    notice.className = 'notice notice-' + (type || 'info') + ' is-dismissible';
    const p = document.createElement('p');
    p.textContent = msg || '';
    notice.appendChild(p);

    // Insert after the first H1 if present.
    const h1 = wrap.querySelector('h1');
    if (h1 && h1.parentNode) {
      h1.parentNode.insertBefore(notice, h1.nextSibling);
    } else {
      wrap.insertBefore(notice, wrap.firstChild);
    }

    // Auto-hide.
    setTimeout(function () {
      if (notice && notice.parentNode) {
        notice.parentNode.removeChild(notice);
      }
    }, 6000);
  }

  function openImagePicker(title, buttonText, onSelect) {
    if (!window.wp || !wp.media || typeof wp.media !== 'function') {
      flashNotice('error', 'Media picker is not available.');
      return;
    }

    const frame = wp.media({
      title: title || 'Select image',
      button: { text: buttonText || 'Use this image' },
      multiple: false,
      library: { type: 'image' },
    });

    frame.on('select', function () {
      const attachment = frame.state().get('selection').first();
      if (!attachment) return;
      const json = attachment.toJSON ? attachment.toJSON() : null;
      if (json && typeof onSelect === 'function') {
        onSelect(json);
      }
    });

    frame.open();
  }

  async function runQuickFix(btn, fix, payload, beforeText) {
    if (!btn) return;
    const nonce = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.nonce) || '';
    if (!nonce) return;

    const strings = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings) || {};
    const fixing = strings.fixing || 'Applying…';
    const applied = strings.fixApplied || 'Applied. Re-scan to verify.';
    const failed = strings.fixFailed || 'Fix failed.';

    const prev = beforeText || btn.textContent;
    btn.disabled = true;
    btn.textContent = fixing;

    try {
      const resp = await postAjax('wpsn_schema_quick_fix', Object.assign({ nonce, fix }, payload || {}));
      if (!resp || !resp.success) {
        throw new Error((resp && resp.data && (resp.data.message || resp.data)) || failed);
      }
      btn.textContent = '✓';
      flashNotice('success', applied);
    } catch (err) {
      btn.disabled = false;
      btn.textContent = prev;
      flashNotice('error', failed + ' ' + (err && err.message ? err.message : ''));
    }
  }

  document.addEventListener('DOMContentLoaded', function () {
    const startBtn = $("wpsn-schema-scan-start");
    const cancelBtn = $("wpsn-schema-scan-cancel");
    const clearBtn = $("wpsn-schema-clear-all");

    if (startBtn) startBtn.addEventListener('click', startScan);
    if (cancelBtn) cancelBtn.addEventListener('click', cancelScan);
    if (clearBtn) clearBtn.addEventListener('click', clearAll);

    setupDetailsModal();

    // Quick-fixes.
    document.addEventListener('click', function (e) {
      const btn = e.target && e.target.closest ? e.target.closest('.wpsn-sv-quick-fix') : null;
      if (!btn) return;

      e.preventDefault();

      const fix = btn.getAttribute('data-fix') || '';
      const strings = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings) || {};

      if (fix === 'set_org_logo') {
        openImagePicker(strings.pickLogoTitle || 'Select organization logo', strings.pickLogoButton || 'Use as logo', function (att) {
          runQuickFix(btn, 'set_org_logo', {
            attachment_id: String(att.id || ''),
            url: String(att.url || ''),
          });
        });
        return;
      }

      if (fix === 'set_author_image') {
        const userId = btn.getAttribute('data-user-id') || '';
        if (!userId) {
          flashNotice('error', 'Missing user ID.');
          return;
        }

        openImagePicker(strings.pickAuthorImageTitle || 'Select author image', strings.pickAuthorImageButton || 'Use as author image', function (att) {
          runQuickFix(btn, 'set_author_image', {
            user_id: String(userId),
            attachment_id: String(att.id || ''),
            url: String(att.url || ''),
          });
        });
        return;
      }

      if (fix === 'set_featured_image') {
        const postId = btn.getAttribute('data-post-id') || '';
        if (!postId) {
          flashNotice('error', 'Missing post ID.');
          return;
        }
        openImagePicker(strings.pickImageTitle || 'Select featured image', strings.pickImageButton || 'Set featured image', function (att) {
          runQuickFix(btn, 'set_featured_image', {
            post_id: String(postId),
            attachment_id: String(att.id || ''),
          });
        });
      }
    });

    // Safe fix workflow (batched + progress).
    const fixPreviewBtn = $("wpsn-schema-fix-safe-preview");
    const fixApplyBtn = $("wpsn-schema-fix-safe-apply");
    const fixCancelBtn = $("wpsn-schema-fix-safe-cancel");
    const fixSpinner = $("wpsn-schema-fix-safe-spinner");
    const fixResult = $("wpsn-schema-fix-safe-result");
    const fixProgressWrap = $("wpsn-schema-fix-safe-progress");
    const fixBar = $("wpsn-schema-fix-safe-bar");
    const fixStatus = $("wpsn-schema-fix-safe-status");
    const fixCountersWrap = $("wpsn-schema-fix-safe-counters");
    const fixCopyWrap = $("wpsn-schema-fix-safe-copy-wrap");
    const fixCopyBtn = $("wpsn-schema-fix-safe-copy");
    const fixCopyOk = $("wpsn-schema-fix-safe-copy-ok");

    // Counter mapping (data-k -> element)
    const fixCounterEls = {};
    const fixChipEls = {};
    if (fixCountersWrap) {
      fixCountersWrap.querySelectorAll('[data-k]').forEach((el) => {
        const k = el.getAttribute('data-k');
        if (k) {
          fixCounterEls[k] = el;
          const chip = el.closest('.wpsn-sv-chip');
          if (chip) fixChipEls[k] = chip;
        }
      });
    }

    function toNum(v) {
      const n = Number(v || 0);
      return Number.isFinite(n) ? n : 0;
    }

    function setCounter(key, value) {
      const el = fixCounterEls[key];
      if (!el) return;
      el.textContent = String(toNum(value));
    }

    function setChipState(key, state) {
      const chip = fixChipEls[key];
      if (!chip) return;
      chip.classList.remove('wpsn-sv-chip-ok', 'wpsn-sv-chip-warn', 'wpsn-sv-chip-bad');
      if (state) {
        chip.classList.add(state);
      }
    }

    function calcLeft(target, fixed, already) {
      const left = toNum(target) - toNum(fixed) - toNum(already);
      return left > 0 ? left : 0;
    }

    function updateFixCounters(counts) {
      if (!fixCountersWrap) return;

      const c = counts || {};

      const tOrgLogo = toNum(c.target_org_logo);
      const tOrgName = toNum(c.target_org_name);
      const fOrgLogo = toNum(c.fixed_org_logo);
      const fOrgName = toNum(c.fixed_org_name);
      const aOrgLogo = toNum(c.skipped_org_logo_already_set);
      const aOrgName = toNum(c.skipped_org_name_already_set);

      const tImg = toNum(c.target_author_image);
      const tSame = toNum(c.target_author_sameas);
      const fImg = toNum(c.fixed_author_image);
      const fSame = toNum(c.fixed_author_sameas);
      const aImg = toNum(c.skipped_author_image_already_set);
      const aSame = toNum(c.skipped_author_sameas_already_set);

      setCounter('target_org_logo', tOrgLogo);
      setCounter('fixed_org_logo', fOrgLogo);
      setCounter('skipped_org_logo_already_set', aOrgLogo);
      const leftOrgLogo = calcLeft(tOrgLogo, fOrgLogo, aOrgLogo);
      setCounter('left_org_logo', leftOrgLogo);
      setChipState('left_org_logo', leftOrgLogo > 0 ? 'wpsn-sv-chip-warn' : 'wpsn-sv-chip-ok');

      setCounter('target_org_name', tOrgName);
      setCounter('fixed_org_name', fOrgName);
      setCounter('skipped_org_name_already_set', aOrgName);
      const leftOrgName = calcLeft(tOrgName, fOrgName, aOrgName);
      setCounter('left_org_name', leftOrgName);
      setChipState('left_org_name', leftOrgName > 0 ? 'wpsn-sv-chip-warn' : 'wpsn-sv-chip-ok');

      setCounter('target_author_image', tImg);
      setCounter('fixed_author_image', fImg);
      setCounter('skipped_author_image_already_set', aImg);
      const leftImg = calcLeft(tImg, fImg, aImg);
      setCounter('left_author_image', leftImg);
      setChipState('left_author_image', leftImg > 0 ? 'wpsn-sv-chip-warn' : 'wpsn-sv-chip-ok');

      setCounter('target_author_sameas', tSame);
      setCounter('fixed_author_sameas', fSame);
      setCounter('skipped_author_sameas_already_set', aSame);
      const leftSame = calcLeft(tSame, fSame, aSame);
      setCounter('left_author_sameas', leftSame);
      setChipState('left_author_sameas', leftSame > 0 ? 'wpsn-sv-chip-warn' : 'wpsn-sv-chip-ok');

      // Skip reasons
      setCounter('skipped_user_forbidden', toNum(c.skipped_user_forbidden));
      setCounter('skipped_author_image_no_avatar', toNum(c.skipped_author_image_no_avatar));
      setCounter('skipped_author_sameas_no_links', toNum(c.skipped_author_sameas_no_links));

      // Show counters once we have any signal.
      fixCountersWrap.style.display = 'block';
    }

    function readFixOptions() {
      const orgLogo = $("wpsn-schema-fix-opt-org-logo");
      const orgName = $("wpsn-schema-fix-opt-org-name");
      const authorImage = $("wpsn-schema-fix-opt-author-image");
      const authorSameAs = $("wpsn-schema-fix-opt-author-sameas");
      const orgWithoutScan = $("wpsn-schema-fix-opt-org-without-scan");

      // Default to enabled if checkboxes are missing (back-compat).
      const v = (el) => (el ? !!el.checked : true);

      return {
        fix_org_logo: v(orgLogo) ? '1' : '0',
        fix_org_name: v(orgName) ? '1' : '0',
        fix_author_image: v(authorImage) ? '1' : '0',
        fix_author_sameas: v(authorSameAs) ? '1' : '0',
        allow_org_without_scan: (orgWithoutScan && orgWithoutScan.checked) ? '1' : '0',
      };
    }

    function syncFixNoScanUi() {
      if (fixRunning) return;

      const orgLogo = $("wpsn-schema-fix-opt-org-logo");
      const orgName = $("wpsn-schema-fix-opt-org-name");
      const authorImage = $("wpsn-schema-fix-opt-author-image");
      const authorSameAs = $("wpsn-schema-fix-opt-author-sameas");
      const orgWithoutScan = $("wpsn-schema-fix-opt-org-without-scan");

      const scan = parseInt((fixApplyBtn && fixApplyBtn.getAttribute('data-scan')) || (fixPreviewBtn && fixPreviewBtn.getAttribute('data-scan')) || '0', 10) || 0;
      if (scan > 0) {
        if (authorImage) authorImage.disabled = false;
        if (authorSameAs) authorSameAs.disabled = false;
        return;
      }

      // No scan: keep author options disabled.
      if (authorImage) {
        authorImage.checked = false;
        authorImage.disabled = true;
      }
      if (authorSameAs) {
        authorSameAs.checked = false;
        authorSameAs.disabled = true;
      }

      // Only enable buttons if user explicitly allows org-only fixes without a scan.
      const allow = !!(orgWithoutScan && orgWithoutScan.checked);
      const orgAny = !!((orgLogo && orgLogo.checked) || (orgName && orgName.checked));
      const enable = allow && orgAny;

      if (fixPreviewBtn) fixPreviewBtn.disabled = !enable;
      if (fixApplyBtn) fixApplyBtn.disabled = !enable;
    }

    let fixRunning = false;
    let fixCancelled = false;
    let fixSession = '';
    let fixTotal = 0;
    let fixProcessed = 0;
    let fixDryRun = false;

    function setFixProgress(pct) {
      if (!fixBar) return;
      const v = Math.max(0, Math.min(100, pct));
      fixBar.style.width = String(v) + '%';
    }

    function setFixStatus(msg) {
      if (fixStatus) fixStatus.textContent = msg || '';
    }

    function showFixProgress(show) {
      if (fixProgressWrap) fixProgressWrap.style.display = show ? 'block' : 'none';
      if (fixCancelBtn) fixCancelBtn.style.display = show ? 'inline-block' : 'none';
    }

    function showFixResult(data, dryRun) {
      if (!fixResult) return;
      const p = fixResult.querySelector('p');
      if (!p) return;

      const strings = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings) || {};
      const done = strings.fixAllDone || 'Safe fixes applied. Re-scan to verify.';
      const dryDone = strings.fixAllDryRun || 'Dry run completed (no changes were saved).';
      const none = strings.fixAllNone || 'Nothing to fix (already OK).';

      const fixed = (data && data.fixed && Array.isArray(data.fixed)) ? data.fixed : [];
      const skipped = (data && data.skipped && Array.isArray(data.skipped)) ? data.skipped : [];
      const errors = (data && data.errors && Array.isArray(data.errors)) ? data.errors : [];
      const counts = (data && data.counts) ? data.counts : {};

      // Targets vs fixed summary (helps large sites).
      const tOrgLogo = Number(counts.target_org_logo || 0);
      const tOrgName = Number(counts.target_org_name || 0);
      const fOrgLogo = Number(counts.fixed_org_logo || 0);
      const fOrgName = Number(counts.fixed_org_name || 0);
      const tImg = Number(counts.target_author_image || 0);
      const tSame = Number(counts.target_author_sameas || 0);
      const fImg = Number(counts.fixed_author_image || 0);
      const fSame = Number(counts.fixed_author_sameas || 0);

      // Skips (why targets weren't fixed)
      const sForbidden = Number(counts.skipped_user_forbidden || 0);
      const sNoAvatar = Number(counts.skipped_author_image_no_avatar || 0);
      const sImgAlready = Number(counts.skipped_author_image_already_set || 0);
      const sNoLinks = Number(counts.skipped_author_sameas_no_links || 0);
      const sSameAlready = Number(counts.skipped_author_sameas_already_set || 0);
      const sOrgLogoAlready = Number(counts.skipped_org_logo_already_set || 0);
      const sOrgNameAlready = Number(counts.skipped_org_name_already_set || 0);

      const leftOrgLogo = Math.max(0, tOrgLogo - fOrgLogo - sOrgLogoAlready);
      const leftOrgName = Math.max(0, tOrgName - fOrgName - sOrgNameAlready);
      const leftImg = Math.max(0, tImg - fImg - sImgAlready);
      const leftSame = Math.max(0, tSame - fSame - sSameAlready);

      let headline = dryRun ? dryDone : done;
      const anyFixed = (Number(counts.fixed_author_image || 0) + Number(counts.fixed_author_sameas || 0) + Number(counts.fixed_org_logo || 0) + Number(counts.fixed_org_name || 0)) > 0 || fixed.length;
      const anyTarget = (Number(counts.target_author_image || 0) + Number(counts.target_author_sameas || 0) + Number(counts.target_org_logo || 0) + Number(counts.target_org_name || 0)) > 0;
      if (!anyFixed && !anyTarget) {
        headline = none;
      }

      const lines = [];
      lines.push(headline);

      // Add targets summary lines (even for dry run).
      if (tOrgLogo || tOrgName || tImg || tSame || fOrgLogo || fOrgName || fImg || fSame) {
        lines.push('Organization logo: target ' + String(tOrgLogo) + ', fixed ' + String(fOrgLogo));
        lines.push('Organization name: target ' + String(tOrgName) + ', fixed ' + String(fOrgName));
        lines.push('Author image: target ' + String(tImg) + ', fixed ' + String(fImg));
        lines.push('Author sameAs: target ' + String(tSame) + ', fixed ' + String(fSame));

        // Explicit "targets left" line (helps large sites triage what remains).
        lines.push('Targets left = target - fixed - already_set: org_logo ' + String(leftOrgLogo) + ', org_name ' + String(leftOrgName) + ', author_image ' + String(leftImg) + ', author_sameAs ' + String(leftSame));
      }

      if (sForbidden || sNoAvatar || sImgAlready || sNoLinks || sSameAlready) {
        lines.push('Skipped forbidden users: ' + String(sForbidden));
        lines.push('Skipped author image: no avatar ' + String(sNoAvatar) + ', already set ' + String(sImgAlready));
        lines.push('Skipped author sameAs: no links ' + String(sNoLinks) + ', already set ' + String(sSameAlready));
      }

      if (fixed.length) {
        fixed.forEach((f) => {
          if (!f || !f.type) return;
          if (f.type === 'org_logo') {
            lines.push('Fixed org_logo: ' + String(f.value || ''));
          } else if (f.type === 'org_name') {
            lines.push('Fixed org_name: ' + String(f.value || ''));
          } else if (f.type === 'author_image') {
            lines.push('Fixed author_image: ' + String(f.count || counts.fixed_author_image || 0) + ' user(s)');
          } else if (f.type === 'author_sameas') {
            lines.push('Fixed author_sameas: ' + String(f.count || counts.fixed_author_sameas || 0) + ' user(s)');
          } else {
            lines.push('Fixed: ' + String(f.type));
          }
        });
      }

      if (skipped.length) {
        skipped.forEach((s) => {
          if (!s || !s.type) return;
          lines.push('Skipped ' + String(s.type) + ': ' + String(s.reason || ''));
        });
      }

      if (errors.length) {
        errors.forEach((er) => {
          if (!er) return;
          lines.push('Error ' + String(er.type || '') + ': ' + String(er.error || ''));
        });
      }

      p.textContent = lines.join('\n');
      p.style.whiteSpace = 'pre-wrap';
      fixResult.style.display = 'block';

      // Show copy button when we have a result.
      if (fixCopyWrap) fixCopyWrap.style.display = 'block';
      if (fixCopyOk) fixCopyOk.style.display = 'none';

      // Keep the counters panel in sync with the final result.
      updateFixCounters(counts);
    }

    async function copyToClipboard(text) {
      if (!text) return false;
      try {
        if (navigator.clipboard && navigator.clipboard.writeText) {
          await navigator.clipboard.writeText(text);
          return true;
        }
      } catch (e) {
        // fallback below
      }

      try {
        const ta = document.createElement('textarea');
        ta.value = text;
        ta.setAttribute('readonly', 'readonly');
        ta.style.position = 'fixed';
        ta.style.left = '-9999px';
        ta.style.top = '0';
        document.body.appendChild(ta);
        ta.select();
        const ok = document.execCommand('copy');
        document.body.removeChild(ta);
        return !!ok;
      } catch (e2) {
        return false;
      }
    }

    function finishFix(ok, message, resultData) {
      fixRunning = false;
      if (fixPreviewBtn) fixPreviewBtn.disabled = false;
      if (fixApplyBtn) fixApplyBtn.disabled = false;
      if (fixSpinner) fixSpinner.classList.remove('is-active');

      if (message) {
        setFixStatus(message);
      }

      showFixProgress(false);

      if (resultData) {
        showFixResult(resultData, fixDryRun);
      }

      if (ok && message) {
        flashNotice('success', message);
      }
    }

    async function stepFix() {
      if (!fixRunning || !fixSession) return;
      if (fixCancelled) {
        finishFix(false, (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.cancelled) || 'Cancelled.');
        return;
      }

      const nonce = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.nonce) || '';
      const strings = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings) || {};
      const failed = strings.fixFailed || 'Fix failed.';

      try {
        const resp = await postAjax('wpsn_schema_fix_safe_step', {
          nonce,
          session: fixSession,
          chunk: '50',
        });

        if (!resp || !resp.success) {
          throw new Error((resp && resp.data && (resp.data.message || resp.data)) || failed);
        }

        const data = resp.data || {};
        fixProcessed = data.processed_users || fixProcessed;
        fixTotal = data.total_users || fixTotal;

        const pct = fixTotal > 0 ? Math.round((fixProcessed / fixTotal) * 100) : 0;
        setFixProgress(pct);

        const counts = data.counts || {};
        const msg = 'Processed ' + fixProcessed + ' / ' + fixTotal +
          ' • target img: ' + String(counts.target_author_image || 0) +
          ' • fixed img: ' + String(counts.fixed_author_image || 0) +
          ' • target sameAs: ' + String(counts.target_author_sameas || 0) +
          ' • fixed sameAs: ' + String(counts.fixed_author_sameas || 0) +
          ' • skip forbidden: ' + String(counts.skipped_user_forbidden || 0) +
          ' • skip no avatar: ' + String(counts.skipped_author_image_no_avatar || 0) +
          ' • skip no links: ' + String(counts.skipped_author_sameas_no_links || 0);
        setFixStatus(msg);

        updateFixCounters(counts);

        if (data.done) {
          const resultData = data.result || null;
          const endMsg = fixDryRun ? (strings.fixAllDryRun || 'Dry run completed (no changes were saved).') : (strings.fixAllDone || 'Safe fixes applied. Re-scan to verify.');
          finishFix(true, endMsg, resultData || data);
          return;
        }

        setTimeout(stepFix, 150);
      } catch (err) {
        fixRunning = false;
        if (fixPreviewBtn) fixPreviewBtn.disabled = false;
        if (fixApplyBtn) fixApplyBtn.disabled = false;
        if (fixSpinner) fixSpinner.classList.remove('is-active');
        showFixProgress(false);
        flashNotice('error', failed + ' ' + (err && err.message ? err.message : ''));
      }
    }

    async function startFix(dryRun) {
      if (fixRunning) return;
      const nonce = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.nonce) || '';
      if (!nonce) return;

      const strings = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings) || {};
      const failed = strings.fixFailed || 'Fix failed.';

      if (!dryRun) {
        const confirmText = strings.confirmFixAll || '';
        // eslint-disable-next-line no-alert
        if (confirmText && !confirm(confirmText)) return;
      }

      const scan = (fixApplyBtn && fixApplyBtn.getAttribute('data-scan')) || (fixPreviewBtn && fixPreviewBtn.getAttribute('data-scan')) || (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.scanId) || '0';

      const opts = readFixOptions();
      const hasAny = (opts.fix_org_logo === '1') || (opts.fix_org_name === '1') || (opts.fix_author_image === '1') || (opts.fix_author_sameas === '1');
      if (!hasAny) {
        const strings2 = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings) || {};
        flashNotice('error', strings2.fixSelectOne || 'Select at least one fix option.');
        return;
      }

      fixRunning = true;
      fixCancelled = false;
      fixSession = '';
      fixTotal = 0;
      fixProcessed = 0;
      fixDryRun = !!dryRun;

      if (fixResult) fixResult.style.display = 'none';
      if (fixCopyWrap) fixCopyWrap.style.display = 'none';
      if (fixCopyOk) fixCopyOk.style.display = 'none';

      // Reset counters UI.
      if (fixCountersWrap) {
        fixCountersWrap.style.display = 'block';
        Object.keys(fixCounterEls).forEach((k) => {
          if (fixCounterEls[k]) fixCounterEls[k].textContent = '0';
        });
      }

      if (fixSpinner) fixSpinner.classList.add('is-active');
      if (fixPreviewBtn) fixPreviewBtn.disabled = true;
      if (fixApplyBtn) fixApplyBtn.disabled = true;
      showFixProgress(true);
      setFixProgress(0);
      setFixStatus(strings.fixStarting || 'Starting safe fixes…');

      try {
        const resp = await postAjax('wpsn_schema_fix_safe_start', {
          nonce,
          scan_id: String(scan || '0'),
          dry_run: dryRun ? '1' : '0',
          chunk: '50',
          fix_org_logo: opts.fix_org_logo,
          fix_org_name: opts.fix_org_name,
          fix_author_image: opts.fix_author_image,
          fix_author_sameas: opts.fix_author_sameas,
          allow_org_without_scan: opts.allow_org_without_scan,
        });

        if (!resp || !resp.success) {
          throw new Error((resp && resp.data && (resp.data.message || resp.data)) || failed);
        }

        const data = resp.data || {};
        fixSession = data.session || '';
        fixTotal = data.total_users || (data.counts && data.counts.total_users) || 0;
        fixProcessed = data.processed_users || 0;

        if (data.counts) {
          updateFixCounters(data.counts);
        }

        if (data.done) {
          const msg = dryRun ? (strings.fixAllDryRun || 'Dry run completed (no changes were saved).') : (strings.fixAllDone || 'Safe fixes applied. Re-scan to verify.');
          finishFix(true, msg, data);
          return;
        }

        setFixStatus(strings.fixProcessing || 'Applying safe fixes…');
        await stepFix();
      } catch (err) {
        fixRunning = false;
        if (fixPreviewBtn) fixPreviewBtn.disabled = false;
        if (fixApplyBtn) fixApplyBtn.disabled = false;
        if (fixSpinner) fixSpinner.classList.remove('is-active');
        showFixProgress(false);
        flashNotice('error', failed + ' ' + (err && err.message ? err.message : ''));
      }
    }

    async function cancelFix() {
      if (!fixRunning) return;
      fixCancelled = true;
      const nonce = (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.nonce) || '';
      try {
        await postAjax('wpsn_schema_fix_safe_cancel', { nonce });
      } catch (e) {
        // ignore
      }
    }

    if (fixPreviewBtn) {
      fixPreviewBtn.addEventListener('click', function (e) {
        e.preventDefault();
        startFix(true);
      });
    }
    if (fixApplyBtn) {
      fixApplyBtn.addEventListener('click', function (e) {
        e.preventDefault();
        startFix(false);
      });
    }
    if (fixCancelBtn) {
      fixCancelBtn.addEventListener('click', function (e) {
        e.preventDefault();
        cancelFix();
      });
    }

    // Copy summary (agency reports)
    if (fixCopyBtn) {
      fixCopyBtn.addEventListener('click', async function (e) {
        e.preventDefault();
        const p = fixResult ? fixResult.querySelector('p') : null;
        const text = p ? String(p.textContent || '').trim() : '';
        const ok = await copyToClipboard(text);
        if (ok) {
          if (fixCopyOk) {
            fixCopyOk.style.display = 'inline';
            setTimeout(() => {
              if (fixCopyOk) fixCopyOk.style.display = 'none';
            }, 1800);
          }
          flashNotice('success', (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.copied) || 'Copied to clipboard.');
        } else {
          flashNotice('error', (window.WPSN_SCHEMA_VALIDATOR && window.WPSN_SCHEMA_VALIDATOR.strings && window.WPSN_SCHEMA_VALIDATOR.strings.copyFailed) || 'Copy failed.');
        }
      });
    }

    // No-scan UX: enable org-only fixes without a scan when explicitly allowed.
    const orgLogoCb = $("wpsn-schema-fix-opt-org-logo");
    const orgNameCb = $("wpsn-schema-fix-opt-org-name");
    const orgWithoutScanCb = $("wpsn-schema-fix-opt-org-without-scan");
    if (orgWithoutScanCb) orgWithoutScanCb.addEventListener('change', syncFixNoScanUi);
    if (orgLogoCb) orgLogoCb.addEventListener('change', syncFixNoScanUi);
    if (orgNameCb) orgNameCb.addEventListener('change', syncFixNoScanUi);
    syncFixNoScanUi();


  });
})();