import type { Context } from 'telegraf';

const MAX_LENGTH = 4000;

/**
 * Split text into Telegram-safe chunks (≤ maxLen chars).
 * Code-block aware: never splits inside a triple-backtick fence.
 */
export function chunkText(text: string, maxLen = MAX_LENGTH): string[] {
  if (text.length <= maxLen) return [text];

  const chunks: string[] = [];
  let remaining = text;
  let inFence = false;
  let fenceLang = '';

  while (remaining.length > 0) {
    if (remaining.length <= maxLen) {
      chunks.push(remaining);
      break;
    }

    // Find a safe split point within maxLen
    let splitAt = maxLen;

    // Scan for code fences up to split point
    const slice = remaining.slice(0, splitAt);
    const fenceMatches = [...slice.matchAll(/```(\w*)/g)];
    for (const m of fenceMatches) {
      if (m[0] === '```' && inFence) {
        inFence = false;
        fenceLang = '';
      } else if (m[0].startsWith('```') && !inFence) {
        inFence = true;
        fenceLang = m[1] ?? '';
      }
    }

    if (inFence) {
      // Close the fence at chunk boundary
      const chunk = remaining.slice(0, splitAt) + '\n```';
      chunks.push(chunk);
      remaining = '```' + fenceLang + '\n' + remaining.slice(splitAt);
      inFence = true;
    } else {
      // Try to split on paragraph boundary
      const paraBreak = remaining.lastIndexOf('\n\n', splitAt);
      const lineBreak = remaining.lastIndexOf('\n', splitAt);
      splitAt =
        paraBreak > maxLen * 0.5 ? paraBreak : lineBreak > maxLen * 0.5 ? lineBreak : splitAt;

      chunks.push(remaining.slice(0, splitAt));
      remaining = remaining.slice(splitAt).trimStart();
    }
  }

  return chunks.filter((c) => c.trim().length > 0);
}

/**
 * Send multiple chunks as sequential Telegram messages.
 * If replyToMessageId is provided, the first chunk replies to that message.
 */
export async function sendChunked(
  ctx: Context,
  text: string,
  replyToMessageId?: number,
): Promise<void> {
  // Telegram rejects empty messages with 400 Bad Request. Skip early so the
  // caller can observe that nothing was sent and fall through to its error path.
  const trimmed = text.trim();
  if (!trimmed) return;

  const chunks = chunkText(trimmed).filter((c) => c.trim().length > 0);
  for (let i = 0; i < chunks.length; i++) {
    const replyParams =
      replyToMessageId && i === 0
        ? { reply_parameters: { message_id: replyToMessageId } }
        : undefined;
    await ctx.reply(chunks[i]!, replyParams);
  }
}

/**
 * Send an immediate placeholder, then stream updates by editing it.
 * Returns a function to call with the final complete text.
 */
export async function createStreamingReply(
  ctx: Context,
  placeholder: string,
): Promise<(finalText: string) => Promise<void>> {
  const sent = await ctx.reply(placeholder);
  const chatId = sent.chat.id;
  const msgId = sent.message_id;

  const lastEditText = placeholder;

  return async (finalText: string) => {
    const chunks = chunkText(finalText);
    if (chunks.length === 0) return;

    // Edit placeholder with first chunk
    try {
      const first = chunks[0]!;
      if (first !== lastEditText) {
        await ctx.telegram.editMessageText(chatId, msgId, undefined, first);
      }
    } catch {
      // Message may have been deleted or not modified
    }

    // Send remaining chunks as new messages
    for (let i = 1; i < chunks.length; i++) {
      await ctx.reply(chunks[i]!);
    }
  };
}
