/* AliothPress — WebMCP core engine
 * ----------------------------------------------------------------------------
 * Dependency-free wrapper around the WebMCP browser API so the rest of the
 * codebase can register agent tools without caring about feature detection,
 * the two surface names, CSRF, lifecycle, or i18n.
 *
 * Surface: the W3C Community Group spec exposes navigator.modelContext, while
 * the MCP-B ecosystem/polyfill exposes document.modelContext (its "v3" name).
 * We detect and use whichever exists, preferring document.modelContext.
 *
 * Lifecycle: we register with an AbortSignal *and* keep unregisterTool() — both
 * are current; some native builds honour one and not the other, so we do both.
 *
 * Everything is a safe no-op when no API is present or the owner hasn't enabled
 * it. Tool results use the MCP shape { content: [{ type, text }] }.
 * ----------------------------------------------------------------------------
 */
(function () {
  'use strict';

  var CFG = window.AP_WEBMCP || {};
  var I18N = CFG.i18n || {};
  var catalog = {};     // name -> tool definition
  var live = {};        // name -> AbortController (registered)
  // Light, strict polyfill (not the full ~285KB MCP-B runtime).
  var POLYFILL_URL = 'https://cdn.jsdelivr.net/npm/@mcp-b/webmcp-polyfill/dist/index.global.js';

  // --- Surface detection ----------------------------------------------------
  function mc() {
    if (typeof document !== 'undefined' && document.modelContext) return document.modelContext;
    if (navigator.modelContext) return navigator.modelContext;
    return null;
  }
  function hasApi() {
    var c = mc();
    return !!(window.isSecureContext && c && typeof c.registerTool === 'function');
  }

  // --- i18n: localized strings with {placeholders}. Strings come from the
  // page's injected map (populated server-side via t()); no English literals
  // live in the JS. A missing key surfaces as the key itself (a loud, catchable
  // bug) rather than being silently masked.
  function tr(key, vars) {
    var s = (I18N && I18N[key]) || key;
    if (vars) {
      Object.keys(vars).forEach(function (k) {
        s = s.replace(new RegExp('\\{' + k + '\\}', 'g'), vars[k]);
      });
    }
    return s;
  }

  // --- CSRF -----------------------------------------------------------------
  function csrf() {
    var m = document.querySelector('meta[name="csrf-token"]');
    if (m && m.getAttribute('content')) return m.getAttribute('content');
    return CFG.csrf || '';
  }

  // --- Result helpers (MCP tool-result shape) -------------------------------
  function asText(x) { return (typeof x === 'string') ? x : JSON.stringify(x); }
  function ok(payload) { return { content: [{ type: 'text', text: asText(payload) }] }; }
  function fail(payload) { return { content: [{ type: 'text', text: asText(payload) }], isError: true }; }

  // --- Human-in-the-loop confirmation ---------------------------------------
  // Single confirms use a themed modal (admin --adm-* tokens only, so light
  // and dark modes both work) with an optional, HUMAN-ONLY "autopilot":
  // auto-approve agent actions for a limited time in this tab. Autopilot can
  // only be enabled by ticking the checkbox inside the dialog (a user
  // gesture) — it is not exposed as an agent tool, an agent cannot switch it
  // on. While active, a fixed badge shows the remaining time with a one-click
  // revoke, and it expires on its own. State lives in sessionStorage: this
  // tab only, survives agent navigation (open_admin_section), gone when the
  // tab closes.
  var AUTO_KEY = 'apWebmcpAutopilot';

  function autopilotActive() {
    try {
      var v = parseInt(sessionStorage.getItem(AUTO_KEY) || '0', 10);
      if (v && Date.now() < v) return true;
      if (v) sessionStorage.removeItem(AUTO_KEY);
    } catch (e) {}
    return false;
  }
  function enableAutopilot(minutes) {
    try {
      sessionStorage.setItem(AUTO_KEY, String(Date.now() + minutes * 60000));
      renderBadge();
    } catch (e) {}
  }
  function revokeAutopilot() {
    try { sessionStorage.removeItem(AUTO_KEY); } catch (e) {}
    var b = document.querySelector('.wmcp-autopilot-badge');
    if (b && b.parentNode) b.parentNode.removeChild(b);
  }
  function renderBadge() {
    if (!autopilotActive() || document.querySelector('.wmcp-autopilot-badge')) return;
    var badge = document.createElement('div');
    badge.className = 'wmcp-autopilot-badge';
    var label = document.createElement('span');
    var btn = document.createElement('button');
    btn.type = 'button';
    btn.textContent = tr('autopilot_revoke');
    btn.addEventListener('click', revokeAutopilot);
    badge.appendChild(label);
    badge.appendChild(btn);
    document.body.appendChild(badge);
    function tick() {
      var v = 0;
      try { v = parseInt(sessionStorage.getItem(AUTO_KEY) || '0', 10); } catch (e) {}
      var left = v - Date.now();
      if (left <= 0) { clearInterval(timer); revokeAutopilot(); return; }
      label.textContent = tr('autopilot_badge', { min: Math.max(1, Math.ceil(left / 60000)) });
    }
    var timer = setInterval(tick, 15000);
    tick();
  }

  function buildOverlay() {
    var overlay = document.createElement('div');
    overlay.className = 'wmcp-overlay';
    var modal = document.createElement('div');
    modal.className = 'wmcp-modal';
    overlay.appendChild(modal);
    return { overlay: overlay, modal: modal };
  }

  function footerButtons(onCancel, onApprove, approveLabelKey) {
    var footer = document.createElement('div');
    footer.className = 'wmcp-footer';
    var auto = document.createElement('label');
    auto.className = 'wmcp-auto';
    var autoCb = document.createElement('input');
    autoCb.type = 'checkbox';
    var autoTxt = document.createElement('span');
    autoTxt.textContent = tr('autopilot_label');
    var autoSel = document.createElement('select');
    [15, 60].forEach(function (m) {
      var o = document.createElement('option');
      o.value = m; o.textContent = tr('autopilot_minutes', { min: m });
      autoSel.appendChild(o);
    });
    auto.appendChild(autoCb); auto.appendChild(autoTxt); auto.appendChild(autoSel);
    var cancel = document.createElement('button');
    cancel.type = 'button'; cancel.className = 'btn';
    cancel.textContent = tr('cancel');
    cancel.addEventListener('click', onCancel);
    var approve = document.createElement('button');
    approve.type = 'button'; approve.className = 'btn btn-primary';
    approve.textContent = tr(approveLabelKey || 'approve');
    approve.addEventListener('click', function () {
      onApprove(autoCb.checked ? parseInt(autoSel.value, 10) : 0);
    });
    footer.appendChild(auto); footer.appendChild(cancel); footer.appendChild(approve);
    return { el: footer, approveBtn: approve };
  }

  function showConfirmModal(message) {
    return new Promise(function (resolve) {
      var ui = buildOverlay();
      var h = document.createElement('h3');
      h.textContent = tr('confirm_title');
      var p = document.createElement('p');
      p.className = 'wmcp-message';
      p.textContent = message;
      function finish(val, autoMin) {
        if (ui.overlay.parentNode) ui.overlay.parentNode.removeChild(ui.overlay);
        document.removeEventListener('keydown', onKey);
        if (val && autoMin) enableAutopilot(autoMin);
        resolve(val);
      }
      function onKey(e) { if (e.key === 'Escape') finish(false, 0); }
      var footer = footerButtons(function () { finish(false, 0); },
                                 function (autoMin) { finish(true, autoMin); });
      ui.modal.appendChild(h); ui.modal.appendChild(p); ui.modal.appendChild(footer.el);
      ui.overlay.addEventListener('click', function (e) { if (e.target === ui.overlay) finish(false, 0); });
      document.addEventListener('keydown', onKey);
      document.body.appendChild(ui.overlay);
      footer.approveBtn.focus();
    });
  }

  function confirmAction(message) {
    if (autopilotActive()) return Promise.resolve(true);
    var run = function () { return showConfirmModal(message); };
    try {
      var c = mc();
      if (c && typeof c.requestUserInteraction === 'function') {
        return c.requestUserInteraction(run).then(function (r) {
          return (typeof r === 'boolean') ? r : run();
        }).catch(run);
      }
    } catch (e) { /* fall through */ }
    return run();
  }

  // Batch approval: ONE dialog for many items — a "select all" master
  // checkbox plus per-item checkboxes, so the user approves everything or a
  // hand-picked subset in a single interaction (e.g. migrating dozens of
  // pages from another CMS). Resolves with the array of approved indices,
  // or null when cancelled. Under autopilot, all items are approved.
  function showBatchModal(title, labels) {
    return new Promise(function (resolve) {
      var ui = buildOverlay();
      var h = document.createElement('h3');
      h.textContent = title;
      var master = document.createElement('label');
      master.className = 'wmcp-selectall';
      var masterCb = document.createElement('input');
      masterCb.type = 'checkbox'; masterCb.checked = true;
      var masterTxt = document.createElement('span');
      masterTxt.textContent = tr('select_all');
      master.appendChild(masterCb); master.appendChild(masterTxt);
      var list = document.createElement('div');
      list.className = 'wmcp-list';
      var boxes = labels.map(function (label) {
        var row = document.createElement('label');
        row.className = 'wmcp-item';
        var cb = document.createElement('input');
        cb.type = 'checkbox'; cb.checked = true;
        var span = document.createElement('span');
        span.textContent = label;
        row.appendChild(cb); row.appendChild(span);
        list.appendChild(row);
        return cb;
      });
      function selectedCount() {
        return boxes.filter(function (b) { return b.checked; }).length;
      }
      function finish(val, autoMin) {
        if (ui.overlay.parentNode) ui.overlay.parentNode.removeChild(ui.overlay);
        document.removeEventListener('keydown', onKey);
        if (val && autoMin) enableAutopilot(autoMin);
        resolve(val);
      }
      function onKey(e) { if (e.key === 'Escape') finish(null, 0); }
      var footer = footerButtons(function () { finish(null, 0); }, function (autoMin) {
        var idx = [];
        boxes.forEach(function (b, i) { if (b.checked) idx.push(i); });
        finish(idx, autoMin);
      }, 'approve_selected');
      function refresh() {
        footer.approveBtn.textContent = tr('approve_selected', { count: selectedCount() });
        footer.approveBtn.disabled = selectedCount() === 0;
        masterCb.checked = selectedCount() === boxes.length;
      }
      masterCb.addEventListener('change', function () {
        boxes.forEach(function (b) { b.checked = masterCb.checked; });
        refresh();
      });
      boxes.forEach(function (b) { b.addEventListener('change', refresh); });
      refresh();
      ui.modal.appendChild(h); ui.modal.appendChild(master);
      ui.modal.appendChild(list); ui.modal.appendChild(footer.el);
      ui.overlay.addEventListener('click', function (e) { if (e.target === ui.overlay) finish(null, 0); });
      document.addEventListener('keydown', onKey);
      document.body.appendChild(ui.overlay);
    });
  }

  function confirmBatch(title, labels) {
    if (autopilotActive()) {
      return Promise.resolve(labels.map(function (_, i) { return i; }));
    }
    var run = function () { return showBatchModal(title, labels); };
    try {
      var c = mc();
      if (c && typeof c.requestUserInteraction === 'function') {
        return c.requestUserInteraction(run).then(function (r) {
          return Array.isArray(r) || r === null ? r : run();
        }).catch(run);
      }
    } catch (e) { /* fall through */ }
    return run();
  }

  // Show the badge on load if a previous page in this tab enabled autopilot.
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', renderBadge);
  } else {
    renderBadge();
  }

  // --- HTTP helpers ---------------------------------------------------------
  // Every write helper marks its request as agent traffic. The server
  // (AuditLog.log) reads this header to attribute the action to the AI
  // agent instead of silently recording it as the human's own action.
  var AGENT_HEADER = 'X-AliothPress-Agent';

  function postForm(url, fields, extraHeaders) {
    var fd = new FormData();
    Object.keys(fields || {}).forEach(function (k) {
      var v = fields[k];
      if (v === undefined || v === null) return;
      fd.append(k, v);
    });
    fd.append('csrf_token', csrf());
    var headers = { 'X-CSRFToken': csrf(), 'Accept': 'application/json' };
    headers[AGENT_HEADER] = 'webmcp';
    Object.keys(extraHeaders || {}).forEach(function (k) { headers[k] = extraHeaders[k]; });
    return fetch(url, {
      method: 'POST', body: fd, credentials: 'same-origin', redirect: 'follow',
      headers: headers
    });
  }
  // Multipart post with a real file part — used by upload_image to hit the
  // same /admin/media/upload route the human upload form uses.
  function postFile(url, fileBlob, filename, fields) {
    var fd = new FormData();
    fd.append('file', fileBlob, filename);
    Object.keys(fields || {}).forEach(function (k) {
      var v = fields[k];
      if (v === undefined || v === null) return;
      fd.append(k, v);
    });
    fd.append('csrf_token', csrf());
    var headers = { 'X-CSRFToken': csrf(), 'Accept': 'application/json' };
    headers[AGENT_HEADER] = 'webmcp';
    return fetch(url, {
      method: 'POST', body: fd, credentials: 'same-origin',
      headers: headers
    });
  }
  function postJson(url, payload) {
    var headers = { 'Content-Type': 'application/json', 'X-CSRFToken': csrf() };
    headers[AGENT_HEADER] = 'webmcp';
    return fetch(url, {
      method: 'POST', credentials: 'same-origin',
      headers: headers,
      body: JSON.stringify(payload || {})
    });
  }
  function putJson(url, payload) {
    var headers = { 'Content-Type': 'application/json', 'X-CSRFToken': csrf() };
    headers[AGENT_HEADER] = 'webmcp';
    return fetch(url, {
      method: 'PUT', credentials: 'same-origin',
      headers: headers,
      body: JSON.stringify(payload || {})
    });
  }
  function getJson(url) {
    return fetch(url, { credentials: 'same-origin', headers: { 'Accept': 'application/json' } });
  }

  // --- Registry -------------------------------------------------------------
  function define(tool) { if (tool && tool.name) catalog[tool.name] = tool; }
  function defineAll(list) { (list || []).forEach(define); }

  function activate(names) {
    var c = mc();
    if (!hasApi()) return;
    (names || Object.keys(catalog)).forEach(function (name) {
      var t = catalog[name];
      if (!t || live[name]) return;
      var ac = (typeof AbortController !== 'undefined') ? new AbortController() : null;
      try {
        // registerTool(tool, { signal }) — second arg ignored by older builds,
        // which is why deactivate() also calls unregisterTool().
        c.registerTool({
          name: t.name,
          description: t.description,
          inputSchema: t.inputSchema || { type: 'object', properties: {} },
          annotations: t.annotations || {},
          execute: t.execute
        }, ac ? { signal: ac.signal } : undefined);
        live[name] = ac || true;
      } catch (e) {
        if (CFG.debug) console.warn('[WebMCP] register failed', name, e);
      }
    });
  }

  function deactivate(names) {
    var c = mc();
    (names || Object.keys(live)).forEach(function (name) {
      var ac = live[name];
      if (!ac) return;
      try { if (ac && ac.abort) ac.abort(); } catch (e) {}
      try { if (c && c.unregisterTool) c.unregisterTool(name); } catch (e) {}
      delete live[name];
    });
  }

  function ensureApi(cb) {
    if (hasApi() || !CFG.usePolyfill || !window.isSecureContext) { cb(); return; }
    var s = document.createElement('script');
    s.src = POLYFILL_URL;
    s.onload = cb;
    s.onerror = function () { if (CFG.debug) console.warn('[WebMCP] polyfill failed'); cb(); };
    document.head.appendChild(s);
  }

  window.AliothMCP = {
    config: CFG, supported: hasApi, surface: function () { return mc(); },
    csrf: csrf, ok: ok, fail: fail, t: tr, confirm: confirmAction,
    confirmBatch: confirmBatch, revokeAutopilot: revokeAutopilot,
    postForm: postForm, postFile: postFile, postJson: postJson, putJson: putJson, getJson: getJson,
    define: define, defineAll: defineAll, activate: activate, deactivate: deactivate,
    ensureApi: ensureApi, _catalog: catalog, _live: live
  };
})();
