const { validateUsername, USERNAME_MAX_LENGTH } = require('./usernameValidation') const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') const isUsernameTaken = async (UserModel, userName, excludeUserId) => { const existing = await UserModel.findOne({ user_name: { $regex: new RegExp(`^${escapeRegex(userName)}$`, 'i') } }) if (!existing) return false if (excludeUserId && existing._id.toString() === excludeUserId) return false return true } const splitBaseAndSuffix = (value) => { const match = value.match(/^(.*?)(\d+)$/) if (!match || !match[1]) { return { core: value, suffix: null } } return { core: match[1], suffix: Number.parseInt(match[2], 10) } } const trimToMax = (value) => value.length > USERNAME_MAX_LENGTH ? value.slice(0, USERNAME_MAX_LENGTH) : value /** پیشنهادهای مشابه: amiri2 → amiri3, amiri5, amiiri3, amiri334 */ const buildCandidates = (base) => { const { core, suffix } = splitBaseAndSuffix(base) const candidates = [] if (suffix !== null && !Number.isNaN(suffix)) { candidates.push(`${core}${suffix + 1}`) candidates.push(`${core}${suffix + 3}`) candidates.push(`${core}${suffix}${Math.floor(Math.random() * 9 + 1)}`) } const numericSuffixes = [3, 5, 7, 12, 21, 34, 99, 334, 567] numericSuffixes.forEach((num) => { candidates.push(`${core}${num}`) }) for (let i = 1; i < Math.min(core.length, 5); i += 1) { const ch = core[i] if (!ch) continue const doubled = `${core.slice(0, i)}${ch}${core.slice(i)}` candidates.push(`${doubled}${suffix !== null ? suffix + 1 : 3}`) candidates.push(`${doubled}${Math.floor(Math.random() * 90 + 10)}`) } candidates.push(`${core}_${suffix !== null ? suffix + 1 : 1}`) candidates.push(`${core}.${Math.floor(Math.random() * 900 + 100)}`) candidates.push(`${core}-${Math.floor(Math.random() * 90 + 10)}`) return [...new Set(candidates.map(trimToMax))] } const generateUsernameSuggestions = async ( UserModel, baseUsername, excludeUserId, count = 3 ) => { const base = baseUsername.trim().toLowerCase() const suggestions = [] const tried = new Set([base]) for (let attempt = 0; attempt < 80 && suggestions.length < count; attempt += 1) { const candidates = buildCandidates(base) for (const candidate of candidates) { if (suggestions.length >= count) break if (tried.has(candidate)) continue tried.add(candidate) if (validateUsername(candidate)) continue const taken = await isUsernameTaken(UserModel, candidate, excludeUserId) if (!taken) suggestions.push(candidate) } if (suggestions.length < count) { const fallback = trimToMax(`${base}${Math.floor(Math.random() * 9000 + 1000)}`) if (!tried.has(fallback) && !validateUsername(fallback)) { tried.add(fallback) const taken = await isUsernameTaken(UserModel, fallback, excludeUserId) if (!taken) suggestions.push(fallback) } } } return suggestions } module.exports = { generateUsernameSuggestions, isUsernameTaken }