/**
 * Client-side validation. `renderSchemasScript` bakes the discovered schema
 * descriptors into `window.__wireSchemas`; `VALIDATE_RUNTIME` is a generic,
 * eval-free validator that reads them and validates every `form[data-schema]`
 * on submit and blur, writing messages into `[data-error="<field>"]` elements.
 * The rule logic mirrors `checkField`/`applyRule` in index.ts.
 */

import type { SchemaDescriptor } from "./index.ts";

/** `window.__wireSchemas = { name: descriptor, ... }` for the client validator. */
export function renderSchemasScript(descriptors: Record<string, SchemaDescriptor>): string {
  return `window.__wireSchemas=${JSON.stringify(descriptors)};`;
}

export const VALIDATE_RUNTIME = String.raw`
(function () {
  var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  var URL_RE = /^https?:\/\/[^\s/$.?#][^\s]*$/i;
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

  function applyRule(type, r, value) {
    if (r.kind === "min") {
      if (type === "string") return String(value).length < r.n ? (r.message || ("Must be at least " + r.n + " characters")) : null;
      return value < r.n ? (r.message || ("Must be at least " + r.n)) : null;
    }
    if (r.kind === "max") {
      if (type === "string") return String(value).length > r.n ? (r.message || ("Must be at most " + r.n + " characters")) : null;
      return value > r.n ? (r.message || ("Must be at most " + r.n)) : null;
    }
    if (r.kind === "length") return String(value).length !== r.n ? (r.message || ("Must be exactly " + r.n + " characters")) : null;
    if (r.kind === "email") return EMAIL.test(String(value)) ? null : (r.message || "Must be a valid email");
    if (r.kind === "url") return URL_RE.test(String(value)) ? null : (r.message || "Must be a valid URL");
    if (r.kind === "uuid") return UUID_RE.test(String(value)) ? null : (r.message || "Must be a valid UUID");
    if (r.kind === "date") return isNaN(Date.parse(String(value))) ? (r.message || "Must be a valid date") : null;
    if (r.kind === "oneOf") return r.values.indexOf(value) !== -1 ? null : (r.message || ("Must be one of: " + r.values.join(", ")));
    if (r.kind === "pattern") { try { return new RegExp(r.source, r.flags || "").test(String(value)) ? null : (r.message || "Invalid format"); } catch (e) { return null; } }
    if (r.kind === "integer") return Number.isInteger(value) ? null : (r.message || "Must be a whole number");
    return null;
  }

  function checkField(desc, raw) {
    if (desc.type === "boolean") {
      var b = raw === true || raw === "true" || raw === "on";
      return (!desc.optional && !b) ? (desc.requiredMessage || "Required") : null;
    }
    var pre = desc.trim && typeof raw === "string" ? raw.trim() : raw;
    var empty = pre === undefined || pre === null || pre === "";
    if (empty) return desc.optional ? null : (desc.requiredMessage || "Required");
    var value;
    if (desc.type === "number") { value = Number(pre); if (isNaN(value)) return "Must be a number"; }
    else value = String(pre);
    for (var i = 0; i < desc.rules.length; i++) {
      var err = applyRule(desc.type, desc.rules[i], value);
      if (err) return err;
    }
    return null;
  }

  function rawValue(form, name) {
    var el = form.elements[name];
    if (!el) return undefined;
    return el.type === "checkbox" ? el.checked : el.value;
  }

  function showError(form, name, err) {
    var box = form.querySelector('[data-error="' + name + '"]');
    if (box) box.textContent = err || "";
    var el = form.elements[name];
    if (el && el.setAttribute) {
      if (err) { el.setAttribute("aria-invalid", "true"); if (el.classList) el.classList.add("wire-invalid"); }
      else { el.removeAttribute("aria-invalid"); if (el.classList) el.classList.remove("wire-invalid"); }
    }
  }

  function validateForm(form, schema) {
    var errors = 0;
    Object.keys(schema.fields).forEach(function (name) {
      var err = checkField(schema.fields[name], rawValue(form, name));
      if (err) errors++;
      showError(form, name, err);
    });
    return errors;
  }

  function collect(form) {
    var out = {};
    for (var i = 0; i < form.elements.length; i++) {
      var el = form.elements[i];
      if (!el.name) continue;
      if (el.type === "checkbox") out[el.name] = el.checked;
      else if (el.type === "radio") { if (el.checked) out[el.name] = el.value; }
      else out[el.name] = el.value;
    }
    return out;
  }

  function onSuccess(form, data) {
    var redirect = form.getAttribute("data-redirect") || (data && data.redirect);
    if (redirect) {
      // Prefer client-side navigation (no full reload) when it is available.
      if (window.__wrnexusNavigate) window.__wrnexusNavigate(redirect);
      else location.assign(redirect);
      return;
    }
    var box = form.querySelector("[data-success]");
    if (box) { box.textContent = box.getAttribute("data-success") || "Success"; box.hidden = false; }
    form.reset();
    form.dispatchEvent(new CustomEvent("wire:success", { detail: data, bubbles: true }));
  }

  function csrfHeader() {
    var m = document.cookie.match(/(?:^|;\s*)wire-csrf=([^;]+)/);
    return m ? { "x-csrf-token": decodeURIComponent(m[1]) } : {};
  }

  function submitForm(form, schema) {
    var method = (form.getAttribute("method") || "post").toUpperCase();
    var action = form.getAttribute("action") || location.pathname;
    var btns = form.querySelectorAll("[type=submit]");
    btns.forEach(function (b) { b.disabled = true; });
    var headers = { "content-type": "application/json", accept: "application/json" };
    var csrf = csrfHeader();
    for (var k in csrf) headers[k] = csrf[k];
    fetch(action, {
      method: method,
      headers: headers,
      credentials: "same-origin",
      body: JSON.stringify(collect(form)),
    })
      .then(function (res) {
        return res.json().catch(function () { return {}; }).then(function (data) { return { res: res, data: data }; });
      })
      .then(function (r) {
        if (r.res.ok) { onSuccess(form, r.data); return; }
        // Surface server-side field errors (e.g. "email already taken").
        var errors = r.data && r.data.errors;
        if (errors) Object.keys(errors).forEach(function (f) { showError(form, f, errors[f]); });
        form.dispatchEvent(new CustomEvent("wire:error", { detail: r.data, bubbles: true }));
      })
      .catch(function () {
        form.dispatchEvent(new CustomEvent("wire:error", { detail: { network: true }, bubbles: true }));
      })
      .then(function () { btns.forEach(function (b) { b.disabled = false; }); });
  }

  function bind(form) {
    if (form.__wireValidateBound) return;
    form.__wireValidateBound = 1;
    var name = form.getAttribute("data-schema");
    var schema = (window.__wireSchemas || {})[name];
    if (!schema) return;
    form.addEventListener("submit", function (e) {
      e.preventDefault();
      if (validateForm(form, schema) > 0) return; // client-invalid: stay put, errors shown
      submitForm(form, schema);
    });
    form.addEventListener("blur", function (e) {
      var t = e.target;
      if (t && t.name && schema.fields[t.name]) {
        showError(form, t.name, checkField(schema.fields[t.name], t.type === "checkbox" ? t.checked : t.value));
      }
    }, true);
  }

  function init(root) {
    (root || document).querySelectorAll("form[data-schema]").forEach(bind);
  }

  window.__wireValidate = { init: init };
  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", function () { init(document); });
  else init(document);
})();
`.trim();
