<script lang="ts">
  import { marked } from 'marked';
  import { sanitizeMarkdown } from '../../services/security/sanitizer.js';
  import { findNIP21Links, parseNIP21 } from '../../services/nostr/nip21-parser.js';
  import { nip19 } from 'nostr-tools';
  import ProfileBadge from '../layout/ProfileBadge.svelte';
  import EmbeddedEvent from './EmbeddedEvent.svelte';
  import { mountComponent } from './mount-component-action.js';
  import { fetchEmojiSet, resolveEmojiShortcode } from '../../services/nostr/nip30-emoji.js';
  import type { NostrEvent } from '../../types/nostr.js';

  interface Props {
    content: string;
    event?: NostrEvent; // Optional event for emoji resolution
  }

  let { content, event }: Props = $props();
  let containerRef = $state<HTMLElement | null>(null);
  let emojiUrls = $state<Map<string, string>>(new Map());

  // Extract pubkey from npub or nprofile
  function getPubkeyFromNIP21(parsed: ReturnType<typeof parseNIP21>): string | null {
    if (!parsed) return null;

    try {
      // parsed.data is the bech32 string (e.g., "npub1..." or "nprofile1...")
      // We need to decode it to get the actual pubkey
      const decoded = nip19.decode(parsed.data);
      
      if (parsed.type === 'npub') {
        // npub decodes directly to pubkey (hex string)
        if (decoded.type === 'npub') {
          return String(decoded.data);
        }
      } else if (parsed.type === 'nprofile') {
        // nprofile decodes to object with pubkey property
        if (decoded.type === 'nprofile' && decoded.data && typeof decoded.data === 'object' && 'pubkey' in decoded.data) {
          return String(decoded.data.pubkey);
        }
      }
    } catch (error) {
      console.error('Error decoding NIP-21 URI:', error, parsed);
      return null;
    }

    return null;
  }

  // Extract event identifier from note, nevent, or naddr
  // Returns the bech32 string (note1..., nevent1..., naddr1...) which EmbeddedEvent can decode
  function getEventIdFromNIP21(parsed: ReturnType<typeof parseNIP21>): string | null {
    if (!parsed) return null;

    if (parsed.type === 'note' || parsed.type === 'nevent' || parsed.type === 'naddr') {
      // Return the bech32 string - EmbeddedEvent will decode it
      return parsed.data;
    }

    return null;
  }

  // Escape HTML to prevent XSS
  function escapeHtml(text: string): string {
    return text
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  // Convert plain media URLs (images, videos, audio) to HTML tags
  function convertMediaUrls(text: string): string {
    // Match media URLs (http/https URLs ending in media extensions)
    const imageExtensions = /\.(png|jpg|jpeg|gif|webp|svg|bmp|ico)(\?[^\s<>"']*)?$/i;
    const videoExtensions = /\.(mp4|webm|ogg|mov|avi|mkv)(\?[^\s<>"']*)?$/i;
    const audioExtensions = /\.(mp3|wav|ogg|flac|aac|m4a)(\?[^\s<>"']*)?$/i;
    const urlPattern = /https?:\/\/[^\s<>"']+/g;
    
    let result = text;
    const matches: Array<{ url: string; index: number; endIndex: number; type: 'image' | 'video' | 'audio' }> = [];
    
    // Find all URLs
    let match;
    while ((match = urlPattern.exec(text)) !== null) {
      const url = match[0];
      const index = match.index;
      const endIndex = index + url.length;
      
      // Check if this URL is already in markdown or HTML
      const before = text.substring(Math.max(0, index - 10), index);
      const after = text.substring(endIndex, Math.min(text.length, endIndex + 10));
      
      // Skip if it's already in markdown or HTML tags
      if (before.includes('![') || before.includes('<img') || before.includes('<video') || before.includes('<audio') || 
          after.startsWith('](') || after.startsWith('</img>') || after.startsWith('</video>') || after.startsWith('</audio>')) {
        continue;
      }
      
      // Determine media type
      if (imageExtensions.test(url)) {
        matches.push({ url, index, endIndex, type: 'image' });
      } else if (videoExtensions.test(url)) {
        matches.push({ url, index, endIndex, type: 'video' });
      } else if (audioExtensions.test(url)) {
        matches.push({ url, index, endIndex, type: 'audio' });
      }
    }
    
    // Replace from end to start to preserve indices
    for (let i = matches.length - 1; i >= 0; i--) {
      const { url, index, endIndex, type } = matches[i];
      const escapedUrl = escapeHtml(url);
      
      if (type === 'image') {
        result = result.substring(0, index) + `<img src="${escapedUrl}" alt="" loading="lazy" />` + result.substring(endIndex);
      } else if (type === 'video') {
        result = result.substring(0, index) + `<video src="${escapedUrl}" controls preload="none" style="max-width: 100%; max-height: 500px;"></video>` + result.substring(endIndex);
      } else if (type === 'audio') {
        result = result.substring(0, index) + `<audio src="${escapedUrl}" controls preload="none" style="width: 100%;"></audio>` + result.substring(endIndex);
      }
    }
    
    return result;
  }

  // Resolve custom emojis in content
  async function resolveContentEmojis(text: string): Promise<void> {
    if (!event) return; // Need event to resolve emojis
    
    // Find all :shortcode: patterns
    const emojiPattern = /:([a-zA-Z0-9_+-]+):/g;
    const matches: Array<{ shortcode: string; fullMatch: string }> = [];
    let match;
    while ((match = emojiPattern.exec(text)) !== null) {
      const shortcode = match[1];
      const fullMatch = match[0];
      if (!matches.find(m => m.fullMatch === fullMatch)) {
        matches.push({ shortcode, fullMatch });
      }
    }
    
    if (matches.length === 0) return;
    
    // Collect all pubkeys to check: event author and p tags
    const pubkeysToCheck = new Set<string>();
    pubkeysToCheck.add(event.pubkey);
    for (const tag of event.tags) {
      if (tag[0] === 'p' && tag[1]) {
        pubkeysToCheck.add(tag[1]);
      }
    }
    
    // Resolve emojis - first try specific pubkeys, then search broadly
    const resolvedUrls = new Map<string, string>();
    for (const { shortcode, fullMatch } of matches) {
      // First try specific pubkeys (event author, p tags)
      let url = await resolveEmojiShortcode(shortcode, Array.from(pubkeysToCheck), false);
      
      // If not found, search broadly across all emoji packs
      if (!url) {
        url = await resolveEmojiShortcode(shortcode, [], true);
      }
      
      if (url) {
        resolvedUrls.set(fullMatch, url);
      }
    }
    
    emojiUrls = resolvedUrls;
  }

  // Replace emoji shortcodes with images in text
  function replaceEmojis(text: string): string {
    let processed = text;
    
    // Replace from end to start to preserve indices
    const sortedEntries = Array.from(emojiUrls.entries()).sort((a, b) => {
      const indexA = processed.lastIndexOf(a[0]);
      const indexB = processed.lastIndexOf(b[0]);
      return indexB - indexA; // Sort by last index descending
    });
    
    for (const [shortcode, url] of sortedEntries) {
      const escapedUrl = escapeHtml(url);
      const escapedShortcode = escapeHtml(shortcode);
      // Replace with img tag, preserving the shortcode as alt text
      const imgTag = `<img src="${escapedUrl}" alt="${escapedShortcode}" class="emoji-inline" style="display: inline-block; width: 1.2em; height: 1.2em; vertical-align: middle;" />`;
      processed = processed.replaceAll(shortcode, imgTag);
    }
    
    return processed;
  }

  // Process content: replace nostr URIs with HTML span elements and convert media URLs
  function processContent(text: string): string {
    // First, replace emoji shortcodes with images if resolved
    let processed = replaceEmojis(text);
    
    // Then, convert plain media URLs (images, videos, audio) to HTML tags
    processed = convertMediaUrls(processed);
    
    // Find all NIP-21 links (nostr:npub, nostr:nprofile, nostr:nevent, etc.)
    const links = findNIP21Links(processed);
    
    // Separate into profile links and event links
    const profileLinks = links.filter(link => 
      link.parsed.type === 'npub' || link.parsed.type === 'nprofile'
    );

    const eventLinks = links.filter(link =>
      link.parsed.type === 'note' || link.parsed.type === 'nevent' || link.parsed.type === 'naddr'
    );

    // Replace event links with HTML div elements (for block-level display)
    // Process from end to start to preserve indices
    for (let i = eventLinks.length - 1; i >= 0; i--) {
      const link = eventLinks[i];
      const eventId = getEventIdFromNIP21(link.parsed);
      if (eventId) {
        // Escape event ID to prevent XSS
        const escapedEventId = escapeHtml(eventId);
        // Create a div element for embedded event cards (block-level)
        const div = `<div data-nostr-event data-event-id="${escapedEventId}"></div>`;
        processed = 
          processed.slice(0, link.start) + 
          div + 
          processed.slice(link.end);
      }
    }

    // Replace profile links with HTML span elements that have data attributes
    // Process from end to start to preserve indices
    for (let i = profileLinks.length - 1; i >= 0; i--) {
      const link = profileLinks[i];
      const pubkey = getPubkeyFromNIP21(link.parsed);
      if (pubkey) {
        // Escape pubkey to prevent XSS (though pubkeys should be safe hex strings)
        const escapedPubkey = escapeHtml(pubkey);
        // Create a span element that will survive markdown rendering
        // Markdown won't process HTML tags, so this will remain as-is
        const span = `<span data-nostr-profile data-pubkey="${escapedPubkey}"></span>`;
        processed = 
          processed.slice(0, link.start) + 
          span + 
          processed.slice(link.end);
      }
    }

    return processed;
  }

  // Configure marked once - ensure images are rendered and HTML is preserved
  marked.setOptions({
    breaks: true, // Convert line breaks to <br>
    gfm: true, // GitHub Flavored Markdown
    silent: false // Don't suppress errors
    // HTML tags (like <img>) pass through by default in marked
  });

  // Resolve emojis when content or event changes
  $effect(() => {
    if (content && event) {
      // Run async resolution without blocking
      resolveContentEmojis(content).catch(err => {
        console.warn('Error resolving content emojis:', err);
      });
    } else {
      emojiUrls = new Map();
    }
  });

  // Render markdown to HTML
  function renderMarkdown(text: string): string {
    if (!content) return '';
    
    const processed = processContent(content);

    // Render markdown - this should convert ![alt](url) to <img src="url" alt="alt">
    let html: string;
    try {
      html = marked.parse(processed) as string;
    } catch (error) {
      console.error('Marked parsing error:', error);
      return processed; // Fallback to raw text if parsing fails
    }
    
    // Sanitize HTML (but preserve our data attributes and image src)
    const sanitized = sanitizeMarkdown(html);
    
    return sanitized;
  }

  const renderedHtml = $derived(renderMarkdown(content));

  // Mount ProfileBadge components after rendering
  function mountProfileBadges() {
    if (!containerRef) return;
    
    // Find all profile placeholders and mount ProfileBadge components
    const placeholders = containerRef.querySelectorAll('[data-nostr-profile]:not([data-mounted])');
    
    if (placeholders.length > 0) {
      console.debug(`Mounting ${placeholders.length} ProfileBadge components`);
      
      placeholders.forEach((placeholder) => {
        const pubkey = placeholder.getAttribute('data-pubkey');
        if (pubkey && /^[0-9a-f]{64}$/i.test(pubkey)) {
          placeholder.setAttribute('data-mounted', 'true');
          
          try {
            // Clear and mount component
            placeholder.innerHTML = '';
            // Use inline mode for profile badges in markdown content
            const instance = mountComponent(placeholder as HTMLElement, ProfileBadge as any, { pubkey, inline: true });
            
            if (!instance) {
              console.warn('ProfileBadge mount returned null', { pubkey });
              // Fallback
              try {
                const npub = nip19.npubEncode(pubkey);
                placeholder.textContent = npub.slice(0, 12) + '...';
              } catch {
                placeholder.textContent = pubkey.slice(0, 12) + '...';
              }
            }
          } catch (error) {
            console.error('Error mounting ProfileBadge:', error, { pubkey });
            // Show fallback
            try {
              const npub = nip19.npubEncode(pubkey);
              placeholder.textContent = npub.slice(0, 12) + '...';
            } catch {
              placeholder.textContent = pubkey.slice(0, 12) + '...';
            }
          }
        } else if (pubkey) {
          console.warn('Invalid pubkey format:', pubkey);
          placeholder.textContent = pubkey.slice(0, 12) + '...';
        }
      });
    }
  }

  // Mount EmbeddedEvent components after rendering
  function mountEmbeddedEvents() {
    if (!containerRef) return;
    
    // Find all event placeholders and mount EmbeddedEvent components
    const placeholders = containerRef.querySelectorAll('[data-nostr-event]:not([data-mounted])');
    
    if (placeholders.length > 0) {
      console.debug(`Mounting ${placeholders.length} EmbeddedEvent components`);
      
      placeholders.forEach((placeholder) => {
        const eventId = placeholder.getAttribute('data-event-id');
        if (eventId) {
          placeholder.setAttribute('data-mounted', 'true');
          
          try {
            // Clear and mount component
            placeholder.innerHTML = '';
            // Mount EmbeddedEvent component - it will decode and fetch the event
            const instance = mountComponent(placeholder as HTMLElement, EmbeddedEvent as any, { eventId });
            
            if (!instance) {
              console.warn('EmbeddedEvent mount returned null', { eventId });
              // Fallback: show the event ID
              placeholder.textContent = eventId.slice(0, 20) + '...';
            }
          } catch (error) {
            console.error('Error mounting EmbeddedEvent:', error, { eventId });
            // Show fallback
            placeholder.textContent = eventId.slice(0, 20) + '...';
          }
        }
      });
    }
  }

  $effect(() => {
    if (!containerRef || !renderedHtml) return;

    // Use requestAnimationFrame + setTimeout to ensure DOM is ready
    const frameId = requestAnimationFrame(() => {
      const timeoutId = setTimeout(() => {
        mountProfileBadges();
        mountEmbeddedEvents();
      }, 150);

      return () => clearTimeout(timeoutId);
    });

    return () => cancelAnimationFrame(frameId);
  });

  // Also use MutationObserver to catch any placeholders added later
  $effect(() => {
    if (!containerRef) return;

    const observer = new MutationObserver(() => {
      mountProfileBadges();
      mountEmbeddedEvents();
    });

    observer.observe(containerRef, {
      childList: true,
      subtree: true
    });

    return () => observer.disconnect();
  });
</script>

<div 
  bind:this={containerRef}
  class="markdown-content prose prose-sm dark:prose-invert max-w-none"
>
  {@html renderedHtml}
</div>

<style>
  :global(.markdown-content) {
    word-wrap: break-word;
    overflow-wrap: break-word;
  }

  :global(.markdown-content img) {
    max-width: 100%;
    height: auto;
    border-radius: 0.25rem;
    margin: 0.5rem 0;
    display: block;
    visibility: visible !important;
    opacity: 1 !important;
    /* Content images should be prominent - no grayscale filters */
    filter: none !important;
  }

  :global(.markdown-content img.emoji-inline) {
    display: inline-block;
    margin: 0;
    vertical-align: middle;
    /* Inline emojis should have grayscale filter like other emojis */
    filter: grayscale(100%) sepia(15%) hue-rotate(200deg) saturate(60%) brightness(0.95);
  }

  :global(.dark .markdown-content img.emoji-inline) {
    filter: grayscale(100%) sepia(20%) hue-rotate(200deg) saturate(70%) brightness(0.9);
  }

  :global(.markdown-content video) {
    max-width: 100%;
    height: auto;
    border-radius: 0.25rem;
    margin: 0.5rem 0;
    /* Content videos should be prominent - no grayscale filters */
    filter: none !important;
  }

  :global(.markdown-content audio) {
    width: 100%;
    margin: 0.5rem 0;
    /* Content audio should be prominent - no grayscale filters */
    filter: none !important;
  }

  :global(.markdown-content a) {
    color: var(--fog-accent, #64748b);
    text-decoration: underline;
  }

  :global(.markdown-content a:hover) {
    text-decoration: none;
  }

  :global(.markdown-content code) {
    background: var(--fog-border, #e5e7eb);
    padding: 0.125rem 0.25rem;
    border-radius: 0.25rem;
    font-size: 0.875em;
  }

  :global(.dark .markdown-content code) {
    background: var(--fog-dark-border, #374151);
  }

  :global(.markdown-content pre) {
    background: var(--fog-border, #e5e7eb);
    padding: 1rem;
    border-radius: 0.5rem;
    overflow-x: auto;
    margin: 0.5rem 0;
  }

  :global(.dark .markdown-content pre) {
    background: var(--fog-dark-border, #374151);
  }

  :global(.markdown-content pre code) {
    background: transparent;
    padding: 0;
  }

  :global(.markdown-content blockquote) {
    border-left: 4px solid var(--fog-border, #e5e7eb);
    padding-left: 1rem;
    margin: 0.5rem 0;
    color: var(--fog-text-light, #6b7280);
  }

  :global(.dark .markdown-content blockquote) {
    border-color: var(--fog-dark-border, #374151);
    color: var(--fog-dark-text-light, #9ca3af);
  }

  /* Profile badges in markdown content should align with text baseline */
  :global(.markdown-content [data-nostr-profile]),
  :global(.markdown-content .profile-badge) {
    vertical-align: middle;
    display: inline-flex;
    align-items: center;
  }

  /* Embedded events should be block-level */
  :global(.markdown-content [data-nostr-event]) {
    display: block;
    margin: 1rem 0;
  }
</style>
