#!/usr/bin/env node
'use strict';
/**
 * File Ingest — read files from a folder and store as brain memories.
 *
 * Each file becomes one memory with:
 *   source: 'file'
 *   source_id: absolute file path (for dedup)
 *   source_channel: folder name
 *   content: file contents (truncated if very large)
 *   subject: filename
 *   memory_type: 'document'
 */
const fs = require('fs');
const path = require('path');
const brain = require(path.resolve(__dirname, '..', '..', '..', 'brain'));

const _standaloneMode = require.main === module;
// Only initialize the DB at module load when running as CLI. The daemon
// (skill-planner) shares the brain singleton and has already called initDb().
if (_standaloneMode) brain.initDb();

const TEXT_EXTENSIONS = new Set([
  '.md', '.txt', '.json', '.csv', '.js', '.ts', '.jsx', '.tsx',
  '.py', '.html', '.xml', '.yaml', '.yml', '.sh', '.bash', '.zsh',
  '.css', '.scss', '.sql', '.env', '.cfg', '.ini', '.log', '.rst',
  '.org', '.toml', '.rb', '.go', '.java', '.c', '.cpp', '.h',
  '.swift', '.kt', '.rs', '.r', '.m', '.php', '.pl', '.lua',
]);

function expandHome(p) {
  if (p.startsWith('~/')) return path.join(process.env.HOME, p.slice(2));
  return p;
}

function walkDir(dir, recursive, pattern) {
  const results = [];
  let entries;
  try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return results; }

  for (const entry of entries) {
    if (entry.name.startsWith('.')) continue; // skip hidden
    const fullPath = path.join(dir, entry.name);
    if (entry.isDirectory() && recursive) {
      results.push(...walkDir(fullPath, recursive, pattern));
    } else if (entry.isFile()) {
      const ext = path.extname(entry.name).toLowerCase();
      if (!TEXT_EXTENSIONS.has(ext)) continue;
      if (pattern && pattern !== '*') {
        const glob = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
        if (!new RegExp('^' + glob + '$', 'i').test(entry.name)) continue;
      }
      results.push(fullPath);
    }
  }
  return results;
}

function main() {
  // Read config from env or args
  const configStr = process.env.SKILL_CONFIG || process.argv[2] || '{}';
  let config;
  try { config = JSON.parse(configStr); } catch { config = {}; }

  const folder = expandHome(config.folder || '');
  if (!folder) {
    throw new Error('folder is required. Pass via config: {"folder": "~/path/to/files"}');
  }

  const resolvedFolder = path.resolve(folder);
  if (!fs.existsSync(resolvedFolder)) {
    throw new Error(`folder not found: ${resolvedFolder}`);
  }

  const recursive = config.recursive !== false;
  const pattern = config.pattern || '*';
  const maxSizeKb = config.max_file_size_kb || 500;
  const maxSizeBytes = maxSizeKb * 1024;

  console.log(`# File Ingest\n`);
  console.log(`Folder: ${resolvedFolder}`);
  console.log(`Pattern: ${pattern} | Recursive: ${recursive} | Max size: ${maxSizeKb}KB\n`);

  const files = walkDir(resolvedFolder, recursive, pattern);
  console.log(`Found ${files.length} text file(s)\n`);

  let ingested = 0, skipped = 0, updated = 0;
  const folderName = path.basename(resolvedFolder);

  for (const filePath of files) {
    const stat = fs.statSync(filePath);
    if (stat.size > maxSizeBytes) {
      skipped++;
      continue;
    }

    let content;
    try { content = fs.readFileSync(filePath, 'utf8'); } catch { skipped++; continue; }

    // Truncate very long files to keep brain manageable
    if (content.length > 50000) {
      content = content.slice(0, 50000) + '\n\n[... truncated at 50KB]';
    }

    const relPath = path.relative(resolvedFolder, filePath);
    const fileName = path.basename(filePath);
    const sourceId = 'file:' + filePath;

    // Check if already exists (dedup by source + source_id)
    const existing = brain.getDb().prepare(
      'SELECT id FROM memories WHERE source = ? AND source_id = ?'
    ).get('file', sourceId);

    if (existing) {
      // Update content if changed
      brain.getDb().prepare(
        'UPDATE memories SET content = ?, timestamp = ?, subject = ? WHERE id = ?'
      ).run(content, stat.mtime.toISOString(), relPath, existing.id);
      updated++;
    } else {
      brain.insertMemory({
        source: 'file',
        source_id: sourceId,
        source_channel: folderName,
        memory_type: 'document',
        content,
        subject: relPath,
        timestamp: stat.mtime.toISOString(),
      });
      ingested++;
    }
  }

  console.log(`Results:`);
  console.log(`  New:     ${ingested}`);
  console.log(`  Updated: ${updated}`);
  console.log(`  Skipped: ${skipped} (too large or unreadable)`);
  console.log(`  Total:   ${files.length}`);

  // closeDb is a no-op while the daemon owns the connection; the CLI guard
  // below also avoids re-closing in module mode.
  if (_standaloneMode) brain.closeDb();
  return { ingested, updated, skipped, total: files.length };
}

// Export for skill-planner; also run directly when invoked as CLI script
module.exports = { run: main };

if (_standaloneMode) {
  try { main(); }
  catch (err) { console.error('[file-ingest]', err.message); process.exit(1); }
}
