import { lookupUsernameByPubkey } from "../directory.ts"; import { nip19 } from "@nostr/tools"; import config from "../config.ts"; const nostrUriRegex = /(nostr:|@)(nprofile|naddr|nevent|npub)[a-z0-9]+/g; export async function nostrUriToUrl(uri: string): Promise { const bech32 = uri.replace(/^(nostr:|@)/, ""); if (bech32.match(/^(naddr|nprofile|npub)/)) { try { const r = nip19.decode(bech32); let username; switch (r.type) { case "naddr": username = await lookupUsernameByPubkey(r.data.pubkey); if (username) return `/${bech32}`; break; case "nprofile": username = await lookupUsernameByPubkey(r.data.pubkey); if (username) return `/@${username}`; break; case "npub": username = await lookupUsernameByPubkey(r.data); if (username) return `/@${username}`; break; } } catch (e) { console.error(e); } } return `${config.njump_url}/${bech32}`; } export async function replaceNostrUris(markdown: string): Promise { const protectedRegex = /(`{3,}[\s\S]*?`{3,})|(`[^`]*`)|(
[\s\S]*?<\/pre>)|(https?:\/\/[^\s<>"']+)/gi;

  // Split text into segments: unprotected text and protected areas (code blocks, URLs)
  const segments: string[] = [];
  let lastIndex = 0;

  markdown.replace(protectedRegex, (match, _fencedCode, _inlineCode, _preTag, _url, index) => {
    segments.push(markdown.slice(lastIndex, index));
    segments.push(match);
    lastIndex = index + match.length;
    return match;
  });
  segments.push(markdown.slice(lastIndex));

  // Process each segment
  let result = '';
  for (let i = 0; i < segments.length; i++) {
    if (i % 2 === 1 || protectedRegex.test(segments[i])) {
      // Protected segment (code block or URL), leave unchanged
      result += segments[i];
    } else {
      // Unprotected text, replace URIs and handle markdown links
      result += await processUnprotectedText(segments[i]);
    }
  }

  return result;
}

async function processUnprotectedText(text: string): Promise {
  const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
  let lastIndex = 0;
  const parts: string[] = [];

  // Process markdown links first
  let match;
  while ((match = markdownLinkRegex.exec(text)) !== null) {
    const [fullMatch, linkText, target] = match;

    // Add text before the link
    parts.push(await replaceUrisInText(text.slice(lastIndex, match.index)));

    // Process the link target
    if (nostrUriRegex.test(target) && target.match(nostrUriRegex)![0] === target) {
      // Target is a Nostr URI, replace with resolved URL
      const resolvedUrl = await nostrUriToUrl(target);
      parts.push(`[${linkText}](${resolvedUrl})`);
    } else {
      // Not a Nostr URI, keep the original link
      parts.push(fullMatch);
    }

    lastIndex = match.index + fullMatch.length;
  }

  // Add any remaining text after the last link
  parts.push(await replaceUrisInText(text.slice(lastIndex)));

  return parts.join('');
}

async function replaceUrisInText(text: string): Promise {
  let modifiedText = text;
  const replacements: { start: number; end: number; replacement: string }[] = [];

  // Collect all replacements for bare Nostr URIs
  let match;
  while ((match = nostrUriRegex.exec(modifiedText)) !== null) {
    const fullUri = match[0];
    const url = await nostrUriToUrl(fullUri);
    const linkTitle = cleanUriForTitle(fullUri);
    const markdownLink = `[${linkTitle}](${url})`;
    replacements.push({
      start: match.index,
      end: match.index + fullUri.length,
      replacement: markdownLink,
    });
  }

  // Apply replacements from right to left to avoid index shifting
  for (let i = replacements.length - 1; i >= 0; i--) {
    const { start, end, replacement } = replacements[i];
    modifiedText = modifiedText.slice(0, start) + replacement + modifiedText.slice(end);
  }

  return modifiedText;
}

function cleanUriForTitle(uri: string): string {
  // Remove "nostr:" prefix, keep "@" for the title
  return uri.startsWith('nostr:') ? uri.replace(/^nostr:/, '') : uri;
}