/* AliothPress — WebMCP for public forms
 * ----------------------------------------------------------------------------
 * For every form rendered on a public page (the page-builder Form block emits
 * <form data-form-submit="ID">), this registers two agent tools:
 *
 *   describe_form_<ID>  — read the fields, types, and which are required
 *   submit_form_<ID>    — submit the form with a {field_name: value} object
 *
 * Submission reuses the existing public endpoint /api/forms/<ID>/submit, which
 * already does honeypot + rate-limit + validation server-side. The agent never
 * touches the honeypot field. Submitting is gated behind a user confirmation,
 * so the human stays in control of what gets sent under their name.
 * ----------------------------------------------------------------------------
 */
(function () {
  'use strict';
  if (!window.AliothMCP) return;
  var M = window.AliothMCP;
  // Captured before anyone can call ensureApi(): if the API exists at this
  // moment it is the browser's own implementation, not the polyfill (the
  // core injects that later, on demand). Used to decide whether forms that
  // carry the declarative annotation are already registered by the browser.
  var nativeAtLoad = M.supported();

  function fieldsFromForm(formEl) {
    var out = [];
    var byName = {};
    var nodes = formEl.querySelectorAll('input[name], textarea[name], select[name]');
    Array.prototype.forEach.call(nodes, function (el) {
      var name = el.getAttribute('name');
      if (!name || name === '_hp_website') return;      // skip honeypot
      var tag = el.tagName.toLowerCase();
      var f = byName[name];
      if (!f) {
        var labelEl = el.closest('.ap-form__field') &&
                      el.closest('.ap-form__field').querySelector('.ap-form__label, .ap-form__consent span');
        f = byName[name] = {
          name: name,
          type: (tag === 'textarea') ? 'textarea'
                : (tag === 'select') ? 'select'
                : (el.getAttribute('type') || 'text'),
          label: labelEl ? labelEl.textContent.replace('*', '').trim() : name,
          required: el.hasAttribute('required')
        };
        // Star rating: a radio group inside .ap-form__rating is an integer
        // 1..max, not a free string — expose the range so agents fill it
        // like a normal field instead of guessing.
        var ratingWrap = el.closest('.ap-form__rating');
        if (f.type === 'radio' && ratingWrap) {
          f.type = 'rating';
          f.max = parseInt(ratingWrap.getAttribute('data-max'), 10) || 5;
        }
        // Selects: expose the allowed values (skip the empty placeholder).
        if (tag === 'select') {
          f.options = [];
          Array.prototype.forEach.call(el.options, function (o) {
            if (o.value) f.options.push(o.value);
          });
        }
        out.push(f);
      }
      // Radio / checkbox groups: collect the allowed values and count the
      // group members (getAttribute, not .value — an attribute-less
      // checkbox reports .value 'on', which is not a real option).
      if (f.type === 'radio' || f.type === 'checkbox') {
        var v = el.getAttribute('value');
        f.options = f.options || [];
        if (v && v !== 'true') f.options.push(v);
        f.count = (f.count || 0) + 1;
      }
    });
    // A checkbox group with several options is a multi-select (the server
    // stores a list of the chosen values), not a boolean.
    out.forEach(function (f) {
      if (f.type === 'checkbox' && f.count > 1) f.type = 'multi_checkbox';
      delete f.count;
    });
    return out;
  }

  function buildSchema(fields) {
    var props = {}, required = [], p;
    fields.forEach(function (f) {
      if (f.type === 'rating') {
        p = { type: 'integer', minimum: 1, maximum: f.max || 5,
              description: f.label + ' — star rating from 1 (worst) to ' +
                           (f.max || 5) + ' (best)' };
      } else if (f.type === 'multi_checkbox') {
        p = { type: 'array', items: { type: 'string', enum: f.options },
              description: f.label + ' — any of the listed values' };
      } else if ((f.type === 'radio' || f.type === 'select') &&
                 f.options && f.options.length) {
        p = { type: 'string', enum: f.options, description: f.label };
      } else {
        var t = (f.type === 'number') ? 'number'
              : (f.type === 'checkbox' || f.type === 'consent') ? 'boolean'
              : 'string';
        p = { type: t, description: f.label };
      }
      props[f.name] = p;
      if (f.required) required.push(f.name);
    });
    return { type: 'object', properties: props, required: required };
  }

  function registerForm(formEl) {
    var id = formEl.getAttribute('data-form-submit');
    if (!id) return;
    /* Declarative-first: with the toolname annotation present and native
       WebMCP support, the browser has already registered this form as a
       tool straight from the markup, and its synthesized schema
       self-describes the fields (what describe_form_ used to tell) —
       imperative twins here would surface as duplicates. Polyfill-only
       setups do not parse the declarative attributes, so for them the
       imperative pair below still carries the job. */
    if (nativeAtLoad && formEl.hasAttribute('toolname')) return;
    var wrap = formEl.closest('.ap-form');
    var titleEl = wrap && wrap.querySelector('.ap-form__title');
    var title = titleEl ? titleEl.textContent.trim() : ('form ' + id);
    var fields = fieldsFromForm(formEl);
    var fieldList = fields.map(function (f) {
      var extra = (f.type === 'rating') ? ' [1-' + (f.max || 5) + ']'
                : (f.options && f.options.length)
                  ? ' [' + f.options.join(' | ') + ']' : '';
      return f.name + (f.required ? ' (required)' : '') + ' — ' + f.label + extra;
    }).join('; ');

    M.define({
      name: 'describe_form_' + id,
      description: 'Describe the fields of the on-page form "' + title +
                   '". Fields: ' + fieldList,
      inputSchema: { type: 'object', properties: {} },
      annotations: { readOnlyHint: true },
      execute: function () {
        return M.ok({ form_id: Number(id), title: title, fields: fields });
      }
    });

    M.define({
      name: 'submit_form_' + id,
      description: 'Fill in and submit the on-page form "' + title +
                   '". Pass a values object keyed by field name. The browser asks ' +
                   'the visitor to approve the call before it runs.',
      inputSchema: buildSchema(fields),
      annotations: { destructiveHint: false },
      execute: function (args) {
        var values = args || {};
        // No custom confirm dialog: the browser/agent layer mediates consent for
        // WebMCP tool calls (per spec). Result messages reuse the form's existing
        // localized strings — nothing new to translate here.
        var localizedSuccess = formEl.getAttribute('data-msg-success') || '';
        var localizedError = formEl.getAttribute('data-msg-error') || '';
        return M.postJson('/api/forms/' + id + '/submit', values)
          .then(function (r) { return r.json(); })
          .then(function (data) {
            if (data && data.success) {
              var msgEl = wrap && wrap.querySelector('.ap-form__message');
              if (msgEl) {
                msgEl.textContent = data.message || localizedSuccess;
                msgEl.className = 'ap-form__message ap-form__message--success';
                msgEl.style.display = 'block';
              }
              return M.ok({ success: true, message: data.message || localizedSuccess });
            }
            return M.fail({ success: false,
              errors: (data && (data.errors || data.error)) || localizedError });
          })
          .catch(function (e) { return M.fail('Network error: ' + e); });
      }
    });

    M.activate(['describe_form_' + id, 'submit_form_' + id]);
  }

  function registerSearch() {
    M.define({
      name: 'search_site',
      description: 'Search this website\'s content (published posts and pages, including ' +
                   'their FAQs) and matching blog categories/tags. Returns titles, URLs, and ' +
                   'snippets — one structured call instead of scraping the page. For a full ' +
                   'site overview, fetch /llms.txt.',
      inputSchema: {
        type: 'object',
        properties: {
          q: { type: 'string', description: 'What to search for' },
          language: { type: 'string', description: 'Optional language code, e.g. en, de' },
          type: { type: 'string', enum: ['post', 'page'], description: 'Optional: limit to posts or pages' }
        },
        required: ['q']
      },
      annotations: { readOnlyHint: true },
      execute: function (a) {
        var p = new URLSearchParams();
        p.set('q', a.q || '');
        if (a.language) p.set('language', a.language);
        if (a.type) p.set('type', a.type);
        return M.getJson('/api/public/search?' + p.toString())
          .then(function (r) { return r.json(); })
          .then(M.ok)
          .catch(function (e) { return M.fail('Search failed: ' + e); });
      }
    });
    M.activate(['search_site']);
  }

  function init() {
    M.ensureApi(function () {
      if (!M.supported()) return;   // no agent API on this browser — stay invisible
      registerSearch();
      var forms = document.querySelectorAll('form[data-form-submit]');
      Array.prototype.forEach.call(forms, registerForm);
    });
  }

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