export interface UrlPreview {
  href: string;
  display: string;
  hostname: string;
  path: string;
}

const MAX_LINKS = 4;
const MAX_URL_LENGTH = 2048;
const TRAILING_PUNCT = /[.,;:!?'"~…—–-]+$/u;

function countChar(text: string, char: string): number {
  let count = 0;
  for (const c of text) if (c === char) count += 1;
  return count;
}

function trimUrl(raw: string): string {
  let out = raw;
  let changed = true;
  while (changed && out.length > 0) {
    changed = false;
    const stripped = out.replace(TRAILING_PUNCT, '');
    if (stripped !== out) {
      out = stripped;
      changed = true;
    }
    // Strip a trailing closer only when it has no matching opener left in
    // the URL itself, so "(https://x/a_(b))" keeps the inner pair and drops
    // the outer markdown/paren closer.
    const pairs: Array<[string, string]> = [
      ['(', ')'],
      ['[', ']'],
      ['{', '}']
    ];
    for (const [open, close] of pairs) {
      if (out.endsWith(close) && countChar(out, close) > countChar(out, open)) {
        out = out.slice(0, -1);
        changed = true;
      }
    }
    if (out.endsWith('>')) {
      out = out.slice(0, -1);
      changed = true;
    }
  }
  return out;
}

export interface LinkSpan {
  start: number;
  end: number;
  href: string;
}

export function linkSpans(text: string): LinkSpan[] {
  if (!text || !text.includes('http')) return [];
  const out: LinkSpan[] = [];
  for (const match of text.matchAll(/https?:\/\/[^\s<>"'`\]]+/gi)) {
    const trimmed = trimUrl(match[0].slice(0, MAX_URL_LENGTH));
    if (trimmed.length < 8) continue;
    let parsed: URL;
    try {
      parsed = new URL(trimmed);
    } catch {
      continue;
    }
    if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') continue;
    out.push({ start: match.index, end: match.index + trimmed.length, href: parsed.toString() });
  }
  return out;
}

/**
 * Find http(s) URLs in message text without fetching anything: rendering is
 * purely local (hostname/path/link card) so no third-party request can leak
 * reader IPs. Callers MUST NOT auto-fetch link targets.
 */
export function findUrls(text: string): UrlPreview[] {
  const out: UrlPreview[] = [];
  const seen = new Set<string>();
  for (const span of linkSpans(text)) {
    if (out.length >= MAX_LINKS) break;
    const parsed = new URL(span.href);
    if (parsed.hostname === 'matrix.to' || seen.has(span.href)) continue;
    seen.add(span.href);
    const trimmed = text.slice(span.start, span.end);
    const rest = `${parsed.pathname}${parsed.search}${parsed.hash}`;
    out.push({
      href: span.href,
      display: trimmed.length > 72 ? `${trimmed.slice(0, 69)}…` : trimmed,
      hostname: parsed.hostname,
      path: rest && rest !== '/' ? (rest.length > 48 ? `${rest.slice(0, 45)}…` : rest) : ''
    });
  }
  return out;
}
