Source: frontend/workbench/public/js/components/MemoryComponent.js

/**
 * Memory Component for Project Memory Management
 * Implements ChatGPT-style memory features using the new memory verbs
 */

import { apiService } from '../services/ApiService.js';
import { stateManager } from '../services/StateManager.js';
import { consoleService } from '../services/ConsoleService.js';
import DomUtils from '../utils/DomUtils.js';

export default class MemoryComponent {
  constructor() {
    this.memories = new Map();
    this.currentDomain = 'user';
    this.activeProject = null;
    this.memoryFilter = {
      domains: [],
      relevanceThreshold: 0.1,
      timeRange: null
    };
    
    // Bind methods
    this.handleRemember = this.handleRemember.bind(this);
    this.handleForget = this.handleForget.bind(this);
    this.handleRecall = this.handleRecall.bind(this);
    this.handleProjectContext = this.handleProjectContext.bind(this);
    this.handleFadeMemory = this.handleFadeMemory.bind(this);
    this.handleDomainSwitch = this.handleDomainSwitch.bind(this);
    this.handleMemoryFilter = this.handleMemoryFilter.bind(this);
  }

  /**
   * Initialize the memory component
   */
  async init() {
    consoleService.info('Initializing Memory Component');
    
    try {
      // Setup event listeners
      this.setupEventListeners();
      
      // Initialize UI state
      this.updateUI();
      
      // Load current memory state
      await this.loadMemoryState();
      
      consoleService.success('Memory Component initialized');
      
    } catch (error) {
      consoleService.error('Failed to initialize Memory Component', error);
      throw error;
    }
  }

  /**
   * Setup event listeners for memory controls
   */
  setupEventListeners() {
    // Remember form
    const rememberForm = DomUtils.$('#remember-form');
    if (rememberForm) {
      rememberForm.addEventListener('submit', this.handleRemember);
    }
    
    // Forget form
    const forgetForm = DomUtils.$('#forget-form');
    if (forgetForm) {
      forgetForm.addEventListener('submit', this.handleForget);
    }
    
    // Recall form
    const recallForm = DomUtils.$('#recall-form');
    if (recallForm) {
      recallForm.addEventListener('submit', this.handleRecall);
    }
    
    // Project context form
    const projectForm = DomUtils.$('#project-context-form');
    if (projectForm) {
      projectForm.addEventListener('submit', this.handleProjectContext);
    }
    
    // Fade memory form
    const fadeForm = DomUtils.$('#fade-memory-form');
    if (fadeForm) {
      fadeForm.addEventListener('submit', this.handleFadeMemory);
    }
    
    // Domain switcher
    const domainSelect = DomUtils.$('#memory-domain-select');
    if (domainSelect) {
      domainSelect.addEventListener('change', this.handleDomainSwitch);
    }
    
    // Memory filter controls
    const filterButton = DomUtils.$('#memory-filter-apply');
    if (filterButton) {
      filterButton.addEventListener('click', this.handleMemoryFilter);
    }
    
    // Quick action buttons
    this.setupQuickActions();
  }

  /**
   * Setup quick action buttons for common memory operations
   */
  setupQuickActions() {
    // Quick recall buttons
    const quickRecallButtons = DomUtils.$$('.quick-recall-button');
    quickRecallButtons.forEach(button => {
      button.addEventListener('click', (e) => {
        const query = e.target.dataset.query;
        const domain = e.target.dataset.domain;
        this.performQuickRecall(query, domain);
      });
    });
    
    // Project quick switch buttons
    const projectSwitchButtons = DomUtils.$$('.project-switch-button');
    projectSwitchButtons.forEach(button => {
      button.addEventListener('click', (e) => {
        const projectId = e.target.dataset.projectId;
        this.switchToProject(projectId);
      });
    });
  }

  /**
   * Handle remember form submission
   */
  async handleRemember(event) {
    event.preventDefault();
    
    const formData = new FormData(event.target);
    const content = formData.get('content');
    const domain = formData.get('domain') || this.currentDomain;
    const domainId = formData.get('domainId') || this.activeProject;
    const importance = parseFloat(formData.get('importance')) || 0.5;
    const tags = formData.get('tags')?.split(',').map(t => t.trim()).filter(t => t) || [];
    const category = formData.get('category') || '';
    
    if (!content?.trim()) {
      DomUtils.showToast('Content is required', 'error');
      return;
    }
    
    consoleService.info('Storing memory in domain: ' + domain, { domain, domainId, importance });
    
    try {
      DomUtils.showLoader('#remember-submit');
      
      const result = await apiService.remember({
        content: content.trim(),
        domain: domain,
        domainId: domainId,
        importance: importance,
        metadata: {
          tags: tags,
          category: category,
          source: 'workbench',
          timestamp: new Date().toISOString()
        }
      });
      
      if (result.success) {
        // Update local memories
        this.memories.set(result.domainId || domain, {
          content: content,
          domain: domain,
          domainId: domainId,
          importance: importance,
          timestamp: new Date(),
          metadata: { tags, category }
        });
        
        // Update UI
        this.updateMemoryDisplay();
        this.updateMemoryStats();
        
        // Clear form
        event.target.reset();
        
        // Show success
        DomUtils.showToast(`Memory stored in ${domain} domain`, 'success');
        consoleService.success('Memory stored successfully', result);
        
      } else {
        throw new Error(result.error || 'Failed to store memory');
      }
      
    } catch (error) {
      consoleService.error('Failed to store memory', error);
      DomUtils.showToast('Failed to store memory: ' + error.message, 'error');
      
    } finally {
      DomUtils.hideLoader('#remember-submit');
    }
  }

  /**
   * Handle forget form submission
   */
  async handleForget(event) {
    event.preventDefault();
    
    const formData = new FormData(event.target);
    const target = formData.get('target');
    const strategy = formData.get('strategy') || 'fade';
    const fadeFactor = parseFloat(formData.get('fadeFactor')) || 0.1;
    
    if (!target?.trim()) {
      DomUtils.showToast('Target is required', 'error');
      return;
    }
    
    consoleService.info('Forgetting memory', { target, strategy, fadeFactor });
    
    try {
      DomUtils.showLoader('#forget-submit');
      
      const result = await apiService.forget({
        target: target.trim(),
        strategy: strategy,
        fadeFactor: fadeFactor
      });
      
      if (result.success) {
        // Update UI to reflect faded memory
        this.updateMemoryDisplay();
        this.updateMemoryStats();
        
        // Clear form
        event.target.reset();
        
        // Show success
        DomUtils.showToast(`Memory ${strategy} applied to ${target}`, 'success');
        consoleService.success('Memory forgotten successfully', result);
        
      } else {
        throw new Error(result.error || 'Failed to forget memory');
      }
      
    } catch (error) {
      consoleService.error('Failed to forget memory', error);
      DomUtils.showToast('Failed to forget memory: ' + error.message, 'error');
      
    } finally {
      DomUtils.hideLoader('#forget-submit');
    }
  }

  /**
   * Handle recall form submission
   */
  async handleRecall(event) {
    event.preventDefault();
    
    const formData = new FormData(event.target);
    const query = formData.get('query');
    const domains = formData.get('domains')?.split(',').map(d => d.trim()).filter(d => d) || [];
    const relevanceThreshold = parseFloat(formData.get('relevanceThreshold')) || 0.1;
    const maxResults = parseInt(formData.get('maxResults')) || 10;
    
    // Time range
    const timeRangeStart = formData.get('timeRangeStart');
    const timeRangeEnd = formData.get('timeRangeEnd');
    const timeRange = (timeRangeStart || timeRangeEnd) ? {
      start: timeRangeStart || undefined,
      end: timeRangeEnd || undefined
    } : undefined;
    
    if (!query?.trim()) {
      DomUtils.showToast('Query is required', 'error');
      return;
    }
    
    consoleService.info('Recalling memories', { query, domains, relevanceThreshold, maxResults });
    
    try {
      DomUtils.showLoader('#recall-submit');
      
      const result = await apiService.recall({
        query: query.trim(),
        domains: domains.length > 0 ? domains : undefined,
        timeRange: timeRange,
        relevanceThreshold: relevanceThreshold,
        maxResults: maxResults
      });
      
      if (result.success) {
        // Display recall results
        this.displayRecallResults(result.memories, query);
        
        // Update memory stats
        this.updateMemoryStats();
        
        // Show success
        const memoriesFound = result.memoriesFound || 0;
        DomUtils.showToast(`Found ${memoriesFound} memories`, 'info');
        consoleService.success(`Recalled ${memoriesFound} memories`, result);
        
      } else {
        throw new Error(result.error || 'Failed to recall memories');
      }
      
    } catch (error) {
      consoleService.error('Failed to recall memories', error);
      DomUtils.showToast('Failed to recall memories: ' + error.message, 'error');
      
    } finally {
      DomUtils.hideLoader('#recall-submit');
    }
  }

  /**
   * Handle project context form submission
   */
  async handleProjectContext(event) {
    event.preventDefault();
    
    const formData = new FormData(event.target);
    const projectId = formData.get('projectId');
    const action = formData.get('action') || 'switch';
    const name = formData.get('name') || '';
    const description = formData.get('description') || '';
    const technologies = formData.get('technologies')?.split(',').map(t => t.trim()).filter(t => t) || [];
    const parentProject = formData.get('parentProject') || '';
    
    if (!projectId?.trim()) {
      DomUtils.showToast('Project ID is required', 'error');
      return;
    }
    
    consoleService.info('Managing project context', { projectId, action });
    
    try {
      DomUtils.showLoader('#project-context-submit');
      
      const result = await apiService.project_context({
        projectId: projectId.trim(),
        action: action,
        metadata: {
          name: name,
          description: description,
          technologies: technologies,
          parentProject: parentProject || undefined
        }
      });
      
      if (result.success) {
        // Update active project
        if (action === 'switch' || action === 'create') {
          this.activeProject = projectId;
          this.updateProjectDisplay();
        }
        
        // Update UI
        this.updateMemoryDisplay();
        this.updateMemoryStats();
        
        // Show success
        let message = `Project ${projectId} ${action}ed successfully`;
        if (action === 'create') message = `Project ${projectId} created`;
        else if (action === 'switch') message = `Switched to project ${projectId}`;
        else if (action === 'archive') message = `Project ${projectId} archived`;
        
        DomUtils.showToast(message, 'success');
        consoleService.success('Project context updated', result);
        
      } else {
        throw new Error(result.error || 'Failed to manage project context');
      }
      
    } catch (error) {
      consoleService.error('Failed to manage project context', error);
      DomUtils.showToast('Failed to manage project: ' + error.message, 'error');
      
    } finally {
      DomUtils.hideLoader('#project-context-submit');
    }
  }

  /**
   * Handle fade memory form submission
   */
  async handleFadeMemory(event) {
    event.preventDefault();
    
    const formData = new FormData(event.target);
    const domain = formData.get('domain');
    const fadeFactor = parseFloat(formData.get('fadeFactor')) || 0.1;
    const transition = formData.get('transition') || 'smooth';
    const preserveInstructions = formData.has('preserveInstructions');
    
    if (!domain?.trim()) {
      DomUtils.showToast('Domain is required', 'error');
      return;
    }
    
    consoleService.info('Fading memory domain', { domain, fadeFactor, transition });
    
    try {
      DomUtils.showLoader('#fade-memory-submit');
      
      const result = await apiService.fade_memory({
        domain: domain.trim(),
        fadeFactor: fadeFactor,
        transition: transition,
        preserveInstructions: preserveInstructions
      });
      
      if (result.success) {
        // Update UI to reflect faded domain
        this.updateMemoryDisplay();
        this.updateMemoryStats();
        
        // Clear form
        event.target.reset();
        
        // Show success
        DomUtils.showToast(`Domain ${domain} faded (${Math.round(fadeFactor * 100)}%)`, 'success');
        consoleService.success('Memory domain faded', result);
        
      } else {
        throw new Error(result.error || 'Failed to fade memory');
      }
      
    } catch (error) {
      consoleService.error('Failed to fade memory', error);
      DomUtils.showToast('Failed to fade memory: ' + error.message, 'error');
      
    } finally {
      DomUtils.hideLoader('#fade-memory-submit');
    }
  }

  /**
   * Handle domain switch
   */
  async handleDomainSwitch(event) {
    const newDomain = event.target.value;
    
    if (newDomain !== this.currentDomain) {
      this.currentDomain = newDomain;
      
      // Update UI to reflect new domain
      this.updateMemoryDisplay();
      this.updateDomainDisplay();
      
      consoleService.info('Switched to domain: ' + newDomain);
    }
  }

  /**
   * Handle memory filter changes
   */
  async handleMemoryFilter(event) {
    const domainsInput = DomUtils.$('#memory-filter-domains');
    const thresholdInput = DomUtils.$('#memory-filter-threshold');
    const startDateInput = DomUtils.$('#memory-filter-start');
    const endDateInput = DomUtils.$('#memory-filter-end');
    
    this.memoryFilter = {
      domains: domainsInput?.value.split(',').map(d => d.trim()).filter(d => d) || [],
      relevanceThreshold: parseFloat(thresholdInput?.value) || 0.1,
      timeRange: (startDateInput?.value || endDateInput?.value) ? {
        start: startDateInput?.value || undefined,
        end: endDateInput?.value || undefined
      } : null
    };
    
    // Apply filter and update display
    await this.applyMemoryFilter();
    
    consoleService.info('Memory filter applied', this.memoryFilter);
  }

  /**
   * Perform quick recall with predefined query and domain
   */
  async performQuickRecall(query, domain) {
    consoleService.info('Quick recall', { query, domain });
    
    try {
      const result = await apiService.recall({
        query: query,
        domains: domain ? [domain] : undefined,
        relevanceThreshold: 0.1,
        maxResults: 5
      });
      
      if (result.success) {
        this.displayRecallResults(result.memories, query, true);
        DomUtils.showToast(`Quick recall: ${result.memoriesFound} memories found`, 'info');
      }
      
    } catch (error) {
      consoleService.error('Quick recall failed', error);
      DomUtils.showToast('Quick recall failed: ' + error.message, 'error');
    }
  }

  /**
   * Switch to a specific project
   */
  async switchToProject(projectId) {
    try {
      const result = await apiService.project_context({
        projectId: projectId,
        action: 'switch'
      });
      
      if (result.success) {
        this.activeProject = projectId;
        this.updateProjectDisplay();
        this.updateMemoryDisplay();
        
        DomUtils.showToast(`Switched to project: ${projectId}`, 'success');
        consoleService.info('Switched to project: ' + projectId);
      }
      
    } catch (error) {
      consoleService.error('Failed to switch project', error);
      DomUtils.showToast('Failed to switch project: ' + error.message, 'error');
    }
  }

  /**
   * Load current memory state
   */
  async loadMemoryState() {
    try {
      // Load session info to get current state
      const sessionResult = await apiService.inspect({ what: 'session' });
      
      if (sessionResult.success && sessionResult.zptState) {
        const zptState = sessionResult.zptState;
        
        // Update current domain if available in state
        if (zptState.pan?.domains?.length > 0) {
          const projectDomains = zptState.pan.domains.filter(d => d.startsWith('project:'));
          if (projectDomains.length > 0) {
            this.activeProject = projectDomains[0].replace('project:', '');
          }
        }
      }
      
      // Update UI with loaded state
      this.updateUI();
      
    } catch (error) {
      consoleService.warning('Could not load memory state', error);
    }
  }

  /**
   * Apply memory filter
   */
  async applyMemoryFilter() {
    // This would trigger a filtered recall if needed
    this.updateMemoryDisplay();
  }

  /**
   * Display recall results in the UI
   */
  displayRecallResults(memories, query, isQuick = false) {
    const resultsContainer = DomUtils.$('#recall-results');
    if (!resultsContainer) return;
    
    if (!memories || memories.length === 0) {
      resultsContainer.innerHTML = `
        <div class="recall-no-results">
          <div class="no-results-icon">🔍</div>
          <p class="no-results-text">No memories found for "${query}"</p>
          <p class="no-results-hint">Try adjusting your search terms or relevance threshold</p>
        </div>
      `;
      return;
    }
    
    const memoriesHtml = memories.map((memory, index) => {
      const relevancePercent = Math.round((memory.relevance || 0) * 100);
      const timeAgo = this.formatTimeAgo(memory.timestamp);
      
      return `
        <div class="memory-result" data-memory-id="${memory.id}">
          <div class="memory-header">
            <div class="memory-meta">
              <span class="memory-domain">${memory.domain || 'unknown'}</span>
              <span class="memory-relevance">${relevancePercent}% relevant</span>
              <span class="memory-time">${timeAgo}</span>
            </div>
          </div>
          <div class="memory-content">
            ${this.highlightQuery(memory.content, query)}
          </div>
          ${memory.metadata ? `
            <div class="memory-metadata">
              ${memory.metadata.isInstruction ? '<span class="memory-tag instruction">instruction</span>' : ''}
              ${memory.metadata.recencyBias ? '<span class="memory-tag recent">recent</span>' : ''}
            </div>
          ` : ''}
        </div>
      `;
    }).join('');
    
    resultsContainer.innerHTML = `
      <div class="recall-results-header">
        <h4 class="results-title">
          ${isQuick ? '⚡ Quick Recall' : '🔍 Recall Results'} 
          <span class="results-count">(${memories.length} memories)</span>
        </h4>
        <div class="results-query">Query: "${query}"</div>
      </div>
      <div class="recall-results-list">
        ${memoriesHtml}
      </div>
    `;
  }

  /**
   * Update memory display
   */
  updateMemoryDisplay() {
    this.updateMemoryStats();
    this.updateDomainDisplay();
    this.updateProjectDisplay();
  }

  /**
   * Update memory statistics
   */
  updateMemoryStats() {
    const memoryCountElement = DomUtils.$('#memory-count');
    if (memoryCountElement) {
      memoryCountElement.textContent = this.memories.size;
    }
    
    const domainCountElement = DomUtils.$('#domain-count');
    if (domainCountElement) {
      const domains = new Set();
      this.memories.forEach(memory => domains.add(memory.domain));
      domainCountElement.textContent = domains.size;
    }
  }

  /**
   * Update domain display
   */
  updateDomainDisplay() {
    const domainElement = DomUtils.$('#current-memory-domain');
    if (domainElement) {
      domainElement.textContent = this.currentDomain;
    }
  }

  /**
   * Update project display
   */
  updateProjectDisplay() {
    const projectElement = DomUtils.$('#current-project');
    if (projectElement) {
      projectElement.textContent = this.activeProject || 'None';
    }
  }

  /**
   * Update UI components
   */
  updateUI() {
    this.updateMemoryDisplay();
    
    // Update form defaults
    const domainSelect = DomUtils.$('#memory-domain-select');
    if (domainSelect) {
      domainSelect.value = this.currentDomain;
    }
  }

  /**
   * Highlight search query in text
   */
  highlightQuery(text, query) {
    if (!query || !text) return text;
    
    const regex = new RegExp(`(${query})`, 'gi');
    return text.replace(regex, '<mark class="query-highlight">$1</mark>');
  }

  /**
   * Format timestamp as "time ago"
   */
  formatTimeAgo(timestamp) {
    if (!timestamp) return 'Unknown';
    
    const date = new Date(timestamp);
    const now = new Date();
    const diffMs = now - date;
    
    if (diffMs < 60000) return 'Just now';
    if (diffMs < 3600000) return `${Math.floor(diffMs / 60000)}m ago`;
    if (diffMs < 86400000) return `${Math.floor(diffMs / 3600000)}h ago`;
    return `${Math.floor(diffMs / 86400000)}d ago`;
  }
}