import { WorkspaceMemberRole } from "@mosaic/shared";

/**
 * Allowed characters in the local part of an email per RFC 5322.
 * Simple character class with anchors -- no backtracking risk.
 */
const LOCAL_PART_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+$/;

/**
 * Checks whether a single character is alphanumeric (a-z, A-Z, 0-9).
 */
function isAlphanumeric(ch: string): boolean {
  const code = ch.charCodeAt(0);
  return (
    (code >= 48 && code <= 57) || // 0-9
    (code >= 65 && code <= 90) || // A-Z
    (code >= 97 && code <= 122) // a-z
  );
}

/**
 * Validates a single domain label per RFC 5321.
 * - 1 to 63 characters
 * - Starts and ends with alphanumeric
 * - Middle characters may include hyphens
 * Entirely programmatic to avoid regex backtracking concerns.
 */
function isValidDomainLabel(label: string): boolean {
  if (label.length === 0 || label.length > 63) {
    return false;
  }
  if (!isAlphanumeric(label.charAt(0))) {
    return false;
  }
  if (label.length > 1 && !isAlphanumeric(label.charAt(label.length - 1))) {
    return false;
  }
  for (let i = 1; i < label.length - 1; i++) {
    const ch = label.charAt(i);
    if (!isAlphanumeric(ch) && ch !== "-") {
      return false;
    }
  }
  return true;
}

/**
 * Validates an email address using RFC 5322-aligned rules.
 * Uses programmatic splitting with bounded checks per segment
 * to avoid ReDoS vulnerabilities from complex single-pass patterns.
 */
export function isValidEmail(email: string): boolean {
  if (!email || email.length > 254) {
    return false;
  }

  const atIndex = email.indexOf("@");
  if (atIndex < 1 || atIndex === email.length - 1) {
    return false;
  }

  // Ensure only one @ sign
  if (email.slice(atIndex + 1).includes("@")) {
    return false;
  }

  const localPart = email.slice(0, atIndex);
  const domain = email.slice(atIndex + 1);

  // Validate local part (max 64 chars per RFC 5321)
  if (localPart.length > 64 || !LOCAL_PART_REGEX.test(localPart)) {
    return false;
  }

  // Validate domain: split into labels and check each
  const labels = domain.split(".");
  if (labels.length < 2) {
    return false;
  }

  return labels.every(isValidDomainLabel);
}

const VALID_ROLES = new Set<string>(Object.values(WorkspaceMemberRole));

/**
 * Validates a string value against the WorkspaceMemberRole enum.
 * Returns the validated role if it matches a known enum value,
 * or falls back to WorkspaceMemberRole.MEMBER if the value is invalid.
 */
export function toWorkspaceMemberRole(value: string): WorkspaceMemberRole {
  if (VALID_ROLES.has(value)) {
    return value as WorkspaceMemberRole;
  }
  return WorkspaceMemberRole.MEMBER;
}
