function tKey(key) {
  return typeof t === 'function' ? t(key) : key;
}
function tfKey(key, vars) {
  return typeof tf === 'function' ? tf(key, vars) : key;
}
var TOTAL_STEPS = window.CrankWizardShell.TOTAL_STEPS;
var loadProtocolCapabilities = window.CrankWizardState.loadProtocolCapabilities;
var currentProtocolCapabilities = window.CrankWizardState.currentProtocolCapabilities;
var loadStep = window.CrankWizardShell.loadStep;
var goToStep = window.CrankWizardShell.goToStep;
var _doGoToStep = window.CrankWizardShell.doGoToStep;
var loadWizardPanels = window.CrankWizardShell.loadWizardPanels;
var bindProtocolCards = window.CrankWizardShell.bindProtocolCards;
var step3PanelId = window.CrankWizardShell.step3PanelId;
var currentStep = window.currentStep;
var wizardProtocol = window.wizardProtocol;
var wizardMode = window.wizardMode;
var wizardEditId = window.wizardEditId;
var wizardWorkspaceId = window.wizardWorkspaceId;
var wizardCurrentOperation = window.wizardCurrentOperation;
var wizardCurrentVersion = window.wizardCurrentVersion;
var wizardProtoUpload = window.wizardProtoUpload;
var wizardDescriptorSetUpload = window.wizardDescriptorSetUpload;
var wizardSoapWsdlUpload = window.wizardSoapWsdlUpload;
var wizardSoapXsdUpload = window.wizardSoapXsdUpload;
var wizardTestResponsePreview = window.wizardTestResponsePreview;
var grpcDescriptorServices = window.grpcDescriptorServices;
var soapServiceCatalog = window.soapServiceCatalog;
var wizardProtocolCapabilities = window.wizardProtocolCapabilities;
var wizardSecrets = window.wizardSecrets;
var wizardAuthProfiles = window.wizardAuthProfiles;
var selectedUpstreamId = window.selectedUpstreamId;
var editingUpstreamId = window.editingUpstreamId;
var protoParsed = window.protoParsed;
var selectedRpcMethod = window.selectedRpcMethod;

async function initWizardPage() {
  document.querySelector('.btn-continue').addEventListener('click', function() {
    if (currentStep < TOTAL_STEPS) {
      goToStep(currentStep + 1);
    } else {
      saveOperation();
    }
  });

  document.querySelector('.btn-back').addEventListener('click', function() {
    if (!this.disabled && currentStep > 1) goToStep(currentStep - 1);
  });

  document.querySelectorAll('.step-item').forEach(function(item, i) {
    item.addEventListener('click', function() { goToStep(i + 1); });
  });

  var backToCatalog = document.getElementById('back-to-catalog');
  if (backToCatalog) {
    backToCatalog.addEventListener('click', function() {
      window.location.href = (window.CrankRoutes && window.CrankRoutes.home) || '/';
    });
  }

  var closeBtn = document.querySelector('.progress-close');
  if (closeBtn) {
    closeBtn.addEventListener('click', function() {
      window.location.href = (window.CrankRoutes && window.CrankRoutes.home) || '/';
    });
  }

  var saveDraftBtn = document.querySelector('.btn-save-draft');
  if (saveDraftBtn) {
    saveDraftBtn.addEventListener('click', function() {
      saveOperation(true);
    });
  }

  await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve());
  var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
  wizardWorkspaceId = workspace ? workspace.id : null;
  await loadProtocolCapabilities();

  await loadWizardPanels([1, 2, 3, 4, 5]);
  await loadWizardAuthResources();
  bindProtocolCards();
  bindWizardLiveActions();
  bindStreamingConfigControls();
  updateUpstreamAuthUi();

  var quickSecretModal = document.getElementById('quick-secret-modal');
  var quickSecretClose = document.getElementById('quick-secret-close-btn');
  var quickSecretCancel = document.getElementById('quick-secret-cancel-btn');
  var quickSecretSubmit = document.getElementById('quick-secret-submit-btn');
  if (quickSecretClose) quickSecretClose.addEventListener('click', closeQuickSecretModal);
  if (quickSecretCancel) quickSecretCancel.addEventListener('click', closeQuickSecretModal);
  if (quickSecretModal) {
    quickSecretModal.addEventListener('click', function(event) {
      if (event.target === quickSecretModal) closeQuickSecretModal();
    });
  }
  if (quickSecretSubmit) {
    quickSecretSubmit.addEventListener('click', async function() {
      var button = quickSecretSubmit;
      var original = button.textContent;
      button.disabled = true;
      button.textContent = tKey('wizard.step2.quick_secret_submit');
      try {
        await submitQuickSecret();
      } catch (error) {
        if (window.CrankUi) {
          window.CrankUi.error(
            error.message || tKey('wizard.toast.quick_secret_error_title'),
            tKey('wizard.toast.quick_secret_error_title')
          );
        }
      } finally {
        button.disabled = false;
        button.textContent = original;
      }
    });
  }

  var params = new URLSearchParams(window.location.search);
  if (params.get('mode') === 'edit' && params.get('operationId')) {
    wizardMode = 'edit';
    wizardEditId = params.get('operationId');
    document.title = 'Crank — ' + tKey('wizard.progress.edit');
    await loadOperationForEdit();
  }

  updateWizardProtocolVisibility();
  _doGoToStep(1);
}

if (window.CrankDiagnostics && typeof window.CrankDiagnostics.bootstrap === 'function') {
  window.CrankDiagnostics.bootstrap('wizard', initWizardPage);
} else {
  document.addEventListener('DOMContentLoaded', function() {
    void initWizardPage();
  });
}


/* ── HTTP method picker (Step 4 REST) ── */

var METHOD_CALLOUTS = {
  GET:    null,
  DELETE: null,
  POST:   { icon: 'info', text: { en: '<strong>POST selected — body encoding required.</strong> In step 4 you will define the JSON schema for the body payload. Crank will automatically serialize MCP tool arguments into the format you specify.', ru: '<strong>Выбран POST — требуется кодирование тела запроса.</strong> На шаге 4 вы зададите JSON-схему для body payload. Crank автоматически сериализует аргументы MCP-инструмента в выбранный формат.' } },
  PUT:    { icon: 'info', text: { en: '<strong>PUT selected — full-resource replacement.</strong> The request body must contain a complete resource representation. Define the body schema in step 4.', ru: '<strong>Выбран PUT — полная замена ресурса.</strong> Request body должен содержать полное представление ресурса. Задайте body schema на шаге 4.' } },
  PATCH:  { icon: 'info', text: { en: '<strong>PATCH selected — partial update.</strong> Only include the fields you want to modify in the body schema defined in step 4.', ru: '<strong>Выбран PATCH — частичное обновление.</strong> В body schema на шаге 4 включайте только те поля, которые хотите изменить.' } },
};

function selectMethod(btn) {
  document.querySelectorAll('.method-card').forEach(function(b) { b.classList.remove('active'); });
  btn.classList.add('active');
  var method = btn.dataset.method;
  var callout = document.getElementById('method-callout-rest');
  if (!callout) return;
  var info = METHOD_CALLOUTS[method];
  if (info) {
    var lang = localStorage.getItem('crank_lang') || 'en';
    var text = typeof info.text === 'string' ? info.text : (info.text[lang] || info.text.en);
    callout.innerHTML =
      '<svg width="15" height="15" style="flex-shrink:0;color:var(--accent)"><use href="' + (window.APP_BASE||'') + 'icons/general/info-circle.svg#icon"/></svg>' +
      '<span>' + text + '</span>';
    callout.style.display = 'flex';
  } else {
    callout.style.display = 'none';
  }
}

/* ── GraphQL operation type picker (Step 4 GraphQL) ── */

function selectGqlType(btn) {
  document.querySelectorAll('.gql-type-card').forEach(function(b) { b.classList.remove('active'); });
  btn.classList.add('active');
  // Update query editor placeholder if it's still the default
  var editor = document.getElementById('gql-query-editor');
  if (!editor) return;
  var type = btn.dataset.gqlType;
  var defaults = {
    query: 'query GetContact($id: ID!) {\n  contact(id: $id) {\n    id\n    firstName\n    lastName\n    email\n    company {\n      id\n      name\n    }\n    createdAt\n  }\n}',
    mutation: 'mutation CreateContact(\n  $firstName: String!\n  $lastName: String!\n  $email: String!\n  $company: String\n) {\n  createContact(input: {\n    firstName: $firstName\n    lastName: $lastName\n    email: $email\n    company: $company\n  }) {\n    id\n    firstName\n    lastName\n    createdAt\n  }\n}'
  };
  if (defaults[type]) editor.value = defaults[type];
}

/* ── Upstream selector (Step 2) ── */

var upstreams = [
  { id: 'open-meteo', name: 'Open Meteo', url: 'https://api.open-meteo.com', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null },
  { id: 'countries-graphql', name: 'Countries GraphQL', url: 'https://countries.trevorblades.com', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null },
  { id: 'grpcb', name: 'grpcb.in', url: 'https://grpcb.in:443', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null }
];

var dropdownOpen = false;

function authProfileById(authProfileId) {
  return wizardAuthProfiles.find(function(item) { return item.id === authProfileId; }) || null;
}

function syncUpstreamAuthMetadata(upstream) {
  if (!upstream) return upstream;
  if (!upstream.authProfileId) {
    upstream.authProfileName = '';
    upstream.authKind = null;
    return upstream;
  }

  var profile = authProfileById(upstream.authProfileId);
  if (profile) {
    upstream.authProfileName = profile.name;
    upstream.authKind = profile.kind;
  }
  return upstream;
}

function inferUpstreamAuth(upstream) {
  syncUpstreamAuthMetadata(upstream);
  if (!upstream || !upstream.authProfileId || !upstream.authKind) {
    return { kind: 'none', label: tKey('wizard.step2.auth_none') };
  }
  if (upstream.authKind === 'bearer') {
    return { kind: 'bearer', label: tKey('wizard.step2.auth_bearer') };
  }
  if (upstream.authKind === 'basic') {
    return { kind: 'basic', label: tKey('wizard.step2.auth_basic') };
  }
  return { kind: 'apikey', label: tKey('wizard.step2.auth_apikey') };
}

function upstreamMeta(upstream) {
  var auth = inferUpstreamAuth(upstream);
  return {
    kind: auth.kind,
    label: auth.label,
  };
}

async function loadWizardAuthResources() {
  if (!wizardWorkspaceId || !window.CrankApi) {
    wizardSecrets = [];
    wizardAuthProfiles = [];
    renderAuthProfileOptions();
    return;
  }

  try {
    var results = await Promise.all([
      window.CrankApi.listSecrets(wizardWorkspaceId),
      window.CrankApi.listAuthProfiles(wizardWorkspaceId),
    ]);
    wizardSecrets = (results[0] && results[0].items) || [];
    wizardAuthProfiles = (results[1] && results[1].items) || [];
  } catch (_error) {
    wizardSecrets = [];
    wizardAuthProfiles = [];
  }

  upstreams.forEach(syncUpstreamAuthMetadata);
  renderAuthProfileOptions();
}

function renderAuthProfileOptions() {
  var profileSelect = document.getElementById('new-upstream-auth-profile');
  var secretSelect = document.getElementById('new-auth-profile-secret-id');
  var usernameSelect = document.getElementById('new-auth-profile-username-secret');
  var passwordSelect = document.getElementById('new-auth-profile-password-secret');
  var profileHint = document.getElementById('new-upstream-auth-profile-hint');

  if (profileSelect) {
    var previousProfile = profileSelect.value;
    profileSelect.innerHTML = '';
    if (!wizardAuthProfiles.length) {
      var emptyProfile = document.createElement('option');
      emptyProfile.value = '';
      emptyProfile.textContent = tKey('wizard.step2.auth_profile_empty');
      profileSelect.appendChild(emptyProfile);
      if (profileHint) profileHint.textContent = tKey('wizard.step2.auth_profile_empty');
    } else {
      var placeholder = document.createElement('option');
      placeholder.value = '';
      placeholder.textContent = tKey('wizard.step2.auth_profile');
      profileSelect.appendChild(placeholder);
      wizardAuthProfiles.forEach(function(profile) {
        var option = document.createElement('option');
        option.value = profile.id;
        option.textContent = profile.name + ' · ' + profile.kind;
        profileSelect.appendChild(option);
      });
      if (previousProfile) {
        profileSelect.value = previousProfile;
      }
      if (profileHint) profileHint.textContent = tKey('wizard.step2.auth_profile_hint');
    }
  }

  [secretSelect, usernameSelect, passwordSelect].forEach(function(select) {
    if (!select) return;
    var previous = select.value;
    select.innerHTML = '';
    var placeholder = document.createElement('option');
    placeholder.value = '';
    placeholder.textContent = tKey('wizard.step2.secret_value');
    select.appendChild(placeholder);
    wizardSecrets.forEach(function(secret) {
      var option = document.createElement('option');
      option.value = secret.id;
      option.textContent = secret.name + ' · ' + secret.kind;
      select.appendChild(option);
    });
    if (previous) {
      select.value = previous;
    }
  });
}

function updateAuthProfileCreateUi() {
  var kind = textValue('new-auth-profile-kind') || 'bearer';
  var headerGroup = document.getElementById('auth-profile-header-name-group');
  var queryGroup = document.getElementById('auth-profile-query-param-group');
  var basicRow = document.getElementById('auth-profile-basic-secret-row');
  var singleSecretGroup = document.getElementById('auth-profile-single-secret-group');

  if (headerGroup) {
    headerGroup.style.display = (kind === 'bearer' || kind === 'api_key_header') ? '' : 'none';
  }
  if (queryGroup) {
    queryGroup.style.display = kind === 'api_key_query' ? '' : 'none';
  }
  if (basicRow) {
    basicRow.style.display = kind === 'basic' ? '' : 'none';
  }
  if (singleSecretGroup) {
    singleSecretGroup.style.display = kind === 'basic' ? 'none' : '';
  }
}

function updateUpstreamAuthUi() {
  var mode = textValue('new-upstream-auth-mode') || 'none';
  var existingGroup = document.getElementById('upstream-auth-existing-group');
  var createGroup = document.getElementById('upstream-auth-create-group');
  if (existingGroup) existingGroup.style.display = mode === 'existing' ? '' : 'none';
  if (createGroup) createGroup.style.display = mode === 'create' ? '' : 'none';
  updateAuthProfileCreateUi();
}

function buildUpstreamTriggerContent(name, url) {
  var fragment = document.createDocumentFragment();
  fragment.appendChild(createDivWithClass('upstream-trigger-name', name));
  fragment.appendChild(createDivWithClass('upstream-trigger-url', url));
  return fragment;
}

function setUpstreamComboboxPlaceholder(container) {
  if (!container) return;
  container.innerHTML = '';
  var placeholder = document.createElement('span');
  placeholder.className = 'upstream-combobox-placeholder';
  placeholder.textContent = tKey('wizard.step2.upstream_placeholder');
  container.appendChild(placeholder);
}

function renderDropdownList(filter) {
  var list = document.getElementById('upstream-dropdown-list');
  if (!list) return;
  var q = (filter || '').toLowerCase();
  var filtered = upstreams.filter(function(u) {
    return !q || u.name.toLowerCase().includes(q) || u.url.toLowerCase().includes(q);
  });

  if (filtered.length === 0) {
    list.innerHTML = '';
    var empty = document.createElement('div');
    empty.className = 'upstream-dropdown-empty';
    empty.textContent = tfKey('agents.drawer.ops_no_match', { query: filter || '' });
    list.appendChild(empty);
    return;
  }

  var tmpl = document.getElementById('tmpl-upstream-item');
  list.innerHTML = '';
  filtered.forEach(function(u) {
    var sel = u.id === selectedUpstreamId;
    var meta = upstreamMeta(u);
    var node = tmpl.content.cloneNode(true);
    var item = node.querySelector('.upstream-dropdown-item');
    if (sel) item.classList.add('selected');
    item.addEventListener('click', function() { pickUpstream(u.id); });
    node.querySelector('.upstream-dropdown-item-name').textContent = u.name;
    node.querySelector('.upstream-dropdown-item-url').textContent  = u.url;
    var badge = node.querySelector('.upstream-auth-badge');
    badge.textContent  = meta.label;
    badge.className    = 'upstream-auth-badge auth-' + meta.kind;
    list.appendChild(node);
  });
}

function openUpstreamDropdown() {
  var combobox = document.getElementById('upstream-combobox');
  var dropdown = document.getElementById('upstream-dropdown');
  var search = document.getElementById('upstream-search');
  if (!combobox || !dropdown) return;
  combobox.classList.add('open');
  dropdown.style.display = '';
  dropdownOpen = true;
  renderDropdownList('');
  if (search) { search.value = ''; search.focus(); }

  // Close new-upstream form if open
  var form = document.getElementById('upstream-new-form');
  if (form) form.style.display = 'none';
}

function closeUpstreamDropdown() {
  var combobox = document.getElementById('upstream-combobox');
  var dropdown = document.getElementById('upstream-dropdown');
  if (!combobox || !dropdown) return;
  combobox.classList.remove('open');
  dropdown.style.display = 'none';
  dropdownOpen = false;
}

function toggleUpstreamDropdown(e) {
  e.stopPropagation();
  if (dropdownOpen) { closeUpstreamDropdown(); } else { openUpstreamDropdown(); }
}

function filterUpstreams(val) {
  renderDropdownList(val);
}

function pickUpstream(id) {
  selectedUpstreamId = id;
  editingUpstreamId = null;
  closeUpstreamDropdown();

  var u = upstreams.find(function(x) { return x.id === id; });
  if (!u) return;

  // Update combobox trigger value
  var val = document.getElementById('upstream-combobox-value');
  if (val) {
    var trigTmpl = document.getElementById('tmpl-upstream-trigger-value');
    if (trigTmpl) {
      val.innerHTML = '';
      var trigNode = trigTmpl.content.cloneNode(true);
      trigNode.querySelector('.upstream-trigger-name').textContent = u.name;
      trigNode.querySelector('.upstream-trigger-url').textContent  = u.url;
      val.appendChild(trigNode);
    } else {
      val.innerHTML = '';
      val.appendChild(buildUpstreamTriggerContent(u.name, u.url));
    }
  }

  // Show preview strip
  var preview = document.getElementById('upstream-preview');
  var pName = document.getElementById('upstream-preview-name');
  var pUrl = document.getElementById('upstream-preview-url');
  var pBadge = document.getElementById('upstream-preview-badge');
  if (preview) {
    var meta = upstreamMeta(u);
    pName.textContent = u.name;
    pUrl.textContent = u.url;
    pBadge.textContent = meta.label;
    pBadge.className = 'upstream-auth-badge auth-' + meta.kind;
    preview.style.display = 'flex';
  }

  // Hide new-upstream form and reset trigger
  var form = document.getElementById('upstream-new-form');
  if (form) form.style.display = 'none';
  var trigger = document.getElementById('upstream-new-trigger');
  if (trigger) trigger.classList.remove('active');

  // Update reflection panel upstream info strip
  var reflectName = document.getElementById('grpc-reflect-upstream-name');
  var reflectUrl  = document.getElementById('grpc-reflect-upstream-url');
  if (reflectName) reflectName.textContent = u.name;
  if (reflectUrl)  reflectUrl.textContent  = u.url;
}

function startNewUpstream() {
  closeUpstreamDropdown();
  editingUpstreamId = null;
  var trigger = document.getElementById('upstream-new-trigger');
  var form = document.getElementById('upstream-new-form');
  var isOpen = form && form.style.display !== 'none';

  if (isOpen) {
    // toggle off — restore previous state
    if (form) form.style.display = 'none';
    if (trigger) trigger.classList.remove('active');
    return;
  }

  // Clear combobox trigger & hide preview
  var val = document.getElementById('upstream-combobox-value');
  if (val) setUpstreamComboboxPlaceholder(val);
  var preview = document.getElementById('upstream-preview');
  if (preview) preview.style.display = 'none';
  selectedUpstreamId = null;

  // Mark trigger active and show form
  if (trigger) trigger.classList.add('active');
  if (form) {
    form.style.display = '';
    setValue('new-upstream-name', '');
    setValue('new-upstream-url', '');
    setValue('new-upstream-static-headers', '{\n}');
    setValue('new-upstream-auth-mode', 'none');
    setValue('new-upstream-auth-profile', '');
    setValue('new-auth-profile-name', '');
    setValue('new-auth-profile-kind', 'bearer');
    setValue('new-auth-profile-header-name', 'Authorization');
    setValue('new-auth-profile-query-param', 'api_key');
    setValue('new-auth-profile-secret-id', '');
    setValue('new-auth-profile-username-secret', '');
    setValue('new-auth-profile-password-secret', '');
    updateUpstreamAuthUi();
    var first = form.querySelector('input');
    if (first) setTimeout(function() { first.focus(); }, 50);
  }
}

function beginEditSelectedUpstream(e) {
  if (e) e.stopPropagation();
  var upstream = upstreams.find(function(item) { return item.id === selectedUpstreamId; });
  if (!upstream) return;

  closeUpstreamDropdown();
  editingUpstreamId = upstream.id;

  var trigger = document.getElementById('upstream-new-trigger');
  var form = document.getElementById('upstream-new-form');
  var preview = document.getElementById('upstream-preview');
  if (trigger) trigger.classList.add('active');
  if (preview) preview.style.display = 'none';
  if (form) {
    form.style.display = '';
    setValue('new-upstream-name', upstream.name);
    setValue('new-upstream-url', upstream.url);
    setValue('new-upstream-static-headers', upstream.staticHeaders || '{\n}');
    setValue('new-upstream-auth-profile', upstream.authProfileId || '');
    setValue('new-upstream-auth-mode', upstream.authProfileId ? 'existing' : 'none');
    updateUpstreamAuthUi();
  }
}

function buildAuthProfileConfig(kind) {
  if (kind === 'bearer') {
    var bearerHeader = textValue('new-auth-profile-header-name') || 'Authorization';
    var bearerSecretId = textValue('new-auth-profile-secret-id');
    if (!bearerHeader) throw new Error(tKey('wizard.error.header_name_required'));
    if (!bearerSecretId) throw new Error(tKey('wizard.error.secret_required'));
    return {
      bearer: {
        header_name: bearerHeader,
        secret_id: bearerSecretId,
      }
    };
  }

  if (kind === 'basic') {
    var usernameSecretId = textValue('new-auth-profile-username-secret');
    var passwordSecretId = textValue('new-auth-profile-password-secret');
    if (!usernameSecretId || !passwordSecretId) throw new Error(tKey('wizard.error.basic_secrets_required'));
    return {
      basic: {
        username_secret_id: usernameSecretId,
        password_secret_id: passwordSecretId,
      }
    };
  }

  if (kind === 'api_key_header') {
    var headerName = textValue('new-auth-profile-header-name');
    var headerSecretId = textValue('new-auth-profile-secret-id');
    if (!headerName) throw new Error(tKey('wizard.error.header_name_required'));
    if (!headerSecretId) throw new Error(tKey('wizard.error.secret_required'));
    return {
      api_key_header: {
        header_name: headerName,
        secret_id: headerSecretId,
      }
    };
  }

  var queryParam = textValue('new-auth-profile-query-param');
  var querySecretId = textValue('new-auth-profile-secret-id');
  if (!queryParam) throw new Error(tKey('wizard.error.query_param_required'));
  if (!querySecretId) throw new Error(tKey('wizard.error.secret_required'));
  return {
    api_key_query: {
      param_name: queryParam,
      secret_id: querySecretId,
    }
  };
}

async function createAuthProfileFromForm() {
  var name = textValue('new-auth-profile-name');
  var kind = textValue('new-auth-profile-kind') || 'bearer';
  if (!name) throw new Error(tKey('wizard.error.auth_profile_name'));

  var profile = await window.CrankApi.createAuthProfile(wizardWorkspaceId, {
    name: name,
    kind: kind,
    config: buildAuthProfileConfig(kind),
  });
  wizardAuthProfiles.push(profile);
  renderAuthProfileOptions();
  if (window.CrankUi) {
    window.CrankUi.success(
      tKey('wizard.toast.auth_profile_created_body'),
      tKey('wizard.toast.auth_profile_created_title')
    );
  }
  return profile;
}

async function saveNewUpstream(e) {
  e.stopPropagation();
  try {
    var nameEl = document.getElementById('new-upstream-name');
    var urlEl = document.getElementById('new-upstream-url');
    var name = nameEl ? nameEl.value.trim() : '';
    var url = urlEl ? urlEl.value.trim() : '';
    if (!name) { if (nameEl) nameEl.focus(); return; }
    if (!url) { if (urlEl) urlEl.focus(); return; }

    var staticHeadersEl = document.getElementById('new-upstream-static-headers');
    var staticHeaders = staticHeadersEl ? staticHeadersEl.value : '{\n}';
    parseHeaderMap(staticHeaders);

    var authMode = textValue('new-upstream-auth-mode') || 'none';
    var authProfileId = null;
    var authProfileName = '';
    var authKind = null;

    if (authMode === 'existing') {
      authProfileId = textValue('new-upstream-auth-profile');
      if (!authProfileId) throw new Error(tKey('wizard.error.auth_profile_required'));
      var existingProfile = authProfileById(authProfileId);
      authProfileName = existingProfile ? existingProfile.name : '';
      authKind = existingProfile ? existingProfile.kind : null;
    } else if (authMode === 'create') {
      var createdProfile = await createAuthProfileFromForm();
      authProfileId = createdProfile.id;
      authProfileName = createdProfile.name;
      authKind = createdProfile.kind;
      setValue('new-upstream-auth-profile', createdProfile.id);
      setValue('new-upstream-auth-mode', 'existing');
    }
    var nextRecord = {
      id: editingUpstreamId || ('custom-' + Date.now()),
      name: name,
      url: url,
      staticHeaders: staticHeaders,
      authProfileId: authProfileId,
      authProfileName: authProfileName,
      authKind: authKind,
    };

    if (editingUpstreamId) {
      upstreams = upstreams.map(function(item) {
        return item.id === editingUpstreamId ? nextRecord : item;
      });
    } else {
      upstreams.push(nextRecord);
    }

    if (nameEl) nameEl.value = '';
    if (urlEl) urlEl.value = '';
    if (staticHeadersEl) staticHeadersEl.value = '{\n}';
    var form = document.getElementById('upstream-new-form');
    if (form) form.style.display = 'none';
    editingUpstreamId = null;
    pickUpstream(nextRecord.id);
  } catch (error) {
    if (window.CrankUi) {
      window.CrankUi.error(
        error.message || tKey('wizard.toast.auth_profile_error_title'),
        tKey('wizard.toast.auth_profile_error_title')
      );
    }
  }
}

function cancelNewUpstream(e) {
  e.stopPropagation();
  var form = document.getElementById('upstream-new-form');
  if (form) form.style.display = 'none';
  var trigger = document.getElementById('upstream-new-trigger');
  if (trigger) trigger.classList.remove('active');
  editingUpstreamId = null;
}

function openQuickSecretModal(e) {
  if (e) e.preventDefault();
  var modal = document.getElementById('quick-secret-modal');
  if (!modal) return;
  setValue('quick-secret-name', '');
  setValue('quick-secret-value', '');
  setValue('quick-secret-kind', 'token');
  modal.classList.add('open');
  setTimeout(function() {
    var input = document.getElementById('quick-secret-name');
    if (input) input.focus();
  }, 30);
}

function closeQuickSecretModal() {
  var modal = document.getElementById('quick-secret-modal');
  if (modal) modal.classList.remove('open');
}

async function submitQuickSecret() {
  var name = textValue('quick-secret-name');
  var value = textValue('quick-secret-value');
  var kind = textValue('quick-secret-kind') || 'token';
  if (!name) throw new Error(tKey('wizard.error.secret_name_required'));
  if (!value) throw new Error(tKey('wizard.error.secret_value_required'));

  var secret = await window.CrankApi.createSecret(wizardWorkspaceId, {
    name: name,
    kind: kind,
    value: value,
  });
  wizardSecrets.push(secret);
  renderAuthProfileOptions();
  var primarySelect = document.getElementById('new-auth-profile-secret-id');
  var usernameSelect = document.getElementById('new-auth-profile-username-secret');
  var passwordSelect = document.getElementById('new-auth-profile-password-secret');
  if (primarySelect && primarySelect.offsetParent !== null) {
    primarySelect.value = secret.id;
  } else if (usernameSelect && !usernameSelect.value) {
    usernameSelect.value = secret.id;
  } else if (passwordSelect) {
    passwordSelect.value = secret.id;
  }
  closeQuickSecretModal();
  if (window.CrankUi) {
    window.CrankUi.success(
      tKey('wizard.toast.quick_secret_created_body'),
      tKey('wizard.toast.quick_secret_created_title')
    );
  }
}

function escapeHtml(str) {
  return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

// Close dropdown on outside click
document.addEventListener('click', function() {
  if (dropdownOpen) closeUpstreamDropdown();
});


/* ══════════════════════════════════════════════
   gRPC — source selector
══════════════════════════════════════════════ */

var grpcSource = 'proto'; // 'proto' | 'reflection' | 'manual'

function selectGrpcSource(source) {
  grpcSource = source;
  document.querySelectorAll('.grpc-source-btn').forEach(function(btn) {
    btn.classList.toggle('active', btn.dataset.source === source);
  });
  document.getElementById('grpc-src-proto').style.display      = source === 'proto'      ? '' : 'none';
  document.getElementById('grpc-src-reflection').style.display = source === 'reflection' ? '' : 'none';
  document.getElementById('grpc-src-manual').style.display     = source === 'manual'     ? '' : 'none';
  // hide shared detail when switching sources
  var detail = document.getElementById('grpc-method-detail');
  if (detail) detail.style.display = 'none';
  selectedRpcMethod = null;
}

/* ══════════════════════════════════════════════
   gRPC — Proto file parser & UI
══════════════════════════════════════════════ */

/* Extract top-level blocks with proper brace matching */
function extractBlocks(text, keyword) {
  var blocks = [];
  var re = new RegExp(keyword + '\\s+(\\w+)\\s*\\{', 'g');
  var match;
  while ((match = re.exec(text)) !== null) {
    var name = match[1];
    var start = match.index + match[0].length;
    var depth = 1;
    var i = start;
    while (i < text.length && depth > 0) {
      if (text[i] === '{') depth++;
      else if (text[i] === '}') depth--;
      i++;
    }
    blocks.push({ name: name, body: text.slice(start, i - 1), start: match.index });
  }
  return blocks;
}

/* Strip single-line and block comments */
function stripProtoComments(text) {
  // block comments
  text = text.replace(/\/\*[\s\S]*?\*\//g, '');
  // line comments
  text = text.replace(/\/\/[^\n]*/g, '');
  return text;
}

/* Parse a proto text → { package, services, messages, streamingCount } */
function parseProto(text) {
  text = stripProtoComments(text);

  var pkg = '';
  var pkgMatch = text.match(/\bpackage\s+([\w.]+)\s*;/);
  if (pkgMatch) pkg = pkgMatch[1];

  // messages: map name → [{ repeated, type, name }]
  var messages = {};
  extractBlocks(text, 'message').forEach(function(blk) {
    var fields = [];
    var fieldRe = /\b(repeated\s+)?([\w.]+)\s+(\w+)\s*=\s*\d+/g;
    var fm;
    while ((fm = fieldRe.exec(blk.body)) !== null) {
      // skip reserved / map keywords
      if (['option','reserved','extensions','oneof'].indexOf(fm[2]) !== -1) continue;
      fields.push({ repeated: !!fm[1], type: fm[2], name: fm[3] });
    }
    messages[blk.name] = fields;
  });

  var streamingCount = 0;
  var services = [];

  extractBlocks(text, 'service').forEach(function(svc) {
    var methods = [];
    // rpc Name ( [stream] ReqType ) returns ( [stream] ResType ) { }
    var rpcRe = /\brpc\s+(\w+)\s*\(\s*(stream\s+)?([\w.]+)\s*\)\s*returns\s*\(\s*(stream\s+)?([\w.]+)\s*\)/g;
    var rm;
    while ((rm = rpcRe.exec(svc.body)) !== null) {
      var streamIn  = !!rm[2];
      var streamOut = !!rm[4];
      if (streamIn || streamOut) {
        streamingCount++;
      } else {
        // Strip package prefix from type names for display
        var reqType = rm[3].split('.').pop();
        var resType = rm[5].split('.').pop();
        methods.push({ name: rm[1], request: reqType, response: resType });
      }
    }
    if (methods.length > 0) {
      services.push({ name: svc.name, methods: methods });
    }
  });

  return { package: pkg, services: services, messages: messages, streamingCount: streamingCount };
}

/* Generic renderer: fills a service/method list into given element IDs */
function renderServiceResults(parsed, ids) {
  protoParsed = parsed;
  selectedRpcMethod = null;

  var notice = document.getElementById(ids.notice);
  var countEl = document.getElementById(ids.count);
  if (notice) {
    notice.style.display = parsed.streamingCount > 0 ? '' : 'none';
    if (countEl && parsed.streamingCount > 0) countEl.textContent = parsed.streamingCount;
  }

  var totalMethods = parsed.services.reduce(function(n, s) { return n + s.methods.length; }, 0);
  var summaryEl = document.getElementById(ids.summary);
  if (summaryEl) {
    var servicesLabel = window.tPlural('wizard.grpc.services_label', parsed.services.length, {
      count: parsed.services.length,
    });
    var methodsLabel = window.tPlural('wizard.grpc.methods_label', totalMethods, {
      count: totalMethods,
    });
    summaryEl.textContent = tfKey('wizard.grpc.services_found', {
      services_label: servicesLabel,
      methods_label: methodsLabel,
    });
  }

  var listEl = document.getElementById(ids.list);
  if (!listEl) return;

  listEl.innerHTML = '';
  if (parsed.services.length === 0) {
    var emptyDiv = document.createElement('div');
    emptyDiv.className = 'proto-empty';
    emptyDiv.textContent = tKey('wizard.grpc.no_methods');
    listEl.appendChild(emptyDiv);
  } else {
    var svcTmpl    = document.getElementById('tmpl-proto-service-group');
    var methodTmpl = document.getElementById('tmpl-proto-method-card');
    parsed.services.forEach(function(svc) {
      var svcNode = svcTmpl.content.cloneNode(true);
      svcNode.querySelector('.proto-service-name').textContent = svc.name;
      var grid = svcNode.querySelector('.proto-method-grid');
      svc.methods.forEach(function(m) {
        var mNode = methodTmpl.content.cloneNode(true);
        var btn = mNode.querySelector('.proto-method-card');
        btn.dataset.service = svc.name;
        btn.dataset.method  = m.name;
        btn.dataset.req     = m.request;
        btn.dataset.res     = m.response;
        mNode.querySelector('.proto-method-name').textContent = m.name;
        mNode.querySelector('.proto-req-type').textContent    = m.request;
        mNode.querySelector('.proto-res-type').textContent    = m.response;
        grid.appendChild(mNode);
      });
      listEl.appendChild(svcNode);
    });
  }

  document.getElementById(ids.view).style.display = '';
  document.getElementById('grpc-method-detail').style.display = 'none';
}

function renderParsedProto(parsed) {
  var notice = document.getElementById('proto-streaming-text');
  if (notice && parsed && parsed.streamingCount) {
    notice.textContent = window.tPlural('wizard.step3.grpc.streaming_hidden', parsed.streamingCount, {
      count: parsed.streamingCount,
    });
  }
  renderServiceResults(parsed, {
    notice: 'proto-streaming-notice', count: 'proto-streaming-count',
    summary: 'proto-services-summary', list: 'proto-services-list', view: 'proto-parsed-view',
  });
}

function renderReflectionResult(parsed) {
  var reflectText = document.querySelector('#reflect-streaming-notice .info-callout-text');
  if (reflectText && parsed && parsed.streamingCount) {
    reflectText.textContent = window.tPlural('wizard.step3.grpc.streaming_hidden', parsed.streamingCount, {
      count: parsed.streamingCount,
    });
  }
  renderServiceResults(parsed, {
    notice: 'reflect-streaming-notice', count: 'reflect-streaming-count',
    summary: 'reflect-services-summary', list: 'reflect-services-list', view: 'reflect-parsed-view',
  });
}

/* Select a method card (shared between proto + reflection) */
function selectRpcMethod(btn) {
  document.querySelectorAll('.proto-method-card').forEach(function(b) { b.classList.remove('active'); });
  btn.classList.add('active');

  var svcName    = btn.dataset.service;
  var methodName = btn.dataset.method;
  var reqType    = btn.dataset.req;
  var resType    = btn.dataset.res;

  selectedRpcMethod = { service: svcName, method: methodName, request: reqType, response: resType };

  var pkg = protoParsed && protoParsed.package ? protoParsed.package + '.' : '';
  var grpcPath = '/' + pkg + svcName + '/' + methodName;

  var pathEl = document.getElementById('grpc-path-value');
  if (pathEl) pathEl.textContent = grpcPath;

  var sub = document.getElementById('grpc-method-subtitle');
  if (sub) sub.textContent = 'rpc ' + methodName + ' (' + reqType + ') returns (' + resType + ')';

  var reqTypeEl = document.getElementById('grpc-req-type');
  var resTypeEl = document.getElementById('grpc-res-type');
  if (reqTypeEl) reqTypeEl.textContent = reqType;
  if (resTypeEl) resTypeEl.textContent = resType;

  var sigGrid = document.getElementById('grpc-detail-sig');
  if (sigGrid) sigGrid.style.display = '';

  renderFieldList('grpc-req-fields', reqType);
  renderFieldList('grpc-res-fields', resType);

  document.getElementById('grpc-method-detail').style.display = '';
}

function renderFieldList(elId, typeName) {
  var el = document.getElementById(elId);
  if (!el) return;
  var fields = protoParsed && protoParsed.messages[typeName];
  el.innerHTML = '';
  if (!fields || fields.length === 0) {
    var emptyEl = document.createElement('div');
    emptyEl.className = 'proto-field-empty';
    emptyEl.innerHTML = tKey('wizard.grpc.fields_unresolved') + '<br><span style="color:var(--text-muted);font-size:11px;">' + tKey('wizard.grpc.fields_unresolved_body') + '</span>';
    el.appendChild(emptyEl);
    return;
  }
  var fieldTmpl = document.getElementById('tmpl-proto-field-row');
  fields.forEach(function(f) {
    var node = fieldTmpl.content.cloneNode(true);
    node.querySelector('.proto-field-name').textContent = f.name;
    node.querySelector('.proto-field-type').textContent = (f.repeated ? 'repeated ' : '') + f.type;
    el.appendChild(node);
  });
}

/* File handling */
function handleProtoFile(event) {
  var file = event.target.files[0];
  if (!file) return;
  var reader = new FileReader();
  reader.onload = function(e) {
    wizardProtoUpload = { fileName: file.name, content: e.target.result };
    showProtoFileInfo(file.name, file.size);
    var parsed = parseProto(e.target.result);
    renderParsedProto(parsed);
  };
  reader.readAsText(file);
}

function handleProtoDrop(event) {
  event.preventDefault();
  var dropzone = document.getElementById('proto-dropzone');
  if (dropzone) dropzone.classList.remove('drag-over');
  var file = event.dataTransfer.files[0];
  if (!file || !file.name.endsWith('.proto')) return;
  var reader = new FileReader();
  reader.onload = function(e) {
    wizardProtoUpload = { fileName: file.name, content: e.target.result };
    showProtoFileInfo(file.name, file.size);
    renderParsedProto(parseProto(e.target.result));
  };
  reader.readAsText(file);
}

function showProtoFileInfo(name, size) {
  var dropzone = document.getElementById('proto-dropzone');
  var info = document.getElementById('proto-file-info');
  var nameEl = document.getElementById('proto-file-name');
  var sizeEl = document.getElementById('proto-file-size');
  if (dropzone) dropzone.style.display = 'none';
  if (info) info.style.display = '';
  if (nameEl) nameEl.textContent = name;
  if (sizeEl) sizeEl.textContent = (size / 1024).toFixed(1) + ' KB';
  // hide paste area
  document.getElementById('proto-paste-area').style.display = 'none';
  document.getElementById('proto-paste-btn').textContent = 'Paste content';
}

function resetProto() {
  var dropzone = document.getElementById('proto-dropzone');
  var info = document.getElementById('proto-file-info');
  var parsedView = document.getElementById('proto-parsed-view');
  if (dropzone) dropzone.style.display = '';
  if (info) info.style.display = 'none';
  if (parsedView) parsedView.style.display = 'none';
  var fileInput = document.getElementById('proto-file-input');
  if (fileInput) fileInput.value = '';
  protoParsed = null;
  selectedRpcMethod = null;
  wizardProtoUpload = null;
}

function toggleProtoPaste() {
  var area = document.getElementById('proto-paste-area');
  var btn = document.getElementById('proto-paste-btn');
  if (!area) return;
  var open = area.style.display !== 'none';
  area.style.display = open ? 'none' : '';
  if (btn) btn.textContent = open ? 'Paste content' : 'Cancel paste';
  if (!open) {
    var ta = document.getElementById('proto-paste-input');
    if (ta) setTimeout(function() { ta.focus(); }, 50);
  }
}

function cancelProtoPaste() {
  var area = document.getElementById('proto-paste-area');
  var btn = document.getElementById('proto-paste-btn');
  if (area) area.style.display = 'none';
  if (btn) btn.textContent = 'Paste content';
}

function parseProtoPasted() {
  var ta = document.getElementById('proto-paste-input');
  if (!ta || !ta.value.trim()) return;
  var parsed = parseProto(ta.value);
  wizardProtoUpload = { fileName: 'pasted.proto', content: ta.value };
  document.getElementById('proto-paste-area').style.display = 'none';
  var dropzone = document.getElementById('proto-dropzone');
  if (dropzone) dropzone.style.display = 'none';
  var info = document.getElementById('proto-file-info');
  var nameEl = document.getElementById('proto-file-name');
  var sizeEl = document.getElementById('proto-file-size');
  if (info) info.style.display = '';
  if (nameEl) nameEl.textContent = 'pasted.proto';
  if (sizeEl) sizeEl.textContent = (ta.value.length / 1024).toFixed(1) + ' KB';
  renderParsedProto(parsed);
}


/* ══════════════════════════════════════════════
   gRPC — Server reflection (simulated)
══════════════════════════════════════════════ */

var REFLECTION_MOCK = {
  package: 'acme.v1',
  streamingCount: 2,
  services: [
    { name: 'ContactService', methods: [
      { name: 'GetContact',    request: 'GetContactRequest',    response: 'Contact' },
      { name: 'CreateContact', request: 'CreateContactRequest', response: 'Contact' },
      { name: 'UpdateContact', request: 'UpdateContactRequest', response: 'Contact' },
      { name: 'DeleteContact', request: 'DeleteContactRequest', response: 'DeleteContactResponse' },
    ]},
    { name: 'CompanyService', methods: [
      { name: 'GetCompany',    request: 'GetCompanyRequest',    response: 'Company' },
      { name: 'ListCompanies', request: 'ListCompaniesRequest', response: 'ListCompaniesResponse' },
    ]},
  ],
  messages: {
    'GetContactRequest':     [{ type: 'string',  name: 'id' }],
    'Contact':               [{ type: 'string',  name: 'id' }, { type: 'string', name: 'first_name' }, { type: 'string', name: 'last_name' }, { type: 'string', name: 'email' }, { type: 'string', name: 'company_id' }, { type: 'string', name: 'created_at' }],
    'CreateContactRequest':  [{ type: 'string',  name: 'first_name' }, { type: 'string', name: 'last_name' }, { type: 'string', name: 'email' }, { type: 'string', name: 'company_id' }],
    'UpdateContactRequest':  [{ type: 'string',  name: 'id' }, { type: 'string', name: 'first_name' }, { type: 'string', name: 'last_name' }, { type: 'string', name: 'email' }],
    'DeleteContactRequest':  [{ type: 'string',  name: 'id' }],
    'DeleteContactResponse': [{ type: 'bool',    name: 'success' }],
    'GetCompanyRequest':     [{ type: 'string',  name: 'id' }],
    'Company':               [{ type: 'string',  name: 'id' }, { type: 'string', name: 'name' }, { type: 'string', name: 'domain' }, { type: 'int32', name: 'employee_count' }],
    'ListCompaniesRequest':  [{ type: 'int32',   name: 'page' }, { type: 'int32', name: 'page_size' }, { type: 'string', name: 'query' }],
    'ListCompaniesResponse': [{ type: 'Company', name: 'companies', repeated: true }, { type: 'int32', name: 'total' }],
  },
};

function startReflection() {
  goToStep(5);
  showWizardLiveStatus(
    tKey('wizard.grpc.discovery_title'),
    tKey('wizard.grpc.discovery_body')
  );
}


/* ══════════════════════════════════════════════
   gRPC — Manual entry
══════════════════════════════════════════════ */

function grpcManualRouteInput(val) {
  var detail = document.getElementById('grpc-method-detail');
  var sigGrid = document.getElementById('grpc-detail-sig');
  if (!val || !val.startsWith('/') || val.indexOf('/', 1) === -1) {
    if (detail) detail.style.display = 'none';
    return;
  }
  // Parse service + method from route: /[pkg.]Service/Method
  var parts = val.replace(/^\//, '').split('/');
  var servicePart = parts[0] || '';
  var methodName  = parts[1] || '';
  var serviceName = servicePart.split('.').pop();

  var pathEl = document.getElementById('grpc-path-value');
  if (pathEl) pathEl.textContent = val;

  var sub = document.getElementById('grpc-method-subtitle');
  if (sub) sub.textContent = serviceName + '/' + methodName;

  // hide sig grid in manual mode (no field data)
  if (sigGrid) sigGrid.style.display = 'none';

  grpcManualTypeInput();
  if (detail) detail.style.display = '';
}

function grpcManualTypeInput() {
  var reqInput = document.getElementById('grpc-manual-req-type');
  var resInput = document.getElementById('grpc-manual-res-type');
  var reqTypeEl = document.getElementById('grpc-req-type');
  var resTypeEl = document.getElementById('grpc-res-type');
  if (reqTypeEl) reqTypeEl.textContent = (reqInput && reqInput.value.trim()) || '—';
  if (resTypeEl) resTypeEl.textContent = (resInput && resInput.value.trim()) || '—';
}


function textValue(id) {
  var element = document.getElementById(id);
  return element ? element.value.trim() : '';
}

function setValue(id, value) {
  var element = document.getElementById(id);
  if (element) element.value = value || '';
}

function parseStructuredText(text) {
  if (!text.trim()) return {};
  try {
    return JSON.parse(text);
  } catch (_error) {
    if (!window.jsyaml) throw new Error(tKey('wizard.error.parser_unavailable'));
    return window.jsyaml.load(text) || {};
  }
}

function inferSchemaKind(typeName) {
  if (typeName === 'object') return 'object';
  if (typeName === 'array') return 'array';
  if (typeName === 'string') return 'string';
  if (typeName === 'integer') return 'integer';
  if (typeName === 'number') return 'number';
  if (typeName === 'boolean') return 'boolean';
  if (typeName === 'null') return 'null';
  return 'string';
}

function convertJsonSchemaToCrankSchema(node, requiredNames) {
  var schemaNode = node || {};
  var typeName = Array.isArray(schemaNode.type)
    ? schemaNode.type.find(function(value) { return value !== 'null'; }) || 'string'
    : schemaNode.type || (schemaNode.properties ? 'object' : 'string');
  var nullable = Array.isArray(schemaNode.type) && schemaNode.type.indexOf('null') >= 0;
  var kind = schemaNode.enum ? 'enum' : inferSchemaKind(typeName);
  var schema = {
    type: kind,
    description: schemaNode.description || null,
    required: false,
    nullable: nullable,
    default_value: Object.prototype.hasOwnProperty.call(schemaNode, 'default') ? schemaNode.default : null,
    fields: {},
    items: null,
    enum_values: schemaNode.enum || [],
    variants: [],
  };

  if (kind === 'object') {
    var requiredSet = new Set(schemaNode.required || requiredNames || []);
    Object.keys(schemaNode.properties || {}).forEach(function(fieldName) {
      var field = convertJsonSchemaToCrankSchema(schemaNode.properties[fieldName], []);
      field.required = requiredSet.has(fieldName);
      schema.fields[fieldName] = field;
    });
  }

  if (kind === 'array' && schemaNode.items) {
    schema.items = convertJsonSchemaToCrankSchema(schemaNode.items, []);
  }

  return schema;
}

function mappingRootForProtocol(protocol) {
  if (protocol === 'graphql') return '$.request.variables';
  if (protocol === 'grpc') return '$.request.grpc';
  var activeMethod = document.querySelector('.method-card.active');
  var method = activeMethod ? activeMethod.dataset.method : 'POST';
  return method === 'GET' || method === 'DELETE' ? '$.request.query' : '$.request.body';
}

function normalizeInputSource(path) {
  if (typeof path !== 'string') return '$.mcp';
  if (path.indexOf('$.input.') === 0) return '$.mcp.' + path.slice('$.input.'.length);
  if (path === '$.input') return '$.mcp';
  return path;
}

function normalizeOutputSource(path) {
  if (typeof path !== 'string') return '$.response.data';
  return path;
}

function buildMappingSet(rawValue, mode) {
  if (rawValue && Array.isArray(rawValue.rules)) {
    return { rules: rawValue.rules };
  }

  var rules = [];
  if (!rawValue || typeof rawValue !== 'object') {
    return { rules: rules };
  }

  var root = mappingRootForProtocol(wizardProtocol);

  Object.keys(rawValue).forEach(function(key) {
    var source = rawValue[key];
    if (typeof source !== 'string') return;
    if (mode === 'input') {
      rules.push({
        source: normalizeInputSource(source),
        target: root + '.' + key,
        required: false,
      });
      return;
    }

    rules.push({
      source: normalizeOutputSource(source),
      target: '$.output.' + key,
      required: false,
    });
  });

  return { rules: rules };
}

function parseExecutionConfig(text) {
  if (window.CrankStreamingForm && typeof window.CrankStreamingForm.validateStreamingConfig === 'function') {
    var validation = window.CrankStreamingForm.validateStreamingConfig([currentProtocolCapabilities()]);
    if (!validation.valid) {
      throw new Error(validation.errors[0]);
    }
  }
  var value = parseStructuredText(text);
  var retry = value.retry || value.retry_policy || null;
  var headers = value.headers && typeof value.headers === 'object' ? value.headers : {};
  var selectedUpstream = currentUpstream();
  if (selectedUpstream && selectedUpstream.authMode === 'create') {
    throw new Error(tKey('wizard.error.save_upstream_first'));
  }
  var authProfileRef = selectedUpstream && selectedUpstream.authProfileId
    ? selectedUpstream.authProfileId
    : (value.auth && value.auth.profile ? value.auth.profile : null);
  var config = {
    timeout_ms: Number(value.timeout_ms || 10000),
    retry_policy: retry && retry.max_attempts ? { max_attempts: Number(retry.max_attempts) } : null,
    auth_profile_ref: authProfileRef,
    headers: headers,
    protocol_options: null,
  };

  if (wizardProtocol === 'grpc') {
    var useTls = false;
    if (value.protocol_options && value.protocol_options.grpc) {
      useTls = !!value.protocol_options.grpc.use_tls;
    } else if (value.tls) {
      useTls = !!value.tls.verify;
    }
    config.protocol_options = { grpc: { use_tls: useTls } };
  } else if (wizardProtocol === 'websocket') {
    var websocketProtocolOptions = value.protocol_options && value.protocol_options.websocket
      ? value.protocol_options.websocket
      : {};
    var heartbeatInterval = websocketProtocolOptions.heartbeat_interval_ms != null
      ? Number(websocketProtocolOptions.heartbeat_interval_ms)
      : numericValueOrNull('websocket-heartbeat-interval-ms');
    var reconnectAttempts = websocketProtocolOptions.reconnect_max_attempts != null
      ? Number(websocketProtocolOptions.reconnect_max_attempts)
      : numericValueOrNull('websocket-reconnect-max-attempts');
    var reconnectBackoff = websocketProtocolOptions.reconnect_backoff_ms != null
      ? Number(websocketProtocolOptions.reconnect_backoff_ms)
      : numericValueOrNull('websocket-reconnect-backoff-ms');
    var runtimeOptions = {};
    if (Number.isFinite(heartbeatInterval) && heartbeatInterval > 0) {
      runtimeOptions.heartbeat_interval_ms = heartbeatInterval;
    }
    if (Number.isFinite(reconnectAttempts) && reconnectAttempts >= 0) {
      runtimeOptions.reconnect_max_attempts = reconnectAttempts;
    }
    if (Number.isFinite(reconnectBackoff) && reconnectBackoff >= 0) {
      runtimeOptions.reconnect_backoff_ms = reconnectBackoff;
    }
    if (Object.keys(runtimeOptions).length) {
      config.protocol_options = { websocket: runtimeOptions };
    }
  }

  config.streaming = collectStreamingConfig();

  return config;
}

function currentUpstream() {
  var selected = upstreams.find(function(item) { return item.id === selectedUpstreamId; });
  if (selected) return selected;

  var customName = textValue('new-upstream-name');
  var customUrl = textValue('new-upstream-url');
  if (!customUrl && wizardProtocol === 'soap') {
    customUrl = textValue('soap-endpoint-override');
  }
  if (!customUrl) return null;

  var authMode = textValue('new-upstream-auth-mode') || 'none';
  var authProfileId = authMode === 'existing' ? textValue('new-upstream-auth-profile') : null;
  var authProfile = authProfileById(authProfileId);
  return {
    id: 'custom',
    name: customName || 'custom-upstream',
    url: customUrl,
    staticHeaders: textValue('new-upstream-static-headers') || '{\n}',
    authMode: authMode,
    authProfileId: authProfileId || null,
    authProfileName: authProfile ? authProfile.name : '',
    authKind: authProfile ? authProfile.kind : null,
  };
}

function joinUrl(baseUrl, path) {
  if (!baseUrl) return path || '';
  if (!path) return baseUrl;
  return baseUrl.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '');
}

function selectedGraphqlOperationType() {
  var active = document.querySelector('.gql-type-card.active');
  return active ? active.dataset.gqlType : 'query';
}

function graphqlTopLevelField(queryText) {
  var match = queryText.replace(/#[^\n]*/g, '').match(/\{\s*([A-Za-z_][A-Za-z0-9_]*)/);
  return match ? match[1] : null;
}

function graphqlOperationName(queryText) {
  var match = queryText.match(/\b(query|mutation)\s+([A-Za-z_][A-Za-z0-9_]*)/);
  return match ? match[2] : textValue('tool-name');
}

function buildTarget() {
  var upstream = currentUpstream();
  if (!upstream || !upstream.url) {
    throw new Error(tKey('wizard.error.select_upstream'));
  }

  var pathTemplate = textValue('endpoint-path') || '/';

  if (wizardProtocol === 'rest') {
    var activeMethod = document.querySelector('.method-card.active');
    return {
      kind: 'rest',
      base_url: upstream.url,
      method: activeMethod ? activeMethod.dataset.method : 'POST',
      path_template: pathTemplate,
      static_headers: parseHeaderMap(upstream.staticHeaders),
    };
  }

  if (wizardProtocol === 'graphql') {
    var queryTemplate = textValue('gql-query-editor');
    var topField = graphqlTopLevelField(queryTemplate);
    return {
      kind: 'graphql',
      endpoint: joinUrl(upstream.url, pathTemplate),
      operation_type: selectedGraphqlOperationType(),
      operation_name: graphqlOperationName(queryTemplate),
      query_template: queryTemplate,
      response_path: topField ? '$.response.body.data.' + topField : '$.response.body.data',
    };
  }

  if (wizardProtocol === 'soap') {
    var endpointOverride = textValue('soap-endpoint-override') || upstream.url;
    var existingWsdlRef = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'soap'
      ? wizardCurrentVersion.target.wsdl_ref
      : 'sample_wsdl_pending';

    return {
      kind: 'soap',
      wsdl_ref: existingWsdlRef,
      service_name: textValue('soap-service-name') || 'Service',
      port_name: textValue('soap-port-name') || 'Port',
      operation_name: textValue('soap-operation-name') || 'Operation',
      endpoint_override: endpointOverride || null,
      soap_version: textValue('soap-version') || 'soap_11',
      soap_action: textValue('soap-action') || null,
      binding_style: textValue('soap-binding-style') || 'document_literal',
      headers: [],
      fault_contract: null,
      metadata: { input_part_names: [], output_part_names: [], namespaces: [] },
    };
  }

  if (wizardProtocol === 'websocket') {
    return {
      kind: 'websocket',
      url: joinUrl(upstream.url, textValue('websocket-path')),
      subprotocols: textareaLines('websocket-subprotocols'),
      subscribe_message_template: stringValueOrNull('websocket-subscribe-template')
        ? parseStructuredText(textValue('websocket-subscribe-template'))
        : null,
      unsubscribe_message_template: stringValueOrNull('websocket-unsubscribe-template')
        ? parseStructuredText(textValue('websocket-unsubscribe-template'))
        : null,
      static_headers: parseHeaderMap(upstream.staticHeaders),
    };
  }

  var route = textValue('grpc-manual-route') || (document.getElementById('grpc-path-value') ? document.getElementById('grpc-path-value').textContent.trim() : '');
  var descriptorRef = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'grpc'
    ? wizardCurrentVersion.target.descriptor_ref
    : 'desc_pending';
  var descriptorSetB64 = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'grpc'
    ? (wizardCurrentVersion.target.descriptor_set_b64 || '')
    : '';
  var parsedRoute = parseGrpcRoute(route);

  return {
    kind: 'grpc',
    server_addr: upstream.url,
    package: parsedRoute.package,
    service: parsedRoute.service,
    method: parsedRoute.method,
    descriptor_ref: descriptorRef,
    descriptor_set_b64: descriptorSetB64,
  };
}

function parseGrpcRoute(route) {
  var normalized = route.replace(/^\/+/, '');
  var parts = normalized.split('/');
  if (parts.length !== 2) {
    return {
      package: '',
      service: '',
      method: '',
    };
  }

  var servicePart = parts[0];
  var method = parts[1];
  var serviceSegments = servicePart.split('.');
  var service = serviceSegments.pop() || '';
  var packageName = serviceSegments.join('.');

  return {
    package: packageName,
    service: service,
    method: method,
  };
}

function parseHeaderMap(text) {
  if (!text || !text.trim()) return {};
  var value = parseStructuredText(text);
  return value && typeof value === 'object' ? value : {};
}

function numericValueOrNull(id) {
  var value = textValue(id);
  if (!value) return null;
  var parsed = Number(value);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}

function stringValueOrNull(id) {
  var value = textValue(id);
  return value ? value : null;
}

function textareaLines(id) {
  var value = textValue(id);
  if (!value) return [];
  return value
    .split(/\r?\n/)
    .map(function(line) { return line.trim(); })
    .filter(Boolean);
}

function selectedStreamingMode() {
  var element = document.getElementById('streaming-mode');
  return element ? element.value : 'unary';
}

function transportOptionsForMode(mode) {
  var capabilities = currentProtocolCapabilities();
  var behaviors = (capabilities.supports_transport_behaviors || []).slice();
  if (mode === 'unary') return behaviors.filter(function(value) { return value === 'request_response'; });
  if (mode === 'window') return behaviors.filter(function(value) {
    return value === 'request_response' || value === 'server_stream';
  });
  if (mode === 'session') return behaviors.filter(function(value) {
    return value === 'server_stream' || value === 'stateful_session';
  });
  if (mode === 'async_job') return behaviors.filter(function(value) {
    return value === 'deferred_result';
  });
  return behaviors;
}

function modeOptionsForProtocol() {
  var capabilities = currentProtocolCapabilities();
  return (capabilities.supports_execution_modes || ['unary']).filter(function(mode) {
    return transportOptionsForMode(mode).length > 0 || mode === 'unary';
  });
}

function updateStreamingTransportOptions(preferredValue) {
  var element = document.getElementById('streaming-transport-behavior');
  if (!element) return;
  var options = transportOptionsForMode(selectedStreamingMode());
  element.innerHTML = '';
  options.forEach(function(value) {
    var option = document.createElement('option');
    option.value = value;
    option.textContent = tKey('wizard.streaming.transport.' + value);
    element.appendChild(option);
  });
  if (preferredValue && options.indexOf(preferredValue) >= 0) {
    element.value = preferredValue;
  } else if (options.length) {
    element.value = options[0];
  }
}

function updateStreamingModeOptions(preferredValue) {
  var element = document.getElementById('streaming-mode');
  if (!element) return;
  var options = modeOptionsForProtocol();
  element.innerHTML = '';
  options.forEach(function(value) {
    var option = document.createElement('option');
    option.value = value;
    option.textContent = tKey('wizard.streaming.mode.' + value);
    element.appendChild(option);
  });
  if (preferredValue && options.indexOf(preferredValue) >= 0) {
    element.value = preferredValue;
  } else {
    element.value = options.indexOf('unary') >= 0 ? 'unary' : (options[0] || 'unary');
  }
  updateStreamingTransportOptions();
}

function ensureStreamingToolFamilyDefaults(mode) {
  var base = textValue('tool-name') || 'stream_tool';
  if (mode === 'session') {
    if (!textValue('streaming-start-tool-name')) setValue('streaming-start-tool-name', base + '_start');
    if (!textValue('streaming-poll-tool-name')) setValue('streaming-poll-tool-name', base + '_poll');
    if (!textValue('streaming-stop-tool-name')) setValue('streaming-stop-tool-name', base + '_stop');
  }
  if (mode === 'async_job') {
    if (!textValue('streaming-start-tool-name')) setValue('streaming-start-tool-name', base + '_start');
    if (!textValue('streaming-status-tool-name')) setValue('streaming-status-tool-name', base + '_status');
    if (!textValue('streaming-result-tool-name')) setValue('streaming-result-tool-name', base + '_result');
    if (!textValue('streaming-cancel-tool-name')) setValue('streaming-cancel-tool-name', base + '_cancel');
  }
}

function updateStreamingConfigVisibility() {
  var mode = selectedStreamingMode();
  var card = document.getElementById('wizard-streaming-config-card');
  var help = document.getElementById('streaming-mode-help');
  var toolFamily = document.getElementById('streaming-tool-family-block');
  var asyncRow = document.getElementById('streaming-async-tool-family-row');
  if (!card) return;

  var isUnary = mode === 'unary';
  var isWindow = mode === 'window';
  var isSession = mode === 'session';
  var isAsyncJob = mode === 'async_job';

  [
    'streaming-window-duration-ms',
    'streaming-poll-interval-ms',
    'streaming-upstream-timeout-ms',
    'streaming-idle-timeout-ms',
    'streaming-session-lifetime-ms',
    'streaming-max-items',
    'streaming-max-bytes',
    'streaming-max-field-length',
    'streaming-sampling-rate',
    'streaming-items-path',
    'streaming-summary-path',
    'streaming-done-path',
    'streaming-cursor-path',
    'streaming-status-path',
    'streaming-redacted-paths',
    'streaming-truncate-item-fields',
    'streaming-drop-duplicates'
  ].forEach(function(id) {
    var node = document.getElementById(id);
    if (!node) return;
    var group = node.closest('.form-group') || node.closest('.checkbox-pill');
    if (group) {
      group.style.display = isUnary ? 'none' : '';
    }
  });

  if (help) {
    help.textContent = isUnary
      ? tKey('wizard.streaming.help.unary')
      : (isSession
        ? tKey('wizard.streaming.help.session')
        : (isAsyncJob
          ? tKey('wizard.streaming.help.async_job')
          : tKey('wizard.streaming.help.window')));
  }

  if (toolFamily) toolFamily.style.display = (isSession || isAsyncJob) ? '' : 'none';
  if (asyncRow) asyncRow.style.display = isAsyncJob ? '' : 'none';

  ensureStreamingToolFamilyDefaults(mode);
}

function bindStreamingConfigControls() {
  updateStreamingModeOptions();
  updateStreamingConfigVisibility();

  var mode = document.getElementById('streaming-mode');
  if (mode) {
    mode.addEventListener('change', function() {
      updateStreamingTransportOptions();
      updateStreamingConfigVisibility();
    });
  }
}

function collectStreamingConfig() {
  var mode = selectedStreamingMode();
  if (mode === 'unary') return null;

  var config = {
    mode: mode,
    transport_behavior: stringValueOrNull('streaming-transport-behavior') || 'request_response',
    window_duration_ms: numericValueOrNull('streaming-window-duration-ms'),
    poll_interval_ms: numericValueOrNull('streaming-poll-interval-ms'),
    upstream_timeout_ms: numericValueOrNull('streaming-upstream-timeout-ms'),
    idle_timeout_ms: numericValueOrNull('streaming-idle-timeout-ms'),
    max_session_lifetime_ms: numericValueOrNull('streaming-session-lifetime-ms'),
    max_items: numericValueOrNull('streaming-max-items'),
    max_bytes: numericValueOrNull('streaming-max-bytes'),
    aggregation_mode: stringValueOrNull('streaming-aggregation-mode') || 'summary_plus_samples',
    summary_path: stringValueOrNull('streaming-summary-path'),
    items_path: stringValueOrNull('streaming-items-path'),
    cursor_path: stringValueOrNull('streaming-cursor-path'),
    status_path: stringValueOrNull('streaming-status-path'),
    done_path: stringValueOrNull('streaming-done-path'),
    redacted_paths: textareaLines('streaming-redacted-paths'),
    truncate_item_fields: !!document.getElementById('streaming-truncate-item-fields').checked,
    max_field_length: numericValueOrNull('streaming-max-field-length'),
    drop_duplicates: !!document.getElementById('streaming-drop-duplicates').checked,
    sampling_rate: (function() {
      var value = textValue('streaming-sampling-rate');
      if (!value) return null;
      var parsed = Number(value);
      return Number.isFinite(parsed) ? parsed : null;
    }()),
    tool_family: {},
  };

  if (mode === 'session') {
    config.tool_family.start_tool_name = stringValueOrNull('streaming-start-tool-name');
    config.tool_family.poll_tool_name = stringValueOrNull('streaming-poll-tool-name');
    config.tool_family.stop_tool_name = stringValueOrNull('streaming-stop-tool-name');
  } else if (mode === 'async_job') {
    config.tool_family.start_tool_name = stringValueOrNull('streaming-start-tool-name');
    config.tool_family.status_tool_name = stringValueOrNull('streaming-status-tool-name');
    config.tool_family.result_tool_name = stringValueOrNull('streaming-result-tool-name');
    config.tool_family.cancel_tool_name = stringValueOrNull('streaming-cancel-tool-name');
  }

  Object.keys(config).forEach(function(key) {
    if (config[key] === null) delete config[key];
  });
  if (!config.redacted_paths.length) delete config.redacted_paths;

  return config;
}

function applyStreamingConfig(streaming) {
  updateStreamingModeOptions(streaming && streaming.mode ? streaming.mode : 'unary');
  if (!streaming) {
    updateStreamingConfigVisibility();
    return;
  }

  setValue('streaming-window-duration-ms', streaming.window_duration_ms || '');
  setValue('streaming-poll-interval-ms', streaming.poll_interval_ms || '');
  setValue('streaming-upstream-timeout-ms', streaming.upstream_timeout_ms || '');
  setValue('streaming-idle-timeout-ms', streaming.idle_timeout_ms || '');
  setValue('streaming-session-lifetime-ms', streaming.max_session_lifetime_ms || '');
  setValue('streaming-max-items', streaming.max_items || '');
  setValue('streaming-max-bytes', streaming.max_bytes || '');
  setValue('streaming-max-field-length', streaming.max_field_length || '');
  setValue('streaming-sampling-rate', streaming.sampling_rate || '');
  setValue('streaming-items-path', streaming.items_path || '');
  setValue('streaming-summary-path', streaming.summary_path || '');
  setValue('streaming-done-path', streaming.done_path || '');
  setValue('streaming-cursor-path', streaming.cursor_path || '');
  setValue('streaming-status-path', streaming.status_path || '');
  setValue('streaming-redacted-paths', (streaming.redacted_paths || []).join('\n'));
  document.getElementById('streaming-truncate-item-fields').checked = !!streaming.truncate_item_fields;
  document.getElementById('streaming-drop-duplicates').checked = !!streaming.drop_duplicates;
  updateStreamingTransportOptions(streaming.transport_behavior);
  setValue('streaming-start-tool-name', streaming.tool_family && streaming.tool_family.start_tool_name ? streaming.tool_family.start_tool_name : '');
  setValue('streaming-poll-tool-name', streaming.tool_family && streaming.tool_family.poll_tool_name ? streaming.tool_family.poll_tool_name : '');
  setValue('streaming-stop-tool-name', streaming.tool_family && streaming.tool_family.stop_tool_name ? streaming.tool_family.stop_tool_name : '');
  setValue('streaming-status-tool-name', streaming.tool_family && streaming.tool_family.status_tool_name ? streaming.tool_family.status_tool_name : '');
  setValue('streaming-result-tool-name', streaming.tool_family && streaming.tool_family.result_tool_name ? streaming.tool_family.result_tool_name : '');
  setValue('streaming-cancel-tool-name', streaming.tool_family && streaming.tool_family.cancel_tool_name ? streaming.tool_family.cancel_tool_name : '');
  var aggregation = document.getElementById('streaming-aggregation-mode');
  if (aggregation && streaming.aggregation_mode) aggregation.value = streaming.aggregation_mode;
  updateStreamingConfigVisibility();
}

function buildToolDescription() {
  return {
    title: textValue('tool-title') || textValue('tool-display-name') || textValue('tool-name'),
    description: textValue('tool-description'),
    tags: [],
    examples: [],
  };
}

function collectWizardPayload() {
  var name = textValue('tool-name');
  if (!name) throw new Error(tKey('wizard.error.tool_name'));

  var inputSchemaValue = parseStructuredText(textValue('tool-input-schema'));
  var outputSchemaValue = parseStructuredText(textValue('tool-output-schema'));
  var inputMappingValue = parseStructuredText(textValue('tool-input-mapping'));
  var outputMappingValue = parseStructuredText(textValue('tool-output-mapping'));

  return {
    name: name,
    display_name: textValue('tool-display-name') || name,
    category: 'general',
    protocol: wizardProtocol,
    target: buildTarget(),
    input_schema: convertJsonSchemaToCrankSchema(inputSchemaValue, []),
    output_schema: convertJsonSchemaToCrankSchema(outputSchemaValue, []),
    input_mapping: buildMappingSet(inputMappingValue, 'input'),
    output_mapping: buildMappingSet(outputMappingValue, 'output'),
    execution_config: parseExecutionConfig(textValue('tool-exec-config')),
    tool_description: buildToolDescription(),
  };
}

async function saveOperation(stayOnPage) {
  try {
    await persistCurrentDraft(stayOnPage);
  } catch (error) {
    if (window.CrankUi) {
      window.CrankUi.error(error.message || tKey('wizard.error.save'), tKey('wizard.error.save_title'));
    }
  }
}

async function loadOperationForEdit() {
  if (!wizardWorkspaceId || !wizardEditId) return;
  var detail = await window.CrankApi.getOperation(wizardWorkspaceId, wizardEditId);
  wizardProtocol = detail.protocol || 'rest';
  await loadWizardPanels([3]);
  var draftVersion = await window.CrankApi.getOperationVersion(
    wizardWorkspaceId,
    wizardEditId,
    detail.draft_version_ref.version
  );
  wizardCurrentOperation = detail;
  wizardCurrentVersion = draftVersion;
  prefillWizardFromEdit(detail, draftVersion);
}

function setEditModePresentation() {
  var progressLabel = document.querySelector('.progress-label');
  if (progressLabel) progressLabel.textContent = tKey('wizard.progress.edit');
  var sidebarBrand = document.querySelector('.step-sidebar-brand');
  if (sidebarBrand) sidebarBrand.innerHTML = tKey('wizard.sidebar.brand.edit');
}

function selectProtocol(protocol) {
  wizardProtocol = protocol || 'rest';
  document.querySelectorAll('.protocol-card').forEach(function(card) {
    var proto = card.dataset.protocol
      || (card.classList.contains('rest')
        ? 'rest'
        : card.classList.contains('graphql')
          ? 'graphql'
          : card.classList.contains('grpc')
            ? 'grpc'
            : card.classList.contains('websocket')
              ? 'websocket'
            : card.classList.contains('soap')
              ? 'soap'
              : 'rest');
    var selected = proto === wizardProtocol;
    card.classList.toggle('selected', selected);
    card.setAttribute('aria-checked', selected ? 'true' : 'false');
  });
  var s3name = document.getElementById('sidebar-step-3-name');
  if (s3name) {
    var step3Labels = window.CrankWizardState.step3Labels();
    s3name.textContent = step3Labels[wizardProtocol] || tKey('wizard.step3.rest.label');
  }
  updateWizardProtocolVisibility();
}

function prefillUpstream(baseUrl, headers, authProfileRef) {
  var known = upstreams.find(function(item) {
    return item.url === baseUrl && (item.authProfileId || null) === (authProfileRef || null);
  });
  if (known) {
    pickUpstream(known.id);
    return;
  }

  selectedUpstreamId = null;
  var trigger = document.getElementById('upstream-new-trigger');
  var form = document.getElementById('upstream-new-form');
  if (trigger) trigger.classList.add('active');
  if (form) form.style.display = '';
  setValue('new-upstream-name', 'imported-upstream');
  setValue('new-upstream-url', baseUrl);
  setValue('new-upstream-static-headers', headers && Object.keys(headers).length ? JSON.stringify(headers, null, 2) : '{\n}');
  setValue('new-upstream-auth-mode', authProfileRef ? 'existing' : 'none');
  setValue('new-upstream-auth-profile', authProfileRef || '');
  updateUpstreamAuthUi();

  var valueEl = document.getElementById('upstream-combobox-value');
  if (valueEl) {
    valueEl.innerHTML = '';
    valueEl.appendChild(buildUpstreamTriggerContent('imported-upstream', baseUrl));
  }
}

function crankSchemaToJsonSchema(schema) {
  var result = {};
  var typeName = schema.type === 'enum' ? 'string' : schema.type;
  result.type = schema.nullable ? [typeName, 'null'] : typeName;
  if (schema.description) result.description = schema.description;
  if (schema.default_value !== null && schema.default_value !== undefined) result.default = schema.default_value;
  if (schema.enum_values && schema.enum_values.length) result.enum = schema.enum_values.slice();
  if (schema.type === 'object') {
    result.type = schema.nullable ? ['object', 'null'] : 'object';
    result.properties = {};
    var required = [];
    Object.keys(schema.fields || {}).forEach(function(fieldName) {
      var field = schema.fields[fieldName];
      result.properties[fieldName] = crankSchemaToJsonSchema(field);
      if (field.required) required.push(fieldName);
    });
    if (required.length) result.required = required;
    result.additionalProperties = false;
  }
  if (schema.type === 'array' && schema.items) {
    result.items = crankSchemaToJsonSchema(schema.items);
  }
  return result;
}

function mappingSetToEditorValue(mappingSet, mode, protocol) {
  var rules = mappingSet && mappingSet.rules ? mappingSet.rules : [];
  var object = {};
  rules.forEach(function(rule) {
    if (mode === 'input') {
      var key = rule.target.replace(mappingRootForProtocol(protocol) + '.', '');
      object[key] = rule.source.replace('$.mcp.', '$.input.');
      return;
    }
    var outputKey = rule.target.replace('$.output.', '');
    object[outputKey] = rule.source;
  });
  return window.jsyaml ? window.jsyaml.dump(object, { lineWidth: -1 }) : JSON.stringify(object, null, 2);
}

function executionConfigToEditorValue(config) {
  var value = {
    timeout_ms: config.timeout_ms,
  };
  if (config.retry_policy) {
    value.retry = { max_attempts: config.retry_policy.max_attempts };
  }
  if (config.auth_profile_ref) {
    value.auth = { profile: config.auth_profile_ref };
  }
  if (config.headers && Object.keys(config.headers).length) {
    value.headers = config.headers;
  }
  if (config.protocol_options && config.protocol_options.grpc) {
    value.tls = { verify: !!config.protocol_options.grpc.use_tls };
  }
  if (config.protocol_options && config.protocol_options.websocket) {
    value.protocol_options = value.protocol_options || {};
    value.protocol_options.websocket = {};
    if (config.protocol_options.websocket.heartbeat_interval_ms != null) {
      value.protocol_options.websocket.heartbeat_interval_ms = config.protocol_options.websocket.heartbeat_interval_ms;
    }
    if (config.protocol_options.websocket.reconnect_max_attempts != null) {
      value.protocol_options.websocket.reconnect_max_attempts = config.protocol_options.websocket.reconnect_max_attempts;
    }
    if (config.protocol_options.websocket.reconnect_backoff_ms != null) {
      value.protocol_options.websocket.reconnect_backoff_ms = config.protocol_options.websocket.reconnect_backoff_ms;
    }
    if (!Object.keys(value.protocol_options.websocket).length) {
      delete value.protocol_options.websocket;
    }
    if (!Object.keys(value.protocol_options).length) {
      delete value.protocol_options;
    }
  }
  return window.jsyaml ? window.jsyaml.dump(value, { lineWidth: -1 }) : JSON.stringify(value, null, 2);
}

function prefillWizardFromEdit(detail, versionDocument) {
  if (!detail || !versionDocument) return;

  setEditModePresentation();
  selectProtocol(detail.protocol);

  var target = versionDocument.target || {};
  if (target.kind === 'rest') {
    prefillUpstream(target.base_url, target.static_headers, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null);
    setValue('endpoint-path', target.path_template);
    document.querySelectorAll('.method-card').forEach(function(card) {
      card.classList.toggle('active', card.dataset.method === target.method);
    });
  } else if (target.kind === 'graphql') {
    var endpoint = target.endpoint || '';
    var parsedEndpoint = new URL(endpoint, window.location.origin);
    prefillUpstream(parsedEndpoint.origin, {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null);
    setValue('endpoint-path', parsedEndpoint.pathname + parsedEndpoint.search);
    setValue('gql-query-editor', target.query_template);
    document.querySelectorAll('.gql-type-card').forEach(function(card) {
      card.classList.toggle('active', card.dataset.gqlType === target.operation_type);
    });
  } else if (target.kind === 'grpc') {
    prefillUpstream(target.server_addr, {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null);
    var route = '/' + (target.package ? target.package + '.' : '') + target.service + '/' + target.method;
    setValue('grpc-manual-route', route);
    setValue('grpc-manual-req-type', '');
    setValue('grpc-manual-res-type', '');
    grpcManualRouteInput(route);
    grpcManualTypeInput();
  } else if (target.kind === 'soap') {
    prefillUpstream(
      target.endpoint_override || '',
      {},
      versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null
    );
    setValue('soap-service-name', target.service_name || '');
    setValue('soap-port-name', target.port_name || '');
    setValue('soap-operation-name', target.operation_name || '');
    setValue('soap-endpoint-override', target.endpoint_override || '');
    setValue('soap-action', target.soap_action || '');
    setValue('soap-version', target.soap_version || 'soap_11');
    setValue('soap-binding-style', target.binding_style || 'document_literal');
  } else if (target.kind === 'websocket') {
    prefillUpstream(
      target.url,
      target.static_headers || {},
      versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null
    );
    setValue('websocket-path', '');
    setValue('websocket-subprotocols', (target.subprotocols || []).join('\n'));
    setValue(
      'websocket-subscribe-template',
      target.subscribe_message_template ? JSON.stringify(target.subscribe_message_template, null, 2) : ''
    );
    setValue(
      'websocket-unsubscribe-template',
      target.unsubscribe_message_template ? JSON.stringify(target.unsubscribe_message_template, null, 2) : ''
    );
    var websocketOptions = versionDocument.execution_config
      && versionDocument.execution_config.protocol_options
      && versionDocument.execution_config.protocol_options.websocket
      ? versionDocument.execution_config.protocol_options.websocket
      : null;
    setValue('websocket-heartbeat-interval-ms', websocketOptions && websocketOptions.heartbeat_interval_ms ? websocketOptions.heartbeat_interval_ms : '');
    setValue('websocket-reconnect-max-attempts', websocketOptions && websocketOptions.reconnect_max_attempts != null ? websocketOptions.reconnect_max_attempts : '');
    setValue('websocket-reconnect-backoff-ms', websocketOptions && websocketOptions.reconnect_backoff_ms ? websocketOptions.reconnect_backoff_ms : '');
  }

  setValue('tool-name', detail.name);
  setValue('tool-display-name', detail.display_name);
  setValue('tool-title', versionDocument.tool_description ? versionDocument.tool_description.title : detail.display_name);
  setValue('tool-description', versionDocument.tool_description ? versionDocument.tool_description.description : '');
  setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(versionDocument.input_schema), null, 2));
  setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(versionDocument.output_schema), null, 2));
  setValue('tool-input-mapping', mappingSetToEditorValue(versionDocument.input_mapping, 'input', detail.protocol));
  setValue('tool-output-mapping', mappingSetToEditorValue(versionDocument.output_mapping, 'output', detail.protocol));
  setValue('tool-exec-config', executionConfigToEditorValue(versionDocument.execution_config));
  applyStreamingConfig(versionDocument.execution_config ? versionDocument.execution_config.streaming : null);

  var toolNameInput = document.getElementById('tool-name');
  if (toolNameInput) toolNameInput.disabled = true;
  updateWizardProtocolVisibility();
}

function bindWizardLiveActions() {
  bindLiveAction('wizard-upload-input-sample', tKey('wizard.busy.input_sample'), uploadInputSampleFromWizard);
  bindLiveAction('wizard-upload-output-sample', tKey('wizard.busy.output_sample'), uploadOutputSampleFromWizard);
  bindLiveAction('wizard-generate-draft', tKey('wizard.busy.generate'), generateDraftFromWizard);
  bindLiveAction('wizard-run-test', tKey('wizard.busy.test'), runWizardTest);
  bindClick('wizard-copy-test-response', copyTestResponseToOutputSample);
  bindLiveAction('wizard-export-yaml', tKey('wizard.busy.export_yaml'), exportWizardYaml);
  bindLiveAction('wizard-import-yaml', tKey('wizard.busy.import_yaml'), importWizardYaml);
  bindLiveAction('wizard-publish-operation', tKey('wizard.busy.publish'), publishWizardOperation);
  bindClick('wizard-select-descriptor-set', function() {
    var input = document.getElementById('wizard-descriptor-set-input');
    if (input) input.click();
  });
  bindLiveAction('wizard-upload-descriptor-set', tKey('wizard.busy.descriptor'), uploadDescriptorSetAndDiscover);
  bindClick('wizard-select-soap-wsdl', function() {
    var input = document.getElementById('wizard-soap-wsdl-input');
    if (input) input.click();
  });
  bindClick('wizard-select-soap-xsd', function() {
    var input = document.getElementById('wizard-soap-xsd-input');
    if (input) input.click();
  });
  bindLiveAction('wizard-upload-soap-contracts', tKey('wizard.busy.wsdl'), uploadSoapArtifactsAndInspect);
  bindClick('wizard-import-yaml-file-trigger', function() {
    var input = document.getElementById('wizard-import-yaml-file');
    if (input) input.click();
  });

  var descriptorInput = document.getElementById('wizard-descriptor-set-input');
  if (descriptorInput) descriptorInput.addEventListener('change', handleDescriptorSetSelection);
  var soapWsdlInput = document.getElementById('wizard-soap-wsdl-input');
  if (soapWsdlInput) soapWsdlInput.addEventListener('change', handleSoapWsdlSelection);
  var soapXsdInput = document.getElementById('wizard-soap-xsd-input');
  if (soapXsdInput) soapXsdInput.addEventListener('change', handleSoapXsdSelection);

  var yamlInput = document.getElementById('wizard-import-yaml-file');
  if (yamlInput) yamlInput.addEventListener('change', handleImportYamlFileSelection);
}

function bindClick(id, handler) {
  var element = document.getElementById(id);
  if (!element) return;
  element.addEventListener('click', function(event) {
    event.preventDefault();
    handler();
  });
}

function bindLiveAction(id, busyLabel, handler) {
  var element = document.getElementById(id);
  if (!element) return;
  element.addEventListener('click', function(event) {
    event.preventDefault();
    runWizardLiveAction(element, busyLabel, handler);
  });
}

async function runWizardLiveAction(button, busyLabel, handler) {
  if (!button || button.dataset.busy === 'true') {
    return;
  }

  var originalLabel = button.textContent;
  button.dataset.busy = 'true';
  button.disabled = true;
  button.classList.add('is-busy');
  button.textContent = busyLabel;

  try {
    await handler();
  } catch (error) {
    showWizardLiveStatus(
      tKey('wizard.live.failed_title'),
      error && error.message ? error.message : tKey('wizard.live.failed_body'),
      true
    );
    if (window.CrankUi) {
      window.CrankUi.error(
        error && error.message ? error.message : tKey('wizard.live.failed_body'),
        tKey('wizard.live.failed_title')
      );
    }
  } finally {
    button.dataset.busy = 'false';
    button.disabled = false;
    button.classList.remove('is-busy');
    button.textContent = originalLabel;
  }
}

function updateWizardProtocolVisibility() {
  var grpcTools = document.getElementById('wizard-grpc-live-tools');
  if (grpcTools) grpcTools.style.display = wizardProtocol === 'grpc' ? '' : 'none';
  updateStreamingModeOptions();
  updateStreamingConfigVisibility();
}

function showWizardLiveStatus(title, text, isError) {
  var root = document.getElementById('wizard-live-status');
  var titleEl = document.getElementById('wizard-live-status-title');
  var textEl = document.getElementById('wizard-live-status-text');
  if (!root || !titleEl || !textEl) return;
  titleEl.textContent = title;
  textEl.textContent = text;
  root.style.display = '';
  root.classList.toggle('is-error', !!isError);
  root.classList.toggle('is-success', !isError);
}

function currentDraftVersion() {
  if (wizardCurrentOperation && wizardCurrentOperation.draft_version_ref) {
    return wizardCurrentOperation.draft_version_ref.version;
  }
  if (wizardCurrentOperation && wizardCurrentOperation.current_draft_version) {
    return wizardCurrentOperation.current_draft_version;
  }
  if (wizardCurrentVersion && wizardCurrentVersion.version) {
    return wizardCurrentVersion.version;
  }
  return 1;
}

function updateOperationPayload(payload) {
  return {
    display_name: payload.display_name,
    category: payload.category,
    target: payload.target,
    input_schema: payload.input_schema,
    output_schema: payload.output_schema,
    input_mapping: payload.input_mapping,
    output_mapping: payload.output_mapping,
    execution_config: payload.execution_config,
    tool_description: payload.tool_description,
  };
}

async function refreshCurrentOperationState() {
  if (!wizardWorkspaceId || !wizardEditId) return null;
  var detail = await window.CrankApi.getOperation(wizardWorkspaceId, wizardEditId);
  var versionNumber = detail.draft_version_ref
    ? detail.draft_version_ref.version
    : detail.current_draft_version;
  var versionDocument = await window.CrankApi.getOperationVersion(
    wizardWorkspaceId,
    wizardEditId,
    versionNumber
  );
  wizardCurrentOperation = detail;
  wizardCurrentVersion = versionDocument;
  updateWizardProtocolVisibility();
  return {
    detail: detail,
    version: versionDocument,
  };
}

async function uploadPendingGrpcArtifacts(payload) {
  if (wizardProtocol !== 'grpc' || !wizardEditId) return;

  var descriptorResponse = null;

  if (wizardProtoUpload) {
    descriptorResponse = await window.CrankApi.uploadProtoFile(
      wizardWorkspaceId,
      wizardEditId,
      new TextEncoder().encode(wizardProtoUpload.content),
      wizardProtoUpload.fileName
    );
    wizardProtoUpload = null;
  }

  if (wizardDescriptorSetUpload) {
    descriptorResponse = await window.CrankApi.uploadDescriptorSet(
      wizardWorkspaceId,
      wizardEditId,
      wizardDescriptorSetUpload.bytes,
      wizardDescriptorSetUpload.fileName
    );
    wizardDescriptorSetUpload = null;
    var descriptorName = document.getElementById('wizard-descriptor-set-name');
    if (descriptorName) descriptorName.textContent = tKey('wizard.descriptor.uploaded');
  }

  if (!descriptorResponse) return;

  payload.target.descriptor_ref = descriptorResponse.descriptor_id;
  payload.target.descriptor_set_b64 = '';
  await window.CrankApi.updateOperation(
    wizardWorkspaceId,
    wizardEditId,
    updateOperationPayload(payload)
  );
}

async function uploadPendingSoapArtifacts(payload) {
  if (wizardProtocol !== 'soap' || !wizardEditId) return;

  var wsdlResponse = null;
  if (wizardSoapWsdlUpload) {
    wsdlResponse = await window.CrankApi.uploadWsdlFile(
      wizardWorkspaceId,
      wizardEditId,
      wizardSoapWsdlUpload.bytes,
      wizardSoapWsdlUpload.fileName
    );
    wizardSoapWsdlUpload = null;
    var wsdlName = document.getElementById('wizard-soap-wsdl-name');
    if (wsdlName) wsdlName.textContent = tKey('wizard.step3.soap.wsdl_uploaded');
  }

  if (wizardSoapXsdUpload) {
    await window.CrankApi.uploadXsdFile(
      wizardWorkspaceId,
      wizardEditId,
      wizardSoapXsdUpload.bytes,
      wizardSoapXsdUpload.fileName
    );
    wizardSoapXsdUpload = null;
    var xsdName = document.getElementById('wizard-soap-xsd-name');
    if (xsdName) xsdName.textContent = tKey('wizard.step3.soap.xsd_uploaded');
  }

  if (!wsdlResponse) return;

  payload.target.wsdl_ref = wsdlResponse.descriptor_id;
  await window.CrankApi.updateOperation(
    wizardWorkspaceId,
    wizardEditId,
    updateOperationPayload(payload)
  );
}

async function persistCurrentDraft(stayOnPage) {
  if (!wizardWorkspaceId) {
    throw new Error(tKey('wizard.error.no_workspace'));
  }

  var payload = collectWizardPayload();
  var response;
  var createdNow = false;

  if (wizardMode === 'edit' && wizardEditId) {
    response = await window.CrankApi.updateOperation(
      wizardWorkspaceId,
      wizardEditId,
      updateOperationPayload(payload)
    );
  } else {
    response = await window.CrankApi.createOperation(wizardWorkspaceId, payload);
    createdNow = true;
    wizardEditId = response.operation_id;
    wizardMode = 'edit';
    history.replaceState(
      {},
      '',
      window.location.pathname + '?mode=edit&operationId=' + encodeURIComponent(wizardEditId)
    );
    setEditModePresentation();
    _doGoToStep(currentStep);
    var toolNameInput = document.getElementById('tool-name');
    if (toolNameInput) toolNameInput.disabled = true;
  }

  await uploadPendingGrpcArtifacts(payload);
  await uploadPendingSoapArtifacts(payload);
  await refreshCurrentOperationState();

  if (stayOnPage) {
    var saveDraftBtn = document.querySelector('.btn-save-draft');
    if (saveDraftBtn) {
      var label = saveDraftBtn.textContent;
      saveDraftBtn.textContent = tKey('wizard.save.saved');
      setTimeout(function() {
        saveDraftBtn.textContent = label;
      }, 1400);
    }
  }

  showWizardLiveStatus(
    createdNow ? tKey('wizard.save.created') : tKey('wizard.save.updated'),
    tKey('wizard.save.body')
  );
  return response;
}

function safeStringify(value) {
  if (value === null || value === undefined) return '';
  if (typeof value === 'string') return value;
  return JSON.stringify(value, null, 2);
}

function setTextareaValue(id, value) {
  var element = document.getElementById(id);
  if (!element) return;
  element.value = safeStringify(value);
}

function describeWizardTestResult(result) {
  if (result.stream_session && result.stream_session.session_id) {
    return {
      title: tKey('wizard.test.session_started'),
      body: tfKey('wizard.test.session_started_body', {
        session_id: result.stream_session.session_id,
        poll_after_ms: result.stream_session.poll_after_ms
      }),
      isError: false,
    };
  }

  if (result.async_job && result.async_job.job_id) {
    return {
      title: tKey('wizard.test.async_job_started'),
      body: tfKey('wizard.test.async_job_started_body', {
        job_id: result.async_job.job_id
      }),
      isError: false,
    };
  }

  if (result.window) {
    var bodyParts = [
      tKey('wizard.test.window_completed_body')
    ];
    if (result.window.truncated) {
      bodyParts.push(tKey('wizard.test.window_truncated_note'));
    }
    if (result.window.has_more) {
      bodyParts.push(tKey('wizard.test.window_more_note'));
    }
    if (!result.window.window_complete) {
      bodyParts.push(tKey('wizard.test.window_incomplete_note'));
    }
    return {
      title: result.ok ? tKey('wizard.test.window_completed') : tKey('wizard.test.failed'),
      body: bodyParts.join(' '),
      isError: !result.ok,
    };
  }

  return {
    title: result.ok ? tKey('wizard.test.completed') : tKey('wizard.test.failed'),
    body: result.ok ? tKey('wizard.test.completed_body') : tKey('wizard.test.failed_body'),
    isError: !result.ok,
  };
}

async function uploadInputSampleFromWizard() {
  await persistCurrentDraft(true);
  await window.CrankApi.uploadInputSample(
    wizardWorkspaceId,
    wizardEditId,
    parseStructuredText(textValue('wizard-input-sample'))
  );
  showWizardLiveStatus(tKey('wizard.sample.input_saved'), tKey('wizard.sample.input_saved_body'));
}

async function uploadOutputSampleFromWizard() {
  await persistCurrentDraft(true);
  await window.CrankApi.uploadOutputSample(
    wizardWorkspaceId,
    wizardEditId,
    parseStructuredText(textValue('wizard-output-sample'))
  );
  showWizardLiveStatus(tKey('wizard.sample.output_saved'), tKey('wizard.sample.output_saved_body'));
}

async function generateDraftFromWizard() {
  await persistCurrentDraft(true);
  var generated = await window.CrankApi.generateDraft(wizardWorkspaceId, wizardEditId, {});
  setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(generated.input_schema), null, 2));
  setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(generated.output_schema), null, 2));
  setValue('tool-input-mapping', mappingSetToEditorValue(generated.input_mapping, 'input', wizardProtocol));
  setValue('tool-output-mapping', mappingSetToEditorValue(generated.output_mapping, 'output', wizardProtocol));
  showWizardLiveStatus(tKey('wizard.sample.generated'), tKey('wizard.sample.generated_body'));
}

async function runWizardTest() {
  await persistCurrentDraft(true);
  var result = await window.CrankApi.runOperationTest(wizardWorkspaceId, wizardEditId, {
    version: currentDraftVersion(),
    input: parseStructuredText(textValue('wizard-test-input')),
  });
  wizardTestResponsePreview = result.response_preview;
  setTextareaValue('wizard-test-request-preview', result.request_preview);
  setTextareaValue('wizard-test-response-preview', result.response_preview);
  setTextareaValue('wizard-test-errors', result.errors && result.errors.length ? result.errors : []);
  var status = describeWizardTestResult(result);
  showWizardLiveStatus(
    status.title,
    status.body,
    status.isError
  );
}

function copyTestResponseToOutputSample() {
  if (!wizardTestResponsePreview) {
    showWizardLiveStatus(tKey('wizard.test.no_response'), tKey('wizard.test.no_response_body'), true);
    return;
  }
  setTextareaValue('wizard-output-sample', wizardTestResponsePreview);
  showWizardLiveStatus(tKey('wizard.test.response_copied'), tKey('wizard.test.response_copied_body'));
}

function downloadTextFile(fileName, content, mimeType) {
  var blob = new Blob([content], { type: mimeType || 'text/plain;charset=utf-8' });
  var url = URL.createObjectURL(blob);
  var link = document.createElement('a');
  link.href = url;
  link.download = fileName;
  link.click();
  URL.revokeObjectURL(url);
}

async function exportWizardYaml() {
  await persistCurrentDraft(true);
  var yaml = await window.CrankApi.exportOperation(wizardWorkspaceId, wizardEditId, {
    mode: 'portable',
    version: currentDraftVersion(),
  });
  downloadTextFile((textValue('tool-name') || 'operation') + '.yaml', yaml, 'application/yaml');
  showWizardLiveStatus(tKey('wizard.yaml.exported'), tKey('wizard.yaml.exported_body'));
}

async function importWizardYaml() {
  if (!wizardWorkspaceId) {
    throw new Error(tKey('wizard.error.no_workspace'));
  }
  var yamlDocument = textValue('wizard-import-yaml-text');
  if (!yamlDocument) {
    showWizardLiveStatus(tKey('wizard.yaml.none'), tKey('wizard.yaml.none_body'), true);
    return;
  }
  var imported = await window.CrankApi.importOperation(wizardWorkspaceId, yamlDocument, 'upsert');
  showWizardLiveStatus(tKey('wizard.yaml.imported'), tKey('wizard.yaml.imported_body'));
  window.location.href = window.location.pathname + '?mode=edit&operationId=' + encodeURIComponent(imported.operation_id);
}

async function publishWizardOperation() {
  await persistCurrentDraft(true);
  var published = await window.CrankApi.publishOperation(
    wizardWorkspaceId,
    wizardEditId,
    currentDraftVersion()
  );
  await refreshCurrentOperationState();
  showWizardLiveStatus(
    tKey('wizard.publish.done'),
    tfKey('wizard.publish.done_body', { version: published.published_version })
  );
}

function handleImportYamlFileSelection(event) {
  var file = event.target.files && event.target.files[0];
  if (!file) return;
  var reader = new FileReader();
  reader.onload = function(loadEvent) {
    setValue('wizard-import-yaml-text', loadEvent.target.result || '');
    showWizardLiveStatus(tKey('wizard.yaml.loaded'), tKey('wizard.yaml.loaded_body'));
  };
  reader.readAsText(file);
}

function handleDescriptorSetSelection(event) {
  var file = event.target.files && event.target.files[0];
  if (!file) return;
  var reader = new FileReader();
  reader.onload = function(loadEvent) {
    wizardDescriptorSetUpload = {
      fileName: file.name,
      bytes: new Uint8Array(loadEvent.target.result),
    };
    var fileNameEl = document.getElementById('wizard-descriptor-set-name');
    if (fileNameEl) fileNameEl.textContent = file.name;
    showWizardLiveStatus(tKey('wizard.descriptor.selected'), tKey('wizard.descriptor.selected_body'));
  };
  reader.readAsArrayBuffer(file);
}

function handleSoapWsdlSelection(event) {
  var file = event.target.files && event.target.files[0];
  if (!file) return;
  var reader = new FileReader();
  reader.onload = function(loadEvent) {
    wizardSoapWsdlUpload = {
      fileName: file.name,
      bytes: new Uint8Array(loadEvent.target.result),
    };
    var fileNameEl = document.getElementById('wizard-soap-wsdl-name');
    if (fileNameEl) fileNameEl.textContent = file.name;
    showWizardLiveStatus(tKey('wizard.step3.soap.wsdl_selected'), tKey('wizard.step3.soap.wsdl_selected_body'));
  };
  reader.readAsArrayBuffer(file);
}

function handleSoapXsdSelection(event) {
  var file = event.target.files && event.target.files[0];
  if (!file) return;
  var reader = new FileReader();
  reader.onload = function(loadEvent) {
    wizardSoapXsdUpload = {
      fileName: file.name,
      bytes: new Uint8Array(loadEvent.target.result),
    };
    var fileNameEl = document.getElementById('wizard-soap-xsd-name');
    if (fileNameEl) fileNameEl.textContent = file.name;
    showWizardLiveStatus(tKey('wizard.step3.soap.xsd_selected'), tKey('wizard.step3.soap.xsd_selected_body'));
  };
  reader.readAsArrayBuffer(file);
}

function renderDescriptorServiceList(services) {
  grpcDescriptorServices = services || [];
  var summaryEl = document.getElementById('wizard-descriptor-services-summary');
  var listEl = document.getElementById('wizard-descriptor-services-list');
  if (!summaryEl || !listEl) return;

  var methodCount = grpcDescriptorServices.reduce(function(total, service) {
    return total + ((service.methods || []).length);
  }, 0);
  summaryEl.textContent = grpcDescriptorServices.length
    ? tfKey('wizard.grpc.services_discovered', { services: grpcDescriptorServices.length, methods: methodCount })
    : tKey('wizard.grpc.services_none');
  listEl.innerHTML = '';

  grpcDescriptorServices.forEach(function(service, serviceIndex) {
    var group = document.createElement('div');
    group.className = 'config-card';
    group.style.marginBottom = '0';

    var body = document.createElement('div');
    body.className = 'config-card-body';
    body.style.padding = '16px';
    body.style.gap = '10px';

    var title = document.createElement('div');
    title.className = 'config-card-title';
    title.textContent = (service.package ? service.package + '.' : '') + service.service;
    body.appendChild(title);

    (service.methods || []).forEach(function(method, methodIndex) {
      var button = document.createElement('button');
      button.type = 'button';
      button.className = 'btn-ghost-sm';
      button.style.width = '100%';
      button.style.justifyContent = 'space-between';
      button.textContent = method.name;
      button.addEventListener('click', function() {
        applyDiscoveredGrpcMethod(serviceIndex, methodIndex);
      });
      body.appendChild(button);
    });

    group.appendChild(body);
    listEl.appendChild(group);
  });
}

function applyDiscoveredGrpcMethod(serviceIndex, methodIndex) {
  var service = grpcDescriptorServices[serviceIndex];
  var method = service && service.methods ? service.methods[methodIndex] : null;
  if (!service || !method) return;

  var route = '/' + (service.package ? service.package + '.' : '') + service.service + '/' + method.name;
  selectGrpcSource('manual');
  setValue('grpc-manual-route', route);
  setValue('grpc-manual-req-type', method.name + 'Request');
  setValue('grpc-manual-res-type', method.name + 'Response');
  grpcManualRouteInput(route);
  grpcManualTypeInput();
  setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(method.input_schema), null, 2));
  setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(method.output_schema), null, 2));
  showWizardLiveStatus(tKey('wizard.descriptor.applied'), tKey('wizard.descriptor.applied_body'));
}

async function uploadDescriptorSetAndDiscover() {
  var hasSavedDescriptor = !!(
    wizardCurrentVersion
    && wizardCurrentVersion.target
    && wizardCurrentVersion.target.kind === 'grpc'
    && wizardCurrentVersion.target.descriptor_ref
  );

  if (!wizardDescriptorSetUpload && !hasSavedDescriptor) {
    showWizardLiveStatus(tKey('wizard.descriptor.no_file'), tKey('wizard.descriptor.no_file_body'), true);
    return;
  }

  await persistCurrentDraft(true);

  var services = await window.CrankApi.listGrpcServices(
    wizardWorkspaceId,
    wizardEditId,
    currentDraftVersion()
  );
  renderDescriptorServiceList(services.services || []);
  showWizardLiveStatus(tKey('wizard.descriptor.discovered'), tKey('wizard.descriptor.discovered_body'));
}

function renderSoapServiceCatalog(services) {
  soapServiceCatalog = services || [];
  var summaryEl = document.getElementById('wizard-soap-services-summary');
  var listEl = document.getElementById('wizard-soap-services-list');
  if (!summaryEl || !listEl) return;

  var portCount = 0;
  var operationCount = 0;
  soapServiceCatalog.forEach(function(service) {
    portCount += (service.ports || []).length;
    (service.ports || []).forEach(function(port) {
      operationCount += (port.operations || []).length;
    });
  });

  summaryEl.textContent = soapServiceCatalog.length
    ? tfKey('wizard.step3.soap.services_discovered', {
        services: soapServiceCatalog.length,
        ports: portCount,
        operations: operationCount
      })
    : tKey('wizard.step3.soap.services_none');
  listEl.innerHTML = '';

  soapServiceCatalog.forEach(function(service, serviceIndex) {
    var group = document.createElement('div');
    group.className = 'config-card';
    group.style.marginBottom = '0';

    var body = document.createElement('div');
    body.className = 'config-card-body';
    body.style.padding = '16px';
    body.style.gap = '10px';

    var title = document.createElement('div');
    title.className = 'config-card-title';
    title.textContent = service.service_name;
    body.appendChild(title);

    (service.ports || []).forEach(function(port, portIndex) {
      var portLabel = document.createElement('div');
      portLabel.className = 'form-hint';
      portLabel.textContent = port.port_name + ' · ' + port.soap_version;
      body.appendChild(portLabel);

      (port.operations || []).forEach(function(operation, operationIndex) {
        var button = document.createElement('button');
        button.type = 'button';
        button.className = 'btn-ghost-sm';
        button.style.width = '100%';
        button.style.justifyContent = 'space-between';
        button.textContent = operation.operation_name;
        button.addEventListener('click', function() {
          applyDiscoveredSoapOperation(serviceIndex, portIndex, operationIndex);
        });
        body.appendChild(button);
      });
    });

    group.appendChild(body);
    listEl.appendChild(group);
  });
}

function applyDiscoveredSoapOperation(serviceIndex, portIndex, operationIndex) {
  var service = soapServiceCatalog[serviceIndex];
  var port = service && service.ports ? service.ports[portIndex] : null;
  var operation = port && port.operations ? port.operations[operationIndex] : null;
  if (!service || !port || !operation) return;

  setValue('soap-service-name', service.service_name);
  setValue('soap-port-name', port.port_name);
  setValue('soap-operation-name', operation.operation_name);
  setValue('soap-version', port.soap_version || 'soap_11');
  setValue('soap-action', operation.soap_action || '');
  setValue('soap-binding-style', operation.binding_style || 'document_literal');
  if (!textValue('soap-endpoint-override') && port.endpoint) {
    setValue('soap-endpoint-override', port.endpoint);
  }

  showWizardLiveStatus(tKey('wizard.step3.soap.applied'), tKey('wizard.step3.soap.applied_body'));
}

async function uploadSoapArtifactsAndInspect() {
  var hasSavedWsdl = !!(
    wizardCurrentVersion
    && wizardCurrentVersion.target
    && wizardCurrentVersion.target.kind === 'soap'
    && wizardCurrentVersion.target.wsdl_ref
    && wizardCurrentVersion.target.wsdl_ref !== 'sample_wsdl_pending'
  );

  if (!wizardSoapWsdlUpload && !hasSavedWsdl) {
    showWizardLiveStatus(tKey('wizard.step3.soap.no_wsdl_error'), tKey('wizard.step3.soap.no_wsdl_error_body'), true);
    return;
  }

  await persistCurrentDraft(true);

  var services = await window.CrankApi.listSoapServices(
    wizardWorkspaceId,
    wizardEditId,
    currentDraftVersion()
  );
  renderSoapServiceCatalog(services.services || []);
  showWizardLiveStatus(tKey('wizard.step3.soap.discovered'), tKey('wizard.step3.soap.discovered_body'));
}
