'use strict';

const CONFIRMATION_RE = /\b(?:yes|yep|yeah|ok(?:ay)?|sure|approved?|confirm(?:ed)?|looks\s+good|go\s+ahead|do\s+it|send\s+it|send\s+this|please\s+send|schedule\s+it|create\s+it|book\s+it)\b/i;
const EXTERNAL_VERB_RE = /\b(?:send|email|mail|post|message|text|sms|notify|schedule|book|calendar|event|invite|reminder)\b/i;
const NEGATIVE_RE = /\b(?:no|nope|don'?t|do\s+not|stop|cancel|hold\s+off|wait)\b/i;
const APPROVAL_WAIVER_RE = /\b(?:no\s+approval\s+needed|no\s+need\s+(?:to\s+)?(?:ask|confirm)|without\s+(?:asking|approval|confirmation)|don'?t\s+ask|do\s+not\s+ask|just\s+do\s+it)\b/i;
const TASK_WORK_RE = /\b(?:fix|implement|code|website|ui|ux|design|review|refactor|test|commit|merge|deploy|branch|pr)\b/i;
const SIDE_EFFECT_TOOL_NAMES = new Set([
  'mail_send',
  'mail_reply',
  'calendar_create',
  'slack_send_message',
  'reminder_create',
  'notification',
]);
const SHORT_CONFIRMATION_PHRASES = [
  'yes',
  'yep',
  'yeah',
  'ok',
  'okay',
  'sure',
  'approved',
  'approve',
  'confirm',
  'confirmed',
  'looks good',
  'go ahead',
  'do it',
  'send it',
  'send this',
  'please send',
  'schedule it',
  'create it',
  'book it',
];
const EXTERNAL_COMPLETION_RE = /\b(?:executed|sent|emailed|created|scheduled|booked|stored|saved)\b/i;
const EXTERNAL_OBJECT_RE = /\b(?:email|mail|calendar|event|invite|message|reminder|notification|rule|memory|action|actions)\b/i;
const EXTERNAL_COMPLETION_PAIR_RE = new RegExp(
  `(?:${EXTERNAL_COMPLETION_RE.source}[\\s\\S]{0,80}${EXTERNAL_OBJECT_RE.source})|(?:${EXTERNAL_OBJECT_RE.source}[\\s\\S]{0,80}${EXTERNAL_COMPLETION_RE.source})`,
  'i',
);
const NEGATED_COMPLETION_RE = /\b(?:not|never|didn'?t|haven'?t|hasn'?t|wasn'?t|isn'?t)\s+(?:actually\s+)?(?:done|complete|completed|executed|sent|emailed|created|scheduled|booked|stored|saved)\b/i;
const EMAIL_APPROVAL_RE = /\b(?:email|emails|e-mail|e-mails|mail|mails)\b/i;
const CALENDAR_APPROVAL_RE = /\b(?:calendar|calendars|event|events|invite|invites|schedule|scheduled|book|booked)\b/i;
const SLACK_APPROVAL_RE = /\bslack\b/i;
const REMINDER_APPROVAL_RE = /\b(?:reminder|reminders)\b/i;
const NOTIFICATION_APPROVAL_RE = /\b(?:notification|notifications|notify)\b/i;

function normalizeApprovalText(text, { removeSpaces = false } = {}) {
  let value = String(text || '')
    .toLowerCase()
    .replace(/[’']/g, '')
    .replace(/[^a-z0-9\s]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
  if (removeSpaces) value = value.replace(/\s+/g, '');
  return value;
}

function levenshteinDistance(a, b) {
  const left = String(a || '');
  const right = String(b || '');
  if (left === right) return 0;
  if (!left.length) return right.length;
  if (!right.length) return left.length;
  const prev = Array.from({ length: right.length + 1 }, (_, i) => i);
  const curr = Array.from({ length: right.length + 1 }, () => 0);
  for (let i = 1; i <= left.length; i += 1) {
    curr[0] = i;
    for (let j = 1; j <= right.length; j += 1) {
      const cost = left[i - 1] === right[j - 1] ? 0 : 1;
      curr[j] = Math.min(
        prev[j] + 1,
        curr[j - 1] + 1,
        prev[j - 1] + cost,
      );
    }
    for (let j = 0; j <= right.length; j += 1) prev[j] = curr[j];
  }
  return prev[right.length];
}

function hasPendingExternalActions(pendingActions) {
  return Array.isArray(pendingActions) && pendingActions.length > 0;
}

function isShortPendingApprovalReply(value) {
  const trimmed = String(value || '').trim();
  if (!trimmed || trimmed.length > 48) return false;
  if (TASK_WORK_RE.test(trimmed)) return false;
  return normalizeApprovalText(trimmed).split(/\s+/).filter(Boolean).length <= 6;
}

function isNearShortConfirmation(value) {
  const normalized = normalizeApprovalText(value);
  if (!normalized) return false;
  const compact = normalizeApprovalText(value, { removeSpaces: true });
  return SHORT_CONFIRMATION_PHRASES.some((phrase) => {
    const canonical = normalizeApprovalText(phrase);
    const canonicalCompact = normalizeApprovalText(phrase, { removeSpaces: true });
    if (normalized === canonical || compact === canonicalCompact) return true;
    const minLen = Math.min(normalized.length, canonical.length);
    if (minLen <= 4) return false;
    const limit = Math.max(normalized.length, canonical.length) <= 8 ? 1 : 2;
    if (levenshteinDistance(normalized, canonical) <= limit) return true;
    return compact.length > 4
      && levenshteinDistance(compact, canonicalCompact) <= limit;
  });
}

function isExternalActionConfirmationText(text, { pendingActions = [] } = {}) {
  const value = String(text || '').trim();
  if (!value) return false;
  const hasPending = hasPendingExternalActions(pendingActions);
  const negative = NEGATIVE_RE.test(value);
  if (negative) {
    const waiverApproval = hasPending
      && APPROVAL_WAIVER_RE.test(value)
      && (CONFIRMATION_RE.test(value) || EXTERNAL_VERB_RE.test(value));
    if (waiverApproval) return true;
    if (!waiverApproval) return false;
  }
  if (CONFIRMATION_RE.test(value)) {
    if (EXTERNAL_VERB_RE.test(value)) return true;
    if (/^(?:yes|yep|yeah|ok(?:ay)?|sure|approved?|confirm(?:ed)?|looks\s+good|go\s+ahead|do\s+it)[.! ]*$/i.test(value)) {
      return true;
    }
    if (/\bgo\s+ahead\b/i.test(value) && value.length <= 48 && !TASK_WORK_RE.test(value)) {
      return true;
    }
  }
  if (hasPending
    && isShortPendingApprovalReply(value)
    && isNearShortConfirmation(value)) {
    return true;
  }
  return false;
}

function externalActionDomainsForApprovalText(text) {
  const value = String(text || '');
  const domains = new Set();
  if (EMAIL_APPROVAL_RE.test(value)) domains.add('email');
  if (CALENDAR_APPROVAL_RE.test(value)) domains.add('calendar');
  if (SLACK_APPROVAL_RE.test(value)) domains.add('slack');
  if (REMINDER_APPROVAL_RE.test(value)) domains.add('reminder');
  if (NOTIFICATION_APPROVAL_RE.test(value)) domains.add('notification');
  return [...domains];
}

function isCurrentTurnExternalActionApprovalText(text) {
  const value = String(text || '').trim();
  if (!value) return false;
  const negative = NEGATIVE_RE.test(value);
  if (negative && !APPROVAL_WAIVER_RE.test(value)) return false;
  if (!CONFIRMATION_RE.test(value) || !EXTERNAL_VERB_RE.test(value)) return false;
  return externalActionDomainsForApprovalText(value).length > 0;
}

function parseMaybeJson(value) {
  if (!value) return null;
  if (typeof value === 'object') return value;
  if (typeof value !== 'string') return null;
  try { return JSON.parse(value); } catch { return null; }
}

function parsedPayloadsFromValue(value) {
  if (!value) return [];
  const parsed = parseMaybeJson(value);
  if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return [parsed];
  if (!Array.isArray(value)) return [];
  const payloads = [];
  for (const item of value) {
    const itemParsed = parseMaybeJson(item);
    if (itemParsed && typeof itemParsed === 'object' && !Array.isArray(itemParsed)) {
      payloads.push(itemParsed);
      continue;
    }
    if (!item || typeof item !== 'object') continue;
    for (const key of ['content', 'text', 'output', 'result']) {
      const nested = parseMaybeJson(item[key]);
      if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
        payloads.push(nested);
      }
    }
  }
  return payloads;
}

function messageContentBlocks(message) {
  const blocks = [];
  for (const content of [message?.content, message?.message?.content]) {
    if (Array.isArray(content)) blocks.push(...content);
    else if (content && typeof content === 'object') blocks.push(content);
  }
  return blocks;
}

function externalActionFromParsedPayload(parsed) {
  if (!parsed || parsed.external_action !== true || parsed.blocked !== true) return null;
  const action = parsed.action;
  if (!action || typeof action !== 'object') return null;
  if (!action.id || !action.payloadHash || !action.toolName) return null;
  return action;
}

function externalActionFromToolCall(toolCall) {
  if (!toolCall || typeof toolCall !== 'object') return null;
  for (const parsed of parsedPayloadsFromValue(toolCall.output || toolCall.result)) {
    const action = externalActionFromParsedPayload(parsed);
    if (action) return action;
  }
  return null;
}

function externalActionFromContentBlock(block) {
  if (!block || typeof block !== 'object') return null;
  const type = block.type || block.kind || '';
  if (type && type !== 'tool_result' && type !== 'tool_call_result') return null;
  for (const key of ['content', 'text', 'output', 'result']) {
    for (const parsed of parsedPayloadsFromValue(block[key])) {
      const action = externalActionFromParsedPayload(parsed);
      if (action) return action;
    }
  }
  return null;
}

function collectPendingExternalActionsFromMessage(message) {
  const seen = new Set();
  const actions = [];
  const add = (action) => {
    if (!action) return;
    const key = action.payloadHash || action.id;
    if (seen.has(key)) return;
    seen.add(key);
    actions.push(action);
  };

  for (const toolCall of message?.toolCalls || []) {
    add(externalActionFromToolCall(toolCall));
  }
  for (const block of messageContentBlocks(message)) {
    add(externalActionFromContentBlock(block));
  }

  return actions;
}

function isSyntheticToolResultMessage(message) {
  if (!message || message.role !== 'user') return false;
  return messageContentBlocks(message).some((block) => {
    const type = block?.type || block?.kind || '';
    return type === 'tool_result' || type === 'tool_call_result';
  });
}

function extractLatestPendingExternalActions(history = []) {
  if (!Array.isArray(history)) return [];
  for (let i = history.length - 1; i >= 0; i -= 1) {
    const message = history[i];
    if (!message) continue;
    const actions = collectPendingExternalActionsFromMessage(message);
    if (actions.length) return actions;
    if (message.role === 'user' && !isSyntheticToolResultMessage(message)) break;
  }
  return [];
}

function buildExternalActionApprovalsForMessage({
  history = [],
  userText = '',
  ctmSessionId = '',
  chatSessionId = '',
  now = () => new Date(),
  pendingActions = null,
} = {}) {
  const actions = Array.isArray(pendingActions)
    ? pendingActions
    : extractLatestPendingExternalActions(history);
  if (!isExternalActionConfirmationText(userText, { pendingActions: actions })) return [];
  return actions.map((action) => ({
    decision: 'allow',
    actionId: action.id,
    payloadHash: action.payloadHash,
    action,
    source: 'ctm_user_confirmation',
    ctmSessionId,
    chatSessionId,
    confirmedAt: now().toISOString(),
    confirmedByUserMessage: String(userText || '').slice(0, 500),
  }));
}

function buildCurrentTurnExternalActionApprovalsForMessage({
  userText = '',
  ctmSessionId = '',
  chatSessionId = '',
  now = () => new Date(),
} = {}) {
  if (!isCurrentTurnExternalActionApprovalText(userText)) return [];
  return [{
    decision: 'allow',
    scope: 'current_turn_external_actions',
    approvalScope: 'current_turn_external_actions',
    domains: externalActionDomainsForApprovalText(userText),
    source: 'ctm_user_current_turn_confirmation',
    ctmSessionId,
    chatSessionId,
    confirmedAt: now().toISOString(),
    confirmedByUserMessage: String(userText || '').slice(0, 500),
  }];
}

function parseToolResultPayload(toolCall) {
  if (!toolCall || typeof toolCall !== 'object') return null;
  return parsedPayloadsFromValue(toolCall.output || toolCall.result)[0] || null;
}

function isSuccessfulExternalActionResult(toolCall) {
  if (!toolCall || typeof toolCall !== 'object') return false;
  if (toolCall.status === 'error' || toolCall.error) return false;
  const name = toolCall.name || toolCall.tool || '';
  const parsed = parseToolResultPayload(toolCall);
  if (parsed?.external_action === true && parsed?.blocked === true) return false;
  if (parsed && (parsed.error || parsed.ok === false || parsed.success === false)) return false;
  if (!SIDE_EFFECT_TOOL_NAMES.has(name)) return false;
  if (!parsed) return /approved action (?:was already executed|executed and verified)/i.test(toolCall.summary || '');
  if (parsed.alreadyExecuted === true) return true;
  if (parsed.verified === true || parsed.verification?.ok === true) return true;
  return /approved action (?:was already executed|executed and verified)/i.test(toolCall.summary || '');
}

function hasExecutedExternalActionToolResult(toolCalls = []) {
  if (!Array.isArray(toolCalls)) return false;
  return toolCalls.some(isSuccessfulExternalActionResult);
}

function hasBlockedExternalActionGatewayToolResult(toolCalls = []) {
  if (!Array.isArray(toolCalls)) return false;
  return toolCalls.some((toolCall) => {
    const parsed = parseToolResultPayload(toolCall);
    return parsed?.external_action_gateway === true && parsed?.blocked === true;
  });
}

function externalActionCompletionClaim(text) {
  const value = String(text || '').trim();
  if (!value || NEGATED_COMPLETION_RE.test(value)) return false;
  if (/\b(?:email sent|mail sent|calendar (?:event )?created|event scheduled|message sent|memory rule stored)\b/i.test(value)) {
    return true;
  }
  if (/\b(?:done|completed|all set)\b[\s\S]{0,80}\b(?:external )?actions?\b/i.test(value)) return true;
  return EXTERNAL_COMPLETION_PAIR_RE.test(value);
}

function externalActionLabel(action = {}) {
  const target = action.target || {};
  const payload = action.payload || {};
  if (action.toolName === 'mail_send') {
    const to = Array.isArray(target.to) ? target.to.join(', ') : (target.to || 'recipient');
    const subject = payload.subject ? ` "${payload.subject}"` : '';
    return `send email${subject} to ${to}`;
  }
  if (action.toolName === 'mail_reply') {
    const subject = payload.subject ? ` "${payload.subject}"` : '';
    return `reply to email${subject} from ${target.messageId || 'source message'}`;
  }
  if (action.toolName === 'calendar_create') {
    return `create calendar event "${payload.title || target.calendar || 'event'}"`;
  }
  if (action.toolName === 'slack_send_message') {
    return `send Slack message to ${target.channel || 'channel'}`;
  }
  if (action.toolName === 'reminder_create') return `create reminder "${payload.title || 'reminder'}"`;
  if (action.toolName === 'notification') return `send notification "${payload.title || 'notification'}"`;
  return action.operation || action.toolName || 'execute external action';
}

function buildPendingExternalActionNotExecutedReply({ actions = [] } = {}) {
  const pending = Array.isArray(actions) ? actions : [];
  if (!pending.length) {
    return [
      'I do not have verified evidence that those external actions completed.',
      'Wall-E now requires external side effects to go through the dedicated action tools and read-after-write verification before it can summarize success.',
      'Please retry with the dedicated mail/calendar action path, or ask me to stage the action again.',
    ].join('\n');
  }
  const lines = [
    'I did not execute those external actions.',
    'The previous turn still has pending approval requests:',
  ];
  pending.slice(0, 5).forEach((action, index) => {
    lines.push(`${index + 1}. ${externalActionLabel(action)}`);
  });
  if (pending.length > 5) lines.push(`...and ${pending.length - 5} more.`);
  lines.push('Reply with an explicit approval such as "approve" or "go ahead" to execute the staged actions, or "cancel" to leave them untouched.');
  return lines.join('\n');
}

module.exports = {
  buildCurrentTurnExternalActionApprovalsForMessage,
  buildExternalActionApprovalsForMessage,
  buildPendingExternalActionNotExecutedReply,
  extractLatestPendingExternalActions,
  externalActionCompletionClaim,
  externalActionFromToolCall,
  hasBlockedExternalActionGatewayToolResult,
  hasExecutedExternalActionToolResult,
  isExternalActionConfirmationText,
};
