// Small shared validators used by the Wave 1 block schemas. Each block's schema
// composes these and returns a flat array of error strings; validateBlocks
// prefixes each with the block path (so 'text is required' becomes
// 'blocks[2].props.text is required'). Phrase messages to read well after that
// prefix — start with the prop name.

/** @returns {boolean} true if v is a non-empty (after trim) string. */
function isNonEmptyString(v) {
  return typeof v === 'string' && v.trim().length > 0
}

/**
 * Accept a same-origin relative URL ("/uploads/x.png", "/wiki/foo") or an
 * absolute http/https URL. Rejects javascript:, data:, protocol-relative
 * ("//evil"), and anything else — the block renderers drop these into hrefs/src
 * so this is a security boundary, not just a format check.
 * @param {unknown} v
 * @returns {boolean}
 */
function isSafeUrl(v) {
  if (typeof v !== 'string' || v.trim() === '') return false
  const s = v.trim()
  if (s.startsWith('//')) return false // protocol-relative — ambiguous origin
  if (s.startsWith('/')) return true // same-origin relative
  try {
    const u = new URL(s)
    return u.protocol === 'http:' || u.protocol === 'https:'
  } catch {
    return false
  }
}

/**
 * Build an enum validator for a prop.
 * @param {string} name prop name (for the message)
 * @param {string[]} allowed
 * @returns {(v: unknown) => string|null} error string or null
 */
function oneOf(name, allowed) {
  return (v) => (allowed.includes(v) ? null : `${name} must be one of ${allowed.join(', ')}`)
}

/**
 * Validate a required text prop: present, non-empty, within maxLen.
 * @returns {string|null}
 */
function requiredText(name, v, maxLen) {
  if (!isNonEmptyString(v)) return `${name} is required`
  if (v.length > maxLen) return `${name} must be at most ${maxLen} characters`
  return null
}

/**
 * Validate an optional text prop: if present it must be a string within maxLen.
 * @returns {string|null}
 */
function optionalText(name, v, maxLen) {
  if (v === undefined || v === null || v === '') return null
  if (typeof v !== 'string') return `${name} must be a string`
  if (v.length > maxLen) return `${name} must be at most ${maxLen} characters`
  return null
}

/**
 * Reject any prop key not in `allowed`. Keeps a block's props tight so nothing
 * unexpected is smuggled through and stored.
 * @returns {string[]} error strings
 */
function onlyKeys(props, allowed) {
  const errors = []
  for (const key of Object.keys(props)) {
    if (!allowed.includes(key)) errors.push(`${key} is not an allowed prop`)
  }
  return errors
}

module.exports = {
  isNonEmptyString,
  isSafeUrl,
  oneOf,
  requiredText,
  optionalText,
  onlyKeys,
}
