/* ── Lightweight input validation middleware (no external deps) ─────────── */

/**
 * validate(schema) — Express middleware factory.
 *
 * Schema format:
 *   { body: { field: { type, required, min, max, minLen, maxLen, match, oneOf, custom } },
 *     params: { ... },
 *     query:  { ... } }
 *
 * Supported rule keys:
 *   type      — 'string' | 'number' | 'boolean' | 'object' | 'array'
 *   required  — true/false  (default false)
 *   min / max — for numbers
 *   minLen / maxLen — for strings
 *   match     — RegExp
 *   oneOf     — array of allowed values
 *   custom    — fn(value) => string|null  (return error string or null)
 */
function validate(schema) {
  return (req, res, next) => {
    const errors = [];

    for (const source of ['body', 'params', 'query']) {
      const rules = schema[source];
      if (!rules) continue;
      const data = req[source] || {};

      for (const [field, rule] of Object.entries(rules)) {
        const val = data[field];
        const label = `${source}.${field}`;

        // Required check
        if (rule.required && (val === undefined || val === null || val === '')) {
          errors.push(`${label} обязателен`);
          continue;
        }

        // Skip optional absent fields
        if (val === undefined || val === null) continue;

        // Type check
        if (rule.type) {
          const t = rule.type;
          if (t === 'array' && !Array.isArray(val)) {
            errors.push(`${label} должен быть массивом`);
            continue;
          }
          if (t !== 'array' && typeof val !== t) {
            errors.push(`${label} должен быть типа ${t}`);
            continue;
          }
        }

        // Number constraints
        if (typeof val === 'number') {
          if (rule.min !== undefined && val < rule.min) errors.push(`${label} минимум ${rule.min}`);
          if (rule.max !== undefined && val > rule.max) errors.push(`${label} максимум ${rule.max}`);
          if (rule.integer && !Number.isInteger(val)) errors.push(`${label} должен быть целым числом`);
        }

        // String constraints
        if (typeof val === 'string') {
          if (rule.minLen !== undefined && val.length < rule.minLen) errors.push(`${label} минимум ${rule.minLen} символов`);
          if (rule.maxLen !== undefined && val.length > rule.maxLen) errors.push(`${label} максимум ${rule.maxLen} символов`);
          if (rule.match && !rule.match.test(val)) errors.push(`${label} неверный формат`);
        }

        // Enum
        if (rule.oneOf && !rule.oneOf.includes(val)) {
          errors.push(`${label} должен быть одним из: ${rule.oneOf.join(', ')}`);
        }

        // Custom validator
        if (rule.custom) {
          const err = rule.custom(val);
          if (err) errors.push(`${label}: ${err}`);
        }
      }
    }

    if (errors.length) {
      return res.status(400).json({ error: errors.join('; ') });
    }
    next();
  };
}

module.exports = validate;
