#!/usr/bin/env node
'use strict';
/**
 * Morning Briefing — pure script, no Claude API calls.
 *
 * 1. Calendar events (today) via brain
 * 2. Recent Slack activity grouped by channel
 * 3. Direct mentions & urgent items
 * 4. Pending tasks
 * 5. Pending questions
 * 6. Training progress (shadow training stats)
 * 7. Quick stats footer
 *
 * Outputs clean markdown to stdout. The task runner stores it as the task result.
 */
const path = require('path');
const brain = require(path.resolve(__dirname, '..', '..', '..', 'brain'));
const { getCalendarEvents } = require(path.resolve(__dirname, '..', '..', '..', 'tools', 'local-tools'));

brain.initDb();
const db = brain.getDb();

async function main() {
  const now = new Date();
  const todayStr = now.toISOString().slice(0, 10);
  const dayLabel = now.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
  const sections = [];

  // ── 1. Today's Schedule ──
  let calSection = '## 📅 Today\'s Schedule\n\n';
  try {
    const { events } = await getCalendarEvents({ days_ahead: 0 });
    const todayEvents = events.filter(e => {
      const eDate = new Date(e.start);
      if (isNaN(eDate)) return false;
      return eDate.toISOString().slice(0, 10) === todayStr;
    });
    const seen = new Set();
    const deduped = todayEvents.filter(e => {
      const key = `${e.title}|${formatTime(e.start)}`;
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    });
    if (deduped.length === 0) {
      calSection += 'No meetings today — clear calendar.\n';
    } else {
      calSection += '| Time | Meeting | With |\n|------|---------|------|\n';
      for (const evt of deduped) {
        const time = evt.allDay ? 'All day' : formatTime(evt.start);
        const ownerName = (process.env.WALLE_OWNER_NAME || '').toLowerCase();
        const ownerEmail = (process.env.WALLE_OWNER_EMAIL || '').toLowerCase().trim();
        const ownerPrefix = ownerEmail.includes('@') ? ownerEmail.split('@')[0] : '';
        const ownerParts = ownerName.split(/\s+/).filter(p => p.length > 1);
        const filteredAttendees = evt.attendees
          .filter(a => {
            const aLow = a.toLowerCase();
            // Skip owner by email prefix or name match
            if (ownerPrefix && aLow.includes(ownerPrefix)) return false;
            if (ownerParts.length > 0 && ownerParts.every(p => aLow.includes(p))) return false;
            return true;
          })
          .map(a => a.split('@')[0].split('.').filter(Boolean).map(s => s[0].toUpperCase() + s.slice(1)).join(' '));
        const attendees = filteredAttendees.slice(0, 4);
        const overflow = filteredAttendees.length > 4 ? ` +${filteredAttendees.length - 4}` : '';
        const attendeeStr = attendees.length > 0 ? attendees.join(', ') + overflow : '—';
        calSection += `| ${time} | ${evt.title} | ${attendeeStr} |\n`;
      }
    }
  } catch (err) {
    calSection += `_Calendar unavailable: ${err.message}_\n`;
  }
  sections.push(calSection);

  // ── 1b. Initiative Activity (last 24h) ──
  let initiativeSection = '';
  try {
    const yesterday = new Date(now.getTime() - 24 * 3600000).toISOString();
    const initiatives = db.prepare(`
      SELECT decision, reasoning, decision_data, created_at
      FROM initiative_log
      WHERE decision != 'noop' AND decision != 'cooldown' AND created_at > ?
      ORDER BY created_at DESC LIMIT 10
    `).all(yesterday);

    if (initiatives.length > 0) {
      initiativeSection = '## WALL-E Activity (last 24h)\n\n';
      for (const init of initiatives) {
        const time = formatTime(init.created_at);
        const action = init.decision === 'observed' ? 'Observed'
          : init.decision === 'notify' ? 'Notified'
          : init.decision === 'create_task' ? 'Created task'
          : init.decision === 'run_skill' ? 'Ran skill'
          : init.decision;
        initiativeSection += `- ${time} -- ${action}: ${init.reasoning || '(no reason)'}\n`;
      }
      initiativeSection += '\n';
    }
  } catch {}
  if (initiativeSection) sections.push(initiativeSection);

  // ── 1c. Weekly Reflection (Mondays only) ──
  if (now.getDay() === 1) { // Monday
    try {
      const lastWeekly = db.prepare(`
        SELECT summary FROM daily_summaries
        WHERE date > datetime('now', '-3 days') AND summary LIKE '%Weekly%'
        ORDER BY date DESC LIMIT 1
      `).get();
      if (lastWeekly?.summary) {
        sections.push(`## Last Week's Reflection\n\n${lastWeekly.summary.slice(0, 500)}\n`);
      }
    } catch {}
  }

  // ── 2. Overnight Slack Activity ──
  const hoursBack = 18;
  const cutoff = new Date(now.getTime() - hoursBack * 3600000).toISOString();

  const recentInbound = db.prepare(`
    SELECT content, source_channel, participants, timestamp
    FROM memories
    WHERE source = 'slack' AND direction = 'inbound'
      AND timestamp > ?
    ORDER BY timestamp DESC
    LIMIT 30
  `).all(cutoff);

  const recentOutbound = db.prepare(`
    SELECT content, source_channel, timestamp
    FROM memories
    WHERE source = 'slack' AND direction = 'outbound'
      AND timestamp > ?
    ORDER BY timestamp DESC
    LIMIT 10
  `).all(cutoff);

  let slackSection = '## 💬 Overnight Slack\n\n';

  if (recentInbound.length === 0 && recentOutbound.length === 0) {
    slackSection += 'No Slack activity in the last 18 hours.\n';
  } else {
    const byChannel = {};
    for (const msg of recentInbound) {
      const ch = msg.source_channel || 'DM';
      if (!byChannel[ch]) byChannel[ch] = [];
      byChannel[ch].push(msg);
    }

    const channels = Object.entries(byChannel)
      .sort((a, b) => b[1].length - a[1].length)
      .slice(0, 6);

    for (const [channel, msgs] of channels) {
      const chName = resolveChannelName(channel);
      const countLabel = msgs.length === 1 ? '1 message' : `${msgs.length} messages`;
      slackSection += `### ${chName}  \`${countLabel}\`\n\n`;

      for (const msg of msgs.slice(0, 3)) {
        const time = formatTime(msg.timestamp);
        const who = extractFirstName(msg.participants);
        const preview = condenseLine(msg.content, 140);
        if (who) {
          slackSection += `- **${who}** at ${time} — ${preview}\n`;
        } else {
          slackSection += `- ${time} — ${preview}\n`;
        }
      }
      if (msgs.length > 3) {
        slackSection += `- _...and ${msgs.length - 3} more_\n`;
      }
      slackSection += '\n';
    }

    if (recentOutbound.length > 0) {
      slackSection += `You sent ${recentOutbound.length} message${recentOutbound.length > 1 ? 's' : ''} in the last ${hoursBack}h.\n`;
    }
  }
  sections.push(slackSection);

  // ── 3. Mentions & Urgent ──
  let mentionSection = '';
  try {
    const hasFts = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'").get();
    if (hasFts) {
      const ownerFirst = (process.env.WALLE_OWNER_NAME || 'owner').split(' ')[0]
        .replace(/['"*()]/g, '');
      const matchExpr = `${ownerFirst} OR urgent OR blocked OR ASAP`;
      const mentions = db.prepare(`
        SELECT m.content, m.source_channel, m.participants, m.timestamp
        FROM memories_fts f
        JOIN memories m ON m.rowid = f.rowid
        WHERE memories_fts MATCH ?
          AND m.source = 'slack' AND m.timestamp > ?
        ORDER BY m.timestamp DESC
        LIMIT 5
      `).all(matchExpr, cutoff);

      if (mentions.length > 0) {
        mentionSection = '## 🔔 Mentions & Urgent\n\n';
        for (const m of mentions) {
          const time = formatTime(m.timestamp);
          const ch = resolveChannelName(m.source_channel);
          const who = extractFirstName(m.participants);
          const preview = condenseLine(m.content, 160);
          const source = who ? `**${who}** in ${ch}` : ch;
          mentionSection += `- ${source} at ${time}\n  ${preview}\n`;
        }
        mentionSection += '\n';
      }
    }
  } catch {}
  if (mentionSection) sections.push(mentionSection);

  // ── 4. Pending Tasks ──
  const tasks = brain.listTasks({ status: 'pending', limit: 10 });
  const failedTasks = brain.listTasks({ status: 'failed', limit: 5 });
  if (tasks.length > 0 || failedTasks.length > 0) {
    let taskSection = '## ✅ Tasks\n\n';
    if (tasks.length > 0) {
      taskSection += '| Task | Priority | Due |\n|------|----------|-----|\n';
      for (const t of tasks) {
        const due = t.due_at ? t.due_at.slice(0, 10) : '—';
        const pri = t.priority === 'urgent' ? '🔴 Urgent' : t.priority === 'high' ? '🟡 High' : '—';
        taskSection += `| ${t.title} | ${pri} | ${due} |\n`;
      }
    }
    if (failedTasks.length > 0) {
      taskSection += '\n**Failed tasks:**\n\n';
      for (const t of failedTasks) {
        taskSection += `- ❌ ${t.title} — _${condenseLine(t.error || 'unknown error', 80)}_\n`;
      }
    }
    taskSection += '\n';
    sections.push(taskSection);
  }

  // ── 5. Pending Questions ──
  const questions = brain.listQuestions({ status: 'pending', limit: 5 });
  if (questions.length > 0) {
    let qSection = '## ❓ Questions for You\n\n';
    for (const q of questions) {
      const typeLabel = q.question_type === 'contradiction' ? 'Contradiction'
        : q.question_type === 'preference' ? 'Preference' : q.question_type;
      qSection += `- **${typeLabel}:** ${q.question}\n`;
    }
    qSection += '\n';
    sections.push(qSection);
  }

  // ── 6. Training Progress ──
  const trainingSection = buildTrainingSection(brain);
  if (trainingSection) sections.push(trainingSection);

  // ── 7. Quick Stats ──
  const stats = brain.getBrainStats();
  const totalSlack = db.prepare("SELECT count(*) as c FROM memories WHERE source = 'slack'").get().c;
  let statsSection = '---\n\n';
  statsSection += `${recentInbound.length} inbound messages · `;
  statsSection += `${tasks.length} pending tasks · `;
  statsSection += `${questions.length} open questions · `;
  statsSection += `${stats.memory_count.toLocaleString()} total memories · `;
  statsSection += `${totalSlack.toLocaleString()} Slack messages\n`;
  sections.push(statsSection);

  // ── Output ──
  const output = `# Morning Briefing\n\n**${dayLabel}**\n\n${sections.join('\n')}`;
  console.log(output);

  brain.closeDb();
}

// ── Training Progress ──

function buildTrainingSection(brain) {
  try {
    const db = brain.getDb();

    // Shadow results evaluated in last 7 days
    const cutoff = new Date(Date.now() - 7 * 86400000).toISOString();
    const weekStats = db.prepare(`
      SELECT task_type, COUNT(*) AS count,
        AVG(shadow_score) AS avg_score
      FROM shadow_results
      WHERE evaluated_at IS NOT NULL AND created_at >= ?
      GROUP BY task_type ORDER BY count DESC
    `).all(cutoff);

    if (weekStats.length === 0) return '';

    const totalWeek = weekStats.reduce((s, r) => s + r.count, 0);

    // Replay counts (lifetime totals)
    const replaySt = brain.getHarvestState('ctm-replay');
    const replayMt = brain.getHarvestState('ctm-replay-mt');

    // Promotion candidates
    const promos = brain.getPromotionCandidates();

    // Model scores with trends
    const scores = brain.getModelTaskScores();

    let md = '## Training Progress\n\n';
    md += `**${totalWeek} evaluations** this week`;
    if (replaySt || replayMt) {
      const stCount = replaySt?.total_harvested || 0;
      const mtCount = replayMt?.total_harvested || 0;
      md += ` (${stCount} single-turn, ${mtCount} multi-turn replayed total)`;
    }
    md += '\n\n';

    // Task type breakdown
    md += '| Task Type | Evals | Avg Score | Trend |\n';
    md += '|-----------|-------|-----------|-------|\n';
    for (const t of weekStats) {
      const scorePct = t.avg_score != null ? (t.avg_score * 100).toFixed(1) + '%' : '--';
      const matchingScore = scores.find(s => s.task_type === t.task_type);
      const trend = matchingScore?.trend || 'stable';
      md += `| ${t.task_type} | ${t.count} | ${scorePct} | ${trend} |\n`;
    }
    md += '\n';

    // Promotion candidates
    if (promos.length > 0) {
      md += '**Promotion candidates:**\n\n';
      for (const p of promos) {
        const wr = p.total_evals > 0 ? (p.win_count / p.total_evals * 100).toFixed(0) : '0';
        md += `- ${p.model} for ${p.task_type}: ${(p.avg_score * 100).toFixed(1)}% score, ${wr}% win rate (${p.trend})\n`;
      }
      md += '\n';
    }

    return md;
  } catch (e) {
    return '';
  }
}

// ── Helpers ──

function formatTime(dateStr) {
  if (!dateStr) return '??:??';
  try {
    const d = new Date(dateStr);
    if (isNaN(d.getTime())) {
      const match = dateStr.match(/(\d{1,2}:\d{2}(?::\d{2})?\s*(?:AM|PM)?)/i);
      return match ? match[1] : dateStr.slice(0, 16);
    }
    return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
  } catch {
    return dateStr.slice(0, 16);
  }
}

// Build a channel name lookup from brain memories
const CHANNEL_NAME_CACHE = {};
function resolveChannelName(channelId) {
  if (!channelId) return '#unknown';
  // Already a readable name
  if (!channelId.match(/^[A-Z][A-Z0-9]{7,}$/)) return `#${channelId}`;
  if (CHANNEL_NAME_CACHE[channelId]) return CHANNEL_NAME_CACHE[channelId];

  // Try to find a human-readable name from memories metadata
  try {
    const row = db.prepare(`
      SELECT metadata FROM memories
      WHERE source = 'slack' AND source_channel = ?
        AND metadata LIKE '%channel_name%'
      LIMIT 1
    `).get(channelId);
    if (row && row.metadata) {
      const meta = JSON.parse(row.metadata);
      if (meta.channel_name) {
        CHANNEL_NAME_CACHE[channelId] = `#${meta.channel_name}`;
        return CHANNEL_NAME_CACHE[channelId];
      }
    }
  } catch {}

  // Try to extract from participants or content patterns
  try {
    const row = db.prepare(`
      SELECT participants FROM memories
      WHERE source = 'slack' AND source_channel = ?
        AND participants IS NOT NULL AND participants != ''
      LIMIT 1
    `).get(channelId);
    if (row && row.participants && !row.participants.match(/^[A-Z][A-Z0-9]{7,}$/)) {
      // For DMs, show participant name instead of channel ID
      const name = row.participants.split(',')[0].trim();
      if (name && name.length < 30) {
        CHANNEL_NAME_CACHE[channelId] = `DM with ${extractFirstName({ participants: row.participants })}`;
        return CHANNEL_NAME_CACHE[channelId];
      }
    }
  } catch {}

  CHANNEL_NAME_CACHE[channelId] = `#${channelId.slice(0, 4)}…`;
  return CHANNEL_NAME_CACHE[channelId];
}

function extractFirstName(msgOrParticipants) {
  const participants = typeof msgOrParticipants === 'string'
    ? msgOrParticipants
    : (msgOrParticipants && msgOrParticipants.participants) || '';
  if (!participants) return '';
  const name = participants.split(',')[0].trim().replace(/^\*+|\*+$/g, '');
  return name.split(' ')[0] || name;
}

function condenseLine(text, maxLen) {
  if (!text) return '';
  let oneLine = text.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
  oneLine = oneLine
    .replace(/<@[A-Z0-9]+\|([^>]+)>/g, '@$1')
    .replace(/<@[A-Z0-9]+>/g, '@someone')
    .replace(/<(https?:\/\/[^|>]+)\|([^>]+)>/g, '$2')
    .replace(/<(https?:\/\/[^>]+)>/g, '[link]')
    .replace(/:[a-z_+-]+:/g, '')
    .replace(/<!(?:here|channel|everyone)>/g, '@here')
    .replace(/\*([^*\n]+)\*/g, '$1')
    .replace(/\s+/g, ' ').trim();
  if (oneLine.length <= maxLen) return oneLine;
  return oneLine.slice(0, maxLen - 1) + '…';
}

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

if (require.main === module) {
  main().catch(err => {
    console.error('[morning-briefing] Error:', err.message);
    process.exit(1);
  });
}
