/**
 * Shared utilities for channel adapters.
 */
import type { Attachment, InboundMessage } from './types.js';
import type { PairingManager } from './pairing.js';

/**
 * Split a long message into chunks that respect a platform's character limit.
 * Prefers splitting at natural boundaries:
 * paragraph breaks, newlines, sentence endings, spaces, then hard-cuts.
 * When code fences are split, chunks are balanced by closing/re-opening fences.
 */
export function splitMessage(text: string, maxLength: number): string[] {
  const rawChunks: string[] = [];
  let remaining = text.trim();

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

    const minAcceptable = Math.floor(maxLength * 0.45);
    const window = remaining.slice(0, maxLength);
    const paragraphSplit = window.lastIndexOf('\n\n');
    const newlineSplit = window.lastIndexOf('\n');
    const sentenceSplit = window.search(/[\.\!\?;:](?!.*[\.\!\?;:])/);
    const spaceSplit = window.lastIndexOf(' ');
    const sentenceIndex = sentenceSplit >= 0 ? sentenceSplit + 1 : -1;

    const preferred = [paragraphSplit + 1, newlineSplit, sentenceIndex, spaceSplit];
    let splitIndex = maxLength;
    for (const candidate of preferred) {
      if (Number.isInteger(candidate) && candidate > 0 && candidate >= minAcceptable) {
        splitIndex = candidate;
        break;
      }
    }

    rawChunks.push(remaining.slice(0, splitIndex));
    remaining = remaining.slice(splitIndex).trimStart();
  }

  return rebalanceMarkdownCodeFences(rawChunks);
}

function rebalanceMarkdownCodeFences(chunks: string[]): string[] {
  const balanced: string[] = [];
  let codeFenceOpen = false;
  let rawParity = 0;

  for (let i = 0; i < chunks.length; i++) {
    const rawChunk = chunks[i];
    let chunk = rawChunk;

    if (codeFenceOpen && !chunk.startsWith('```')) {
      chunk = `\`\`\`\n${chunk}`;
    }

    const rawFenceCount = (rawChunk.match(/```/g) ?? []).length;
    rawParity = (rawParity + rawFenceCount) % 2;
    const hasNextChunk = i < chunks.length - 1;

    if (rawParity === 1 && hasNextChunk) {
      chunk = `${chunk}\n\`\`\``;
      codeFenceOpen = true;
    } else {
      codeFenceOpen = false;
    }

    balanced.push(chunk);
  }

  if (rawParity === 1 && balanced.length > 0) {
    balanced[balanced.length - 1] = `${balanced[balanced.length - 1]}\n\`\`\``;
  }

  return balanced;
}

/**
 * Normalize reset command variants to a canonical text form.
 * Returns '!reset' for recognized variants, otherwise returns the original text.
 */
export function normalizeResetCommandText(text: string): string {
  if (text === '!reset' || text === 'reset') {
    return '!reset';
  }
  return text;
}

/** Check whether a value is allowed by an optional allowlist (empty/missing list means allow-all). */
export function isAllowedByAllowlist(value: string, allowlist?: string[]): boolean {
  if (!allowlist || allowlist.length === 0) {
    return true;
  }
  return allowlist.includes(value);
}

/**
 * Returns true when mention gating should ignore the message.
 * `requireMention` defaults per adapter; if enabled, the message must mention the bot.
 */
export function shouldIgnoreForMissingMention(params: {
  requireMention: boolean | undefined;
  defaultRequireMention: boolean;
  mentionsBot: boolean;
}): boolean {
  const needsMention = params.requireMention ?? params.defaultRequireMention;
  return needsMention && !params.mentionsBot;
}

interface PairingGateParams {
  pairingManager?: PairingManager;
  channel: InboundMessage['channel'];
  senderId: string;
  text: string;
  /** True for trusted senders/channels that may bypass pairing. */
  isTrusted: boolean;
  /** Optional side effect when a pairing code is accepted. */
  onPaired?: () => Promise<void> | void;
}

/**
 * Shared pairing gate for channel adapters.
 * Returns true when the inbound message should continue to normal processing.
 */
export async function allowTrustedOrPairedSender(params: PairingGateParams): Promise<boolean> {
  const pm = params.pairingManager;
  if (params.isTrusted) {
    return true;
  }
  if (!pm?.enabled) {
    return false;
  }
  if (pm.isApproved(params.channel, params.senderId)) {
    return true;
  }
  const code = params.text.trim();
  if (!code || !pm.validateCode(params.channel, params.senderId, code)) {
    return false;
  }
  await params.onPaired?.();
  return false;
}

interface ResetMessageParams {
  id: string;
  channel: InboundMessage['channel'];
  senderId: string;
  senderName?: string;
  timestamp?: number;
  attachments?: Attachment[];
}

/** Build a normalized inbound reset command message. */
export function buildResetInboundMessage(params: ResetMessageParams): InboundMessage {
  return {
    id: params.id,
    channel: params.channel,
    senderId: params.senderId,
    senderName: params.senderName,
    text: '!reset',
    timestamp: params.timestamp ?? Date.now(),
    metadata: { isCommand: true, command: 'reset' },
    ...(params.attachments && params.attachments.length > 0 ? { attachments: params.attachments } : {}),
  };
}
