<script lang="ts">
  import CardHeader from '../../components/layout/CardHeader.svelte';
  import ProfileBadge from '../../components/layout/ProfileBadge.svelte';
  import EventMenu from '../../components/EventMenu.svelte';
  import MarkdownRenderer from '../../components/content/MarkdownRenderer.svelte';
  import MediaAttachments from '../../components/content/MediaAttachments.svelte';
  import MetadataCard from '../../components/content/MetadataCard.svelte';
  import ReplyContext from '../../components/content/ReplyContext.svelte';
  import QuotedContext from '../../components/content/QuotedContext.svelte';
  import ReferencedEventPreview from '../../components/content/ReferencedEventPreview.svelte';
  import FeedReactionButtons from '../reactions/FeedReactionButtons.svelte';
  import MediaViewer from '../../components/content/MediaViewer.svelte';
  import CommentForm from '../comments/CommentForm.svelte';
  import PollCard from '../../components/content/PollCard.svelte';
  import type { NostrEvent } from '../../types/nostr.js';
  import { getKindInfo, KIND } from '../../types/kind-lookup.js';
  import { stripMarkdown } from '../../services/text-utils.js';
  import { isBookmarked } from '../../services/user-actions.js';
  import { sessionManager } from '../../services/auth/session-manager.js';
  import { findNIP21Links, parseNIP21 } from '../../services/nostr/nip21-parser.js';
  import { nip19 } from 'nostr-tools';
  import { onMount } from 'svelte';
  import { getEventLink } from '../../services/event-links.js';
  import { goto } from '$app/navigation';
  import IconButton from '../../components/ui/IconButton.svelte';
  import { page } from '$app/stores';
  import { keyboardShortcuts } from '../../services/keyboard-shortcuts.js';

  interface Props {
    post: NostrEvent;
    fullView?: boolean; // If true, show full content (markdown, media, profile pics, reactions)
    preloadedReactions?: NostrEvent[]; // Pre-loaded reactions to avoid duplicate fetches
    parentEvent?: NostrEvent; // Optional parent event if already loaded
    quotedEvent?: NostrEvent; // Optional quoted event if already loaded
    hideTitle?: boolean; // If true, don't render the title (useful when title is rendered elsewhere)
    preloadedReferencedEvent?: NostrEvent | null; // Preloaded referenced event from e/a/q tags
  }

  let { post, fullView = false, preloadedReactions, parentEvent: providedParentEvent, quotedEvent: providedQuotedEvent, hideTitle = false, preloadedReferencedEvent }: Props = $props();
  
  // Check if this event is bookmarked (async, so we use state)
  // Only check if user is logged in
  let bookmarked = $state(false);
  const isLoggedIn = $derived(sessionManager.isLoggedIn());
  
  // Collapse state for feed view
  let isExpanded = $state(false);
  let shouldCollapse = $state(false);
  let cardElement: HTMLElement | null = $state(null);
  let contentElement: HTMLElement | null = $state(null);
  
  // Reply state
  let showReplyForm = $state(false);
  
  // Keyboard shortcuts - register when component is focused
  let isFocused = $state(false);
  
  onMount(() => {
    // Register keyboard shortcuts for this post when it's focused
    const unregisterShortcuts = keyboardShortcuts.register('r', (e) => {
      if (isFocused && isLoggedIn && !showReplyForm) {
        e.preventDefault();
        showReplyForm = true;
        return false;
      }
    });
    
    return () => {
      unregisterShortcuts();
    };
  });
  
  // Media kinds that should auto-render media (except on /feed)
  const MEDIA_KINDS: number[] = [KIND.PICTURE_NOTE, KIND.VIDEO_NOTE, KIND.SHORT_VIDEO_NOTE, KIND.VOICE_NOTE, KIND.VOICE_REPLY, KIND.FILE_METADATA];
  const isMediaKind = $derived(MEDIA_KINDS.includes(post.kind));
  const isOnFeedPage = $derived($page.url.pathname === '/feed');
  const isOnEventPage = $derived($page.url.pathname.startsWith('/event/'));
  const shouldAutoRenderMedia = $derived(isMediaKind && !isOnFeedPage);
  
  // Check if card should be collapsed (only in feed view)
  let isMounted = $state(true);
  
  // Don't collapse media kinds when they should auto-render (they need full space for images/videos)
  const shouldNeverCollapse = $derived(fullView || shouldAutoRenderMedia);
  
  $effect(() => {
    if (shouldNeverCollapse) {
      shouldCollapse = false;
      return;
    }
    
    isMounted = true;
    
    // Wait for content to render, then check height
    let timeoutId: ReturnType<typeof setTimeout> | null = null;
    let rafId1: number | null = null;
    let rafId2: number | null = null;
    
    const checkHeight = () => {
      if (!isMounted || !cardElement) return;
      
      // Measure the full card height (using scrollHeight to get full content height)
      const cardHeight = cardElement.scrollHeight;
      shouldCollapse = cardHeight > 500;
    };
    
    // Check after content is rendered
    timeoutId = setTimeout(() => {
      if (!isMounted) return;
      rafId1 = requestAnimationFrame(() => {
        if (!isMounted) return;
        rafId2 = requestAnimationFrame(checkHeight);
      });
    }, 200);
    
    return () => {
      isMounted = false;
      if (timeoutId) clearTimeout(timeoutId);
      if (rafId1) cancelAnimationFrame(rafId1);
      if (rafId2) cancelAnimationFrame(rafId2);
    };
  });
  
  function toggleExpand() {
    isExpanded = !isExpanded;
  }
  
  $effect(() => {
    let cancelled = false;
    
    if (isLoggedIn) {
      isBookmarked(post.id).then(b => {
        if (!cancelled) {
        bookmarked = b;
        }
      });
    } else {
      bookmarked = false;
    }
    
    return () => {
      cancelled = true;
    };
  });

  function getRelativeTime(): string {
    const now = Math.floor(Date.now() / 1000);
    const diff = now - post.created_at;
    const hours = Math.floor(diff / 3600);
    const days = Math.floor(diff / 86400);
    const minutes = Math.floor(diff / 60);

    if (days > 0) return `${days}d ago`;
    if (hours > 0) return `${hours}h ago`;
    if (minutes > 0) return `${minutes}m ago`;
    return 'just now';
  }

  function getClientName(): string | null {
    const clientTag = post.tags.find((t) => t[0] === 'client');
    return clientTag?.[1] || null;
  }

  function getTitle(): string | null {
    const titleTag = post.tags.find((t) => t[0] === 'title');
    const title = titleTag?.[1];
    return title && title.trim() ? title.trim() : null;
  }

  function getSummary(): string | null {
    const summaryTag = post.tags.find((t) => t[0] === 'summary');
    const summary = summaryTag?.[1];
    return summary && summary.trim() ? summary.trim() : null;
  }

  function getPlaintextContent(): string {
    // Plaintext only (no markdown/images) - keep full length
    return stripMarkdown(post.content);
  }

  // Check if this is a highlight event with context tag
  function hasContextTag(): boolean {
    if (post.kind !== KIND.HIGHLIGHTED_ARTICLE) return false;
    return post.tags.some(t => t[0] === 'context' && t[1]);
  }

  // Get highlight content to highlight (for highlight events with context)
  function getHighlightContent(): string | null {
    if (!hasContextTag()) return null;
    return post.content.trim() || null;
  }

  // Parse NIP-21 links and create segments for rendering
  interface ContentSegment {
    type: 'text' | 'profile' | 'event' | 'url' | 'wikilink' | 'hashtag' | 'greentext';
    content: string; // Display text (without nostr: prefix for links)
    pubkey?: string; // For profile badges
    eventId?: string; // For event links (bech32 or hex)
    url?: string; // For regular HTTP/HTTPS URLs
    wikilink?: string; // For wikilink d-tag
    hashtag?: string; // For hashtag topic name
  }

  // Process text to detect greentext (lines starting with >)
  function processGreentext(text: string): ContentSegment[] {
    const lines = text.split('\n');
    const segments: ContentSegment[] = [];
    
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      const trimmed = line.trimStart();
      
      // Check if line starts with > (greentext)
      if (trimmed.startsWith('>') && trimmed.length > 1) {
        // Preserve leading whitespace before >
        const leadingWhitespace = line.substring(0, line.length - trimmed.length);
        segments.push({
          type: 'greentext',
          content: leadingWhitespace + trimmed
        });
      } else {
        // Regular text line
        segments.push({
          type: 'text',
          content: line
        });
      }
      
      // Add newline between lines (except for last line)
      if (i < lines.length - 1) {
        segments.push({
          type: 'text',
          content: '\n'
        });
      }
    }
    
    return segments;
  }

  function parseContentWithNIP21Links(): ContentSegment[] {
    const plaintext = getPlaintextContent();
    const links = findNIP21Links(plaintext);
    const segments: ContentSegment[] = [];
    
    let lastIndex = 0;
    
    for (const link of links) {
      // Add text before the link
      if (link.start > lastIndex) {
        segments.push({
          type: 'text',
          content: plaintext.slice(lastIndex, link.start)
        });
      }
      
      // Add the link as appropriate segment
      if (link.parsed.type === 'npub' || link.parsed.type === 'nprofile') {
        // Extract pubkey from bech32
        let pubkey: string | null = null;
        try {
          const decoded = nip19.decode(link.parsed.data);
          if (decoded.type === 'npub') {
            pubkey = decoded.data as string;
          } else if (decoded.type === 'nprofile') {
            pubkey = (decoded.data as any).pubkey;
          }
        } catch (e) {
          // If decoding fails, just show as text
          segments.push({
            type: 'text',
            content: link.uri
          });
          lastIndex = link.end;
          continue;
        }
        
        if (pubkey) {
          segments.push({
            type: 'profile',
            content: link.uri,
            pubkey
          });
        } else {
          segments.push({
            type: 'text',
            content: link.uri
          });
        }
      } else if (link.parsed.type === 'nevent' || link.parsed.type === 'naddr' || link.parsed.type === 'note' || link.parsed.type === 'hexID') {
        // Event link - use bech32 string or hex ID
        const eventId = link.parsed.type === 'hexID' ? link.parsed.data : link.parsed.data;
        // Display without "nostr:" prefix
        const displayText = link.parsed.data;
        segments.push({
          type: 'event',
          content: displayText,
          eventId
        });
      } else {
        // Unknown type, show as text
        segments.push({
          type: 'text',
          content: link.uri
        });
      }
      
      lastIndex = link.end;
    }
    
    // Add remaining text
    if (lastIndex < plaintext.length) {
      segments.push({
        type: 'text',
        content: plaintext.slice(lastIndex)
      });
    }
    
    // If no links found, return single text segment
    if (segments.length === 0) {
      segments.push({
        type: 'text',
        content: plaintext
      });
    }
    
    // Normalize d-tag according to NIP-54
    function normalizeDTag(text: string): string {
      let normalized = text;
      
      // Convert to lowercase (preserving non-ASCII characters)
      normalized = normalized.toLowerCase();
      
      // Convert whitespace to `-`
      normalized = normalized.replace(/\s+/g, '-');
      
      // Remove punctuation and symbols (but preserve non-ASCII letters and numbers)
      normalized = normalized.replace(/[^\p{L}\p{N}-]/gu, '');
      
      // Collapse multiple consecutive `-` to a single `-`
      normalized = normalized.replace(/-+/g, '-');
      
      // Remove leading and trailing `-`
      normalized = normalized.replace(/^-+|-+$/g, '');
      
      return normalized;
    }

    // Now parse wikilinks [[wikilink]], hashtags #topic, and regular HTTP/HTTPS URLs from text segments
    const finalSegments: ContentSegment[] = [];
    // Match [[target]] or [[target|display text]]
    const wikilinkRegex = /\[\[([^\]]+)\]\]/g;
    // Match hashtags: # followed by word characters, but not if it's part of a URL
    const hashtagRegex = /#([\p{L}\p{N}_]+)/gu;
    const urlRegex = /(https?:\/\/[^\s<>"{}|\\^`\[\]]+)/gi;
    
    for (const segment of segments) {
      if (segment.type === 'text') {
        let textIndex = 0;
        let match;
        const text = segment.content;
        
        // First, parse wikilinks according to NIP-54
        const wikilinkMatches: Array<{ index: number; length: number; dTag: string; displayText: string }> = [];
        while ((match = wikilinkRegex.exec(text)) !== null) {
          const content = match[1];
          // Check if it has pipe syntax: [[target|display]]
          const pipeIndex = content.indexOf('|');
          let targetText: string;
          let displayText: string;
          
          if (pipeIndex !== -1) {
            targetText = content.slice(0, pipeIndex).trim();
            displayText = content.slice(pipeIndex + 1).trim();
          } else {
            targetText = content.trim();
            displayText = content.trim();
          }
          
          // Normalize the d-tag using the function defined above
          const normalizedDTag = normalizeDTag(targetText);
          
          wikilinkMatches.push({
            index: match.index,
            length: match[0].length,
            dTag: normalizedDTag,
            displayText: displayText
          });
        }
        
        // Then parse hashtags
        const hashtagMatches: Array<{ index: number; length: number; hashtag: string }> = [];
        hashtagRegex.lastIndex = 0; // Reset regex
        while ((match = hashtagRegex.exec(text)) !== null) {
          hashtagMatches.push({
            index: match.index,
            length: match[0].length,
            hashtag: match[1]
          });
        }
        
        // Then parse URLs
        const urlMatches: Array<{ index: number; length: number; url: string }> = [];
        urlRegex.lastIndex = 0; // Reset regex
        while ((match = urlRegex.exec(text)) !== null) {
          urlMatches.push({
            index: match.index,
            length: match[0].length,
            url: match[1]
          });
        }
        
        // Merge and sort all matches by index
        const allMatches = [
          ...wikilinkMatches.map(m => ({ ...m, type: 'wikilink' as const })),
          ...hashtagMatches.map(m => ({ ...m, type: 'hashtag' as const })),
          ...urlMatches.map(m => ({ ...m, type: 'url' as const }))
        ].sort((a, b) => a.index - b.index);
        
        // Filter out hashtags that are inside URLs or wikilinks
        const filteredMatches = allMatches.filter((match, idx) => {
          if (match.type === 'hashtag') {
            // Check if this hashtag is inside any URL
            const insideUrl = urlMatches.some(urlMatch => 
              match.index >= urlMatch.index && 
              match.index < urlMatch.index + urlMatch.length
            );
            if (insideUrl) return false;
            
            // Check if this hashtag is inside any wikilink
            const insideWikilink = wikilinkMatches.some(wikilinkMatch => 
              match.index >= wikilinkMatch.index && 
              match.index < wikilinkMatch.index + wikilinkMatch.length
            );
            if (insideWikilink) return false;
            
            return true;
          }
          return true;
        });
        
        // Process matches in order
        for (const match of filteredMatches) {
          // Add text before the match
          if (match.index > textIndex) {
            finalSegments.push({
              type: 'text',
              content: text.slice(textIndex, match.index)
            });
          }
          
          // Add the match
          if (match.type === 'wikilink') {
            const wikilinkMatch = match as typeof wikilinkMatches[0] & { type: 'wikilink' };
            finalSegments.push({
              type: 'wikilink',
              content: wikilinkMatch.displayText || wikilinkMatch.dTag,
              wikilink: wikilinkMatch.dTag
            });
          } else if (match.type === 'hashtag') {
            const hashtagMatch = match as typeof hashtagMatches[0] & { type: 'hashtag' };
            finalSegments.push({
              type: 'hashtag',
              content: `#${hashtagMatch.hashtag}`,
              hashtag: hashtagMatch.hashtag
            });
          } else {
            finalSegments.push({
              type: 'url',
              content: match.url,
              url: match.url
            });
          }
          
          textIndex = match.index + match.length;
        }
        
        // Add remaining text after last match
        if (textIndex < text.length) {
          finalSegments.push({
            type: 'text',
            content: text.slice(textIndex)
          });
        }
      } else {
        // Keep non-text segments as-is
        finalSegments.push(segment);
      }
    }
    
    // Process greentext on final text segments (only in feed view)
    if (!fullView && finalSegments.length > 0) {
      const processedSegments: ContentSegment[] = [];
      for (const segment of finalSegments) {
        if (segment.type === 'text') {
          const greentextSegments = processGreentext(segment.content);
          processedSegments.push(...greentextSegments);
        } else {
          processedSegments.push(segment);
        }
      }
      return processedSegments.length > 0 ? processedSegments : finalSegments;
    }
    
    return finalSegments.length > 0 ? finalSegments : segments;
  }


  function getEventUrl(eventId: string): string {
    // Decode bech32 or use hex directly to get the event ID
    try {
      if (eventId.length === 64 && /^[a-f0-9]{64}$/i.test(eventId)) {
        // Already hex - use it directly
        return `/event/${eventId}`;
      } else {
        // Try to decode bech32
        const decoded = nip19.decode(eventId);
        if (decoded.type === 'note' || decoded.type === 'nevent') {
          const id = decoded.type === 'note' ? String(decoded.data) : (decoded.data && typeof decoded.data === 'object' && 'id' in decoded.data ? String(decoded.data.id) : eventId);
          return `/event/${id}`;
        } else if (decoded.type === 'naddr') {
          // For naddr, use the bech32 string in the URL
          return `/event/${eventId}`;
        } else {
          return `/event/${eventId}`;
        }
      }
    } catch (decodeError) {
      // If decoding fails, use the ID as-is
      return `/event/${eventId}`;
    }
  }

  function getTopics(): string[] {
    return post.tags.filter(t => t[0] === 't').map(t => t[1]);
  }

  function isReply(): boolean {
    // Check for all event reference tags: e, E, a, A, i, I, q
    return post.tags.some((t) => {
      const tagName = t[0];
      if (tagName === 'e' || tagName === 'E' || tagName === 'q') {
        return t[1] && t[1] !== post.id;
      }
      if (tagName === 'a' || tagName === 'A' || tagName === 'i' || tagName === 'I') {
        return t[1] && t[1].includes(':');
      }
      return false;
    });
  }

  // Get reply-to event ID from e/E tags only (excluding q tags)
  function getReplyToEventId(): string | null {
    // 1. Check for e/E tags (NIP-10 replies)
    const replyTag = post.tags.find((t) => (t[0] === 'e' || t[0] === 'E') && t[3] === 'reply');
    if (replyTag && replyTag[1]) return replyTag[1];
    
    const rootId = getRootEventId();
    const eTag = post.tags.find((t) => (t[0] === 'e' || t[0] === 'E') && t[1] && t[1] !== rootId && t[1] !== post.id);
    if (eTag && eTag[1]) return eTag[1];
    
    return null;
  }

  // Get reply-to a-tag value from a/A tags
  function getReplyToATag(): string | null {
    const aTag = post.tags.find((t) => (t[0] === 'a' || t[0] === 'A') && t[1]);
    return aTag?.[1] || null;
  }

  function getReplyEventId(): string | null {
    // Priority order: e/E (reply), q (quote), a/A (parameterized), i/I (identifier)
    
    // 1. Check for e/E tags (NIP-10 replies)
    const replyToEventId = getReplyToEventId();
    if (replyToEventId) return replyToEventId;
    
    // 2. Check for q tag (quoted event)
    const qTag = post.tags.find((t) => t[0] === 'q' && t[1]);
    if (qTag && qTag[1]) return qTag[1];
    
    // 3. Check for a/A tags (parameterized replaceable events)
    const aTagValue = getReplyToATag();
    if (aTagValue) return aTagValue;
    
    // 4. Check for i/I tags (identifier references)
    const iTag = post.tags.find((t) => (t[0] === 'i' || t[0] === 'I') && t[1]);
    if (iTag && iTag[1]) return iTag[1];
    
    return null;
  }
  
  function getReplyTagType(): 'e' | 'E' | 'a' | 'A' | 'i' | 'I' | 'q' | null {
    // Return the tag type for the reply, in priority order
    const replyTag = post.tags.find((t) => (t[0] === 'e' || t[0] === 'E') && t[3] === 'reply');
    if (replyTag) return replyTag[0] as 'e' | 'E';
    
    const rootId = getRootEventId();
    const eTag = post.tags.find((t) => (t[0] === 'e' || t[0] === 'E') && t[1] && t[1] !== rootId && t[1] !== post.id);
    if (eTag) return eTag[0] as 'e' | 'E';
    
    const qTag = post.tags.find((t) => t[0] === 'q' && t[1]);
    if (qTag) return 'q';
    
    const aTag = post.tags.find((t) => (t[0] === 'a' || t[0] === 'A') && t[1]);
    if (aTag) return aTag[0] as 'a' | 'A';
    
    const iTag = post.tags.find((t) => (t[0] === 'i' || t[0] === 'I') && t[1]);
    if (iTag) return iTag[0] as 'i' | 'I';
    
    return null;
  }
  
  // Check if a-tag references the same event as the quoted event
  function doesATagMatchQuotedEvent(aTagValue: string, quotedEvent: NostrEvent | null | undefined): boolean {
    if (!aTagValue || !quotedEvent) return false;
    
    // Parse a-tag: format is "kind:pubkey:d-tag"
    const parts = aTagValue.split(':');
    if (parts.length !== 3) return false;
    
    const [kindStr, pubkey, dTag] = parts;
    const kind = parseInt(kindStr, 10);
    
    // Check if quoted event matches a-tag reference
    if (quotedEvent.kind !== kind || quotedEvent.pubkey !== pubkey) {
      return false;
    }
    
    // Check d-tag
    const quotedDTag = quotedEvent.tags.find((t) => t[0] === 'd' && t[1]);
    if (!quotedDTag || quotedDTag[1] !== dTag) {
      return false;
    }
    
    return true;
  }

  // Check if reply-to and quote reference the same event
  function shouldShowReply(): boolean {
    const replyToEventId = getReplyToEventId();
    const quotedEventId = getQuotedEventId();
    
    // If e/E tag and q tag reference the same event ID, only show quote
    if (replyToEventId && quotedEventId && replyToEventId === quotedEventId) {
      return false;
    }
    
    // Check a-tags: if a-tag and q-tag reference the same event
    const replyToATag = getReplyToATag();
    if (replyToATag && quotedEventId) {
      // If we have the quoted event loaded, check if it matches the a-tag reference
      if (providedQuotedEvent && doesATagMatchQuotedEvent(replyToATag, providedQuotedEvent)) {
        return false; // Same event referenced, only show quote
      }
      
      // Also check if the quoted event ID matches the a-tag value directly (unlikely but possible)
      if (quotedEventId === replyToATag) {
        return false;
      }
    }
    
    return isReply();
  }

  function getRootEventId(): string | null {
    const rootTag = post.tags.find((t) => t[0] === 'root');
    return rootTag?.[1] || null;
  }

  function hasQuotedEvent(): boolean {
    return post.tags.some((t) => t[0] === 'q');
  }

  function getQuotedEventId(): string | null {
    const quotedTag = post.tags.find((t) => t[0] === 'q');
    return quotedTag?.[1] || null;
  }

  // Check if URL appears in content (as plain URL or in markdown)
  function isUrlInContent(url: string): boolean {
    const normalized = normalizeUrl(url);
    const content = post.content.toLowerCase();
    
    // Check if URL appears as plain text (with or without protocol)
    const urlWithoutProtocol = normalized.replace(/^https?:\/\//i, '');
    if (content.includes(normalized.toLowerCase()) || content.includes(urlWithoutProtocol.toLowerCase())) {
      return true;
  }

    // Check if URL appears in markdown image syntax ![alt](url)
    const markdownImageRegex = /!\[.*?\]\((.*?)\)/gi;
    let match;
    while ((match = markdownImageRegex.exec(post.content)) !== null) {
      const markdownUrl = normalizeUrl(match[1]);
      if (markdownUrl === normalized) {
        return true;
  }
    }
    
    // Check if URL appears in HTML img/video/audio tags
    const htmlTagRegex = /<(img|video|audio)[^>]+src=["']([^"']+)["']/gi;
    while ((match = htmlTagRegex.exec(post.content)) !== null) {
      const htmlUrl = normalizeUrl(match[2]);
      if (htmlUrl === normalized) {
        return true;
    }
    }
    
    return false;
  }

  function normalizeUrl(url: string): string {
    if (!url || typeof url !== 'string') return url;
    try {
      const parsed = new URL(url);
      // Normalize for comparison (same as MediaAttachments)
      const normalized = `${parsed.protocol}//${parsed.host.toLowerCase()}${parsed.pathname}`.replace(/\/$/, '');
      return normalized;
    } catch {
      return url.trim().replace(/\/$/, '').toLowerCase();
    }
  }

  // Get media URLs that MediaAttachments will display (for fullView)
  // This matches the logic in MediaAttachments.extractMedia()
  function getMediaAttachmentUrls(): string[] {
    const urls: string[] = [];
    const seen = new Set<string>();
    const forceRender = isMediaKind; // Same as what we pass to MediaAttachments

    // 1. Image tag (NIP-23)
    const imageTag = post.tags.find((t) => t[0] === 'image');
    if (imageTag && imageTag[1]) {
      const normalized = normalizeUrl(imageTag[1]);
      if (!seen.has(normalized)) {
        urls.push(imageTag[1]);
        seen.add(normalized);
      }
    }

    // 2. imeta tags (NIP-92)
    for (const tag of post.tags) {
      if (tag[0] === 'imeta') {
        let url: string | undefined;
        for (let i = 1; i < tag.length; i++) {
          const item = tag[i];
          if (item.startsWith('url ')) {
            url = item.substring(4).trim();
            break;
          }
        }

        if (url) {
          const normalized = normalizeUrl(url);
          // Skip if already displayed in content (imeta is just metadata reference)
          // UNLESS forceRender is true (for media kinds where media is the primary content)
          if (!forceRender && isUrlInContent(url)) {
            continue;
          }
          
          if (!seen.has(normalized)) {
            urls.push(url);
            seen.add(normalized);
          }
        }
      }
    }

    // 3. file tags (NIP-94)
    for (const tag of post.tags) {
      if (tag[0] === 'file' && tag[1]) {
        const normalized = normalizeUrl(tag[1]);
        if (!seen.has(normalized)) {
          urls.push(tag[1]);
          seen.add(normalized);
        }
      }
    }

    // 4. Extract from markdown content (images in markdown syntax)
    const imageRegex = /!\[.*?\]\((.*?)\)/g;
    let match;
    while ((match = imageRegex.exec(post.content)) !== null) {
      const url = match[1];
      const normalized = normalizeUrl(url);
      if (!seen.has(normalized)) {
        urls.push(url);
        seen.add(normalized);
      }
    }

    return urls;
  }
    
  // Extract media URLs from event tags (image, imeta, file) - for feed view only
  // Excludes URLs that are already in the content
  function getMediaUrls(): string[] {
    const urls: string[] = [];
    const seen = new Set<string>();
    
    // 1. Image tag (NIP-23)
    const imageTag = post.tags.find((t) => t[0] === 'image');
    if (imageTag && imageTag[1]) {
      const normalized = normalizeUrl(imageTag[1]);
      if (!seen.has(normalized) && !isUrlInContent(imageTag[1])) {
        urls.push(imageTag[1]);
        seen.add(normalized);
      }
  }

    // 2. imeta tags (NIP-92)
    for (const tag of post.tags) {
      if (tag[0] === 'imeta') {
        for (let i = 1; i < tag.length; i++) {
          const item = tag[i];
          if (item.startsWith('url ')) {
            const url = item.substring(4).trim();
            const normalized = normalizeUrl(url);
            if (!seen.has(normalized) && !isUrlInContent(url)) {
              urls.push(url);
              seen.add(normalized);
            }
            break;
          }
        }
      }
    }

    // 3. file tags (NIP-94)
    for (const tag of post.tags) {
      if (tag[0] === 'file' && tag[1]) {
        const normalized = normalizeUrl(tag[1]);
        if (!seen.has(normalized) && !isUrlInContent(tag[1])) {
          urls.push(tag[1]);
          seen.add(normalized);
      }
      }
    }

    return urls;
  }


  // Media viewer state
  let mediaViewerOpen = $state(false);
  let mediaViewerUrl = $state<string | null>(null);

  function handleMediaUrlClick(e: MouseEvent, url: string) {
    e.stopPropagation(); // Don't open drawer when clicking media URL
    e.preventDefault();
    mediaViewerUrl = url;
    mediaViewerOpen = true;
  }

  function closeMediaViewer() {
    mediaViewerOpen = false;
    mediaViewerUrl = null;
  }

</script>

<div
  tabindex="0"
  role="button"
  aria-label="Post by {post.pubkey.slice(0, 8)}"
  onfocus={() => isFocused = true}
  onblur={() => isFocused = false}
  onkeydown={(e) => {
    // Handle shortcuts when post is focused
    if (e.key === 'r' && isLoggedIn && !showReplyForm) {
      e.preventDefault();
      showReplyForm = true;
    } else if (e.key === 'Enter' && !showReplyForm) {
      // Enter to view full event
      e.preventDefault();
      goto(getEventLink(post));
    }
  }}
>
<article 
  bind:this={cardElement}
  class="Feed-post" 
  data-post-id={post.id} 
  id="event-{post.id}" 
  data-event-id={post.id}
  class:collapsed={!fullView && shouldCollapse && !isExpanded}
>
  {#if fullView}
    <!-- Full view: show complete content with markdown, media, profile pics, reactions -->
    <CardHeader 
      pubkey={post.pubkey}
      relativeTime={getRelativeTime()}
      clientName={getClientName()}
    >
      {#snippet actions()}
        {#if isLoggedIn && bookmarked}
          <span class="bookmark-indicator bookmarked" title="Bookmarked">🔖</span>
        {/if}
        <IconButton 
          icon="eye" 
          label="View"
          size={16}
          onclick={() => goto(getEventLink(post))}
        />
        {#if isLoggedIn}
          <IconButton 
            icon="message-square" 
            label="Reply" 
            size={16}
            onclick={() => showReplyForm = !showReplyForm}
          />
        {/if}
        <EventMenu event={post} showContentActions={true} onReply={() => showReplyForm = !showReplyForm} />
      {/snippet}
    </CardHeader>

    {#if shouldShowReply()}
      <ReplyContext 
        parentEvent={providedParentEvent || undefined} 
        parentEventId={getReplyEventId() || undefined}
        parentEventTagType={getReplyTagType() || undefined}
        targetId={providedParentEvent ? `event-${providedParentEvent.id}` : undefined}
      />
    {/if}

    {#if hasQuotedEvent()}
      <QuotedContext 
        quotedEvent={providedQuotedEvent}
        quotedEventId={getQuotedEventId() || undefined}
        targetId={providedQuotedEvent ? `event-${providedQuotedEvent.id}` : undefined}
      />
    {/if}

    <MetadataCard event={post} hideTitle={true} hideSummary={true} />

    {#if getTitle()}
      <div class="event-title-row">
        <h2 class="event-title">{getTitle()}</h2>
      </div>
    {/if}
    {#if getSummary()}
      <div class="event-summary-row">
        <p class="event-summary">{getSummary()}</p>
      </div>
    {/if}

    <div class="post-content mb-2">
      {#if (shouldAutoRenderMedia || fullView) && (post.content && post.content.trim() || isMediaKind)}
        <MediaAttachments event={post} forceRender={isMediaKind} onMediaClick={(url, e) => handleMediaUrlClick(e, url)} />
      {/if}
      {#if post.kind === KIND.POLL && fullView}
        <PollCard pollEvent={post} />
      {:else if post.content && post.content.trim()}
        {@const mediaAttachmentUrls = getMediaAttachmentUrls()}
        <MarkdownRenderer content={post.content} event={post} excludeMediaUrls={mediaAttachmentUrls} />
      {:else if !isMediaKind && post.kind !== KIND.POLL}
        <!-- Show empty content message for non-media kinds without content -->
        <p class="text-fog-text-light dark:text-fog-dark-text-light italic text-sm">No content</p>
      {/if}
    </div>

    <div class="post-actions flex flex-wrap items-center justify-between gap-2 sm:gap-4">
      <div class="post-actions-left flex items-center gap-2 sm:gap-4">
        <FeedReactionButtons event={post} preloadedReactions={preloadedReactions} />
      </div>
      <div class="kind-badge">
        <span class="kind-number">{getKindInfo(post.kind).number}</span>
        <span class="kind-description">{getKindInfo(post.kind).description}</span>
      </div>
    </div>
        {:else}
    <!-- Feed view: plaintext only, no profile pics, media as URLs -->
    <CardHeader 
      pubkey={post.pubkey}
      relativeTime={getRelativeTime()}
      clientName={getClientName()}
      inline={true}
    >
      {#snippet actions()}
        <IconButton 
          icon="eye" 
          label="View"
          size={16}
          onclick={() => goto(getEventLink(post))}
        />
        {#if isLoggedIn}
          <IconButton 
            icon="message-square" 
            label="Reply" 
            size={16}
            onclick={() => showReplyForm = !showReplyForm}
          />
        {/if}
        <EventMenu event={post} showContentActions={true} onReply={() => showReplyForm = !showReplyForm} />
      {/snippet}
    </CardHeader>

    {#if getTitle()}
      <div class="event-title-row">
        <h2 class="event-title">{getTitle()}</h2>
      </div>
    {/if}
    {#if getSummary()}
      <div class="event-summary-row">
        <p class="event-summary">{getSummary()}</p>
      </div>
    {/if}

    <!-- Show referenced event preview in feed view -->
    <ReferencedEventPreview event={post} preloadedReferencedEvent={preloadedReferencedEvent} />

    <!-- Show metadata in feed view when content is empty, but skip for media kinds (MediaAttachments handles those) -->
    {#if !fullView && (!post.content || !post.content.trim()) && !isMediaKind}
      <MetadataCard event={post} hideTitle={true} hideSummary={true} />
    {/if}

    <div bind:this={contentElement} class="post-content mb-2" class:collapsed-content={!fullView && shouldCollapse && !isExpanded}>
      {#if shouldAutoRenderMedia}
        <MediaAttachments event={post} forceRender={isMediaKind} onMediaClick={(url, e) => handleMediaUrlClick(e, url)} />
      {/if}
      <p class="text-fog-text dark:text-fog-dark-text whitespace-pre-wrap word-wrap">
        {#each parseContentWithNIP21Links() as segment}
          {@const highlightContent = getHighlightContent()}
          {#if segment.type === 'text'}
            {#if highlightContent && segment.content.includes(highlightContent)}
              {@const parts = segment.content.split(highlightContent)}
              {#each parts as part, i}
                {part}
                {#if i < parts.length - 1}
                  <mark class="highlighted-text">{highlightContent}</mark>
                {/if}
              {/each}
            {:else}
              {segment.content}
            {/if}
          {:else if segment.type === 'greentext'}
            <span class="greentext">{segment.content}</span>
          {:else if segment.type === 'profile' && segment.pubkey}
            <ProfileBadge pubkey={segment.pubkey} inline={true} />
          {:else if segment.type === 'event' && segment.eventId}
            <a
              href={getEventUrl(segment.eventId)}
              target="_blank"
              rel="noopener noreferrer"
              class="nostr-event-link text-fog-accent dark:text-fog-dark-accent hover:underline"
            >
              {segment.content}
            </a>
          {:else if segment.type === 'url' && segment.url}
            {@const isMediaUrl = /\.(jpg|jpeg|png|gif|webp|svg|bmp|mp4|webm|ogg|mov|avi|mkv|mp3|wav|ogg|flac|aac|m4a)(\?|#|$)/i.test(segment.url)}
            {#if isMediaUrl}
              <button
                type="button"
                class="text-fog-accent dark:text-fog-dark-accent hover:underline bg-transparent border-none p-0 cursor-pointer"
                onclick={(e) => handleMediaUrlClick(e, segment.url!)}
              >
                {segment.content}
              </button>
            {:else}
              <a
                href={segment.url}
                target="_blank"
                rel="noopener noreferrer"
                class="text-fog-accent dark:text-fog-dark-accent hover:underline"
                onclick={(e) => e.stopPropagation()}
              >
                {segment.content}
              </a>
            {/if}
          {:else if segment.type === 'wikilink' && segment.wikilink}
            <a
              href="/find?q={encodeURIComponent(segment.wikilink)}"
              class="text-fog-accent dark:text-fog-dark-accent hover:underline"
              onclick={(e) => e.stopPropagation()}
            >
              {segment.content}
            </a>
          {:else if segment.type === 'hashtag' && segment.hashtag}
            <a
              href="/topics/{segment.hashtag}"
              class="text-fog-accent dark:text-fog-dark-accent hover:underline"
              onclick={(e) => e.stopPropagation()}
            >
              {segment.content}
            </a>
          {/if}
        {/each}
      </p>
      
      {#if getMediaUrls().length > 0}
        {@const mediaUrls = getMediaUrls()}
        <div class="media-urls mt-2">
          {#each mediaUrls as url}
            <button
              type="button"
              onclick={(e) => handleMediaUrlClick(e, url)}
              class="media-url-link text-fog-accent dark:text-fog-dark-accent hover:underline break-all bg-transparent border-none p-0 cursor-pointer text-left"
              style="font-size: 0.875em;"
            >
              {url}
            </button>
          {/each}
          </div>
      {/if}
  </div>
  
    {#if !fullView && shouldCollapse}
      <div class="show-more-container">
    <button 
      onclick={toggleExpand}
          class="show-more-btn text-fog-accent dark:text-fog-dark-accent hover:underline"
          style="font-size: 0.875em;"
    >
          {isExpanded ? 'Show Less' : 'Show More'}
    </button>
      </div>
  {/if}

    {#if !fullView}
      <div class="feed-card-footer flex items-center justify-between flex-wrap gap-2">
        <div class="feed-card-reactions">
          <FeedReactionButtons event={post} preloadedReactions={preloadedReactions} />
        </div>
        <div class="kind-badge feed-card-kind-badge">
          <span class="kind-number">{getKindInfo(post.kind).number}</span>
          <span class="kind-description">{getKindInfo(post.kind).description}</span>
        </div>
      </div>
    {/if}
  {/if}
  
</article>
</div>

{#if isLoggedIn && showReplyForm}
  <div class="reply-form-container mb-4">
    <CommentForm
      threadId={post.id}
      rootEvent={post}
      onPublished={() => {
        showReplyForm = false;
      }}
      onCancel={() => {
        showReplyForm = false;
      }}
    />
  </div>
{/if}

{#if mediaViewerUrl && mediaViewerOpen}
  <MediaViewer url={mediaViewerUrl} isOpen={mediaViewerOpen} onClose={closeMediaViewer} />
{/if}

<style>
  .Feed-post {
    padding: 1rem;
    margin-bottom: 1rem;
    background: var(--fog-post, #ffffff);
    border: 1px solid var(--fog-border, #e5e7eb);
    border-radius: 0.25rem;
    position: relative;
    overflow: hidden;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    word-break: break-word;
    overflow-wrap: anywhere;
  }

  @media (max-width: 640px) {
    .Feed-post {
      padding: 0.75rem;
      width: 100%;
      max-width: 100%;
    }
  }
  
  .Feed-post.collapsed {
    max-height: 500px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }
  
  /* Ensure header and other non-content elements are always fully visible when collapsed */
  .Feed-post.collapsed :global(.card-header),
  .Feed-post.collapsed .event-title-row,
  .Feed-post.collapsed .event-summary-row,
  .Feed-post.collapsed :global(.reply-context),
  .Feed-post.collapsed :global(.quoted-context),
  .Feed-post.collapsed :global(.metadata-card) {
    flex-shrink: 0;
    min-height: fit-content;
    overflow: visible;
  }
  
  .Feed-post.collapsed .post-content.collapsed-content {
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }

  :global(.dark) .Feed-post {
    background: var(--fog-dark-post, #1f2937);
    border-color: var(--fog-dark-border, #374151);
  }
  
  .post-content.collapsed-content {
    max-height: 350px;
    overflow: hidden;
    position: relative;
  }
  
  .post-content.collapsed-content::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 60px;
    background: linear-gradient(to bottom, transparent, var(--fog-post, #ffffff));
    pointer-events: none;
  }
  
  :global(.dark) .post-content.collapsed-content::after {
    background: linear-gradient(to bottom, transparent, var(--fog-dark-post, #1f2937));
  }
  
  .show-more-container {
    text-align: center;
    padding: 0.5rem 0;
  }
  
  .show-more-btn {
    background: none;
    border: none;
    cursor: pointer;
    padding: 0.25rem 0.5rem;
    font-weight: 500;
  }

  .post-content {
    line-height: 1.6;
    word-wrap: break-word;
    overflow-wrap: anywhere;
    word-break: break-word;
    padding-top: 1rem;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    overflow: hidden;
  }

  .post-content :global(.nostr-event-link),
  .post-content :global(a[href^="nostr:"]) {
    word-break: break-all !important;
    overflow-wrap: anywhere !important;
    word-wrap: break-word !important;
    white-space: normal !important;
    max-width: 100% !important;
    display: inline-block !important;
    box-sizing: border-box !important;
  }

  .post-content :global(.profile-badge) {
    max-width: 100% !important;
    min-width: 0 !important;
    flex-shrink: 1 !important;
    word-break: break-word !important;
    overflow-wrap: anywhere !important;
    flex-wrap: wrap !important;
  }

  .word-wrap {
    word-wrap: break-word;
    overflow-wrap: anywhere;
    word-break: break-word;
  }


  .media-urls {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: flex-start; /* Left-align URLs */
  }

  .media-url-link {
    word-break: break-all;
    overflow-wrap: anywhere;
    text-align: left; /* Ensure left alignment */
    max-width: 100%;
  }

  .nostr-event-link {
    word-break: break-all !important;
    overflow-wrap: anywhere !important;
    word-wrap: break-word !important;
    white-space: normal !important;
    text-decoration: underline;
    max-width: 100% !important;
    display: inline-block !important;
    box-sizing: border-box !important;
  }

  .nostr-event-link:hover {
    text-decoration: underline;
  }

  .post-actions {
    padding-top: 0.5rem;
    border-top: 1px solid var(--fog-border, #e5e7eb);
    margin-top: 0.5rem;
  }

  .post-actions-left {
    flex: 1;
    min-width: 0;
  }

  :global(.dark) .post-actions {
    border-top-color: var(--fog-dark-border, #374151);
  }

  .feed-card-footer {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--fog-border, #e5e7eb);
    flex-shrink: 0;
  }

  :global(.dark) .feed-card-footer {
    border-top-color: var(--fog-dark-border, #374151);
  }

  .feed-card-reactions {
    flex: 1;
    min-width: 0;
  }

  .event-title-row {
    margin-top: 0.75rem;
    margin-bottom: 0.5rem;
  }

  .event-title {
    font-size: 1.5em;
    font-weight: bold;
    color: var(--fog-text, #1f2937);
    margin: 0;
    line-height: 1.3;
  }

  :global(.dark) .event-title {
    color: var(--fog-dark-text, #f9fafb);
  }

  .event-summary-row {
    margin-top: 0.5rem;
    margin-bottom: 0.75rem;
  }

  .event-summary {
    font-size: 0.9375em;
    color: var(--fog-text-light, #52667a);
    margin: 0;
    line-height: 1.5;
  }

  :global(.dark) .event-summary {
    color: var(--fog-dark-text-light, #a8b8d0);
  }

  .kind-badge {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.625rem;
    line-height: 1;
    color: var(--fog-text-light, #52667a);
    flex-shrink: 0;
    white-space: nowrap;
  }

  .feed-card-kind-badge {
    flex-shrink: 0;
    white-space: nowrap;
  }

  :global(.dark) .kind-badge {
    color: var(--fog-dark-text-light, #a8b8d0);
  }

  .kind-number {
    font-weight: 600;
  }

  .kind-description {
    font-size: 0.625rem;
    opacity: 0.8;
  }

  .Feed-post {
    position: relative;
  }


  @media (max-width: 640px) {
    .event-title {
      word-break: break-word;
      overflow-wrap: anywhere;
      max-width: 100%;
    }
  }

  .bookmark-indicator {
    display: inline-block;
    font-size: 1rem;
    line-height: 1;
    filter: grayscale(100%);
    transition: filter 0.2s;
  }

  .bookmark-indicator.bookmarked {
    filter: grayscale(0%);
  }

  .highlighted-text {
    background-color: rgba(255, 255, 0, 0.3);
    padding: 0.125rem 0.25rem;
    border-radius: 0.125rem;
    font-weight: 500;
  }

  :global(.dark) .highlighted-text {
    background-color: rgba(255, 255, 0, 0.2);
  }

  .greentext {
    color: #789922;
  }

  :global(.dark) .greentext {
    color: #8ab378;
  }

  /* Focusable wrapper for keyboard navigation */
  div[role="button"] {
    outline: none;
    cursor: default;
  }

  div[role="button"]:focus {
    outline: 2px solid var(--fog-accent, #64748b);
    outline-offset: 2px;
  }

  :global(.dark) div[role="button"]:focus {
    outline-color: var(--fog-dark-accent, #94a3b8);
  }

</style>
