package social

import (
	"errors"
	"fmt"
	"regexp"
	"strings"

	"mvdan.cc/xurls/v2"
)

// ErrForbiddenContent is returned when a chat message contains a web link, email
// address or phone number — including lightly obfuscated forms. The chat is for
// quick in-game reactions, not for exchanging contact details.
var ErrForbiddenContent = errors.New("social: message contains a link, email or phone number")

// phoneDigits is the minimum run of consecutive digits treated as a phone number.
const phoneDigits = 7

// relaxedURL matches URLs, bare domains and email addresses (xurls relaxed mode).
var relaxedURL = xurls.Relaxed()

// spelledSeparators rewrites the words people use to dodge a detector back into
// their symbols, so "gmail dot com" and "user at host" are still caught.
var spelledSeparators = strings.NewReplacer(
	" dot ", ".", "(dot)", ".", "[dot]", ".", " punto ", ".", " точка ", ".",
	" at ", "@", "(at)", "@", "[at]", "@", " собака ", "@", " собачка ", "@",
)

// leet folds digits and symbols commonly substituted for letters, so "g00gl3.com"
// is recognised. It leaves '.' and '@' untouched, since those carry the link and
// email structure the matcher relies on.
var leet = strings.NewReplacer(
	"0", "o", "1", "i", "3", "e", "4", "a", "5", "s", "7", "t", "8", "b",
	"$", "s", "!", "i", "|", "l",
)

// spaceAroundPunct collapses whitespace around '.' and '@' so "gmail . com" and
// "user @ host" close up into a detectable address.
var spaceAroundPunct = regexp.MustCompile(`\s*([.@])\s*`)

// phoneSeparators are the characters stripped before counting a digit run, so a
// grouped number like "8 (900) 123-45-67" collapses to a single run.
var phoneSeparators = regexp.MustCompile(`[\s\-.()+]`)

// Clean reports whether body is free of links, emails and phone numbers, returning
// ErrForbiddenContent (naming the category) otherwise. Detection is best-effort
// over a short, rune-limited message: it folds common letter/digit obfuscation
// and spelled-out separators before matching, but does not claim to defeat every
// evasion.
func Clean(body string) error {
	lower := strings.ToLower(body)
	if hasLinkOrEmail(lower) {
		return fmt.Errorf("%w: link or email", ErrForbiddenContent)
	}
	if hasPhone(lower) {
		return fmt.Errorf("%w: phone number", ErrForbiddenContent)
	}
	return nil
}

// hasLinkOrEmail matches the lower-cased text both as written and after folding
// spelled separators, spacing and leet substitutions.
func hasLinkOrEmail(lower string) bool {
	if relaxedURL.MatchString(lower) {
		return true
	}
	deobfuscated := leet.Replace(spaceAroundPunct.ReplaceAllString(spelledSeparators.Replace(lower), "$1"))
	return relaxedURL.MatchString(deobfuscated)
}

// hasPhone reports a run of phoneDigits or more digits once phone-style separators
// are removed.
func hasPhone(lower string) bool {
	stripped := phoneSeparators.ReplaceAllString(lower, "")
	run := 0
	for _, r := range stripped {
		if r >= '0' && r <= '9' {
			run++
			if run >= phoneDigits {
				return true
			}
			continue
		}
		run = 0
	}
	return false
}
