# ═══════════════════════════════════════════════════
#   Wivexx Config  —  edit this file to customise
#   (formerly "Wivexx"; internal module/var names kept for compatibility)
# ═══════════════════════════════════════════════════
import os

# ── Step 1: API key is loaded from .env (recommended) ──
# Create a file named  .env  in this folder containing:
#   ANTHROPIC_API_KEY=sk-ant-api03-...your key here...
# Get a key at: https://console.anthropic.com/settings/keys
# The .env file is listed in .gitignore so it won't be committed to git.
try:
    from dotenv import load_dotenv
    load_dotenv(dotenv_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"))
except ImportError:
    pass  # python-dotenv not installed yet — preflight will install it

API_KEY = os.getenv("ANTHROPIC_API_KEY", "")
if not API_KEY:
    # Fallback: you can still paste it here directly as a last resort
    API_KEY = ""   # ← paste key here only if .env doesn't work

# ── Portable per-user data locations ──────────────────────────────────────────
# Wivexx must work on ANY PC out of the box, so the default save/import folders
# are computed under the *current user's* profile (no hardcoded drive letters).
# These are robust even when the silent VBS launcher strips environment vars:
# USERPROFILE usually survives; we fall back through HOMEDRIVE/HOMEPATH, then
# expanduser, then the app folder, so they NEVER return an invalid path.
# You can still override WEB_SAVED_DIR / MEDIA_IMPORT_DEST below to point anywhere.
def user_home_dir():
    """Best writable home dir for the current user (drive-letter independent)."""
    for _v in ("USERPROFILE", "HOME"):
        _p = os.environ.get(_v)
        if _p and os.path.isdir(_p):
            return _p
    _hd, _hp = os.environ.get("HOMEDRIVE"), os.environ.get("HOMEPATH")
    if _hd and _hp and os.path.isdir(_hd + _hp):
        return _hd + _hp
    _p = os.path.expanduser("~")
    if _p and _p != "~" and os.path.isdir(_p):
        return _p
    return os.path.dirname(os.path.abspath(__file__))

def default_saved_dir():
    """Where phone 'Save to PC' files + analytics reports land by default."""
    return os.path.join(user_home_dir(), "Documents", "Wivexx", "Saved")

def default_media_dest():
    """Where SD-card / USB photo+video imports land by default."""
    return os.path.join(user_home_dir(), "Pictures", "Wivexx")

# ── Step 2: Choose a model ─────────────────────────
# "claude-sonnet-4-6"          ← recommended (best quality, ~$1-3/month)
# "claude-haiku-4-5-20251001"  ← fastest & cheapest  (~$0.20/month)
MODEL = "claude-sonnet-4-6"

# ── Step 3: Wake word ──────────────────────────────
# The phrase you say to activate Wivexx.  The brand name is "Wivexx" everywhere;
# the spoken trigger is "hey wivexx".  WAKE_WORD is the canonical token the
# transcript is matched/stripped against; WAKE_DISPLAY is what we show/say.
# ("jarvis" is kept as a quiet fallback variant in wivexx_api so old habit works.)
WAKE_WORD = "wivexx"
WAKE_DISPLAY = "hey wivexx"

# ── Step 4: Voice ──────────────────────────────────
# Male:   "en-US-GuyNeural"  "en-GB-RyanNeural"  "en-AU-WilliamNeural"
# Female: "en-US-AriaNeural" "en-US-JennyNeural" "en-GB-SoniaNeural"
VOICE = "en-US-GuyNeural"

# ── Language (v1.26; interface-only since v1.51) ───────────────────────────────
# Wivexx's INTERFACE language — the menus, the default voice, and the default
# reply language.
#   "en"   → English (default)
#   "es"   → Spanish (UI translated, Spanish voice, replies in Spanish)
# The SPOKEN language is detected automatically on every utterance (multilingual
# Whisper), independent of this setting — so you can run the UI in English and
# still be understood when you speak Spanish. Change it live in the control room:
# Settings → Language (switches the UI instantly; no voice-model reload needed).
LANGUAGE = "en"
# Per-language neural TTS voices (used instead of VOICE when LANGUAGE isn't "en").
VOICES = {
    "en": "en-US-GuyNeural",
    "es": "es-ES-AlvaroNeural",   # or "es-MX-JorgeNeural" for Latin-American Spanish
}

# ── Advanced settings (optional) ──────────────────
# How many back-and-forth exchanges Wivexx remembers per session
MEMORY_TURNS = 10

# Microphone sensitivity (RMS threshold for silence detection)
#   Lowered 2000 -> 800 so Wivexx hears you at a normal volume / distance
#   (2000 forced you to speak loud and close to the mic).
#   Raise toward ~1500 if you start getting noise false-triggers in a loud room
#   Lower toward ~400 if it still misses you in a quiet room
SILENCE_THRESHOLD = 800

# VAD gate multiplier for the wake-word listener
#   Lowered to 0.012 so quieter / faster wake words still register.
#   Raise toward 0.02 if you get noise false-triggers.
VAD_THRESHOLD = 0.012

# Max characters Wivexx will read aloud before summarising
# (very long answers get trimmed so they don't take forever to read)
TTS_MAX_CHARS = 700

# Max tokens the Claude API will generate per response.
# Keeping this low (300) means faster API responses and shorter spoken answers.
# Raise to 600 if you find answers getting cut off.
RESPONSE_MAX_TOKENS = 300

# Microphone device (for voice recognition)
#   Leave as None to use your system default microphone
#   If Wivexx can't hear you, run start_wivexx.bat and check the mic test
#   output — it will list all available devices with their numbers.
#   Then set MIC_DEVICE to that number, e.g.:  MIC_DEVICE = 2
MIC_DEVICE = None

# ── Behavior tunables ──────────────────────────────────────────────────────────
FLASH_DURATION_MS        = 600    # How long the screen border flash lasts (ms)
FLASH_COLOR              = "#C1F73A"  # Hex color for the screen flash border (volt)
DEFAULT_SCREENSHOT_MODE  = "primary"   # 'primary', 'secondary', or 'both'
RECORD_POST_SILENCE_SECS = 3.0    # Seconds of silence before stopping recording
YESNO_MAX_WAIT           = 8.0    # Hard cap for yes/no listener (seconds)
HUD_SIZE                 = 80     # Arc-reactor HUD diameter in pixels
HUD_X                    = 20     # HUD X position from top-left
HUD_Y                    = 20     # HUD Y position from top-left
HUD_ENABLED              = True   # Show the HUD widget
WAVEFORM_ENABLED         = True   # Show the audio waveform overlay
WAVEFORM_WIDTH           = 300    # Waveform overlay width in pixels
WAVEFORM_HEIGHT          = 40     # Waveform overlay height in pixels
WAVEFORM_X               = 20     # Waveform X position
WAVEFORM_Y               = 110    # Waveform Y position

# ── Feature 1: Voice-gated barge-in + adaptive endpointing ────────────────────
ENDPOINT_SILENCE_MS      = 900    # ms of non-voiced audio before ending turn
VAD_MIN_VOICED_FRAMES    = 6      # sustained voiced frames needed to trigger
BARGEIN_GATE             = 0.03   # v1.58: was 0.06 (~1966 RMS) — you had to nearly
                                  # SHOUT to interrupt, so normal speech over Wivexx
                                  # was ignored (the "deadzone"). 0.03 (~983 RMS) is
                                  # conversational level; AEC + human-voice check +
                                  # the consec-frame guard still block TTS self-trigger.
BARGEIN_CONSEC_FRAMES    = 4      # 50 ms frames of sustained VOICE needed to interrupt (4 = 200 ms; was 6)
BARGEIN_SPEECH_BAND_RATIO = 0.5   # min share of energy in the 200–3500 Hz speech band
                                  # (music/keyboard/door sounds fail this check)

# ── Whisper speech-to-text engines ────────────────────────────────────────────
# English-only models (fast, most accurate for English) — used when LANGUAGE="en".
WHISPER_FULL_MODEL = "medium.en"  # used in full-power mode (GPU if available)
WHISPER_LITE_MODEL = "small.en"   # used in low-power mode / when CUDA is missing
# Multilingual models — used automatically when LANGUAGE is "es" or "auto" so the
# listener can understand Spanish / other languages (the .en models cannot).
WHISPER_ML_FULL    = "medium"     # multilingual, full-power
WHISPER_ML_LITE    = "small"      # multilingual, low-power / CPU
# File transcription (wivexx_transcribe) uses its OWN model: the most accurate one
# the PC can run, and MULTILINGUAL (auto-detects Spanish/English/…). large-v3 is the
# best quality; drop to "medium" or "small" if you want it lighter/faster.
WHISPER_TRANSCRIBE_MODEL = "large-v3"

# ── Porcupine wake-word engine (optional; far more reliable than Whisper) ──────
# A dedicated always-on detector for the wake word. To enable:
#   1) Get a FREE access key at https://console.picovoice.ai/
#   2) Create a custom "Wivexx" keyword (platform: Windows) and download the .ppn
#   3) Put that .ppn in this folder; set PORCUPINE_KEYWORD_PATH to its filename
#   4) Put  PICOVOICE_ACCESS_KEY=...  in your .env
# If the key or keyword file is missing, Wivexx silently falls back to the
# Whisper-based wake detection — nothing breaks.
PICOVOICE_ACCESS_KEY   = os.getenv("PICOVOICE_ACCESS_KEY", "")
PORCUPINE_KEYWORD_PATH = "wivexx_windows.ppn"   # filename of your custom keyword
PORCUPINE_SENSITIVITY  = 0.6                     # 0..1 (higher = more sensitive)

# ── Performance guard: auto low-power mode ────────────────────────────────────
# When CPU, GPU or RAM stays above its limit for PERF_HIGH_SECS, WIVEXX switches
# to low-power mode: lite Whisper engine on CPU + memory consolidation paused.
# It returns to full power after PERF_RECOVER_SECS below the limits.
PERF_MONITOR_ENABLED = True
PERF_CPU_LIMIT       = 85     # percent
PERF_GPU_LIMIT       = 85     # percent (NVIDIA only; skipped on other GPUs)
PERF_RAM_LIMIT       = 90     # percent
PERF_CHECK_INTERVAL  = 5      # seconds between samples
PERF_HIGH_SECS       = 15     # sustained overload before switching down
PERF_RECOVER_SECS    = 45     # sustained normal load before switching back up

# ── Active-session listening (v1.11) ──────────────────────────────────────────
SESSION_IDLE_TIMEOUT = 30   # seconds of silence in a conversation before
                            # WIVEXX quietly returns to wake-word standby

# ── Work mode (v1.11) ─────────────────────────────────────────────────────────
# When ON ("enable work mode"): every finished .zip in Downloads is extracted
# into a folder named after it. The lightroom_import shortcut (default
# Ctrl+Shift+L) opens the newest Downloads folder in Lightroom Classic.
# WORK_MODE_AVAILABLE gates the whole feature — it's a niche photographer tool,
# so the public build ships it OFF (make_release sets this False). Leave True
# for your own copy.
WORK_MODE_AVAILABLE  = False
WORK_MODE_DEFAULT    = False
WORK_DOWNLOADS_DIR   = r"~\Downloads"

# ── Push-to-talk / call mode ──────────────────────────────────────────────────
# In call mode the wake word is disabled — WIVEXX only listens while the
# push-to-talk key is held (see wivexx_hotkeys.json / GUI ⌨ Shortcuts).
PTT_MIN_AUDIO_SECS   = 0.4    # ignore PTT presses shorter than this
PTT_MAX_AUDIO_SECS   = 45     # hard cap per push-to-talk capture

# ── Phone / browser interface (v1.7) ──────────────────────────────────────────
# WIVEXX serves a mobile chat page on the local network. Commands typed there
# execute on this PC; questions are answered in the browser.
#   URL:  http://your-pc-name:7777   (or http://<pc-ip>:7777)
# Change the PIN — every request requires it. Never port-forward this port;
# use Tailscale/WireGuard for access from outside the house.
WEB_ENABLED          = True
WEB_PORT             = 7777
WEB_PIN              = "6728"
# Files sent from the phone with "Save to PC" land here, sorted by type
# (Photos / Videos / Documents / Audio / Other):
WEB_SAVED_DIR        = default_saved_dir()

# ── Disk usage monitor + analytics (v1.8.1) ───────────────────────────────────
DISK_MONITOR_ENABLED = True
DISK_MONITOR_DRIVES  = None   # None = all fixed drives
DISK_WARN_PERCENT    = 85             # announce when a drive crosses this (non-interrupting)
DISK_WARN_ONCE_PER_SESSION = True     # v1.12: ONE combined storage heads-up per
                                      # session — never repeated until restart
DISK_CHECK_INTERVAL  = 300            # seconds between usage checks
DISK_STALE_DAYS      = 90             # "analyze drives": files untouched this long
DISK_MIN_FILE_MB     = 100            # only list files at least this big
DISK_TOP_N           = 40             # how many biggest files to list

# ── External storage auto-import (v1.7) ───────────────────────────────────────
# When an allowed SD card / USB drive is connected, all photos & videos on it
# are copied to MEDIA_IMPORT_DEST\MM-DD-YYYY\{photos,video}[\tag].
# First connection of a new device pops an Allow / Deny / Later dialog.
MEDIA_IMPORT_ENABLED = True
MEDIA_IMPORT_DEST    = default_media_dest()

# ── Social media trackers (v1.12) ─────────────────────────────────────────────
# "track instagram <handle>" → WIVEXX snapshots the account and reports growth
# (followers this week, new comments, latest video performance, engagement).
# One spoken briefing per session (~90 s after boot), or ask any time with
# "social media report" / "how is my instagram doing".
SOCIAL_BRIEF_ON_BOOT = True
# Instagram rate-limits anonymous reads hard.  For RELIABLE tracking install
# instaloader (pip install instaloader), log in once from a terminal:
#     instaloader --login your_username
# …then put that username here.  WIVEXX will reuse the saved session.
INSTAGRAM_LOGIN = ""

# ── Client photo delivery (v1.14) ─────────────────────────────────────────────
# "deliver photos to a client" / "send photos to drive" opens the 📦 Deliver
# window: develop .ARW → JPG, rename into a clean sequence, upload to a fresh
# PUBLIC Google Drive folder, and hand you the share link.
DELIVER_DEFAULT_CONVERT = "develop"   # "develop" = true RAW develop (camera WB,
                                      # neutral, full quality) | "preview" =
                                      # the full-size JPEG the camera embedded
                                      # (instant, matches the camera screen)
DELIVER_JPEG_QUALITY    = 95          # JPEG quality for developed photos (1-100)
DELIVER_AUTO_BRIGHTEN   = True        # match the camera's exposure (libraw auto-
                                      # brightness). OFF makes JPGs ~0.8 EV darker
                                      # than the camera — leave True unless you
                                      # specifically want the raw/neutral exposure.
DELIVER_BRIGHTNESS      = 1.0         # extra exposure multiplier applied after
                                      # develop (1.0 = none, 1.1 ≈ a touch brighter)
DELIVER_OUTPUT_SUBDIR   = "JPG"       # converted JPGs go to  <source>\JPG\
DELIVER_CONVERT_WORKERS = 4           # parallel RAW develops (libraw frees the GIL)
# Google Drive — one-time OAuth setup, see DRIVE_SETUP.md.  Drop the OAuth
# "Desktop app" credentials.json in this folder; the token is cached after the
# first browser consent.
DRIVE_PARENT_FOLDER     = "Wivexx Client Galleries"  # galleries are created here
DRIVE_MAKE_PUBLIC       = True        # share new galleries as "anyone with link"
DRIVE_CREDENTIALS_FILE  = "credentials.json"
DRIVE_TOKEN_FILE        = "wivexx_drive_token.json"

# ── Google Calendar (v1.16) ───────────────────────────────────────────────────
# "what's my first meeting tomorrow?", "what's on my calendar today", "what's
# next", "schedule a call tomorrow at 3pm for an hour".  Reuses the SAME
# credentials.json as Drive (enable the Google Calendar API in the same Cloud
# project); keeps its own token so calendar access is granted once.
CALENDAR_TOKEN_FILE     = "wivexx_calendar_token.json"

# ── Voice biometrics (v1.17) ──────────────────────────────────────────────────
# Sensitive commands (system shutdown, wiping memory) only run if the speaker
# matches your enrolled voiceprint.  Inert until you say "enroll my voice"
# (needs:  pip install resemblyzer).  Never locks you out — on a mismatch it just
# refuses that one command; typed/phone commands are exempt (PIN-protected).
VOICEID_ENABLED   = True
VOICEID_THRESHOLD = 0.75              # cosine match cutoff (higher = stricter)
VOICEID_GATED     = ["shutdown", "memory"]   # which command groups to protect

# ── Acoustic echo cancellation (v1.17) ────────────────────────────────────────
# Stops WIVEXX interrupting himself when the mic hears his own TTS.
AEC_GATING        = True              # echo-aware barge-in (no dependency) — keep on
AEC_FACTOR        = 0.8               # how strongly his output level raises the gate
AEC_ENABLED       = False             # experimental true-AEC subtraction (needs
                                      # speexdsp, which has no Windows wheel here);
                                      # gating already covers the self-trigger fix
AEC_LATENCY_MS    = 180               # speaker→mic round-trip used to align the echo

# ── Wivexx online relay (v1.18) ───────────────────────────────────────────────
# Drive this PC assistant from wivexx.com/ai (gated to YOUR signed-in account).
# It talks to Supabase over HTTPS using a SERVICE-ROLE key kept ONLY on this PC —
# no inbound port, no tunnel.  Inert until you set RELAY_ENABLED   = False AND put
# SUPABASE_URL + SUPABASE_SERVICE_KEY in the .env file (see RELAY_SETUP.md).
RELAY_ENABLED   = False
RELAY_POLL_SECS = 2.0     # how often the PC checks Supabase for new commands

# ── Autostart with Windows ────────────────────────────────────────────────────
# True  → WIVEXX (re)creates a silent launcher in the Startup folder each run
# False → WIVEXX removes that launcher and never starts with Windows
AUTOSTART_ENABLED    = True

# ── Memory engine quality controls (v1.6 / v1.10) ─────────────────────────────
MEMORY_MAX_TOPICS    = 12     # keep only the N most recent topic summaries
MEMORY_TOPIC_EVERY_N_TURNS = 4  # refresh the session topic every N exchanges
# ── Semantic (vector) recall (v1.16) ──────────────────────────────────────────
# WIVEXX finds stored facts by MEANING, not just shared keywords, by embedding
# each fact with a local Ollama model and ranking by cosine similarity (blended
# with the existing recency/importance score).  Falls back to keyword-only when
# Ollama or the model is missing — nothing breaks.  One-time setup:
#     ollama pull nomic-embed-text
MEMORY_SEMANTIC      = True   # set False to force the old keyword-only recall
MEMORY_EMBED_MODEL   = "nomic-embed-text"   # any Ollama embedding model
MEMORY_SEM_WEIGHT    = 2.5    # how strongly cosine similarity boosts a fact
MEMORY_SEM_THRESHOLD = 0.55   # min cosine for a no-keyword fact to be recalled
MEMORY_MAX_FACTS     = 60     # auto-learned facts above this get pruned by
                              # decay (manual / "remember that" facts never are)

# ── Feature 2: Request-creation dictation mode ────────────────────────────────
REQUEST_STOP_PHRASES     = [
    # "done" variants
    "done", "i'm done", "im done", "finish", "finished", "i'm finished", "i am done",
    # "that's all" variants
    "that's all", "thats all", "that all", "that's it", "thats it", "that is all",
    "that is it", "dats all", "dat's all", "that would be all",
    # "end" variants
    "end request", "end the request", "end it", "end recording", "stop recording",
    "stop", "stop dictating",
    # "complete" variants
    "complete", "completed", "request complete",
    # "submit" variants
    "submit", "submit request", "log it", "log request", "save it", "save request",
    # "all good" variants
    "all good", "that will be all", "that'll be all",
    # "ok done" variants
    "ok done", "okay done", "alright done", "alright that's all", "ok that's all",
    # short confirmations
    "yes log it", "go ahead log it", "record that",
]
REQUEST_MAX_SENTENCES    = 10     # max sentences before auto-save
REQUEST_TIMEOUT_SECS     = 30     # max silence before auto-save

# ── Feature 3: Weather cache TTL (seconds) ────────────────────────────────────
WEATHER_CACHE_TTL        = 600    # 10 minutes

# ── Feature 4: API token usage ledger ─────────────────────────────────────────
TOKEN_PRICE_IN           = 3.00   # USD per million input tokens
TOKEN_PRICE_OUT          = 15.00  # USD per million output tokens
MONTHLY_TOKEN_BUDGET     = 0      # 0 = no budget tracking

# ── Feature 5: Holographic 3D spectrogram ─────────────────────────────────────
SPECTROGRAM_ENABLED      = True   # Show the spectrogram overlay
SPECTROGRAM_SIZE         = 280    # Pixel diameter of the overlay window
SPECTROGRAM_X            = 0     # X position (0 = auto bottom-right corner)
SPECTROGRAM_Y            = 0     # Y position (0 = auto bottom-right corner)
SPECTROGRAM_FPS          = 30    # Target frames per second
SPECTROGRAM_N_RINGS      = 7     # Number of concentric rings

GUI_SUPPRESS_CONSOLE = True    # Hide the console window on Windows (pythonw equivalent)

# ── GUI (unified window — replaces HUD/waveform/spectrogram overlays) ─────────
GUI_ENABLED          = True    # Show the unified Wivexx GUI window
GUI_WIDTH            = 600     # Main window default width in pixels
GUI_HEIGHT           = 700     # Main window default height in pixels
GUI_SPHERE_POINTS    = 70      # Fibonacci-lattice points on the geodesic sphere
GUI_RING_SPAWN_RATE  = 2.0     # Seconds between ripple rings in idle state
GUI_FPS              = 30      # Target animation frames per second

# ── The 3D DOT (v1.19) — software-shaded sphere (numpy + Pillow, tk engine) ───
GUI_DOT_3D     = True   # True = the centred 3D DOT; False = the flat 2D DOT
GUI_DOT_MAX_PX = 320    # render-diameter cap for the orb (lower = lighter CPU)
GUI_DOT_GLOW   = True   # soft volt glow halo behind the orb

# ── UI engine (v1.20) — the control-room window ──────────────────────────────
# "web" = the wivexx.com-style HTML/CSS/JS UI in Edge WebView2 (recommended;
#          needs the WebView2 runtime, which ships with Windows 11, + pywebview).
# "tk"  = the legacy tkinter window (fallback; used automatically if WebView2
#          is unavailable).  The heavy editors/studios stay native in both modes.
GUI_ENGINE     = "web"
GUI_WINDOW_W   = 980    # web window default width  (px)
GUI_WINDOW_H   = 720    # web window default height (px)
GUI_WEB_DEBUG  = False  # True = open WebView2 devtools (debugging only)
