<?php
// components/Common/ClientValidation.php
namespace Components\Common;

class ClientValidation
{
    /**
     * Returns a <script> block that defines a shared client-side validator helper.
     * The helper exposes window.MBZValidator.create({ schema, getValue, getElement, isVisible })
     * and returns { clientErrors, validateField(name), validateAll() }.
     *
     * - schema: object mapping fieldName => { type, required, min, max, compare: [...] }
     * - getValue(name): fn that returns current value for a field (string or array); booleans should use 'on' / ''.
     * - getElement(name): fn that returns the input element in DOM; if not found (e.g., hidden via x-if), validation is skipped.
     * - isVisible(name): optional fn(name) => boolean; if provided and returns false, validation is skipped for that field.
     */
    public static function script(): string
    {
        ob_start();
        ?>
<script>
(function(){
  if (window.MBZValidator) return; // singleton
  window.MBZValidator = {
    create: function(cfg){
      var schema = cfg && cfg.schema ? cfg.schema : {};
      var getValue = (cfg && typeof cfg.getValue === 'function') ? cfg.getValue : function(){ return ''; };
      var getElement = (cfg && typeof cfg.getElement === 'function') ? cfg.getElement : function(){ return null; };
      var isVisible = (cfg && typeof cfg.isVisible === 'function') ? cfg.isVisible : null;
      var clientErrors = {};
      function _isIntString(s){ return /^-?\d+$/.test(String(s)); }
      function _parseDateToTs(val){
        if (val === undefined || val === null) return null;
        var s = String(val).trim(); if (!s) return null;
        if (/^\d{4}-\d{2}-\d{2}$/.test(s)) { var d = new Date(s + 'T00:00:00'); if (!isNaN(d)) return Math.floor(d.getTime()/1000); }
        var t = Date.parse(s); if (!isNaN(t)) return Math.floor(t/1000);
        return null;
      }
      function validateField(name){
        var r = schema[name];
        var el = getElement(name);
        if (isVisible && !isVisible(name)) { clientErrors[name] = []; return true; }
        if (!el) { clientErrors[name] = []; return true; }
        var v = getValue(name);
        var errs = [];
        if (!r) { clientErrors[name] = []; return true; }
        if (r.required && String(v).trim() === '') { errs.push('This field is required.'); }
        if (r.type === 'email' && String(v).trim() !== '') {
          var emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
          if (!emailRe.test(String(v))) { errs.push('Please enter a valid email address.'); }
          var len = String(v).length;
          if (r.min !== undefined && _isIntString(r.min) && len < parseInt(r.min,10)) { errs.push('Minimum length is ' + r.min + '.'); }
          if (r.max !== undefined && _isIntString(r.max) && len > parseInt(r.max,10)) { errs.push('Maximum length is ' + r.max + '.'); }
        }
        if (r.type === 'integer' && String(v).trim() !== '') {
          if (!_isIntString(v)) { errs.push('Please enter a valid integer.'); }
          var num = parseInt(v, 10);
          if (r.min !== undefined && _isIntString(r.min) && num < parseInt(r.min,10)) { errs.push('Minimum value is ' + r.min + '.'); }
          if (r.max !== undefined && _isIntString(r.max) && num > parseInt(r.max,10)) { errs.push('Maximum value is ' + r.max + '.'); }
        }
        if ((r.type === 'date' || r.type === 'date_time') && String(v).trim() !== '') {
          var tsv = _parseDateToTs(v);
          if (tsv === null) {
            errs.push(r.type === 'date_time' ? 'Please enter a valid date and time.' : 'Please enter a valid date.');
          } else {
            if (r.min !== undefined && String(r.min).trim() !== '') {
              var tmin = _parseDateToTs(r.min);
              if (tmin !== null && tsv < tmin) { errs.push('Date must be on or after ' + String(r.min) + '.'); }
            }
            if (r.max !== undefined && String(r.max).trim() !== '') {
              var tmax = _parseDateToTs(r.max);
              if (tmax !== null && tsv > tmax) { errs.push('Date must be on or before ' + String(r.max) + '.'); }
            }
          }
        }
        if (Array.isArray(r.compare)){
          for (var i=0;i<r.compare.length;i++){
            var cmp = r.compare[i];
            var otherName = cmp.field; if (!otherName) continue;
            var op = (cmp.op || 'gte').toLowerCase();
            var offset = cmp.offset || 0;
            var left = v; var right = getValue(otherName);
            var compared = false;
            if (_isIntString(left) && _isIntString(right)){
              left = parseInt(left,10); right = parseInt(right,10) + (_isIntString(offset) ? parseInt(offset,10) : 0); compared = true;
            } else {
              var lTs = _parseDateToTs(left); var rTs = _parseDateToTs(right);
              if (lTs !== null && rTs !== null){ var days = _isIntString(offset) ? parseInt(offset,10) : 0; left = lTs; right = rTs + days*86400; compared = true; }
            }
            if (compared){
              var ok = true;
              switch(op){
                case 'gt': case '>': ok = (left > right); break;
                case 'gte': case '>=': ok = (left >= right); break;
                case 'lt': case '<': ok = (left < right); break;
                case 'lte': case '<=': ok = (left <= right); break;
                case 'eq': case '==': ok = (left == right); break;
                case 'ne': case '!=': ok = (left != right); break;
              }
              if (!ok){ errs.push(cmp.message || ('Value must be ' + op + ' ' + otherName + (_isIntString(offset) ? (' + ' + offset) : '') + '.')); }
            }
          }
        }
        clientErrors[name] = errs;
        return errs.length === 0;
      }
      function validateAll(){
        var ok = true;
        for (var name in schema){ if (!validateField(name)) ok = false; }
        return ok;
      }
      return { clientErrors: clientErrors, validateField: validateField, validateAll: validateAll };
    }
  };
})();
</script>
<?php
        return ob_get_clean();
    }
}
