jQuery(function ($) {
  const $selected = $('#field-sortable');
  const $available = $('#field-available');
  if (!$selected.length) return;

  function uid(prefix) {
    return prefix + '_' + Math.floor(Math.random() * 1e8);
  }

  function getTextSyncFieldOptions() {
    const seen = {};
    const out = [];

    $('#field-sortable li[data-kind="field"][data-field-type="text"], #field-available li[data-kind="field"][data-field-type="text"]').each(function () {
      const $item = $(this);
      const gid = String($item.attr('data-id') || '').trim();
      if (!gid || seen[gid]) return;

      seen[gid] = true;
      const label = String($item.find('.afb-item-title').first().text() || gid).trim();
      out.push({ gid, label });
    });

    return out;
  }

  function buildSyncSelectOptionsHtml(selectedGid) {
    const selected = String(selectedGid || '').trim();
    const options = getTextSyncFieldOptions();
    let html = '<option value="">Do not sync (append to task description)</option>';

    options.forEach(function (opt) {
      const value = String(opt.gid || '').trim();
      if (!value) return;

      const label = $('<div></div>').text((opt.label || value) + ' (' + value + ')').html();
      const isSelected = value === selected ? ' selected' : '';
      html += '<option value="' + value + '"' + isSelected + '>' + label + '</option>';
    });

    return html;
  }

  let lastSyncDuplicateSignature = '';

  function getSyncTargetSelects() {
    return $selected.find('select[name^="custom_text_sync_fields["], select[name^="custom_single_input_sync_fields["], select[name^="custom_dropdown_sync_fields["], select[name^="custom_calendar_sync_fields["]');
  }

  function isSyncTargetSelectActive($select) {
    if (!$select || !$select.length) return false;
    const name = String($select.attr('name') || '');
    if (!name) return false;

    if (name.startsWith('custom_calendar_sync_fields[')) {
      const $item = $select.closest('li[data-kind="calendar"]');
      if (!$item.length) return false;
      const mode = String($item.find('select[name^="custom_calendar_sync_modes["]').first().val() || '').trim();
      return mode === 'text';
    }

    return true;
  }

  function clearSyncDuplicateWarnings() {
    getSyncTargetSelects().each(function () {
      const $select = $(this);
      $select.removeClass('afb-sync-duplicate');
      $select.next('.afb-sync-duplicate-note').remove();
      $select.removeAttr('title');
    });
  }

  function runSyncFieldDuplicateCheck(showAlert) {
    clearSyncDuplicateWarnings();

    const ownersByTarget = {};
    const duplicatesByTarget = {};

    getSyncTargetSelects().each(function () {
      const $select = $(this);
      if (!isSyncTargetSelectActive($select)) return;

      const targetGid = String($select.val() || '').trim();
      if (!targetGid) return;

      const owner = {
        $select,
        label: String($select.closest('.afb-builder-item').find('.afb-item-title').first().text() || 'Field').trim(),
      };

      if (!ownersByTarget[targetGid]) {
        ownersByTarget[targetGid] = [owner];
      } else {
        ownersByTarget[targetGid].push(owner);
      }
    });

    Object.keys(ownersByTarget).forEach(function (targetGid) {
      const owners = ownersByTarget[targetGid] || [];
      if (owners.length <= 1) return;
      duplicatesByTarget[targetGid] = owners;

      owners.forEach(function (owner) {
        const $select = owner.$select;
        const msg = `Duplicate sync target: ${targetGid} is already used by another field.`;
        $select.addClass('afb-sync-duplicate').attr('title', msg);
        if (!$select.next('.afb-sync-duplicate-note').length) {
          $select.after(`<p class="afb-sync-duplicate-note text-xs text-red-600 mt-1">${msg}</p>`);
        }
      });
    });

    const signature = Object.keys(duplicatesByTarget).sort().join('|');

    if (showAlert && signature && signature !== lastSyncDuplicateSignature) {
      window.alert('This Asana text field is already selected by another synced field in this form. Please choose a unique sync target.');
    }

    lastSyncDuplicateSignature = signature;
  }

  function syncCollapseToggleButton($item) {
    if (!$item || !$item.length) return;

    const collapsed = $item.hasClass('is-collapsed');
    const kind = String($item.attr('data-kind') || '').trim();
    const isSection = kind === 'section';
    const text = isSection
      ? (collapsed ? 'Open Step' : 'Close Step')
      : (collapsed ? 'Expand' : 'Collapse');
    const label = isSection
      ? (collapsed ? 'Open step' : 'Close step')
      : (collapsed ? 'Expand block' : 'Collapse block');

    $item.find('.afb-collapse-toggle').first().text(text).attr('aria-label', label).attr('title', label);
  }

  function syncCollapsedSectionVisibility() {
    let hideFollowers = false;

    $selected.children('li').each(function () {
      const $item = $(this);
      const kind = String($item.attr('data-kind') || '').trim();

      if (kind === 'section') {
        hideFollowers = $item.hasClass('is-collapsed');
        $item.removeClass('is-under-collapsed-section');
        return;
      }

      $item.toggleClass('is-under-collapsed-section', hideFollowers);
    });
  }

  function rememberItemHeaderDefaults($item) {
    if (!$item || !$item.length) return;

    if (!$item.data('afbDefaultTitle')) {
      $item.data('afbDefaultTitle', String($item.find('.afb-item-title').first().text() || '').trim());
    }
  }

  function syncItemHeaderText($item) {
    if (!$item || !$item.length) return;

    const kind = String($item.attr('data-kind') || '').trim();
    const $title = $item.find('.afb-item-title').first();
    if (!$title.length) return;

    rememberItemHeaderDefaults($item);

    const defaultTitle = String($item.data('afbDefaultTitle') || '').trim();
    let editedTitle = '';

    if (kind === 'field') {
      editedTitle = String($item.find('input[name^="labels["]').first().val() || '').trim();
    } else if (kind === 'section') {
      editedTitle = String($item.find('input[name^="custom_section_titles["]').first().val() || '').trim();
    } else if (kind === 'agreement') {
      editedTitle = String($item.find('input[name^="custom_agreement_labels["]').first().val() || '').trim();
    } else if (kind === 'upload') {
      editedTitle = String($item.find('input[name^="custom_upload_titles["]').first().val() || '').trim();
    } else if (kind === 'single_input') {
      editedTitle = String($item.find('input[name^="custom_single_input_labels["]').first().val() || '').trim();
    } else if (kind === 'dropdown') {
      editedTitle = String($item.find('input[name^="custom_dropdown_labels["]').first().val() || '').trim();
    } else if (kind === 'calendar') {
      editedTitle = String($item.find('input[name^="custom_calendar_labels["]').first().val() || '').trim();
    } else if (kind === 'repeater') {
      editedTitle = String($item.find('input[name^="custom_repeater_labels["]').first().val() || '').trim();
    } else if (kind === 'task_name') {
      editedTitle = String($item.find('input[name^="custom_task_name_labels["]').first().val() || '').trim();
    } else if (kind === 'assignee_tool') {
      editedTitle = String($item.find('input[name^="custom_assignee_labels["]').first().val() || '').trim();
    } else if (kind === 'recipient_tool') {
      editedTitle = String($item.find('input[name^="custom_recipient_labels["]').first().val() || '').trim();
    } else if (kind === 'paragraph') {
      editedTitle = String($item.find('input[name^="custom_paragraph_titles["]').first().val() || '').trim();
    }

    $title.text(editedTitle || defaultTitle);
  }

  function syncIconButtonLabels($scope) {
    const $root = ($scope && $scope.length) ? $scope : $(document);

    $root.find('.afb-duplicate-item').attr('aria-label', 'Duplicate block').attr('title', 'Duplicate block');
    $root.find('.remove-builder-item').attr('aria-label', 'Remove block').attr('title', 'Remove block');
    $root.find('.afb-move-section-up').attr('aria-label', 'Move step up').attr('title', 'Move step up');
    $root.find('.afb-move-section-down').attr('aria-label', 'Move step down').attr('title', 'Move step down');
    $root.find('.afb-add-to-form').attr('aria-label', 'Add field to form').attr('title', 'Add field to form');

    $root.find('.afb-add-block').each(function () {
      const $btn = $(this);
      const blockTitle = String($btn.find('.afb-add-block-title').first().text() || '').trim();
      const label = blockTitle ? ('Add ' + blockTitle) : 'Add block';
      $btn.attr('aria-label', label).attr('title', label);
    });

    $root.filter('.afb-builder-item').each(function () {
      const $item = $(this);
      syncCollapseToggleButton($item);
      syncItemHeaderText($item);
    });

    $root.find('.afb-builder-item').each(function () {
      const $item = $(this);
      syncCollapseToggleButton($item);
      syncItemHeaderText($item);
    });
  }

  function toggleItem($item) {
    $item.toggleClass('is-collapsed');
    syncCollapseToggleButton($item);

    if (String($item.attr('data-kind') || '').trim() === 'section') {
      syncCollapsedSectionVisibility();
    }
  }

  function syncFlags($item) {
    if (($item.attr('data-kind') || '') !== 'field') return;

    const flags = [];
    if ($item.find('input[name="required_fields[]"]').first().is(':checked')) flags.push('Req');
    if ($item.find('input[name="hidden_fields[]"]').first().is(':checked')) flags.push('Hidden');
    if ($item.find('input[name="readonly_fields[]"]').first().is(':checked')) flags.push('RO');
    if ($item.find('input[name="half_width_fields[]"]').first().is(':checked')) flags.push('1/2');
    if ($item.find('input[name="third_width_fields[]"]').first().is(':checked')) flags.push('1/3');
    if ($item.find('input[name="paragraph_fields[]"]').first().is(':checked')) flags.push('Para');
    if ($item.find('input[name="submit_block_fields[]"]').first().is(':checked')) flags.push('Block');

    let hasCond = false;
    const condRaw = ($item.find('.afb-cond-rules-input').first().val() || '').toString().trim();
    if (condRaw) {
      try {
        const parsedCond = JSON.parse(condRaw);
        hasCond = Array.isArray(parsedCond) && parsedCond.length > 0;
      } catch (_e) {
        hasCond = false;
      }
    }

    if (!hasCond) {
      const depVal = ($item.find('.depends-on-selector').first().val() || '').toString().trim();
      hasCond = !!depVal;
    }

    if (hasCond) flags.push('Cond');

    const visibleOpts = $item.find('select[name^="visible_enum_options["]').first().val();
    if (Array.isArray(visibleOpts) && visibleOpts.length) flags.push('Opts');

    const optionMapSource = ($item.find('.afb-option-filter-source').first().val() || '').toString().trim();
    const optionMapRaw = ($item.find('.afb-option-filter-map').first().val() || '').toString();
    let hasOptionMap = false;
    if (optionMapSource && optionMapRaw) {
      try {
        const parsed = JSON.parse(optionMapRaw);
        hasOptionMap = !!(parsed && typeof parsed === 'object' && Object.keys(parsed).length);
      } catch (_e) {
        hasOptionMap = false;
      }
    }
    if (hasOptionMap) flags.push('Map');

    const textSyncTarget = ($item.find('select[name^="custom_text_sync_fields["]').first().val() || '').toString().trim();
    if (textSyncTarget) flags.push('Sync');

    const labelVal = ($item.find('input[name^="labels["]').first().val() || '').toString().trim();
    if (labelVal) flags.push('Label');

    const $flags = $item.find('.afb-item-flags').first();
    if (!$flags.length) return;

    if (flags.length) {
      $flags.text(flags.join(' • '));
    } else {
      $flags.html('<span class="is-empty">Default</span>');
    }
  }

  function enforceExclusiveWidthSelections($item, changedName) {
    if (!$item || !$item.length) return;

    const changed = String(changedName || '').trim();
    const $half = $item.find('input[name="half_width_fields[]"]').first();
    const $third = $item.find('input[name="third_width_fields[]"]').first();

    if (!$half.length || !$third.length) return;

    if (changed === 'half_width_fields[]' && $half.is(':checked')) {
      $third.prop('checked', false);
      return;
    }

    if (changed === 'third_width_fields[]' && $third.is(':checked')) {
      $half.prop('checked', false);
      return;
    }

    if ($half.is(':checked') && $third.is(':checked')) {
      $third.prop('checked', false);
    }
  }

  function syncWidthBadge($item, chipSelector) {
    if (!$item || !$item.length) return;

    const isHalf = $item.find('input[name="half_width_fields[]"]').first().is(':checked');
    const isThird = $item.find('input[name="third_width_fields[]"]').first().is(':checked');
    const $chip = $item.find(chipSelector).first();
    if (!$chip.length) return;

    if (isThird) {
      $chip.text('1/3').show();
      return;
    }

    if (isHalf) {
      $chip.text('1/2').show();
      return;
    }

    $chip.hide();
  }

  function syncSingleInputHalfBadge($item) {
    if (($item.attr('data-kind') || '') !== 'single_input') return;
    syncWidthBadge($item, '.afb-single-half-chip');
  }

  function syncSingleInputAiBadge($item) {
    if (($item.attr('data-kind') || '') !== 'single_input') return;

    const isAiEnabled = $item.find('input[name="custom_single_input_ai_enabled[]"]').first().is(':checked');
    const $chip = $item.find('.afb-single-ai-chip').first();
    if (!$chip.length) return;
    $chip.toggle(!!isAiEnabled);
  }

  function syncSingleInputWordLimitControls($item) {
    if (($item.attr('data-kind') || '') !== 'single_input') return;

    const mode = String($item.find('select[name^="custom_single_input_modes["]').first().val() || 'text').trim();
    const isTextarea = mode === 'textarea';

    const $toggleWrap = $item.find('.afb-single-max-words-controls').first();
    const $toggle = $item.find('.afb-single-max-words-toggle').first();
    const $limitWrap = $item.find('.afb-single-max-words-wrap').first();

    if ($toggleWrap.length) {
      $toggleWrap.toggle(isTextarea);
    }

    const isEnabled = $toggle.length ? $toggle.is(':checked') : false;
    if ($limitWrap.length) {
      $limitWrap.toggle(isTextarea && isEnabled);
    }
  }

  function syncDropdownHalfBadge($item) {
    if (($item.attr('data-kind') || '') !== 'dropdown') return;
    syncWidthBadge($item, '.afb-dropdown-half-chip');
  }

  function syncCalendarHalfBadge($item) {
    if (($item.attr('data-kind') || '') !== 'calendar') return;
    syncWidthBadge($item, '.afb-calendar-half-chip');
  }

  function syncRepeaterHalfBadge($item) {
    if (($item.attr('data-kind') || '') !== 'repeater') return;
    syncWidthBadge($item, '.afb-repeater-half-chip');
  }

  function normalizeRepeaterSaveMode(rawMode) {
    return String(rawMode || '').trim() === 'description' ? 'description' : 'subtasks';
  }

  function syncRepeaterSaveModeSummary($item) {
    if (($item.attr('data-kind') || '') !== 'repeater') return;

    const $modeSelect = $item.find('select[name^="custom_repeater_save_mode["]').first();
    const mode = normalizeRepeaterSaveMode($modeSelect.val());

    const $subtitle = $item.find('.afb-item-subtitle').first();
    if ($subtitle.length) {
      $subtitle.text(
        mode === 'description'
          ? 'Rows append to the main task description'
          : 'One row per entry; each row creates a subtask'
      );
    }

    const $modeNote = $item.find('.afb-repeater-mode-note').first();
    if ($modeNote.length) {
      $modeNote.text(
        mode === 'description'
          ? 'Rows are appended to the main task Notes/Description.'
          : 'Rows create child subtasks under the main task.'
      );
    }

    const $mapWrap = $item.find('.afb-repeater-map-wrap').first();
    if ($mapWrap.length) {
      $mapWrap.attr('data-repeater-save-mode', mode);
    }

    const $mapHint = $item.find('.afb-repeater-map-hint').first();
    if ($mapHint.length) {
      $mapHint.text(
        mode === 'description'
          ? 'Drag Asana fields into this list if you want structured repeater inputs. Mapping is only used when save mode is subtasks.'
          : 'Drag Asana fields from the left into this list. The order here controls each repeater row layout and the custom fields written on subtasks.'
      );
    }

    const $outputHint = $item.find('.afb-repeater-output-hint').first();
    if ($outputHint.length) {
      $outputHint.text(
        mode === 'description'
          ? 'Rows are appended to the main task description when this block is visible and has content.'
          : 'Each repeater row becomes one Asana subtask under the main task when this block is visible and has content.'
      );
    }
  }

  function syncAgreementRequiredBadge($item) {
    if (($item.attr('data-kind') || '') !== 'agreement') return;

    const isRequired = $item.find('input[name="agreement_required_fields[]"]').first().is(':checked');
    const $chip = $item.find('.afb-agreement-required-chip').first();
    if (!$chip.length) return;
    $chip.toggle(!!isRequired);
  }

  function syncCollapsibleSection($section) {
    if (!$section || !$section.length) return;

    const isExpanded = $section.hasClass('is-expanded') && !$section.hasClass('is-collapsed');
    let $body = $section.children('.afb-collapsible-body').first();
    if (!$body.length && $section.hasClass('afb-cond-editor')) {
      $body = $section.children('.afb-cond-body').first();
    }
    const $toggle = $section.find('.afb-section-toggle').first();

    if ($body.length) {
      $body.toggle(!!isExpanded);
    }

    if ($toggle.length) {
      $toggle
        .attr('aria-expanded', isExpanded ? 'true' : 'false')
        .attr('title', isExpanded ? 'Hide section' : 'Show section')
        .text(isExpanded ? 'Hide' : 'Show');
    }
  }

  function syncCollapsibleSections($scope) {
    const $root = ($scope && $scope.length) ? $scope : $(document);

    $root.find('.afb-cond-editor').each(function () {
      const $section = $(this);
      const $controls = $section.find('.afb-cond-header-controls').first();

      $section.addClass('afb-collapsible-section');

      if ($controls.length && !$controls.find('.afb-section-toggle').length) {
        $controls.prepend('<button type="button" class="afb-admin-btn afb-section-toggle" aria-expanded="false">Show</button>');
      }

      if (!$section.hasClass('is-expanded') && !$section.hasClass('is-collapsed')) {
        $section.addClass('is-collapsed');
      }

      syncCollapsibleSection($section);
    });

    $root.find('.afb-visible-options-wrap, .afb-optmap-section').each(function () {
      const $section = $(this);
      $section.addClass('afb-collapsible-section');

      if (!$section.hasClass('is-expanded') && !$section.hasClass('is-collapsed')) {
        $section.addClass('is-collapsed');
      }

      syncCollapsibleSection($section);
    });
  }

  function syncUploadRequiredToggleFromMin($item) {
    if (($item.attr('data-kind') || '') !== 'upload') return;

    const $toggle = $item.find('.afb-upload-required-toggle').first();
    const $minInput = $item.find('input[name^="custom_upload_min_files["]').first();
    if (!$toggle.length || !$minInput.length) return;

    const minFiles = Math.max(0, parseInt($minInput.val(), 10) || 0);
    $toggle.prop('checked', minFiles > 0);
  }

  function ensureOrderInput($item) {
    if ($item.find('.afb-order-input').length) return;
    const gid = $item.attr('data-id') || '';
    if (!gid) return;
    $item.prepend(`<input type="hidden" name="field_order[]" value="${gid}" class="afb-order-input" />`);
  }

  function setFieldSelected($item, selected) {
    if (($item.attr('data-kind') || '') !== 'field') return;

    const $used = $item.find('.afb-used-toggle').first();
    $item.toggleClass('is-selected', !!selected);
    $item.toggleClass('is-available', !selected);

    if ($used.length) $used.prop('checked', !!selected);

    if (selected) {
      ensureOrderInput($item);
    } else {
      $item.find('.afb-order-input').remove();
      $item.addClass('is-collapsed');
    }

    syncCollapseToggleButton($item);
  }

  const allOptionsMap = {};
  $('#field-sortable li, #field-available li').each(function () {
    const gid = $(this).attr('data-id');
    try {
      allOptionsMap[gid] = JSON.parse($(this).attr('data-options') || '[]');
    } catch (e) {
      allOptionsMap[gid] = [];
    }
  });

  function normalizeAssigneeGid(rawGid) {
    return String(rawGid || '').trim().replace(/\D+/g, '');
  }

  function getAssigneeMemberOptions() {
    const raw = String($selected.attr('data-assignee-options') || '[]');
    let parsed = [];

    try {
      parsed = JSON.parse(raw);
    } catch (_e) {
      parsed = [];
    }

    const out = [];
    const seen = {};
    (Array.isArray(parsed) ? parsed : []).forEach(function (entry) {
      const gid = normalizeAssigneeGid(entry && entry.gid);
      if (!gid || seen[gid]) return;
      seen[gid] = true;

      const name = String((entry && entry.name) || ('User ' + gid)).trim() || ('User ' + gid);
      out.push({ gid, name });
    });

    return out;
  }

  function mergeAssigneeMemberOptions(extraGids) {
    const merged = getAssigneeMemberOptions().slice();
    const seen = {};

    merged.forEach(function (opt) {
      const gid = normalizeAssigneeGid(opt && opt.gid);
      if (gid) seen[gid] = true;
    });

    (Array.isArray(extraGids) ? extraGids : []).forEach(function (rawGid) {
      const gid = normalizeAssigneeGid(rawGid);
      if (!gid || seen[gid]) return;
      seen[gid] = true;
      merged.push({ gid, name: 'User ' + gid });
    });

    merged.sort(function (a, b) {
      return String((a && a.name) || '').localeCompare(String((b && b.name) || ''));
    });

    return merged;
  }

  function buildAssigneeSelectOptionsHtml(selectedGid, includeBlank, blankLabel, extraGids) {
    const selected = normalizeAssigneeGid(selectedGid);
    const opts = mergeAssigneeMemberOptions([selected].concat(Array.isArray(extraGids) ? extraGids : []));
    let html = '';

    if (includeBlank) {
      const label = $('<div></div>').text(blankLabel || 'Select assignee').html();
      html += '<option value="">' + label + '</option>';
    }

    opts.forEach(function (opt) {
      const gid = normalizeAssigneeGid(opt && opt.gid);
      if (!gid) return;

      const label = $('<div></div>').text((opt.name || ('User ' + gid)) + ' (' + gid + ')').html();
      const isSelected = gid === selected ? ' selected' : '';
      html += '<option value="' + gid + '"' + isSelected + '>' + label + '</option>';
    });

    return html;
  }

  function parseCustomDropdownOptions($item) {
    if (!$item || !$item.length) return [];

    const raw = String($item.find('textarea[name^="custom_dropdown_options["]').first().val() || '');
    return raw
      .split(/\r?\n/)
      .map(function (line) { return String(line || '').trim(); })
      .filter(Boolean)
      .map(function (label) {
        return {
          gid: label,
          name: label,
          enabled: true,
        };
      });
  }

  function ensureRepeaterDragButtonStyles() {
    if (document.getElementById('afb-repeater-drag-field-style')) return;

    const style = document.createElement('style');
    style.id = 'afb-repeater-drag-field-style';
    style.textContent = [
      '.afb-builder-item-header .afb-repeater-drag-field {',
      '  position: relative;',
      '  width: 38px;',
      '  min-width: 38px;',
      '  padding: 0;',
      '  gap: 0;',
      '  font-size: 0;',
      '  line-height: 0;',
      '}',
      '.afb-builder-item-header .afb-repeater-drag-field::before {',
      '  content: "";',
      '  position: absolute;',
      '  left: 50%;',
      '  top: 50%;',
      '  transform: translate(-50%, -50%);',
      '  width: 18px;',
      '  height: 18px;',
      '  background-color: currentColor;',
      '  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns=\"http://www.w3.org/2000/svg\"%20viewBox=\"0%200%2024%2024\"%20fill=\"none\"%20stroke=\"white\"%20stroke-width=\"2\"%20stroke-linecap=\"round\"%20stroke-linejoin=\"round\"%3E%3Cpath%20d=\"M5%2012h14\"/%3E%3Cpath%20d=\"M13%206l6%206-6%206\"/%3E%3C/svg%3E");',
      '  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns=\"http://www.w3.org/2000/svg\"%20viewBox=\"0%200%2024%2024\"%20fill=\"none\"%20stroke=\"white\"%20stroke-width=\"2\"%20stroke-linecap=\"round\"%20stroke-linejoin=\"round\"%3E%3Cpath%20d=\"M5%2012h14\"/%3E%3Cpath%20d=\"M13%206l6%206-6%206\"/%3E%3C/svg%3E");',
      '  -webkit-mask-repeat: no-repeat;',
      '  mask-repeat: no-repeat;',
      '  -webkit-mask-position: center;',
      '  mask-position: center;',
      '  -webkit-mask-size: 18px 18px;',
      '  mask-size: 18px 18px;',
      '}',
      '#field-sortable .afb-repeater-drag-field,',
      '#field-available .afb-repeater-drag-field {',
      '  display: inline-flex;',
      '}',
    ].join('\n');

    document.head.appendChild(style);
  }

  function getRepeaterFieldMetaFromItem($item) {
    if (!$item || !$item.length) return null;

    const kind = String($item.attr('data-kind') || '').trim();
    if (kind !== 'field') return null;

    const gid = String($item.attr('data-id') || '').trim();
    if (!gid || !/^\d+$/.test(gid)) return null;

    const type = String($item.attr('data-field-type') || 'text').trim().toLowerCase() || 'text';

    const customLabel = String($item.find('input[name^="labels["]').first().val() || '').trim();
    const baseLabel = String($item.find('.afb-item-title').first().text() || '').trim();
    const label = customLabel || baseLabel || ('Field ' + gid);

    return { gid, type, label };
  }

  function getRepeaterFieldMetaByGid(rawGid) {
    const gid = String(rawGid || '').trim();
    if (!gid) return null;

    const $item = $('#field-sortable li[data-kind="field"], #field-available li[data-kind="field"]').filter(function () {
      return String($(this).attr('data-id') || '').trim() === gid;
    }).first();

    return getRepeaterFieldMetaFromItem($item);
  }

  function buildRepeaterMapItem(repeaterId, fieldMeta) {
    const rid = String(repeaterId || '').trim();
    const gid = String((fieldMeta && fieldMeta.gid) || '').trim();
    const type = String((fieldMeta && fieldMeta.type) || 'text').trim().toLowerCase() || 'text';
    const label = String((fieldMeta && fieldMeta.label) || ('Field ' + gid)).trim() || ('Field ' + gid);

    const $item = $('<li class="afb-repeater-map-item flex items-center gap-2 rounded border border-slate-200 bg-white px-2 py-2"></li>')
      .attr('data-field-gid', gid)
      .attr('data-field-type', type);

    const $hidden = $('<input type="hidden">')
      .attr('name', `custom_repeater_field_map[${rid}][]`)
      .val(gid);
    const $handle = $('<span class="afb-repeater-map-handle" aria-hidden="true">::</span>');
    const $metaWrap = $('<div class="min-w-0 flex-1"></div>');
    const $name = $('<div class="afb-repeater-map-name text-sm text-slate-800"></div>').text(label);
    const $meta = $('<div class="afb-item-subtitle"></div>').text(`${type} - ${gid}`);
    const $remove = $('<button type="button" class="afb-admin-btn afb-repeater-map-remove">Remove</button>');

    $metaWrap.append($name, $meta);
    $item.append($hidden, $handle, $metaWrap, $remove);
    return $item;
  }

  function syncRepeaterMapEmptyState($scope) {
    const $root = ($scope && $scope.length) ? $scope : $(document);
    const $wraps = $root.hasClass('afb-repeater-map-wrap')
      ? $root
      : $root.find('.afb-repeater-map-wrap');

    $wraps.each(function () {
      const $wrap = $(this);
      const $list = $wrap.find('.afb-repeater-map-list').first();
      const $empty = $wrap.find('.afb-repeater-map-empty').first();
      const hasItems = $list.find('.afb-repeater-map-item').length > 0;
      if ($empty.length) {
        $empty.toggle(!hasItems);
      }
    });
  }

  function refreshRepeaterMapItems($scope) {
    const $root = ($scope && $scope.length) ? $scope : $(document);
    const $lists = $root.hasClass('afb-repeater-map-list')
      ? $root
      : $root.find('.afb-repeater-map-list');

    $lists.each(function () {
      const $list = $(this);
      const repeaterId = String($list.attr('data-repeater-id') || $list.closest('li[data-kind="repeater"]').attr('data-id') || '').trim();
      const seen = {};

      $list.find('.afb-repeater-map-item').each(function () {
        const $item = $(this);
        const gid = String($item.attr('data-field-gid') || '').trim();
        if (!gid) {
          $item.remove();
          return;
        }

        if (seen[gid]) {
          $item.remove();
          return;
        }
        seen[gid] = true;

        const meta = getRepeaterFieldMetaByGid(gid);
        const type = String((meta && meta.type) || $item.attr('data-field-type') || 'text').trim().toLowerCase() || 'text';
        const label = String((meta && meta.label) || $item.find('.afb-repeater-map-name').first().text() || ('Field ' + gid)).trim() || ('Field ' + gid);

        $item.attr('data-field-type', type);
        $item.find('.afb-repeater-map-name').first().text(label);
        $item.find('.afb-item-subtitle').first().text(`${type} - ${gid}`);

        if (repeaterId) {
          const $hidden = $item.find('input[type="hidden"][name^="custom_repeater_field_map["]').first();
          if ($hidden.length) {
            $hidden.attr('name', `custom_repeater_field_map[${repeaterId}][]`).val(gid);
          }
        }
      });
    });

    syncRepeaterMapEmptyState($root);
  }

  function initRepeaterMapLists($scope) {
    const $root = ($scope && $scope.length) ? $scope : $(document);
    const $lists = $root.hasClass('afb-repeater-map-list')
      ? $root
      : $root.find('.afb-repeater-map-list');

    if (!$.fn.sortable) {
      syncRepeaterMapEmptyState($root);
      return;
    }

    $lists.each(function () {
      const $list = $(this);
      if ($list.data('ui-sortable')) return;

      $list.sortable({
        items: '> .afb-repeater-map-item',
        handle: '.afb-repeater-map-handle',
        placeholder: 'ui-state-highlight',
        axis: 'y',
        stop: function () {
          refreshRepeaterMapItems($list);
        }
      });
    });

    syncRepeaterMapEmptyState($root);
  }

  function appendFieldToRepeaterMapList($list, fieldMeta) {
    if (!$list || !$list.length || !fieldMeta) return null;

    const repeaterId = String($list.attr('data-repeater-id') || $list.closest('li[data-kind="repeater"]').attr('data-id') || '').trim();
    if (!repeaterId) return null;

    const gid = String(fieldMeta.gid || '').trim();
    if (!gid || !/^\d+$/.test(gid)) return null;

    const alreadyExists = $list.find('.afb-repeater-map-item').filter(function () {
      return String($(this).attr('data-field-gid') || '').trim() === gid;
    }).length > 0;
    if (alreadyExists) return null;

    const $newItem = buildRepeaterMapItem(repeaterId, fieldMeta);
    $list.append($newItem);
    refreshRepeaterMapItems($list);
    return $newItem;
  }

  function syncFieldRepeaterDragAction($fieldItem) {
    if (!$fieldItem || !$fieldItem.length) return;
    if (String($fieldItem.attr('data-kind') || '').trim() !== 'field') return;

    ensureRepeaterDragButtonStyles();

    const meta = getRepeaterFieldMetaFromItem($fieldItem);
    let $btn = $fieldItem.find('.afb-repeater-drag-field').first();
    const $chip = $fieldItem.find('.afb-chip.afb-chip-field').first();

    if (!meta) {
      if ($btn.length) {
        $btn.remove();
      }

      if ($chip.length) {
        $chip
          .removeAttr('draggable')
          .removeAttr('data-field-gid')
          .removeAttr('data-field-type')
          .removeAttr('data-field-label')
          .removeAttr('title')
          .removeAttr('aria-label');
      }
      return;
    }

    if (!$btn.length) {
      $btn = $('<button type="button" class="afb-admin-btn afb-repeater-drag-field" draggable="true">Map</button>');
      const $actionAnchor = $fieldItem.find('.afb-collapse-toggle').first();
      if ($actionAnchor.length) {
        $actionAnchor.before($btn);
      } else {
        const $chipAnchor = $fieldItem.find('.afb-chip.afb-chip-field').first();
        if ($chipAnchor.length) {
          $chipAnchor.after($btn);
        } else {
          $fieldItem.find('.afb-builder-item-header').first().append($btn);
        }
      }
    }

    $btn
      .attr('data-field-gid', meta.gid)
      .attr('data-field-type', meta.type)
      .attr('data-field-label', meta.label)
      .attr('title', `Drag ${meta.label} into a repeater`)
      .attr('aria-label', `Drag ${meta.label} into a repeater`);

    if ($chip.length) {
      $chip
        .attr('draggable', 'true')
        .attr('data-field-gid', meta.gid)
        .attr('data-field-type', meta.type)
        .attr('data-field-label', meta.label)
        .attr('title', `Drag ${meta.label} into a repeater`)
        .attr('aria-label', `Drag ${meta.label} into a repeater`);
    }
  }

  function refreshRepeaterDragSources($scope) {
    const $root = ($scope && $scope.length) ? $scope : $(document);
    const $fields = $root.is('li[data-kind="field"]')
      ? $root
      : $root.find('li[data-kind="field"]');

    $fields.each(function () {
      syncFieldRepeaterDragAction($(this));
    });
  }

  function getConditionSourceOptions(sourceGid) {
    const gid = String(sourceGid || '').trim();
    if (!gid) return [];

    const $item = $selected.children('li').filter(function () {
      return String($(this).attr('data-id') || '').trim() === gid;
    }).first();

    if (!$item.length) {
      return allOptionsMap[gid] || [];
    }

    const kind = String($item.attr('data-kind') || '').trim();
    if (kind === 'dropdown') {
      return parseCustomDropdownOptions($item);
    }

    if (kind === 'agreement') {
      return [
        { gid: '1', name: 'Checked', enabled: true },
      ];
    }

    return allOptionsMap[gid] || [];
  }

  function getSelectedSourceFields() {
    const selectedFields = [];
    const seen = {};

    $selected.children('li').each(function () {
      const $li = $(this);
      const kind = String($li.attr('data-kind') || '').trim();
      const gid = String($li.attr('data-id') || '').trim();
      if (!gid || seen[gid]) return;

      // Asana fields and custom dropdown/agreement blocks can be dependency sources.
      if (kind === 'field') {
        if (!$li.hasClass('is-selected')) return;
      } else if (kind !== 'dropdown' && kind !== 'agreement') {
        return;
      }

      seen[gid] = true;
      selectedFields.push({
        gid,
        name: ($li.find('.afb-item-title').first().text() || '').trim()
      });
    });

    return selectedFields;
  }

  function getRecipientSourceFieldOptions() {
    const out = [];
    const seen = {};

    $selected.children('li').each(function () {
      const $item = $(this);
      const kind = String($item.attr('data-kind') || '').trim();
      const id = String($item.attr('data-id') || '').trim();
      if (!id || seen[id]) return;

      if (kind === 'field') {
        if (!$item.hasClass('is-selected')) return;
      } else if (!['single_input', 'dropdown', 'calendar', 'agreement'].includes(kind)) {
        return;
      }

      let label = String($item.find('.afb-item-title').first().text() || id).trim();
      if (kind === 'single_input') {
        label = String($item.find('input[name^="custom_single_input_labels["]').first().val() || label || 'Single Input Text').trim();
      } else if (kind === 'dropdown') {
        label = String($item.find('input[name^="custom_dropdown_labels["]').first().val() || label || 'Dropdown').trim();
      } else if (kind === 'calendar') {
        label = String($item.find('input[name^="custom_calendar_labels["]').first().val() || label || 'Calendar Picker').trim();
      } else if (kind === 'agreement') {
        label = String($item.find('input[name^="custom_agreement_labels["]').first().val() || label || 'Agreement Checkbox').trim();
      }

      seen[id] = true;
      out.push({ gid: id, label: (label || id) + ' (' + id + ')' });
    });

    return out;
  }

  function buildRecipientSourceOptionsHtml(selectedValues) {
    const selected = Array.isArray(selectedValues)
      ? selectedValues.map(function (v) { return String(v || '').trim(); }).filter(Boolean)
      : [];
    const options = getRecipientSourceFieldOptions();
    let html = '';

    options.forEach(function (opt) {
      const value = String((opt && opt.gid) || '').trim();
      if (!value) return;
      const label = $('<div></div>').text(String(opt.label || value)).html();
      const isSelected = selected.includes(value) ? ' selected' : '';
      html += '<option value="' + value + '"' + isSelected + '>' + label + '</option>';
    });

    return html;
  }

  function refreshRecipientSourceSelectors() {
    $selected.find('li[data-kind="recipient_tool"]').each(function () {
      const $item = $(this);
      const $select = $item.find('select[name^="custom_recipient_source_fields["]').first();
      if (!$select.length) return;

      const current = Array.isArray($select.val())
        ? $select.val().map(function (v) { return String(v || '').trim(); }).filter(Boolean)
        : [];
      $select.html(buildRecipientSourceOptionsHtml(current));
      $select.val(current);
    });
  }

  function getTaskNameTokenOptions() {
    const options = [
      { token: '{{job_number}}', label: 'Job Number (generated)' },
      { token: '{{submitter_name}}', label: 'Submitter Name' },
      { token: '{{submitter_email}}', label: 'Submitter Email' },
      { token: '{{due_date}}', label: 'Resolved Due Date' }
    ];

    const seen = {};

    function pushFieldToken(id, label) {
      const cleanId = String(id || '').trim();
      if (!cleanId || seen[cleanId]) return;
      seen[cleanId] = true;

      const cleanLabel = String(label || '').trim() || cleanId;
      options.push({
        token: '{{field:' + cleanId + '}}',
        label: cleanLabel + ' (' + cleanId + ')',
      });
    }

    $selected.children('li').each(function () {
      const $item = $(this);
      const kind = String($item.attr('data-kind') || '').trim();
      const id = String($item.attr('data-id') || '').trim();
      if (!id) return;

      if (kind === 'field') {
        if (!$item.hasClass('is-selected')) return;
        const title = String($item.find('.afb-item-title').first().text() || '').trim();
        pushFieldToken(id, title);
        return;
      }

      if (kind === 'single_input') {
        const label = String($item.find('input[name^="custom_single_input_labels["]').first().val() || '').trim() || 'Single Input Text';
        pushFieldToken(id, label);
        return;
      }

      if (kind === 'dropdown') {
        const label = String($item.find('input[name^="custom_dropdown_labels["]').first().val() || '').trim() || 'Dropdown';
        pushFieldToken(id, label);
        return;
      }

      if (kind === 'calendar') {
        const label = String($item.find('input[name^="custom_calendar_labels["]').first().val() || '').trim() || 'Calendar Picker';
        pushFieldToken(id, label);
      }
    });

    return options;
  }

  function refreshTaskNameVariablePickers() {
    const tokenOptions = getTaskNameTokenOptions();

    $selected.find('.afb-task-name-variable-select').each(function () {
      const $select = $(this);
      const current = String($select.val() || '').trim();

      $select.empty().append('<option value="">Choose variable</option>');

      tokenOptions.forEach(function (option) {
        const value = String(option.token || '').trim();
        if (!value) return;

        const $opt = $('<option></option>').val(value).text(option.label || value);
        if (current && current === value) {
          $opt.prop('selected', true);
        }
        $select.append($opt);
      });
    });
  }

  function getRecipientTokenOptions() {
    const options = [
      { token: '{{task_name}}', label: 'Task Name' },
      { token: '{{request_number}}', label: 'Request Number / Job Number' },
      { token: '{{comms_link}}', label: 'Task Link' },
      { token: '{{fields_html}}', label: 'Submitted Fields HTML' },
    ];

    const taskNameOptions = getTaskNameTokenOptions();
    const seen = {};

    options.forEach(function (option) {
      const token = String((option && option.token) || '').trim();
      if (!token) return;
      seen[token] = true;
    });

    taskNameOptions.forEach(function (option) {
      const token = String((option && option.token) || '').trim();
      if (!token || seen[token]) return;

      seen[token] = true;
      options.push(option);
    });

    return options;
  }

  function refreshRecipientVariablePickers() {
    const tokenOptions = getRecipientTokenOptions();

    $selected.find('.afb-recipient-variable-select').each(function () {
      const $select = $(this);
      const current = String($select.val() || '').trim();

      $select.empty().append('<option value="">Choose variable</option>');

      tokenOptions.forEach(function (option) {
        const value = String(option.token || '').trim();
        if (!value) return;

        const $opt = $('<option></option>').val(value).text(option.label || value);
        if (current && current === value) {
          $opt.prop('selected', true);
        }
        $select.append($opt);
      });
    });
  }

  function insertTaskNameToken($textarea, token) {
    if (!$textarea || !$textarea.length) return;

    const cleanToken = String(token || '').trim();
    if (!cleanToken) return;

    const el = $textarea.get(0);
    if (!el) return;

    const current = String($textarea.val() || '');
    if (typeof el.selectionStart === 'number' && typeof el.selectionEnd === 'number') {
      const start = el.selectionStart;
      const end = el.selectionEnd;
      const before = current.slice(0, start);
      const after = current.slice(end);

      const needsLeadingSpace = before.length > 0 && !/\s$/.test(before);
      const needsTrailingSpace = after.length > 0 && !/^\s/.test(after);
      const insert = (needsLeadingSpace ? ' ' : '') + cleanToken + (needsTrailingSpace ? ' ' : '');

      const nextValue = before + insert + after;
      $textarea.val(nextValue).trigger('input').trigger('change');

      const caret = before.length + insert.length;
      el.focus();
      el.setSelectionRange(caret, caret);
      return;
    }

    const separator = current && !/\s$/.test(current) ? ' ' : '';
    $textarea.val(current + separator + cleanToken).trigger('input').trigger('change');
    el.focus();
  }

  function normalizeConditionRules(rawRules) {
    const out = [];
    const list = Array.isArray(rawRules) ? rawRules : [];

    list.forEach(function (rule) {
      if (!rule || typeof rule !== 'object') return;

      const source = String(rule.depends_on || '').trim();
      if (!source) return;

      const vals = Array.isArray(rule.values)
        ? rule.values.map(function (v) { return String(v || '').trim(); }).filter(Boolean)
        : ((rule.value !== '' && rule.value !== null && typeof rule.value !== 'undefined')
          ? [String(rule.value).trim()]
          : []);

      if (!vals.length) return;

      out.push({
        depends_on: source,
        values: Array.from(new Set(vals))
      });
    });

    return out;
  }

  function parseConditionRulesInput($editor) {
    const $input = $editor.find('.afb-cond-rules-input').first();
    if (!$input.length) return [];

    const parsed = parseJsonObject($input.val(), []);
    return normalizeConditionRules(Array.isArray(parsed) ? parsed : []);
  }

  function writeConditionRulesInput($editor, rules) {
    const $input = $editor.find('.afb-cond-rules-input').first();
    if (!$input.length) return;
    $input.val(JSON.stringify(normalizeConditionRules(rules))).trigger('change');
  }

  function collectConditionRules($editor) {
    const rules = [];

    $editor.find('.afb-cond-rule').each(function () {
      const $row = $(this);
      const source = String($row.find('.depends-on-selector').first().val() || '').trim();
      if (!source) return;

      const valsRaw = $row.find('.expected-value-dropdown').first().val();
      const vals = Array.isArray(valsRaw)
        ? valsRaw.map(function (v) { return String(v || '').trim(); }).filter(Boolean)
        : [];

      if (!vals.length) return;

      rules.push({
        depends_on: source,
        values: Array.from(new Set(vals))
      });
    });

    return rules;
  }

  function hydrateExpectedValues($depends, selectedValues) {
    const $row = $depends.closest('.afb-cond-rule');
    const $values = $row.find('.expected-value-dropdown').first();
    if (!$values.length) return;

    const current = Array.isArray(selectedValues)
      ? selectedValues.map(function (v) { return String(v); })
      : [];

    const options = getConditionSourceOptions($depends.val());
    $values.empty();

    options.forEach(function (opt) {
      if (!opt) return;
      if (opt.enabled === false) return;
      const gid = String(opt.gid || '').trim();
      if (!gid) return;

      const selected = current.includes(gid);
      const $o = $('<option></option>').val(gid).text(opt.name || gid);
      if (selected) $o.prop('selected', true);
      $values.append($o);
    });
  }

  function buildConditionRuleRow($editor, rule, selectedFields) {
    const target = String($editor.data('target') || '').trim();
    const selectedSource = String((rule && rule.depends_on) || '').trim();
    const selectedValues = Array.isArray(rule && rule.values)
      ? rule.values.map(function (v) { return String(v); })
      : [];

    const $row = $(`
      <div class="afb-cond-rule afb-cond-rule-card">
        <div class="flex items-start gap-3">
          <div class="flex-1 grid gap-3 md:grid-cols-2">
            <div>
              <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Depends on</label>
              <select class="afb-admin-select depends-on-selector" data-target="${target}">
                <option value="">Select field</option>
              </select>
            </div>
            <div class="afb-cond-values-wrap">
              <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Expected value(s)</label>
              <select class="expected-value-dropdown" multiple></select>
              <p class="text-xs text-slate-400 mt-1.5 mb-0">Hold Ctrl / Cmd to pick multiple</p>
            </div>
          </div>
          <button type="button" class="afb-cond-remove" aria-label="Remove condition" title="Remove condition">Remove</button>
        </div>
      </div>
    `);

    const $sourceSel = $row.find('.depends-on-selector').first();
    selectedFields.forEach(function (f) {
      if (!f.gid || f.gid === target) return;
      const $o = $('<option></option>').val(f.gid).text(f.name || f.gid);
      if (f.gid === selectedSource) $o.prop('selected', true);
      $sourceSel.append($o);
    });

    hydrateExpectedValues($sourceSel, selectedValues);
    return $row;
  }

  function syncConditionEditor($editor) {
    const rules = collectConditionRules($editor);
    writeConditionRulesInput($editor, rules);
    syncFlags($editor.closest('.afb-builder-item'));
  }

  function rebuildConditionEditor($editor, selectedFields) {
    const $rulesWrap = $editor.find('.afb-cond-rules').first();
    if (!$rulesWrap.length) return;

    const rules = parseConditionRulesInput($editor);
    $rulesWrap.empty();

    if (!rules.length) {
      const $blank = buildConditionRuleRow($editor, { depends_on: '', values: [] }, selectedFields);
      $rulesWrap.append($blank);
      syncConditionEditor($editor);
      return;
    }

    rules.forEach(function (rule) {
      $rulesWrap.append(buildConditionRuleRow($editor, rule, selectedFields));
    });

    syncConditionEditor($editor);
  }

  function getDueDateBaseFieldOptions() {
    const out = [];
    const seen = {};

    $selected.children('li[data-kind="field"]').each(function () {
      const $item = $(this);
      if (!$item.hasClass('is-selected')) return;
      const fieldType = String($item.attr('data-field-type') || '').trim().toLowerCase();
      if (fieldType !== 'date') return;

      const gid = String($item.attr('data-id') || '').trim();
      if (!gid || seen[gid]) return;

      seen[gid] = true;
      const label = String($item.find('.afb-item-title').first().text() || gid).trim();
      out.push({ gid, label: label + ' (' + gid + ')' });
    });

    $selected.children('li[data-kind="calendar"]').each(function () {
      const $item = $(this);
      const gid = String($item.attr('data-id') || '').trim();
      if (!gid || seen[gid]) return;

      seen[gid] = true;
      const label = String(
        $item.find('input[name^="custom_calendar_labels["]').first().val()
        || $item.find('.afb-item-title').first().text()
        || 'Calendar Picker'
      ).trim();
      out.push({ gid, label: (label || 'Calendar Picker') + ' (' + gid + ')' });
    });

    return out;
  }

  function normalizeDueDateRules(rawRules) {
    const list = Array.isArray(rawRules) ? rawRules : [];
    const out = [];

    list.forEach(function (rule) {
      if (!rule || typeof rule !== 'object') return;

      const dependsOn = String(rule.depends_on || '').trim();
      const vals = Array.isArray(rule.values)
        ? rule.values.map(function (v) { return String(v || '').trim(); }).filter(Boolean)
        : [];
      if (!dependsOn || !vals.length) return;

      const baseRaw = String(rule.base || 'today').trim();
      const base = baseRaw === 'field' ? 'field' : 'today';
      const baseField = String(rule.base_field || '').trim();
      let offset = Number.parseInt(rule.offset, 10);
      if (!Number.isFinite(offset)) offset = 0;
      if (offset > 3650) offset = 3650;
      if (offset < -3650) offset = -3650;

      const modeRaw = String(rule.mode || 'business').trim();
      const mode = modeRaw === 'calendar' ? 'calendar' : 'business';
      const allowWeekends = !!rule.allow_weekends;

      out.push({
        depends_on: dependsOn,
        values: Array.from(new Set(vals)),
        base,
        base_field: baseField,
        offset,
        mode,
        allow_weekends: allowWeekends,
      });
    });

    return out;
  }

  function parseDueDateRulesInput($item) {
    const $input = $item.find('.afb-due-date-rules-input').first();
    if (!$input.length) return [];

    const parsed = parseJsonObject($input.val(), []);
    return normalizeDueDateRules(Array.isArray(parsed) ? parsed : []);
  }

  function writeDueDateRulesInput($item, rules) {
    const $input = $item.find('.afb-due-date-rules-input').first();
    if (!$input.length) return;

    $input.val(JSON.stringify(normalizeDueDateRules(rules))).trigger('change');
  }

  function hydrateDueRuleSourceValues($row, selectedValues) {
    const $depends = $row.find('.afb-due-rule-depends-on').first();
    const $values = $row.find('.afb-due-rule-values').first();
    if (!$depends.length || !$values.length) return;

    const current = Array.isArray(selectedValues)
      ? selectedValues.map(function (v) { return String(v); })
      : [];

    const options = getConditionSourceOptions($depends.val());
    $values.empty();

    options.forEach(function (opt) {
      if (!opt || opt.enabled === false) return;
      const gid = String(opt.gid || '').trim();
      if (!gid) return;

      const $o = $('<option></option>').val(gid).text(opt.name || gid);
      if (current.includes(gid)) $o.prop('selected', true);
      $values.append($o);
    });
  }

  function hydrateDueRuleBaseFields($row, selectedValue) {
    const $baseField = $row.find('.afb-due-rule-base-field').first();
    if (!$baseField.length) return;

    const current = String(selectedValue || $baseField.val() || '').trim();
    const opts = getDueDateBaseFieldOptions();

    $baseField.empty().append('<option value="">Select date field</option>');
    opts.forEach(function (opt) {
      const gid = String((opt && opt.gid) || '').trim();
      if (!gid) return;
      const $o = $('<option></option>').val(gid).text(opt.label || gid);
      if (gid === current) $o.prop('selected', true);
      $baseField.append($o);
    });
  }

  function syncDueRuleBaseFieldVisibility($row) {
    const $base = $row.find('.afb-due-rule-base').first();
    const $baseFieldWrap = $row.find('.afb-due-rule-base-field-wrap').first();
    if (!$base.length || !$baseFieldWrap.length) return;

    const useField = String($base.val() || '').trim() === 'field';
    $baseFieldWrap.toggle(useField);
  }

  function collectDueDateRules($item) {
    const rules = [];

    $item.find('.afb-due-rule').each(function () {
      const $row = $(this);
      const dependsOn = String($row.find('.afb-due-rule-depends-on').first().val() || '').trim();
      if (!dependsOn) return;

      const valsRaw = $row.find('.afb-due-rule-values').first().val();
      const vals = Array.isArray(valsRaw)
        ? valsRaw.map(function (v) { return String(v || '').trim(); }).filter(Boolean)
        : [];
      if (!vals.length) return;

      const baseRaw = String($row.find('.afb-due-rule-base').first().val() || 'today').trim();
      const base = baseRaw === 'field' ? 'field' : 'today';
      const baseField = String($row.find('.afb-due-rule-base-field').first().val() || '').trim();

      let offset = Number.parseInt($row.find('.afb-due-rule-offset').first().val(), 10);
      if (!Number.isFinite(offset)) offset = 0;
      if (offset > 3650) offset = 3650;
      if (offset < -3650) offset = -3650;

      const modeRaw = String($row.find('.afb-due-rule-mode').first().val() || 'business').trim();
      const mode = modeRaw === 'calendar' ? 'calendar' : 'business';
      const allowWeekends = $row.find('.afb-due-rule-allow-weekends').first().is(':checked');

      rules.push({
        depends_on: dependsOn,
        values: Array.from(new Set(vals)),
        base,
        base_field: baseField,
        offset,
        mode,
        allow_weekends: allowWeekends,
      });
    });

    return rules;
  }

  function syncDueDateRuleEditor($item) {
    const rules = collectDueDateRules($item);
    writeDueDateRulesInput($item, rules);
  }

  function buildDueDateRuleRow($item, rule) {
    const targetId = String($item.attr('data-id') || '').trim();
    const selectedSource = String((rule && rule.depends_on) || '').trim();
    const selectedValues = Array.isArray(rule && rule.values)
      ? rule.values.map(function (v) { return String(v); })
      : [];
    const base = String((rule && rule.base) || 'today').trim() === 'field' ? 'field' : 'today';
    const baseField = String((rule && rule.base_field) || '').trim();
    const offset = Number.isFinite(Number.parseInt(rule && rule.offset, 10))
      ? Number.parseInt(rule.offset, 10)
      : 0;
    const mode = String((rule && rule.mode) || 'business').trim() === 'calendar' ? 'calendar' : 'business';
    const allowWeekends = !!(rule && rule.allow_weekends);

    const $row = $(`
      <div class="afb-due-rule afb-cond-rule-card">
        <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
          <div>
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">When field</label>
            <select class="afb-admin-select afb-due-rule-depends-on">
              <option value="">Select field</option>
            </select>
          </div>
          <div>
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Matches option(s)</label>
            <select class="afb-due-rule-values" multiple></select>
            <p class="text-xs text-slate-400 mt-1.5 mb-0">Hold Ctrl / Cmd to pick multiple</p>
          </div>
          <div>
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Base date</label>
            <select class="afb-admin-select afb-due-rule-base">
              <option value="today">Today</option>
              <option value="field">Date field from form</option>
            </select>
          </div>
          <div class="afb-due-rule-base-field-wrap">
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Base field</label>
            <select class="afb-admin-select afb-due-rule-base-field">
              <option value="">Select date field</option>
            </select>
          </div>
          <div>
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Offset</label>
            <input type="number" step="1" class="afb-admin-input afb-due-rule-offset" placeholder="Example: 7 or -3" />
          </div>
          <div>
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Offset mode</label>
            <select class="afb-admin-select afb-due-rule-mode">
              <option value="business">Business days (default)</option>
              <option value="calendar">Calendar days</option>
            </select>
          </div>
        </div>
        <div class="flex items-center justify-between mt-3">
          <label class="afb-flag-toggle m-0">
            <input type="checkbox" class="afb-admin-check afb-due-rule-allow-weekends" />
            Allow weekends in result
          </label>
          <button type="button" class="afb-cond-remove afb-due-rule-remove" aria-label="Remove due date rule" title="Remove due date rule">Remove</button>
        </div>
      </div>
    `);

    const $depends = $row.find('.afb-due-rule-depends-on').first();
    getSelectedSourceFields().forEach(function (f) {
      if (!f || !f.gid || f.gid === targetId) return;
      const $o = $('<option></option>').val(f.gid).text(f.name || f.gid);
      if (f.gid === selectedSource) $o.prop('selected', true);
      $depends.append($o);
    });

    $row.find('.afb-due-rule-base').first().val(base);
    $row.find('.afb-due-rule-offset').first().val(String(offset));
    $row.find('.afb-due-rule-mode').first().val(mode);
    $row.find('.afb-due-rule-allow-weekends').first().prop('checked', allowWeekends);

    hydrateDueRuleSourceValues($row, selectedValues);
    hydrateDueRuleBaseFields($row, baseField);
    syncDueRuleBaseFieldVisibility($row);

    return $row;
  }

  function rebuildDueDateRuleEditor($item) {
    const $rulesWrap = $item.find('.afb-due-date-rules').first();
    if (!$rulesWrap.length) return;

    const rules = parseDueDateRulesInput($item);
    $rulesWrap.empty();

    if (!rules.length) {
      $rulesWrap.append(buildDueDateRuleRow($item, {
        depends_on: '',
        values: [],
        base: 'today',
        base_field: '',
        offset: 0,
        mode: 'business',
        allow_weekends: false,
      }));
      syncDueDateRuleEditor($item);
      return;
    }

    rules.forEach(function (rule) {
      $rulesWrap.append(buildDueDateRuleRow($item, rule));
    });

    syncDueDateRuleEditor($item);
  }

  function refreshDueDateBaseFieldSelectors() {
    const options = getDueDateBaseFieldOptions();

    $selected.find('select[name^="custom_due_date_default_base_field["]').each(function () {
      const $select = $(this);
      const current = String($select.val() || '').trim();
      $select.empty().append('<option value="">Select date field</option>');

      options.forEach(function (opt) {
        const gid = String((opt && opt.gid) || '').trim();
        if (!gid) return;
        const $o = $('<option></option>').val(gid).text(opt.label || gid);
        if (gid === current) $o.prop('selected', true);
        $select.append($o);
      });
    });

    $selected.find('.afb-due-rule').each(function () {
      const $row = $(this);
      const selected = String($row.find('.afb-due-rule-base-field').first().val() || '').trim();
      hydrateDueRuleBaseFields($row, selected);
    });
  }

  function syncDueDateDefaultBaseVisibility($item) {
    if (!$item || !$item.length) return;

    const $base = $item.find('.afb-due-default-base').first();
    const $wrap = $item.find('.afb-due-default-base-field-wrap').first();
    if (!$base.length || !$wrap.length) return;

    const useField = String($base.val() || '').trim() === 'field';
    $wrap.toggle(useField);
  }

  function normalizeAssigneeRules(rawRules) {
    const out = [];
    const list = Array.isArray(rawRules) ? rawRules : [];

    list.forEach(function (rule) {
      if (!rule || typeof rule !== 'object') return;

      const dependsOn = String(rule.depends_on || '').trim();
      if (!dependsOn) return;

      const vals = Array.isArray(rule.values)
        ? rule.values.map(function (v) { return String(v || '').trim(); }).filter(Boolean)
        : [];
      if (!vals.length) return;

      const assignee = normalizeAssigneeGid(rule.assignee || '');
      if (!assignee) return;

      const fallbackAssignees = Array.isArray(rule.fallback_assignees)
        ? rule.fallback_assignees.map(normalizeAssigneeGid).filter(Boolean)
        : [];

      const dedupFallbacks = [];
      const seenFallbacks = {};
      fallbackAssignees.forEach(function (gid) {
        if (!gid || gid === assignee || seenFallbacks[gid]) return;
        seenFallbacks[gid] = true;
        dedupFallbacks.push(gid);
      });

      out.push({
        depends_on: dependsOn,
        values: Array.from(new Set(vals)),
        assignee,
        fallback_assignees: dedupFallbacks,
      });
    });

    return out;
  }

  function parseAssigneeRulesInput($item) {
    const $input = $item.find('.afb-assignee-rules-input').first();
    if (!$input.length) return [];

    const parsed = parseJsonObject($input.val(), []);
    return normalizeAssigneeRules(Array.isArray(parsed) ? parsed : []);
  }

  function writeAssigneeRulesInput($item, rules) {
    const $input = $item.find('.afb-assignee-rules-input').first();
    if (!$input.length) return;

    $input.val(JSON.stringify(normalizeAssigneeRules(rules))).trigger('change');
  }

  function hydrateAssigneeRuleSourceValues($row, selectedValues) {
    const $depends = $row.find('.afb-assignee-rule-depends-on').first();
    const $values = $row.find('.afb-assignee-rule-values').first();
    if (!$depends.length || !$values.length) return;

    const current = Array.isArray(selectedValues)
      ? selectedValues.map(function (v) { return String(v); })
      : [];

    const options = getConditionSourceOptions($depends.val());
    $values.empty();

    options.forEach(function (opt) {
      if (!opt || opt.enabled === false) return;
      const gid = String(opt.gid || '').trim();
      if (!gid) return;

      const $o = $('<option></option>').val(gid).text(opt.name || gid);
      if (current.includes(gid)) $o.prop('selected', true);
      $values.append($o);
    });
  }

  function hydrateAssigneeRuleMemberSelectors($row, selectedAssignee, selectedFallbacks) {
    const assignee = normalizeAssigneeGid(selectedAssignee || '');
    const fallbackList = Array.isArray(selectedFallbacks)
      ? selectedFallbacks.map(normalizeAssigneeGid).filter(Boolean)
      : [];

    const $assignee = $row.find('.afb-assignee-rule-assignee').first();
    if ($assignee.length) {
      $assignee.html(buildAssigneeSelectOptionsHtml(assignee, true, 'Select assignee', fallbackList));
    }

    const $fallbacks = $row.find('.afb-assignee-rule-fallbacks').first();
    if ($fallbacks.length) {
      $fallbacks.html(buildAssigneeSelectOptionsHtml('', false, '', [assignee].concat(fallbackList)));

      const selected = [];
      const seen = {};
      fallbackList.forEach(function (gid) {
        if (!gid || gid === assignee || seen[gid]) return;
        seen[gid] = true;
        selected.push(gid);
      });

      $fallbacks.val(selected);
    }
  }

  function collectAssigneeRules($item) {
    const rules = [];

    $item.find('.afb-assignee-rule').each(function () {
      const $row = $(this);

      const dependsOn = String($row.find('.afb-assignee-rule-depends-on').first().val() || '').trim();
      if (!dependsOn) return;

      const valsRaw = $row.find('.afb-assignee-rule-values').first().val();
      const vals = Array.isArray(valsRaw)
        ? valsRaw.map(function (v) { return String(v || '').trim(); }).filter(Boolean)
        : [];
      if (!vals.length) return;

      const assignee = normalizeAssigneeGid($row.find('.afb-assignee-rule-assignee').first().val() || '');
      if (!assignee) return;

      const fallbackRaw = $row.find('.afb-assignee-rule-fallbacks').first().val();
      const fallbackVals = Array.isArray(fallbackRaw)
        ? fallbackRaw.map(normalizeAssigneeGid).filter(Boolean)
        : [];

      const fallbackAssignees = [];
      const seenFallbacks = {};
      fallbackVals.forEach(function (gid) {
        if (!gid || gid === assignee || seenFallbacks[gid]) return;
        seenFallbacks[gid] = true;
        fallbackAssignees.push(gid);
      });

      rules.push({
        depends_on: dependsOn,
        values: Array.from(new Set(vals)),
        assignee,
        fallback_assignees: fallbackAssignees,
      });
    });

    return rules;
  }

  function syncAssigneeRuleEditor($item) {
    writeAssigneeRulesInput($item, collectAssigneeRules($item));
  }

  function syncAssigneeDefaultFallbackSelection($item) {
    const $default = $item.find('select[name^="custom_assignee_default["]').first();
    const $fallbacks = $item.find('select[name^="custom_assignee_default_fallbacks["]').first();
    if (!$default.length || !$fallbacks.length) return;

    const defaultGid = normalizeAssigneeGid($default.val() || '');
    const rawVals = Array.isArray($fallbacks.val())
      ? $fallbacks.val().map(normalizeAssigneeGid).filter(Boolean)
      : [];

    const nextVals = [];
    const seen = {};
    rawVals.forEach(function (gid) {
      if (!gid || gid === defaultGid || seen[gid]) return;
      seen[gid] = true;
      nextVals.push(gid);
    });

    $fallbacks.val(nextVals);
  }

  function buildAssigneeRuleRow($item, rule) {
    const targetId = String($item.attr('data-id') || '').trim();
    const selectedSource = String((rule && rule.depends_on) || '').trim();
    const selectedValues = Array.isArray(rule && rule.values)
      ? rule.values.map(function (v) { return String(v); })
      : [];
    const selectedAssignee = normalizeAssigneeGid((rule && rule.assignee) || '');
    const selectedFallbacks = Array.isArray(rule && rule.fallback_assignees)
      ? rule.fallback_assignees.map(normalizeAssigneeGid).filter(Boolean)
      : [];

    const $row = $(`
      <div class="afb-assignee-rule afb-cond-rule-card">
        <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
          <div>
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">When field</label>
            <select class="afb-admin-select afb-assignee-rule-depends-on">
              <option value="">Select field</option>
            </select>
          </div>
          <div>
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Matches option(s)</label>
            <select class="afb-assignee-rule-values" multiple></select>
            <p class="text-xs text-slate-400 mt-1.5 mb-0">Hold Ctrl / Cmd to pick multiple</p>
          </div>
          <div>
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Assign to</label>
            <select class="afb-admin-select afb-assignee-rule-assignee">
              <option value="">Select assignee</option>
            </select>
          </div>
          <div class="md:col-span-2 lg:col-span-3">
            <label class="block text-xs font-semibold text-slate-500 mb-1.5 uppercase tracking-wider">Fallback assignees</label>
            <select class="afb-admin-select afb-assignee-rule-fallbacks" multiple size="5"></select>
            <p class="text-xs text-slate-400 mt-1.5 mb-0">Checked in order until one person is not out-of-office.</p>
          </div>
        </div>
        <div class="flex items-center justify-end mt-3">
          <button type="button" class="afb-cond-remove afb-assignee-rule-remove" aria-label="Remove assignee rule" title="Remove assignee rule">Remove</button>
        </div>
      </div>
    `);

    const $depends = $row.find('.afb-assignee-rule-depends-on').first();
    getSelectedSourceFields().forEach(function (f) {
      if (!f || !f.gid || f.gid === targetId) return;
      const $o = $('<option></option>').val(f.gid).text(f.name || f.gid);
      if (f.gid === selectedSource) $o.prop('selected', true);
      $depends.append($o);
    });

    hydrateAssigneeRuleSourceValues($row, selectedValues);
    hydrateAssigneeRuleMemberSelectors($row, selectedAssignee, selectedFallbacks);

    return $row;
  }

  function rebuildAssigneeRuleEditor($item) {
    const $rulesWrap = $item.find('.afb-assignee-rules').first();
    if (!$rulesWrap.length) return;

    const rules = parseAssigneeRulesInput($item);
    $rulesWrap.empty();

    if (!rules.length) {
      $rulesWrap.append(buildAssigneeRuleRow($item, {
        depends_on: '',
        values: [],
        assignee: '',
        fallback_assignees: [],
      }));
      syncAssigneeRuleEditor($item);
      return;
    }

    rules.forEach(function (rule) {
      $rulesWrap.append(buildAssigneeRuleRow($item, rule));
    });

    syncAssigneeRuleEditor($item);
  }

  function refreshAssigneeSelectors() {
    $selected.find('li[data-kind="assignee_tool"]').each(function () {
      const $item = $(this);

      const $default = $item.find('select[name^="custom_assignee_default["]').first();
      const $fallbacks = $item.find('select[name^="custom_assignee_default_fallbacks["]').first();
      if ($default.length) {
        const currentDefault = normalizeAssigneeGid($default.val() || '');
        const fallbackVals = $fallbacks.length && Array.isArray($fallbacks.val())
          ? $fallbacks.val().map(normalizeAssigneeGid).filter(Boolean)
          : [];
        $default.html(buildAssigneeSelectOptionsHtml(currentDefault, true, 'Leave unassigned by default', fallbackVals));
      }

      if ($fallbacks.length) {
        const currentDefault = normalizeAssigneeGid($default.val() || '');
        const currentFallbacks = Array.isArray($fallbacks.val())
          ? $fallbacks.val().map(normalizeAssigneeGid).filter(Boolean)
          : [];

        $fallbacks.html(buildAssigneeSelectOptionsHtml('', false, '', [currentDefault].concat(currentFallbacks)));

        const selectedFallbacks = [];
        const seenFallbacks = {};
        currentFallbacks.forEach(function (gid) {
          if (!gid || gid === currentDefault || seenFallbacks[gid]) return;
          seenFallbacks[gid] = true;
          selectedFallbacks.push(gid);
        });

        $fallbacks.val(selectedFallbacks);
      }

      syncAssigneeDefaultFallbackSelection($item);

      $item.find('.afb-assignee-rule').each(function () {
        const $row = $(this);
        const rowAssignee = normalizeAssigneeGid($row.find('.afb-assignee-rule-assignee').first().val() || '');
        const rowFallbacks = Array.isArray($row.find('.afb-assignee-rule-fallbacks').first().val())
          ? $row.find('.afb-assignee-rule-fallbacks').first().val().map(normalizeAssigneeGid).filter(Boolean)
          : [];
        hydrateAssigneeRuleMemberSelectors($row, rowAssignee, rowFallbacks);
      });
    });
  }

  function refreshDependencySources() {
    const selectedFields = getSelectedSourceFields();

    $selected.find('.afb-cond-editor').each(function () {
      rebuildConditionEditor($(this), selectedFields);
    });

    $selected.find('li[data-kind="due_date_tool"]').each(function () {
      const $item = $(this);
      rebuildDueDateRuleEditor($item);
      syncDueDateDefaultBaseVisibility($item);
    });

    $selected.find('li[data-kind="assignee_tool"]').each(function () {
      rebuildAssigneeRuleEditor($(this));
    });

    refreshDueDateBaseFieldSelectors();
    refreshAssigneeSelectors();
    refreshRecipientSourceSelectors();

    $selected.find('.afb-option-filter-source').each(function () {
      const $sel = $(this);
      const target = ($sel.data('target') || '').toString();
      const current = ($sel.val() || '').toString();

      $sel.empty().append('<option value="">Disabled (show all visible options)</option>');

      selectedFields.forEach(function (f) {
        if (!f.gid || f.gid === target) return;

        const srcOptions = allOptionsMap[f.gid] || [];
        const hasEnabledOptions = Array.isArray(srcOptions) && srcOptions.some(function (opt) {
          if (!opt) return false;
          if (opt.enabled === false) return false;
          return String(opt.gid || '').trim() !== '';
        });

        if (!hasEnabledOptions) return;

        const $o = $('<option></option>').val(f.gid).text(f.name || f.gid);
        if (f.gid === current) $o.prop('selected', true);
        $sel.append($o);
      });

      rebuildOptionFilterRows($sel.closest('.afb-optmap-section'));
    });

    runSyncFieldDuplicateCheck(false);
    refreshTaskNameVariablePickers();
    refreshRecipientVariablePickers();
  }

  function insertBlockByType(type, $insertBefore) {
    const t = (type || '').toString();
    if (t === 'paragraph') return addParagraphBlock($insertBefore);
    if (t === 'section') return addSectionBreakBlock($insertBefore);
    if (t === 'agreement') return addAgreementBlock($insertBefore);
    if (t === 'upload') return addUploadBlock($insertBefore);
    if (t === 'single_input') return addSingleInputBlock($insertBefore);
    if (t === 'dropdown') return addDropdownBlock($insertBefore);
    if (t === 'calendar') return addCalendarBlock($insertBefore);
    if (t === 'repeater') return addRepeaterBlock($insertBefore);
    if (t === 'task_name') return addTaskNameBlock($insertBefore);
    if (t === 'due_date') return addDueDateBlock($insertBefore);
    if (t === 'assignee') return addAssigneeBlock($insertBefore);
    if (t === 'recipient') return addRecipientBlock($insertBefore);
    return null;
  }

  function kindToUidPrefix(kind) {
    const k = (kind || '').toString();
    if (k === 'paragraph') return 'para';
    if (k === 'section') return 'section';
    if (k === 'agreement') return 'agree';
    if (k === 'upload') return 'upload';
    if (k === 'single_input') return 'single';
    if (k === 'dropdown') return 'dropdown';
    if (k === 'calendar') return 'calendar';
    if (k === 'repeater') return 'repeater';
    if (k === 'task_name') return 'task_name';
    if (k === 'due_date_tool') return 'due_date';
    if (k === 'assignee_tool') return 'assignee';
    if (k === 'recipient_tool') return 'recipient';
    return 'item';
  }

  function remapCloneIdentifiers($clone, oldId, newId) {
    const from = `[${oldId}]`;
    const to = `[${newId}]`;

    $clone.attr('data-id', newId);

    $clone.find('[name]').each(function () {
      const $el = $(this);
      const name = ($el.attr('name') || '').toString();
      if (name.includes(from)) {
        $el.attr('name', name.split(from).join(to));
      }
    });

    $clone.find('[data-target]').each(function () {
      const $el = $(this);
      const target = ($el.attr('data-target') || '').toString();
      if (target === oldId) {
        $el.attr('data-target', newId);
      }
    });

    $clone.find('input[name="field_order[]"]').first().val(newId);

    $clone.find('input, textarea, select').each(function () {
      const $el = $(this);
      const val = ($el.val() || '').toString();
      if (val === oldId) {
        $el.val(newId);
      }
    });
  }

  function copyFormControlState($source, $clone) {
    const $srcControls = $source.find('input, textarea, select');
    const $dstControls = $clone.find('input, textarea, select');

    $srcControls.each(function (i) {
      const src = this;
      const $src = $(src);
      const $dst = $dstControls.eq(i);
      if (!$dst.length) return;

      const tag = (src.tagName || '').toLowerCase();
      const type = (src.type || '').toLowerCase();

      if (type === 'checkbox' || type === 'radio') {
        $dst.prop('checked', !!src.checked);
        return;
      }

      if (tag === 'select' && src.multiple) {
        const vals = Array.isArray($src.val()) ? $src.val() : [];
        $dst.val(vals);
        return;
      }

      $dst.val($src.val());
    });

    const $srcEditors = $source.find('.afb-paragraph-wysiwyg');
    const $dstEditors = $clone.find('.afb-paragraph-wysiwyg');
    $srcEditors.each(function (i) {
      const html = $(this).html();
      const $dst = $dstEditors.eq(i);
      if ($dst.length) {
        $dst.html(html);
      }
    });
  }

  function duplicateBuilderItem($item) {
    if (!$item || !$item.length) return null;

    const kind = ($item.attr('data-kind') || '').toString();
    if (!kind || kind === 'field') {
      return null;
    }

    const oldId = ($item.attr('data-id') || '').toString();
    if (!oldId) return null;

    const newId = uid(kindToUidPrefix(kind));
    const $clone = $item.clone(false, false);

    copyFormControlState($item, $clone);
    remapCloneIdentifiers($clone, oldId, newId);

    $item.after($clone);
    syncIconButtonLabels($clone);

    enforceExclusiveWidthSelections($clone, '');
    syncSingleInputHalfBadge($clone);
    syncSingleInputAiBadge($clone);
    syncSingleInputWordLimitControls($clone);
    syncDropdownHalfBadge($clone);
    syncCalendarHalfBadge($clone);
    syncRepeaterHalfBadge($clone);
    syncRepeaterSaveModeSummary($clone);
    syncAgreementRequiredBadge($clone);
    syncUploadRequiredToggleFromMin($clone);
    syncCollapsibleSections($clone);
    syncFlags($clone);
    initRepeaterMapLists($clone);
    refreshRepeaterMapItems($clone);
    if (kind === 'section') {
      syncSectionMoveButtons();
    }

    $clone.find('.afb-paragraph-editor-wrap').each(function () {
      syncParagraphEditor($(this));
    });

    refreshDependencySources();
    return $clone;
  }

  function parseJsonObject(raw, fallback) {
    try {
      const parsed = JSON.parse(raw || '');
      if (parsed && typeof parsed === 'object') return parsed;
    } catch (_e) {}
    return fallback;
  }

  function normalizeOptionMap(rawMap) {
    const out = {};
    if (!rawMap || typeof rawMap !== 'object') return out;

    Object.keys(rawMap).forEach(function (sourceKey) {
      const key = String(sourceKey || '').trim();
      if (!key) return;

      const vals = Array.isArray(rawMap[sourceKey]) ? rawMap[sourceKey] : [];
      const clean = vals
        .map(function (v) { return String(v || '').trim(); })
        .filter(Boolean);

      if (clean.length) {
        out[key] = Array.from(new Set(clean));
      }
    });

    return out;
  }

  function getEnabledFieldOptions(fieldGid) {
    const all = allOptionsMap[fieldGid] || [];
    return all.filter(function (opt) {
      if (!opt) return false;
      if (opt.enabled === false) return false;
      const gid = String(opt.gid || '').trim();
      return gid !== '';
    });
  }

  function readTargetOptions($section) {
    const raw = $section.attr('data-target-options') || '[]';
    const parsed = parseJsonObject(raw, []);
    const arr = Array.isArray(parsed) ? parsed : [];
    return arr
      .map(function (opt) {
        return {
          gid: String((opt && opt.gid) || '').trim(),
          name: String((opt && opt.name) || '').trim(),
        };
      })
      .filter(function (opt) { return opt.gid !== ''; });
  }

  function syncOptionFilterMap($section) {
    const $mapInput = $section.find('.afb-option-filter-map').first();
    if (!$mapInput.length) return;

    const out = {};
    $section.find('.afb-option-filter-row').each(function () {
      const $row = $(this);
      const sourceValue = String($row.attr('data-source-option') || '').trim();
      if (!sourceValue) return;

      const valsRaw = $row.find('.afb-option-filter-allowed').first().val();
      const vals = Array.isArray(valsRaw)
        ? valsRaw.map(function (v) { return String(v || '').trim(); }).filter(Boolean)
        : [];

      if (vals.length) {
        out[sourceValue] = Array.from(new Set(vals));
      }
    });

    $mapInput.val(JSON.stringify(out));
  }

  function rebuildOptionFilterRows($section) {
    const $source = $section.find('.afb-option-filter-source').first();
    const $mapInput = $section.find('.afb-option-filter-map').first();
    const $rows = $section.find('.afb-option-filter-rows').first();
    if (!$source.length || !$mapInput.length || !$rows.length) return;

    const sourceFieldGid = String($source.val() || '').trim();
    const targetOptions = readTargetOptions($section);
    const currentMap = normalizeOptionMap(parseJsonObject($mapInput.val(), {}));

    $rows.empty();

    if (!sourceFieldGid) {
      $rows.html('<p class="text-xs text-slate-400 m-0">Select a source field to define option mapping.</p>');
      $mapInput.val('{}');
      return;
    }

    const sourceOptions = getEnabledFieldOptions(sourceFieldGid);
    if (!sourceOptions.length) {
      $rows.html('<p class="text-xs text-slate-400 m-0">The selected source field has no active options.</p>');
      $mapInput.val('{}');
      return;
    }

    if (!targetOptions.length) {
      $rows.html('<p class="text-xs text-slate-400 m-0">No target options are available for this field.</p>');
      $mapInput.val('{}');
      return;
    }

    sourceOptions.forEach(function (sourceOpt) {
      const sourceValue = String(sourceOpt.gid || '').trim();
      if (!sourceValue) return;

      const selectedValues = Array.isArray(currentMap[sourceValue]) ? currentMap[sourceValue] : [];
      const sourceLabelSafe = $('<div></div>').text(sourceOpt.name || sourceValue).html();
      const $row = $(`
        <div class="afb-option-filter-row" data-source-option="${sourceValue}">
          <div class="text-xs font-medium text-slate-700 pt-1">${sourceLabelSafe}</div>
          <select class="afb-admin-select afb-option-filter-allowed" multiple size="4"></select>
        </div>
      `);

      const $allowed = $row.find('.afb-option-filter-allowed').first();
      targetOptions.forEach(function (targetOpt) {
        const $opt = $('<option></option>').val(targetOpt.gid).text(targetOpt.name || targetOpt.gid);
        if (selectedValues.includes(targetOpt.gid)) {
          $opt.prop('selected', true);
        }
        $allowed.append($opt);
      });

      $rows.append($row);
    });

    syncOptionFilterMap($section);
  }

  function getSectionRanges() {
    const $items = $selected.children('li');
    const starts = [];

    $items.each(function (idx) {
      const kind = ($(this).attr('data-kind') || '').toString();
      if (kind === 'section') starts.push(idx);
    });

    return starts.map(function (start, i) {
      const end = (i + 1 < starts.length) ? starts[i + 1] : $items.length;
      return {
        start,
        end,
        $section: $items.eq(start),
      };
    });
  }

  function syncSectionMoveButtons() {
    const ranges = getSectionRanges();
    if (!ranges.length) return;

    ranges.forEach(function (range, i) {
      const isFirst = i === 0;
      const isLast = i === ranges.length - 1;
      range.$section.find('.afb-move-section-up').first().prop('disabled', isFirst);
      range.$section.find('.afb-move-section-down').first().prop('disabled', isLast);
    });
  }

  function moveSectionRange($section, direction) {
    if (!$section || !$section.length) return;

    const ranges = getSectionRanges();
    if (!ranges.length) return;

    let currentIndex = -1;
    ranges.forEach(function (range, i) {
      if (range.$section.length && range.$section[0] === $section[0]) {
        currentIndex = i;
      }
    });
    if (currentIndex < 0) return;

    const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
    if (targetIndex < 0 || targetIndex >= ranges.length) return;

    const $items = $selected.children('li');
    const currentRange = ranges[currentIndex];
    const targetRange = ranges[targetIndex];
    const $segment = $items.slice(currentRange.start, currentRange.end).detach();

    if (direction === 'up') {
      const $anchor = $selected.children('li').eq(targetRange.start);
      if ($anchor.length) {
        $anchor.before($segment);
      } else {
        $selected.prepend($segment);
      }
    } else {
      const anchorEl = $items.get(targetRange.end - 1);
      if (anchorEl) {
        $(anchorEl).after($segment);
      } else {
        $selected.append($segment);
      }
    }

    syncSectionMoveButtons();
    syncCollapsedSectionVisibility();
    refreshDependencySources();
  }

  $selected.add($available).sortable({
    placeholder: 'ui-state-highlight',
    // Reorder selected fields by grip; drag available fields by header area.
    handle: '.afb-handle, .afb-builder-item.is-available .afb-builder-item-header',
    cancel: '.afb-admin-btn, button, input, select, textarea, a',
    connectWith: '#field-sortable, #field-available',
    start: function (_e, ui) {
      const $item = ui.item;
      const kind = ($item.attr('data-kind') || '').toString();

      if (kind !== 'section') {
        $item.removeData('afbSectionFollowerIds');
        return;
      }

      // Capture rows that currently belong to this section so drag-reorder can
      // keep the section as a container.
      const followerIds = [];
      let $cursor = $item.next('li');
      while ($cursor.length) {
        const cursorKind = ($cursor.attr('data-kind') || '').toString();
        if (cursorKind === 'section') break;

        const followerId = ($cursor.attr('data-id') || '').toString();
        if (followerId) followerIds.push(followerId);

        $cursor = $cursor.next('li');
      }

      $item.data('afbSectionFollowerIds', followerIds);
    },
    receive: function (_e, ui) {
      const $list = $(this);
      const $item = ui.item;
      const kind = ($item.attr('data-kind') || '').toString();
      const intoSelected = $list.is('#field-sortable');

      // Dropped from static "Add block" tools.
      if ($item.hasClass('afb-add-block')) {
        const type = ($item.data('block-type') || '').toString();

        if (!intoSelected) {
          $item.remove();
          return;
        }

        const $newItem = insertBlockByType(type, $item);
        $item.remove();
        if ($newItem && $newItem.length) {
          refreshDependencySources();
        }
        return;
      }

      if (kind !== 'field' && !intoSelected) {
        $item.appendTo($selected);
        return;
      }

      if (kind === 'field') {
        setFieldSelected($item, intoSelected);
        refreshRepeaterDragSources($item);
        refreshDependencySources();
      }
    },
    stop: function (_e, ui) {
      const $item = ui && ui.item ? ui.item : $();
      const kind = ($item.attr('data-kind') || '').toString();

      if (kind === 'section') {
        const followerIds = $item.data('afbSectionFollowerIds');
        if (Array.isArray(followerIds) && followerIds.length) {
          let $anchor = $item;

          followerIds.forEach(function (id) {
            const wantedId = String(id || '').trim();
            if (!wantedId) return;

            const $row = $selected.children('li').filter(function () {
              return String($(this).attr('data-id') || '').trim() === wantedId;
            }).first();

            if (!$row.length) return;
            $anchor.after($row);
            $anchor = $row;
          });
        }

        $item.removeData('afbSectionFollowerIds');
      }

      syncSectionMoveButtons();
      syncCollapsedSectionVisibility();
      refreshDependencySources();
    }
  });

  $(document).on('click', '.afb-collapse-all', function () {
    $('.afb-builder-item').addClass('is-collapsed').each(function () {
      syncCollapseToggleButton($(this));
    });
    syncCollapsedSectionVisibility();
  });

  $(document).on('click', '.afb-expand-all', function () {
    $('.afb-builder-item').removeClass('is-collapsed').each(function () {
      syncCollapseToggleButton($(this));
    });
    syncCollapsedSectionVisibility();
  });

  $(document).on('click', '.afb-collapse-toggle', function (e) {
    e.preventDefault();
    toggleItem($(this).closest('.afb-builder-item'));
  });

  $(document).on('click', '.afb-move-section-up, .afb-move-section-down', function (e) {
    e.preventDefault();
    const $section = $(this).closest('.afb-builder-item[data-kind="section"]');
    if (!$section.length) return;

    const direction = $(this).hasClass('afb-move-section-up') ? 'up' : 'down';
    moveSectionRange($section, direction);
  });

  $(document).on('click', '.afb-duplicate-item', function (e) {
    e.preventDefault();
    const $item = $(this).closest('.afb-builder-item');
    if (!$item.length) return;

    duplicateBuilderItem($item);
  });

  $(document).on('click', '.afb-add-to-form', function (e) {
    e.preventDefault();
    const $item = $(this).closest('.afb-builder-item');
    if (!$item.length) return;
    setFieldSelected($item, true);
    $item.appendTo($selected);
    refreshDependencySources();
  });

  $(document).on('click', '.remove-builder-item', function (e) {
    e.preventDefault();
    const $item = $(this).closest('.afb-builder-item');
    if (!$item.length) return;

    const kind = ($item.attr('data-kind') || '').toString();
    if (kind === 'field') {
      setFieldSelected($item, false);
      $item.appendTo($available);
      refreshRepeaterDragSources($item);
      refreshDependencySources();
      return;
    }

    $item.remove();
    syncSectionMoveButtons();
    syncCollapsedSectionVisibility();
    runSyncFieldDuplicateCheck(false);
    syncRepeaterMapEmptyState($(document));
    refreshDependencySources();
  });

  $(document).on('click', '.afb-repeater-map-remove', function (e) {
    e.preventDefault();
    const $item = $(this).closest('.afb-repeater-map-item');
    const $list = $item.closest('.afb-repeater-map-list');
    if (!$item.length || !$list.length) return;

    $item.remove();
    refreshRepeaterMapItems($list);
  });

  $(document).on('dragstart', '.afb-repeater-drag-field, .afb-chip.afb-chip-field[draggable="true"]', function (e) {
    const $source = $(this);
    const gid = String($source.attr('data-field-gid') || $source.closest('li[data-kind="field"]').attr('data-id') || '').trim();
    if (!gid) return;

    const meta = getRepeaterFieldMetaByGid(gid) || {
      gid,
      type: String($source.attr('data-field-type') || $source.closest('li[data-kind="field"]').attr('data-field-type') || 'text').trim().toLowerCase(),
      label: String($source.attr('data-field-label') || $source.closest('li[data-kind="field"]').find('.afb-item-title').first().text() || ('Field ' + gid)).trim(),
    };

    const oe = e.originalEvent;
    if (!oe || !oe.dataTransfer) return;

    oe.dataTransfer.effectAllowed = 'copy';
    oe.dataTransfer.setData('application/x-afb-repeater-field', JSON.stringify(meta));
    oe.dataTransfer.setData('text/plain', gid);
  });

  $(document).on('dragover', '.afb-repeater-map-list', function (e) {
    const oe = e.originalEvent;
    if (!oe || !oe.dataTransfer) return;

    oe.preventDefault();
    oe.dataTransfer.dropEffect = 'copy';
    $(this).addClass('is-drag-over');
  });

  $(document).on('dragleave', '.afb-repeater-map-list', function () {
    $(this).removeClass('is-drag-over');
  });

  $(document).on('drop', '.afb-repeater-map-list', function (e) {
    const $list = $(this);
    const oe = e.originalEvent;
    if (!oe || !oe.dataTransfer) return;

    oe.preventDefault();
    $list.removeClass('is-drag-over');

    let meta = null;
    const rawPayload = oe.dataTransfer.getData('application/x-afb-repeater-field');
    if (rawPayload) {
      try {
        const parsed = JSON.parse(rawPayload);
        if (parsed && typeof parsed === 'object' && parsed.gid) {
          meta = {
            gid: String(parsed.gid || '').trim(),
            type: String(parsed.type || '').trim().toLowerCase(),
            label: String(parsed.label || '').trim(),
          };
        }
      } catch (_err) {
        meta = null;
      }
    }

    if (!meta || !meta.gid) {
      const fallbackGid = String(oe.dataTransfer.getData('text/plain') || '').trim();
      if (!fallbackGid) return;
      meta = getRepeaterFieldMetaByGid(fallbackGid);
    }

    if (!meta) return;
    appendFieldToRepeaterMapList($list, meta);
  });

  $(document).on(
    'change input',
    'input[name="required_fields[]"], input[name="agreement_required_fields[]"], input[name="submit_block_fields[]"], input[name="hidden_fields[]"], input[name="readonly_fields[]"], input[name="half_width_fields[]"], input[name="third_width_fields[]"], input[name="paragraph_fields[]"], input[name="custom_single_input_ai_enabled[]"], .depends-on-selector, select[name^="visible_enum_options["], .afb-option-filter-source, .afb-option-filter-allowed, input[name^="labels["], select[name^="custom_single_input_modes["], .afb-single-max-words-toggle, input[name^="custom_single_input_max_words["], select[name^="custom_repeater_save_mode["]',
    function () {
      const $item = $(this).closest('.afb-builder-item');
      const changedName = String($(this).attr('name') || '').trim();
      enforceExclusiveWidthSelections($item, changedName);
      syncFlags($item);
      syncSingleInputHalfBadge($item);
      syncSingleInputAiBadge($item);
      syncSingleInputWordLimitControls($item);
      syncDropdownHalfBadge($item);
      syncCalendarHalfBadge($item);
      syncRepeaterHalfBadge($item);
      syncRepeaterSaveModeSummary($item);
      syncAgreementRequiredBadge($item);
    }
  );

  $(document).on('change', '.afb-upload-required-toggle', function () {
    const $item = $(this).closest('.afb-builder-item');
    const $minInput = $item.find('input[name^="custom_upload_min_files["]').first();
    if (!$minInput.length) return;

    if (this.checked) {
      const current = Math.max(0, parseInt($minInput.val(), 10) || 0);
      if (current <= 0) {
        $minInput.val('1').trigger('input').trigger('change');
      }
      return;
    }

    $minInput.val('0').trigger('input').trigger('change');
  });

  $(document).on('change input', 'input[name^="custom_upload_min_files["]', function () {
    const $item = $(this).closest('.afb-builder-item');
    syncUploadRequiredToggleFromMin($item);
  });

  $(document).on('change input', 'input[name^="custom_repeater_min_rows["], input[name^="custom_repeater_max_rows["]', function () {
    const $item = $(this).closest('.afb-builder-item');
    if (!$item.length) return;

    const $min = $item.find('input[name^="custom_repeater_min_rows["]').first();
    const $max = $item.find('input[name^="custom_repeater_max_rows["]').first();
    if (!$min.length || !$max.length) return;

    let minRows = Math.max(0, Math.min(250, parseInt($min.val(), 10) || 0));
    let maxRows = Math.max(0, Math.min(250, parseInt($max.val(), 10) || 0));

    if (maxRows > 0 && maxRows < minRows) {
      maxRows = minRows;
    }

    $min.val(String(minRows));
    $max.val(String(maxRows));
  });

  $(document).on(
    'change input',
    'input[name^="labels["], input[name^="custom_paragraph_titles["], input[name^="custom_section_titles["], input[name^="custom_agreement_labels["], input[name^="custom_upload_titles["], input[name^="custom_single_input_labels["], input[name^="custom_dropdown_labels["], input[name^="custom_calendar_labels["], input[name^="custom_repeater_labels["], input[name^="custom_task_name_labels["], input[name^="custom_assignee_labels["], input[name^="custom_recipient_labels["]',
    function () {
      const $item = $(this).closest('.afb-builder-item');
      syncItemHeaderText($item);
      refreshRepeaterDragSources($item);
      refreshRepeaterMapItems($(document));
      refreshTaskNameVariablePickers();
      refreshRecipientVariablePickers();
      refreshDueDateBaseFieldSelectors();
    }
  );

  $(document).on('click', '.afb-task-name-insert-variable', function (e) {
    e.preventDefault();
    const $body = $(this).closest('.afb-builder-item-body');
    if (!$body.length) return;

    const token = String($body.find('.afb-task-name-variable-select').first().val() || '').trim();
    if (!token) return;

    const $textarea = $body.find('.afb-task-name-template').first();
    if (!$textarea.length) return;

    insertTaskNameToken($textarea, token);
  });

  $(document).on('click', '.afb-recipient-insert-variable', function (e) {
    e.preventDefault();
    const $body = $(this).closest('.afb-builder-item-body');
    if (!$body.length) return;

    const token = String($body.find('.afb-recipient-variable-select').first().val() || '').trim();
    if (!token) return;

    const targetTemplate = String($(this).attr('data-target-template') || '').trim();
    const $target = targetTemplate === 'subject'
      ? $body.find('input[name^="custom_recipient_subject_templates["]').first()
      : $body.find('textarea[name^="custom_recipient_body_templates["]').first();

    if (!$target.length) return;

    insertTaskNameToken($target, token);
  });

  $(document).on(
    'change',
    'select[name^="custom_text_sync_fields["], select[name^="custom_single_input_sync_fields["], select[name^="custom_dropdown_sync_fields["], select[name^="custom_calendar_sync_fields["], select[name^="custom_calendar_sync_modes["]',
    function () {
      runSyncFieldDuplicateCheck(true);
    }
  );

  function getParagraphEditorNodes($scope) {
    const $wrap = $scope && $scope.length ? $scope.closest('.afb-paragraph-editor-wrap') : $();
    const $editor = $wrap.find('.afb-paragraph-wysiwyg').first();
    const $textarea = $wrap.find('.afb-paragraph-editor').first();
    return { $wrap, $editor, $textarea };
  }

  function syncParagraphEditor($scope) {
    const { $editor, $textarea } = getParagraphEditorNodes($scope);
    if (!$editor.length || !$textarea.length) return;

    let html = ($editor.html() || '').trim();
    if (html === '<br>' || html === '<div><br></div>') {
      html = '';
    }

    $textarea.val(html).trigger('input').trigger('change');
  }

  function execParagraphCommand($scope, command, value) {
    const { $editor } = getParagraphEditorNodes($scope);
    if (!$editor.length) return;

    const editorEl = $editor[0];
    editorEl.focus();

    try {
      if (typeof value === 'undefined') {
        document.execCommand(command, false, null);
      } else {
        document.execCommand(command, false, value);
      }
    } catch (_e) {
      return;
    }

    syncParagraphEditor($editor);
  }

  function getParagraphSelectionRange($editor) {
    if (!$editor || !$editor.length) return null;

    const editorEl = $editor[0];
    const sel = window.getSelection ? window.getSelection() : null;
    if (!sel || sel.rangeCount === 0) return null;

    const range = sel.getRangeAt(0);
    if (!editorEl.contains(range.commonAncestorContainer)) return null;
    return range.cloneRange();
  }

  function restoreParagraphSelectionRange($editor, range) {
    if (!$editor || !$editor.length || !range || !window.getSelection) return;

    const editorEl = $editor[0];
    editorEl.focus();

    const sel = window.getSelection();
    if (!sel) return;

    sel.removeAllRanges();
    sel.addRange(range);
  }

  function normalizeParagraphLinkUrl(rawUrl) {
    const url = String(rawUrl || '').trim();
    if (!url) return '';

    if (/^(https?:|mailto:|tel:)/i.test(url)) {
      return url;
    }

    return 'https://' + url.replace(/^\/+/, '');
  }

  function insertParagraphFormat($btn) {
    const format = ($btn.data('format') || '').toString();
    const { $editor } = getParagraphEditorNodes($btn);
    if (!$editor.length) return;

    if (format === 'heading') {
      execParagraphCommand($editor, 'formatBlock', 'H3');
      return;
    }

    if (format === 'bold') {
      execParagraphCommand($editor, 'bold');
      return;
    }

    if (format === 'list') {
      execParagraphCommand($editor, 'insertUnorderedList');
      return;
    }

    if (format === 'link') {
      const range = getParagraphSelectionRange($editor);
      if (!range || range.collapsed) {
        window.alert('Select text in the paragraph first, then click Link.');
        return;
      }

      const entered = window.prompt('Enter link URL (https://, http://, mailto:, or tel:):', 'https://');
      if (entered === null) return;

      const href = normalizeParagraphLinkUrl(entered);
      if (!href) return;

      restoreParagraphSelectionRange($editor, range);
      execParagraphCommand($editor, 'createLink', href);
    }
  }

  // Prevent toolbar buttons from stealing textarea selection on mouse-down.
  $(document).on('mousedown', '.afb-para-insert', function (e) {
    e.preventDefault();
  });

  $(document).on('click', '.afb-para-insert', function (e) {
    e.preventDefault();
    insertParagraphFormat($(this));
  });

  $(document).on('input blur keyup paste', '.afb-paragraph-wysiwyg', function () {
    syncParagraphEditor($(this));
  });

  function placeNewBuilderItem($item, $insertBefore) {
    if (!$item || !$item.length) return $item;
    if ($insertBefore && $insertBefore.length) {
      $insertBefore.before($item);
      syncIconButtonLabels($item);
      syncCollapsibleSections($item);
      initRepeaterMapLists($item);
      refreshRepeaterMapItems($item);
      refreshRepeaterDragSources($item);
      return $item;
    }
    $selected.append($item);
    syncIconButtonLabels($item);
    syncCollapsibleSections($item);
    initRepeaterMapLists($item);
    refreshRepeaterMapItems($item);
    refreshRepeaterDragSources($item);
    return $item;
  }

  function addParagraphBlock($insertBefore) {
    const id = uid('para');
    const $item = $(`
      <li data-id="${id}" class="afb-builder-item afb-paragraph-block" data-kind="paragraph">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Paragraph Block</div>
            <div class="afb-item-subtitle">Rich helper text between inputs</div>
          </div>
          <span class="afb-chip afb-chip-paragraph">Paragraph</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div>
            <label class="block text-xs text-slate-600 mb-1">Internal title</label>
            <input type="text" name="custom_paragraph_titles[${id}]" class="afb-admin-input" placeholder="Paragraph title (not shown on frontend)">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Paragraph content</label>
            <div class="afb-paragraph-editor-wrap">
              <div class="flex items-center gap-2 mb-2">
                <button type="button" class="afb-admin-btn afb-para-insert" data-format="heading">Heading</button>
                <button type="button" class="afb-admin-btn afb-para-insert" data-format="bold">Bold</button>
                <button type="button" class="afb-admin-btn afb-para-insert" data-format="list">List</button>
                <button type="button" class="afb-admin-btn afb-para-insert" data-format="link">Link</button>
              </div>
              <div class="afb-admin-textarea afb-paragraph-wysiwyg" contenteditable="true" style="min-height: 150px; max-height: 280px; overflow: auto; white-space: pre-wrap;"></div>
              <textarea name="custom_paragraphs[${id}]" rows="6" class="afb-paragraph-editor" style="display:none;" aria-hidden="true"></textarea>
              <p class="text-xs text-slate-400 mt-2 mb-0">Basic formatting only. Use toolbar buttons to insert heading, bold text, and bullet lists.</p>
            </div>
          </div>
          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Each rule uses a source field and expected value(s).</p>
              <p class="text-xs text-slate-500 mt-1 mb-0">Tip: Hidden steps are skipped automatically in frontend navigation.</p>
            </div>
          </div>
        </div>
      </li>
    `);
    placeNewBuilderItem($item, $insertBefore);
    refreshDependencySources();
    return $item;
  }

  function addSectionBreakBlock($insertBefore) {
    const id = uid('section');
    const $item = $(`
      <li data-id="${id}" class="afb-builder-item" data-kind="section">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Section Break</div>
            <div class="afb-item-subtitle">Starts a new frontend step</div>
          </div>
          <span class="afb-chip afb-chip-section">Step</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-move-section-up">Up</button>
          <button type="button" class="afb-admin-btn afb-move-section-down">Down</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div>
            <label class="block text-xs text-slate-600 mb-1">Step title</label>
            <input type="text" name="custom_section_titles[${id}]" class="afb-admin-input" placeholder="Example: Service Details">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Step helper text</label>
            <textarea name="custom_sections[${id}]" rows="3" class="afb-admin-textarea" placeholder="Optional short instructions for this step"></textarea>
          </div>
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="submit_block_fields[]" value="${id}" />
              Block submit when visible
            </label>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Internal title</label>
            <input type="text" name="custom_paragraph_titles[${id}]" class="afb-admin-input" placeholder="Paragraph title (not shown on frontend)">
          </div>
          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Each rule uses a source field and expected value(s).</p>
            </div>
          </div>
        </div>
      </li>
    `);
    placeNewBuilderItem($item, $insertBefore);
    syncSectionMoveButtons();
    refreshDependencySources();
    return $item;
  }

  function addAgreementBlock($insertBefore) {
    const id = uid('agree');
    const $item = $(`
      <li data-id="${id}" class="afb-builder-item" data-kind="agreement">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Agreement Checkbox</div>
            <div class="afb-item-subtitle">Must be ticked before submit</div>
          </div>
          <span class="afb-chip afb-chip-paragraph">Agreement</span>
          <span class="afb-chip afb-agreement-required-chip" style="display:none;">Req</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="agreement_required_fields[]" value="${id}" checked />
              Required
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="submit_block_fields[]" value="${id}" />
              Block submit when visible
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="agreement_description_fields[]" value="${id}" />
              Append checked state to task description
            </label>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Agreement label</label>
            <input type="text" name="custom_agreement_labels[${id}]" class="afb-admin-input" placeholder="Example: I agree to the terms and privacy policy">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Helper text</label>
            <textarea name="custom_agreement_details[${id}]" rows="3" class="afb-admin-textarea" placeholder="Optional short explanation shown below the agreement label"></textarea>
          </div>
          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Each rule uses a source field and expected value(s).</p>
            </div>
          </div>
        </div>
      </li>
    `);
    placeNewBuilderItem($item, $insertBefore);
    syncAgreementRequiredBadge($item);
    refreshDependencySources();
    return $item;
  }

  function addUploadBlock($insertBefore) {
    const id = uid('upload');
    const $item = $(`
      <li data-id="${id}" class="afb-builder-item is-collapsed" data-kind="upload">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Upload Area</div>
            <div class="afb-item-subtitle">Files stay local to the request and are sent together</div>
          </div>
          <span class="afb-chip">Upload</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check afb-upload-required-toggle" />
              Required
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="submit_block_fields[]" value="${id}" />
              Block submit when visible
            </label>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Upload title</label>
            <input type="text" name="custom_upload_titles[${id}]" class="afb-admin-input" placeholder="Example: Supporting documents">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Helper text</label>
            <textarea name="custom_upload_details[${id}]" rows="3" class="afb-admin-textarea" placeholder="Explain what files to upload here"></textarea>
          </div>
          <div class="max-w-xs">
            <label class="block text-xs text-slate-600 mb-1">Minimum files required</label>
            <input type="number" min="0" step="1" name="custom_upload_min_files[${id}]" value="0" class="afb-admin-input" placeholder="0">
            <p class="text-xs text-slate-400 mt-1 mb-0">Tick Required for at least one file, or set a higher number for multi-file minimums.</p>
          </div>
          <div class="max-w-sm">
            <label class="block text-xs text-slate-600 mb-1">Accepted file types (optional)</label>
            <input type="text" name="custom_upload_accept_types[${id}]" class="afb-admin-input" placeholder="Examples: image/*, .pdf, .jpg">
            <p class="text-xs text-slate-400 mt-1 mb-0">Leave blank to allow any file type. Use commas to separate multiple file extensions or MIME types.</p>
          </div>
          <div class="max-w-sm">
            <label class="afb-flag-toggle mt-1">
              <input type="checkbox" class="afb-admin-check" name="custom_upload_compress_images[]" value="${id}" checked />
              Compress image files on upload
            </label>
            <p class="text-xs text-slate-400 mt-1 mb-0">Compression happens as users pick files, before task submission starts.</p>
          </div>
          <div class="max-w-xs">
            <label class="block text-xs text-slate-600 mb-1">Max file size per file (MB, optional)</label>
            <input type="number" min="0" step="0.1" name="custom_upload_max_file_mb[${id}]" class="afb-admin-input" placeholder="Example: 10">
            <p class="text-xs text-slate-400 mt-1 mb-0">Leave blank for no extra limit. Limit is checked after image compression.</p>
          </div>
          <div class="max-w-sm">
            <label class="block text-xs text-slate-600 mb-1">Image max width (px)</label>
            <div class="flex items-center gap-3">
              <input type="range" min="100" max="2400" step="50" name="custom_upload_max_width_px[${id}]" value="1200" class="afb-admin-range flex-1" oninput="this.nextElementSibling.textContent=this.value + 'px'" />
              <span class="text-xs text-slate-500 min-w-[56px] text-right">1200px</span>
            </div>
            <p class="text-xs text-slate-400 mt-1 mb-0">Large images are resized to this width before upload. 1200px is the default.</p>
          </div>
          <div class="max-w-sm">
            <label class="block text-xs text-slate-600 mb-1">Image compression quality (%)</label>
            <div class="flex items-center gap-3">
              <input type="range" min="1" max="100" step="1" name="custom_upload_compress_quality[${id}]" value="60" class="afb-admin-range flex-1" oninput="this.nextElementSibling.textContent=this.value + '%'" />
              <span class="text-xs text-slate-500 min-w-[44px] text-right">60%</span>
            </div>
            <p class="text-xs text-slate-400 mt-1 mb-0">Lower values reduce file size more aggressively. 60% is the default.</p>
          </div>
          <div class="max-w-sm">
            <label class="block text-xs text-slate-600 mb-1">Upload destination for this block</label>
            <select name="custom_upload_private_modes[${id}]" class="afb-admin-select">
              <option value="use_project_default" selected>Use project default</option>
              <option value="main_only">Main task only</option>
              <option value="copy_to_companion">Main + companion task</option>
              <option value="companion_only">Companion task only</option>
            </select>
            <p class="text-xs text-slate-400 mt-1 mb-0">Override where this upload block attaches files.</p>
          </div>
          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Each rule uses a source field and expected value(s).</p>
            </div>
          </div>
        </div>
      </li>
    `);
    placeNewBuilderItem($item, $insertBefore);
    syncUploadRequiredToggleFromMin($item);
    refreshDependencySources();
    return $item;
  }

  function addSingleInputBlock($insertBefore) {
    const id = uid('single');
    const syncOptions = buildSyncSelectOptionsHtml('');
    const $item = $(`
      <li data-id="${id}" class="afb-builder-item is-collapsed" data-kind="single_input">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Single Input Text</div>
            <div class="afb-item-subtitle">Single-line or textarea answer; optionally synced to Asana text field</div>
          </div>
          <span class="afb-chip">Text</span>
          <span class="afb-chip afb-single-half-chip" style="display:none;">1/2</span>
          <span class="afb-chip afb-single-ai-chip" style="display:none;">AI</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="required_fields[]" value="${id}" />
              Required
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="half_width_fields[]" value="${id}" />
              1/2 width
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="third_width_fields[]" value="${id}" />
              1/3 width
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="custom_single_input_ai_enabled[]" value="${id}" />
              Enable AI help
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="submit_block_fields[]" value="${id}" />
              Block submit when visible
            </label>
          </div>
          <p class="text-xs text-slate-500 mt-1 mb-0">Shows a "Rewrite with AI" helper button on this field in the frontend form.</p>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Field label</label>
            <input type="text" name="custom_single_input_labels[${id}]" class="afb-admin-input" placeholder="Example: Preferred call-back number">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Helper text</label>
            <textarea name="custom_single_input_descriptions[${id}]" rows="3" class="afb-admin-textarea" placeholder="Optional short context shown below the label"></textarea>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Placeholder</label>
            <input type="text" name="custom_single_input_placeholders[${id}]" class="afb-admin-input" placeholder="Example: Enter a short answer">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Input type</label>
            <select name="custom_single_input_modes[${id}]" class="afb-admin-select afb-single-input-mode">
              <option value="text" selected>Single-line input</option>
              <option value="textarea">Textarea (multi-line)</option>
            </select>
          </div>
          <div class="afb-single-max-words-controls" style="display:none;">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check afb-single-max-words-toggle" name="custom_single_input_max_words_enabled[]" value="${id}" />
              Enable max word limit
            </label>
          </div>
          <div class="afb-single-max-words-wrap max-w-xs" style="display:none;">
            <label class="block text-xs text-slate-600 mb-1">Max words</label>
            <input type="number" min="1" step="1" name="custom_single_input_max_words[${id}]" class="afb-admin-input afb-single-max-words-input" placeholder="Example: 120">
            <p class="text-xs text-slate-400 mt-1 mb-0">Only applies when input type is textarea. Users will see live over/under word feedback.</p>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Sync to Asana text field (optional)</label>
            <select name="custom_single_input_sync_fields[${id}]" class="afb-admin-select">
              ${syncOptions}
            </select>
          </div>
          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Each rule uses a source field and expected value(s).</p>
              <p class="text-xs text-slate-500 mt-1 mb-0">When synced, the submitted value writes to the selected Asana text field only. Without sync, it is appended to the task description.</p>
            </div>
          </div>
        </div>
      </li>
    `);
    placeNewBuilderItem($item, $insertBefore);
    syncSingleInputHalfBadge($item);
    syncSingleInputAiBadge($item);
    syncSingleInputWordLimitControls($item);
    refreshDependencySources();
    return $item;
  }

  function addDropdownBlock($insertBefore) {
    const id = uid('dropdown');
    const syncOptions = buildSyncSelectOptionsHtml('');
    const $item = $(`
      <li data-id="${id}" class="afb-builder-item is-collapsed" data-kind="dropdown">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Dropdown</div>
            <div class="afb-item-subtitle">One selection from one-per-line options; optional text-field sync</div>
          </div>
          <span class="afb-chip">Dropdown</span>
          <span class="afb-chip afb-dropdown-half-chip" style="display:none;">1/2</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="required_fields[]" value="${id}" />
              Required
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="half_width_fields[]" value="${id}" />
              1/2 width
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="third_width_fields[]" value="${id}" />
              1/3 width
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="submit_block_fields[]" value="${id}" />
              Block submit when visible
            </label>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Field label</label>
            <input type="text" name="custom_dropdown_labels[${id}]" class="afb-admin-input" placeholder="Example: Preferred contact method">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Helper text</label>
            <textarea name="custom_dropdown_descriptions[${id}]" rows="3" class="afb-admin-textarea" placeholder="Optional short context shown below the label"></textarea>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Placeholder</label>
            <input type="text" name="custom_dropdown_placeholders[${id}]" class="afb-admin-input" placeholder="Example: Select an option">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Selection type</label>
            <select name="custom_dropdown_selection_modes[${id}]" class="afb-admin-select">
              <option value="single" selected>Single select (dropdown)</option>
              <option value="multi">Multi select (checkboxes)</option>
            </select>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Options (one per line)</label>
            <textarea name="custom_dropdown_options[${id}]" rows="6" class="afb-admin-textarea" placeholder="Option A&#10;Option B&#10;Option C"></textarea>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Sync to Asana text field (optional)</label>
            <select name="custom_dropdown_sync_fields[${id}]" class="afb-admin-select">
              ${syncOptions}
            </select>
          </div>
          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Each rule uses a source field and expected value(s).</p>
              <p class="text-xs text-slate-500 mt-1 mb-0">When synced, the selected value(s) write to the selected Asana text field only. Without sync, selection(s) are appended to the task description.</p>
            </div>
          </div>
        </div>
      </li>
    `);
    placeNewBuilderItem($item, $insertBefore);
    syncDropdownHalfBadge($item);
    refreshDependencySources();
    return $item;
  }

  function addCalendarBlock($insertBefore) {
    const id = uid('calendar');
    const syncOptions = getTextSyncFieldOptions().map(function (opt) {
      const value = String((opt && opt.gid) || '').trim();
      if (!value) return '';
      const label = $('<div></div>').text((opt.label || value) + ' (' + value + ')').html();
      return '<option value="' + value + '">' + label + '</option>';
    }).join('');
    const $item = $(`
      <li data-id="${id}" class="afb-builder-item is-collapsed" data-kind="calendar">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Calendar Picker</div>
            <div class="afb-item-subtitle">Date picker with optional eAlert Wednesdays preset</div>
          </div>
          <span class="afb-chip">Calendar</span>
          <span class="afb-chip afb-calendar-half-chip" style="display:none;">1/2</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="required_fields[]" value="${id}" />
              Required
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="half_width_fields[]" value="${id}" />
              1/2 width
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="third_width_fields[]" value="${id}" />
              1/3 width
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="submit_block_fields[]" value="${id}" />
              Block submit when visible
            </label>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Field label</label>
            <input type="text" name="custom_calendar_labels[${id}]" class="afb-admin-input" placeholder="Example: Requested publish date">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Helper text</label>
            <textarea name="custom_calendar_descriptions[${id}]" rows="3" class="afb-admin-textarea" placeholder="Optional short context shown below the label"></textarea>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Placeholder</label>
            <input type="text" name="custom_calendar_placeholders[${id}]" class="afb-admin-input" placeholder="Example: Select a date">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Date source</label>
            <select name="custom_calendar_preset_modes[${id}]" class="afb-admin-select">
              <option value="none" selected>Manual date picker</option>
              <option value="ealert_next_4_wednesdays">Next 4 Wednesdays (eAlert)</option>
            </select>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Sync destination</label>
            <select name="custom_calendar_sync_modes[${id}]" class="afb-admin-select afb-calendar-sync-mode">
              <option value="none" selected>Do not sync (append to task description)</option>
              <option value="text">Asana text field</option>
              <option value="due_date">Task due date</option>
            </select>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Sync to Asana text field (when destination is text)</label>
            <select name="custom_calendar_sync_fields[${id}]" class="afb-admin-select">
              <option value="">Select text field</option>
              ${syncOptions}
            </select>
          </div>
          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Each rule uses a source field and expected value(s).</p>
              <p class="text-xs text-slate-500 mt-1 mb-0">When using the eAlert preset, dates come from triage Wednesday logic if available.</p>
            </div>
          </div>
        </div>
      </li>
    `);
    placeNewBuilderItem($item, $insertBefore);
    syncCalendarHalfBadge($item);
    refreshDependencySources();
    return $item;
  }

  function addRepeaterBlock($insertBefore) {
    const id = uid('repeater');
    const $item = $(`
      <li data-id="${id}" class="afb-builder-item is-collapsed" data-kind="repeater">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Repeater List</div>
            <div class="afb-item-subtitle">One row per entry; each row creates a subtask</div>
          </div>
          <span class="afb-chip">Repeater</span>
          <span class="afb-chip afb-repeater-half-chip" style="display:none;">1/2</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="required_fields[]" value="${id}" />
              Required
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="half_width_fields[]" value="${id}" />
              1/2 width
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="third_width_fields[]" value="${id}" />
              1/3 width
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="submit_block_fields[]" value="${id}" />
              Block submit when visible
            </label>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Field label</label>
            <input type="text" name="custom_repeater_labels[${id}]" class="afb-admin-input" placeholder="Example: People on this shoot">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Helper text</label>
            <textarea name="custom_repeater_descriptions[${id}]" rows="3" class="afb-admin-textarea" placeholder="Optional short context shown below the label"></textarea>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Save repeater rows as</label>
            <select name="custom_repeater_save_mode[${id}]" class="afb-admin-select afb-repeater-save-mode">
              <option value="subtasks" selected>Asana subtasks (one per row)</option>
              <option value="description">Task description content</option>
            </select>
            <p class="text-xs text-slate-500 mt-1 mb-0 afb-repeater-mode-note">Rows create child subtasks under the main task.</p>
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Columns (one per line)</label>
            <textarea name="custom_repeater_columns[${id}]" rows="7" class="afb-admin-textarea" placeholder="Name&#10;Surname&#10;Email&#10;Phone&#10;Agreement">Name\nSurname\nEmail\nPhone\nAgreement</textarea>
          </div>
          <div class="afb-repeater-map-wrap" data-repeater-id="${id}" data-repeater-save-mode="subtasks">
            <label class="block text-xs text-slate-600 mb-1">Asana field mapping (subtask mode)</label>
            <p class="text-xs text-slate-500 mt-0 mb-2 afb-repeater-map-hint">Drag Asana fields from the left into this list. The order here controls each repeater row layout and the custom fields written on subtasks.</p>
            <ul class="afb-repeater-map-list space-y-2" data-repeater-id="${id}"></ul>
            <p class="text-xs text-slate-400 mt-2 mb-0 afb-repeater-map-empty">No mapped Asana fields yet. Drag a field from the left list into this area.</p>
            <p class="text-xs text-slate-500 mt-2 mb-0">When mapped fields are present, the repeater uses them instead of the plain column list.</p>
          </div>
          <div class="grid gap-3 md:grid-cols-2">
            <div>
              <label class="block text-xs text-slate-600 mb-1">Minimum rows</label>
              <input type="number" min="0" max="250" step="1" name="custom_repeater_min_rows[${id}]" class="afb-admin-input afb-repeater-min-rows" value="0" placeholder="0">
            </div>
            <div>
              <label class="block text-xs text-slate-600 mb-1">Maximum rows</label>
              <input type="number" min="0" max="250" step="1" name="custom_repeater_max_rows[${id}]" class="afb-admin-input afb-repeater-max-rows" value="0" placeholder="0 (unlimited)">
            </div>
          </div>
          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Each rule uses a source field and expected value(s).</p>
              <p class="text-xs text-slate-500 mt-1 mb-0 afb-repeater-output-hint">Each repeater row becomes one Asana subtask under the main task when this block is visible and has content.</p>
            </div>
          </div>
        </div>
      </li>
    `);

    placeNewBuilderItem($item, $insertBefore);
    syncRepeaterHalfBadge($item);
    syncRepeaterSaveModeSummary($item);
    refreshDependencySources();
    return $item;
  }

  function addTaskNameBlock($insertBefore) {
    const id = uid('task_name');
    const $item = $(`
      <li data-id="${id}" class="afb-builder-item is-collapsed" data-kind="task_name">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Task Name Builder</div>
            <div class="afb-item-subtitle">Build task titles from submitted form values</div>
          </div>
          <span class="afb-chip">Task Name</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div>
            <label class="block text-xs text-slate-600 mb-1">Builder label</label>
            <input type="text" name="custom_task_name_labels[${id}]" class="afb-admin-input" placeholder="Example: Campaign task title">
          </div>
          <div>
            <label class="block text-xs text-slate-600 mb-1">Template</label>
            <textarea name="custom_task_name_templates[${id}]" rows="3" class="afb-admin-textarea afb-task-name-template" placeholder="Example: {{submitter_name}} {{field:calendar_12345678}}"></textarea>
            <p class="text-xs text-slate-500 mt-1 mb-0">If the template resolves to a blank value, the job number remains the task title.</p>
          </div>
          <div class="grid gap-3 md:grid-cols-[minmax(0,1fr)_auto] items-end">
            <div>
              <label class="block text-xs text-slate-600 mb-1">Insert variable</label>
              <select class="afb-admin-select afb-task-name-variable-select">
                <option value="">Choose variable</option>
              </select>
            </div>
            <div>
              <button type="button" class="afb-admin-btn afb-task-name-insert-variable">Insert</button>
            </div>
          </div>
          <p class="text-xs text-slate-400 mt-2 mb-0">Built-ins: {{job_number}}, {{submitter_name}}, {{submitter_email}}, {{due_date}}. Field tokens use {{field:...}}.</p>
          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Each rule uses a source field and expected value(s).</p>
            </div>
          </div>
        </div>
      </li>
    `);

    placeNewBuilderItem($item, $insertBefore);
    refreshDependencySources();
    return $item;
  }

  function addDueDateBlock($insertBefore) {
    const id = uid('due_date');
    const dueBaseOptions = getDueDateBaseFieldOptions().map(function (opt) {
      const value = String((opt && opt.gid) || '').trim();
      if (!value) return '';
      const label = $('<div></div>').text((opt.label || value)).html();
      return '<option value="' + value + '">' + label + '</option>';
    }).join('');

    const $item = $(`
      <li data-id="${id}" class="afb-builder-item is-collapsed" data-kind="due_date_tool">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Due Date Tool</div>
            <div class="afb-item-subtitle">Rule-driven due date formulas (business days by default)</div>
          </div>
          <span class="afb-chip">Due Date</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="custom_due_date_override_manual[]" value="${id}" />
              Override manual due date input
            </label>
          </div>

          <div class="afb-collapsible-section afb-due-date-default-section is-expanded">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <span>Default Formula</span>
              </div>
              <div class="afb-cond-header-controls">
                <button type="button" class="afb-admin-btn afb-section-toggle" aria-expanded="true">Hide</button>
              </div>
            </div>
            <div class="afb-collapsible-body">
              <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
                <div>
                  <label class="block text-xs text-slate-600 mb-1">Base date</label>
                  <select name="custom_due_date_default_base[${id}]" class="afb-admin-select afb-due-default-base">
                    <option value="today" selected>Today</option>
                    <option value="field">Date field from form</option>
                  </select>
                </div>
                <div class="afb-due-default-base-field-wrap" style="display:none;">
                  <label class="block text-xs text-slate-600 mb-1">Base date field</label>
                  <select name="custom_due_date_default_base_field[${id}]" class="afb-admin-select afb-due-default-base-field">
                    <option value="">Select date field</option>
                    ${dueBaseOptions}
                  </select>
                </div>
                <div>
                  <label class="block text-xs text-slate-600 mb-1">Offset days</label>
                  <input type="number" step="1" name="custom_due_date_default_offset[${id}]" value="0" class="afb-admin-input" placeholder="Example: 7 or -3">
                </div>
                <div>
                  <label class="block text-xs text-slate-600 mb-1">Offset mode</label>
                  <select name="custom_due_date_default_mode[${id}]" class="afb-admin-select">
                    <option value="business" selected>Business days (default)</option>
                    <option value="calendar">Calendar days</option>
                  </select>
                </div>
                <div>
                  <label class="afb-flag-toggle">
                    <input type="checkbox" class="afb-admin-check" name="custom_due_date_default_allow_weekends[]" value="${id}" />
                    Allow weekends in result
                  </label>
                </div>
              </div>
            </div>
          </div>

          <div class="afb-collapsible-section afb-due-date-rules-section is-expanded">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <span>Rules</span>
              </div>
              <div class="afb-cond-header-controls">
                <button type="button" class="afb-admin-btn afb-section-toggle" aria-expanded="true">Hide</button>
                <button type="button" class="afb-admin-btn afb-due-rule-add">Add rule</button>
              </div>
            </div>
            <div class="afb-collapsible-body">
              <div class="afb-due-date-rules space-y-2"></div>
              <input type="hidden" name="custom_due_date_rules[${id}]" class="afb-due-date-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Rules are evaluated top-to-bottom. First match wins.</p>
              <p class="text-xs text-slate-500 mt-1 mb-0">Each rule can use today or a selected date field as a base, then apply + or - day offsets.</p>
            </div>
          </div>
        </div>
      </li>
    `);

    placeNewBuilderItem($item, $insertBefore);
    refreshDependencySources();
    return $item;
  }

  function addAssigneeBlock($insertBefore) {
    const id = uid('assignee');
    const defaultOptions = buildAssigneeSelectOptionsHtml('', true, 'Leave unassigned by default');
    const fallbackOptions = buildAssigneeSelectOptionsHtml('', false, '');

    const $item = $(`
      <li data-id="${id}" class="afb-builder-item is-collapsed" data-kind="assignee_tool">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Assignee Tool</div>
            <div class="afb-item-subtitle">Rule-driven assignment with OOO-aware fallback chain</div>
          </div>
          <span class="afb-chip">Assignee</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div>
            <label class="block text-xs text-slate-600 mb-1">Builder label</label>
            <input type="text" name="custom_assignee_labels[${id}]" class="afb-admin-input" placeholder="Example: Campaign assignee">
          </div>
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="custom_assignee_check_ooo[]" value="${id}" checked />
              Check out-of-office status (today)
            </label>
          </div>

          <div class="grid gap-3 md:grid-cols-2">
            <div>
              <label class="block text-xs text-slate-600 mb-1">Default assignee</label>
              <select name="custom_assignee_default[${id}]" class="afb-admin-select afb-assignee-default">
                ${defaultOptions}
              </select>
            </div>
            <div>
              <label class="block text-xs text-slate-600 mb-1">Default fallback assignees</label>
              <select name="custom_assignee_default_fallbacks[${id}][]" class="afb-admin-select afb-assignee-default-fallbacks" multiple size="6">
                ${fallbackOptions}
              </select>
              <p class="text-xs text-slate-400 mt-1 mb-0">Hold Ctrl / Cmd to choose multiple fallback assignees.</p>
            </div>
          </div>

          <div class="afb-collapsible-section afb-assignee-rules-section is-expanded">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <span>Rules</span>
              </div>
              <div class="afb-cond-header-controls">
                <button type="button" class="afb-admin-btn afb-section-toggle" aria-expanded="true">Hide</button>
                <button type="button" class="afb-admin-btn afb-assignee-rule-add">Add rule</button>
              </div>
            </div>
            <div class="afb-collapsible-body">
              <div class="afb-assignee-rules space-y-2"></div>
              <input type="hidden" name="custom_assignee_rules[${id}]" class="afb-assignee-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Rules are evaluated top-to-bottom. First match wins.</p>
              <p class="text-xs text-slate-500 mt-1 mb-0">If all candidates are out-of-office, the task is left unassigned. If OOO lookup is unavailable, assignment continues with the normal candidate order.</p>
            </div>
          </div>
        </div>
      </li>
    `);

    placeNewBuilderItem($item, $insertBefore);
    refreshDependencySources();
    return $item;
  }

  function addRecipientBlock($insertBefore) {
    const id = uid('recipient');
    const sourceOptions = buildRecipientSourceOptionsHtml([]);

    const $item = $(`
      <li data-id="${id}" class="afb-builder-item is-collapsed" data-kind="recipient_tool">
        <input type="hidden" name="field_order[]" value="${id}" />
        <div class="afb-builder-item-header">
          <span class="afb-handle" aria-hidden="true">::</span>
          <div class="min-w-0 flex-1">
            <div class="afb-item-title">Recipients Tool</div>
            <div class="afb-item-subtitle">Send submission copy to emails from selected fields or manual addresses</div>
          </div>
          <span class="afb-chip">Recipients</span>
          <button type="button" class="afb-admin-btn afb-collapse-toggle">Collapse</button>
          <button type="button" class="afb-admin-btn afb-duplicate-item">Duplicate</button>
          <button type="button" class="afb-admin-btn remove-builder-item">Remove</button>
        </div>
        <div class="afb-builder-item-body">
          <div>
            <label class="block text-xs text-slate-600 mb-1">Builder label</label>
            <input type="text" name="custom_recipient_labels[${id}]" class="afb-admin-input" placeholder="Example: Internal recipients">
          </div>
          <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="custom_recipient_enabled[]" value="${id}" checked />
              Enabled
            </label>
            <label class="afb-flag-toggle">
              <input type="checkbox" class="afb-admin-check" name="custom_recipient_include_submitter[]" value="${id}" />
              Include submitter email
            </label>
          </div>

          <div>
            <label class="block text-xs text-slate-600 mb-1">Recipient source fields</label>
            <select name="custom_recipient_source_fields[${id}][]" class="afb-admin-select afb-recipient-source-fields" multiple size="7">
              ${sourceOptions}
            </select>
            <p class="text-xs text-slate-400 mt-1 mb-0">Fields can contain one email or multiple emails separated by commas.</p>
          </div>

          <div>
            <label class="block text-xs text-slate-600 mb-1">Manual recipient emails (optional)</label>
            <textarea name="custom_recipient_manual_emails[${id}]" rows="3" class="afb-admin-textarea" placeholder="name@example.com&#10;second@example.com"></textarea>
            <p class="text-xs text-slate-400 mt-1 mb-0">Add fixed addresses separated by commas or new lines. These are combined with source-field emails.</p>
          </div>

          <div class="max-w-sm">
            <label class="block text-xs text-slate-600 mb-1">Delivery mode</label>
            <select name="custom_recipient_mode[${id}]" class="afb-admin-select">
              <option value="separate" selected>Send separate email to each recipient</option>
              <option value="cc">Send one email with recipients in CC</option>
            </select>
          </div>

          <div>
            <label class="block text-xs text-slate-600 mb-1">Subject template (optional)</label>
            <input type="text" name="custom_recipient_subject_templates[${id}]" class="afb-admin-input" placeholder="Defaults to Submission Copy email subject">
          </div>

          <div class="grid gap-3 md:grid-cols-[minmax(0,1fr)_auto_auto] items-end">
            <div>
              <label class="block text-xs text-slate-600 mb-1">Insert variable</label>
              <select class="afb-admin-select afb-recipient-variable-select">
                <option value="">Choose variable</option>
              </select>
            </div>
            <div>
              <button type="button" class="afb-admin-btn afb-recipient-insert-variable" data-target-template="subject">Insert in Subject</button>
            </div>
            <div>
              <button type="button" class="afb-admin-btn afb-recipient-insert-variable" data-target-template="body">Insert in Body</button>
            </div>
          </div>

          <div>
            <label class="block text-xs text-slate-600 mb-1">Body template (optional)</label>
            <textarea name="custom_recipient_body_templates[${id}]" rows="5" class="afb-admin-textarea" placeholder="Defaults to Submission Copy email body"></textarea>
            <p class="text-xs text-slate-500 mt-1 mb-0">Tokens: {{task_name}}, {{request_number}}, {{comms_link}}, {{fields_html}}, {{job_number}}, {{submitter_name}}, {{submitter_email}}, {{due_date}}, {{field:...}}</p>
          </div>

          <div class="afb-cond-editor afb-cond-section" data-target="${id}">
            <div class="afb-cond-header">
              <div class="afb-cond-header-title">
                <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
                <span>Conditions</span>
              </div>
              <div class="afb-cond-header-controls">
                <select name="condition_logic[${id}]" class="afb-cond-logic afb-cond-logic-select">
                  <option value="all" selected>Match all</option>
                  <option value="any">Match any</option>
                </select>
                <button type="button" class="afb-cond-add-btn afb-cond-add">Add rule</button>
              </div>
            </div>
            <div class="afb-cond-body">
              <div class="afb-cond-rules space-y-2"></div>
              <input type="hidden" name="condition_rules[${id}]" class="afb-cond-rules-input" value='[]'>
              <p class="text-xs text-slate-400 mt-3 mb-0">Only send this recipient email when the condition rules match.</p>
            </div>
          </div>
        </div>
      </li>
    `);

    placeNewBuilderItem($item, $insertBefore);
    refreshDependencySources();
    return $item;
  }

  $('#add-paragraph-button').on('click', addParagraphBlock);
  $('#add-section-break-button').on('click', addSectionBreakBlock);

  $(document).on('click', '.afb-add-block', function (e) {
    e.preventDefault();
    const type = ($(this).data('block-type') || '').toString();
    insertBlockByType(type, null);
  });

  // Optional enhancement: drag static "Add block" tools into selected flow.
  if ($.fn.draggable) {
    $('.afb-add-block').draggable({
      helper: 'clone',
      connectToSortable: '#field-sortable',
      appendTo: 'body',
      revert: 'invalid',
      revertDuration: 120,
      distance: 8,
      zIndex: 10000,
      start: function (_e, ui) {
        const w = $(this).outerWidth();
        ui.helper.css({ width: w });
      }
    });
  }

  // Initial partition based on used toggle.
  $selected.find('li[data-kind="field"]').each(function () {
    const $item = $(this);
    const isUsed = $item.find('.afb-used-toggle').first().is(':checked');
    setFieldSelected($item, isUsed);
    if (!isUsed) {
      $item.appendTo($available);
    } else {
      // Items start collapsed (PHP renders is-collapsed); sync button text.
      $item.addClass('is-collapsed');
      syncCollapseToggleButton($item);
    }
    syncFlags($item);
  });

  $selected.children('li').each(function () {
    const $item = $(this);
    enforceExclusiveWidthSelections($item, '');
    syncFlags($item);
  });

  $selected.find('li[data-kind="single_input"]').each(function () {
    syncSingleInputHalfBadge($(this));
    syncSingleInputAiBadge($(this));
    syncSingleInputWordLimitControls($(this));
  });

  $selected.find('li[data-kind="dropdown"]').each(function () {
    syncDropdownHalfBadge($(this));
  });

  $selected.find('li[data-kind="calendar"]').each(function () {
    syncCalendarHalfBadge($(this));
  });

  $selected.find('li[data-kind="repeater"]').each(function () {
    syncRepeaterHalfBadge($(this));
    syncRepeaterSaveModeSummary($(this));
  });

  initRepeaterMapLists($(document));
  refreshRepeaterMapItems($(document));
  refreshRepeaterDragSources($(document));

  $selected.find('li[data-kind="agreement"]').each(function () {
    syncAgreementRequiredBadge($(this));
  });

  $selected.find('li[data-kind="upload"]').each(function () {
    syncUploadRequiredToggleFromMin($(this));
  });

  syncCollapsibleSections($(document));

  syncIconButtonLabels($(document));

  runSyncFieldDuplicateCheck(false);

  syncSectionMoveButtons();
  syncCollapsedSectionVisibility();

  $(document).on('click', '.afb-section-toggle', function (e) {
    e.preventDefault();

    const $section = $(this).closest('.afb-collapsible-section').first();
    if (!$section.length) return;

    const isExpanded = $section.hasClass('is-expanded') && !$section.hasClass('is-collapsed');
    $section.toggleClass('is-expanded', !isExpanded);
    $section.toggleClass('is-collapsed', isExpanded);
    syncCollapsibleSection($section);
  });

  $(document).on('click', '.afb-cond-add', function (e) {
    e.preventDefault();
    const $editor = $(this).closest('.afb-cond-editor');
    if (!$editor.length) return;

    const selectedFields = getSelectedSourceFields();
    const $rulesWrap = $editor.find('.afb-cond-rules').first();
    if (!$rulesWrap.length) return;

    $rulesWrap.append(buildConditionRuleRow($editor, { depends_on: '', values: [] }, selectedFields));
    syncConditionEditor($editor);
  });

  $(document).on('click', '.afb-select-all-options', function () {
    $(this).closest('.afb-visible-options-wrap').find('.afb-visible-options-select').find('option').prop('selected', true);
  });

  $(document).on('click', '.afb-clear-all-options', function () {
    $(this).closest('.afb-visible-options-wrap').find('.afb-visible-options-select').find('option').prop('selected', false);
  });

  $(document).on('click', '.afb-cond-remove', function (e) {
    e.preventDefault();
    const $row = $(this).closest('.afb-cond-rule');
    const $editor = $(this).closest('.afb-cond-editor');
    if (!$editor.length || !$row.length) return;

    $row.remove();

    const $rulesWrap = $editor.find('.afb-cond-rules').first();
    if ($rulesWrap.length && !$rulesWrap.find('.afb-cond-rule').length) {
      const selectedFields = getSelectedSourceFields();
      $rulesWrap.append(buildConditionRuleRow($editor, { depends_on: '', values: [] }, selectedFields));
    }

    syncConditionEditor($editor);
  });

  $(document).on('change', '.afb-cond-editor .depends-on-selector', function () {
    const $source = $(this);
    const $editor = $source.closest('.afb-cond-editor');
    if (!$editor.length) return;

    const $values = $source.closest('.afb-cond-rule').find('.expected-value-dropdown').first();
    const currentVals = Array.isArray($values.val())
      ? $values.val().map(function (v) { return String(v); })
      : [];

    hydrateExpectedValues($source, currentVals);
    syncConditionEditor($editor);
  });

  $(document).on('change', '.afb-cond-editor .expected-value-dropdown, .afb-cond-editor .afb-cond-logic', function () {
    const $editor = $(this).closest('.afb-cond-editor');
    if (!$editor.length) return;
    syncConditionEditor($editor);
  });

  $(document).on('click', '.afb-due-rule-add', function (e) {
    e.preventDefault();
    const $item = $(this).closest('li[data-kind="due_date_tool"]');
    if (!$item.length) return;

    const $rulesWrap = $item.find('.afb-due-date-rules').first();
    if (!$rulesWrap.length) return;

    $rulesWrap.append(buildDueDateRuleRow($item, {
      depends_on: '',
      values: [],
      base: 'today',
      base_field: '',
      offset: 0,
      mode: 'business',
      allow_weekends: false,
    }));

    syncDueDateRuleEditor($item);
  });

  $(document).on('click', '.afb-due-rule-remove', function (e) {
    e.preventDefault();
    const $row = $(this).closest('.afb-due-rule');
    const $item = $(this).closest('li[data-kind="due_date_tool"]');
    if (!$row.length || !$item.length) return;

    $row.remove();

    const $rulesWrap = $item.find('.afb-due-date-rules').first();
    if ($rulesWrap.length && !$rulesWrap.find('.afb-due-rule').length) {
      $rulesWrap.append(buildDueDateRuleRow($item, {
        depends_on: '',
        values: [],
        base: 'today',
        base_field: '',
        offset: 0,
        mode: 'business',
        allow_weekends: false,
      }));
    }

    syncDueDateRuleEditor($item);
  });

  $(document).on('change', '.afb-due-rule-depends-on', function () {
    const $row = $(this).closest('.afb-due-rule');
    const $item = $(this).closest('li[data-kind="due_date_tool"]');
    if (!$row.length || !$item.length) return;

    const $values = $row.find('.afb-due-rule-values').first();
    const currentVals = Array.isArray($values.val())
      ? $values.val().map(function (v) { return String(v); })
      : [];

    hydrateDueRuleSourceValues($row, currentVals);
    syncDueDateRuleEditor($item);
  });

  $(document).on('change', '.afb-due-rule-base', function () {
    const $row = $(this).closest('.afb-due-rule');
    const $item = $(this).closest('li[data-kind="due_date_tool"]');
    if (!$row.length || !$item.length) return;

    syncDueRuleBaseFieldVisibility($row);
    syncDueDateRuleEditor($item);
  });

  $(document).on('change input', '.afb-due-rule-values, .afb-due-rule-base-field, .afb-due-rule-offset, .afb-due-rule-mode, .afb-due-rule-allow-weekends', function () {
    const $item = $(this).closest('li[data-kind="due_date_tool"]');
    if (!$item.length) return;
    syncDueDateRuleEditor($item);
  });

  $(document).on('change', '.afb-due-default-base', function () {
    const $item = $(this).closest('li[data-kind="due_date_tool"]');
    if (!$item.length) return;
    syncDueDateDefaultBaseVisibility($item);
  });

  $(document).on('change', 'select[name^="custom_assignee_default["]', function () {
    const $item = $(this).closest('li[data-kind="assignee_tool"]');
    if (!$item.length) return;
    refreshAssigneeSelectors();
  });

  $(document).on('change', 'select[name^="custom_assignee_default_fallbacks["]', function () {
    const $item = $(this).closest('li[data-kind="assignee_tool"]');
    if (!$item.length) return;
    syncAssigneeDefaultFallbackSelection($item);
  });

  $(document).on('click', '.afb-assignee-rule-add', function (e) {
    e.preventDefault();
    const $item = $(this).closest('li[data-kind="assignee_tool"]');
    if (!$item.length) return;

    const $rulesWrap = $item.find('.afb-assignee-rules').first();
    if (!$rulesWrap.length) return;

    $rulesWrap.append(buildAssigneeRuleRow($item, {
      depends_on: '',
      values: [],
      assignee: '',
      fallback_assignees: [],
    }));

    syncAssigneeRuleEditor($item);
  });

  $(document).on('click', '.afb-assignee-rule-remove', function (e) {
    e.preventDefault();
    const $row = $(this).closest('.afb-assignee-rule');
    const $item = $(this).closest('li[data-kind="assignee_tool"]');
    if (!$row.length || !$item.length) return;

    $row.remove();

    const $rulesWrap = $item.find('.afb-assignee-rules').first();
    if ($rulesWrap.length && !$rulesWrap.find('.afb-assignee-rule').length) {
      $rulesWrap.append(buildAssigneeRuleRow($item, {
        depends_on: '',
        values: [],
        assignee: '',
        fallback_assignees: [],
      }));
    }

    syncAssigneeRuleEditor($item);
  });

  $(document).on('change', '.afb-assignee-rule-depends-on', function () {
    const $row = $(this).closest('.afb-assignee-rule');
    const $item = $(this).closest('li[data-kind="assignee_tool"]');
    if (!$row.length || !$item.length) return;

    const currentVals = Array.isArray($row.find('.afb-assignee-rule-values').first().val())
      ? $row.find('.afb-assignee-rule-values').first().val().map(function (v) { return String(v); })
      : [];
    hydrateAssigneeRuleSourceValues($row, currentVals);
    syncAssigneeRuleEditor($item);
  });

  $(document).on('change', '.afb-assignee-rule-assignee', function () {
    const $row = $(this).closest('.afb-assignee-rule');
    const $item = $(this).closest('li[data-kind="assignee_tool"]');
    if (!$row.length || !$item.length) return;

    const assignee = normalizeAssigneeGid($row.find('.afb-assignee-rule-assignee').first().val() || '');
    const fallbacks = Array.isArray($row.find('.afb-assignee-rule-fallbacks').first().val())
      ? $row.find('.afb-assignee-rule-fallbacks').first().val().map(normalizeAssigneeGid).filter(Boolean)
      : [];

    hydrateAssigneeRuleMemberSelectors($row, assignee, fallbacks);
    syncAssigneeRuleEditor($item);
  });

  $(document).on('change input', '.afb-assignee-rule-values, .afb-assignee-rule-fallbacks', function () {
    const $row = $(this).closest('.afb-assignee-rule');
    const $item = $(this).closest('li[data-kind="assignee_tool"]');
    if (!$row.length || !$item.length) return;

    const assignee = normalizeAssigneeGid($row.find('.afb-assignee-rule-assignee').first().val() || '');
    const fallbacks = Array.isArray($row.find('.afb-assignee-rule-fallbacks').first().val())
      ? $row.find('.afb-assignee-rule-fallbacks').first().val().map(normalizeAssigneeGid).filter(Boolean)
      : [];
    hydrateAssigneeRuleMemberSelectors($row, assignee, fallbacks);
    syncAssigneeRuleEditor($item);
  });

  $(document).on('change', '.afb-option-filter-source', function () {
    const $section = $(this).closest('.afb-optmap-section');
    rebuildOptionFilterRows($section);
    syncFlags($(this).closest('.afb-builder-item'));
  });

  $(document).on('change', '.afb-option-filter-allowed', function () {
    const $section = $(this).closest('.afb-optmap-section');
    syncOptionFilterMap($section);
    syncFlags($(this).closest('.afb-builder-item'));
  });

  $('.afb-optmap-section').each(function () {
    rebuildOptionFilterRows($(this));
  });

  $('.afb-paragraph-editor-wrap').each(function () {
    syncParagraphEditor($(this));
  });

  refreshDependencySources();

  const $builderForm = $('form').has('input[name="config_project_gid"]').first();
  if ($builderForm.length) {
    const defaultSaveLabels = new Map();
    const $privateProjectSelect = $builderForm.find('select[name="private_data_project_gid"]').first();
    let lastPrivateProjectValue = $privateProjectSelect.length
      ? String($privateProjectSelect.val() || '').trim()
      : '';

    function notice(type, message) {
      const cls = type === 'error'
        ? 'notice notice-error is-dismissible'
        : 'notice notice-success is-dismissible';

      $('#afb-field-flow-ajax-notice').remove();
      const $n = $(`<div id="afb-field-flow-ajax-notice" class="${cls}"><p></p></div>`);
      $n.find('p').text(message || 'Saved.');
      $('.wrap').first().prepend($n);
    }

    function setSavingState(isSaving) {
      const $buttons = $builderForm.find('button[type="submit"]');
      $buttons.each(function () {
        const $btn = $(this);
        if (!defaultSaveLabels.has(this)) defaultSaveLabels.set(this, $btn.text());
        $btn.prop('disabled', !!isSaving);
        $btn.text(isSaving ? 'Saving...' : defaultSaveLabels.get(this));
      });
    }

    $builderForm.on('submit', function (e) {
      e.preventDefault();

      const ajaxUrl = (window.afbFieldFlowAdmin && window.afbFieldFlowAdmin.ajaxUrl) || window.ajaxurl;
      const action  = (window.afbFieldFlowAdmin && window.afbFieldFlowAdmin.saveAction) || 'afb_save_field_flow_config';

      if (!ajaxUrl) {
        this.submit();
        return;
      }

      const data = $builderForm.serializeArray();
      data.push({ name: 'action', value: action });

      setSavingState(true);

      $.post(ajaxUrl, $.param(data))
        .done(function (res) {
          if (res && res.success) {
            notice('success', (res.data && res.data.message) ? res.data.message : 'Field flow saved.');

            if ($privateProjectSelect.length) {
              const currentPrivateProjectValue = String($privateProjectSelect.val() || '').trim();
              if (currentPrivateProjectValue !== lastPrivateProjectValue) {
                window.location.reload();
                return;
              }
              lastPrivateProjectValue = currentPrivateProjectValue;
            }

            return;
          }

          const msg = (res && res.data && res.data.message) ? res.data.message : 'Save failed.';
          notice('error', msg);
        })
        .fail(function (xhr) {
          const msg = xhr && xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message
            ? xhr.responseJSON.data.message
            : 'Save failed due to a server error.';
          notice('error', msg);
        })
        .always(function () {
          setSavingState(false);
        });
    });
  }
});
