'use strict';
const brain = require('../brain');
const { spawn } = require('child_process');
const path = require('path');
const { findSkill } = require('../skills/skill-loader');

const WALL_E_DIR = path.join(__dirname, '..');

// ── Live log buffer — in-memory, keyed by task ID ──
const taskLogs = new Map(); // task_id → { lines: string[], startedAt, status }
const taskProcesses = new Map(); // task_id → child process (for stopping)

function stopTask(taskId) {
  const child = taskProcesses.get(taskId);
  if (child) {
    child.kill('SIGTERM');
    taskProcesses.delete(taskId);
    return true;
  }
  return false;
}

function getTaskLogs(taskId) {
  return taskLogs.get(taskId) || null;
}

function clearTaskLogs(taskId) {
  taskLogs.delete(taskId);
}

function appendLog(taskId, line) {
  let entry = taskLogs.get(taskId);
  if (!entry) {
    entry = { lines: [], startedAt: new Date().toISOString(), status: 'running' };
    taskLogs.set(taskId, entry);
  }
  entry.lines.push(line);
  // Cap at 500 lines to prevent memory bloat
  if (entry.lines.length > 500) entry.lines.shift();
}

/**
 * On daemon startup, recover tasks that were interrupted (stuck in 'running').
 * Resets them to 'pending' so they get re-picked up.
 */
function recoverInterruptedTasks() {
  const db = brain.getDb();
  const stuck = db.prepare("SELECT * FROM tasks WHERE status = 'running'").all();
  if (stuck.length === 0) return 0;

  for (const t of stuck) {
    const hasCheckpoint = t.checkpoint ? ' (has checkpoint — will resume)' : '';
    console.log(`[tasks] Recovering interrupted task: ${t.title}${hasCheckpoint}`);
    brain.updateTask(t.id, {
      status: 'pending',
      started_at: null,
      next_run_at: new Date().toISOString(), // run immediately on recovery
    });
  }
  return stuck.length;
}

/**
 * Try to auto-resume Slack tasks that were paused due to token expiry.
 * Runs at the start of each task loop cycle.
 */
async function tryResumeSlackTasks() {
  try {
    const allTasks = brain.listTasks({});
    const pausedSlack = allTasks.filter(t => t.status === 'paused' && (
      (t.skill && t.skill.includes('slack')) ||
      (t.error && t.error.includes('Slack token expired'))
    ));
    if (pausedSlack.length === 0) return;

    // Try refreshing the Slack token
    const slackMcp = require('../tools/slack-mcp');
    const valid = await slackMcp.ensureValidToken();
    if (!valid) return;

    // Token is valid — un-pause all Slack tasks
    for (const t of pausedSlack) {
      brain.updateTask(t.id, {
        status: 'pending',
        error: null,
        next_run_at: new Date().toISOString(),
      });
      console.log(`[tasks] Auto-resumed: ${t.title} (Slack token refreshed)`);
    }
  } catch {}
}

/**
 * Task executor loop — picks up due tasks and runs them.
 */
async function runDueTasks() {
  // Try to auto-resume Slack tasks paused due to token expiry
  await tryResumeSlackTasks();

  const tasks = brain.getDueTasks();
  if (tasks.length === 0) return { processed: 0 };

  let processed = 0;
  for (const task of tasks) {
    if (task.status === 'running') continue;

    const now = new Date().toISOString();
    const hasCheckpoint = task.checkpoint ? ' (resuming from checkpoint)' : '';
    brain.updateTask(task.id, { status: 'running', started_at: now });
    appendLog(task.id, `[${now.slice(11, 19)}] Starting: ${task.title}${hasCheckpoint}`);

    try {
      let resultText;

      if (task.skill) {
        resultText = await executeSkill(task.id, task);
      } else if (task.execution === 'script' && task.script) {
        resultText = await executeScript(task.id, task.script, task.checkpoint);
      } else {
        resultText = await executeChat(task.id, task);
      }

      const completedAt = new Date().toISOString();
      appendLog(task.id, `[${completedAt.slice(11, 19)}] Completed`);

      const logEntry = taskLogs.get(task.id);
      if (logEntry) logEntry.status = 'completed';

      // Strip BRIEFING_ITEMS block from displayed result, store items separately
      const cleanResult = resultText.replace(/<!--\s*BRIEFING_ITEMS[\s\S]*?-->/, '').trim();

      // Slack delivery verification: if the task originated from Slack but the
      // LLM result suggests the reply was never delivered, retry once directly.
      let slackDeliveryFailed = false;
      if (task.source === 'slack' && (task.run_count || 0) < 3) {
        const deliveryIssue = looksLikeSlackDeliveryFailure(cleanResult);
        if (deliveryIssue) {
          console.log(`[tasks] Slack delivery issue for task ${task.id}: ${deliveryIssue}`);
          appendLog(task.id, `Slack delivery issue: ${deliveryIssue} — retrying send...`);
          slackDeliveryFailed = !(await retrySlackDelivery(task, cleanResult));
          if (!slackDeliveryFailed) {
            appendLog(task.id, 'Slack retry succeeded');
            console.log(`[tasks] Slack retry succeeded for task ${task.id}`);
          } else {
            appendLog(task.id, 'Slack retry also failed — marking task for retry');
            console.log(`[tasks] Slack retry failed for task ${task.id}, will retry later`);
          }
        }
      }

      brain.updateTask(task.id, {
        status: slackDeliveryFailed ? 'pending' : (task.type === 'recurring' ? 'pending' : 'completed'),
        completed_at: completedAt,
        last_run_at: completedAt,
        run_count: (task.run_count || 0) + 1,
        result: cleanResult.slice(0, 10000),
        checkpoint: null, // clear checkpoint on success
        error: slackDeliveryFailed ? 'Slack reply delivery failed — scheduled for retry' : null,
        next_run_at: slackDeliveryFailed
          ? new Date(Date.now() + 2 * 60 * 1000).toISOString()  // retry in 2 min
          : (task.type === 'recurring' ? computeNextDue(task.schedule) : null),
        // Keep started_at so duration can be computed as completed_at - started_at
      });

      // Consolidate briefing items if present
      try {
        consolidateBriefingItems(task, resultText, completedAt);
      } catch (e) {
        console.error('[tasks] Briefing item consolidation failed:', e.message);
      }

      // Notify on completion — only for one-off tasks (user-initiated).
      // Recurring/background tasks (daily briefing, calendar sync, etc.) are silent.
      if (task.type !== 'recurring') {
        try {
          const { sendNotification } = require('../tools/local-tools');
          await sendNotification('WALL-E Task', task.title + ' — done');
        } catch {}
      }

      // Persist activity completion (Phase 2: Activity History)
      try {
        const activityLog = require('../lib/activity-log');
        activityLog.log({
          session_id: `task-${task.id}`,
          type: 'task_complete',
          title: `Task completed: ${task.title}`,
          body: cleanResult.slice(0, 500),
          detail: JSON.stringify({ task_id: task.id, skill: task.skill || null, execution: task.execution || 'chat' }),
        });
      } catch {}

      processed++;
      console.log(`[tasks] Completed: ${task.title} (${task.execution || 'chat'})`);
    } catch (err) {
      console.error('[tasks] Failed:', task.title, err.message);
      appendLog(task.id, `[ERROR] ${err.message}`);

      // Persist activity error (Phase 2: Activity History)
      try {
        const activityLog = require('../lib/activity-log');
        activityLog.log({
          session_id: `task-${task.id}`,
          type: 'task_error',
          title: `Task failed: ${task.title}`,
          body: err.message,
          detail: JSON.stringify({ task_id: task.id, skill: task.skill || null }),
        });
      } catch {}

      const logEntry = taskLogs.get(task.id);
      if (logEntry) logEntry.status = 'failed';

      // Detect actionable errors and create pending questions
      const errMsg = err.message || '';
      const isSlackAuth = errMsg.includes('Slack token expired') || errMsg.includes('invalid_auth') || errMsg.includes('401') || errMsg.includes('not authenticated');

      // For Slack auth errors, try auto-refresh before pausing
      let slackRefreshSucceeded = false;
      if (isSlackAuth) {
        try {
          const slackMcp = require('../tools/slack-mcp');
          const refreshed = await slackMcp.refreshToken();
          if (refreshed) {
            console.log('[tasks] Slack token auto-refreshed after auth error — will retry next cycle');
            slackRefreshSucceeded = true;
          }
        } catch {}
      }

      // Only pause Slack tasks if refresh failed (token truly unusable)
      if (isSlackAuth && !slackRefreshSucceeded) {
        try {
          const allTasks = brain.listTasks({});
          for (const t of allTasks) {
            if (t.skill && t.skill.includes('slack') && (t.status === 'pending' || t.status === 'running')) {
              brain.updateTask(t.id, { status: 'paused' });
              console.log(`[tasks] Auto-paused: ${t.title} (Slack auth expired, refresh failed)`);
            }
          }
          const existingQ = brain.listQuestions({ status: 'pending', question_type: 'action_required' })
            .find(q => q.question.includes('Slack token expired'));
          if (!existingQ) {
            brain.insertQuestion({
              question_type: 'action_required',
              question: 'Slack token expired — WALL-E cannot access Slack until you re-authenticate.',
              context: JSON.stringify({
                action: 'slack_reauth',
                failed_task: task.title,
                error: errMsg,
                instructions: 'Click "Reconnect Slack" in the Tasks tab, or run: node tools/slack-mcp.js auth',
              }),
              priority: 'high',
            });
            console.log('[tasks] Created pending question for Slack re-auth');
            try {
              const { sendNotification } = require('../tools/local-tools');
              await sendNotification('WALL-E: Slack Disconnected', 'Re-authenticate to resume Slack tasks');
            } catch {}
          }
        } catch (e) {
          console.error('[tasks] Failed to create auth question:', e.message);
        }
      }

      brain.updateTask(task.id, {
        status: task.type === 'recurring' ? 'pending' : 'failed',
        error: isSlackAuth
          ? (slackRefreshSucceeded ? 'Slack token refreshed — retrying.' : 'Slack token expired. Reconnect Slack to retry.')
          : err.message,
        completed_at: new Date().toISOString(),
        last_run_at: new Date().toISOString(),
        run_count: (task.run_count || 0) + 1,
        next_run_at: task.type === 'recurring' ? computeNextDue(task.schedule) : null,
      });

      // Only pause recurring tasks if refresh truly failed
      if (isSlackAuth && !slackRefreshSucceeded && task.type === 'recurring') {
        brain.updateTask(task.id, { status: 'paused' });
      }
    }
  }

  return { processed };
}

// ── Skill execution ──

async function executeSkill(taskId, task) {
  const skill = findSkill(task.skill);
  if (!skill) throw new Error(`Skill "${task.skill}" not found`);

  appendLog(taskId, `Skill: ${skill.name} v${skill.version} (${skill.execution})`);

  if (skill.execution === 'script') {
    const { runScriptSkill } = require('../skills/script-skill-runner');
    return runScriptSkill(skill, task, {
      log: line => appendLog(taskId, line),
      onCheckpoint: checkpoint => brain.updateTask(taskId, { checkpoint }),
      onProcess: child => taskProcesses.set(taskId, child),
      onClose: child => {
        if (taskProcesses.get(taskId) === child) taskProcesses.delete(taskId);
      },
    });
  }

  if (skill.execution === 'agent') {
    // Build a task-like object with skill instructions as the description
    const agentTask = {
      ...task,
      title: task.title || skill.name,
      description: skill.instructions || skill.description,
    };
    appendLog(taskId, `Agent skill: sending instructions to Claude...`);
    return executeChat(taskId, agentTask);
  }

  throw new Error(`Unknown skill execution mode: ${skill.execution}`);
}

// ── Script execution with live log streaming ──

function executeScript(taskId, script, checkpoint, extraEnv) {
  return new Promise((resolve, reject) => {
    // Pass checkpoint as env var so scripts can resume from where they left off
    const env = { ...process.env, HOME: process.env.HOME };
    if (checkpoint) env.WALL_E_CHECKPOINT = checkpoint;
    if (extraEnv) Object.assign(env, extraEnv);

    const child = spawn('bash', ['-c', script], {
      cwd: WALL_E_DIR,
      env,
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    taskProcesses.set(taskId, child);

    let stdout = '';
    let stderr = '';
    // No timeout — tasks run until completion. User can stop via UI.

    child.stdout.on('data', (chunk) => {
      const text = chunk.toString();
      stdout += text;
      text.split('\n').filter(Boolean).forEach(line => {
        appendLog(taskId, line);
        // Lines starting with CHECKPOINT: are saved as resume points
        if (line.startsWith('CHECKPOINT:')) {
          brain.updateTask(taskId, { checkpoint: line.slice(11).trim() });
        }
      });
    });

    child.stderr.on('data', (chunk) => {
      const text = chunk.toString();
      stderr += text;
      text.split('\n').filter(Boolean).forEach(line => {
        appendLog(taskId, `[stderr] ${line}`);
      });
    });

    child.on('close', (code) => {
      taskProcesses.delete(taskId);
      if (code !== 0 && code !== null) {
        reject(new Error(`Exit code ${code}: ${stderr.slice(0, 500)}`));
      } else {
        let output = stdout.trim();
        if (stderr.trim()) output += '\n[stderr] ' + stderr.trim();
        resolve(output);
      }
    });

    child.on('error', (err) => {
      reject(err);
    });
  });
}

// ── Chat execution with log streaming ──

async function executeChat(taskId, task) {
  // Use multi-turn for complex tasks
  if (task.execution === 'multi-turn') {
    return executeMultiTurnChat(taskId, task);
  }

  // Single-turn (existing behavior)
  const chatModule = require('../chat');
  const prompt = buildTaskPrompt(task);

  appendLog(taskId, 'Sending to Claude...');

  const result = await chatModule.chat(prompt, {
    channel: 'task',
    session_id: `task-${task.id}`,
    onProgress: (event) => {
      if (event.type === 'tool_call') appendLog(taskId, event.summary);
      else if (event.type === 'tool_done') appendLog(taskId, `Done: ${event.summary}`);
      else if (event.type === 'thinking') appendLog(taskId, `Turn ${(event.turn || 0) + 1}...`);
    },
  });

  return result.reply || '';
}

/**
 * Execute a task using multiple chat turns, maintaining session state.
 * Each turn can use tools, and progress is checkpointed between turns.
 */
async function executeMultiTurnChat(taskId, task) {
  const chatModule = require('../chat');
  const sessionId = `task-${task.id}`;
  const MAX_TASK_TURNS = 5;
  let lastReply = '';

  for (let turn = 0; turn < MAX_TASK_TURNS; turn++) {
    const prompt = turn === 0
      ? buildTaskPrompt(task)
      : 'Continue working on this task. Review your progress so far and take the next step. If you are done, include [TASK COMPLETE] in your response.';

    appendLog(taskId, `Multi-turn ${turn + 1}/${MAX_TASK_TURNS}...`);

    const result = await chatModule.chat(prompt, {
      channel: 'task',
      session_id: sessionId,
      onProgress: (event) => {
        if (event.type === 'tool_call') appendLog(taskId, event.summary);
        else if (event.type === 'tool_done') appendLog(taskId, `Done: ${event.summary}`);
      },
    });

    lastReply = result.reply || '';
    appendLog(taskId, `Turn ${turn + 1} result: ${lastReply.slice(0, 200)}`);

    // Check if task declared itself complete
    if (lastReply.includes('[TASK COMPLETE]') || lastReply.includes('[DONE]')) {
      appendLog(taskId, 'Task self-declared complete');
      break;
    }

    // Checkpoint between turns
    brain.updateTask(taskId, {
      checkpoint: JSON.stringify({ turn: turn + 1, partial: lastReply.slice(0, 2000) }),
    });
  }

  return lastReply;
}

function buildTaskPrompt(task) {
  let prompt = `[TASK] ${task.title}`;
  if (task.description) prompt += `\n\n${task.description}`;
  prompt += `\n\nThis is a background task. Complete it thoroughly using your tools. Provide a clear, actionable result.`;
  if (task.priority === 'urgent' || task.priority === 'high') {
    prompt += ` This is ${task.priority} priority.`;
  }
  return prompt;
}

function computeNextDue(schedule) {
  if (!schedule) return null;
  const now = new Date();

  const dailyAtMatch = schedule.match(/^daily\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?$/i);
  if (dailyAtMatch) {
    let hour = parseInt(dailyAtMatch[1]);
    const min = parseInt(dailyAtMatch[2] || '0');
    const ampm = dailyAtMatch[3]?.toLowerCase();
    if (ampm === 'pm' && hour < 12) hour += 12;
    if (ampm === 'am' && hour === 12) hour = 0;
    const next = new Date(now);
    next.setHours(hour, min, 0, 0);
    if (next <= now) next.setDate(next.getDate() + 1);
    return next.toISOString();
  }

  const weeklyMatch = schedule.match(/^weekly\s+(?:on\s+)?(\w+)(?:\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?)?$/i);
  if (weeklyMatch) {
    const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
    const dayAbbrevs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
    const dayInput = weeklyMatch[1].toLowerCase();
    let targetDay = dayNames.indexOf(dayInput);
    if (targetDay === -1) targetDay = dayAbbrevs.indexOf(dayInput.slice(0, 3));
    if (targetDay === -1) targetDay = 1;
    let hour = parseInt(weeklyMatch[2] || '9');
    const min = parseInt(weeklyMatch[3] || '0');
    const ampm = weeklyMatch[4]?.toLowerCase();
    if (ampm === 'pm' && hour < 12) hour += 12;
    const next = new Date(now);
    next.setHours(hour, min, 0, 0);
    const daysUntil = (targetDay - now.getDay() + 7) % 7 || 7;
    next.setDate(next.getDate() + daysUntil);
    return next.toISOString();
  }

  if (schedule === 'hourly') return new Date(now.getTime() + 3600000).toISOString();
  if (schedule === 'daily') return new Date(now.getTime() + 86400000).toISOString();
  if (schedule === 'weekly') return new Date(now.getTime() + 604800000).toISOString();

  const everyMatch = schedule.match(/^every\s+(\d+)\s*(s|m|h|d)$/i);
  if (everyMatch) {
    const n = parseInt(everyMatch[1]);
    const unit = everyMatch[2].toLowerCase();
    const ms = unit === 's' ? n * 1000 : unit === 'h' ? n * 3600000 : unit === 'm' ? n * 60000 : n * 86400000;
    return new Date(now.getTime() + ms).toISOString();
  }

  return null;
}

/**
 * Immediately run a task by ID — bypasses the daemon loop timer.
 * Sets the task to pending with immediate next_run_at, then executes runDueTasks().
 */
async function runTaskById(taskId) {
  const task = brain.getTask(taskId);
  if (!task) throw new Error('Task not found');
  if (task.status === 'running') throw new Error('Task is already running');
  brain.updateTask(taskId, { status: 'pending', next_run_at: new Date().toISOString(), error: null, result: null });
  return runDueTasks();
}

// ── Briefing Item Consolidation ──

/**
 * Parse BRIEFING_ITEMS from task result and upsert into briefing_items table.
 * New items are inserted; existing items (matched by skill + title) get updated.
 * Items from previous runs that don't appear in the new result are left as-is
 * (user may have modified their status).
 */
function consolidateBriefingItems(task, resultText, timestamp) {
  const match = resultText.match(/<!--\s*BRIEFING_ITEMS\s*\n([\s\S]*?)-->/);
  if (!match) return;

  let items;
  try {
    items = JSON.parse(match[1].trim());
  } catch (e) {
    console.error('[tasks] Failed to parse BRIEFING_ITEMS JSON:', e.message);
    return;
  }

  if (!Array.isArray(items) || items.length === 0) return;

  const skill = task.skill || 'unknown';
  const now = timestamp || new Date().toISOString();
  let inserted = 0, updated = 0;

  for (const item of items) {
    if (!item.title) continue;

    // Try exact title match first
    const existing = brain.findBriefingItemByTitle(skill, item.title);

    if (existing) {
      // Update existing item — bump times_seen, update last_seen, and refresh metadata
      const updates = {
        last_seen: now,
        times_seen: (existing.times_seen || 1) + 1,
      };
      // Update urgency/owner/category if they changed (but don't downgrade user edits)
      if (item.urgency) updates.urgency = item.urgency;
      if (item.owner) updates.owner = item.owner;
      if (item.category) updates.category = item.category;
      if (item.context) updates.context = typeof item.context === 'string' ? item.context : JSON.stringify(item.context);
      brain.updateBriefingItem(existing.id, updates);
      updated++;
    } else {
      // Insert new item
      brain.insertBriefingItem({
        task_id: task.id,
        skill,
        title: item.title,
        category: item.category || null,
        owner: item.owner || null,
        urgency: item.urgency || 'async',
        context: item.context ? (typeof item.context === 'string' ? item.context : JSON.stringify(item.context)) : null,
        first_seen: now,
        last_seen: now,
      });
      inserted++;
    }
  }

  if (inserted || updated) {
    console.log(`[tasks] Briefing items consolidated: ${inserted} new, ${updated} updated (skill: ${skill})`);
  }
}

/**
 * Detect if a task result text indicates the Slack reply was never delivered.
 * Returns a string describing the issue, or null if delivery looks OK.
 */
function looksLikeSlackDeliveryFailure(resultText) {
  if (!resultText) return 'empty result';
  const lower = resultText.toLowerCase();

  // Explicit failure patterns
  const failurePatterns = [
    'mcp server error',
    'error sending',
    'failed to send',
    "couldn't send",
    'could not send',
    'unable to send',
    'slack_send_message failed',
    'failed to deliver',
    'error posting message',
    'connection refused',
    'mcp call failed',
    'encountering a technical issue sending',
  ];
  for (const p of failurePatterns) {
    if (lower.includes(p)) return `explicit failure: ${p}`;
  }

  // No reply was attempted — model researched but never sent
  // Success patterns: if any of these appear, the reply was likely sent
  const successPatterns = [
    'replied to your slack',
    'sent the message',
    'message was sent',
    'reply sent',
    'posted to slack',
    'slack_send_message',
    'sent in thread',
    'responded in the thread',
    'replied in the slack thread',
    'sent to the thread',
    'delivered the answer',
  ];
  if (!successPatterns.some(p => lower.includes(p))) {
    return 'no reply sent (model exhausted turns without calling slack_send_message)';
  }

  return null;
}

/**
 * Attempt to retry sending the Slack reply directly via MCP.
 * Extracts the answer from the result text and sends it to the channel/thread
 * referenced in the task description.
 * Returns true if the retry succeeded, false otherwise.
 */
async function retrySlackDelivery(task, resultText) {
  try {
    const slackMcp = require('../tools/slack-mcp');

    // Extract channel_id and thread_ts from the task description
    const channelMatch = task.description?.match(/"channel_id":\s*"([^"]+)"/);
    const threadMatch = task.description?.match(/"thread_ts":\s*"([^"]+)"/);
    console.log(`[tasks] retrySlackDelivery: channel=${channelMatch?.[1]}, thread=${threadMatch?.[1]}`);
    if (!channelMatch) {
      console.error('[tasks] Cannot retry Slack delivery: no channel_id in task description');
      return false;
    }

    // Extract the useful content from the result, stripping failure/meta messages
    let answer = resultText;

    // Remove lines that are just research narration ("Let me...", "I'll...")
    answer = answer.replace(/^(?:Let me|I'll|I will|Now let me|Let's)[^\n]*$/gm, '').trim();

    // Strip error messages about delivery failure
    answer = answer.replace(/(?:However|Unfortunately|I'm currently)[^.]*(?:unable to send|MCP server|authentication issue|technical issue|couldn't send|could not send)[^.]*\./gi, '').trim();

    // Try to find substantive answer (before any error part)
    const errorIdx = answer.search(/unfortunately.*(?:technical issue|error|couldn't send|MCP server)/i);
    if (errorIdx > 50) {
      answer = answer.slice(0, errorIdx).trim();
    }

    console.log(`[tasks] retrySlackDelivery: extracted answer (${answer.length} chars): ${answer.slice(0, 200)}`);

    // If the answer is still just meta-narration with no real content, try to
    // generate a brief reply using the chat engine with strict instructions
    if (answer.length < 20 || !/[a-z]{3,}/i.test(answer)) {
      console.log('[tasks] No substantive answer in result — generating a brief reply via chat...');
      try {
        const chatModule = require('../chat');
        const briefReply = await chatModule.chat(
          `[URGENT] You MUST reply to a Slack question immediately. The question was:\n\n${task.description?.match(/(?:Question|task) from.*?:\n\n([\s\S]*?)(?:\n\nFull thread|\n\n---)/i)?.[1] || task.title}\n\nGive a helpful 2-3 sentence answer. Do NOT use any tools. Just reply with your best answer based on what you know.`,
          { channel: 'task', session_id: `retry-${task.id}`, maxToolCalls: 0, timeoutMs: 30000 }
        );
        answer = briefReply.reply || '';
      } catch (chatErr) {
        console.error(`[tasks] Brief reply generation failed: ${chatErr.message}`);
        return false;
      }
    }

    // Truncate if too long for Slack
    if (answer.length > 3000) answer = answer.slice(0, 3000) + '...';
    if (answer.length < 10) {
      console.error('[tasks] Cannot retry Slack delivery: no substantive answer to send');
      return false;
    }

    const args = { channel_id: channelMatch[1], message: answer };
    if (threadMatch) args.thread_ts = threadMatch[1];

    console.log(`[tasks] retrySlackDelivery: sending ${answer.length} chars to ${args.channel_id} thread=${args.thread_ts || 'none'}`);
    const result = await slackMcp.callSlackMcp('slack_send_message', args);
    const resultStr = JSON.stringify(result).slice(0, 200);
    console.log(`[tasks] retrySlackDelivery: MCP result: ${resultStr}`);

    // Check for actual success — look for message_link in response
    const hasMessageLink = resultStr.includes('message_link') || resultStr.includes('message_ts');
    if (!hasMessageLink) {
      console.error(`[tasks] retrySlackDelivery: no message_link in response — likely failed`);
      return false;
    }
    return true;
  } catch (err) {
    console.error(`[tasks] Slack delivery retry failed: ${err.message}`);
    return false;
  }
}

module.exports = { runDueTasks, runTaskById, recoverInterruptedTasks, buildTaskPrompt, computeNextDue, getTaskLogs, clearTaskLogs, stopTask, executeSkill, executeMultiTurnChat, consolidateBriefingItems, taskLogs };
