<script lang="ts">
  import { nostrClient } from '../../services/nostr/nostr-client.js';
  import { relayManager } from '../../services/nostr/relay-manager.js';
  import { getEvent, getEventsByKind, getEventsByPubkey } from '../../services/cache/event-cache.js';
  import { cacheEvent } from '../../services/cache/event-cache.js';
  import { nip19 } from 'nostr-tools';
  import { goto } from '$app/navigation';
  import { KIND, KIND_LOOKUP } from '../../types/kind-lookup.js';
  import type { NostrEvent } from '../../types/nostr.js';
  import { parseProfile } from '../../services/user-data.js';

  interface Props {
    mode?: 'search' | 'filter'; // 'search' shows dropdown, 'filter' filters page content
    placeholder?: string;
    onFilterChange?: (result: { type: 'event' | 'pubkey' | 'text' | null; value: string | null; kind?: number | null }) => void;
    showKindFilter?: boolean; // Show kind filter dropdown
    selectedKind?: number | null; // Selected kind for filtering
    onKindChange?: (kind: number | null) => void; // Callback when kind filter changes
    hideDropdownResults?: boolean; // If true, don't show dropdown results (for /find page)
    onSearchResults?: (results: { events: NostrEvent[]; profiles: string[]; relays?: string[]; eventRelays?: Map<string, string> }) => void; // Callback for search results (events, profile pubkeys, relays used, and relay per event)
    allowedKinds?: number[]; // Hard-coded list of kinds to search (hides kind dropdown)
  }

  let { mode = 'search', placeholder = 'Search events, profiles, pubkeys, or enter event ID...', onFilterChange, showKindFilter = false, selectedKind = null, onKindChange, hideDropdownResults = false, onSearchResults, allowedKinds }: Props = $props();
  
  // Use allowedKinds if provided, otherwise use selectedKind
  let effectiveKinds = $derived(allowedKinds && allowedKinds.length > 0 ? allowedKinds : (selectedKind !== null ? [selectedKind] : null));

  let searchQuery = $state('');
  let searching = $state(false);
  let resolving = $state(false);
  let searchResults = $state<Array<{ event: NostrEvent; matchType: string }>>([]);
  let showResults = $state(false);
  let searchInput: HTMLInputElement | null = $state(null);
  let searchTimeout: ReturnType<typeof setTimeout> | null = null;
  let cacheSearchTimeoutId: ReturnType<typeof setTimeout> | null = null;
  
  // For collecting results when hideDropdownResults is true
  let foundEvents: NostrEvent[] = [];
  let foundProfiles: string[] = [];
  // For cache-only results
  let cacheEvents: NostrEvent[] = [];
  let cacheProfiles: string[] = [];
  // Map to track which relay each event came from
  const eventRelayMap = new Map<string, string>();
  const CACHE_SEARCH_DEBOUNCE = 500; // 500ms debounce for cache search
  
  // Clear results at start of search
  function clearResults() {
    foundEvents = [];
    foundProfiles = [];
    eventRelayMap.clear();
  }
  
  // Clear cache results
  function clearCacheResults() {
    cacheEvents = [];
    cacheProfiles = [];
  }
  
  // Search cache only (debounced)
  async function searchCacheOnly() {
    if (!searchQuery.trim()) {
      clearCacheResults();
      if (hideDropdownResults && onSearchResults) {
        onSearchResults({ events: [], profiles: [], relays: [], eventRelays: new Map() });
      }
      return;
    }

    const query = searchQuery.trim();
    clearCacheResults();

    try {
      // 1. Check if it's a hex event ID
      if (/^[0-9a-f]{64}$/i.test(query)) {
        const hexId = query.toLowerCase();
        const event = await getEvent(hexId);
        
        if (event) {
          // Check if kind matches filter
          if (effectiveKinds && effectiveKinds.length > 0 && !effectiveKinds.includes(event.kind)) {
            // Doesn't match, continue
          } else {
            cacheEvents = [event];
            if (hideDropdownResults && onSearchResults) {
              const cacheRelays = new Map<string, string>();
              cacheRelays.set(event.id, 'cache');
              onSearchResults({ events: cacheEvents, profiles: [], relays: [], eventRelays: cacheRelays });
            }
            return;
          }
        }
        
        // Try as pubkey
        const cachedByPubkey = await getEventsByPubkey(hexId, 100);
        if (cachedByPubkey.length > 0) {
          const filtered = effectiveKinds && effectiveKinds.length > 0
            ? cachedByPubkey.filter(e => effectiveKinds.includes(e.kind))
            : cachedByPubkey;
          cacheEvents = filtered;
          cacheProfiles = [hexId];
          if (hideDropdownResults && onSearchResults) {
            const cacheRelays = new Map<string, string>();
            for (const event of cacheEvents) {
              cacheRelays.set(event.id, 'cache');
            }
            onSearchResults({ events: cacheEvents, profiles: cacheProfiles, relays: [], eventRelays: cacheRelays });
          }
          return;
        }
      }

      // 2. Check if it's a bech32 npub/nprofile/naddr
      if (/^(npub|nprofile|naddr)1[a-z0-9]+$/i.test(query)) {
        try {
          const decoded = nip19.decode(query);
          let pubkey: string | null = null;
          
          if (decoded.type === 'npub') {
            pubkey = String(decoded.data);
          } else if (decoded.type === 'nprofile') {
            if (decoded.data && typeof decoded.data === 'object' && 'pubkey' in decoded.data) {
              pubkey = String(decoded.data.pubkey);
            }
          } else if (decoded.type === 'naddr') {
            // naddr encodes kind + pubkey + d tag
            if (decoded.data && typeof decoded.data === 'object' && 'kind' in decoded.data && 'pubkey' in decoded.data && 'identifier' in decoded.data) {
              const naddrData = decoded.data as { kind: number; pubkey: string; identifier: string };
              const kind = naddrData.kind;
              pubkey = String(naddrData.pubkey);
              const dTag = String(naddrData.identifier);
              
              // Check if kind matches filter
              if (effectiveKinds && effectiveKinds.length > 0 && !effectiveKinds.includes(kind)) {
                // Doesn't match, continue
              } else {
                // Search cache for event with matching kind, pubkey, and d tag
                const cachedByKind = await getEventsByKind(kind, 1000);
                const event = cachedByKind.find(e => 
                  pubkey && e.pubkey.toLowerCase() === pubkey.toLowerCase() && 
                  e.tags.some(t => t[0] === 'd' && t[1] === dTag)
                );
                
                if (event) {
                  cacheEvents = [event];
                  if (hideDropdownResults && onSearchResults) {
                    const cacheRelays = new Map<string, string>();
                    cacheRelays.set(event.id, 'cache');
                    onSearchResults({ events: cacheEvents, profiles: [], relays: [], eventRelays: cacheRelays });
                  }
                  return;
                }
              }
            }
          }
          
          if (pubkey) {
            const normalizedPubkey = pubkey.toLowerCase();
            const cachedByPubkey = await getEventsByPubkey(normalizedPubkey, 100);
            if (cachedByPubkey.length > 0) {
              const filtered = effectiveKinds && effectiveKinds.length > 0
                ? cachedByPubkey.filter(e => effectiveKinds.includes(e.kind))
                : cachedByPubkey;
              cacheEvents = filtered;
              cacheProfiles = [normalizedPubkey];
              if (hideDropdownResults && onSearchResults) {
                const cacheRelays = new Map<string, string>();
                for (const event of cacheEvents) {
                  cacheRelays.set(event.id, 'cache');
                }
                onSearchResults({ events: cacheEvents, profiles: cacheProfiles, relays: [], eventRelays: cacheRelays });
              }
              return;
            }
          }
        } catch {
          // Not a valid bech32, continue
        }
      }

      // 3. Search by kind if specified
      if (effectiveKinds && effectiveKinds.length > 0) {
        const allCached: NostrEvent[] = [];
        for (const kind of effectiveKinds) {
          const cached = await getEventsByKind(kind, 100);
          allCached.push(...cached);
        }
        
        // Filter by content matching query
        const queryLower = query.toLowerCase();
        const matching = allCached.filter(event => {
          return event.content.toLowerCase().includes(queryLower) ||
                 event.tags.some(tag => tag.some(v => v && v.toLowerCase().includes(queryLower)));
        });
        
        cacheEvents = matching.slice(0, 100);
        if (hideDropdownResults && onSearchResults && cacheEvents.length > 0) {
          const cacheRelays = new Map<string, string>();
          for (const event of cacheEvents) {
            cacheRelays.set(event.id, 'cache');
          }
          onSearchResults({ events: cacheEvents, profiles: [], relays: [], eventRelays: cacheRelays });
        }
      }
    } catch (error) {
      console.error('Error searching cache:', error);
    }
  }

  // Debounced cache search
  $effect(() => {
    if (cacheSearchTimeoutId) {
      clearTimeout(cacheSearchTimeoutId);
    }

    if (searchQuery.trim() && hideDropdownResults && onSearchResults) {
      cacheSearchTimeoutId = setTimeout(() => {
        searchCacheOnly();
      }, CACHE_SEARCH_DEBOUNCE);
    } else {
      clearCacheResults();
    }

    return () => {
      if (cacheSearchTimeoutId) {
        clearTimeout(cacheSearchTimeoutId);
      }
    };
  });
  
  // For filter mode: resolved search result
  let filterResult = $state<{ type: 'event' | 'pubkey' | 'text' | null; value: string | null; kind?: number | null }>({ type: null, value: null, kind: null });
  
  // Note: filterResult kind is updated in performSearch, not here to avoid loops

  // Check if input looks like NIP-05 (user@domain.com)
  function isNIP05(input: string): boolean {
    const trimmed = input.trim();
    return /^[^@]+@[^@]+\.[^@]+$/.test(trimmed);
  }

  // Search cache for profiles with matching NIP-05
  async function searchCacheForNIP05(nip05: string): Promise<string | null> {
    try {
      const db = await import('../../services/cache/indexeddb-store.js').then(m => m.getDB());
      const tx = db.transaction('profiles', 'readonly');
      const store = tx.store;
      const profiles: any[] = [];
      
      let cursor = await store.openCursor();
      while (cursor) {
        profiles.push(cursor.value);
        cursor = await cursor.continue();
      }
      await tx.done;

      const normalizedNIP05 = nip05.toLowerCase();
      for (const cached of profiles) {
        const profile = parseProfile(cached.event);
        if (profile.nip05) {
          for (const profileNip05 of profile.nip05) {
            if (profileNip05.toLowerCase() === normalizedNIP05) {
              return cached.pubkey;
            }
          }
        }
      }
    } catch (error) {
      console.debug('Error searching cache for NIP-05:', error);
    }
    return null;
  }

  // Resolve NIP-05 from well-known.json
  async function resolveNIP05FromWellKnown(nip05: string): Promise<string | null> {
    try {
      const [localPart, domain] = nip05.split('@');
      if (!localPart || !domain) return null;
      
      const wellKnownUrl = `https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(localPart)}`;
      
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 5000);
      
      try {
        const response = await fetch(wellKnownUrl, { signal: controller.signal });
        clearTimeout(timeout);
        
        if (!response.ok) {
          return null;
        }
        
        const data = await response.json();
        const names = data.names || {};
        const pubkey = names[localPart];
        
        if (pubkey && /^[0-9a-f]{64}$/i.test(pubkey)) {
          return pubkey.toLowerCase();
        }
      } catch (fetchError) {
        clearTimeout(timeout);
        if (fetchError instanceof Error && fetchError.name !== 'AbortError') {
          console.debug('Error fetching well-known.json:', fetchError);
        }
      }
    } catch (error) {
      console.debug('Error resolving NIP-05:', error);
    }
    return null;
  }

  async function performSearch() {
    if (!searchQuery.trim()) {
      searchResults = [];
      showResults = false;
      filterResult = { type: null, value: null, kind: effectiveKinds?.[0] || selectedKind || null };
      if (onFilterChange) onFilterChange(filterResult);
      return;
    }

    // Clear results at start of search
    clearResults();

    await nostrClient.initialize();
    searching = true;
    resolving = true;
    searchResults = [];
    showResults = true;
    filterResult = { type: null, value: null, kind: effectiveKinds?.[0] || selectedKind || null };

    // Get relays that will be used for search (used for empty result messages)
    const relaysUsed = relayManager.getAllAvailableRelays();

    try {
      const query = searchQuery.trim();
      
      // 1. Check if it's a hex event ID (64 hex chars)
      if (/^[0-9a-f]{64}$/i.test(query)) {
        const hexId = query.toLowerCase();
        let event: NostrEvent | undefined = await getEvent(hexId);
        
        if (!event) {
          const relays = relayManager.getAllAvailableRelays();
          const events = await nostrClient.fetchEvents(
            [{ ids: [hexId] }],
            relays,
            { useCache: false, cacheResults: true }
          );
          
          if (events.length > 0) {
            event = events[0];
            await cacheEvent(event);
          }
        }
        
        if (event) {
          // If kinds are specified, filter by kind
          if (effectiveKinds && effectiveKinds.length > 0 && !effectiveKinds.includes(event.kind)) {
            // Event found but doesn't match allowed kinds, continue to next check
          } else {
            if (mode === 'search') {
              if (hideDropdownResults && onSearchResults) {
                foundEvents = [event];
                // Create relay map for found events
                const foundEventRelays = new Map<string, string>();
                for (const event of foundEvents) {
                  const relay = eventRelayMap.get(event.id);
                  if (relay) {
                    foundEventRelays.set(event.id, relay);
                  }
                }
                onSearchResults({ events: foundEvents, profiles: [], relays: relaysUsed, eventRelays: foundEventRelays });
              } else {
                searchResults = [{ event, matchType: 'Event ID' }];
                showResults = true;
              }
            } else {
              filterResult = { type: 'event', value: event.id, kind: effectiveKinds?.[0] || selectedKind || null };
              if (onFilterChange) onFilterChange(filterResult);
            }
            searching = false;
            resolving = false;
            return;
          }
        }
        
        // Event not found, try as pubkey (step 2)
        const hexPubkey = hexId.toLowerCase();
        // If kinds are specified, search for events with this pubkey in p/q tags
        if (effectiveKinds && effectiveKinds.length > 0 && mode === 'search' && hideDropdownResults && onSearchResults) {
          const relays = relayManager.getAllAvailableRelays();
          const allEvents = new Map<string, NostrEvent>();
          
          // Search each allowed kind
          for (const kind of effectiveKinds) {
            // Search by author (most important for bookmarks and highlights)
            const eventsByAuthor = await nostrClient.fetchEvents(
              [{ kinds: [kind], authors: [hexPubkey], limit: 100 }],
              relays,
              { useCache: true, cacheResults: true, timeout: 10000 }
            );
            
            // Search for events with pubkey in p or q tags
            const eventsWithP = await nostrClient.fetchEvents(
              [{ kinds: [kind], '#p': [hexPubkey], limit: 100 }],
              relays,
              { useCache: true, cacheResults: true, timeout: 10000 }
            );
            
            const eventsWithQ = await nostrClient.fetchEvents(
              [{ kinds: [kind], '#q': [hexPubkey], limit: 100 }],
              relays,
              { useCache: true, cacheResults: true, timeout: 10000 }
            );
            
            // Add to combined results
            for (const event of [...eventsByAuthor, ...eventsWithP, ...eventsWithQ]) {
              allEvents.set(event.id, event);
            }
          }
          
          foundEvents = Array.from(allEvents.values());
          // Create relay map for found events
          const foundEventRelays = new Map<string, string>();
          for (const event of foundEvents) {
            const relay = eventRelayMap.get(event.id);
            if (relay) {
              foundEventRelays.set(event.id, relay);
            }
          }
          onSearchResults({ events: foundEvents, profiles: [], relays: relaysUsed, eventRelays: foundEventRelays });
          searching = false;
          resolving = false;
          return;
        } else if (mode === 'search') {
          if (hideDropdownResults && onSearchResults) {
            foundProfiles = [hexPubkey];
            onSearchResults({ events: [], profiles: foundProfiles, relays: relaysUsed });
          } else {
            // For search mode, navigate to profile
            handleProfileClick(hexPubkey);
          }
          searching = false;
          resolving = false;
          return;
        } else {
          filterResult = { type: 'pubkey', value: hexPubkey, kind: effectiveKinds?.[0] || selectedKind || null };
          if (onFilterChange) onFilterChange(filterResult);
          searching = false;
          resolving = false;
          return;
        }
      }

      // 3. Check npub, nprofile (resolve to hex)
      if (/^(npub|nprofile)1[a-z0-9]+$/i.test(query)) {
        try {
          const decoded = nip19.decode(query);
          let pubkey: string | null = null;
          
          if (decoded.type === 'npub') {
            pubkey = String(decoded.data);
          } else if (decoded.type === 'nprofile') {
            if (decoded.data && typeof decoded.data === 'object' && 'pubkey' in decoded.data) {
              pubkey = String(decoded.data.pubkey);
            }
          }
          
          if (pubkey) {
            const normalizedPubkey = pubkey.toLowerCase();
            // If kinds are specified, search for events with this pubkey in p/q tags
            if (effectiveKinds && effectiveKinds.length > 0 && mode === 'search' && hideDropdownResults && onSearchResults) {
              const relays = relayManager.getAllAvailableRelays();
              const allEvents = new Map<string, NostrEvent>();
              
              // Search each allowed kind
              for (const kind of effectiveKinds) {
                // Search by author (most important for bookmarks and highlights)
                const eventsByAuthor = await nostrClient.fetchEvents(
                  [{ kinds: [kind], authors: [normalizedPubkey], limit: 100 }],
                  relays,
                  { useCache: true, cacheResults: true, timeout: 10000 }
                );
                
                // Search for events with pubkey in p or q tags
                const eventsWithP = await nostrClient.fetchEvents(
                  [{ kinds: [kind], '#p': [normalizedPubkey], limit: 100 }],
                  relays,
                  { useCache: true, cacheResults: true, timeout: 10000 }
                );
                
                const eventsWithQ = await nostrClient.fetchEvents(
                  [{ kinds: [kind], '#q': [normalizedPubkey], limit: 100 }],
                  relays,
                  { useCache: true, cacheResults: true, timeout: 10000 }
                );
                
                // Add to combined results
                for (const event of [...eventsByAuthor, ...eventsWithP, ...eventsWithQ]) {
                  allEvents.set(event.id, event);
                }
              }
              
              foundEvents = Array.from(allEvents.values());
              // Create relay map for found events
              const foundEventRelays = new Map<string, string>();
              for (const event of foundEvents) {
                const relay = eventRelayMap.get(event.id);
                if (relay) {
                  foundEventRelays.set(event.id, relay);
                }
              }
              onSearchResults({ events: foundEvents, profiles: [], relays: relaysUsed, eventRelays: foundEventRelays });
              searching = false;
              resolving = false;
              return;
            } else if (mode === 'search') {
              if (hideDropdownResults && onSearchResults) {
                foundProfiles = [normalizedPubkey];
                onSearchResults({ events: [], profiles: foundProfiles, relays: relaysUsed });
              } else {
                handleProfileClick(normalizedPubkey);
              }
              searching = false;
              resolving = false;
              return;
            } else {
              filterResult = { type: 'pubkey', value: normalizedPubkey, kind: effectiveKinds?.[0] || selectedKind || null };
              if (onFilterChange) onFilterChange(filterResult);
              searching = false;
              resolving = false;
              return;
            }
          }
        } catch (error) {
          console.debug('Error decoding npub/nprofile:', error);
        }
      }

      // 4. Check NIP-05 (resolve to hex)
      if (isNIP05(query)) {
        resolving = true;
        try {
          // First check cache
          const cachedPubkey = await searchCacheForNIP05(query);
          if (cachedPubkey) {
            const normalizedPubkey = cachedPubkey.toLowerCase();
            // If kinds are specified, search for events with this pubkey in p/q tags
            if (effectiveKinds && effectiveKinds.length > 0 && mode === 'search' && hideDropdownResults && onSearchResults) {
              const relays = relayManager.getAllAvailableRelays();
              const allEvents = new Map<string, NostrEvent>();
              
              // Search each allowed kind
              for (const kind of effectiveKinds) {
                // Search by author (most important for bookmarks and highlights)
                const eventsByAuthor = await nostrClient.fetchEvents(
                  [{ kinds: [kind], authors: [normalizedPubkey], limit: 100 }],
                  relays,
                  { useCache: true, cacheResults: true, timeout: 10000 }
                );
                
                // Search for events with pubkey in p or q tags
                const eventsWithP = await nostrClient.fetchEvents(
                  [{ kinds: [kind], '#p': [normalizedPubkey], limit: 100 }],
                  relays,
                  { useCache: true, cacheResults: true, timeout: 10000 }
                );
                
                const eventsWithQ = await nostrClient.fetchEvents(
                  [{ kinds: [kind], '#q': [normalizedPubkey], limit: 100 }],
                  relays,
                  { useCache: true, cacheResults: true, timeout: 10000 }
                );
                
                // Add to combined results
                for (const event of [...eventsByAuthor, ...eventsWithP, ...eventsWithQ]) {
                  allEvents.set(event.id, event);
                }
              }
              
              foundEvents = Array.from(allEvents.values());
              // Create relay map for found events
              const foundEventRelays = new Map<string, string>();
              for (const event of foundEvents) {
                const relay = eventRelayMap.get(event.id);
                if (relay) {
                  foundEventRelays.set(event.id, relay);
                }
              }
              onSearchResults({ events: foundEvents, profiles: [], relays: relaysUsed, eventRelays: foundEventRelays });
              searching = false;
              resolving = false;
              return;
            } else if (mode === 'search') {
              if (hideDropdownResults && onSearchResults) {
                foundProfiles = [normalizedPubkey];
                onSearchResults({ events: [], profiles: foundProfiles, relays: relaysUsed });
              } else {
                handleProfileClick(normalizedPubkey);
              }
              searching = false;
              resolving = false;
              return;
            } else {
              filterResult = { type: 'pubkey', value: normalizedPubkey, kind: effectiveKinds?.[0] || selectedKind || null };
              if (onFilterChange) onFilterChange(filterResult);
              searching = false;
              resolving = false;
              return;
            }
          }

          // Then check well-known.json
          const wellKnownPubkey = await resolveNIP05FromWellKnown(query);
          if (wellKnownPubkey) {
            const normalizedPubkey = wellKnownPubkey.toLowerCase();
            // If kinds are specified, search for events with this pubkey in p/q tags
            if (effectiveKinds && effectiveKinds.length > 0 && mode === 'search' && hideDropdownResults && onSearchResults) {
              const relays = relayManager.getAllAvailableRelays();
              const allEvents = new Map<string, NostrEvent>();
              
              // Search each allowed kind
              for (const kind of effectiveKinds) {
                // Search by author (most important for bookmarks and highlights)
                const eventsByAuthor = await nostrClient.fetchEvents(
                  [{ kinds: [kind], authors: [normalizedPubkey], limit: 100 }],
                  relays,
                  { useCache: true, cacheResults: true, timeout: 10000 }
                );
                
                // Search for events with pubkey in p or q tags
                const eventsWithP = await nostrClient.fetchEvents(
                  [{ kinds: [kind], '#p': [normalizedPubkey], limit: 100 }],
                  relays,
                  { useCache: true, cacheResults: true, timeout: 10000 }
                );
                
                const eventsWithQ = await nostrClient.fetchEvents(
                  [{ kinds: [kind], '#q': [normalizedPubkey], limit: 100 }],
                  relays,
                  { useCache: true, cacheResults: true, timeout: 10000 }
                );
                
                // Add to combined results
                for (const event of [...eventsByAuthor, ...eventsWithP, ...eventsWithQ]) {
                  allEvents.set(event.id, event);
                }
              }
              
              foundEvents = Array.from(allEvents.values());
              // Create relay map for found events
              const foundEventRelays = new Map<string, string>();
              for (const event of foundEvents) {
                const relay = eventRelayMap.get(event.id);
                if (relay) {
                  foundEventRelays.set(event.id, relay);
                }
              }
              onSearchResults({ events: foundEvents, profiles: [], relays: relaysUsed, eventRelays: foundEventRelays });
              searching = false;
              resolving = false;
              return;
            } else if (mode === 'search') {
              if (hideDropdownResults && onSearchResults) {
                foundProfiles = [normalizedPubkey];
                onSearchResults({ events: [], profiles: foundProfiles, relays: relaysUsed });
              } else {
                handleProfileClick(normalizedPubkey);
              }
              searching = false;
              resolving = false;
              return;
            } else {
              filterResult = { type: 'pubkey', value: normalizedPubkey, kind: effectiveKinds?.[0] || selectedKind || null };
              if (onFilterChange) onFilterChange(filterResult);
              searching = false;
              resolving = false;
              return;
            }
          }
        } catch (error) {
          console.debug('Error resolving NIP-05:', error);
        }
        resolving = false;
      }

      // 5. Check note, nevent, naddr (resolve to hex event ID)
      if (/^(note|nevent|naddr)1[a-z0-9]+$/i.test(query)) {
        try {
          const decoded = nip19.decode(query);
          let eventId: string | null = null;
          
          if (decoded.type === 'note') {
            eventId = String(decoded.data);
          } else if (decoded.type === 'nevent') {
            if (decoded.data && typeof decoded.data === 'object' && 'id' in decoded.data) {
              eventId = String(decoded.data.id);
            }
          } else if (decoded.type === 'naddr') {
            // naddr encodes kind + pubkey + d tag
            if (decoded.data && typeof decoded.data === 'object' && 'kind' in decoded.data && 'pubkey' in decoded.data && 'identifier' in decoded.data) {
              const naddrData = decoded.data as { kind: number; pubkey: string; identifier: string; relays?: string[] };
              const kind = naddrData.kind;
              const pubkey = String(naddrData.pubkey);
              const dTag = String(naddrData.identifier);
              
              // Check if kind matches filter
              if (effectiveKinds && effectiveKinds.length > 0 && !effectiveKinds.includes(kind)) {
                // Doesn't match, continue
              } else {
                // Try to find in cache first
                const cachedByKind = await getEventsByKind(kind, 1000);
                let event: NostrEvent | undefined = cachedByKind.find(e => 
                  e.pubkey.toLowerCase() === pubkey.toLowerCase() && 
                  e.tags.some(t => t[0] === 'd' && t[1] === dTag)
                );
                
                if (!event) {
                  // Fetch from relays
                  const relays = naddrData.relays && naddrData.relays.length > 0 
                    ? naddrData.relays 
                    : relayManager.getAllAvailableRelays();
                  
                  const events = await nostrClient.fetchEvents(
                    [{ kinds: [kind], authors: [pubkey], '#d': [dTag], limit: 1 }],
                    relays,
                    { useCache: true, cacheResults: true }
                  );
                  
                  if (events.length > 0) {
                    event = events[0];
                  }
                }
                
                if (event) {
                  if (mode === 'search') {
                    if (hideDropdownResults && onSearchResults) {
                      foundEvents = [event];
                      const foundEventRelays = new Map<string, string>();
                      foundEventRelays.set(event.id, 'cache');
                      onSearchResults({ events: foundEvents, profiles: [], relays: relaysUsed, eventRelays: foundEventRelays });
                    } else {
                      searchResults = [{ event, matchType: 'Addressable Event (naddr)' }];
                      showResults = true;
                    }
                  } else {
                    filterResult = { type: 'event', value: event.id, kind: kind };
                    if (onFilterChange) onFilterChange(filterResult);
                  }
                  searching = false;
                  resolving = false;
                  return;
                }
              }
            }
          }
          
          if (eventId) {
            let event: NostrEvent | undefined = await getEvent(eventId);
            
            if (!event) {
              const relays = relayManager.getAllAvailableRelays();
              const events = await nostrClient.fetchEvents(
                [{ ids: [eventId] }],
                relays,
                { useCache: false, cacheResults: true }
              );
              
              if (events.length > 0) {
                event = events[0];
                await cacheEvent(event);
              }
            }
            
            if (event) {
              // If kinds are specified, filter by kind
              if (effectiveKinds && effectiveKinds.length > 0 && !effectiveKinds.includes(event.kind)) {
                // Event found but doesn't match allowed kinds, continue to next check
              } else {
                if (mode === 'search') {
                  if (hideDropdownResults && onSearchResults) {
                    foundEvents = [event];
                    // Create relay map for found events
                const foundEventRelays = new Map<string, string>();
                for (const event of foundEvents) {
                  const relay = eventRelayMap.get(event.id);
                  if (relay) {
                    foundEventRelays.set(event.id, relay);
                  }
                }
                onSearchResults({ events: foundEvents, profiles: [], relays: relaysUsed, eventRelays: foundEventRelays });
                  } else {
                    searchResults = [{ event, matchType: 'Event ID' }];
                    showResults = true;
                  }
                } else {
                  filterResult = { type: 'event', value: event.id, kind: effectiveKinds?.[0] || selectedKind || null };
                  if (onFilterChange) onFilterChange(filterResult);
                }
                searching = false;
                resolving = false;
                return;
              }
            }
          }
        } catch (error) {
          console.debug('Error decoding note/nevent/naddr:', error);
        }
      }

      // 6. Anything else is a full-text search
      if (mode === 'search') {
        let allEvents: NostrEvent[] = [];
        const uniqueEventIds = new Set<string>();
        
        // Helper to add event and notify if needed
        const addEvent = (event: NostrEvent, relay?: string) => {
          if (!uniqueEventIds.has(event.id)) {
            uniqueEventIds.add(event.id);
            allEvents.push(event);
            
            // Track relay source
            if (relay) {
              eventRelayMap.set(event.id, relay);
            }
            
            // If hideDropdownResults, notify incrementally
            if (hideDropdownResults && onSearchResults) {
              const queryLower = query.toLowerCase();
              const matches = allEvents.filter(event => {
                const contentMatch = event.content.toLowerCase().includes(queryLower);
                const titleTag = event.tags.find(t => t[0] === 'title');
                const titleMatch = titleTag?.[1]?.toLowerCase().includes(queryLower) || false;
                const summaryTag = event.tags.find(t => t[0] === 'summary');
                const summaryMatch = summaryTag?.[1]?.toLowerCase().includes(queryLower) || false;
                return contentMatch || titleMatch || summaryMatch;
              });
              
              // Sort and limit
              const sorted = matches.sort((a, b) => {
                const aExact = a.content.toLowerCase() === queryLower;
                const bExact = b.content.toLowerCase() === queryLower;
                if (aExact && !bExact) return -1;
                if (!aExact && bExact) return 1;
                return b.created_at - a.created_at;
              });
              
              foundEvents = sorted.slice(0, 100);
              // Create a map of event IDs to relay URLs for the found events
              const foundEventRelays = new Map<string, string>();
              for (const event of foundEvents) {
                const relay = eventRelayMap.get(event.id);
                if (relay) {
                  foundEventRelays.set(event.id, relay);
                }
              }
              onSearchResults({ events: foundEvents, profiles: [], relays: relaysUsed, eventRelays: foundEventRelays });
            }
          }
        };
        
        // If kinds are specified, search from relays
        if (effectiveKinds && effectiveKinds.length > 0) {
          const relays = relayManager.getAllAvailableRelays();
          const queryLower = query.toLowerCase();
          
          // Search each allowed kind with onUpdate for incremental results
          for (const kind of effectiveKinds) {
            await nostrClient.fetchEvents(
              [{ kinds: [kind], limit: 100 }],
              relays,
              { 
                useCache: 'cache-first', 
                cacheResults: true, 
                timeout: 10000,
                onUpdateWithRelay: (eventsWithRelay: Array<{ event: NostrEvent; relay: string }>) => {
                  // Filter and add events as they arrive with relay info
                  for (const { event, relay } of eventsWithRelay) {
                    const contentMatch = event.content.toLowerCase().includes(queryLower);
                    const titleTag = event.tags.find(t => t[0] === 'title');
                    const titleMatch = titleTag?.[1]?.toLowerCase().includes(queryLower) || false;
                    const summaryTag = event.tags.find(t => t[0] === 'summary');
                    const summaryMatch = summaryTag?.[1]?.toLowerCase().includes(queryLower) || false;
                    
                    if (contentMatch || titleMatch || summaryMatch) {
                      addEvent(event, relay);
                    }
                  }
                }
              }
            );
          }
        } else {
          // Text search in cached events (title, summary, content)
          const allCached: NostrEvent[] = [];
          
          // If kind filter is selected, only search that kind
          if (selectedKind !== null) {
            const kindEvents = await getEventsByKind(selectedKind, 100);
            allCached.push(...kindEvents);
          } else {
            // Search all kinds we handle (limit each to 20 to keep total reasonable, then limit final to 100)
            const kindsToSearch = Object.keys(KIND_LOOKUP).map(k => parseInt(k)).filter(k => !KIND_LOOKUP[k].isSecondaryKind);
            for (const kind of kindsToSearch) {
              try {
                const kindEvents = await getEventsByKind(kind, 20);
                allCached.push(...kindEvents);
                // Stop if we have enough events already
                if (allCached.length >= 100) break;
              } catch (e) {
                // Skip kinds that fail
              }
            }
          }
          
          const queryLower = query.toLowerCase();
          allEvents = allCached.filter(event => {
            const contentMatch = event.content.toLowerCase().includes(queryLower);
            
            const titleTag = event.tags.find(t => t[0] === 'title');
            const titleMatch = titleTag?.[1]?.toLowerCase().includes(queryLower) || false;
            
            const summaryTag = event.tags.find(t => t[0] === 'summary');
            const summaryMatch = summaryTag?.[1]?.toLowerCase().includes(queryLower) || false;
            
            // Mark all cached events as coming from cache
            if (contentMatch || titleMatch || summaryMatch) {
              eventRelayMap.set(event.id, 'cache');
            }
            
            return contentMatch || titleMatch || summaryMatch;
          });
        }
        
        // Final sort and limit (only if not already handled incrementally)
        if (!(hideDropdownResults && onSearchResults && effectiveKinds && effectiveKinds.length > 0)) {
          const queryLower = query.toLowerCase();
          const sorted = allEvents.sort((a, b) => {
            const aExact = a.content.toLowerCase() === queryLower;
            const bExact = b.content.toLowerCase() === queryLower;
            if (aExact && !bExact) return -1;
            if (!aExact && bExact) return 1;
            return b.created_at - a.created_at;
          });
          
          // Deduplicate by event ID
          const uniqueEvents = new Map<string, NostrEvent>();
          for (const event of sorted) {
            uniqueEvents.set(event.id, event);
          }
          
          const limitedResults = Array.from(uniqueEvents.values()).slice(0, 100);
          
          if (hideDropdownResults && onSearchResults) {
            foundEvents = limitedResults;
            // Create relay map for found events
            const foundEventRelays = new Map<string, string>();
            for (const event of foundEvents) {
              const relay = eventRelayMap.get(event.id);
              if (relay) {
                foundEventRelays.set(event.id, relay);
              }
            }
            onSearchResults({ events: foundEvents, profiles: [], relays: relaysUsed, eventRelays: foundEventRelays });
          } else {
            searchResults = limitedResults.map(e => ({ event: e, matchType: 'Content' }));
            showResults = true;
          }
        }
      } else {
        // Filter mode: treat as text search
        filterResult = { type: 'text', value: query, kind: effectiveKinds?.[0] || selectedKind || null };
        if (onFilterChange) onFilterChange(filterResult);
      }
    } catch (error) {
      console.error('Search error:', error);
      // Ensure we reset state even on error
      if (hideDropdownResults && onSearchResults) {
        onSearchResults({ events: [], profiles: [], relays: relaysUsed });
      }
    } finally {
      searching = false;
      resolving = false;
    }
  }

  function handleSearchInput(e: Event) {
    const target = e.target as HTMLInputElement;
    const newValue = target.value;
    searchQuery = newValue;
    
    if (searchTimeout) {
      clearTimeout(searchTimeout);
    }
    
    if (newValue.trim()) {
      searching = true;
      searchTimeout = setTimeout(() => {
        performSearch();
      }, 300);
    } else {
      searchResults = [];
      showResults = false;
      searching = false;
      filterResult = { type: null, value: null, kind: selectedKind };
      if (onFilterChange) onFilterChange(filterResult);
    }
  }

  function handleKeyDown(e: KeyboardEvent) {
    if (e.key === 'Enter') {
      if (searchTimeout) {
        clearTimeout(searchTimeout);
      }
      performSearch();
    } else if (e.key === 'Escape') {
      showResults = false;
      searchQuery = '';
      filterResult = { type: null, value: null, kind: selectedKind };
      if (onFilterChange) onFilterChange(filterResult);
    }
  }

  function handleResultClick(event: NostrEvent) {
    showResults = false;
    searchQuery = '';
    goto(`/event/${event.id}`);
  }

  function handleProfileClick(pubkey: string) {
    showResults = false;
    searchQuery = '';
    goto(`/profile/${pubkey}`);
  }

  // Close results when clicking outside
  $effect(() => {
    if (showResults) {
      const handleClickOutside = (e: MouseEvent) => {
        const target = e.target as HTMLElement;
        if (!target.closest('.unified-search-container')) {
          showResults = false;
        }
      };
      
      document.addEventListener('click', handleClickOutside, true);
      return () => {
        document.removeEventListener('click', handleClickOutside, true);
      };
    }
  });

  // Cleanup timeout on unmount
  $effect(() => {
    return () => {
      if (searchTimeout) {
        clearTimeout(searchTimeout);
      }
    };
  });

  // Expose filter result for parent components (filter mode)
  export function getFilterResult(): { type: 'event' | 'pubkey' | 'text' | null; value: string | null; kind?: number | null } {
    return filterResult;
  }

  // Expose performSearch for manual trigger
  export function triggerSearch() {
    performSearch();
  }

  export function clearSearch() {
    searchQuery = '';
    searchResults = [];
    showResults = false;
    clearResults();
    clearCacheResults();
    filterResult = { type: null, value: null, kind: selectedKind };
    if (onFilterChange) onFilterChange(filterResult);
    if (hideDropdownResults && onSearchResults) {
      onSearchResults({ events: [], profiles: [], relays: [], eventRelays: new Map() });
    }
  }

  // Note: filterResult kind is updated in performSearch, not here to avoid loops

  // Get all kinds for dropdown (sorted by number)
  let allKinds = $derived(Object.values(KIND_LOOKUP).sort((a, b) => a.number - b.number));

  function handleKindChange(e: Event) {
    const select = e.target as HTMLSelectElement;
    const kind = select.value === '' ? null : parseInt(select.value);
    if (onKindChange) {
      onKindChange(kind);
    }
    // Update filter result
    filterResult = { ...filterResult, kind };
    if (onFilterChange) {
      onFilterChange(filterResult);
    }
    // Re-run search if there's a query
    if (searchQuery.trim()) {
      performSearch();
    }
  }
</script>

<div class="unified-search-container">
  <div class="search-input-wrapper">
    {#if showKindFilter && !allowedKinds}
      <select
        value={selectedKind?.toString() || ''}
        onchange={handleKindChange}
        class="kind-filter-select"
        aria-label="Filter by kind"
      >
        <option value="">All Kinds</option>
        {#each allKinds as kindInfo}
          <option value={kindInfo.number}>{kindInfo.number}: {kindInfo.description}</option>
        {/each}
      </select>
    {/if}
    <input
      bind:this={searchInput}
      type="text"
      placeholder={placeholder}
      value={searchQuery}
      oninput={handleSearchInput}
      onkeydown={handleKeyDown}
      class="search-input"
      class:resolving={resolving}
      class:with-kind-filter={showKindFilter}
      aria-label="Search"
    />
    {#if searching || resolving}
      <span class="search-loading">⟳</span>
    {/if}
  </div>
  
  {#if mode === 'search' && !hideDropdownResults && showResults && searchResults.length > 0}
    <div class="search-results">
      {#each searchResults as { event, matchType }}
        <button
          onclick={() => {
            if (event.kind === KIND.METADATA) {
              handleProfileClick(event.pubkey);
            } else {
              handleResultClick(event);
            }
          }}
          class="search-result-item"
        >
          <div class="search-result-header">
            <span class="search-result-type">{matchType}</span>
            <span class="search-result-id">{event.id.substring(0, 16)}...</span>
          </div>
          <div class="search-result-content">
            {event.content.substring(0, 100)}{event.content.length > 100 ? '...' : ''}
          </div>
          <div class="search-result-meta">
            Kind {event.kind} • {new Date(event.created_at * 1000).toLocaleDateString()}
          </div>
        </button>
      {/each}
    </div>
  {:else if mode === 'search' && !hideDropdownResults && showResults && !searching && searchQuery.trim()}
    <div class="search-results">
      <div class="search-no-results">No results found</div>
    </div>
  {/if}
</div>

<style>
  .unified-search-container {
    position: relative;
    width: 100%;
    max-width: 600px;
  }

  .search-input-wrapper {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }

  .kind-filter-select {
    padding: 0.75rem;
    border: 1px solid var(--fog-border, #e5e7eb);
    border-radius: 0.375rem;
    background: var(--fog-post, #ffffff);
    background-color: var(--fog-post, #ffffff);
    color: var(--fog-text, #1f2937);
    font-size: 0.875rem;
    font-family: monospace;
    cursor: pointer;
    min-width: 150px;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
  }

  .kind-filter-select option {
    background: var(--fog-post, #ffffff);
    background-color: var(--fog-post, #ffffff);
    color: var(--fog-text, #1f2937);
  }

  .kind-filter-select option:checked {
    background: var(--fog-accent, #64748b) !important;
    background-color: var(--fog-accent, #64748b) !important;
    color: var(--fog-post, #ffffff) !important;
  }

  /* Ensure selected option in dropdown has proper contrast */
  .kind-filter-select option:checked:not(:disabled) {
    background: var(--fog-accent, #64748b) !important;
    background-color: var(--fog-accent, #64748b) !important;
    color: var(--fog-post, #ffffff) !important;
  }

  .kind-filter-select:focus {
    outline: none;
    border-color: var(--fog-accent, #64748b);
    box-shadow: 0 0 0 3px rgba(100, 116, 139, 0.1);
  }

  :global(.dark) .kind-filter-select {
    border-color: var(--fog-dark-border, #374151);
    background: var(--fog-dark-post, #1f2937);
    background-color: var(--fog-dark-post, #1f2937);
    color: var(--fog-dark-text, #f9fafb);
  }

  :global(.dark) .kind-filter-select option {
    background: var(--fog-dark-post, #1f2937);
    background-color: var(--fog-dark-post, #1f2937);
    color: var(--fog-dark-text, #f9fafb);
  }

  :global(.dark) .kind-filter-select option:checked {
    background: var(--fog-dark-accent, #94a3b8) !important;
    background-color: var(--fog-dark-accent, #94a3b8) !important;
    color: #ffffff !important;
  }

  /* Ensure selected option in dropdown has proper contrast in dark mode */
  :global(.dark) .kind-filter-select option:checked:not(:disabled) {
    background: var(--fog-dark-accent, #94a3b8) !important;
    background-color: var(--fog-dark-accent, #94a3b8) !important;
    color: #ffffff !important;
  }

  :global(.dark) .kind-filter-select:focus {
    border-color: var(--fog-dark-accent, #94a3b8);
    box-shadow: 0 0 0 3px rgba(148, 163, 184, 0.1);
  }

  .search-input.with-kind-filter {
    flex: 1;
  }

  .search-input {
    width: 100%;
    padding: 0.75rem 1rem;
    border: 1px solid var(--fog-border, #e5e7eb);
    border-radius: 0.375rem;
    background: var(--fog-post, #ffffff);
    color: var(--fog-text, #1f2937);
    font-size: 0.875rem;
    transition: all 0.2s;
  }

  .search-input:focus {
    outline: none;
    border-color: var(--fog-accent, #64748b);
    box-shadow: 0 0 0 3px rgba(100, 116, 139, 0.1);
  }

  .search-input.resolving {
    border-color: var(--fog-accent, #64748b);
  }

  :global(.dark) .search-input {
    background: var(--fog-dark-post, #1f2937);
    border-color: var(--fog-dark-border, #374151);
    color: var(--fog-dark-text, #f9fafb);
  }

  :global(.dark) .search-input:focus {
    border-color: var(--fog-dark-accent, #94a3b8);
    box-shadow: 0 0 0 3px rgba(148, 163, 184, 0.1);
  }

  .search-loading {
    position: absolute;
    right: 1rem;
    color: var(--fog-text-light, #9ca3af);
    animation: spin 1s linear infinite;
  }

  @keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
  }

  .search-results {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 0.25rem;
    background: var(--fog-post, #ffffff);
    border: 1px solid var(--fog-border, #e5e7eb);
    border-radius: 0.375rem;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    max-height: 400px;
    overflow-y: auto;
    z-index: 1000;
  }

  :global(.dark) .search-results {
    background: var(--fog-dark-post, #1f2937);
    border-color: var(--fog-dark-border, #374151);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  }

  .search-result-item {
    width: 100%;
    padding: 0.75rem;
    border: none;
    background: transparent;
    text-align: left;
    cursor: pointer;
    transition: background 0.2s;
    border-bottom: 1px solid var(--fog-border, #e5e7eb);
  }

  .search-result-item:last-child {
    border-bottom: none;
  }

  .search-result-item:hover {
    background: var(--fog-highlight, #f3f4f6);
  }

  :global(.dark) .search-result-item {
    border-bottom-color: var(--fog-dark-border, #374151);
  }

  :global(.dark) .search-result-item:hover {
    background: var(--fog-dark-highlight, #374151);
  }

  .search-result-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.25rem;
  }

  .search-result-type {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--fog-accent, #64748b);
    text-transform: uppercase;
  }

  :global(.dark) .search-result-type {
    color: var(--fog-dark-accent, #94a3b8);
  }

  .search-result-id {
    font-size: 0.75rem;
    font-family: monospace;
    color: var(--fog-text-light, #9ca3af);
  }

  :global(.dark) .search-result-id {
    color: var(--fog-dark-text-light, #6b7280);
  }

  .search-result-content {
    font-size: 0.875rem;
    color: var(--fog-text, #1f2937);
    margin-bottom: 0.25rem;
    line-height: 1.4;
  }

  :global(.dark) .search-result-content {
    color: var(--fog-dark-text, #f9fafb);
  }

  .search-result-meta {
    font-size: 0.75rem;
    color: var(--fog-text-light, #9ca3af);
  }

  :global(.dark) .search-result-meta {
    color: var(--fog-dark-text-light, #6b7280);
  }

  .search-no-results {
    padding: 1rem;
    text-align: center;
    color: var(--fog-text-light, #9ca3af);
    font-size: 0.875rem;
  }

  :global(.dark) .search-no-results {
    color: var(--fog-dark-text-light, #6b7280);
  }
</style>
