
/* tslint:disable */
/**
 * @license
 * SPDX-License-Identifier: Apache-2.0
 */

import {LitElement, css, html} from 'lit';
import {customElement, state} from 'lit/decorators.js';
import './visual-3d';

// New Modular Components
import './components/status-bar';
import './components/subtitle-overlay';
import './components/control-dock';
import './components/chat-history';
import './components/login-overlay';
import './components/media-overlays';
import './components/operator-onboarding'; // New Component

// Controllers
import { AudioInputController } from './controllers/audio-input-controller';
import { MediaStreamController } from './controllers/media-stream-controller';
import { GeminiSessionController } from './controllers/gemini-session-controller';
import { AccelerometerController } from './controllers/accelerometer-controller';
import { FaceCaptureController } from './controllers/face-capture-controller';
import { FaceRecognitionController } from './controllers/face-recognition-controller';
import { VoiceRecognitionController } from './controllers/voice-recognition-controller';
import { decode, encode, encodeWAV } from './utils';

// Type definition for Web Speech API
declare global {
  interface Window {
    webkitSpeechRecognition: any;
    SpeechRecognition: any;
  }
}

interface ChatMessage {
  role: 'user' | 'ai';
  text: string;
  timestamp: number;
}

@customElement('gdm-live-audio')
export class GdmLiveAudio extends LitElement {
  @state() isRecording = false;
  @state() status = '';
  @state() error = '';
  @state() contextJson: any | null = null;
  @state() settingsJson: any | null = null;
  @state() translationsJson: {[key: string]: string} | null = null;
  
  @state() userSubtitles = '';
  @state() aiSubtitles = '';
  
  @state() overlayImage: string | null = null;
  @state() overlayVideo: string | null = null;
  @state() overlayWebsite: string | null = null;
  @state() animationClips: string[] = [];
  @state() clientName = 'default';
  @state() private _controlsVisible = true;
  
  @state() vadActive = false; 
  @state() currentRms = 0; 
  @state() isAiSpeaking = false;

  @state() history: ChatMessage[] = [];
  @state() isHistoryVisible = false;
  
  @state() isFrontendAuthenticated = false;
  @state() loginError = '';
  
  @state() currentActivationMode: 'camera' | 'manual' | 'accelerometer' = 'manual';
  
  @state() currentFaceId = 'unknown';
  @state() currentVoiceId = 'unknown';
  @state() showVoiceEnroll = false;
  
  @state() userId: string = '';
  
  @state() facePosition: { x: number, y: number, active: boolean } = { x: 0.5, y: 0.5, active: false };

  // Operator Mode State
  @state() operatorMode = false;
  @state() operatorId = '';
  
  // State for hard reset
  @state() private showVisuals = true;

  private currentInputTranscription = '';
  private currentOutputTranscription = '';

  private ignoreCloseEvent = false;
  private lastInteractionTime = 0; // For Mic Timeout logic
  private lastGlobalActivity = Date.now(); // For Idle Reload logic
  private lastIdentitySwitchTime = 0; 
  
  private _clickBuffer = 0;
  private _clickTimer: any = null;
  
  private currentVisualContext = '';
  private isEnrolling = false;
  
  private lastFaceDetectedTime = 0; 
  private lastFaceSaveTime = 0;
  
  private creationTime = Date.now();

  private audioController: AudioInputController | null = null;
  private mediaController: MediaStreamController | null = null;
  private sessionController: GeminiSessionController | null = null;
  private accelerometerController: AccelerometerController | null = null;
  private faceController: FaceCaptureController | null = null;
  private faceRecognitionController: FaceRecognitionController | null = null;
  private voiceController: VoiceRecognitionController | null = null;

  private recognition: any = null;

  private outputAudioContext: AudioContext | null = null;
  private inputAudioContext: AudioContext | null = null;

  private eventSource: EventSource | null = null;
  private keywordAudio: HTMLAudioElement | null = null;
  private processedKeywords = new Set<string>();
  
  private vadUiFrameId: number | null = null;
  
  // Voice Sample Logic
  private voiceSampleBuffer: Int16Array[] = [];
  private voiceSampleLength = 0;
  private voiceSampleSaved = false;
  
  // Greeting Cooldown Map: UserId -> Timestamp
  private lastGreetingTimes: Map<string, number> = new Map();
  
  // Watchdog
  private watchdogInterval: any = null;
  
  // Dedicated audio stream (when camera has no audio track)
  private dedicatedAudioStream: MediaStream | null = null;
  
  // Session initialization mutex and reconnection logic
  private isInitializingSession = false;
  private reconnectAttempts = 0;
  private static readonly MAX_RECONNECT = 5;
  private static readonly RECONNECT_BACKOFF = [1000, 2000, 5000, 10000, 30000];
  
  // Track all pending timeouts for cleanup on destroy/reset
  private pendingTimers = new Set<number>();
  
  // Max history entries for long-running sessions
  private static readonly MAX_HISTORY = 200;

  static styles = css`
    :host {
      display: block;
      width: 100vw;
      height: 100vh;
      overflow: hidden;
      position: relative;
      background: #000;
      color: white;
      font-family: 'Inter', system-ui, -apple-system, sans-serif;
      --primary-color: #6c5ce7;
      --glass-bg: rgba(20, 20, 25, 0.65);
      --glass-border: rgba(255, 255, 255, 0.1);
      --glass-blur: 20px;
    }

    .main-wrapper {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
    }

    gdm-live-audio-visuals-3d {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 1;
    }

    .loading-screen {
        position: fixed;
        inset: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        background: #000;
        color: white;
        z-index: 2000;
    }
    .spinner {
        width: 40px;
        height: 40px;
        border: 4px solid rgba(255,255,255,0.1);
        border-top-color: var(--primary-color);
        border-radius: 50%;
        animation: spin 1s linear infinite;
        margin-bottom: 20px;
    }
    @keyframes spin { to { transform: rotate(360deg); } }
    
    .error-box {
        max-width: 400px;
        padding: 20px;
        background: #2d1a1a;
        border: 1px solid #ff4757;
        border-radius: 12px;
        text-align: center;
    }
    .retry-btn {
        margin-top: 15px;
        padding: 10px 20px;
        background: #ff4757;
        color: white;
        border: none;
        border-radius: 8px;
        cursor: pointer;
    }
    
    /* Motion Detector Debug */
    #motion-detector {
        position: absolute;
        top: 20px;
        right: 20px;
        width: 160px;
        height: 120px;
        border-radius: 12px;
        overflow: hidden;
        border: 1px solid rgba(255,255,255,0.2);
        z-index: 100;
        opacity: 0.8;
    }
    #roi-box { position: absolute; border: 2px dashed rgba(255,255,255,0.6); }
  `;

  constructor() {
    super();
    this._initAudioContexts();
    this.mediaController = new MediaStreamController();
    this.accelerometerController = new AccelerometerController();
    this.faceController = new FaceCaptureController();
    this.faceRecognitionController = new FaceRecognitionController();
    this.voiceController = new VoiceRecognitionController();
    
    this.initSpeechRecognition();
    this.setupControllerListeners();
    this.initClient();
    this.setupSSE();
    this.initUserIdentity();
    this.checkOperatorMode();
  }
  
  private _initAudioContexts() {
      // Re-create contexts if closed
      if (!this.inputAudioContext || this.inputAudioContext.state === 'closed') {
          this.inputAudioContext = new (window.AudioContext || (window as any).webkitAudioContext)({sampleRate: 16000});
      }
      if (!this.outputAudioContext || this.outputAudioContext.state === 'closed') {
          this.outputAudioContext = new (window.AudioContext || (window as any).webkitAudioContext)({sampleRate: 24000});
      }
      this.audioController = new AudioInputController(this.inputAudioContext);
      this.sessionController = new GeminiSessionController(this.outputAudioContext);
  }

  // --- MEMORY WATCHDOG & IDLE RELOAD ---
  private _initWatchdog() {
      if (this.watchdogInterval) clearInterval(this.watchdogInterval);
      
      this.watchdogInterval = setInterval(() => {
          // 1. Idle Reload Check
          if (this.settingsJson?.system?.performance?.reloadOnIdle) {
              const timeoutSec = this.settingsJson.system.performance.idleTimeoutSeconds || 600;
              const timeSinceLastActivity = Date.now() - this.lastGlobalActivity;
              
              if (timeSinceLastActivity > timeoutSec * 1000) {
                  // Ensure we don't reload if AI is speaking or User is speaking
                  if (!this.isAiSpeaking && !this.vadActive) {
                      console.warn(`[Idle Reload] Inactive for ${timeSinceLastActivity/1000}s. Reloading to clear memory.`);
                      window.location.reload();
                  } else {
                      // Reset if active (just in case listeners missed it)
                      this.lastGlobalActivity = Date.now();
                  }
              }
          }

          // 2. Memory Limit Check
          if (!this.settingsJson?.system?.performance?.maxMemoryMB) return;
          
          const maxMB = this.settingsJson.system.performance.maxMemoryMB;
          if (maxMB <= 0) return;

          // Performance.memory is a Chrome-specific extension
          const perf = window.performance as any;
          if (perf && perf.memory && perf.memory.usedJSHeapSize) {
              const usedMB = perf.memory.usedJSHeapSize / (1024 * 1024);
              if (usedMB > maxMB) {
                  console.warn(`[Watchdog] Memory Exceeded: ${usedMB.toFixed(0)}MB / ${maxMB}MB. Forcing Soft Reset.`);
                  this.reset(); // Trigger Deep Soft Reset instead of reload
              }
          }
      }, 10000); // Check every 10 seconds
  }
  
  private _updateActivity() {
      // Throttle updates to max 1 per second
      const now = Date.now();
      if (now - this.lastGlobalActivity > 1000) {
          this.lastGlobalActivity = now;
      }
  }

  // --- IDENTITY & SPEECH ---
  private initUserIdentity() {
      const storedId = 'customer-' + Date.now();
      localStorage.setItem('gdm_user_id', storedId);
      this.userId = storedId;
  }
  
  private checkOperatorMode() {
      const urlParams = new URLSearchParams(window.location.search);
      const op = urlParams.get('operator');
      if (op) {
          this.operatorMode = true;
          this.operatorId = op;
          console.log("Operator Onboarding Mode Active:", op);
      }
  }
  
  private initSpeechRecognition() {
    if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
      const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
      this.recognition = new SpeechRecognition();
      this.recognition.continuous = true;
      this.recognition.interimResults = true;
      this.recognition.lang = 'es-ES'; 
      this.recognition.onresult = (event: any) => {
        this._updateActivity(); // Speech counts as activity
        let interimTranscript = '';
        for (let i = event.resultIndex; i < event.results.length; ++i) {
          if (event.results[i].isFinal) {
             const text = event.results[i][0].transcript;
             this._handleUserTranscript(text, true);
          } else {
             interimTranscript += event.results[i][0].transcript;
          }
        }
        if (interimTranscript) {
             this._handleUserTranscript(interimTranscript, false);
        }
      };
      this.recognition.onerror = (event: any) => { if (event.error !== 'no-speech') console.warn("Speech error", event.error); };
      this.recognition.onend = () => { if (this.isRecording) try { this.recognition.start(); } catch(e) {} };
    }
  }

  setupControllerListeners() {
    this.mediaController?.addEventListener('motion-detected', () => {
        this._updateActivity(); // Motion counts as activity
        if (this.currentActivationMode === 'camera') {
            this.updateStatus(this.t('status.motion_detected', 'Motion detected! Starting...'));
            this.startRecording();
        }
    });
    this.accelerometerController?.addEventListener('motion-detected', () => {
        this._updateActivity();
        if (this.currentActivationMode === 'accelerometer') {
            this.updateStatus(this.t('status.motion_detected', 'Motion detected! Starting...'));
            this.startRecording();
        }
    });
    this.faceRecognitionController?.addEventListener('face-detected', (e: any) => {
        const { match, descriptor, demographics, coordinates } = e.detail;
        this.lastFaceDetectedTime = Date.now();
        this._updateActivity(); // Face detected counts as activity
        
        // Update Face Position for 3D Gaze Tracking
        if (coordinates) {
            this.facePosition = { x: coordinates.x, y: coordinates.y, active: true };
        } else {
            // If scanning event has no coords, assume lost tracking or no face
            // We use a timeout logic in visual-3d typically, but setting active=false here is safer
            this.facePosition = { x: 0.5, y: 0.5, active: false };
        }
        
        const bufferSeconds = this.settingsJson?.system?.identityBufferSeconds || 30;
        const inBuffer = (Date.now() - this.lastIdentitySwitchTime) < (bufferSeconds * 1000);

        if (match.label !== 'scanning...') {
             if (inBuffer && match.label !== this.userId) return; 

             this.currentFaceId = match.label;
             this._updatePairingStatus();
             
             if (match.label !== 'unknown' && match.label !== this.userId) {
                console.log(`Face Identified as: ${match.label}. Switching profile.`);
                this.switchUser(match.label);
             }
             
             const isTemp = this.userId.startsWith('customer-');
             const isGrace = (Date.now() - this.creationTime) < 5000;

             if (descriptor && !(isTemp && isGrace)) {
                 this._saveFaceDescriptor(descriptor);
             }
        }
        
        if (this.isRecording && descriptor && !(this.userId.startsWith('customer-') && (Date.now() - this.creationTime) < 5000)) {
             this.faceController?.triggerCapture(this.userId, this.clientName);
        }

        if (demographics) {
            let descParts = [];
            if (demographics.gender) descParts.push(`gender: ${demographics.gender}`);
            if (demographics.age) descParts.push(`approx age: ${demographics.age}`);
            if (demographics.expression) descParts.push(`expression: ${demographics.expression}`);
            if (descParts.length > 0) {
                this.currentVisualContext = `The user is present. Attributes: ${descParts.join(', ')}.`;
            }
        }
    });
    this.voiceController?.addEventListener('voice-identified', (e: any) => {
        this._updateActivity();
        const { match } = e.detail;
        this.currentVoiceId = match.label;
        this._updatePairingStatus();
        
        const bufferSeconds = this.settingsJson?.system?.identityBufferSeconds || 30;
        const inBuffer = (Date.now() - this.lastIdentitySwitchTime) < (bufferSeconds * 1000);
        
        if (inBuffer && match.label !== this.userId) return;

        if (match.label !== 'unknown' && match.label !== this.userId) {
             console.log(`Voice Identified as: ${match.label}. Switching profile.`);
             this.switchUser(match.label);
        }
    });
    this.sessionController?.addEventListener('status', (e: any) => this.updateStatus(e.detail));
    this.sessionController?.addEventListener('error', (e: any) => this.updateError(e.detail));
    this.sessionController?.addEventListener('close', (e: any) => {
        if (this.ignoreCloseEvent) return;
        // Clear dangling transcription state
        this.currentOutputTranscription = '';
        this.currentInputTranscription = '';
        
        this._triggerMemorySummarization();
        this.faceController?.resetSession();
        
        // Reconnect with exponential backoff and retry limit
        if (this.reconnectAttempts >= GdmLiveAudio.MAX_RECONNECT) {
            this.updateError(this.t('error.connection_lost', 'Connection lost. Please refresh the page.'));
            return;
        }
        const delay = GdmLiveAudio.RECONNECT_BACKOFF[this.reconnectAttempts] || 30000;
        this.reconnectAttempts++;
        this.updateStatus(this.t('status.closed', 'Session closed: ') + e.detail.reason + `. Reconnecting in ${delay/1000}s...`);
        this.safeTimeout(() => this.initSession(), delay);
    });
    this.sessionController?.addEventListener('speaking-changed', (e: any) => {
        this.isAiSpeaking = e.detail;
        if (e.detail) {
            this.lastInteractionTime = Date.now();
            this._updateActivity();
        }
    });
    this.sessionController?.addEventListener('output-transcription', (e: any) => {
        const trans = e.detail;
        if (trans.text) {
            this.lastInteractionTime = Date.now();
            this._updateActivity();
            this.currentOutputTranscription += trans.text;
            this.aiSubtitles = this.currentOutputTranscription;
            this.checkForKeywords(this.currentOutputTranscription);
        }
    });
    this.sessionController?.addEventListener('turn-complete', () => {
        if (this.currentOutputTranscription.trim()) {
            const timestamp = Date.now();
            this.history = [...this.history, { role: 'ai', text: this.currentOutputTranscription, timestamp }];
            if (this.history.length > GdmLiveAudio.MAX_HISTORY) {
                this.history = this.history.slice(-GdmLiveAudio.MAX_HISTORY);
            }
            this._logToBackend('ai', this.currentOutputTranscription, timestamp);
            this.currentOutputTranscription = '';
            this.safeTimeout(() => {
                if (!this.isAiSpeaking && !this.currentOutputTranscription) {
                    this.aiSubtitles = '';
                }
            }, 3000);
        }
    });
    this.sessionController?.addEventListener('interrupted', () => {
        if (this.currentOutputTranscription.trim()) {
            const timestamp = Date.now();
            const text = this.currentOutputTranscription + ' [Interrupted]';
            this.history = [...this.history, { role: 'ai', text, timestamp }];
            if (this.history.length > GdmLiveAudio.MAX_HISTORY) {
                this.history = this.history.slice(-GdmLiveAudio.MAX_HISTORY);
            }
            this._logToBackend('ai', text, timestamp);
        }
        this.currentOutputTranscription = '';
        this.aiSubtitles = '';
    });
  }
  
  private switchUser(newUserId: string) {
      if (!newUserId || newUserId === this.userId) return;
      
      this.userId = newUserId;
      this.lastIdentitySwitchTime = Date.now(); 
      this._updateActivity();
      
      localStorage.setItem('gdm_user_id', this.userId);
      const welcomeMsg = this.t('status.welcome', 'Welcome back, ') + newUserId.replace(/-/g, ' ') + '!';
      this.updateStatus(welcomeMsg);
      this.requestUpdate(); 
      this.initSession(); // Reload session with new memory
      
      // Auto Greeting Logic
      this._triggerAutoGreeting(newUserId);
  }
  
  private _triggerAutoGreeting(userId: string) {
      const config = this.contextJson?.greeting_config;
      
      // 1. Feature Check
      if (!config || !config.enabled) return;
      
      // 2. Ignore temporary/unknown users
      if (userId.startsWith('customer-') || userId === 'unknown') return;
      
      // 3. Cooldown Check
      const now = Date.now();
      const lastTime = this.lastGreetingTimes.get(userId) || 0;
      const cooldownMs = (config.cooldown_seconds || 300) * 1000;
      
      if (now - lastTime < cooldownMs) {
          console.log(`Greeting skipped for ${userId} (Cooldown active)`);
          return;
      }
      
      // 4. Activity Check - Don't interrupt if AI is already talking
      if (this.isAiSpeaking) {
          console.log(`Greeting skipped for ${userId} (AI is speaking)`);
          return;
      }
      
      // 5. Execute Greeting
      this.lastGreetingTimes.set(userId, now);
      
      const template = config.prompt_template || "The user {name} has approached. Greet them warmly and specifically by name.";
      const prompt = template.replace('{name}', userId.replace(/-/g, ' '));
      const delayMs = (config.delay_seconds ?? 1.5) * 1000;
      
      console.log(`Triggering Auto-Greeting for ${userId} in ${delayMs}ms`);
      
      // Delay slightly to ensure session is reconnected/stabilized
      this.safeTimeout(() => {
          this.sessionController?.sendText(`SYSTEM NOTIFICATION: ${prompt}`);
      }, delayMs);
  }
  
  private _updatePairingStatus() {
      const canEnroll = this.currentVoiceId === 'unknown';
      if (canEnroll) {
          if (this.settingsJson?.audio?.voiceRecognition?.autoEnroll) {
              this.showVoiceEnroll = false; 
              this._enrollVoice(true); 
          } else {
              this.showVoiceEnroll = true;
          }
      } else {
          this.showVoiceEnroll = false;
      }
  }
  
  private async _enrollVoice(isAuto = false) {
      this._updateActivity();
      if (this.isEnrolling) return;
      if (!this.vadActive && !isAuto) {
          alert(this.t('ui.speak_to_enroll', 'Please speak while enrolling voice.'));
          return;
      }
      this.isEnrolling = true;
      try {
          const targetId = this.userId; 
          const success = await this.voiceController?.enrollVoice(targetId, this.clientName);
          if (success) {
              if (!isAuto) alert(`Voice paired with ${targetId}!`);
              else {
                  this.updateStatus(this.t('status.voice_enrolled', `Voice automatically paired with ${targetId}`));
              }
              this.showVoiceEnroll = false;
              this.currentVoiceId = targetId; 
          } else if (!isAuto) {
              alert(this.t('ui.enroll_failed', 'Voice not detected. Try again.'));
          }
      } catch(e: any) { 
          if (!isAuto) alert(e.message); 
      } finally { this.isEnrolling = false; }
  }
  
  private async _saveFaceDescriptor(descriptor: number[]) {
      const now = Date.now();
      if (now - this.lastFaceSaveTime < 5000) return;
      this.lastFaceSaveTime = now;
      this.faceRecognitionController?.addKnownFace(this.userId, descriptor);
      try {
          await fetch('/api/memory/face-descriptor', {
              method: 'POST',
              headers: {'Content-Type': 'application/json'},
              body: JSON.stringify({ theme: this.clientName, userId: this.userId, descriptor })
          });
      } catch(e) {}
  }
  
  private _handleUserTranscript(text: string, isFinal: boolean) {
      if (!text) return;
      this.lastInteractionTime = Date.now();
      this._updateActivity();
      this.userSubtitles = `${this.t('ui.you', 'You')}: ${text}`;
      
      if (isFinal) {
          const timestamp = Date.now();
          this.history = [...this.history, { role: 'user', text: text, timestamp }];
          if (this.history.length > GdmLiveAudio.MAX_HISTORY) {
              this.history = this.history.slice(-GdmLiveAudio.MAX_HISTORY);
          }
          this._logToBackend('user', text, timestamp);
          if (this.settingsJson?.system?.limits?.enabled) this._incrementInteractionCount();
          this.checkForKeywords(text);
          this.processedKeywords.clear();
          
          const namePattern = /\b(my name is|my name's|i am|i'm|call me|soy|mi nombre es|me llamo|me dicen|eu sou|meu nome é|je m'appelle)\s+([a-zA-Z\u00C0-\u00FF]+)(?:\s+([a-zA-Z\u00C0-\u00FF]+))?/i;
          const match = text.match(namePattern);
          
          if (match && match[2]) {
              const firstName = match[2];
              const lastName = match[3] || '';
              const fullName = `${firstName}-${lastName}`.replace(/-$/, ''); 
              if (fullName.length > 2) {
                  console.log(`Regex detected name: ${fullName}. Switching identity.`);
                  this._performDirectRename(fullName);
              }
          }
          
          this.safeTimeout(() => { if (this.userSubtitles.includes(text)) this.userSubtitles = ''; }, 3000);
      }
  }
  
  private async _performDirectRename(newName: string) {
      if (this.userId === newName || newName.toLowerCase().includes('agent')) return;
      try {
          const response = await fetch(`/api/people/${this.clientName}/${this.userId}/rename`, {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ newName: newName })
          });
          if (response.ok) {
              const data = await response.json();
              if (data.success && data.newId) {
                  this.switchUser(data.newId);
                  this.updateStatus(this.t('status.welcome', 'Identity updated: ') + data.newId);
                  await this.faceRecognitionController?.loadKnownFaces(this.clientName);
                  console.log("Vectors reloaded after rename.");
              }
          }
      } catch (e) {
          console.warn("Direct rename failed", e);
      }
  }
  
  private async _checkUsageLimits(): Promise<boolean> {
      if (!this.settingsJson?.system?.limits?.enabled) return true;
      try {
          const res = await fetch(`/api/usage/${this.clientName}`);
          if (res.ok) {
              const stats = await res.json();
              const limits = this.settingsJson.system.limits;
              if (stats.day.count >= limits.daily) { this.updateError(this.t('error.limit_daily', 'Daily interaction limit reached.')); return false; }
              if (stats.week.count >= limits.weekly) { this.updateError(this.t('error.limit_weekly', 'Weekly interaction limit reached.')); return false; }
              if (stats.month.count >= limits.monthly) { this.updateError(this.t('error.limit_monthly', 'Monthly interaction limit reached.')); return false; }
          }
      } catch (e) { console.warn("Failed to check usage limits", e); }
      return true;
  }

  private async _incrementInteractionCount() {
      try { await fetch(`/api/usage/${this.clientName}`, { method: 'POST' }); } catch (e) {}
  }
  
  private t(key: string, defaultText: string): string {
      if (this.translationsJson && this.translationsJson[key]) return this.translationsJson[key];
      return defaultText;
  }
  
  // Safe setTimeout that tracks IDs for cleanup on destroy/reset
  private safeTimeout(fn: () => void, ms: number): number {
      const id = window.setTimeout(() => {
          this.pendingTimers.delete(id);
          fn();
      }, ms);
      this.pendingTimers.add(id);
      return id;
  }
  
  private clearAllTimers() {
      for (const id of this.pendingTimers) clearTimeout(id);
      this.pendingTimers.clear();
  }
  
  private setupSSE() {
      if (this.eventSource) this.eventSource.close(); // Guard against duplicates
      this.eventSource = new EventSource('/api/events');
      this.eventSource.onmessage = (event) => {
          try {
              const data = JSON.parse(event.data);
              if (data.type === 'settings_updated') {
                  this.updateStatus(this.t('status.update', 'Remote update detected. Refreshing config...'));
                  this.fetchConfiguration();
              }
          } catch(e) { console.error("SSE Error", e); }
      };
  }

  connectedCallback() {
    super.connectedCallback();
    window.addEventListener('keydown', this._handleGlobalKeydown);
    // Add global activity trackers for idle reload logic
    window.addEventListener('mousemove', this._handleUserActivity);
    window.addEventListener('click', this._handleUserActivity);
    window.addEventListener('touchstart', this._handleUserActivity);
    window.addEventListener('keydown', this._handleUserActivity);
    
    setTimeout(() => {
        const video = (this as any).shadowRoot?.querySelector('#webcam-feed') as HTMLVideoElement;
        if (video) {
            this.mediaController?.setVideoElement(video);
            this.faceController?.setVideoElement(video); 
            this.faceRecognitionController?.setVideoElement(video); 
        }
    }, 0);
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    window.removeEventListener('keydown', this._handleGlobalKeydown);
    window.removeEventListener('mousemove', this._handleUserActivity);
    window.removeEventListener('click', this._handleUserActivity);
    window.removeEventListener('touchstart', this._handleUserActivity);
    window.removeEventListener('keydown', this._handleUserActivity);
    
    // Clear all tracked timers
    this.clearAllTimers();
    if (this._clickTimer) { clearTimeout(this._clickTimer); this._clickTimer = null; }
    
    if (this.eventSource) { this.eventSource.close(); this.eventSource = null; }
    if (this.watchdogInterval) { clearInterval(this.watchdogInterval); this.watchdogInterval = null; }
    this._stopVadUiLoop();
    this.accelerometerController?.stop();
    this.faceRecognitionController?.dispose(); 
    this.voiceController?.stop();
    this.mediaController?.stopCamera();
    this.audioController?.stop();
    this.ignoreCloseEvent = true;
    this.sessionController?.disconnect();
    
    // Stop dedicated audio stream tracks (prevent mic staying on)
    if (this.dedicatedAudioStream) {
        this.dedicatedAudioStream.getTracks().forEach(t => t.stop());
        this.dedicatedAudioStream = null;
    }
    
    // Clean up SpeechRecognition
    if (this.recognition) {
        this.recognition.onend = null;
        this.recognition.onresult = null;
        this.recognition.onerror = null;
        try { this.recognition.stop(); } catch(e) {}
        this.recognition = null;
    }
    
    // Clean up keyword audio element
    if (this.keywordAudio) {
        this.keywordAudio.pause();
        this.keywordAudio.removeAttribute('src');
        this.keywordAudio.load();
        this.keywordAudio = null;
    }
    
    // Clean audio contexts
    if (this.inputAudioContext) { this.inputAudioContext.close(); this.inputAudioContext = null; }
    if (this.outputAudioContext) { this.outputAudioContext.close(); this.outputAudioContext = null; }
  }
  
  private _handleUserActivity = () => {
      this._updateActivity();
  };

  private _handleGlobalKeydown = (e: KeyboardEvent) => {
    if (e.ctrlKey && e.altKey) {
      if (e.code === 'KeyS') this._controlsVisible = true;
      else if (e.code === 'KeyH') this._controlsVisible = false;
    }
  };
  
  private _handleScreenClick(e: MouseEvent) {
      const path = e.composedPath();
      const isInteractive = path.some((el) => {
          return el instanceof HTMLElement && (
              el.tagName === 'BUTTON' || el.tagName === 'INPUT' || el.tagName === 'SELECT' ||
              el.classList.contains('control-btn')
          );
      });
      if (isInteractive) return;
      this._clickBuffer++;
      if (this._clickTimer) clearTimeout(this._clickTimer);
      this._clickTimer = setTimeout(() => {
          if (this._clickBuffer === 2) this._toggleFullscreen();
          else if (this._clickBuffer === 3) this._toggleUi();
          this._clickBuffer = 0;
      }, 400); 
  }

  private _toggleFullscreen() {
      if (!document.fullscreenElement) {
          document.documentElement.requestFullscreen().catch(err => { console.warn(`Error attempting to enable fullscreen: ${err.message}`); });
      } else {
          if (document.exitFullscreen) document.exitFullscreen();
      }
  }

  private _toggleUi() { this._controlsVisible = !this._controlsVisible; }

  updated(changedProperties: Map<string | symbol, unknown>) {
    if (changedProperties.has('settingsJson') && this.settingsJson) {
      const newSettings = this.settingsJson;
      const oldSettings = changedProperties.get('settingsJson') as any;
      this.audioController?.updateSettings(newSettings);
      this.mediaController?.calculateRoi(newSettings);
      this.faceRecognitionController?.updateSettings(newSettings);
      this.voiceController?.updateSettings(newSettings);
      if (oldSettings) {
          const oldMode = oldSettings?.interaction?.mode || (oldSettings?.camera?.useMotionDetection ? 'camera' : 'manual');
          const newMode = newSettings.interaction?.mode || (newSettings.camera.useMotionDetection ? 'camera' : 'manual');
          if (oldMode !== newMode) {
              this.currentActivationMode = newMode;
              this._handleActivationModeChange(newMode);
          }
          if (this.mediaController?.stream && (oldSettings?.camera?.deviceId !== newSettings.camera.deviceId)) {
              this.mediaController.stopCamera();
              if (this.currentActivationMode === 'camera') this.initWebcamAndMotionDetection();
          }
          if (oldSettings?.avatar?.voice !== newSettings.avatar?.voice) this.reset();
      }
      this.updateRoiBoxCss();
      
      // Update Watchdog
      this._initWatchdog();
    }
  }

  private _handleActivationModeChange(mode: 'camera' | 'manual' | 'accelerometer') {
      this.mediaController?.stopMotionDetection();
      this.accelerometerController?.stop();
      
      const faceRecEnabled = this.settingsJson?.interaction?.faceRecognition?.enabled ?? true;

      if (mode === 'camera') {
          this.initWebcamAndMotionDetection();
      } else {
          if (faceRecEnabled) {
              this.initWebcamAndMotionDetection();
              this.mediaController?.stopMotionDetection(); 
          } else {
              this.mediaController?.stopCamera();
              this.faceRecognitionController?.stopScanning();
          }
          
          if (mode === 'accelerometer') {
              const threshold = this.settingsJson?.interaction?.accelerometer?.threshold || 3.0;
              this.accelerometerController?.start(threshold);
              this.updateStatus(this.t('status.ready_shake', 'Ready. Shake to start.'));
          } else {
              this.updateStatus(this.t('status.ready_manual', 'Ready. Press record to start.'));
          }
      }
  }
  
  private async _requestAccelerometerPermission() {
      try {
          const res = await this.accelerometerController?.requestPermission();
          if (res === 'granted') {
              const threshold = this.settingsJson?.interaction?.accelerometer?.threshold || 3.0;
              this.accelerometerController?.start(threshold);
          } else {
              alert(this.t('ui.permission_denied', 'Permission denied'));
          }
      } catch(e) { console.error(e); }
  }

  private updateRoiBoxCss() {
      const roi = this.mediaController?.getRoiBox();
      const roiEl = (this as any).shadowRoot?.querySelector('#roi-box') as HTMLElement;
      if (roiEl && this.settingsJson && roi) {
          roiEl.style.left = `${(roi.x / 160) * 100}%`;
          roiEl.style.top = `${(roi.y / 120) * 100}%`;
          roiEl.style.width = `${(roi.width / 160) * 100}%`;
          roiEl.style.height = `${(roi.height / 120) * 100}%`;
      }
  }

  // --- CONFIG LOADING ---
  private async fetchConfiguration() {
      const urlParams = new URLSearchParams(window.location.search);
      let clientParam = urlParams.get('cliente');
      const contextParam = urlParams.get('context');

      if (!clientParam) {
        try {
          const res = await fetch(`/api/default-theme?t=${Date.now()}`);
          if (res.ok) {
            const data = await res.json();
            if (data && data.theme) clientParam = data.theme;
          }
        } catch (e) { console.warn('Failed to fetch default theme', e); }
      }

      this.clientName = clientParam || 'default';

      let settingsTarget = `/clients/${this.clientName}/settings.json`;
      let contextTarget = `/clients/${this.clientName}/context.json`;
      let translationsTarget = `/clients/${this.clientName}/translations.json`;
      
      if (contextParam) {
          contextTarget = (contextParam.includes('/') || contextParam.includes('http')) 
              ? contextParam 
              : `/clients/${contextParam}`; 
      }

      if (urlParams.get('cliente') && this.clientName !== 'default') {
          try {
              const check = await fetch(settingsTarget, { method: 'HEAD' });
              if (!check.ok) {
                  throw new Error(`ACCESS DENIED: Theme '${this.clientName}' not found.`);
              }
          } catch (e: any) {
              throw new Error(`ACCESS DENIED: Theme '${this.clientName}' check failed.`);
          }
      }

      const fetchJson = async (target: string, fallback: string) => {
        const bust = Date.now();
        let res = await fetch(`${target}?t=${bust}`);
        if (!res.ok && target !== fallback) {
          res = await fetch(`${fallback}?t=${bust}`);
        }
        if (!res.ok) {
          if (target.includes('translations')) return {};
          throw new Error(`Failed to load ${target}`);
        }
        return res.json();
      };

      try {
          const [contextData, settingsData, translationsData] = await Promise.all([
              fetchJson(contextTarget, '/context.json'),
              fetchJson(settingsTarget, '/settings.json'),
              fetchJson(translationsTarget, '/translations.json'),
          ]);

          this.contextJson = contextData;
          this.settingsJson = settingsData;
          this.translationsJson = translationsData;
          
          this.audioController?.updateSettings(this.settingsJson);
          this.faceRecognitionController?.updateSettings(this.settingsJson);
          this.error = ''; // Clear errors on success
      } catch (e: any) {
          this.updateError(e.message);
      }
  }

  private async initClient() {
    try {
      await this.fetchConfiguration();
      
      const protectionEnabled = this.settingsJson?.security?.frontendProtectionEnabled;
      
      if (protectionEnabled) {
          // Check persistence
          const storedAuth = localStorage.getItem('gdm_frontend_auth');
          const expiryStr = localStorage.getItem('gdm_frontend_auth_expiry');
          
          if (storedAuth === 'true' && expiryStr) {
              const expiry = parseInt(expiryStr, 10);
              if (Date.now() < expiry) {
                  this.isFrontendAuthenticated = true;
              } else {
                  // Expired
                  localStorage.removeItem('gdm_frontend_auth');
                  localStorage.removeItem('gdm_frontend_auth_expiry');
                  this.isFrontendAuthenticated = false;
                  return;
              }
          } else {
              this.isFrontendAuthenticated = false;
              return;
          }
      } else {
          this.isFrontendAuthenticated = true;
      }
      
      await this._startSessionFlow();
    } catch (e: any) {
      // Don't throw, just let render handle error
    }
  }
  
  private async _startSessionFlow() {
      try {
          const configRes = await fetch('/config.json');
          const config = await configRes.json();
          let apiKey = config.API_KEY;
          if (this.settingsJson?.admin?.apiKey) apiKey = this.settingsJson.admin.apiKey;

          this.updateStatus(this.t('status.loaded', 'Context, settings and config loaded.'));
          await this.sessionController?.initClient(apiKey);
          await this.initSession();

          const mode = this.settingsJson?.interaction?.mode || (this.settingsJson?.camera?.useMotionDetection ? 'camera' : 'manual');
          this.currentActivationMode = mode;
          this._handleActivationModeChange(mode);
      } catch (e: any) {
          this.updateError(e.message);
      }
  }

  private async initWebcamAndMotionDetection() {
    if (this.mediaController?.stream) {
        const faceRecEnabled = this.settingsJson?.interaction?.faceRecognition?.enabled ?? true;
        if (faceRecEnabled) {
            this._startFaceRecognition();
        }
        if (this.currentActivationMode === 'camera') {
            this.mediaController?.startMotionDetection(this.settingsJson);
            this.updateStatus(this.t('status.ready_motion', 'Ready. Move hand in the box to start.'));
        }
        return;
    }
    try {
        if (this.currentActivationMode === 'camera') {
            this.updateStatus(this.t('status.starting', 'Starting capture...'));
        }
        const audioConstraints = {
            echoCancellation: this.settingsJson?.audio?.echoCancellation ?? true,
            noiseSuppression: this.settingsJson?.audio?.noiseSuppression ?? true,
            autoGainControl: this.settingsJson?.audio?.autoGainControl ?? true,
        };
        await this.mediaController?.startCamera(this.settingsJson?.camera?.deviceId, audioConstraints);
        const video = (this as any).shadowRoot?.querySelector('#webcam-feed') as HTMLVideoElement;
        if (video) {
            this.mediaController?.setVideoElement(video);
            this.faceController?.setVideoElement(video); 
            this.faceRecognitionController?.setVideoElement(video); 
        }
        
        const faceRecEnabled = this.settingsJson?.interaction?.faceRecognition?.enabled ?? true;
        if (faceRecEnabled) {
            this._startFaceRecognition();
        }

        if (this.currentActivationMode === 'camera') {
            this.mediaController?.startMotionDetection(this.settingsJson);
            this.updateStatus(this.t('status.ready_motion', 'Ready. Move hand in the box to start.'));
        }
    } catch (e: any) {
        console.warn("Camera init failed:", e);
        this.updateStatus(this.t('status.camera_failed', 'Camera not available. Switched to Audio-Only Mode.'));
        if (this.currentActivationMode === 'camera') {
            this.currentActivationMode = 'manual';
            this.mediaController?.stopCamera(); 
        }
    }
  }
  
  private async _startFaceRecognition() {
      if (this.settingsJson?.interaction?.faceRecognition?.enabled) {
          await this.faceRecognitionController?.loadKnownFaces(this.clientName);
          this.faceRecognitionController?.startScanning();
      }
      await this.voiceController?.loadKnownVoices(this.clientName);
  }

  private async initSession() {
    if (this.isInitializingSession) return;
    this.isInitializingSession = true;
    
    let knowledgeBaseText = '';
    const urlParams = new URLSearchParams(window.location.search);
    const docsFilter = urlParams.get('docs');

    if (this.clientName) {
        try {
            const knowledgeUrl = docsFilter 
                ? `/api/knowledge/${this.clientName}?files=${encodeURIComponent(docsFilter)}` 
                : `/api/knowledge/${this.clientName}`;
            const res = await fetch(knowledgeUrl);
            if (res.ok) {
                const data = await res.json();
                if (data.text) knowledgeBaseText = `\n\n--- KNOWLEDGE BASE ---\n${data.text}\n`;
            }
        } catch (e) { console.warn("Failed to load knowledge", e); }
    }

    let memoryContext = '';
    try {
        if (this.clientName && this.userId) {
            const memRes = await fetch(`/api/memory/${this.clientName}/${this.userId}`);
            if (memRes.ok) {
                const memData = await memRes.json();
                if (memData.facts && memData.facts.length > 0) {
                    memoryContext = memData.facts.map((f: string) => `- ${f}`).join('\n');
                }
            }
        }
    } catch (e) { console.warn("Failed to load memory", e); }

    try {
        await this.sessionController?.connect(this.settingsJson, this.contextJson, knowledgeBaseText, memoryContext, this.currentVisualContext);
        this.updateStatus(this.t('status.connected', 'Session connected'));
        this.reconnectAttempts = 0; // Reset backoff on successful connection
    } catch (e: any) {
        this.updateError(e.message);
    } finally {
        this.isInitializingSession = false;
    }
  }
  
  private async _triggerMemorySummarization() {
      if (!this.history || this.history.length === 0) return;
      console.log("Sending summarization request for:", this.userId);
      try {
          const response = await fetch('/api/memory/summarize', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                  theme: this.clientName,
                  userId: this.userId,
                  history: this.history
              })
          });
          
          if (response.ok) {
              const data = await response.json();
              if (data.success === false) {
                  console.warn("Summarization logic skipped or failed:", data.error || "");
              }
              if (data.switchedToUser) {
                  this.switchUser(data.switchedToUser);
                  this.updateStatus(this.t('status.welcome', 'Identity updated: ') + data.switchedToUser);
              }
          }
      } catch (e) { console.error("Summarization failed", e); }
  }

  private async startRecording() {
    if (this.isRecording) return;
    const canStart = await this._checkUsageLimits();
    if (!canStart) return;
    
    if (!this.mediaController?.stream) {
        await this.initWebcamAndMotionDetection();
        if (this.currentActivationMode === 'camera' && !this.mediaController?.stream) return;
    }
    
    if (this.currentActivationMode === 'camera') this.mediaController?.stopMotionDetection();
    if (this.currentActivationMode === 'accelerometer') this.accelerometerController?.stop();

    this.updateStatus(this.t('status.recording', '🔴 Recording...'));
    if (this.recognition) try { this.recognition.start(); } catch(e) {}
    
    try {
        await this.audioController?.context.resume();

        let stream = this.mediaController?.stream;
        const hasAudio = stream && stream.getAudioTracks().length > 0 && stream.active;

        if (!hasAudio) {
             console.log("Acquiring dedicated audio stream...");
             // Stop any existing dedicated stream before acquiring a new one
             if (this.dedicatedAudioStream) {
                 this.dedicatedAudioStream.getTracks().forEach(t => t.stop());
             }
             this.dedicatedAudioStream = await navigator.mediaDevices.getUserMedia({ 
                 audio: {
                     echoCancellation: this.settingsJson?.audio?.echoCancellation ?? true,
                     noiseSuppression: this.settingsJson?.audio?.noiseSuppression ?? true,
                     autoGainControl: this.settingsJson?.audio?.autoGainControl ?? true
                 } 
             });
             stream = this.dedicatedAudioStream;
        }

        await this.audioController?.start(
            stream!, 
            (blob) => {
                if (this.settingsJson?.audio?.preventSelfHearing && this.isAiSpeaking) {
                    return; 
                }
                
                // Voice Capture Logic
                if (!this.voiceSampleSaved && this.audioController?.vadActive) {
                    // Convert base64 data back to Int16 for accumulation
                    const rawBytes = decode(blob.data);
                    const int16 = new Int16Array(rawBytes.buffer);
                    this.voiceSampleBuffer.push(int16);
                    this.voiceSampleLength += int16.length;
                    
                    const durationSec = this.voiceSampleLength / 16000;
                    
                    // Stop collecting if we hit 10s
                    if (durationSec >= 10) {
                        this._saveVoiceSample();
                    } else if (durationSec >= 3 && !this.audioController?.vadActive) {
                        // If user paused and we have > 3s, assume good sample
                        this._saveVoiceSample();
                    }
                }
                
                if (!this.overlayVideo) this.sessionController?.sendAudio(blob);
            },
            this.settingsJson
        );
        if (this.settingsJson?.audio?.voiceRecognition?.enabled !== false) {
            // Need non-null inputAudioContext for voice recognition
            if (this.inputAudioContext && this.audioController) {
                this.voiceController?.start(this.inputAudioContext, this.audioController.inputNode); 
            }
        }
        this.isRecording = true;
        this.lastInteractionTime = Date.now();
        this._startVadUiLoop();
    } catch (e: any) {
        this.updateError(`Error: ${e.message}`);
        this.stopRecording();
    }
  }
  
  private async _saveVoiceSample() {
      if (this.voiceSampleSaved || this.voiceSampleBuffer.length === 0) return;
      
      console.log("Saving voice sample...");
      this.voiceSampleSaved = true;
      
      // Flatten buffer
      const fullBuffer = new Int16Array(this.voiceSampleLength);
      let offset = 0;
      for (const chunk of this.voiceSampleBuffer) {
          fullBuffer.set(chunk, offset);
          offset += chunk.length;
      }
      
      // Encode to WAV
      const wavBuffer = encodeWAV(fullBuffer, 16000);
      const wavBase64 = encode(new Uint8Array(wavBuffer));
      
      try {
          await fetch('/api/memory/voice-sample', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                  theme: this.clientName,
                  userId: this.userId,
                  originalId: this.userId, // Use current ID as filename basis
                  audio: wavBase64
              })
          });
          console.log("Voice sample saved.");
      } catch (e) {
          console.error("Failed to save voice sample", e);
      }
      
      // Clear memory
      this.voiceSampleBuffer = [];
      this.voiceSampleLength = 0;
  }

  private stopRecording() {
    if (!this.isRecording) return;
    this.updateStatus(this.t('status.stopping', 'Stopping recording...'));
    this.isRecording = false;
    this._stopVadUiLoop();
    this.audioController?.stop();
    this.voiceController?.stop();
    this.userSubtitles = '';
    this.aiSubtitles = '';
    this.processedKeywords.clear();
    if (this.recognition) try { this.recognition.stop(); } catch(e) {}
    
    // Stop dedicated audio stream tracks (mic LED off)
    if (this.dedicatedAudioStream) {
        this.dedicatedAudioStream.getTracks().forEach(t => t.stop());
        this.dedicatedAudioStream = null;
    }
    
    // Clear voice sample buffer to free memory
    this.voiceSampleBuffer = [];
    this.voiceSampleLength = 0;
    
    this._handleActivationModeChange(this.currentActivationMode);
  }
  
  // Click-to-Toggle Logic
  private _toggleRecording() {
      if (this.isRecording) {
          this.stopRecording();
      } else {
          this.startRecording();
      }
  }

  private _startVadUiLoop() {
      if (this.vadUiFrameId) cancelAnimationFrame(this.vadUiFrameId);
      const loop = () => {
          if (!this.isRecording) return;
          
          this.currentRms = this.audioController?.currentRms || 0;
          this.vadActive = this.audioController?.vadActive || false;

          // MIC IDLE TIMEOUT LOGIC
          const timeoutEnabled = this.settingsJson?.audio?.micTimeout?.enabled;
          if (timeoutEnabled) {
              const now = Date.now();
              // Activity = User talking OR AI talking. Reset timer if active.
              // Use explicit speech detection flag to handle "VAD Disabled" case correctly
              const userSpeaking = this.audioController?.isSpeechDetected;
              
              if (userSpeaking || this.isAiSpeaking) {
                  this.lastInteractionTime = now;
              } else {
                  // Inactive check
                  const timeoutSec = this.settingsJson?.audio?.micTimeout?.idleSeconds || 30;
                  if (now - this.lastInteractionTime > (timeoutSec * 1000)) {
                      console.log(`Mic Idle Timeout (${timeoutSec}s). Stopping recording.`);
                      this.stopRecording();
                      return; // Exit loop
                  }
              }
          }

          this.vadUiFrameId = requestAnimationFrame(loop);
      };
      loop();
  }

  private _stopVadUiLoop() {
      if (this.vadUiFrameId) {
          cancelAnimationFrame(this.vadUiFrameId);
          this.vadUiFrameId = null;
      }
      this.currentRms = 0;
      this.vadActive = false;
  }

  private triggerKeywordAction(keyword: any) {
      if (this.processedKeywords.has(keyword.term)) return;
      this.processedKeywords.add(keyword.term);
      console.log(`Triggering keyword action: ${keyword.term}`);

      if (keyword.type === 'image') {
          this.overlayImage = keyword.src;
          this.overlayVideo = null;
          this.overlayWebsite = null;
          const duration = this.settingsJson?.visuals?.ui?.popupDuration || 5000;
          this.safeTimeout(() => { if(this.overlayImage === keyword.src) this.overlayImage = null; }, duration);
      } else if (keyword.type === 'video') {
          this.stopRecording();
          this.overlayVideo = keyword.src;
          this.overlayImage = null;
          this.overlayWebsite = null;
      } else if (keyword.type === 'audio') {
          if (this.keywordAudio) {
              this.keywordAudio.pause();
              this.keywordAudio.removeAttribute('src');
              this.keywordAudio.load(); // Release media pipeline resources
          }
          this.keywordAudio = new Audio(keyword.src);
          this.keywordAudio.play().catch(e => console.warn("Audio play failed", e));
      } else if (keyword.type === 'website') {
          this.overlayWebsite = keyword.src;
          this.overlayImage = null;
          this.overlayVideo = null;
      }
  }
  
  private closeOverlay() {
      if (this.overlayVideo) {
          const vid = (this as any).shadowRoot?.querySelector('video');
          if (vid) vid.pause();
          this.startRecording(); 
      }
      this.overlayImage = null;
      this.overlayVideo = null;
      this.overlayWebsite = null;
  }

  private checkForKeywords(text: string) {
      const keywords = this.contextJson?.keywords || [];
      const lowerText = text.toLowerCase();
      
      keywords.forEach((k: any) => {
          if (lowerText.includes(k.term.toLowerCase()) && !this.processedKeywords.has(k.term)) {
              this.triggerKeywordAction(k);
          }
      });
  }

  private _logToBackend(role: string, content: string, timestamp: number) {
      fetch('/api/log-conversation', {
          method: 'POST',
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify({
              theme: this.clientName,
              userId: this.userId,
              role,
              content,
              timestamp
          })
      }).catch(e => console.warn("Logging failed", e));
  }
  
  private updateStatus(status: string) {
    this.status = status;
    this.error = '';
  }

  private updateError(error: string) {
    this.error = error;
    this.status = '';
  }

  // --- HARD RESET (Deep Memory Cleanup) ---
  private async reset() {
    this.stopRecording();
    this.ignoreCloseEvent = true; // Prevent close handler from firing during teardown
    this.mediaController?.stopCamera();
    this.audioController?.stop();
    this.sessionController?.disconnect();
    
    // Clear all pending timers to prevent stale callbacks
    this.clearAllTimers();
    
    // Explicit Disposal
    this.faceRecognitionController?.dispose();
    (this.faceController as any)?.dispose?.();
    (this.mediaController as any)?.dispose?.();
    
    // Stop dedicated audio stream
    if (this.dedicatedAudioStream) {
        this.dedicatedAudioStream.getTracks().forEach(t => t.stop());
        this.dedicatedAudioStream = null;
    }
    
    // Clean up keyword audio
    if (this.keywordAudio) {
        this.keywordAudio.pause();
        this.keywordAudio.removeAttribute('src');
        this.keywordAudio.load();
        this.keywordAudio = null;
    }
    
    // Force Visual Teardown
    const visual = (this as any).shadowRoot?.querySelector('gdm-live-audio-visuals-3d');
    if (visual && visual.forceRelease) {
        visual.forceRelease();
    }
    
    // Nullify all controllers to ensure GC sweeps them up
    // We will recreate them after contexts close
    this.faceRecognitionController = null;
    this.mediaController = null;
    this.audioController = null;
    this.sessionController = null;
    this.accelerometerController = null;
    this.voiceController = null;
    this.faceController = null;
    
    // Clean audio contexts — AWAIT close to prevent racing with new context creation
    // (Browsers limit to ~6 AudioContexts; creating new before old closes leaks them)
    const closePromises: Promise<void>[] = [];
    if (this.inputAudioContext) { 
        closePromises.push(this.inputAudioContext.close().catch(() => {})); 
        this.inputAudioContext = null; 
    }
    if (this.outputAudioContext) { 
        closePromises.push(this.outputAudioContext.close().catch(() => {})); 
        this.outputAudioContext = null; 
    }
    await Promise.all(closePromises);
    
    // Reset State
    this.initUserIdentity();
    this.voiceSampleSaved = false;
    this.voiceSampleBuffer = [];
    this.voiceSampleLength = 0;
    this.history = [];
    this.lastGreetingTimes.clear();
    this.processedKeywords.clear();
    this.currentOutputTranscription = '';
    this.currentInputTranscription = '';
    this.reconnectAttempts = 0;
    this.isInitializingSession = false;
    this.showVisuals = false; // Hide component to unmount
    
    this.updateStatus(this.t('status.reset', 'Session reset.'));
    
    console.log("System Deep Reset: Memory Flushed.");

    // Re-initialize after brief delay (contexts are guaranteed closed now)
    this.safeTimeout(() => {
        this.ignoreCloseEvent = false;
        
        // Re-create AudioContexts
        this._initAudioContexts();
        
        // Re-create Controllers
        this.mediaController = new MediaStreamController();
        this.accelerometerController = new AccelerometerController();
        this.faceController = new FaceCaptureController();
        this.faceRecognitionController = new FaceRecognitionController();
        this.voiceController = new VoiceRecognitionController();
        
        this.showVisuals = true; // Remount visual component
        
        // Push settings
        this.audioController?.updateSettings(this.settingsJson);
        this.mediaController?.calculateRoi(this.settingsJson);
        this.faceRecognitionController?.updateSettings(this.settingsJson);
        this.voiceController?.updateSettings(this.settingsJson);
        
        this.setupControllerListeners(); // Re-attach listeners
        this._handleActivationModeChange(this.currentActivationMode);
    }, 500);
  }

  private _handleLoginSubmit(detail: {password: string, remember: boolean}) {
      const pass = typeof detail === 'string' ? detail : detail.password;
      const remember = typeof detail === 'object' ? detail.remember : false;

      if (pass === (this.settingsJson?.security?.frontendPassword || '')) {
          this.isFrontendAuthenticated = true;
          this.loginError = '';
          
          if (remember) {
              const oneYear = 365 * 24 * 60 * 60 * 1000;
              const expiry = Date.now() + oneYear;
              localStorage.setItem('gdm_frontend_auth', 'true');
              localStorage.setItem('gdm_frontend_auth_expiry', expiry.toString());
          }
          
          this._startSessionFlow();
      } else {
          this.loginError = this.t('login.error', 'Incorrect Password');
      }
  }

  render() {
    if (this.operatorMode) {
        return html`
            <operator-onboarding 
                .operatorId=${this.operatorId}
                .clientName=${this.clientName}
                @done=${() => this.operatorMode = false}
            ></operator-onboarding>
        `;
    }

    // Better Loading/Error State
    if (!this.settingsJson) {
        if (this.error) {
            return html`
                <div class="loading-screen">
                    <div class="error-box">
                        <h3>Configuration Error</h3>
                        <p>${this.error}</p>
                        <button class="retry-btn" @click=${() => this.fetchConfiguration()}>Retry Connection</button>
                    </div>
                </div>
            `;
        }
        return html`
            <div class="loading-screen">
                <div class="spinner"></div>
                <div>Loading System...</div>
            </div>
        `;
    }

    if (!this.isFrontendAuthenticated) {
        return html`
            <login-overlay 
                .error=${this.loginError}
                .translations=${this.translationsJson}
                @login=${(e: CustomEvent) => this._handleLoginSubmit(e.detail)}
            ></login-overlay>
        `;
    }

    return html`
      <div class="main-wrapper" @click=${this._handleScreenClick}>
        ${this.showVisuals && this.audioController && this.sessionController ? html`
            <gdm-live-audio-visuals-3d 
                .inputNode=${this.audioController.inputNode}
                .outputNode=${this.sessionController.outputNode}
                .settings=${this.settingsJson}
                .isAiSpeaking=${this.isAiSpeaking}
                .facePosition=${this.facePosition}
                @animation-clips-found=${(e: CustomEvent) => this.animationClips = e.detail}
            ></gdm-live-audio-visuals-3d>
        ` : ''}

        <status-bar
            .status=${this.status}
            .vadActive=${this.vadActive}
            .currentRms=${this.currentRms}
            .currentFaceId=${this.currentFaceId}
            .currentVoiceId=${this.currentVoiceId}
            .showVoiceEnroll=${this.showVoiceEnroll}
            @enroll-voice=${() => this._enrollVoice(false)}
        ></status-bar>
        
        ${this.error ? html`<div style="position:absolute; bottom:120px; left:50%; transform:translateX(-50%); color: #ff4757; font-weight: bold; background:rgba(0,0,0,0.7); padding:8px 16px; border-radius:12px;">${this.error}</div>` : ''}

        <subtitle-overlay
            .userSubtitles=${this.userSubtitles}
            .aiSubtitles=${this.aiSubtitles}
            .settings=${this.settingsJson}
        ></subtitle-overlay>

        <media-overlays
            .image=${this.overlayImage}
            .video=${this.overlayVideo}
            .website=${this.overlayWebsite}
            @close=${this.closeOverlay}
        ></media-overlays>

        <control-dock
            .visible=${this._controlsVisible}
            .isRecording=${this.isRecording}
            .mode=${this.currentActivationMode}
            .translations=${this.translationsJson}
            @toggle-recording=${this._toggleRecording}
            @start-recording=${this.startRecording}
            @stop-recording=${this.stopRecording}
            @view-history=${() => this.isHistoryVisible = true}
            @reset-session=${() => this.reset()}
        ></control-dock>

        <div id="motion-detector" ?hidden=${!this.settingsJson?.camera?.useMotionDetection || this.currentActivationMode !== 'camera'}>
            <video id="webcam-feed" autoplay playsinline muted style="width:100%;height:100%;object-fit:cover;transform:scaleX(-1);"></video>
            <div id="roi-box"></div>
            <p style="position:absolute;bottom:0;width:100%;text-align:center;font-size:10px;background:rgba(0,0,0,0.5);margin:0;color:white;">${this.t('ui.motion_instruction', 'Move hand in box')}</p>
        </div>
        
        <chat-history
            .visible=${this.isHistoryVisible}
            .history=${this.history}
            .aiName=${this.contextJson?.instruction_config?.role || 'AI'}
            .translations=${this.translationsJson}
            @close=${() => this.isHistoryVisible = false}
        ></chat-history>
      </div>
    `;
  }
}
