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.message';
  if (protocol === 'soap') return '$.request.body';
  if (protocol === 'websocket') return '$.request.body';
  return '$.request.body';
}

function normalizeInputSource(path) {
  if (!path) return '$.mcp';
  if (path === '$') return '$.mcp';
  if (path.indexOf('$.mcp') === 0) return path;
  if (path.indexOf('$.input') === 0) return '$.mcp' + path.slice('$.input'.length);
  return '$.mcp.' + path.replace(/^\$\./, '');
}

function normalizeOutputSource(path) {
  if (!path) return '$.response.body';
  if (path.indexOf('$.') === 0) return path;
  return '$.' + path.replace(/^\$\./, '');
}

function buildMappingSet(rawValue, mode) {
  var value = rawValue || {};
  var root = mappingRootForProtocol(wizardProtocol);
  var rules = [];

  Object.keys(value).forEach(function(target) {
    var source = value[target];
    if (mode === 'input') {
      rules.push({
        source: normalizeInputSource(source),
        target: root + '.' + target,
      });
      return;
    }

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

  return { rules: rules };
}

function parseExecutionConfig(text) {
  var value = parseStructuredText(text);
  var retry = value.retry || {};
  var headers = value.headers && typeof value.headers === 'object' ? value.headers : {};
  var selectedUpstream = currentUpstream();
  if (!selectedUpstream && wizardMode !== 'edit') {
    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 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 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 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 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.hidden = false;
  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 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();
}
