package domain

import "time"

// Intent score weights — spec §3.3. Band thresholds stay locked at 80／50.
const (
	WeightAuthenticity = 30
	WeightIntent       = 30
	WeightRegion       = 15
	WeightFreshness    = 15
	WeightFit          = 10
)

// Freshness scoring decays after 14 days. Hard-reject is wider so Exa/Threads
// hits from the last month still reach the inbox as stale, not as "found nothing".
const MaxFreshnessDays = 14
const StaleHardRejectDays = 30

// FreshnessScore maps age in hours to the 0–15 freshness dimension score.
func FreshnessScore(hours int) int {
	if hours > MaxFreshnessDays*24 {
		return 0
	}
	if hours < 0 {
		hours = 0
	}
	switch {
	case hours <= 24:
		return WeightFreshness
	case hours <= 72:
		// linear decay 24→72h: 15 → ~8
		return 8 + (WeightFreshness-8)*(72-hours)/(72-24)
	case hours <= MaxFreshnessDays*24:
		return 3
	default:
		return 0
	}
}

// FreshnessHoursSince returns whole hours between postedAt and now (unix ns).
func FreshnessHoursSince(postedAt, now int64) int {
	if postedAt <= 0 {
		return MaxFreshnessDays*24 + 1
	}
	if now <= 0 {
		now = time.Now().UTC().UnixNano()
	}
	if now < postedAt {
		return 0
	}
	h := int((now - postedAt) / int64(time.Hour))
	return h
}

// IsStaleHardReject is true when the post is older than a month.
// 14–30 day posts stay scoreable (low freshness) so a just-run patrol can
// still surface a real pain that Threads/Exa returned as an older result.
func IsStaleHardReject(postedAt, now int64) bool {
	if postedAt <= 0 {
		return false
	}
	return FreshnessHoursSince(postedAt, now) > StaleHardRejectDays*24
}

// SumReasonScores totals dimension scores (capped components assumed already).
func SumReasonScores(reasons []OpportunityReason) int {
	total := 0
	for _, r := range reasons {
		total += r.Score
	}
	if total > 100 {
		return 100
	}
	if total < 0 {
		return 0
	}
	return total
}
