
(function () {

  // ─── TIMING ────────────────────────────────────────────────────────────────
  // Run at three checkpoints to beat the WebMCP DOM scan regardless of
  // whether our script loads before or after DOMContentLoaded/load.
  // This fixes the 0/1 tools detected race condition on refresh.

  tryAnnotate();

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', tryAnnotate);
  }

  window.addEventListener('load', tryAnnotate);

  observeDynamicForms();

  // Annotates all forms not yet fully annotated.
  // Safe to call multiple times — skips already-annotated forms.
function tryAnnotate() {
  document.querySelectorAll('form').forEach(function(form) {

    // ── ID SANITIZER ───────────────────────────────────────────────────
    // WebMCP early preview derives its internal lookup key from the
    // form's id attribute directly — not from toolname. So we must
    // also clean the id itself to match what toolname will be.
    // We back up the original id to data-original-id so nothing breaks.
    if (form.id && !form.dataset.originalId) {
      form.dataset.originalId = form.id;
      form.id = toSnakeCase(
        form.id
          .replace(/^wf[-_]form[-_]/i, '')
          .replace(/[-_]form$/i, '')
          .replace(/^form[-_]/i, '')
          .replace(/[-_]\d+$/i, '')
      );
    }

    if (form.hasAttribute('toolname') && form.hasAttribute('tooldescription')) return;
    annotateForm(form);
  });
}
  function annotateForm(form) {
    setToolName(form);
    setToolDescription(form);
    annotateFields(form);
    console.info('[WebMCP Auto-Annotator] Annotated: ' + form.getAttribute('toolname'));
  }


  // ─── TOOL NAME ─────────────────────────────────────────────────────────────
  // Derives toolname from: id → name attr → action URL → positional fallback.
  // Strips common CMS prefixes/suffixes before converting to snake_case.
  // Skip if already set manually — explicit is always better than inferred.

  function setToolName(form) {
    if (form.hasAttribute('toolname')) return;

    var sources = [
      form.id,
      form.getAttribute('name'),
      deriveNameFromAction(form.action),
      getFallbackFormName(form)
    ];

    var rawName = '';
    for (var i = 0; i < sources.length; i++) {
      if (sources[i] && sources[i].trim()) {
        rawName = sources[i].trim();
        break;
      }
    }

    // Strip common CMS/framework prefixes and suffixes that add noise.
    // Webflow generates IDs like "wf-form-Contact-03-form" — we strip
    // the wrapper and keep only the meaningful middle part.
    rawName = rawName
      .replace(/^wf[-_]form[-_]/i, '')  // Webflow prefix "wf-form-"
      .replace(/[-_]form$/i, '')         // trailing "-form" or "_form"
      .replace(/^form[-_]/i, '')         // leading "form-" or "form_"
      .replace(/[-_]\d+$/i, '');         // trailing numbers e.g. "-03", "_1"

    var toolName = toSnakeCase(rawName);

    // If we ended up with something too short or empty, use positional fallback
    if (!toolName || toolName.length < 2) {
      toolName = getFallbackFormName(form);
    }

    form.setAttribute('toolname', toolName);
    console.debug('[WebMCP Auto-Annotator] toolname="' + toolName + '"', form);
  }

  // Extracts the last path segment from a form's action URL.
  // e.g. "/api/search-flights" → "search-flights"
  function deriveNameFromAction(action) {
    if (!action || action === '#' || action === '') return null;
    try {
      var url = new URL(action, window.location.href);
      var segments = url.pathname.split('/').filter(Boolean);
      return segments[segments.length - 1] || null;
    } catch (e) {
      return null;
    }
  }

  // Returns a positional fallback name based on the form's index in the DOM.
  // e.g. 3rd form on the page → "form_2"
  function getFallbackFormName(form) {
    var allForms = Array.from(document.querySelectorAll('form'));
    return 'form_' + allForms.indexOf(form);
  }


  // ─── TOOL DESCRIPTION ──────────────────────────────────────────────────────
  // Derives from: aria-label → legend → nearest h1-h6 → humanized toolname.
  // Skip if already set manually.

  function setToolDescription(form) {
    if (form.hasAttribute('tooldescription')) return;
    var legend = form.querySelector('legend');
    var description =
      form.getAttribute('aria-label') ||
      (legend && legend.textContent.trim()) ||
      findNearestHeading(form) ||
      humanizeSnakeCase(form.getAttribute('toolname'));
    form.setAttribute('tooldescription', description);
    console.debug('[WebMCP Auto-Annotator] tooldescription="' + description + '"', form);
  }

  // Walks up the DOM from a form looking for the nearest preceding h1-h6.
  // Mimics how a human would visually associate a heading with a form.
  function findNearestHeading(element) {
    var current = element;
    while (current && current !== document.body) {
      var sibling = current.previousElementSibling;
      while (sibling) {
        if (/^H[1-6]$/.test(sibling.tagName)) return sibling.textContent.trim();
        sibling = sibling.previousElementSibling;
      }
      current = current.parentElement;
    }
    return null;
  }


  // ─── FIELD ANNOTATION ──────────────────────────────────────────────────────
  // WebMCP spec: radio/checkbox groups get toolparamdescription on <fieldset>,
  // not on individual inputs.
  // All :not() must be chained on one input selector — do NOT split with commas.

  function annotateFields(form) {
    annotateFieldsets(form);
    var selector = 'input:not([type=hidden]):not([type=submit]):not([type=reset]):not([type=button]), select, textarea';
    form.querySelectorAll(selector).forEach(function(field) {
      if (field.closest('fieldset')) return;
      if (field.hasAttribute('toolparamdescription')) return;
      var description = getFieldDescription(field, form);
      if (description) {
        field.setAttribute('toolparamdescription', description);
        console.debug('[WebMCP Auto-Annotator] toolparamdescription="' + description + '"', field);
      }
    });
  }

  // Annotates <fieldset> elements for grouped inputs (radio, checkbox).
  // Per WebMCP spec the group description must go on the <fieldset>.
  function annotateFieldsets(form) {
    form.querySelectorAll('fieldset').forEach(function(fieldset) {
      if (fieldset.hasAttribute('toolparamdescription')) return;
      var legend = fieldset.querySelector('legend');
      var description = legend && legend.textContent.trim();
      if (description) {
        fieldset.setAttribute('toolparamdescription', description);
        console.debug('[WebMCP Auto-Annotator] fieldset toolparamdescription="' + description + '"', fieldset);
      }
    });
  }

  // Finds the best description for an individual field.
  // Checks: <label for> → wrapping <label> → placeholder → aria-label → humanized name
  function getFieldDescription(field, form) {
    if (field.id) {
      var label = form.querySelector('label[for="' + field.id + '"]');
      if (label) return label.textContent.trim();
    }
    var wrappingLabel = field.closest('label');
    if (wrappingLabel) return wrappingLabel.textContent.trim();
    var placeholder = field.getAttribute('placeholder');
    if (placeholder) return placeholder;
    var ariaLabel = field.getAttribute('aria-label');
    if (ariaLabel) return ariaLabel;
    var name = field.getAttribute('name');
    if (name) return humanizeSnakeCase(name);
    return null;
  }


  // ─── DYNAMIC FORM OBSERVER ─────────────────────────────────────────────────
  // Catches forms added after initial load — handles React, Vue, Angular,
  // and any other framework that renders forms asynchronously.

  function observeDynamicForms() {
    var observer = new MutationObserver(function(mutations) {
      mutations.forEach(function(mutation) {
        mutation.addedNodes.forEach(function(node) {
          if (node.nodeType !== Node.ELEMENT_NODE) return;
          if (node.tagName === 'FORM') annotateForm(node);
          if (node.querySelectorAll) {
            node.querySelectorAll('form').forEach(function(form) {
              annotateForm(form);
            });
          }
        });
      });
    });
    observer.observe(document.body, { childList: true, subtree: true });
    console.debug('[WebMCP Auto-Annotator] MutationObserver active.');
  }


  // ─── UTILITIES ─────────────────────────────────────────────────────────────

  // Converts any string to strict snake_case.
  // Must be 100% deterministic — the browser derives its lookup key from the
  // same attribute, so any inconsistency causes "Tool not found".
  // Tool names must start with a letter — prefix with "f_" if starts with number.
  function toSnakeCase(str) {
    if (!str) return '';
    var result = str
      .replace(/([a-z])([A-Z])/g, '$1_$2')       // camelCase → camel_case
      .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') // ACRONYMCase → acronym_case
      .replace(/[\s\-.]+/g, '_')                  // spaces, hyphens, dots → underscores
      .replace(/[^a-z0-9_]/gi, '')                // strip everything else
      .replace(/_+/g, '_')                        // collapse multiple underscores
      .replace(/^_+|_+$/g, '')                   // trim leading/trailing underscores
      .toLowerCase();
    if (result && /^[0-9]/.test(result)) result = 'f_' + result;
    return result;
  }

  // Converts snake_case to a readable human label.
  // Used as last-resort tooldescription when no better text is available.
  // "contact_form" → "Contact Form"
  function humanizeSnakeCase(str) {
    return str
      .replace(/_/g, ' ')
      .replace(/([a-z])([A-Z])/g, '$1 $2')
      .replace(/\b\w/g, function(c) { return c.toUpperCase(); });
  }

})();
