// Shared helpers for `m.room.server_acl` server-name globs, used by both the
// Room Settings ACL editor and the `/acl` slash command so their validation and
// self-lockout detection stay identical.

/**
 * Validate a server-name glob for an ACL entry.
 *
 * Matrix ACL `allow`/`deny` entries are globs where `*` (any run of chars) and
 * `?` (single char) may appear ANYWHERE — e.g. `*`, `*.example.com`,
 * `1.2.3.*`, `10.0.0.?`, `*.evil.*`, `*bad*`. We therefore validate the *glob*
 * rather than a concrete hostname:
 *   - reject empty / whitespace-only
 *   - allow only hostname/IP chars plus the wildcards `*` and `?`
 *     (letters, digits, dots, hyphens, colons for ports/IPv6 — NO underscore)
 *   - reject consecutive/leading/trailing dots (`...`, `.foo`, `foo.`)
 *   - reject entries with no alphanumeric or wildcard char (bare `-`, lone `:`)
 */
export function isValidServerPattern(value: string): boolean {
  const v = value.trim();
  if (!v) return false;
  // Only hostname/IP glob chars — wildcards may appear at any position.
  if (!/^[A-Za-z0-9.:*?-]+$/.test(v)) return false;
  // Structural rules for the dotted parts.
  if (v.startsWith('.') || v.endsWith('.') || v.includes('..')) return false;
  // Must carry actual signal — reject pure punctuation like `-`, `:` or `-.-`.
  if (!/[A-Za-z0-9*?]/.test(v)) return false;
  return true;
}

/**
 * Convert an ACL glob (`*` = any run, `?` = single char) to an anchored RegExp,
 * escaping every other regex metacharacter. Used only for local self-ban
 * detection — never sent to the server.
 */
export function globToRegExp(glob: string): RegExp {
  const escaped = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&');
  const pattern = escaped.replace(/\*/g, '.*').replace(/\?/g, '.');
  // Case-INsensitive: Synapse's glob_to_regex uses IGNORECASE and hostnames are
  // case-insensitive, so a deny like `MATRIX.foo.org` must still be detected as
  // self-banning `matrix.foo.org` (otherwise the warning is a false negative).
  return new RegExp(`^${pattern}$`, 'i');
}

export function matchesAnyGlob(domain: string, globs: string[]): boolean {
  return globs.some((glob) => {
    try {
      return globToRegExp(glob).test(domain);
    } catch {
      return false;
    }
  });
}
