"""HeyLead constants — limits, defaults, tier definitions."""

from __future__ import annotations

# ──────────────────────────────────────────────
# Rate Limits (Simple Adaptive)
# ──────────────────────────────────────────────

INITIAL_DAILY_INVITATIONS = 15       # Start conservative for new accounts
MAX_DAILY_INVITATIONS = 50           # Cap (regular LinkedIn)
MAX_DAILY_INVITATIONS_SALES_NAV = 80 # Cap (Sales Navigator)
WEEKLY_INVITATION_CAP = 100          # Hard weekly ceiling
WEEKLY_INVITATION_CAP_SALES_NAV = 200
DAILY_MESSAGES = 50                  # Messages to existing connections

# Adaptive thresholds
ACCEPTANCE_RATE_RAMP_UP = 0.30       # >30% acceptance → increase limit by 5
ACCEPTANCE_RATE_PULL_BACK = 0.15     # <15% acceptance → decrease limit by 5
RAMP_STEP = 5                        # How much to change per adjustment
MIN_DAILY_LIMIT = 10                 # Floor (never go below)
BLOCK_HALVE_FLOOR = 5               # Minimum after halving on block

# Timing
MIN_DELAY_MINUTES = 15               # Min gap between invitations
MAX_DELAY_MINUTES = 45               # Max gap (randomized)
DEFAULT_START_HOUR = 0               # Autonomous bot: 24/7
DEFAULT_END_HOUR = 24                # Autonomous bot: 24/7
DEFAULT_ACTIVE_DAYS = [0, 1, 2, 3, 4, 5, 6]  # All days

# Message limits
INVITATION_NOTE_MAX_CHARS = 200      # Regular LinkedIn
INVITATION_NOTE_MAX_CHARS_SALES_NAV = 300  # Sales Navigator

# ──────────────────────────────────────────────
# Free Tier Caps
# ──────────────────────────────────────────────
# TODO(release): restore production limits before public release:
#   INVITATIONS=50, MESSAGES=20, CAMPAIGNS=1, CONTACTS=30,
#   ICP_GEN=1, ICP_V2=3, FOLLOWUPS=2, ENGAGEMENTS=30, ACCOUNTS=1

FREE_MONTHLY_INVITATIONS = 9999
FREE_MONTHLY_MESSAGES = 9999
FREE_MAX_CAMPAIGNS = 9999
FREE_MAX_CONTACTS_ANALYZED = 9999
FREE_MAX_ICP_GENERATIONS = 9999
FREE_MAX_ICP_V2_GENERATIONS = 9999
FREE_MAX_FOLLOWUPS = 9999
FREE_MAX_ENGAGEMENTS = 9999
FREE_MAX_LINKEDIN_ACCOUNTS = 9999

# ──────────────────────────────────────────────
# Pro Tier
# ──────────────────────────────────────────────

PRO_PRICE_MONTHLY = 29               # USD
PRO_PRICE_ANNUAL_MONTHLY = 24        # USD (billed annually)
PRO_MAX_FOLLOWUPS = 5
PRO_MAX_LINKEDIN_ACCOUNTS = 5
PRO_FOLLOWUP_SCHEDULE_DAYS = [1, 7, 14, 21, 28]

# ──────────────────────────────────────────────
# Engagement Limits
# ──────────────────────────────────────────────

COMMENT_MAX_CHARS = 200              # Keep comments concise for best engagement
DAILY_ENGAGEMENT_LIMIT = 20          # Max engagements per day (LinkedIn safety)
MIN_ENGAGEMENT_DELAY_MINUTES = 5     # Min gap between engagements
AUTO_REACT_PROBABILITY = 0.30        # 30% react / 70% comment in auto mode

# ──────────────────────────────────────────────
# LLM Defaults
# ──────────────────────────────────────────────

DEFAULT_LLM_PRIORITY = ["gemini", "claude", "openai"]
DEFAULT_GEMINI_MODEL = "gemini-2.0-flash"
DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-20250514"
DEFAULT_OPENAI_MODEL = "gpt-4o-mini"

GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models"
CLAUDE_API_URL = "https://api.anthropic.com/v1/messages"
OPENAI_API_URL = "https://api.openai.com/v1/chat/completions"

# ──────────────────────────────────────────────
# Unipile API
# ──────────────────────────────────────────────

UNIPILE_POLL_INTERVAL_SECONDS = 4   # How often to check for account during setup
UNIPILE_POLL_TIMEOUT_SECONDS = 120  # Max wait for LinkedIn OAuth completion

# ──────────────────────────────────────────────
# Paths
# ──────────────────────────────────────────────

HEYLEAD_DIR_NAME = ".heylead"
CONFIG_FILE = "config.json"
DB_DIR = "data"
DB_FILE = "heylead.db"
AUTH_DIR = "auth"
COOKIE_FILE = "linkedin.enc"  # Legacy — kept for cleanup of old installs
LOG_DIR = "logs"
LOG_FILE = "heylead.log"
KB_DIR = "knowledge-base"
EMBEDDINGS_DIR = "embeddings"

# ICP generation
DEFAULT_EMBEDDING_MODEL = "all-MiniLM-L6-v2"
FIRECRAWL_API_URL = "https://api.firecrawl.dev/v1"
SERPER_API_URL = "https://google.serper.dev/news"

# ──────────────────────────────────────────────
# Backend Defaults
# ──────────────────────────────────────────────

DEFAULT_BACKEND_URL = "https://heylead.dev"
LOGIN_URL_PATH = "/auth/login-url"
GEMINI_KEY_URL = "https://aistudio.google.com/apikey"

# ──────────────────────────────────────────────
# Misc
# ──────────────────────────────────────────────

MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024  # 10 MB
LOG_BACKUP_COUNT = 3
COPILOT_APPROVAL_THRESHOLD = 10  # Auto-prompt for Autopilot after N approvals

# Tier enum-like
TIER_FREE = "free"
TIER_PRO = "pro"

# Campaign statuses
STATUS_ACTIVE = "active"
STATUS_PAUSED = "paused"
STATUS_COMPLETED = "completed"
STATUS_DRAFT = "draft"

# Warm-up enforcement
MIN_WARMUP_ENGAGEMENTS = 0           # Invites go out immediately; engagements run in parallel

# Outreach statuses
OUTREACH_PENDING = "pending"
OUTREACH_INVITED = "invited"
OUTREACH_CONNECTED = "connected"
OUTREACH_MESSAGED = "messaged"
OUTREACH_REPLIED = "replied"
OUTREACH_HOT_LEAD = "hot_lead"
OUTREACH_CLOSED_HAPPY = "closed_happy"
OUTREACH_CLOSED_UNHAPPY = "closed_unhappy"
OUTREACH_OPTED_OUT = "opted_out"
OUTREACH_REVIEW_PENDING = "review_pending"
OUTREACH_SKIPPED = "skipped"

# Sentiment labels
SENTIMENT_POSITIVE = "positive"
SENTIMENT_NEGATIVE = "negative"
SENTIMENT_QUESTION = "question"
SENTIMENT_NEUTRAL = "neutral"
SENTIMENT_OOO = "out_of_office"
SENTIMENT_OPT_OUT = "opt_out"

# ──────────────────────────────────────────────
# Scheduler (Sprint 17)
# ──────────────────────────────────────────────

# Loop intervals (seconds)
SCHEDULER_TICK_SECONDS = 60          # Main loop frequency
SCHEDULER_REPLY_CHECK_SECONDS = 300  # Check replies every 5 min
SCHEDULER_ENGAGEMENT_SECONDS = 600   # Schedule engagements every 10 min

# Action delays (seconds, randomized — matches original NestJS intervals)
INVITE_DELAY_MIN = 20 * 60           # 20 min between invitations
INVITE_DELAY_MAX = 40 * 60           # 40 min
FOLLOWUP_DELAY_MIN = 20 * 60         # 20 min between follow-up messages
FOLLOWUP_DELAY_MAX = 35 * 60         # 35 min
ENGAGEMENT_DELAY_MIN = 5 * 60        # 5 min between engagements
ENGAGEMENT_DELAY_MAX = 15 * 60       # 15 min
FOLLOW_DELAY_MIN = 10 * 60           # 10 min between follows
FOLLOW_DELAY_MAX = 25 * 60           # 25 min
DAILY_FOLLOW_LIMIT = 30              # Max follows per day (LinkedIn safety)
SCHEDULER_FOLLOW_SECONDS = 900       # Schedule follows every 15 min

# Retry & cleanup
SCHEDULER_MAX_RETRIES = 3            # Max retries per failed job
SCHEDULER_RETRY_DELAY = 300          # 5 min retry delay
SCHEDULER_CLEANUP_DAYS = 7           # Purge completed jobs after N days

# Inbound invitation auto-accept
INBOUND_CHECK_SECONDS = 900          # Check inbound invitations every 15 min
DAILY_INBOUND_ACCEPT_LIMIT = 50      # Max inbound accepts per day

# Skill endorsement warm-up
ENDORSE_DELAY_MIN = 10 * 60          # 10 min between endorsements
ENDORSE_DELAY_MAX = 25 * 60          # 25 min
DAILY_ENDORSE_LIMIT = 20             # Max endorsements per day (LinkedIn safety)
SCHEDULER_ENDORSE_SECONDS = 900      # Schedule endorsements every 15 min

# Stale invite withdrawal
STALE_INVITE_DAYS = 21               # Withdraw invites older than 21 days
WITHDRAW_CHECK_SECONDS = 3600        # Check stale invites every hour

# Job types
JOB_INVITE = "invite"
JOB_FOLLOWUP = "followup"
JOB_ENGAGE = "engage"
JOB_FOLLOW = "follow"
JOB_CHECK_REPLIES = "check_replies"
JOB_ACCEPT_INBOUND = "accept_inbound"
JOB_ENDORSE = "endorse"
JOB_WITHDRAW_INVITE = "withdraw_invite"
JOB_QUALIFY_INBOUND = "qualify_inbound"
JOB_CHECK_POST_COMMENTS = "check_post_comments"
JOB_BRAND_POST = "brand_post"
JOB_BRAND_ENGAGE = "brand_engage"
JOB_BRAND_ANALYZE = "brand_analyze"

# Signal collection jobs (v1.0)
JOB_COLLECT_KEYWORD_SIGNALS = "collect_keyword_signals"
JOB_SCAN_PROSPECT_POSTS = "scan_prospect_posts"
JOB_CLASSIFY_SIGNALS = "classify_signals"
JOB_COLLECT_PROFILE_VIEWS = "collect_profile_views"
JOB_DETECT_JOB_CHANGES = "detect_job_changes"
JOB_COLLECT_COMPETITOR_SIGNALS = "collect_competitor_signals"
JOB_COLLECT_HIRING_SIGNALS = "collect_hiring_signals"
JOB_COLLECT_NEWS_SIGNALS = "collect_news_signals"

# Job statuses
JOB_PENDING = "pending"
JOB_RUNNING = "running"
JOB_COMPLETED = "completed"
JOB_FAILED = "failed"

# Experiment analysis & A/B testing
SCHEDULER_AB_EVAL_SECONDS = 3600       # Evaluate A/B tests every hour
SCHEDULER_EXPERIMENT_SECONDS = 21600   # Run experiment analysis every 6 hours

# Inbound qualification pipeline
INBOUND_QUALIFY_SECONDS = 900          # Qualify new signals every 15 min
POST_COMMENT_CHECK_SECONDS = 1800      # Check post comments every 30 min
INBOUND_ENGAGE_CONFIDENCE = 0.7        # Auto-engage threshold
INBOUND_ASK_PURPOSE_CONFIDENCE = 0.4   # Ask-purpose threshold
DAILY_INBOUND_DM_LIMIT = 20           # Max discovery DMs per day
PUBLISHED_POST_MONITOR_DAYS = 7        # Monitor comments for 7 days

# ──────────────────────────────────────────────
# Brand Strategy Automation
# ──────────────────────────────────────────────

# Scheduling check intervals
BRAND_POST_CHECK_SECONDS = 3600          # Check if brand post is due every hour
BRAND_ENGAGE_CHECK_SECONDS = 4 * 3600    # Check brand engagement every 4 hours
BRAND_LIFECYCLE_CHECK_SECONDS = 3600     # Check lifecycle every hour

# Action delays (randomized for humanization)
BRAND_POST_DELAY_MIN = 30 * 60          # 30 min
BRAND_POST_DELAY_MAX = 4 * 3600         # 4 hours
BRAND_ENGAGE_DELAY_MIN = 5 * 60         # 5 min
BRAND_ENGAGE_DELAY_MAX = 20 * 60        # 20 min

# Daily limits
DAILY_BRAND_POST_LIMIT = 1              # Max 1 brand post per day
DAILY_BRAND_ENGAGE_LIMIT = 5            # Max 5 brand engagements per day

# Lifecycle
BRAND_REANALYZE_DAYS = 28               # Re-analyze every 4 weeks

# ──────────────────────────────────────────────
# Signal-Based Selling (v1.0)
# ──────────────────────────────────────────────

# Signal types
SIGNAL_KEYWORD_MENTION = "keyword_mention"
SIGNAL_PROSPECT_POST = "prospect_post"
SIGNAL_JOB_CHANGE = "job_change"
SIGNAL_COMPETITOR_MENTION = "competitor_mention"
SIGNAL_HIRING_SURGE = "hiring_surge"
SIGNAL_FUNDING_EVENT = "funding_event"
SIGNAL_NEWS_EVENT = "news_event"
SIGNAL_PROFILE_VIEW = "profile_view"
SIGNAL_POST_ENGAGEMENT = "post_engagement"
SIGNAL_COMMENTER_MATCH = "commenter_match"

# Signal intents
SIGNAL_INTENT_BUYING = "buying_signal"
SIGNAL_INTENT_PAIN_POINT = "pain_point"
SIGNAL_INTENT_COMPETITOR_EVAL = "competitor_eval"
SIGNAL_INTENT_JOB_SEEKING = "job_seeking"
SIGNAL_INTENT_THOUGHT_LEADERSHIP = "thought_leadership"
SIGNAL_INTENT_UNKNOWN = "unknown"

# Signal statuses
SIGNAL_STATUS_NEW = "new"
SIGNAL_STATUS_CLASSIFIED = "classified"
SIGNAL_STATUS_ACTIONED = "actioned"
SIGNAL_STATUS_DISMISSED = "dismissed"
SIGNAL_STATUS_EXPIRED = "expired"

# Collection intervals (seconds)
SIGNAL_KEYWORD_POLL_SECONDS = 1800       # Keyword search every 30 min
SIGNAL_PROSPECT_SCAN_SECONDS = 3600      # Scan prospect posts every 1 hour
SIGNAL_CLASSIFY_SECONDS = 900            # Classify new signals every 15 min
SIGNAL_PROFILE_VIEW_SECONDS = 3600       # Check profile viewers every 1 hour
SIGNAL_JOB_CHANGE_SCAN_SECONDS = 14400   # Scan for job changes every 4 hours
SIGNAL_COMPETITOR_POLL_SECONDS = 1800    # Poll competitor keywords every 30 min
SIGNAL_HIRING_SCAN_SECONDS = 14400      # Scan for hiring surges every 4 hours
SIGNAL_NEWS_SCAN_SECONDS = 14400        # Scan for news/funding every 4 hours

# Batch sizes & limits
SIGNAL_DAILY_KEYWORD_SEARCHES = 50       # Max keyword searches per day (rate limit safety)
SIGNAL_PROSPECT_SCAN_BATCH = 20          # Max prospects scanned per run
SIGNAL_CLASSIFY_BATCH = 30               # Max signals classified per run
SIGNAL_PROSPECT_POST_LOOKBACK_DAYS = 7   # Only analyze posts from last 7 days
SIGNAL_JOB_CHANGE_BATCH = 50             # Max profiles per job change scan
SIGNAL_JOB_CHANGE_RESCAN_DAYS = 7        # Re-scan each contact weekly

# Signal scoring weights
SIGNAL_WEIGHT_KEYWORD_MENTION = 0.30
SIGNAL_WEIGHT_PROSPECT_POST = 0.35
SIGNAL_WEIGHT_JOB_CHANGE = 0.50
SIGNAL_WEIGHT_COMPETITOR_MENTION = 0.40
SIGNAL_WEIGHT_HIRING_SURGE = 0.25
SIGNAL_WEIGHT_FUNDING_EVENT = 0.30
SIGNAL_WEIGHT_NEWS_EVENT = 0.20
SIGNAL_WEIGHT_PROFILE_VIEW = 0.25
SIGNAL_WEIGHT_POST_ENGAGEMENT = 0.20
SIGNAL_WEIGHT_COMMENTER_MATCH = 0.35

# Signal freshness TTLs (seconds)
SIGNAL_TTL_KEYWORD_MENTION = 7 * 86400    # 7 days
SIGNAL_TTL_PROSPECT_POST = 14 * 86400     # 14 days
SIGNAL_TTL_JOB_CHANGE = 30 * 86400        # 30 days
SIGNAL_TTL_COMPETITOR_MENTION = 14 * 86400 # 14 days
SIGNAL_TTL_FUNDING_EVENT = 30 * 86400      # 30 days
SIGNAL_TTL_HIRING_SURGE = 14 * 86400       # 14 days
SIGNAL_TTL_NEWS_EVENT = 14 * 86400         # 14 days
SIGNAL_TTL_PROFILE_VIEW = 3 * 86400        # 3 days
SIGNAL_TTL_COMMENTER_MATCH = 14 * 86400    # 14 days
SIGNAL_TTL_DEFAULT = 14 * 86400            # 14 days fallback

# ──────────────────────────────────────────────
# Signal Activation (v1.1)
# ──────────────────────────────────────────────

# Activation thresholds (calibrated for best-signal-as-baseline scoring)
SIGNAL_AUTO_OUTREACH_THRESHOLD = 0.65   # Auto-create outreach with signal context
SIGNAL_HOT_SKIP_WARMUP_THRESHOLD = 0.80 # Skip warm-up, go direct to invite
SIGNAL_BOOST_THRESHOLD = 0.40           # Add to campaign as warm lead
SIGNAL_WARM_THRESHOLD = 0.20            # Boost engagement priority for existing
DAILY_SIGNAL_OUTREACH_LIMIT = 10        # Max signal-triggered outreaches per day
SIGNAL_ICP_MIN_OVERLAP = 0.2            # Min ICP overlap to auto-add to campaign

# Activation-qualifying intents (sets, not lists — used for `in` checks)
SIGNAL_OUTREACH_INTENTS = frozenset({"buying_signal", "pain_point", "competitor_eval"})
SIGNAL_BOOST_INTENTS = frozenset({"buying_signal", "pain_point", "competitor_eval", "thought_leadership"})

# Scheduler
JOB_ACTIVATE_SIGNALS = "activate_signals"
SIGNAL_ACTIVATE_SECONDS = 900           # Activate signals every 15 min

# Compound intent detection (Week 10 — signal stacking)
COMPOUND_INTENT_LOOKBACK_DAYS = 14      # Window for detecting stacked signals
COMPOUND_INTENT_MIN_TYPES = 2           # Minimum distinct signal types for compound event

# Compound intent patterns: frozenset of signal types → (event_type, score_boost)
# When 2+ matching signal types appear within LOOKBACK_DAYS for the same prospect,
# a synthetic intent_event is created with the given boost.
COMPOUND_INTENT_PATTERNS: dict[frozenset[str], tuple[str, float]] = {
    frozenset({"job_change", "hiring_surge"}): ("new_leader_building", 0.90),
    frozenset({"competitor_mention", "keyword_mention"}): ("active_evaluation", 0.85),
    frozenset({"prospect_post", "commenter_match"}): ("engaged_thought_leader", 0.70),
    frozenset({"funding_event", "hiring_surge"}): ("growth_mode", 0.80),
    frozenset({"job_change", "keyword_mention"}): ("new_role_exploring", 0.80),
    frozenset({"job_change", "competitor_mention"}): ("new_role_evaluating", 0.85),
    frozenset({"funding_event", "keyword_mention"}): ("funded_and_searching", 0.75),
    frozenset({"competitor_mention", "prospect_post"}): ("vocal_evaluator", 0.80),
}

# Scheduler job for compound intent detection
JOB_DETECT_COMPOUND_INTENT = "detect_compound_intent"
SIGNAL_COMPOUND_DETECT_SECONDS = 1800   # Check for compound intents every 30 min

# Signal decay engine (Week 11)
JOB_SIGNAL_DECAY_CYCLE = "signal_decay_cycle"
SIGNAL_DECAY_CYCLE_SECONDS = 21600      # Run decay cycle every 6 hours

# ──────────────────────────────────────────────
# Strategy Engine (Autonomous Campaign Optimization)
# ──────────────────────────────────────────────

JOB_STRATEGY_CYCLE = "strategy_cycle"
SCHEDULER_STRATEGY_SECONDS = 14400      # Run strategy cycle every 4 hours

# Revenue estimation heuristics (annual contract value in USD)
HEADCOUNT_BASE_ACV: dict[tuple[int, int | None], int] = {
    (1, 10): 2_000,
    (11, 50): 5_000,
    (51, 200): 15_000,
    (201, 500): 30_000,
    (501, 1000): 50_000,
    (1001, 5000): 80_000,
    (5001, 10000): 120_000,
    (10001, None): 200_000,
}

INDUSTRY_MULTIPLIERS: dict[str, float] = {
    "financial services": 1.5,
    "fintech": 1.4,
    "banking": 1.5,
    "insurance": 1.4,
    "healthcare": 1.3,
    "pharmaceutical": 1.3,
    "technology": 1.2,
    "saas": 1.3,
    "software": 1.2,
    "cybersecurity": 1.3,
    "telecommunications": 1.1,
    "energy": 1.2,
    "oil & gas": 1.3,
    "consulting": 1.1,
    "legal": 1.2,
    "real estate": 1.0,
    "e-commerce": 1.0,
    "retail": 0.9,
    "manufacturing": 0.9,
    "logistics": 0.9,
    "media": 0.8,
    "advertising": 0.8,
    "education": 0.7,
    "non-profit": 0.5,
    "government": 0.6,
}

SENIORITY_MULTIPLIERS: dict[str, float] = {
    "owner": 1.5,
    "cxo": 1.4,
    "vp": 1.2,
    "director": 1.0,
    "manager": 0.7,
    "senior": 0.5,
    "entry": 0.3,
}

SENIORITY_KEYWORDS: dict[str, list[str]] = {
    "owner": ["owner", "founder", "co-founder", "cofounder"],
    "cxo": ["ceo", "cto", "cfo", "coo", "cmo", "cpo", "ciso", "cro",
            "chief", "president", "managing partner"],
    "vp": ["vp", "vice president", "svp", "evp", "avp"],
    "director": ["director", "head of"],
    "manager": ["manager", "lead", "team lead", "supervisor"],
    "senior": ["senior", "sr.", "sr ", "principal", "staff"],
    "entry": ["associate", "analyst", "coordinator", "intern", "junior",
             "jr.", "jr ", "assistant", "specialist"],
}

# Strategy engine thresholds
STRATEGY_MIN_SAMPLE_SIZE = 15           # Min prospects before pattern detection
STRATEGY_SKIP_ACCEPTANCE_THRESHOLD = 0.10  # Skip segment if acceptance < 10%
STRATEGY_SPAWN_MIN_CONFIDENCE = 0.5     # Min pattern confidence to spawn campaign
STRATEGY_SPAWN_MIN_REVENUE = 10_000     # Min estimated revenue impact to spawn
STRATEGY_CONFIDENCE_BOOST = 0.1         # Confidence increase on validation
STRATEGY_CONFIDENCE_PENALTY = 0.2       # Confidence decrease on rollback
STRATEGY_MAX_SPAWNED_CAMPAIGNS = 3      # Max auto-spawned campaigns per cycle

# ──────────────────────────────────────────────
# Voice Memos / Hume AI TTS
# ──────────────────────────────────────────────

HUME_TTS_API_URL = "https://api.hume.ai/v0/tts"
HUME_DEFAULT_OUTPUT_FORMAT = "mp3"
HUME_DEFAULT_SPEED = 1.0
VOICE_MEMO_MAX_TEXT_CHARS = 500           # Keep voice messages concise
VOICE_MEMO_MAX_DURATION_SECONDS = 60     # LinkedIn voice message limit
DAILY_VOICE_MEMO_LIMIT = 20             # Rate limit for audio generation
VOICE_MEMO_DIR = "voice_memos"          # Subdir under ~/.heylead/

# Voice mode options for campaigns
VOICE_MODE_TEXT_ONLY = "text_only"       # Default: all messages as text
VOICE_MODE_VOICE_ONLY = "voice_only"     # All follow-ups/replies as voice (text fallback)
VOICE_MODE_MIXED = "mixed"               # Alternate between text and voice
VOICE_MODE_AB_TEST = "ab_test"           # A/B test voice vs text
VALID_VOICE_MODES = frozenset({
    VOICE_MODE_TEXT_ONLY, VOICE_MODE_VOICE_ONLY,
    VOICE_MODE_MIXED, VOICE_MODE_AB_TEST,
})

# Voice Memo Enhancement (v0.10)
VALID_NOISE_TYPES = frozenset({"office", "cafe", "street", "quiet", "none", "auto"})
VALID_NOISE_VOLUMES = frozenset({"subtle", "moderate", "noticeable"})
DEFAULT_NOISE_TYPE = "auto"
DEFAULT_NOISE_VOLUME = "subtle"
DEFAULT_VOICE_HUMANIZE = True
