<?php

namespace GriffinForms\Admin\Js\Local;

class Submission extends \GriffinForms\Admin\Js\Local\Single
{
    public function js()
    {
        $js = '';
        $js .= 'jQuery(document).ready(function($) {' . PHP_EOL;
        $js .= $this->openFirstAccordionPanel() . PHP_EOL;
        $js .= $this->markAsPaid() . PHP_EOL;
        $js .= $this->markAsUnpaid() . PHP_EOL;
        $js .= $this->deleteSubmission() . PHP_EOL;
        $js .= $this->loadSubmissionHistoryTimeline() . PHP_EOL;
        $js .= $this->loadCompareModal() . PHP_EOL;
        $js .= $this->loadAccountsPanel() . PHP_EOL;
        $js .= $this->loadLogsTimeline() . PHP_EOL;
        $js .= '});' . PHP_EOL;
        
        return $js;
    }

    protected function markAsPaid()
    {
        $config = isset($this->data['mark_paid']) && is_array($this->data['mark_paid']) ? $this->data['mark_paid'] : array();
        $defaults = array(
            'ajax_url' => admin_url('admin-ajax.php'),
            'action' => 'griffinforms_mark_payment_paid',
            'nonce' => wp_create_nonce('griffinforms_mark_payment_paid'),
            'submission_id' => 0,
            'strings' => array(
                'success' => __('Payment marked as paid.', 'griffinforms-form-builder'),
                'error' => __('Unable to mark payment as paid.', 'griffinforms-form-builder'),
            ),
        );

        $config = wp_parse_args($config, $defaults);
        $config['strings'] = wp_parse_args($config['strings'], $defaults['strings']);
        $json = wp_json_encode($config);

        $js  = 'var gfMarkPaidConfig = ' . $json . ';' . PHP_EOL;
        $js .= 'var $markPaidBtn = $("#gf-mark-paid-btn");' . PHP_EOL;
        $js .= 'if ($markPaidBtn.length && gfMarkPaidConfig.submission_id) {' . PHP_EOL;
        $js .= '  var markPaidAjaxUrl = gfMarkPaidConfig.ajax_url || (typeof ajaxurl !== "undefined" ? ajaxurl : "");' . PHP_EOL;
        $js .= '  var gfShowSnack = (typeof griffinformsSnackBar === "function") ? griffinformsSnackBar : function(msg){ alert(msg); };' . PHP_EOL;
        $js .= '  $markPaidBtn.on("click", function(e) {' . PHP_EOL;
        $js .= '    e.preventDefault();' . PHP_EOL;
        $js .= '    if (!markPaidAjaxUrl) {' . PHP_EOL;
        $js .= '      gfShowSnack(gfMarkPaidConfig.strings.error);' . PHP_EOL;
        $js .= '      return;' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    var $btn = $(this);' . PHP_EOL;
        $js .= '    var $spinner = $btn.find(".spinner-border");' . PHP_EOL;
        $js .= '    $btn.prop("disabled", true);' . PHP_EOL;
        $js .= '    $spinner.removeClass("d-none");' . PHP_EOL;
        $js .= '    $.ajax({' . PHP_EOL;
        $js .= '      url: markPaidAjaxUrl,' . PHP_EOL;
        $js .= '      method: "POST",' . PHP_EOL;
        $js .= '      dataType: "json",' . PHP_EOL;
        $js .= '      data: {' . PHP_EOL;
        $js .= '        action: gfMarkPaidConfig.action,' . PHP_EOL;
        $js .= '        nonce: gfMarkPaidConfig.nonce,' . PHP_EOL;
        $js .= '        submission_id: gfMarkPaidConfig.submission_id' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }).done(function(response) {' . PHP_EOL;
        $js .= '      if (response && response.success) {' . PHP_EOL;
        $js .= '        gfShowSnack(gfMarkPaidConfig.strings.success);' . PHP_EOL;
        $js .= '        setTimeout(function(){ window.location.reload(); }, 500);' . PHP_EOL;
        $js .= '        return;' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '      var msg = gfMarkPaidConfig.strings.error;' . PHP_EOL;
        $js .= '      if (response) {' . PHP_EOL;
        $js .= '        if (response.msg) { msg = response.msg; }' . PHP_EOL;
        $js .= '        else if (typeof response.data === "string") { msg = response.data; }' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '      gfShowSnack(msg);' . PHP_EOL;
        $js .= '    }).fail(function() {' . PHP_EOL;
        $js .= '      gfShowSnack(gfMarkPaidConfig.strings.error);' . PHP_EOL;
        $js .= '    }).always(function() {' . PHP_EOL;
        $js .= '      $spinner.addClass("d-none");' . PHP_EOL;
        $js .= '      $btn.prop("disabled", false);' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '}' . PHP_EOL;

        return $js;
    }

    protected function markAsUnpaid()
    {
        $config = isset($this->data['mark_unpaid']) && is_array($this->data['mark_unpaid']) ? $this->data['mark_unpaid'] : array();
        $defaults = array(
            'ajax_url' => admin_url('admin-ajax.php'),
            'action' => 'griffinforms_mark_payment_unpaid',
            'nonce' => wp_create_nonce('griffinforms_mark_payment_unpaid'),
            'submission_id' => 0,
            'strings' => array(
                'success' => __('Payment marked as unpaid.', 'griffinforms-form-builder'),
                'error' => __('Unable to mark payment as unpaid.', 'griffinforms-form-builder'),
            ),
        );

        $config = wp_parse_args($config, $defaults);
        $config['strings'] = wp_parse_args($config['strings'], $defaults['strings']);
        $json = wp_json_encode($config);

        $js  = 'var gfMarkUnpaidConfig = ' . $json . ';' . PHP_EOL;
        $js .= 'var $markUnpaidBtn = $("#gf-mark-unpaid-btn");' . PHP_EOL;
        $js .= 'if ($markUnpaidBtn.length && gfMarkUnpaidConfig.submission_id) {' . PHP_EOL;
        $js .= '  var markUnpaidAjaxUrl = gfMarkUnpaidConfig.ajax_url || (typeof ajaxurl !== "undefined" ? ajaxurl : "");' . PHP_EOL;
        $js .= '  $markUnpaidBtn.on("click", function(e) {' . PHP_EOL;
        $js .= '    e.preventDefault();' . PHP_EOL;
        $js .= '    if (!markUnpaidAjaxUrl) {' . PHP_EOL;
        $js .= '      alert(gfMarkUnpaidConfig.strings.error);' . PHP_EOL;
        $js .= '      return;' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    var $btn = $(this);' . PHP_EOL;
        $js .= '    var $spinner = $btn.find(".spinner-border");' . PHP_EOL;
        $js .= '    $btn.prop("disabled", true);' . PHP_EOL;
        $js .= '    $spinner.removeClass("d-none");' . PHP_EOL;
        $js .= '    $.ajax({' . PHP_EOL;
        $js .= '      url: markUnpaidAjaxUrl,' . PHP_EOL;
        $js .= '      method: "POST",' . PHP_EOL;
        $js .= '      dataType: "json",' . PHP_EOL;
        $js .= '      data: {' . PHP_EOL;
        $js .= '        action: gfMarkUnpaidConfig.action,' . PHP_EOL;
        $js .= '        nonce: gfMarkUnpaidConfig.nonce,' . PHP_EOL;
        $js .= '        submission_id: gfMarkUnpaidConfig.submission_id' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }).done(function(response) {' . PHP_EOL;
        $js .= '      if (response && response.success) {' . PHP_EOL;
        $js .= '        alert(gfMarkUnpaidConfig.strings.success);' . PHP_EOL;
        $js .= '        setTimeout(function(){ window.location.reload(); }, 500);' . PHP_EOL;
        $js .= '        return;' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '      var msg = gfMarkUnpaidConfig.strings.error;' . PHP_EOL;
        $js .= '      if (response) {' . PHP_EOL;
        $js .= '        if (response.msg) { msg = response.msg; }' . PHP_EOL;
        $js .= '        else if (typeof response.data === "string") { msg = response.data; }' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '      alert(msg);' . PHP_EOL;
        $js .= '    }).fail(function() {' . PHP_EOL;
        $js .= '      alert(gfMarkUnpaidConfig.strings.error);' . PHP_EOL;
        $js .= '    }).always(function() {' . PHP_EOL;
        $js .= '      $spinner.addClass("d-none");' . PHP_EOL;
        $js .= '      $btn.prop("disabled", false);' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '}' . PHP_EOL;

        return $js;
    }

    protected function deleteSubmission()
    {
        $config = isset($this->data['delete_submission']) && is_array($this->data['delete_submission']) ? $this->data['delete_submission'] : array();
        $defaults = array(
            'ajax_url' => admin_url('admin-ajax.php'),
            'action' => 'deleteItem',
            'nonce' => wp_create_nonce('delete_item'),
            'submission_id' => 0,
            'redirect' => admin_url('admin.php?page=griffinforms-submissions'),
            'strings' => array(
                'confirm' => __('Are you sure you want to delete this submission?', 'griffinforms-form-builder'),
                'success' => __('Submission deleted.', 'griffinforms-form-builder'),
                'error' => __('Unable to delete submission.', 'griffinforms-form-builder'),
            ),
        );

        $config = wp_parse_args($config, $defaults);
        $config['strings'] = wp_parse_args($config['strings'], $defaults['strings']);
        $json = wp_json_encode($config);

        $js  = 'var gfDeleteConfig = ' . $json . ';' . PHP_EOL;
        $js .= 'var $deleteBtn = $("#gf-delete-submission-btn");' . PHP_EOL;
        $js .= 'if ($deleteBtn.length && gfDeleteConfig.submission_id) {' . PHP_EOL;
        $js .= '  var deleteAjaxUrl = gfDeleteConfig.ajax_url || (typeof ajaxurl !== "undefined" ? ajaxurl : "");' . PHP_EOL;
        $js .= '  $deleteBtn.on("click", function(e) {' . PHP_EOL;
        $js .= '    e.preventDefault();' . PHP_EOL;
        $js .= '    if (!deleteAjaxUrl) { alert(gfDeleteConfig.strings.error); return; }' . PHP_EOL;
        $js .= '    if (!confirm(gfDeleteConfig.strings.confirm)) { return; }' . PHP_EOL;
        $js .= '    var $btn = $(this);' . PHP_EOL;
        $js .= '    var $spinner = $btn.find(".spinner-border");' . PHP_EOL;
        $js .= '    $btn.prop("disabled", true);' . PHP_EOL;
        $js .= '    $spinner.removeClass("d-none");' . PHP_EOL;
        $js .= '    $.ajax({' . PHP_EOL;
        $js .= '      url: deleteAjaxUrl,' . PHP_EOL;
        $js .= '      method: "POST",' . PHP_EOL;
        $js .= '      dataType: "text",' . PHP_EOL;
        $js .= '      data: {' . PHP_EOL;
        $js .= '        action: gfDeleteConfig.action,' . PHP_EOL;
        $js .= '        nonce: gfDeleteConfig.nonce,' . PHP_EOL;
        $js .= '        item_type: "submission",' . PHP_EOL;
        $js .= '        item_id: gfDeleteConfig.submission_id' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }).done(function(response) {' . PHP_EOL;
        $js .= '      var success = false;' . PHP_EOL;
        $js .= '      var parsed = response;' . PHP_EOL;
        $js .= '      try { parsed = JSON.parse(response); } catch (e) {}' . PHP_EOL;
        $js .= '      if (parsed === true || parsed === "1" || parsed === 1) { success = true; }' . PHP_EOL;
        $js .= '      if (parsed && parsed.success) { success = true; }' . PHP_EOL;
        $js .= '      if (!success && typeof parsed === "number" && parsed > 0) { success = true; }' . PHP_EOL;
        $js .= '      if (success) {' . PHP_EOL;
        $js .= '        alert(gfDeleteConfig.strings.success);' . PHP_EOL;
        $js .= '        window.location.href = gfDeleteConfig.redirect;' . PHP_EOL;
        $js .= '        return;' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '      var msg = gfDeleteConfig.strings.error;' . PHP_EOL;
        $js .= '      if (parsed && parsed.msg) { msg = parsed.msg; }' . PHP_EOL;
        $js .= '      alert(msg);' . PHP_EOL;
        $js .= '    }).fail(function() {' . PHP_EOL;
        $js .= '      alert(gfDeleteConfig.strings.error);' . PHP_EOL;
        $js .= '    }).always(function() {' . PHP_EOL;
        $js .= '      $spinner.addClass("d-none");' . PHP_EOL;
        $js .= '      $btn.prop("disabled", false);' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '}' . PHP_EOL;

        return $js;
    }

    protected function loadSubmissionHistoryTimeline()
    {
        $config = isset($this->data['history']) && is_array($this->data['history']) ? $this->data['history'] : array();
        $defaults = array(
            'ajax_url'       => admin_url('admin-ajax.php'),
            'submission_id'  => 0,
            'restore_action' => 'griffinforms_restore_submission_revision',
            'restore_nonce'  => wp_create_nonce('restore_submission_revision'),
            'preview_action' => 'griffinforms_preview_submission_revision',
            'preview_nonce'  => wp_create_nonce('preview_submission_revision'),
            'strings' => array(
                'title'           => __('Submission history', 'griffinforms-form-builder'),
                'empty'           => __('No submission revisions have been recorded yet.', 'griffinforms-form-builder'),
                'loading'         => __('Loading...', 'griffinforms-form-builder'),
                'preview_loading' => __('Loading preview...', 'griffinforms-form-builder'),
                'preview_error'   => __('Unable to load the snapshot preview.', 'griffinforms-form-builder'),
                'confirm_restore' => __('Restore this revision?', 'griffinforms-form-builder'),
                'restore_success' => __('Submission restored.', 'griffinforms-form-builder'),
                'restore_error'   => __('Unable to restore the selected snapshot.', 'griffinforms-form-builder'),
            ),
        );

        $config = wp_parse_args($config, $defaults);
        $config['strings'] = wp_parse_args($config['strings'], $defaults['strings']);
        $json = wp_json_encode($config);

        $js  = 'var gfSubmissionHistoryConfig = ' . $json . ';' . PHP_EOL;
        $js .= '(function($) {' . PHP_EOL;
        $js .= '  var $panel = $(".gf-submission-history-panel[data-gf-submission-history=\"1\"]");' . PHP_EOL;
        $js .= '  if (!$panel.length || !gfSubmissionHistoryConfig.submission_id) { return; }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  var ajaxUrl        = gfSubmissionHistoryConfig.ajax_url || (typeof ajaxurl !== "undefined" ? ajaxurl : "");' . PHP_EOL;
        $js .= '  var $items         = $panel.find("[data-gf-submission-history-item=\"1\"]");' . PHP_EOL;
        $js .= '  var $previewBtn    = $panel.find("[data-gf-submission-history-preview-btn=\"1\"]");' . PHP_EOL;
        $js .= '  var $restoreBtn    = $panel.find("[data-gf-submission-history-restore=\"1\"]");' . PHP_EOL;
        $js .= '  var $modal         = $("[data-gf-revision-preview-modal=\"1\"]");' . PHP_EOL;
        $js .= '  var $modalBody     = $modal.find("[data-gf-revision-preview-body]");' . PHP_EOL;
        $js .= '  var $modalMeta     = $modal.find("[data-gf-revision-preview-meta]");' . PHP_EOL;
        $js .= '  var $modalLoading  = $modal.find("[data-gf-revision-preview-loading]");' . PHP_EOL;
        $js .= '  var $modalRestore  = $modal.find("[data-gf-revision-preview-restore-btn=\"1\"]");' . PHP_EOL;
        $js .= '  var $modalTitle    = $modal.find(".modal-title");' . PHP_EOL;
        $js .= '  var selectedEventUid  = "";' . PHP_EOL;
        $js .= '  var selectedRevisionId = 0;' . PHP_EOL;
        $js .= '  var currentRevisionId  = parseInt($panel.attr("data-gf-current-revision-id") || "0", 10);' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // ── Item selection ─────────────────────────────────────────────────────' . PHP_EOL;
        $js .= '  function setSelectedItem($item) {' . PHP_EOL;
        $js .= '    $items.removeClass("is-selected").attr("aria-pressed", "false");' . PHP_EOL;
        $js .= '    if (!$item || !$item.length) {' . PHP_EOL;
        $js .= '      selectedEventUid   = "";' . PHP_EOL;
        $js .= '      selectedRevisionId = 0;' . PHP_EOL;
        $js .= '      $previewBtn.prop("disabled", true);' . PHP_EOL;
        $js .= '      $restoreBtn.prop("disabled", true);' . PHP_EOL;
        $js .= '      return;' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    $item.addClass("is-selected").attr("aria-pressed", "true");' . PHP_EOL;
        $js .= '    selectedEventUid   = $item.attr("data-gf-event-uid") || "";' . PHP_EOL;
        $js .= '    selectedRevisionId = parseInt($item.attr("data-gf-revision-id") || "0", 10);' . PHP_EOL;
        $js .= '    var isCurrent = parseInt($item.attr("data-gf-current-revision") || "0", 10) === 1' . PHP_EOL;
        $js .= '                 || (currentRevisionId > 0 && selectedRevisionId === currentRevisionId);' . PHP_EOL;
        $js .= '    $previewBtn.prop("disabled", !selectedEventUid);' . PHP_EOL;
        $js .= '    $restoreBtn.prop("disabled", !selectedEventUid || isCurrent);' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // Auto-select current revision (or first) on load' . PHP_EOL;
        $js .= '  if ($items.length) {' . PHP_EOL;
        $js .= '    var $current = $items.filter("[data-gf-current-revision=\"1\"]");' . PHP_EOL;
        $js .= '    setSelectedItem($current.length ? $current : $items.first());' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // Click / keyboard selection' . PHP_EOL;
        $js .= '  $(document).on("click keydown", "[data-gf-submission-history-item=\"1\"]", function(e) {' . PHP_EOL;
        $js .= '    if (e.type === "keydown" && e.key !== "Enter" && e.key !== " ") { return; }' . PHP_EOL;
        $js .= '    e.preventDefault();' . PHP_EOL;
        $js .= '    setSelectedItem($(this));' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // ── Restore helper (shared by sidebar btn + modal btn) ─────────────────' . PHP_EOL;
        $js .= '  function doRestore($btn) {' . PHP_EOL;
        $js .= '    if (!selectedEventUid || !selectedRevisionId) { return; }' . PHP_EOL;
        $js .= '    if (!window.confirm(gfSubmissionHistoryConfig.strings.confirm_restore)) { return; }' . PHP_EOL;
        $js .= '    var origText = $btn.text();' . PHP_EOL;
        $js .= '    $btn.prop("disabled", true).text(gfSubmissionHistoryConfig.strings.loading);' . PHP_EOL;
        $js .= '    $.ajax({' . PHP_EOL;
        $js .= '      url: ajaxUrl, method: "POST", dataType: "json",' . PHP_EOL;
        $js .= '      data: {' . PHP_EOL;
        $js .= '        action: gfSubmissionHistoryConfig.restore_action,' . PHP_EOL;
        $js .= '        nonce:  gfSubmissionHistoryConfig.restore_nonce,' . PHP_EOL;
        $js .= '        submission_id: gfSubmissionHistoryConfig.submission_id,' . PHP_EOL;
        $js .= '        event_uid: selectedEventUid' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }).done(function(r) {' . PHP_EOL;
        $js .= '      if (r && r.success) {' . PHP_EOL;
        $js .= '        alert(r.message || gfSubmissionHistoryConfig.strings.restore_success);' . PHP_EOL;
        $js .= '        window.location.reload();' . PHP_EOL;
        $js .= '        return;' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '      alert((r && r.msg) ? r.msg : gfSubmissionHistoryConfig.strings.restore_error);' . PHP_EOL;
        $js .= '    }).fail(function() {' . PHP_EOL;
        $js .= '      alert(gfSubmissionHistoryConfig.strings.restore_error);' . PHP_EOL;
        $js .= '    }).always(function() {' . PHP_EOL;
        $js .= '      $btn.prop("disabled", false).text(origText);' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // Sidebar restore button' . PHP_EOL;
        $js .= '  $restoreBtn.on("click", function() { doRestore($(this)); });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // ── Preview button → AJAX → modal ──────────────────────────────────────' . PHP_EOL;
        $js .= '  $previewBtn.on("click", function() {' . PHP_EOL;
        $js .= '    if (!selectedEventUid || !$modal.length) { return; }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    // Find the selected item for its metadata' . PHP_EOL;
        $js .= '    var $sel     = $items.filter("[data-gf-event-uid=\"" + selectedEventUid + "\"]");' . PHP_EOL;
        $js .= '    var revNum   = $sel.attr("data-gf-revision-number") || "";' . PHP_EOL;
        $js .= '    var summary  = $sel.attr("data-gf-revision-label")  || "";' . PHP_EOL;
        $js .= '    var created  = $sel.attr("data-gf-revision-created") || "";' . PHP_EOL;
        $js .= '    var isCurrent = parseInt($sel.attr("data-gf-current-revision") || "0", 10) === 1' . PHP_EOL;
        $js .= '                 || (currentRevisionId > 0 && selectedRevisionId === currentRevisionId);' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    // Reset modal state' . PHP_EOL;
        $js .= '    $modalTitle.text("' . esc_js(__('Snapshot preview', 'griffinforms-form-builder')) . '"' . PHP_EOL;
        $js .= '      + (revNum ? " — ' . esc_js(__('Revision', 'griffinforms-form-builder')) . ' #" + revNum : ""));' . PHP_EOL;
        $js .= '    $modalMeta.text(summary + (created ? " · " + created : ""));' . PHP_EOL;
        $js .= '    $modalBody.children(":not([data-gf-revision-preview-loading])").remove();' . PHP_EOL;
        $js .= '    $modalLoading.show();' . PHP_EOL;
        $js .= '    $modalRestore.prop("disabled", isCurrent);' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    // Open modal first so user sees loading state immediately' . PHP_EOL;
        $js .= '    var bsModal = bootstrap.Modal.getOrCreateInstance($modal[0]);' . PHP_EOL;
        $js .= '    bsModal.show();' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    $.ajax({' . PHP_EOL;
        $js .= '      url: ajaxUrl, method: "POST", dataType: "json",' . PHP_EOL;
        $js .= '      data: {' . PHP_EOL;
        $js .= '        action: gfSubmissionHistoryConfig.preview_action,' . PHP_EOL;
        $js .= '        nonce:  gfSubmissionHistoryConfig.preview_nonce,' . PHP_EOL;
        $js .= '        submission_id: gfSubmissionHistoryConfig.submission_id,' . PHP_EOL;
        $js .= '        event_uid: selectedEventUid' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }).done(function(r) {' . PHP_EOL;
        $js .= '      $modalLoading.hide();' . PHP_EOL;
        $js .= '      if (r && r.success && r.html) {' . PHP_EOL;
        $js .= '        $modalBody.append(r.html);' . PHP_EOL;
        $js .= '      } else {' . PHP_EOL;
        $js .= '        $modalBody.append("<div class=\"text-danger small\">" + gfSubmissionHistoryConfig.strings.preview_error + "</div>");' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }).fail(function() {' . PHP_EOL;
        $js .= '      $modalLoading.hide();' . PHP_EOL;
        $js .= '      $modalBody.append("<div class=\"text-danger small\">" + gfSubmissionHistoryConfig.strings.preview_error + "</div>");' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // Modal restore button delegates to the shared doRestore helper' . PHP_EOL;
        $js .= '  $modalRestore.on("click", function() { doRestore($(this)); });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // Clear modal body when it closes so stale content is never re-shown' . PHP_EOL;
        $js .= '  $modal.on("hidden.bs.modal", function() {' . PHP_EOL;
        $js .= '    $modalBody.children(":not([data-gf-revision-preview-loading])").remove();' . PHP_EOL;
        $js .= '    $modalLoading.hide();' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '}(jQuery));' . PHP_EOL;

        return $js;
    }

    protected function loadCompareModal()
    {
        $config = isset($this->data['compare']) && is_array($this->data['compare']) ? $this->data['compare'] : array();
        $defaults = array(
            'ajax_url'       => admin_url('admin-ajax.php'),
            'submission_id'  => 0,
            'preview_action' => 'griffinforms_preview_submission_revision',
            'preview_nonce'  => '',
            'revisions'      => array(),
            'strings'        => array(
                'loading'       => __('Loading...', 'griffinforms-form-builder'),
                'error'         => __('Unable to load revision.', 'griffinforms-form-builder'),
                'no_data'       => __('No field data in this revision.', 'griffinforms-form-builder'),
                'empty_prompt'  => __('Select a revision in each column to compare.', 'griffinforms-form-builder'),
                'field_removed' => __('Field removed', 'griffinforms-form-builder'),
            ),
        );

        $config = wp_parse_args($config, $defaults);
        $config['strings'] = wp_parse_args($config['strings'] ?? array(), $defaults['strings']);
        $json = wp_json_encode($config);

        $js  = 'var gfCompareConfig = ' . $json . ';' . PHP_EOL;
        $js .= '(function($) {' . PHP_EOL;
        $js .= '  var $modal = $("[data-gf-revision-compare-modal=\"1\"]");' . PHP_EOL;
        $js .= '  if (!$modal.length || !gfCompareConfig.submission_id) { return; }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  var ajaxUrl  = gfCompareConfig.ajax_url || (typeof ajaxurl !== "undefined" ? ajaxurl : "");' . PHP_EOL;
        $js .= '  var colData  = { a: null, b: null };' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // ── Populate dropdowns from inline revision list ──────────────────────' . PHP_EOL;
        $js .= '  function eventTypeLabel(type) {' . PHP_EOL;
        $js .= '    if (type === "create") { return "' . esc_js(__('Create', 'griffinforms-form-builder')) . '"; }' . PHP_EOL;
        $js .= '    if (type === "restore") { return "' . esc_js(__('Restore', 'griffinforms-form-builder')) . '"; }' . PHP_EOL;
        $js .= '    return "' . esc_js(__('Edit', 'griffinforms-form-builder')) . '";' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  function buildOptionLabel(r) {' . PHP_EOL;
        $js .= '    var label = "#" + r.revision_number + " — " + eventTypeLabel(r.event_type);' . PHP_EOL;
        $js .= '    if (r.actor_label) { label += " ' . esc_js(__('by', 'griffinforms-form-builder')) . ' " + r.actor_label; }' . PHP_EOL;
        $js .= '    if (r.created) { label += " · " + r.created; }' . PHP_EOL;
        $js .= '    return label;' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  $modal.on("show.bs.modal", function() {' . PHP_EOL;
        $js .= '    $modal.find(".gf-compare-revision-select").each(function() {' . PHP_EOL;
        $js .= '      var $sel = $(this);' . PHP_EOL;
        $js .= '      if ($sel.find("option").length > 1) { return; }' . PHP_EOL;
        $js .= '      $.each(gfCompareConfig.revisions, function(i, r) {' . PHP_EOL;
        $js .= '        $sel.append($("<option>").val(r.event_uid).text(buildOptionLabel(r)));' . PHP_EOL;
        $js .= '      });' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // ── AJAX column load ─────────────────────────────────────────────────' . PHP_EOL;
        $js .= '  function loadColumn(target, eventUid) {' . PHP_EOL;
        $js .= '    var $th = $modal.find("[data-gf-compare-col=\"" + target + "\"]");' . PHP_EOL;
        $js .= '    colData[target] = null;' . PHP_EOL;
        $js .= '    $th.find(".gf-compare-col-body").remove();' . PHP_EOL;
        $js .= '    var $loading = $("<div class=\"gf-compare-col-body text-muted small p-2\">").text(gfCompareConfig.strings.loading);' . PHP_EOL;
        $js .= '    $th.append($loading);' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    $.ajax({' . PHP_EOL;
        $js .= '      url: ajaxUrl, method: "POST", dataType: "json",' . PHP_EOL;
        $js .= '      data: {' . PHP_EOL;
        $js .= '        action: gfCompareConfig.preview_action,' . PHP_EOL;
        $js .= '        nonce:  gfCompareConfig.preview_nonce,' . PHP_EOL;
        $js .= '        submission_id: gfCompareConfig.submission_id,' . PHP_EOL;
        $js .= '        event_uid: eventUid' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }).done(function(r) {' . PHP_EOL;
        $js .= '      $loading.remove();' . PHP_EOL;
        $js .= '      if (r && r.success && r.html) {' . PHP_EOL;
        $js .= '        colData[target] = { html: r.html };' . PHP_EOL;
        $js .= '      } else {' . PHP_EOL;
        $js .= '        colData[target] = { html: "<div class=\"text-danger small p-2\">" + gfCompareConfig.strings.error + "</div>" };' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '      maybeRenderTable();' . PHP_EOL;
        $js .= '    }).fail(function() {' . PHP_EOL;
        $js .= '      $loading.remove();' . PHP_EOL;
        $js .= '      colData[target] = { html: "<div class=\"text-danger small p-2\">" + gfCompareConfig.strings.error + "</div>" };' . PHP_EOL;
        $js .= '      maybeRenderTable();' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // ── Build comparison table when both columns are ready ────────────────' . PHP_EOL;
        $js .= '  function parseFieldBlocks(html) {' . PHP_EOL;
        $js .= '    var $wrap = $("<div>").html(html);' . PHP_EOL;
        $js .= '    var map = {};' . PHP_EOL;
        $js .= '    $wrap.find("[data-field-id]").each(function() {' . PHP_EOL;
        $js .= '      var id = $(this).attr("data-field-id");' . PHP_EOL;
        $js .= '      if (id) { map[id] = $(this).clone(); }' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '    return map;' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  function getFieldMeta($block) {' . PHP_EOL;
        $js .= '    return {' . PHP_EOL;
        $js .= '      heading: ($block.attr("data-field-heading") || "").trim(),' . PHP_EOL;
        $js .= '      name:    ($block.attr("data-field-name") || "").trim()' . PHP_EOL;
        $js .= '    };' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  function buildLabelCell(meta) {' . PHP_EOL;
        $js .= '    var $td = $("<td class=\"gf-compare-label-col\">");' . PHP_EOL;
        $js .= '    if (meta.name) {' . PHP_EOL;
        $js .= '      $td.append($("<div class=\"gf-compare-field-name text-uppercase text-muted small fw-semibold\">").text(meta.name));' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    var heading = meta.heading || meta.name || "";' . PHP_EOL;
        $js .= '    $td.append($("<div class=\"gf-compare-field-heading fw-semibold\">").text(heading));' . PHP_EOL;
        $js .= '    return $td;' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  function maybeRenderTable() {' . PHP_EOL;
        $js .= '    if (!colData.a || !colData.b) { return; }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    var mapA = parseFieldBlocks(colData.a.html);' . PHP_EOL;
        $js .= '    var mapB = parseFieldBlocks(colData.b.html);' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    // Union of field ids in document order (A first, then any B-only ids)' . PHP_EOL;
        $js .= '    var ids = [];' . PHP_EOL;
        $js .= '    $.each(mapA, function(id) { ids.push(id); });' . PHP_EOL;
        $js .= '    $.each(mapB, function(id) { if ($.inArray(id, ids) === -1) { ids.push(id); } });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    var $tbody = $modal.find("[data-gf-compare-body]").empty();' . PHP_EOL;
        $js .= '    var $empty = $modal.find("[data-gf-compare-empty]");' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    if (!ids.length) {' . PHP_EOL;
        $js .= '      $empty.show();' . PHP_EOL;
        $js .= '      return;' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    $empty.hide();' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '    $.each(ids, function(i, id) {' . PHP_EOL;
        $js .= '      var $blockA = mapA[id] || null;' . PHP_EOL;
        $js .= '      var $blockB = mapB[id] || null;' . PHP_EOL;
        $js .= '      var meta    = getFieldMeta($blockA || $blockB);' . PHP_EOL;
        $js .= '      if (!meta.heading && !meta.name) { meta.heading = "Field " + id; }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '      var $row = $("<tr>");' . PHP_EOL;
        $js .= '      $row.append(buildLabelCell(meta));' . PHP_EOL;
        $js .= '      $row.append($("<td class=\"gf-compare-col\">").html($blockA ? $blockA[0].outerHTML : ""));' . PHP_EOL;
        $js .= '      $row.append($("<td class=\"gf-compare-col\">").html($blockB ? $blockB[0].outerHTML : ""));' . PHP_EOL;
        $js .= '      $tbody.append($row);' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // ── Dropdown change ───────────────────────────────────────────────────' . PHP_EOL;
        $js .= '  $modal.on("change", ".gf-compare-revision-select", function() {' . PHP_EOL;
        $js .= '    var target   = $(this).attr("data-gf-compare-target");' . PHP_EOL;
        $js .= '    var eventUid = $(this).val();' . PHP_EOL;
        $js .= '    colData[target] = null;' . PHP_EOL;
        $js .= '    $modal.find("[data-gf-compare-body]").empty();' . PHP_EOL;
        $js .= '    $modal.find("[data-gf-compare-empty]").show();' . PHP_EOL;
        $js .= '    if (eventUid) { loadColumn(target, eventUid); }' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // ── Open modal from compare button ────────────────────────────────────' . PHP_EOL;
        $js .= '  $(document).on("click", "[data-gf-compare-revisions-btn=\"1\"]", function() {' . PHP_EOL;
        $js .= '    var bsModal = bootstrap.Modal.getOrCreateInstance($modal[0]);' . PHP_EOL;
        $js .= '    bsModal.show();' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '  // Reset columns when modal closes' . PHP_EOL;
        $js .= '  $modal.on("hidden.bs.modal", function() {' . PHP_EOL;
        $js .= '    colData = { a: null, b: null };' . PHP_EOL;
        $js .= '    $modal.find(".gf-compare-revision-select").val("").trigger("change.reset");' . PHP_EOL;
        $js .= '    $modal.find("[data-gf-compare-body]").empty();' . PHP_EOL;
        $js .= '    $modal.find("[data-gf-compare-col] .gf-compare-col-body").remove();' . PHP_EOL;
        $js .= '    $modal.find("[data-gf-compare-empty]").show();' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '' . PHP_EOL;
        $js .= '}(jQuery));' . PHP_EOL;

        return $js;
    }

    protected function loadLogsTimeline()
    {
        $js  = 'var gfLogsTimelines = document.querySelectorAll(".gf-log-timeline[data-gf-log-ajax=\\"1\\"]");' . PHP_EOL;
        $js .= 'if (!gfLogsTimelines.length) { return; }' . PHP_EOL;
        $js .= 'gfLogsTimelines.forEach(function(timeline){' . PHP_EOL;
        $js .= '  var isLoading = false;' . PHP_EOL;
        $js .= '  var hasMore = true;' . PHP_EOL;
        $js .= '  var offset = parseInt(timeline.getAttribute("data-offset") || "0", 10);' . PHP_EOL;
        $js .= '  var perPage = parseInt(timeline.getAttribute("data-per-page") || "20", 10);' . PHP_EOL;
        $js .= '  var itemId = timeline.getAttribute("data-item-id");' . PHP_EOL;
        $js .= '  var nonce = timeline.getAttribute("data-nonce");' . PHP_EOL;
        $js .= '  var emptyMessage = timeline.getAttribute("data-empty-message") || "";' . PHP_EOL;
        $js .= '  var placeholderTemplate = timeline.querySelector("[data-gf-log-placeholder]");' . PHP_EOL;
        $js .= '  var refreshIntervalMs = 15000;' . PHP_EOL;
        $js .= '  var refreshTimer = null;' . PHP_EOL;
        $js .= '  var includeJobs = timeline.getAttribute("data-include-jobs") === "1";' . PHP_EOL;
        $js .= '  var sentinel = timeline.querySelector("[data-gf-log-sentinel]");' . PHP_EOL;
        $js .= '  var initialLoading = timeline.querySelector("[data-gf-log-loading]");' . PHP_EOL;
        $js .= '  if (!sentinel) {' . PHP_EOL;
        $js .= '    sentinel = document.createElement("div");' . PHP_EOL;
        $js .= '    sentinel.className = "gf-log-sentinel";' . PHP_EOL;
        $js .= '    timeline.appendChild(sentinel);' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  var buildPlaceholders = function(count) {' . PHP_EOL;
        $js .= '    var html = "";' . PHP_EOL;
        $js .= '    if (!placeholderTemplate) { return html; }' . PHP_EOL;
        $js .= '    for (var i = 0; i < count; i++) { html += placeholderTemplate.innerHTML; }' . PHP_EOL;
        $js .= '    return html;' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  var ensureSentinel = function() {' . PHP_EOL;
        $js .= '    if (!sentinel || !sentinel.parentNode) {' . PHP_EOL;
        $js .= '      sentinel = document.createElement("div");' . PHP_EOL;
        $js .= '      sentinel.className = "gf-log-sentinel";' . PHP_EOL;
        $js .= '      timeline.appendChild(sentinel);' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  var insertBeforeSentinel = function(node) {' . PHP_EOL;
        $js .= '    ensureSentinel();' . PHP_EOL;
        $js .= '    if (sentinel && sentinel.parentNode === timeline) {' . PHP_EOL;
        $js .= '      timeline.insertBefore(node, sentinel);' . PHP_EOL;
        $js .= '    } else {' . PHP_EOL;
        $js .= '      timeline.appendChild(node);' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  var removePlaceholders = function() {' . PHP_EOL;
        $js .= '    var nodes = timeline.querySelectorAll(".gf-log-entry-placeholder");' . PHP_EOL;
        $js .= '    nodes.forEach(function(node){ node.parentNode.removeChild(node); });' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  var fetchLogs = function(options) {' . PHP_EOL;
        $js .= '    options = options || {};' . PHP_EOL;
        $js .= '    var isRefresh = options.refresh === true;' . PHP_EOL;
        $js .= '    var refreshOffset = isRefresh ? 0 : offset;' . PHP_EOL;
        $js .= '    var limit = perPage;' . PHP_EOL;
        $js .= '    if (isLoading || (!hasMore && !isRefresh)) { return; }' . PHP_EOL;
        $js .= '    isLoading = true;' . PHP_EOL;
        $js .= '    if (!isRefresh) {' . PHP_EOL;
        $js .= '      var placeholderHtml = buildPlaceholders(perPage);' . PHP_EOL;
        $js .= '      if (placeholderHtml) {' . PHP_EOL;
        $js .= '        var holder = document.createElement("div");' . PHP_EOL;
        $js .= '        holder.innerHTML = placeholderHtml;' . PHP_EOL;
        $js .= '        while (holder.firstChild) {' . PHP_EOL;
        $js .= '          insertBeforeSentinel(holder.firstChild);' . PHP_EOL;
        $js .= '        }' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    $.ajax({' . PHP_EOL;
        $js .= '      url: (typeof ajaxurl !== "undefined" ? ajaxurl : ""), ' . PHP_EOL;
        $js .= '      method: "POST",' . PHP_EOL;
        $js .= '      dataType: "json",' . PHP_EOL;
        $js .= '      data: {' . PHP_EOL;
        $js .= '        action: "griffinforms_get_submission_logs",' . PHP_EOL;
        $js .= '        nonce: nonce,' . PHP_EOL;
        $js .= '        submission_id: itemId,' . PHP_EOL;
        $js .= '        offset: refreshOffset,' . PHP_EOL;
        $js .= '        limit: limit,' . PHP_EOL;
        $js .= '        include_jobs: includeJobs ? "1" : "0"' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }).done(function(response){' . PHP_EOL;
        $js .= '      if (!isRefresh) {' . PHP_EOL;
        $js .= '        removePlaceholders();' . PHP_EOL;
        $js .= '        if (initialLoading) { initialLoading.remove(); initialLoading = null; }' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '      if (response && response.success) {' . PHP_EOL;
        $js .= '        var html = response.html || (response.data ? response.data.html : "");' . PHP_EOL;
        $js .= '        var hasMoreResponse = response.has_more;' . PHP_EOL;
        $js .= '        if (response.data) { hasMoreResponse = response.data.has_more; }' . PHP_EOL;
        $js .= '        if (html) {' . PHP_EOL;
        $js .= '          if (isRefresh) {' . PHP_EOL;
        $js .= '            var existing = timeline.querySelectorAll(".gf-log-entry");' . PHP_EOL;
        $js .= '            existing.forEach(function(node){ if (node.parentNode) { node.parentNode.removeChild(node); } });' . PHP_EOL;
        $js .= '          }' . PHP_EOL;
        $js .= '          var wrapper = document.createElement("div");' . PHP_EOL;
        $js .= '          wrapper.innerHTML = html;' . PHP_EOL;
        $js .= '          while (wrapper.firstChild) {' . PHP_EOL;
        $js .= '            insertBeforeSentinel(wrapper.firstChild);' . PHP_EOL;
        $js .= '          }' . PHP_EOL;
        $js .= '          if (!isRefresh) {' . PHP_EOL;
        $js .= '            offset += perPage;' . PHP_EOL;
        $js .= '          }' . PHP_EOL;
        $js .= '        } else if (offset === 0) {' . PHP_EOL;
        $js .= '          var emptyText = response.empty_message || (response.data ? response.data.empty_message : "") || emptyMessage;' . PHP_EOL;
        $js .= '          if (emptyText) {' . PHP_EOL;
        $js .= '            var emptyNode = document.createElement("div");' . PHP_EOL;
        $js .= '            emptyNode.className = "gf-log-empty text-muted small";' . PHP_EOL;
        $js .= '            emptyNode.textContent = emptyText;' . PHP_EOL;
        $js .= '            insertBeforeSentinel(emptyNode);' . PHP_EOL;
        $js .= '          }' . PHP_EOL;
        $js .= '        }' . PHP_EOL;
        $js .= '        if (!isRefresh) {' . PHP_EOL;
        $js .= '          hasMore = hasMoreResponse !== false;' . PHP_EOL;
        $js .= '        }' . PHP_EOL;
        $js .= '        if (isRefresh) {' . PHP_EOL;
        $js .= '          $(document).trigger("griffinforms:submission-logs-refreshed", [{' . PHP_EOL;
        $js .= '            submissionId: itemId,' . PHP_EOL;
        $js .= '            hasFutureJobs: timeline.querySelector(".gf-log-entry-future") !== null' . PHP_EOL;
        $js .= '          }]);' . PHP_EOL;
        $js .= '        }' . PHP_EOL;
        $js .= '      } else {' . PHP_EOL;
        $js .= '        if (!isRefresh) { removePlaceholders(); }' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }).fail(function(){' . PHP_EOL;
        $js .= '      if (!isRefresh) { removePlaceholders(); }' . PHP_EOL;
        $js .= '    }).always(function(){' . PHP_EOL;
        $js .= '      isLoading = false;' . PHP_EOL;
        $js .= '      if (!hasMore) {' . PHP_EOL;
        $js .= '        if (sentinel) { sentinel.remove(); }' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  var scheduleRefresh = function() {' . PHP_EOL;
        $js .= '    if (!includeJobs) { return; }' . PHP_EOL;
        $js .= '    if (refreshTimer) { clearInterval(refreshTimer); }' . PHP_EOL;
        $js .= '    refreshTimer = setInterval(function(){' . PHP_EOL;
        $js .= '      if (timeline.querySelector(".gf-log-entry-future")) {' . PHP_EOL;
        $js .= '        fetchLogs({ refresh: true });' . PHP_EOL;
        $js .= '      } else if (refreshTimer) {' . PHP_EOL;
        $js .= '        clearInterval(refreshTimer);' . PHP_EOL;
        $js .= '        refreshTimer = null;' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    }, refreshIntervalMs);' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  if ("IntersectionObserver" in window && sentinel) {' . PHP_EOL;
        $js .= '    var observer = new IntersectionObserver(function(entries){' . PHP_EOL;
        $js .= '      entries.forEach(function(entry){' . PHP_EOL;
        $js .= '        if (entry.isIntersecting) { fetchLogs(); }' . PHP_EOL;
        $js .= '      });' . PHP_EOL;
        $js .= '    }, { root: timeline, rootMargin: "0px 0px 200px 0px" });' . PHP_EOL;
        $js .= '    observer.observe(sentinel);' . PHP_EOL;
        $js .= '  } else {' . PHP_EOL;
        $js .= '    timeline.addEventListener("scroll", function(){' . PHP_EOL;
        $js .= '      if (timeline.scrollTop + timeline.clientHeight >= timeline.scrollHeight - 100) {' . PHP_EOL;
        $js .= '        fetchLogs();' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '    });' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  $(document).on("griffinforms:submission-accounts-updated", function(event, payload){' . PHP_EOL;
        $js .= '    payload = payload || {};' . PHP_EOL;
        $js .= '    if (parseInt(payload.submissionId || 0, 10) !== parseInt(itemId || 0, 10)) { return; }' . PHP_EOL;
        $js .= '    fetchLogs({ refresh: true });' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '  fetchLogs();' . PHP_EOL;
        $js .= '  scheduleRefresh();' . PHP_EOL;
        $js .= '});' . PHP_EOL;

        return $js;
    }

    protected function loadAccountsPanel()
    {
        $config = isset($this->data['accounts']) && is_array($this->data['accounts']) ? $this->data['accounts'] : array();
        $defaults = array(
            'ajax_url' => admin_url('admin-ajax.php'),
            'submission_id' => 0,
            'get_state_action' => 'griffinforms_get_submission_accounts',
            'get_state_nonce' => wp_create_nonce('griffinforms_get_submission_accounts'),
            'validate_action' => 'griffinforms_validate_submission_account_action',
            'validate_nonce' => wp_create_nonce('griffinforms_validate_submission_account_action'),
            'execute_action' => 'griffinforms_execute_submission_account_action',
            'execute_nonce' => wp_create_nonce('griffinforms_execute_submission_account_action'),
            'strings' => array(
                'title' => __('User Registration', 'griffinforms-form-builder'),
                'empty' => __('No user registration activity has been recorded for this submission yet.', 'griffinforms-form-builder'),
                'loading' => __('Loading user registration activity...', 'griffinforms-form-builder'),
                'error' => __('Unable to load user registration activity for this submission.', 'griffinforms-form-builder'),
                'action_error' => __('Unable to complete this user registration action.', 'griffinforms-form-builder'),
                'confirm_prefix' => __('Confirm action:', 'griffinforms-form-builder'),
                'confirm_cancel' => __('Cancel', 'griffinforms-form-builder'),
                'confirm_button' => __('Confirm', 'griffinforms-form-builder'),
                'working' => __('Working...', 'griffinforms-form-builder'),
                'action_success' => __('User registration action completed.', 'griffinforms-form-builder'),
                'setup_singular' => __('1 setup', 'griffinforms-form-builder'),
                'setup_plural' => __('%d setups', 'griffinforms-form-builder'),
                'account_singular' => __('1 account', 'griffinforms-form-builder'),
                'account_plural' => __('%d accounts', 'griffinforms-form-builder'),
                'mixed_results' => __('Mixed results', 'griffinforms-form-builder'),
                'unknown' => __('Unknown', 'griffinforms-form-builder'),
                'target_unknown' => __('Unknown target', 'griffinforms-form-builder'),
                'meta_password_setup' => __('Password setup', 'griffinforms-form-builder'),
                'summary_create_automatically' => __('Create automatically', 'griffinforms-form-builder'),
                'summary_approve_before_creating' => __('Approve before creating the user', 'griffinforms-form-builder'),
                'summary_review_first' => __('Review first, then decide', 'griffinforms-form-builder'),
                'summary_block_duplicate' => __('Block duplicate email', 'griffinforms-form-builder'),
                'summary_update_existing' => __('Update existing user', 'griffinforms-form-builder'),
                'summary_accept_duplicate' => __('Accept duplicate without changes', 'griffinforms-form-builder'),
            ),
        );

        $config = wp_parse_args($config, $defaults);
        $config['strings'] = wp_parse_args(
            isset($config['strings']) && is_array($config['strings']) ? $config['strings'] : array(),
            $defaults['strings']
        );

        $json = wp_json_encode($config);

        $js  = 'var gfAccountsConfig = ' . $json . ';' . PHP_EOL;
        $js .= 'var $accountsPanel = $("#gf-submission-accounts-panel");' . PHP_EOL;
        $js .= 'if (!$accountsPanel.length || !gfAccountsConfig.submission_id) { return; }' . PHP_EOL;
        $js .= 'var gfSubmissionUiLocks = window.gfSubmissionUiLocks || {' . PHP_EOL;
        $js .= '  locks: {},' . PHP_EOL;
        $js .= '  setRegionLock: function(regionKey, $region, isLocked) {' . PHP_EOL;
        $js .= '    if (!$region || !$region.length || !regionKey) { return; }' . PHP_EOL;
        $js .= '    this.locks[regionKey] = !!isLocked;' . PHP_EOL;
        $js .= '    $region.toggleClass("is-busy", !!isLocked);' . PHP_EOL;
        $js .= '    $region.attr("aria-busy", isLocked ? "true" : "false");' . PHP_EOL;
        $js .= '    $region.find("[data-gf-submission-lockable=\"1\"]").prop("disabled", !!isLocked);' . PHP_EOL;
        $js .= '  },' . PHP_EOL;
        $js .= '  isRegionLocked: function(regionKey) {' . PHP_EOL;
        $js .= '    return !!this.locks[regionKey];' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'window.gfSubmissionUiLocks = gfSubmissionUiLocks;' . PHP_EOL;
        $js .= 'var gfAccountsBusy = false;' . PHP_EOL;
        $js .= 'var gfAccountsNotice = null;' . PHP_EOL;
        $js .= 'var gfEscapeAccountsHtml = function(value) {' . PHP_EOL;
        $js .= '  return $("<div>").text(value === undefined || value === null ? "" : String(value)).html();' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildAccountsNoticeHtml = function(notice) {' . PHP_EOL;
        $js .= '  if (!notice || !notice.message) { return ""; }' . PHP_EOL;
        $js .= '  var variant = notice.variant || "info";' . PHP_EOL;
        $js .= '  if (["success", "warning", "danger", "info"].indexOf(variant) === -1) { variant = "info"; }' . PHP_EOL;
        $js .= '  return \'<div class="gf-submission-accounts-notice gf-notice gf-notice-\' + gfEscapeAccountsHtml(variant) + \' mb-3" role="alert"><div>\' + gfEscapeAccountsHtml(notice.message) + \'</div></div>\';' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfApplyAccountsNotice = function() {' . PHP_EOL;
        $js .= '  $accountsPanel.find(".gf-submission-accounts-notice").remove();' . PHP_EOL;
        $js .= '  if (!gfAccountsNotice || !gfAccountsNotice.message) { return; }' . PHP_EOL;
        $js .= '  $accountsPanel.prepend(gfBuildAccountsNoticeHtml(gfAccountsNotice));' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfSetAccountsNotice = function(message, variant) {' . PHP_EOL;
        $js .= '  gfAccountsNotice = message ? { message: String(message), variant: variant || "info" } : null;' . PHP_EOL;
        $js .= '  gfApplyAccountsNotice();' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfFormatAccountsSummaryLabel = function(value, type) {' . PHP_EOL;
        $js .= '  var normalized = String(value || "");' . PHP_EOL;
        $js .= '  if (!normalized) { return ""; }' . PHP_EOL;
        $js .= '  var creationModeMap = {' . PHP_EOL;
        $js .= '    "Immediate Create": gfAccountsConfig.strings.summary_create_automatically,' . PHP_EOL;
        $js .= '    "Pending Admin Activation": gfAccountsConfig.strings.summary_approve_before_creating,' . PHP_EOL;
        $js .= '    "Manual Review Only": gfAccountsConfig.strings.summary_review_first,' . PHP_EOL;
        $js .= '    "Require admin approval": gfAccountsConfig.strings.summary_approve_before_creating,' . PHP_EOL;
        $js .= '    "Review manually": gfAccountsConfig.strings.summary_review_first' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  var existingBehaviorMap = {' . PHP_EOL;
        $js .= '    "Show Error": gfAccountsConfig.strings.summary_block_duplicate,' . PHP_EOL;
        $js .= '    "Update Existing User": gfAccountsConfig.strings.summary_update_existing,' . PHP_EOL;
        $js .= '    "Accept Form / Do Nothing": gfAccountsConfig.strings.summary_accept_duplicate' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  if (type === "creation_mode" && creationModeMap[normalized]) { return creationModeMap[normalized]; }' . PHP_EOL;
        $js .= '  if (type === "existing_behavior" && existingBehaviorMap[normalized]) { return existingBehaviorMap[normalized]; }' . PHP_EOL;
        $js .= '  return normalized;' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfAccountsStatusClass = function(statusKey) {' . PHP_EOL;
        $js .= '  var map = {' . PHP_EOL;
        $js .= '    queued: "secondary",' . PHP_EOL;
        $js .= '    processing: "info",' . PHP_EOL;
        $js .= '    pending_admin_activation: "warning",' . PHP_EOL;
        $js .= '    manual_review_only: "warning",' . PHP_EOL;
        $js .= '    blocked: "secondary",' . PHP_EOL;
        $js .= '    failed: "danger",' . PHP_EOL;
        $js .= '    completed: "success"' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  return map[statusKey] || "secondary";' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfAccountsConfirmationClass = function(statusKey) {' . PHP_EOL;
        $js .= '  var map = {' . PHP_EOL;
        $js .= '    sent: "success",' . PHP_EOL;
        $js .= '    deferred: "warning",' . PHP_EOL;
        $js .= '    failed: "danger",' . PHP_EOL;
        $js .= '    blocked: "secondary",' . PHP_EOL;
        $js .= '    skipped: "secondary",' . PHP_EOL;
        $js .= '    pending_runtime: "info",' . PHP_EOL;
        $js .= '    not_requested: "secondary"' . PHP_EOL;
        $js .= '  };' . PHP_EOL;
        $js .= '  return map[statusKey] || "secondary";' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildStatusText = function(label, className) {' . PHP_EOL;
        $js .= '  if (!label) { return ""; }' . PHP_EOL;
        $js .= '  return \'<span class="gf-submission-account-status gf-submission-account-status-\' + gfEscapeAccountsHtml(className || "secondary") + \'">\' + gfEscapeAccountsHtml(label) + \'</span>\';' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfRenderAccountsLoading = function() {' . PHP_EOL;
        $js .= '  $accountsPanel.html(' . PHP_EOL;
        $js .= '    \'<div class="gf-submission-accounts-loading text-muted small">\' +' . PHP_EOL;
        $js .= '      \'<div class="placeholder-glow mb-2"><span class="placeholder col-9"></span></div>\' +' . PHP_EOL;
        $js .= '      \'<div class="placeholder-glow mb-2"><span class="placeholder col-7"></span></div>\' +' . PHP_EOL;
        $js .= '      \'<div class="placeholder-glow"><span class="placeholder col-10"></span></div>\' +' . PHP_EOL;
        $js .= '    \'</div>\'' . PHP_EOL;
        $js .= '  );' . PHP_EOL;
        $js .= '  gfApplyAccountsNotice();' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfSetAccountsBusy = function(isBusy) {' . PHP_EOL;
        $js .= '  gfAccountsBusy = !!isBusy;' . PHP_EOL;
        $js .= '  gfSubmissionUiLocks.setRegionLock("submission_accounts", $accountsPanel, gfAccountsBusy);' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfRenderAccountsEmpty = function(message) {' . PHP_EOL;
        $js .= '  $accountsPanel.html(' . PHP_EOL;
        $js .= '    \'<div class="gf-submission-accounts-empty border rounded p-3 text-muted small">\' +' . PHP_EOL;
        $js .= '      gfEscapeAccountsHtml(message || gfAccountsConfig.strings.empty) +' . PHP_EOL;
        $js .= '    \'</div>\'' . PHP_EOL;
        $js .= '  );' . PHP_EOL;
        $js .= '  gfApplyAccountsNotice();' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildAccountsSummary = function(accounts) {' . PHP_EOL;
        $js .= '  var setups = Array.isArray(accounts.setups) ? accounts.setups : [];' . PHP_EOL;
        $js .= '  var targetCount = 0;' . PHP_EOL;
        $js .= '  setups.forEach(function(setup){ targetCount += parseInt(setup.target_count || 0, 10) || 0; });' . PHP_EOL;
        $js .= '  var setupLabel = setups.length === 1 ? gfAccountsConfig.strings.setup_singular : String(gfAccountsConfig.strings.setup_plural || "").replace("%d", String(setups.length));' . PHP_EOL;
        $js .= '  var targetLabel = targetCount === 1 ? gfAccountsConfig.strings.account_singular : String(gfAccountsConfig.strings.account_plural || "").replace("%d", String(targetCount));' . PHP_EOL;
        $js .= '  return \'<div class="gf-submission-accounts-summary d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3 text-muted small">\' +' . PHP_EOL;
        $js .= '    \'<div class="gf-submission-accounts-summary-stat">\' + gfEscapeAccountsHtml(setupLabel) + \'</div>\' +' . PHP_EOL;
        $js .= '    \'<div class="gf-submission-accounts-summary-stat text-md-end">\' + gfEscapeAccountsHtml(targetLabel) + \'</div>\' +' . PHP_EOL;
        $js .= '  \'</div>\';' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildActionButtons = function(target) {' . PHP_EOL;
        $js .= '  var available = target && target.available_actions ? target.available_actions : {};' . PHP_EOL;
        $js .= '  var keys = Object.keys(available);' . PHP_EOL;
        $js .= '  if (!keys.length) {' . PHP_EOL;
        $js .= '    return "";' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  var html = \'<div class="gf-submission-account-actions-wrap">\';' . PHP_EOL;
        $js .= '  html += \'<div class="gf-submission-account-actions d-flex flex-wrap gap-2">\';' . PHP_EOL;
        $js .= '  keys.forEach(function(actionType){' . PHP_EOL;
        $js .= '    var item = available[actionType] || {};' . PHP_EOL;
        $js .= '    html += \'<button type="button" class="button button-small gf-submission-account-action" data-gf-submission-lockable="1" aria-expanded="false" data-action-id="\' + gfEscapeAccountsHtml(target.action_id || "") + \'" data-action-type="\' + gfEscapeAccountsHtml(actionType) + \'" data-action-label="\' + gfEscapeAccountsHtml(item.label || "") + \'" data-target-email="\' + gfEscapeAccountsHtml(target.target_email || "") + \'">\' + gfEscapeAccountsHtml(item.label || actionType) + \'</button>\';' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '  html += \'</div>\';' . PHP_EOL;
        $js .= '  html += \'<div class="gf-submission-account-confirmation-host"></div>\';' . PHP_EOL;
        $js .= '  html += \'</div>\';' . PHP_EOL;
        $js .= '  return html;' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildConfirmationDescription = function(actionType) {' . PHP_EOL;
        $js .= '  var descriptions = (gfAccountsConfig.strings && gfAccountsConfig.strings.confirm_descriptions) ? gfAccountsConfig.strings.confirm_descriptions : {};' . PHP_EOL;
        $js .= '  return descriptions[actionType] || "";' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildConfirmationPanel = function(actionId, actionType, actionLabel, targetEmail) {' . PHP_EOL;
        $js .= '  var description = gfBuildConfirmationDescription(actionType);' . PHP_EOL;
        $js .= '  var html = \'<div class="gf-submission-account-confirmation-panel" role="group" aria-label="\' + gfEscapeAccountsHtml(gfAccountsConfig.strings.confirm_title || "") + \'">\';' . PHP_EOL;
        $js .= '  html += \'<div class="gf-submission-account-confirmation-title">\' + gfEscapeAccountsHtml(actionLabel || actionType || "") + \'</div>\';' . PHP_EOL;
        $js .= '  if (targetEmail) { html += \'<div class="gf-submission-account-confirmation-target">\' + gfEscapeAccountsHtml(targetEmail) + \'</div>\'; }' . PHP_EOL;
        $js .= '  if (description) { html += \'<div class="gf-submission-account-confirmation-copy">\' + gfEscapeAccountsHtml(description) + \'</div>\'; }' . PHP_EOL;
        $js .= '  html += \'<div class="gf-submission-account-confirmation-actions d-flex flex-wrap align-items-center gap-2">\';' . PHP_EOL;
        $js .= '  html += \'<button type="button" class="button button-small gf-submission-account-confirm-cancel" data-gf-submission-lockable="1">\' + gfEscapeAccountsHtml(gfAccountsConfig.strings.confirm_cancel || "") + \'</button>\';' . PHP_EOL;
        $js .= '  html += \'<button type="button" class="button button-small button-primary gf-submission-account-confirm-execute" data-gf-submission-lockable="1" data-action-id="\' + gfEscapeAccountsHtml(actionId || "") + \'" data-action-type="\' + gfEscapeAccountsHtml(actionType || "") + \'" data-action-label="\' + gfEscapeAccountsHtml(actionLabel || actionType || "") + \'" data-target-email="\' + gfEscapeAccountsHtml(targetEmail || "") + \'">\' + gfEscapeAccountsHtml(gfAccountsConfig.strings.confirm_button || "") + \'</button>\';' . PHP_EOL;
        $js .= '  html += \'<span class="gf-submission-account-confirmation-working" aria-live="polite"><span class="spinner-border spinner-border-sm" aria-hidden="true"></span><span class="gf-submission-account-confirmation-working-text">\' + gfEscapeAccountsHtml(gfAccountsConfig.strings.working || "") + \'</span></span>\';' . PHP_EOL;
        $js .= '  html += \'</div>\';' . PHP_EOL;
        $js .= '  html += \'</div>\';' . PHP_EOL;
        $js .= '  return html;' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfCloseAccountsConfirmations = function() {' . PHP_EOL;
        $js .= '  $accountsPanel.find(".gf-submission-account-confirmation-host").empty();' . PHP_EOL;
        $js .= '  $accountsPanel.find(".gf-submission-account-action").removeClass("is-awaiting-confirmation").attr("aria-expanded", "false");' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfOpenAccountsConfirmation = function($button, actionId, actionType, actionLabel, targetEmail) {' . PHP_EOL;
        $js .= '  var $wrap = $button.closest(".gf-submission-account-actions-wrap");' . PHP_EOL;
        $js .= '  var $host = $wrap.find(".gf-submission-account-confirmation-host").first();' . PHP_EOL;
        $js .= '  if (!$host.length) { return; }' . PHP_EOL;
        $js .= '  gfCloseAccountsConfirmations();' . PHP_EOL;
        $js .= '  $button.addClass("is-awaiting-confirmation").attr("aria-expanded", "true");' . PHP_EOL;
        $js .= '  $host.html(gfBuildConfirmationPanel(actionId, actionType, actionLabel, targetEmail));' . PHP_EOL;
        $js .= '  var $confirm = $host.find(".gf-submission-account-confirm-execute").first();' . PHP_EOL;
        $js .= '  if ($confirm.length) { $confirm.trigger("focus"); }' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildRuntimeIssues = function(target) {' . PHP_EOL;
        $js .= '  var issues = Array.isArray(target.runtime_issues) ? target.runtime_issues : [];' . PHP_EOL;
        $js .= '  if (!issues.length) { return ""; }' . PHP_EOL;
        $js .= '  var html = \'<div class="gf-submission-account-issues mt-3"><div class="gf-notice gf-notice-warning mb-0"><div>\';' . PHP_EOL;
        $js .= '  issues.forEach(function(issue){' . PHP_EOL;
        $js .= '    var message = "";' . PHP_EOL;
        $js .= '    if (issue && typeof issue === "object") {' . PHP_EOL;
        $js .= '      message = issue.message || issue.code || "";' . PHP_EOL;
        $js .= '    } else {' . PHP_EOL;
        $js .= '      message = issue || "";' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    if (message) { html += \'<div>\' + gfEscapeAccountsHtml(message) + \'</div>\'; }' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '  html += \'</div></div></div>\';' . PHP_EOL;
        $js .= '  return html;' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildMetaRow = function(label, value) {' . PHP_EOL;
        $js .= '  if (!value) { return ""; }' . PHP_EOL;
        $js .= '  return \'<div class="gf-submission-account-meta-row"><div class="gf-submission-account-meta-label">\' + gfEscapeAccountsHtml(label) + \'</div><div class="gf-submission-account-meta-value">\' + gfEscapeAccountsHtml(value) + \'</div></div>\';' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildMetaLinkRow = function(label, value, href) {' . PHP_EOL;
        $js .= '  if (!value || !href) { return gfBuildMetaRow(label, value); }' . PHP_EOL;
        $js .= '  return \'<div class="gf-submission-account-meta-row"><div class="gf-submission-account-meta-label">\' + gfEscapeAccountsHtml(label) + \'</div><div class="gf-submission-account-meta-value"><a class="gf-submission-account-meta-link" href="\' + gfEscapeAccountsHtml(href) + \'" target="_blank" rel="noopener noreferrer">\' + gfEscapeAccountsHtml(value) + \'<span class="gf-submission-account-meta-link-icon" aria-hidden="true"></span></a></div></div>\';' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildStatusMetaValue = function(label, statusClass, reason, sentAt) {' . PHP_EOL;
        $js .= '  var html = \'<div class="gf-submission-account-status-block">\';' . PHP_EOL;
        $js .= '  html += \'<div class="gf-submission-account-status-chip">\' + gfBuildStatusText(label || gfAccountsConfig.strings.unknown || "", statusClass || "secondary") + \'</div>\';' . PHP_EOL;
        $js .= '  html += \'<div class="gf-submission-account-status-details">\';' . PHP_EOL;
        $js .= '  if (reason) { html += \'<div class="gf-submission-account-status-reason">\' + gfEscapeAccountsHtml(reason) + \'</div>\'; }' . PHP_EOL;
        $js .= '  if (sentAt) { html += \'<div class="gf-submission-account-status-time">\' + gfEscapeAccountsHtml(sentAt) + \'</div>\'; }' . PHP_EOL;
        $js .= '  html += \'</div>\';' . PHP_EOL;
        $js .= '  html += \'</div>\';' . PHP_EOL;
        $js .= '  return html;' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildTargetMeta = function(target, setup) {' . PHP_EOL;
        $js .= '  var html = \'<div class="gf-submission-account-meta-list">\';' . PHP_EOL;
        $js .= '  if ((parseInt(setup.target_count || 0, 10) || 0) > 1) {' . PHP_EOL;
        $js .= '    html += gfBuildMetaRow("Target", String((parseInt(target.target_index || 0, 10) || 0) + 1));' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  if (target.display_username || target.resolved_username) { html += gfBuildMetaRow("Username", target.display_username || target.resolved_username); }' . PHP_EOL;
        $js .= '  if (parseInt(target.user_id || 0, 10) > 0) { html += gfBuildMetaLinkRow("User ID", String(target.user_id), target.user_edit_url || ""); }' . PHP_EOL;
        $js .= '  if (target.admin_action_summary && target.admin_action_summary.value) {' . PHP_EOL;
        $js .= '    html += gfBuildMetaRow(target.admin_action_summary.label || "Admin action", target.admin_action_summary.value);' . PHP_EOL;
        $js .= '    if (target.admin_action_summary.timestamp) { html += gfBuildMetaRow("Action time", target.admin_action_summary.timestamp); }' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  if (target.status_message) { html += gfBuildMetaRow("Result", target.status_message); }' . PHP_EOL;
        $js .= '  var passwordSetup = target.password_setup_email || {};' . PHP_EOL;
        $js .= '  if (passwordSetup.enabled && passwordSetup.label) {' . PHP_EOL;
        $js .= '    var passwordSetupValue = gfBuildStatusMetaValue(passwordSetup.label || gfAccountsConfig.strings.unknown || "", gfAccountsConfirmationClass(passwordSetup.status || ""), passwordSetup.reason || "", passwordSetup.sent_at || "");' . PHP_EOL;
        $js .= '    html += \'<div class="gf-submission-account-meta-row"><div class="gf-submission-account-meta-label">\' + gfEscapeAccountsHtml(gfAccountsConfig.strings.meta_password_setup || "") + \'</div><div class="gf-submission-account-meta-value gf-submission-account-meta-value-confirmation">\' + passwordSetupValue + \'</div></div>\';' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  var confirmation = target.confirmation_email || {};' . PHP_EOL;
        $js .= '  var confirmationValue = gfBuildStatusMetaValue(confirmation.label || gfAccountsConfig.strings.unknown || "", gfAccountsConfirmationClass(confirmation.status || ""), confirmation.reason || "", confirmation.sent_at || "");' . PHP_EOL;
        $js .= '  html += \'<div class="gf-submission-account-meta-row"><div class="gf-submission-account-meta-label">Confirmation</div><div class="gf-submission-account-meta-value gf-submission-account-meta-value-confirmation">\' + confirmationValue + \'</div></div>\';' . PHP_EOL;
        $js .= '  html += \'</div>\';' . PHP_EOL;
        $js .= '  return html;' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildTargetRow = function(target, setup) {' . PHP_EOL;
        $js .= '  var status = target.status || {};' . PHP_EOL;
        $js .= '  var statusBadge = gfBuildStatusText(status.label || gfAccountsConfig.strings.unknown || "", gfAccountsStatusClass(status.key || ""));' . PHP_EOL;
        $js .= '  var avatarUrl = target.avatar_url || "";' . PHP_EOL;
        $js .= '  return \'<div class="gf-submission-account-target py-3">\' +' . PHP_EOL;
        $js .= '    \'<div class="d-flex flex-wrap align-items-start justify-content-between gap-3">\' +' . PHP_EOL;
        $js .= '      \'<div class="gf-submission-account-target-main flex-grow-1">\' +' . PHP_EOL;
        $js .= '        \'<div class="gf-submission-account-target-header d-flex flex-wrap align-items-center gap-2 mb-2">\' +' . PHP_EOL;
        $js .= '          \'<span class="gf-submission-account-target-avatar-wrap"><img class="gf-submission-account-target-avatar" src="\' + gfEscapeAccountsHtml(avatarUrl) + \'" alt="" loading="lazy" decoding="async"></span>\' +' . PHP_EOL;
        $js .= '          \'<div class="gf-submission-account-target-email">\' + gfEscapeAccountsHtml(target.target_email || gfAccountsConfig.strings.target_unknown || "") + \'</div>\' +' . PHP_EOL;
        $js .= '          statusBadge +' . PHP_EOL;
        $js .= '        \'</div>\' +' . PHP_EOL;
        $js .= '        gfBuildTargetMeta(target, setup) +' . PHP_EOL;
        $js .= '        gfBuildRuntimeIssues(target) +' . PHP_EOL;
        $js .= '      \'</div>\' +' . PHP_EOL;
        $js .= '      \'<div class="gf-submission-account-target-side">\' +' . PHP_EOL;
        $js .= '        gfBuildActionButtons(target) +' . PHP_EOL;
        $js .= '      \'</div>\' +' . PHP_EOL;
        $js .= '    \'</div>\' +' . PHP_EOL;
        $js .= '  \'</div>\';' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfBuildSetupCard = function(setup) {' . PHP_EOL;
        $js .= '  var targets = Array.isArray(setup.targets) ? setup.targets : [];' . PHP_EOL;
        $js .= '  var summaryBits = [];' . PHP_EOL;
        $js .= '  if (setup.creation_mode_label) { summaryBits.push(gfFormatAccountsSummaryLabel(setup.creation_mode_label, "creation_mode")); }' . PHP_EOL;
        $js .= '  if (setup.existing_account_behavior_label) { summaryBits.push(gfFormatAccountsSummaryLabel(setup.existing_account_behavior_label, "existing_behavior")); }' . PHP_EOL;
        $js .= '  summaryBits.push((parseInt(setup.target_count || 0, 10) || 0) === 1 ? gfAccountsConfig.strings.account_singular : String(gfAccountsConfig.strings.account_plural || "").replace("%d", String(parseInt(setup.target_count || 0, 10) || 0)));' . PHP_EOL;
        $js .= '  if (setup.has_mixed_statuses) { summaryBits.push(gfAccountsConfig.strings.mixed_results || ""); }' . PHP_EOL;
        $js .= '  var shouldScrollTargets = (parseInt(setup.target_count || 0, 10) || 0) >= 3;' . PHP_EOL;
        $js .= '  var html = \'<div class="gf-submission-account-setup mb-4 pb-3">\' +' . PHP_EOL;
        $js .= '    \'<div class="gf-submission-account-setup-title">User Registration \' + String(parseInt(setup.setup_index || 0, 10) + 1) + \'</div>\' +' . PHP_EOL;
        $js .= '    \'<div class="gf-submission-account-setup-summary mb-2">\' + gfEscapeAccountsHtml(summaryBits.join(" • ")) + \'</div>\';' . PHP_EOL;
        $js .= '  if (shouldScrollTargets) { html += \'<div class="gf-submission-account-targets-scroll">\'; }' . PHP_EOL;
        $js .= '  targets.forEach(function(target){ html += gfBuildTargetRow(target, setup); });' . PHP_EOL;
        $js .= '  if (shouldScrollTargets) { html += \'</div>\'; }' . PHP_EOL;
        $js .= '  html += \'</div>\';' . PHP_EOL;
        $js .= '  return html;' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfRenderAccounts = function(accounts) {' . PHP_EOL;
        $js .= '  if (!accounts || !Array.isArray(accounts.setups) || !accounts.setups.length) {' . PHP_EOL;
        $js .= '    gfRenderAccountsEmpty(gfAccountsConfig.strings.empty);' . PHP_EOL;
        $js .= '    return;' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  var html = gfBuildAccountsSummary(accounts);' . PHP_EOL;
        $js .= '  accounts.setups.forEach(function(setup){ html += gfBuildSetupCard(setup); });' . PHP_EOL;
        $js .= '  $accountsPanel.html(html);' . PHP_EOL;
        $js .= '  gfApplyAccountsNotice();' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfAccountsRequest = function(action, nonce, extraData) {' . PHP_EOL;
        $js .= '  var payload = $.extend({' . PHP_EOL;
        $js .= '    action: action,' . PHP_EOL;
        $js .= '    nonce: nonce,' . PHP_EOL;
        $js .= '    submission_id: gfAccountsConfig.submission_id' . PHP_EOL;
        $js .= '  }, extraData || {});' . PHP_EOL;
        $js .= '  return $.ajax({' . PHP_EOL;
        $js .= '    url: gfAccountsConfig.ajax_url || (typeof ajaxurl !== "undefined" ? ajaxurl : ""),' . PHP_EOL;
        $js .= '    method: "POST",' . PHP_EOL;
        $js .= '    dataType: "json",' . PHP_EOL;
        $js .= '    data: payload' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= 'var gfLoadAccountsState = function(showLoading) {' . PHP_EOL;
        $js .= '  if (showLoading) { gfRenderAccountsLoading(); }' . PHP_EOL;
        $js .= '  gfSetAccountsBusy(true);' . PHP_EOL;
        $js .= '  gfAccountsRequest(gfAccountsConfig.get_state_action, gfAccountsConfig.get_state_nonce).done(function(response){' . PHP_EOL;
        $js .= '    if (response && response.success) {' . PHP_EOL;
        $js .= '      var payload = response.data || response;' . PHP_EOL;
        $js .= '      gfRenderAccounts(payload.accounts || {});' . PHP_EOL;
        $js .= '      return;' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    gfRenderAccountsEmpty(gfAccountsConfig.strings.error);' . PHP_EOL;
        $js .= '  }).fail(function(){' . PHP_EOL;
        $js .= '    gfRenderAccountsEmpty(gfAccountsConfig.strings.error);' . PHP_EOL;
        $js .= '  }).always(function(){' . PHP_EOL;
        $js .= '    gfSetAccountsBusy(false);' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '};' . PHP_EOL;
        $js .= '$(document).on("griffinforms:submission-logs-refreshed", function(event, payload){' . PHP_EOL;
        $js .= '  payload = payload || {};' . PHP_EOL;
        $js .= '  if (parseInt(payload.submissionId || 0, 10) !== parseInt(gfAccountsConfig.submission_id || 0, 10)) { return; }' . PHP_EOL;
        $js .= '  if (gfAccountsBusy) { return; }' . PHP_EOL;
        $js .= '  gfLoadAccountsState(false);' . PHP_EOL;
        $js .= '});' . PHP_EOL;
        $js .= '$accountsPanel.on("click", ".gf-submission-account-confirm-cancel", function(event){' . PHP_EOL;
        $js .= '  event.preventDefault();' . PHP_EOL;
        $js .= '  if (gfAccountsBusy) { return; }' . PHP_EOL;
        $js .= '  gfCloseAccountsConfirmations();' . PHP_EOL;
        $js .= '});' . PHP_EOL;
        $js .= '$accountsPanel.on("click", ".gf-submission-account-action", function(event){' . PHP_EOL;
        $js .= '  event.preventDefault();' . PHP_EOL;
        $js .= '  if (gfAccountsBusy) { return; }' . PHP_EOL;
        $js .= '  var $btn = $(this);' . PHP_EOL;
        $js .= '  var actionId = $btn.data("action-id") || "";' . PHP_EOL;
        $js .= '  var actionType = $btn.data("action-type") || "";' . PHP_EOL;
        $js .= '  var actionLabel = $btn.data("action-label") || actionType;' . PHP_EOL;
        $js .= '  var targetEmail = $btn.data("target-email") || "";' . PHP_EOL;
        $js .= '  if ($btn.hasClass("is-awaiting-confirmation")) {' . PHP_EOL;
        $js .= '    gfCloseAccountsConfirmations();' . PHP_EOL;
        $js .= '    return;' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  if (!actionId || !actionType) {' . PHP_EOL;
        $js .= '    gfSetAccountsNotice(gfAccountsConfig.strings.action_error, "danger");' . PHP_EOL;
        $js .= '    return;' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  gfCloseAccountsConfirmations();' . PHP_EOL;
        $js .= '  gfSetAccountsBusy(true);' . PHP_EOL;
        $js .= '  gfAccountsRequest(gfAccountsConfig.validate_action, gfAccountsConfig.validate_nonce, {' . PHP_EOL;
        $js .= '    action_id: actionId,' . PHP_EOL;
        $js .= '    account_action_type: actionType' . PHP_EOL;
        $js .= '  }).done(function(response){' . PHP_EOL;
        $js .= '    if (!(response && response.success)) {' . PHP_EOL;
        $js .= '      var msg = gfAccountsConfig.strings.action_error;' . PHP_EOL;
        $js .= '      if (response) {' . PHP_EOL;
        $js .= '        if (response.msg) { msg = response.msg; }' . PHP_EOL;
        $js .= '        else if (typeof response.data === "string") { msg = response.data; }' . PHP_EOL;
        $js .= '      }' . PHP_EOL;
        $js .= '      gfSetAccountsNotice(msg, "danger");' . PHP_EOL;
        $js .= '      return;' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    gfOpenAccountsConfirmation($btn, actionId, actionType, actionLabel, targetEmail);' . PHP_EOL;
        $js .= '  }).fail(function(){' . PHP_EOL;
        $js .= '    gfSetAccountsNotice(gfAccountsConfig.strings.action_error, "danger");' . PHP_EOL;
        $js .= '  }).always(function(){' . PHP_EOL;
        $js .= '    if (gfAccountsBusy) {' . PHP_EOL;
        $js .= '      gfSetAccountsBusy(false);' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '});' . PHP_EOL;
        $js .= '$accountsPanel.on("click", ".gf-submission-account-confirm-execute", function(event){' . PHP_EOL;
        $js .= '  event.preventDefault();' . PHP_EOL;
        $js .= '  if (gfAccountsBusy) { return; }' . PHP_EOL;
        $js .= '  var $btn = $(this);' . PHP_EOL;
        $js .= '  var actionId = $btn.data("action-id") || "";' . PHP_EOL;
        $js .= '  var actionType = $btn.data("action-type") || "";' . PHP_EOL;
        $js .= '  if (!actionId || !actionType) {' . PHP_EOL;
        $js .= '    gfSetAccountsNotice(gfAccountsConfig.strings.action_error, "danger");' . PHP_EOL;
        $js .= '    gfCloseAccountsConfirmations();' . PHP_EOL;
        $js .= '    return;' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '  var $panel = $btn.closest(".gf-submission-account-confirmation-panel");' . PHP_EOL;
        $js .= '  $panel.addClass("is-working");' . PHP_EOL;
        $js .= '  gfSetAccountsBusy(true);' . PHP_EOL;
        $js .= '  gfAccountsRequest(gfAccountsConfig.execute_action, gfAccountsConfig.execute_nonce, {' . PHP_EOL;
        $js .= '    action_id: actionId,' . PHP_EOL;
        $js .= '    account_action_type: actionType' . PHP_EOL;
        $js .= '  }).done(function(executeResponse){' . PHP_EOL;
        $js .= '    if (executeResponse && executeResponse.success) {' . PHP_EOL;
        $js .= '      var payload = executeResponse.data || executeResponse;' . PHP_EOL;
        $js .= '      gfRenderAccounts(payload.accounts || {});' . PHP_EOL;
        $js .= '      gfSetAccountsNotice(payload.message || gfAccountsConfig.strings.action_success, payload.notice_variant || "success");' . PHP_EOL;
        $js .= '      $(document).trigger("griffinforms:submission-accounts-updated", [{' . PHP_EOL;
        $js .= '        submissionId: gfAccountsConfig.submission_id,' . PHP_EOL;
        $js .= '        accounts: payload.accounts || {}' . PHP_EOL;
        $js .= '      }]);' . PHP_EOL;
        $js .= '      return;' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    var errorMessage = gfAccountsConfig.strings.action_error;' . PHP_EOL;
        $js .= '    if (executeResponse) {' . PHP_EOL;
        $js .= '      if (executeResponse.msg) { errorMessage = executeResponse.msg; }' . PHP_EOL;
        $js .= '      else if (typeof executeResponse.data === "string") { errorMessage = executeResponse.data; }' . PHP_EOL;
        $js .= '    }' . PHP_EOL;
        $js .= '    gfSetAccountsNotice(errorMessage, "danger");' . PHP_EOL;
        $js .= '    gfCloseAccountsConfirmations();' . PHP_EOL;
        $js .= '    gfLoadAccountsState(false);' . PHP_EOL;
        $js .= '  }).fail(function(){' . PHP_EOL;
        $js .= '    gfSetAccountsNotice(gfAccountsConfig.strings.action_error, "danger");' . PHP_EOL;
        $js .= '    gfCloseAccountsConfirmations();' . PHP_EOL;
        $js .= '    gfLoadAccountsState(false);' . PHP_EOL;
        $js .= '  }).always(function(){' . PHP_EOL;
        $js .= '    gfSetAccountsBusy(false);' . PHP_EOL;
        $js .= '  });' . PHP_EOL;
        $js .= '});' . PHP_EOL;
        $js .= 'gfLoadAccountsState(true);' . PHP_EOL;

        return $js;
    }

    protected function openFirstAccordionPanel()
    {
        $js  = 'var $gfAccordion = $(".gf-submission-accordion");' . PHP_EOL;
        $js .= 'if ($gfAccordion.length) {' . PHP_EOL;
        $js .= '  var $gfFirstCollapse = $gfAccordion.find(".accordion-collapse").first();' . PHP_EOL;
        $js .= '  var $gfFirstBtn = $gfAccordion.find(".accordion-button").first();' . PHP_EOL;
        $js .= '  if ($gfFirstCollapse.length) {' . PHP_EOL;
        $js .= '    $gfFirstCollapse.addClass("show");' . PHP_EOL;
        $js .= '    $gfFirstBtn.removeClass("collapsed").attr("aria-expanded", "true");' . PHP_EOL;
        $js .= '  }' . PHP_EOL;
        $js .= '}' . PHP_EOL;

        return $js;
    }
}
