const DEFAULT_LABEL = "Field";

const normalizePath = (path) => {
  return String(path || "")
    .replace(/\[(\d+)\]/g, ".$1")
    .replace(/\[\*\]/g, ".*")
    .replace(/\[\]/g, ".*")
    .split(".")
    .filter(Boolean);
};

const toLabel = (path) => {
  if (!path) {
    return DEFAULT_LABEL;
  }

  return String(path)
    .replace(/\[\d+\]/g, "")
    .replace(/\.\*/g, "")
    .replace(/[._-]+/g, " ")
    .replace(/\s+/g, " ")
    .trim()
    .replace(/^./, (ch) => ch.toUpperCase());
};

const collectMatches = (value, segments, currentPath = [], results = []) => {
  if (segments.length === 0) {
    results.push({
      path: currentPath.join("."),
      value,
    });
    return results;
  }

  const [segment, ...rest] = segments;

  if (segment === "*") {
    if (!Array.isArray(value)) {
      return results;
    }

    value.forEach((item, index) => {
      collectMatches(item, rest, [...currentPath, String(index)], results);
    });

    return results;
  }

  if (value === null || value === undefined) {
    return results;
  }

  if (typeof value !== "object") {
    return results;
  }

  if (!Object.prototype.hasOwnProperty.call(value, segment)) {
    return results;
  }

  return collectMatches(value[segment], rest, [...currentPath, segment], results);
};

const countWords = (value) => {
  const normalized = String(value || "")
    .replace(/\s+/g, " ")
    .trim();

  if (!normalized) {
    return 0;
  }

  return normalized.split(" ").length;
};

const buildErrorMessage = (label, path, maxLength, maxWords) => {
  const target = path ? `${label} (${path})` : label;

  if (maxLength && maxWords) {
    return `${target} must not exceed ${maxLength} characters or ${maxWords} words.`;
  }

  if (maxLength) {
    return `${target} must not exceed ${maxLength} characters.`;
  }

  return `${target} must not exceed ${maxWords} words.`;
};

const validateLengthRules = (payload, rules = []) => {
  const errors = [];

  for (const rule of rules) {
    const paths = Array.isArray(rule.paths)
      ? rule.paths
      : [rule.path].filter(Boolean);

    for (const path of paths) {
      const segments = normalizePath(path);
      const matches = collectMatches(payload, segments);

      for (const match of matches) {
        if (typeof match.value !== "string") {
          continue;
        }

        const normalized = match.value.trim();
        if (!normalized && rule.allowEmpty !== false) {
          continue;
        }

        const actualLength = normalized.length;
        const actualWords = countWords(normalized);
        const maxLength = Number(rule.maxLength);
        const maxWords = Number(rule.maxWords);
        const exceedsLength = Number.isFinite(maxLength) && maxLength > 0 && actualLength > maxLength;
        const exceedsWords = Number.isFinite(maxWords) && maxWords > 0 && actualWords > maxWords;

        if (!exceedsLength && !exceedsWords) {
          continue;
        }

        const label = rule.label || toLabel(path);
        errors.push({
          path: match.path,
          label,
          message: buildErrorMessage(label, match.path, exceedsLength ? maxLength : null, exceedsWords ? maxWords : null),
          maxLength: Number.isFinite(maxLength) ? maxLength : undefined,
          maxWords: Number.isFinite(maxWords) ? maxWords : undefined,
          actualLength,
          actualWords,
        });
      }
    }
  }

  return {
    valid: errors.length === 0,
    errors,
  };
};

const summarizeLengthErrors = (validation, limit = 1) => {
  if (!validation || !Array.isArray(validation.errors) || validation.errors.length === 0) {
    return "";
  }

  return validation.errors
    .slice(0, limit)
    .map((error) => error.message)
    .join(" ");
};

module.exports = {
  validateLengthRules,
  summarizeLengthErrors,
  toLabel,
};
