'use strict';

// Daily question digest: select the few questions worth the owner's input, render them as a
// numbered message, and resolve them from the owner's reply. Channel fan-out (Slack/email/
// CTM tab) and the once-a-day scheduling wrap these pure helpers so they stay testable.
//
// Design: docs/superpowers/specs/2026-05-31-question-digest-and-brain-retention-design.md
// (daily-digest-only; deliver to Slack + email + CTM tab; answer via Slack reply or CTM).

const brain = require('../brain');

const DEFAULT_MAX = Number(process.env.WALL_E_QUESTION_DIGEST_MAX || 10);

function _ownerName() {
  try { return brain.getOwnerName() || 'there'; } catch { return 'there'; }
}

// Render a numbered, priority-tagged digest the owner can answer by replying "1: ... 2: ...".
function renderDigestText(questions, ownerName = _ownerName()) {
  if (!questions || questions.length === 0) return '';
  const n = questions.length;
  const lines = [
    `Hi ${ownerName} — Wall-E has ${n} question${n === 1 ? '' : 's'} for you:`,
    '',
  ];
  questions.forEach((q, i) => {
    const tag = q.priority && q.priority !== 'normal' ? `[${q.priority}] ` : '';
    lines.push(`${i + 1}. ${tag}${q.question}`);
  });
  lines.push('');
  lines.push('Reply like "1: your answer  2: your answer" — or answer in the Wall-E tab.');
  return lines.join('\n');
}

// Build today's digest from undelivered pending questions. Does NOT mark delivered — the
// caller marks only after a successful send (so a send failure re-tries tomorrow).
function buildDigest({ max = DEFAULT_MAX } = {}) {
  const questions = brain.listUndeliveredDigestQuestions({ max });
  return {
    count: questions.length,
    questions,
    text: renderDigestText(questions),
  };
}

// Parse an owner reply that answers numbered questions, e.g.:
//   "1: yes   2 - it's the Dropbox path\n3. no"
// Maps each leading number to the question at that 1-based position in `questions`.
// Returns [{ id, answer }]. Unparseable lines are ignored.
function parseDigestReply(text, questions) {
  if (!text || !Array.isArray(questions) || questions.length === 0) return [];
  const out = [];
  const seen = new Set();
  const re = /(?:^|\n)\s*(\d{1,2})\s*[:.\)\-]\s*(.+?)(?=\n\s*\d{1,2}\s*[:.\)\-]|$)/gs;
  let m;
  while ((m = re.exec(text)) !== null) {
    const idx = parseInt(m[1], 10) - 1;
    const answer = m[2].trim();
    if (idx < 0 || idx >= questions.length || !answer) continue;
    const q = questions[idx];
    if (!q || seen.has(q.id)) continue;
    seen.add(q.id);
    out.push({ id: q.id, answer });
  }
  return out;
}

// Resolve questions from parsed replies. Returns the count resolved.
function applyDigestReplies(parsed, { resolutionEvidence = 'digest reply' } = {}) {
  if (!Array.isArray(parsed)) return 0;
  let resolved = 0;
  for (const { id, answer } of parsed) {
    try {
      brain.answerQuestion(id, { answer, resolution_type: 'answered', resolution_evidence: resolutionEvidence });
      resolved += 1;
    } catch { /* question may have been resolved/expired already */ }
  }
  return resolved;
}

// ---- Channel fan-out -------------------------------------------------------------------
// The CTM Wall-E tab is the always-on surface (it lists pending questions + Answer/Dismiss
// buttons, no opt-in). Slack DM and email are OPT-IN push notifications, off by default,
// enabled via kv flags (set from the Wall-E settings UI). "Delivered" only gates the push
// channels — the CTM tab always shows pending questions regardless.
const KV = {
  slackEnabled: 'digest:slack_enabled',
  emailEnabled: 'digest:email_enabled',
  slackChannel: 'digest:slack_channel',
  emailTo: 'digest:email_to',
  lastSent: 'digest:last_sent_date',
};
function _kv(key) { try { return brain.getKv(key) || ''; } catch { return ''; } }
function _kvBool(key) { return String(_kv(key)).toLowerCase() === 'true'; }

function getDigestChannelConfig() {
  return {
    ctm: true, // always on
    slack: _kvBool(KV.slackEnabled),
    slackChannel: _kv(KV.slackChannel),
    email: _kvBool(KV.emailEnabled),
    emailTo: _kv(KV.emailTo),
  };
}

async function _defaultSendSlack(channel, text) {
  const { callSlackMcp } = require('../tools/slack-mcp');
  return callSlackMcp('slack_send_message', { channel, text });
}
async function _defaultSendEmail(to, subject, body) {
  const { sendMail } = require('../tools/local-tools');
  return sendMail({ to, subject, body, source: 'wall-e-digest' });
}

// Once-a-day digest job (registered in agent.js). Builds the undelivered digest and pushes
// it to the OPT-IN channels (Slack/email); marks delivered only if a push actually sent, so
// enabling a channel later still pushes the backlog once. The CTM tab needs no push.
// `opts` injects { date, force, sendSlack, sendEmail } for tests.
async function runDigestJob(opts = {}) {
  const date = opts.date || new Date();
  const todayStr = date.toISOString().slice(0, 10);
  const targetHour = Number(process.env.WALL_E_DIGEST_HOUR || 8);
  if (!opts.force) {
    if (date.getHours() < targetHour) return { sent: false, reason: 'before-target-hour' };
    if (_kv(KV.lastSent) === todayStr) return { sent: false, reason: 'already-sent-today' };
  }
  const cfg = getDigestChannelConfig();
  if (!cfg.slack && !cfg.email) return { sent: false, reason: 'no-push-channels-enabled' };

  const digest = buildDigest({ max: opts.max || DEFAULT_MAX });
  if (digest.count === 0) return { sent: false, reason: 'no-questions' };

  const sendSlack = opts.sendSlack || _defaultSendSlack;
  const sendEmail = opts.sendEmail || _defaultSendEmail;
  const results = {};
  let anySent = false;
  if (cfg.slack && cfg.slackChannel) {
    try { await sendSlack(cfg.slackChannel, digest.text); results.slack = 'sent'; anySent = true; }
    catch (e) { results.slack = `err:${e.message}`; }
  } else if (cfg.slack) {
    results.slack = 'skipped:no-channel-configured';
  }
  if (cfg.email && cfg.emailTo) {
    try { await sendEmail(cfg.emailTo, `Wall-E: ${digest.count} question${digest.count === 1 ? '' : 's'} for you`, digest.text); results.email = 'sent'; anySent = true; }
    catch (e) { results.email = `err:${e.message}`; }
  } else if (cfg.email) {
    results.email = 'skipped:no-recipient-configured';
  }

  if (anySent) {
    brain.markQuestionsDelivered(digest.questions.map((q) => q.id));
    try { brain.setKv(KV.lastSent, todayStr); } catch {}
  }
  return { sent: anySent, count: digest.count, results };
}

module.exports = {
  buildDigest, renderDigestText, parseDigestReply, applyDigestReplies,
  getDigestChannelConfig, runDigestJob, DEFAULT_MAX, KV,
};
