/**
 * WhatsApp Outbound Messaging
 *
 * Handles sending messages with LID resolution and presence indicators.
 * Based on OpenClaw's outbound.ts pattern.
 */

import type { OutboundMessage, OutboundFile } from "../../core/types.js";
import type { WAMessage, WAUrlInfo } from '@whiskeysockets/baileys';
import { isLid } from "./utils.js";
import { basename } from "node:path";

import { createLogger } from '../../logger.js';

const log = createLogger('WhatsApp');

/**
 * URL detection regex - matches https:// URLs.
 * This is the same pattern Baileys uses internally for link preview detection.
 * @see https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url
 */
const URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g;

/**
 * Extract the first URL from text.
 * Used to enable link previews in WhatsApp messages.
 */
function extractUrl(text: string): string | null {
  const matches = text.match(URL_REGEX);
  return matches?.[0] ?? null;
}
/**
 * LID (Linked Identifier) mapping for message sending.
 * Maps LID addresses to real JIDs.
 */
export interface LidMapper {
  /** Self-chat LID */
  selfChatLid: string;

  /** Bot's phone number */
  myNumber: string;

  /** Map of LID -> real JID */
  lidToJid: Map<string, string>;

  /** Message store for getMessage callback (stores full WAMessage with key) */
  messageStore?: Map<string, WAMessage>;
}

/**
 * Resolve LID to real JID for sending messages.
 *
 * LIDs (Linked Identifiers) are privacy-focused WhatsApp identifiers that
 * need to be resolved to real JIDs before sending.
 *
 * Resolution order:
 * 1. Check if it's self-chat LID -> use bot's own JID
 * 2. Check signalRepository.lidMapping (Baileys built-in)
 * 3. Check manual lidToJid mapping (from senderPn in received messages)
 * 4. Fail safe: throw error (prevents sending to wrong person)
 *
 * @param chatId - Target chat ID (may be LID)
 * @param sock - Baileys socket instance
 * @param lidMapper - LID mapping data
 * @returns Resolved JID
 * @throws Error if LID cannot be resolved
 */
export function resolveSendJid(
  chatId: string,
  sock: import("@whiskeysockets/baileys").WASocket,
  lidMapper: LidMapper
): string {
  // Not an LID - return as-is
  if (!isLid(chatId)) {
    return chatId;
  }

  // Self-chat LID -> convert to bot's own JID
  if (chatId === lidMapper.selfChatLid && lidMapper.myNumber) {
    return `${lidMapper.myNumber}@s.whatsapp.net`;
  }

  // Try signalRepository mapping (Baileys built-in)
  // Note: lidMapping may not exist on all Baileys versions - use safe access
  const signalRepo = sock.signalRepository as unknown as { lidMapping?: Map<string, string> } | undefined;
  const signalMapping = signalRepo?.lidMapping?.get(chatId);
  if (signalMapping) {
    return signalMapping;
  }

  // Try manual mapping (from senderPn field in received messages)
  const manualMapping = lidMapper.lidToJid.get(chatId);
  if (manualMapping) {
    return manualMapping;
  }

  // FAIL SAFE: Cannot resolve LID - don't send to unknown address
  log.error(`Cannot resolve LID: ${chatId}`);
  throw new Error("Cannot send to unknown LID - no mapping found");
}

/**
 * Send a WhatsApp message with proper LID resolution and tracking.
 *
 * @param sock - Baileys socket instance
 * @param msg - Message to send
 * @param lidMapper - LID mapping data
 * @param sentMessageIds - Set to track sent messages (prevents self-echo)
 * @returns Message ID
 *
 * @example
 * const { messageId } = await sendWhatsAppMessage(
 *   sock,
 *   { chatId: '1234567890@s.whatsapp.net', text: 'Hello!' },
 *   lidMapper,
 *   sentMessageIds
 * );
 */
export async function sendWhatsAppMessage(
  sock: import("@whiskeysockets/baileys").WASocket,
  msg: OutboundMessage,
  lidMapper: LidMapper,
  sentMessageIds: Set<string>
): Promise<{ messageId: string }> {
  if (!sock) {
    throw new Error("WhatsApp not connected");
  }

  // Resolve LID to real JID
  const targetJid = resolveSendJid(msg.chatId, sock, lidMapper);

  try {
    // Send composing indicator (typing...)
    try {
      await sock.sendPresenceUpdate("composing", targetJid);
    } catch {
      // Ignore presence errors
    }

    // Build message content with link preview support
    // Baileys will auto-generate link previews, but we need to ensure
    // matchedText is set even if preview fetch fails (network timeout, etc.)
    // This ensures URLs are always clickable/tappable in WhatsApp.
    const url = extractUrl(msg.text);
    const messageContent: { text: string; linkPreview?: WAUrlInfo } = { text: msg.text };

    if (url) {
      // Provide minimal linkPreview with matched-text to ensure URL is clickable.
      // Baileys will try to fetch full preview data (title, description, thumbnail),
      // but if that fails, the URL will still be tappable because matched-text is set.
      messageContent.linkPreview = {
        'canonical-url': url,
        'matched-text': url,
        title: '',  // Will be filled by Baileys if preview fetch succeeds
      };
    }

    // Send message
    const result = await sock.sendMessage(targetJid, messageContent);
    const messageId = result?.key?.id || "";
    const message = result?.message;

    // Track sent message to prevent processing it as incoming (self-echo prevention)
    if (messageId) {
      sentMessageIds.add(messageId);

      // CRITICAL: Store sent message for getMessage callback (enables retry on delivery failure)
      if (result && lidMapper.messageStore) {
        lidMapper.messageStore.set(messageId, result);
        // Auto-cleanup after 24 hours
        setTimeout(() => {
          lidMapper.messageStore?.delete(messageId);
        }, 24 * 60 * 60 * 1000);
      }

      // Auto-cleanup sent ID after 60 seconds
      setTimeout(() => {
        sentMessageIds.delete(messageId);
      }, 60000);
    }

    return { messageId };
  } catch (error) {
    log.error("sendMessage error:", error);
    throw error;
  }
}

/**
 * Send typing indicator to a chat.
 *
 * @param sock - Baileys socket instance
 * @param chatId - Target chat ID
 */
export async function sendTypingIndicator(
  sock: import("@whiskeysockets/baileys").WASocket,
  chatId: string
): Promise<void> {
  if (!sock) return;

  try {
    await sock.sendPresenceUpdate("composing", chatId);
  } catch {
    // Ignore presence errors
  }
}

/**
 * Stop typing indicator for a chat.
 * Sends "paused" presence to immediately clear the "typing..." indicator
 * instead of waiting for WhatsApp's built-in timeout (~15-25s).
 */
export async function stopTypingIndicator(
  sock: import("@whiskeysockets/baileys").WASocket,
  chatId: string
): Promise<void> {
  if (!sock) return;

  try {
    await sock.sendPresenceUpdate("paused", chatId);
  } catch {
    // Ignore presence errors
  }
}

/**
 * Send read receipt for a message.
 *
 * @param sock - Baileys socket instance
 * @param remoteJid - Chat JID
 * @param messageId - Message ID to mark as read
 * @param participant - Optional participant JID (for group messages)
 */
export async function sendReadReceipt(
  sock: import("@whiskeysockets/baileys").WASocket,
  remoteJid: string,
  messageId: string,
  participant?: string | null
): Promise<void> {
  try {
    await sock.readMessages([
      {
        remoteJid,
        id: messageId,
        participant,
        fromMe: false,
      },
    ]);
  } catch (err) {
    // Ignore read receipt errors - not critical
    log.warn(`Failed to send read receipt for ${messageId}:`, err);
  }
}

/**
 * Send a file (image or document) to WhatsApp.
 *
 * @param sock - Baileys socket instance
 * @param file - File to send
 * @param lidMapper - LID mapping data
 * @param sentMessageIds - Set to track sent messages
 * @returns Message ID
 */
export async function sendWhatsAppFile(
  sock: import("@whiskeysockets/baileys").WASocket,
  file: OutboundFile,
  lidMapper: LidMapper,
  sentMessageIds: Set<string>
): Promise<{ messageId: string }> {
  if (!sock) {
    throw new Error("WhatsApp not connected");
  }

  // Resolve LID to real JID
  const targetJid = resolveSendJid(file.chatId, sock, lidMapper);

  // Build payload based on file kind
  const caption = file.caption || undefined;
  const fileName = basename(file.filePath);

  let payload;
  if (file.kind === "image") {
    payload = { image: { url: file.filePath }, caption };
  } else if (file.kind === "audio") {
    payload = { audio: { url: file.filePath }, ptt: true };
  } else {
    payload = { document: { url: file.filePath }, mimetype: "application/octet-stream", caption, fileName };
  }

  try {
    // Send file
    const result = await sock.sendMessage(targetJid, payload);
    const messageId = result?.key?.id || "";
    const message = result?.message;

    // Track sent message to prevent self-echo
    if (messageId) {
      sentMessageIds.add(messageId);

      // Store in getMessage cache for retry capability
      if (result && lidMapper.messageStore) {
        lidMapper.messageStore.set(messageId, result);
        setTimeout(() => {
          lidMapper.messageStore?.delete(messageId);
        }, 24 * 60 * 60 * 1000);
      }

      // Cleanup sent ID after 60 seconds
      setTimeout(() => {
        sentMessageIds.delete(messageId);
      }, 60000);
    }

    return { messageId };
  } catch (error) {
    log.error("sendFile error:", error);
    throw error;
  }
}
