'use strict';

const { generateMemoryIndex, extractKeyEntities, extractKeyTopics } = require('../extraction/indexer');

const MAX_FIELD = 20;

function createSourceIndexEntry(memory) {
  if (!memory || !memory.id || !memory.content) return null;
  const content = String(memory.content || '');
  if (content.trim().length < 10) return null;

  const metadata = parseMetadata(memory.metadata);
  const base = generateMemoryIndex(memory) || {
    memory_id: memory.id,
    summary: content.slice(0, 200).trim(),
    key_entities: null,
    key_topics: null,
    domain: memory.domain || null,
    topic: memory.topic || null,
    timestamp: memory.timestamp,
  };

  const filesTouched = uniqueStrings([
    ...arrayOfStrings(metadata.filesEdited),
    ...arrayOfStrings(metadata.changed_files),
    ...extractFilesTouched(content),
  ], MAX_FIELD);
  const commandsRun = uniqueStrings([
    ...arrayOfStrings(metadata.commands),
    ...extractCommandsRun(content),
  ], MAX_FIELD);
  const decisions = uniqueStrings(extractDecisions(content), MAX_FIELD);
  const failuresBlockers = uniqueStrings(extractFailuresBlockers(content), MAX_FIELD);
  const exactQuotes = extractExactQuotes(content);
  validateExactQuotes(content, exactQuotes);

  const genericTopics = parseJsonArray(base.key_topics);
  const sourceId = metadata.sourceId || inferSourceId(memory.source_id);
  const sourceFacts = {
    topics: uniqueStrings([
      ...genericTopics,
      ...extractKeyTopics(content),
      ...filesTouched,
      ...commandsRun.map(commandTopic),
      ...decisions.map(shortTopic),
      ...failuresBlockers.map(shortTopic),
      sourceId,
    ], 60),
    filesTouched,
    commandsRun,
    decisions,
    failuresBlockers,
    exactQuotes,
    sourceId,
    cwd: metadata.cwd || memory.source_channel || '',
    branch: metadata.gitBranch || metadata.branch || '',
    role: metadata.role || '',
    memoryType: memory.memory_type || '',
  };

  return {
    memory_id: memory.id,
    summary: buildSummary(base.summary, sourceFacts),
    key_entities: JSON.stringify(uniqueStrings([
      ...parseJsonArray(base.key_entities),
      ...extractKeyEntities(content),
    ], 30)),
    key_topics: JSON.stringify(sourceFacts),
    domain: memory.domain || domainForMemory(memory),
    topic: memory.topic || topicForMemory(memory, sourceFacts),
    timestamp: memory.timestamp,
  };
}

function indexMemory(memory, { brain = require('../brain') } = {}) {
  const entry = createSourceIndexEntry(memory);
  if (!entry) return null;
  return brain.insertMemoryIndex(entry);
}

function rebuildSourceIndex({ brain = require('../brain'), sourceId = '', dryRun = false, limit = 10000 } = {}) {
  const db = brain.getDb();
  const params = [];
  const conditions = ['archived_at IS NULL', 'content IS NOT NULL', 'length(content) > 10'];
  if (sourceId) {
    conditions.push('(source_id = ? OR source_id LIKE ? OR metadata LIKE ?)');
    params.push(sourceId, `${sourceId}:%`, `%"sourceId":"${escapeLikeJson(sourceId)}"%`);
  }
  params.push(Math.min(Math.max(Number(limit) || 10000, 1), 50000));
  const memories = db.prepare(`
    SELECT * FROM memories
    WHERE ${conditions.join(' AND ')}
    ORDER BY timestamp ASC
    LIMIT ?
  `).all(...params);

  if (dryRun) {
    return { dry_run: true, source_id: sourceId || null, scanned: memories.length, indexed: 0, skipped: 0 };
  }

  let indexed = 0;
  let skipped = 0;
  const run = db.transaction(() => {
    for (const memory of memories) {
      const entry = createSourceIndexEntry(memory);
      if (!entry) {
        skipped++;
        continue;
      }
      brain.insertMemoryIndex(entry);
      indexed++;
    }
  });
  run();
  return { dry_run: false, source_id: sourceId || null, scanned: memories.length, indexed, skipped };
}

function extractFilesTouched(content) {
  const files = [];
  const re = /(?:^|[\s`'"(])((?:\.?[A-Za-z0-9_.@-]+\/)+[A-Za-z0-9_.@-]+\.[A-Za-z0-9]+|[A-Za-z0-9_.@-]+\.(?:js|ts|tsx|jsx|json|md|py|go|rs|java|rb|css|html|sql|sh|yml|yaml))(?=[\s`'",):;.\]]|$)/g;
  let match;
  while ((match = re.exec(content))) {
    const value = match[1].replace(/^[./]*\/+/, '').replace(/[),.;:]+$/, '');
    if (value && !value.includes('node_modules/')) files.push(value);
  }
  return files;
}

function extractCommandsRun(content) {
  const commands = [];
  const lineRe = /(?:^|\n)\s*(?:\$|>|Command:|Run:|ran)\s+([^\n]{2,220})/gi;
  let match;
  while ((match = lineRe.exec(content))) commands.push(cleanCommand(match[1]));
  const inlineRe = /\b((?:node|npm|pnpm|yarn|git|npx|python|pytest|cargo|go test|ruby|bash|make)\s+[^\n.;]{2,180})/gi;
  while ((match = inlineRe.exec(content))) commands.push(cleanCommand(match[1]));
  return commands.filter(Boolean);
}

function extractDecisions(content) {
  return extractLabeledLines(content, [
    /\bdecision\s*:\s*(.+)$/i,
    /\bdecided\s+(?:to|that)\s+(.+)$/i,
    /\bwe will\s+(.+)$/i,
    /\buse\s+(.+?)\s+instead of\s+(.+)$/i,
  ]);
}

function extractFailuresBlockers(content) {
  return extractLabeledLines(content, [
    /\bblocker\s*:\s*(.+)$/i,
    /\bblocked by\s+(.+)$/i,
    /\bfailed because\s+(.+)$/i,
    /\berror\s*:\s*(.+)$/i,
  ]);
}

function extractLabeledLines(content, patterns) {
  const results = [];
  for (const rawLine of String(content || '').split('\n')) {
    const line = rawLine.trim();
    if (!line) continue;
    for (const pattern of patterns) {
      const match = line.match(pattern);
      if (match?.[1]) {
        results.push(match[1].trim());
        break;
      }
    }
  }
  return results;
}

function extractExactQuotes(content) {
  const text = String(content || '');
  const lines = text.split('\n').map((line) => line.trim()).filter((line) => line.length >= 18);
  const scored = lines
    .map((line) => ({ line, score: quoteScore(line) }))
    .sort((a, b) => b.score - a.score)
    .map(({ line }) => line.slice(0, 180));
  return uniqueStrings(scored, 5);
}

function validateExactQuotes(content, quotes) {
  for (const quote of quotes || []) {
    if (!String(content || '').includes(quote)) {
      throw new Error(`source index quote is not verbatim: ${quote.slice(0, 40)}`);
    }
  }
}

function quoteScore(line) {
  let score = Math.min(line.length, 160) / 160;
  if (/\b(decision|decided|blocker|failed|error|command|test|file|fix)\b/i.test(line)) score += 1;
  if (/[`$]/.test(line)) score += 0.4;
  if (/\.[A-Za-z0-9]+/.test(line)) score += 0.3;
  return score;
}

function buildSummary(baseSummary, facts) {
  const parts = [baseSummary || 'Coding session memory'];
  if (facts.filesTouched.length) parts.push(`Files: ${facts.filesTouched.slice(0, 4).join(', ')}`);
  if (facts.decisions.length) parts.push(`Decisions: ${facts.decisions.slice(0, 2).join('; ')}`);
  if (facts.failuresBlockers.length) parts.push(`Blockers: ${facts.failuresBlockers.slice(0, 2).join('; ')}`);
  return parts.join(' | ').slice(0, 700);
}

function domainForMemory(memory) {
  const source = memory.source || '';
  const type = memory.memory_type || '';
  return source.endsWith('-jsonl') || type.startsWith('coding_session_') ? 'technical' : null;
}

function topicForMemory(memory, facts) {
  if (facts.failuresBlockers.length) return 'bugs';
  if (facts.decisions.length) return 'decision';
  if ((memory.memory_type || '').includes('tool')) return 'tools';
  return facts.filesTouched.length ? 'coding' : null;
}

function parseMetadata(value) {
  try {
    const parsed = JSON.parse(value || '{}');
    return parsed && typeof parsed === 'object' ? parsed : {};
  } catch {
    return {};
  }
}

function parseJsonArray(value) {
  try {
    const parsed = JSON.parse(value || '[]');
    return Array.isArray(parsed) ? parsed.filter((item) => typeof item === 'string') : [];
  } catch {
    return [];
  }
}

function arrayOfStrings(value) {
  return Array.isArray(value)
    ? value.filter((item) => typeof item === 'string' && item.trim()).map((item) => item.trim())
    : [];
}

function uniqueStrings(values, limit = MAX_FIELD) {
  const seen = new Set();
  const out = [];
  for (const value of values || []) {
    const item = String(value || '').trim();
    if (!item || seen.has(item)) continue;
    seen.add(item);
    out.push(item);
    if (out.length >= limit) break;
  }
  return out;
}

function inferSourceId(sourceId = '') {
  const parts = String(sourceId || '').split(':');
  if (parts.length <= 2) return sourceId || '';
  return parts.slice(0, 2).join(':');
}

function cleanCommand(value) {
  return String(value || '').trim().replace(/^`|`$/g, '').replace(/\s+/g, ' ').slice(0, 220);
}

function commandTopic(command) {
  return String(command || '').split(/\s+/).slice(0, 3).join(' ');
}

function shortTopic(value) {
  return String(value || '').slice(0, 120);
}

function escapeLikeJson(value) {
  return String(value || '').replace(/["\\]/g, '');
}

module.exports = {
  createSourceIndexEntry,
  extractCommandsRun,
  extractDecisions,
  extractExactQuotes,
  extractFailuresBlockers,
  extractFilesTouched,
  indexMemory,
  rebuildSourceIndex,
  validateExactQuotes,
};
