/**
 * Memory Update Queue Tool
 * Queue mechanism for batching CLAUDE.md updates
 *
 * Configuration:
 * - Threshold: 5 paths trigger update
 * - Timeout: 5 minutes auto-trigger
 * - Storage: ~/.claude/.memory-queue.json
 * - Deduplication: Same path only kept once
 */

import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { join, dirname, resolve } from 'path';
import { homedir } from 'os';

// Default configuration
const DEFAULT_THRESHOLD = 5;
const DEFAULT_TIMEOUT_SECONDS = 300; // 5 minutes
const QUEUE_FILE_PATH = join(homedir(), '.claude', '.memory-queue.json');

/**
 * Get queue configuration (from file or defaults)
 * @returns {{ threshold: number, timeoutMs: number }}
 */
function getQueueConfig() {
  try {
    if (existsSync(QUEUE_FILE_PATH)) {
      const content = readFileSync(QUEUE_FILE_PATH, 'utf8');
      const data = JSON.parse(content);
      return {
        threshold: data.config?.threshold || DEFAULT_THRESHOLD,
        timeoutMs: (data.config?.timeout || DEFAULT_TIMEOUT_SECONDS) * 1000
      };
    }
  } catch (e) {
    // Use defaults
  }
  return {
    threshold: DEFAULT_THRESHOLD,
    timeoutMs: DEFAULT_TIMEOUT_SECONDS * 1000
  };
}

// In-memory timeout reference (for cross-call persistence, we track via file timestamp)
let scheduledTimeoutId = null;

/**
 * Ensure parent directory exists
 */
function ensureDir(filePath) {
  const dir = dirname(filePath);
  if (!existsSync(dir)) {
    mkdirSync(dir, { recursive: true });
  }
}

/**
 * Load queue from file
 * @returns {{ items: Array<{path: string, tool: string, strategy: string, addedAt: string}>, createdAt: string | null, config?: { threshold: number, timeout: number } }}
 */
function loadQueue() {
  try {
    if (existsSync(QUEUE_FILE_PATH)) {
      const content = readFileSync(QUEUE_FILE_PATH, 'utf8');
      const data = JSON.parse(content);
      return {
        items: Array.isArray(data.items) ? data.items : [],
        createdAt: data.createdAt || null,
        config: data.config || null
      };
    }
  } catch (e) {
    console.error('[MemoryQueue] Failed to load queue:', e.message);
  }
  return { items: [], createdAt: null, config: null };
}

/**
 * Save queue to file
 * @param {{ items: Array<{path: string, tool: string, strategy: string, addedAt: string}>, createdAt: string | null }} data
 */
function saveQueue(data) {
  try {
    ensureDir(QUEUE_FILE_PATH);
    writeFileSync(QUEUE_FILE_PATH, JSON.stringify(data, null, 2), 'utf8');
  } catch (e) {
    console.error('[MemoryQueue] Failed to save queue:', e.message);
    throw e;
  }
}

/**
 * Normalize path for comparison (handle Windows/Unix differences)
 * @param {string} p
 * @returns {string}
 */
function normalizePath(p) {
  return resolve(p).replace(/\\/g, '/').toLowerCase();
}

/**
 * Add path to queue with deduplication
 * @param {string} path - Module path to update
 * @param {{ tool?: string, strategy?: string }} options
 * @returns {{ queued: boolean, queueSize: number, willFlush: boolean, message: string }}
 */
function addToQueue(path, options = {}) {
  const { tool = 'gemini', strategy = 'single-layer' } = options;
  const queue = loadQueue();
  const config = getQueueConfig();
  const normalizedPath = normalizePath(path);
  const now = new Date().toISOString();

  // Check for duplicates
  const existingIndex = queue.items.findIndex(
    item => normalizePath(item.path) === normalizedPath
  );

  if (existingIndex !== -1) {
    // Update existing entry timestamp but keep it deduplicated
    queue.items[existingIndex].addedAt = now;
    queue.items[existingIndex].tool = tool;
    queue.items[existingIndex].strategy = strategy;
    saveQueue(queue);

    return {
      queued: false,
      queueSize: queue.items.length,
      willFlush: queue.items.length >= config.threshold,
      message: `Path already in queue (updated): ${path}`
    };
  }

  // Add new item
  queue.items.push({
    path,
    tool,
    strategy,
    addedAt: now
  });

  // Set createdAt if this is the first item
  if (!queue.createdAt) {
    queue.createdAt = now;
  }

  saveQueue(queue);

  const willFlush = queue.items.length >= config.threshold;

  // Schedule timeout if not already scheduled
  scheduleTimeout();

  return {
    queued: true,
    queueSize: queue.items.length,
    willFlush,
    message: willFlush
      ? `Queue threshold reached (${queue.items.length}/${config.threshold}), will flush`
      : `Added to queue (${queue.items.length}/${config.threshold})`
  };
}

/**
 * Get current queue status
 * @returns {{ queueSize: number, threshold: number, items: Array, timeoutMs: number | null, createdAt: string | null }}
 */
function getQueueStatus() {
  const queue = loadQueue();
  const config = getQueueConfig();
  let timeUntilTimeout = null;

  if (queue.createdAt && queue.items.length > 0) {
    const createdTime = new Date(queue.createdAt).getTime();
    const elapsed = Date.now() - createdTime;
    timeUntilTimeout = Math.max(0, config.timeoutMs - elapsed);
  }

  return {
    queueSize: queue.items.length,
    threshold: config.threshold,
    items: queue.items,
    timeoutMs: config.timeoutMs,
    timeoutSeconds: config.timeoutMs / 1000,
    timeUntilTimeout,
    createdAt: queue.createdAt
  };
}

/**
 * Configure queue settings
 * @param {{ threshold?: number, timeout?: number }} settings
 * @returns {{ success: boolean, config: { threshold: number, timeout: number } }}
 */
function configureQueue(settings) {
  const queue = loadQueue();
  const currentConfig = getQueueConfig();

  const newConfig = {
    threshold: settings.threshold || currentConfig.threshold,
    timeout: settings.timeout || (currentConfig.timeoutMs / 1000)
  };

  // Validate
  if (newConfig.threshold < 1 || newConfig.threshold > 20) {
    throw new Error('Threshold must be between 1 and 20');
  }
  if (newConfig.timeout < 60 || newConfig.timeout > 1800) {
    throw new Error('Timeout must be between 60 and 1800 seconds');
  }

  queue.config = newConfig;
  saveQueue(queue);

  return {
    success: true,
    config: newConfig,
    message: `Queue configured: threshold=${newConfig.threshold}, timeout=${newConfig.timeout}s`
  };
}

/**
 * Flush queue - execute batch update
 * @returns {Promise<{ success: boolean, processed: number, results: Array, errors: Array }>}
 */
async function flushQueue() {
  const queue = loadQueue();

  if (queue.items.length === 0) {
    return {
      success: true,
      processed: 0,
      results: [],
      errors: [],
      message: 'Queue is empty'
    };
  }

  // Clear timeout
  clearScheduledTimeout();

  // Import update_module_claude dynamically to avoid circular deps
  const { updateModuleClaudeTool } = await import('./update-module-claude.js');

  const results = [];
  const errors = [];

  // Group by tool and strategy for efficiency
  const groups = new Map();
  for (const item of queue.items) {
    const key = `${item.tool}:${item.strategy}`;
    if (!groups.has(key)) {
      groups.set(key, []);
    }
    groups.get(key).push(item);
  }

  // Process each group
  for (const [key, items] of groups) {
    const [tool, strategy] = key.split(':');
    console.log(`[MemoryQueue] Processing ${items.length} items with ${tool}/${strategy}`);

    for (const item of items) {
      try {
        const result = await updateModuleClaudeTool.execute({
          path: item.path,
          tool: item.tool,
          strategy: item.strategy
        });

        results.push({
          path: item.path,
          success: result.success !== false,
          result
        });
      } catch (e) {
        console.error(`[MemoryQueue] Failed to update ${item.path}:`, e.message);
        errors.push({
          path: item.path,
          error: e.message
        });
      }
    }
  }

  // Clear queue after processing
  saveQueue({ items: [], createdAt: null });

  return {
    success: errors.length === 0,
    processed: queue.items.length,
    results,
    errors,
    message: `Processed ${results.length} items, ${errors.length} errors`
  };
}

/**
 * Schedule timeout for auto-flush
 */
function scheduleTimeout() {
  // We use file-based timeout tracking for persistence across process restarts
  // The actual timeout check happens on next add/status call
  const queue = loadQueue();
  const config = getQueueConfig();

  if (!queue.createdAt || queue.items.length === 0) {
    return;
  }

  const createdTime = new Date(queue.createdAt).getTime();
  const elapsed = Date.now() - createdTime;

  if (elapsed >= config.timeoutMs) {
    // Timeout already exceeded, should flush
    console.log('[MemoryQueue] Timeout exceeded, auto-flushing');
    // Don't await here to avoid blocking
    flushQueue().catch(e => {
      console.error('[MemoryQueue] Auto-flush failed:', e.message);
    });
  } else if (!scheduledTimeoutId) {
    // Schedule in-memory timeout for current process
    const remaining = config.timeoutMs - elapsed;
    scheduledTimeoutId = setTimeout(() => {
      scheduledTimeoutId = null;
      const currentQueue = loadQueue();
      if (currentQueue.items.length > 0) {
        console.log('[MemoryQueue] Timeout reached, auto-flushing');
        flushQueue().catch(e => {
          console.error('[MemoryQueue] Auto-flush failed:', e.message);
        });
      }
    }, remaining);

    // Prevent timeout from keeping process alive
    if (scheduledTimeoutId.unref) {
      scheduledTimeoutId.unref();
    }
  }
}

/**
 * Clear scheduled timeout
 */
function clearScheduledTimeout() {
  if (scheduledTimeoutId) {
    clearTimeout(scheduledTimeoutId);
    scheduledTimeoutId = null;
  }
}

/**
 * Check if timeout has expired and auto-flush if needed
 * @returns {Promise<{ expired: boolean, flushed: boolean, result?: object }>}
 */
async function checkTimeout() {
  const queue = loadQueue();
  const config = getQueueConfig();

  if (!queue.createdAt || queue.items.length === 0) {
    return { expired: false, flushed: false };
  }

  const createdTime = new Date(queue.createdAt).getTime();
  const elapsed = Date.now() - createdTime;

  if (elapsed >= config.timeoutMs) {
    console.log('[MemoryQueue] Timeout expired, triggering flush');
    const result = await flushQueue();
    return { expired: true, flushed: true, result };
  }

  return { expired: false, flushed: false };
}

/**
 * Main execute function for tool interface
 * @param {Record<string, unknown>} params
 * @returns {Promise<unknown>}
 */
async function execute(params) {
  const { action, path, tool = 'gemini', strategy = 'single-layer', threshold, timeout } = params;

  switch (action) {
    case 'add':
      if (!path) {
        throw new Error('Parameter "path" is required for add action');
      }
      // Check timeout first
      const timeoutCheck = await checkTimeout();
      if (timeoutCheck.flushed) {
        // Queue was flushed due to timeout, add to fresh queue
        const result = addToQueue(path, { tool, strategy });
        return `[MemoryQueue] Timeout flush (${timeoutCheck.result.processed} items) → ${result.message}`;
      }

      const addResult = addToQueue(path, { tool, strategy });

      // Auto-flush if threshold reached
      if (addResult.willFlush) {
        const flushResult = await flushQueue();
        // Return string for hook-friendly output
        return `[MemoryQueue] ${addResult.message} → Flushed ${flushResult.processed} items`;
      }

      // Return string for hook-friendly output
      return `[MemoryQueue] ${addResult.message}`;

    case 'status':
      // Check timeout first
      await checkTimeout();
      return getQueueStatus();

    case 'flush':
      return await flushQueue();

    case 'configure':
      return configureQueue({ threshold, timeout });

    default:
      throw new Error(`Unknown action: ${action}. Valid actions: add, status, flush, configure`);
  }
}

/**
 * Tool Definition
 */
export const memoryQueueTool = {
  name: 'memory_queue',
  description: `Memory update queue management. Batches CLAUDE.md updates for efficiency.

Actions:
- add: Add path to queue (auto-flushes at configured threshold/timeout)
- status: Get queue status and configuration
- flush: Immediately execute all queued updates
- configure: Set threshold and timeout settings`,
  parameters: {
    type: 'object',
    properties: {
      action: {
        type: 'string',
        enum: ['add', 'status', 'flush', 'configure'],
        description: 'Queue action to perform'
      },
      path: {
        type: 'string',
        description: 'Module directory path (required for add action)'
      },
      threshold: {
        type: 'number',
        description: 'Number of paths to trigger flush (1-20, for configure action)',
        minimum: 1,
        maximum: 20
      },
      timeout: {
        type: 'number',
        description: 'Timeout in seconds to trigger flush (60-1800, for configure action)',
        minimum: 60,
        maximum: 1800
      },
      tool: {
        type: 'string',
        enum: ['gemini', 'qwen', 'codex'],
        description: 'CLI tool to use (default: gemini)',
        default: 'gemini'
      },
      strategy: {
        type: 'string',
        enum: ['single-layer', 'multi-layer'],
        description: 'Update strategy (default: single-layer)',
        default: 'single-layer'
      }
    },
    required: ['action']
  },
  execute
};

// Export individual functions for direct use
export {
  loadQueue,
  saveQueue,
  addToQueue,
  getQueueStatus,
  flushQueue,
  configureQueue,
  scheduleTimeout,
  clearScheduledTimeout,
  checkTimeout,
  DEFAULT_THRESHOLD,
  DEFAULT_TIMEOUT_SECONDS,
  QUEUE_FILE_PATH
};
