<script lang="ts">
  import { onMount } from 'svelte';
  import { goto } from '$app/navigation';
  import { page } from '$app/stores';
  import { isNIP07Available, getPublicKeyWithNIP07, signEventWithNIP07 } from '../../lib/services/nostr/nip07-signer.js';
  import { decodeNostrAddress } from '../../lib/services/nostr/nip19-utils.js';
  import { NostrClient } from '../../lib/services/nostr/nostr-client.js';
  import { KIND } from '../../lib/types/nostr.js';
  import type { NostrEvent } from '../../lib/types/nostr.js';
  import { nip19 } from 'nostr-tools';
  import { userStore } from '../../lib/stores/user-store.js';
  import { hasUnlimitedAccess, isLoggedIn } from '../../lib/utils/user-access.js';

  let nip07Available = $state(false);
  let loading = $state(false);
  let error = $state<string | null>(null);
  let success = $state(false);

  // Form fields
  let repoName = $state('');
  let description = $state('');
  let cloneUrls = $state<string[]>(['']);
  let webUrls = $state<string[]>(['']);
  let maintainers = $state<string[]>(['']);
  let relays = $state<string[]>(['']);
  let blossoms = $state<string[]>(['']);
  let tags = $state<string[]>(['']);
  let documentation = $state<string[]>(['']);
  let alt = $state('');
  let imageUrl = $state('');
  let bannerUrl = $state('');
  let earliestCommit = $state('');
  let visibility = $state<'public' | 'unlisted' | 'restricted' | 'private'>('public');
  let projectRelays = $state<string[]>(['']);
  let isFork = $state(false);
  let forkOriginalRepo = $state(''); // Original repo identifier: npub/repo, naddr, or 30617:owner:repo format
  let addClientTag = $state(true); // Add ["client", "gitrepublic-web"] tag
  let existingRepoRef = $state(''); // hex, nevent, or naddr
  let loadingExisting = $state(false);

  // URL preview state
  let previewingUrlIndex = $state<number | null>(null);
  let previewUrl = $state<string | null>(null);
  let previewError = $state<string | null>(null);
  let previewLoading = $state(false);
  let previewTimeout: ReturnType<typeof setTimeout> | null = null;

  // Lookup state
  let lookupLoading = $state<{ [key: string]: boolean }>({});
  let lookupError = $state<{ [key: string]: string | null }>({});
  type ProfileData = { pubkey: string; npub: string; name?: string; about?: string; picture?: string };
  let lookupResults = $state<{ [key: string]: Array<ProfileData | NostrEvent> | null }>({});

  import { DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_SEARCH_RELAYS, combineRelays } from '../../lib/config.js';

  const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS);
  const searchClient = new NostrClient(DEFAULT_NOSTR_SEARCH_RELAYS);

  onMount(async () => {
    nip07Available = isNIP07Available();
    
    // Check for query params to pre-fill form (for registering local clones or transfers)
    const urlParams = $page.url.searchParams;
    const transferParam = urlParams.get('transfer');
    const transferEventId = urlParams.get('transferEventId');
    const originalOwnerParam = urlParams.get('originalOwner');
    const repoParam = urlParams.get('repo');
    const repoTagParam = urlParams.get('repoTag');
    const npubParam = urlParams.get('npub') || originalOwnerParam;
    
    // Handle transfer flow (step 4)
    if (transferParam === 'true' && originalOwnerParam && repoParam && repoTagParam) {
      try {
        // Fetch the original repo announcement to preload data
        const decoded = nip19.decode(originalOwnerParam);
        if (decoded.type === 'npub') {
          const pubkey = decoded.data as string;
          const events = await nostrClient.fetchEvents([
            {
              kinds: [KIND.REPO_ANNOUNCEMENT],
              authors: [pubkey],
              '#d': [repoParam],
              limit: 1
            }
          ]);
          
          if (events.length > 0) {
            const event = events[0];
            
            // Pre-fill repo name
            repoName = repoParam;
            
            // Pre-fill description
            const descTag = event.tags.find(t => t[0] === 'description')?.[1];
            if (descTag) description = descTag;
            
            // Pre-fill clone URLs (add current domain URL)
            const existingCloneUrls = event.tags
              .filter(t => t[0] === 'clone')
              .flatMap(t => t.slice(1))
              .filter(url => url && typeof url === 'string');
            
            const gitDomain = $page.data.gitDomain || 'localhost:6543';
            const isLocalhost = gitDomain.startsWith('localhost') || gitDomain.startsWith('127.0.0.1');
            
            // Only add clone URL if not localhost
            if (!isLocalhost) {
              const protocol = 'https';
              const currentDomainUrl = `${protocol}://${gitDomain}/${originalOwnerParam}/${repoParam}.git`;
              
              // Check if current domain URL already exists
              const hasCurrentDomain = existingCloneUrls.some(url => url.includes(gitDomain));
              
              if (!hasCurrentDomain) {
                cloneUrls = [...existingCloneUrls, currentDomainUrl];
              } else {
                cloneUrls = existingCloneUrls.length > 0 ? existingCloneUrls : [currentDomainUrl];
              }
            } else {
              // Localhost: just use existing clone URLs
              cloneUrls = existingCloneUrls.length > 0 ? existingCloneUrls : [''];
            }
            
            // Pre-fill other fields
            const nameTag = event.tags.find(t => t[0] === 'name')?.[1];
            if (nameTag && !repoName) repoName = nameTag;
            
            const imageTag = event.tags.find(t => t[0] === 'image')?.[1];
            if (imageTag) imageUrl = imageTag;
            
            const bannerTag = event.tags.find(t => t[0] === 'banner')?.[1];
            if (bannerTag) bannerUrl = bannerTag;
            
            const webTags = event.tags.filter(t => t[0] === 'web');
            if (webTags.length > 0) {
              webUrls = webTags.flatMap(t => t.slice(1)).filter(url => url && typeof url === 'string');
            }
            
            const maintainerTags = event.tags.filter(t => t[0] === 'maintainers');
            if (maintainerTags.length > 0) {
              maintainers = maintainerTags.flatMap(t => t.slice(1)).filter(m => m && typeof m === 'string');
            }
            
            const relayTags = event.tags.filter(t => t[0] === 'relays');
            if (relayTags.length > 0) {
              relays = relayTags.flatMap(t => t.slice(1)).filter(r => r && typeof r === 'string');
            }
            
            // Extract blossoms
            const blossomsTags = event.tags.filter(t => t[0] === 'blossoms');
            if (blossomsTags.length > 0) {
              blossoms = blossomsTags.flatMap(t => t.slice(1)).filter(b => b && typeof b === 'string');
            }
            
            // Extract tags/labels (excluding 'private' and 'fork')
            const tagsList: string[] = [];
            for (const tag of event.tags) {
              if (tag[0] === 't' && tag[1] && tag[1] !== 'private' && tag[1] !== 'fork') {
                tagsList.push(tag[1]);
              }
            }
            tags = tagsList.length > 0 ? tagsList : [''];
            
            // Extract documentation - handle relay hints correctly
            const docsList: string[] = [];
            const isRelayUrl = (value: string): boolean => {
              return typeof value === 'string' && (value.startsWith('wss://') || value.startsWith('ws://'));
            };
            
            const getDocFormat = (value: string): string | null => {
              if (value.startsWith('naddr1')) return 'naddr';
              if (/^\d+:[0-9a-f]{64}:[a-zA-Z0-9_-]+$/.test(value)) return 'kind:pubkey:identifier';
              return null;
            };
            
            for (const tag of event.tags) {
              if (tag[0] === 'documentation') {
                let i = 1;
                
                while (i < tag.length) {
                  const value = tag[i];
                  if (!value || typeof value !== 'string' || !value.trim()) {
                    i++;
                    continue;
                  }
                  
                  const trimmed = value.trim();
                  
                  if (isRelayUrl(trimmed)) {
                    i++;
                    continue;
                  }
                  
                  const format = getDocFormat(trimmed);
                  if (!format) {
                    i++;
                    continue;
                  }
                  
                  const nextValue = i + 1 < tag.length ? tag[i + 1] : null;
                  if (nextValue && typeof nextValue === 'string' && isRelayUrl(nextValue.trim())) {
                    docsList.push(trimmed);
                    i += 2;
                    continue;
                  }
                  
                  const sameFormatEntries: string[] = [trimmed];
                  let j = i + 1;
                  while (j < tag.length) {
                    const nextVal = tag[j];
                    if (!nextVal || typeof nextVal !== 'string' || !nextVal.trim()) {
                      j++;
                      continue;
                    }
                    
                    const nextTrimmed = nextVal.trim();
                    
                    if (isRelayUrl(nextTrimmed)) {
                      break;
                    }
                    
                    const nextFormat = getDocFormat(nextTrimmed);
                    if (nextFormat === format) {
                      sameFormatEntries.push(nextTrimmed);
                      j++;
                    } else {
                      break;
                    }
                  }
                  
                  docsList.push(...sameFormatEntries);
                  i = j;
                }
              }
            }
            documentation = docsList.length > 0 ? docsList : [''];
            
            // Extract alt tag
            const altTag = event.tags.find(t => t[0] === 'alt');
            alt = altTag?.[1] || '';
            
            // Extract fork information
            const aTag = event.tags.find(t => t[0] === 'a' && t[1]?.startsWith('30617:'));
            if (aTag?.[1]) {
              forkOriginalRepo = aTag[1];
              isFork = true;
            } else {
              isFork = event.tags.some(t => t[0] === 't' && t[1] === 'fork');
              if (isFork) {
                const pTag = event.tags.find(t => t[0] === 'p' && t[1] && t[1] !== event.pubkey);
                const dTag = event.tags.find(t => t[0] === 'd')?.[1];
                if (pTag?.[1] && dTag) {
                  forkOriginalRepo = `${KIND.REPO_ANNOUNCEMENT}:${pTag[1]}:${dTag}`;
                }
              }
            }
            
            // Extract earliest unique commit
            const rTag = event.tags.find(t => t[0] === 'r' && t[2] === 'euc');
            earliestCommit = rTag?.[1] || '';
            
            // Check if client tag exists
            addClientTag = !event.tags.some(t => t[0] === 'client' && t[1] === 'gitrepublic-web');
            
            // Read visibility tag (defaults to 'public')
            const visibilityTag = event.tags.find(t => t[0] === 'visibility' && t[1]);
            if (visibilityTag && visibilityTag[1]) {
              const vis = visibilityTag[1].toLowerCase();
              if (['public', 'unlisted', 'restricted', 'private'].includes(vis)) {
                visibility = vis as typeof visibility;
              }
            }
            
            // Read project-relay tags
            const projectRelayTags = event.tags.filter(t => t[0] === 'project-relay');
            if (projectRelayTags.length > 0) {
              projectRelays = projectRelayTags.flatMap(t => t.slice(1)).filter(r => r && typeof r === 'string');
              if (projectRelays.length === 0) projectRelays = [''];
            }
            
            // Backward compatibility: check for old private tag
            const isPrivateTag = event.tags.find(t => 
              (t[0] === 'private' && t[1] === 'true') || 
              (t[0] === 't' && t[1] === 'private')
            );
            if (isPrivateTag && !visibilityTag) {
              visibility = 'restricted'; // Map old private to restricted
            }
            
            // Set existing repo ref for updating
            existingRepoRef = event.id;
          } else {
            // No announcement found
            repoName = repoParam;
            const gitDomain = $page.data.gitDomain || 'localhost:6543';
            const isLocalhost = gitDomain.startsWith('localhost') || gitDomain.startsWith('127.0.0.1');
            
            // Only add clone URL if not localhost
            if (!isLocalhost) {
              cloneUrls = [`https://${gitDomain}/${originalOwnerParam}/${repoParam}.git`];
            } else {
              cloneUrls = [''];
            }
          }
        }
      } catch (err) {
        console.warn('Failed to pre-fill form from transfer data:', err);
        // Still set basic info
        repoName = repoParam;
        const gitDomain = $page.data.gitDomain || 'localhost:6543';
        const isLocalhost = gitDomain.startsWith('localhost') || gitDomain.startsWith('127.0.0.1');
        
        // Only add clone URL if not localhost
        if (!isLocalhost) {
          cloneUrls = [`https://${gitDomain}/${originalOwnerParam}/${repoParam}.git`];
        } else {
          cloneUrls = [''];
        }
      }
    } else if (npubParam && repoParam) {
      // Pre-fill repo name
      repoName = repoParam;
      
      // Try to fetch existing announcement to pre-fill other fields
      try {
        const decoded = nip19.decode(npubParam);
        if (decoded.type === 'npub') {
          const pubkey = decoded.data as string;
          const events = await nostrClient.fetchEvents([
            {
              kinds: [KIND.REPO_ANNOUNCEMENT],
              authors: [pubkey],
              '#d': [repoParam],
              limit: 1
            }
          ]);
          
          if (events.length > 0) {
            const event = events[0];
            
            // Pre-fill description
            const descTag = event.tags.find(t => t[0] === 'description')?.[1];
            if (descTag) description = descTag;
            
            // Pre-fill clone URLs (add current domain URL)
            const existingCloneUrls = event.tags
              .filter(t => t[0] === 'clone')
              .flatMap(t => t.slice(1))
              .filter(url => url && typeof url === 'string');
            
            const gitDomain = $page.data.gitDomain || 'localhost:6543';
            const isLocalhost = gitDomain.startsWith('localhost') || gitDomain.startsWith('127.0.0.1');
            
            // Only add clone URL if not localhost
            if (!isLocalhost) {
              const protocol = 'https';
              const currentDomainUrl = `${protocol}://${gitDomain}/${npubParam}/${repoParam}.git`;
              
              // Check if current domain URL already exists
              const hasCurrentDomain = existingCloneUrls.some(url => url.includes(gitDomain));
              
              if (!hasCurrentDomain) {
                cloneUrls = [...existingCloneUrls, currentDomainUrl];
              } else {
                cloneUrls = existingCloneUrls.length > 0 ? existingCloneUrls : [currentDomainUrl];
              }
            } else {
              // Localhost: just use existing clone URLs
              cloneUrls = existingCloneUrls.length > 0 ? existingCloneUrls : [''];
            }
            
            // Pre-fill other fields
            const nameTag = event.tags.find(t => t[0] === 'name')?.[1];
            if (nameTag && !repoName) repoName = nameTag;
            
            const imageTag = event.tags.find(t => t[0] === 'image')?.[1];
            if (imageTag) imageUrl = imageTag;
            
            const bannerTag = event.tags.find(t => t[0] === 'banner')?.[1];
            if (bannerTag) bannerUrl = bannerTag;
            
            const webTags = event.tags.filter(t => t[0] === 'web');
            if (webTags.length > 0) {
              webUrls = webTags.flatMap(t => t.slice(1)).filter(url => url && typeof url === 'string');
            }
            
            const maintainerTags = event.tags.filter(t => t[0] === 'maintainers');
            if (maintainerTags.length > 0) {
              maintainers = maintainerTags.flatMap(t => t.slice(1)).filter(m => m && typeof m === 'string');
            }
            
            const relayTags = event.tags.filter(t => t[0] === 'relays');
            if (relayTags.length > 0) {
              relays = relayTags.flatMap(t => t.slice(1)).filter(r => r && typeof r === 'string');
            }
            
            // Extract blossoms
            const blossomsTags = event.tags.filter(t => t[0] === 'blossoms');
            if (blossomsTags.length > 0) {
              blossoms = blossomsTags.flatMap(t => t.slice(1)).filter(b => b && typeof b === 'string');
            }
            
            // Extract tags/labels (excluding 'private' and 'fork')
            const tagsList: string[] = [];
            for (const tag of event.tags) {
              if (tag[0] === 't' && tag[1] && tag[1] !== 'private' && tag[1] !== 'fork') {
                tagsList.push(tag[1]);
              }
            }
            tags = tagsList.length > 0 ? tagsList : [''];
            
            // Extract documentation - handle relay hints correctly
            const docsList: string[] = [];
            const isRelayUrl = (value: string): boolean => {
              return typeof value === 'string' && (value.startsWith('wss://') || value.startsWith('ws://'));
            };
            
            const getDocFormat = (value: string): string | null => {
              if (value.startsWith('naddr1')) return 'naddr';
              if (/^\d+:[0-9a-f]{64}:[a-zA-Z0-9_-]+$/.test(value)) return 'kind:pubkey:identifier';
              return null;
            };
            
            for (const tag of event.tags) {
              if (tag[0] === 'documentation') {
                let i = 1;
                
                while (i < tag.length) {
                  const value = tag[i];
                  if (!value || typeof value !== 'string' || !value.trim()) {
                    i++;
                    continue;
                  }
                  
                  const trimmed = value.trim();
                  
                  if (isRelayUrl(trimmed)) {
                    i++;
                    continue;
                  }
                  
                  const format = getDocFormat(trimmed);
                  if (!format) {
                    i++;
                    continue;
                  }
                  
                  const nextValue = i + 1 < tag.length ? tag[i + 1] : null;
                  if (nextValue && typeof nextValue === 'string' && isRelayUrl(nextValue.trim())) {
                    docsList.push(trimmed);
                    i += 2;
                    continue;
                  }
                  
                  const sameFormatEntries: string[] = [trimmed];
                  let j = i + 1;
                  while (j < tag.length) {
                    const nextVal = tag[j];
                    if (!nextVal || typeof nextVal !== 'string' || !nextVal.trim()) {
                      j++;
                      continue;
                    }
                    
                    const nextTrimmed = nextVal.trim();
                    
                    if (isRelayUrl(nextTrimmed)) {
                      break;
                    }
                    
                    const nextFormat = getDocFormat(nextTrimmed);
                    if (nextFormat === format) {
                      sameFormatEntries.push(nextTrimmed);
                      j++;
                    } else {
                      break;
                    }
                  }
                  
                  docsList.push(...sameFormatEntries);
                  i = j;
                }
              }
            }
            documentation = docsList.length > 0 ? docsList : [''];
            
            // Extract alt tag
            const altTag = event.tags.find(t => t[0] === 'alt');
            alt = altTag?.[1] || '';
            
            // Extract fork information
            const aTag = event.tags.find(t => t[0] === 'a' && t[1]?.startsWith('30617:'));
            if (aTag?.[1]) {
              forkOriginalRepo = aTag[1];
              isFork = true;
            } else {
              isFork = event.tags.some(t => t[0] === 't' && t[1] === 'fork');
              if (isFork) {
                const pTag = event.tags.find(t => t[0] === 'p' && t[1] && t[1] !== event.pubkey);
                const dTag = event.tags.find(t => t[0] === 'd')?.[1];
                if (pTag?.[1] && dTag) {
                  forkOriginalRepo = `${KIND.REPO_ANNOUNCEMENT}:${pTag[1]}:${dTag}`;
                }
              }
            }
            
            // Extract earliest unique commit
            const rTag = event.tags.find(t => t[0] === 'r' && t[2] === 'euc');
            earliestCommit = rTag?.[1] || '';
            
            // Check if client tag exists
            addClientTag = !event.tags.some(t => t[0] === 'client' && t[1] === 'gitrepublic-web');
            
            // Read visibility tag (defaults to 'public')
            const visibilityTag = event.tags.find(t => t[0] === 'visibility' && t[1]);
            if (visibilityTag && visibilityTag[1]) {
              const vis = visibilityTag[1].toLowerCase();
              if (['public', 'unlisted', 'restricted', 'private'].includes(vis)) {
                visibility = vis as typeof visibility;
              }
            }
            
            // Read project-relay tags
            const projectRelayTags = event.tags.filter(t => t[0] === 'project-relay');
            if (projectRelayTags.length > 0) {
              projectRelays = projectRelayTags.flatMap(t => t.slice(1)).filter(r => r && typeof r === 'string');
              if (projectRelays.length === 0) projectRelays = [''];
            }
            
            // Backward compatibility: check for old private tag
            const isPrivateTag = event.tags.find(t => 
              (t[0] === 'private' && t[1] === 'true') || 
              (t[0] === 't' && t[1] === 'private')
            );
            if (isPrivateTag && !visibilityTag) {
              visibility = 'restricted'; // Map old private to restricted
            }
            
            // Set existing repo ref for updating
            existingRepoRef = event.id;
          } else {
            // No announcement found
            const gitDomain = $page.data.gitDomain || 'localhost:6543';
            const isLocalhost = gitDomain.startsWith('localhost') || gitDomain.startsWith('127.0.0.1');
            
            // Only add clone URL if not localhost
            if (!isLocalhost) {
              cloneUrls = [`https://${gitDomain}/${npubParam}/${repoParam}.git`];
            } else {
              cloneUrls = [''];
            }
          }
        }
      } catch (err) {
        console.warn('Failed to pre-fill form from query params:', err);
        // Still set basic info
        const gitDomain = $page.data.gitDomain || 'localhost:6543';
        const isLocalhost = gitDomain.startsWith('localhost') || gitDomain.startsWith('127.0.0.1');
        
        // Only add clone URL if not localhost
        if (!isLocalhost) {
          cloneUrls = [`https://${gitDomain}/${npubParam}/${repoParam}.git`];
        } else {
          cloneUrls = [''];
        }
      }
    }
  });

  function addCloneUrl() {
    cloneUrls = [...cloneUrls, ''];
  }

  function removeCloneUrl(index: number) {
    cloneUrls = cloneUrls.filter((_, i) => i !== index);
  }

  function updateCloneUrl(index: number, value: string) {
    const newUrls = [...cloneUrls];
    newUrls[index] = value;
    cloneUrls = newUrls;
  }

  function addWebUrl() {
    webUrls = [...webUrls, ''];
  }

  function removeWebUrl(index: number) {
    webUrls = webUrls.filter((_, i) => i !== index);
  }

  function updateWebUrl(index: number, value: string) {
    const newUrls = [...webUrls];
    newUrls[index] = value;
    webUrls = newUrls;
  }

  function addMaintainer() {
    maintainers = [...maintainers, ''];
  }

  function removeMaintainer(index: number) {
    maintainers = maintainers.filter((_, i) => i !== index);
  }

  function updateMaintainer(index: number, value: string) {
    const newMaintainers = [...maintainers];
    newMaintainers[index] = value;
    maintainers = newMaintainers;
  }

  function addRelay() {
    relays = [...relays, ''];
  }

  function removeRelay(index: number) {
    relays = relays.filter((_, i) => i !== index);
  }

  function updateRelay(index: number, value: string) {
    const newRelays = [...relays];
    newRelays[index] = value;
    relays = newRelays;
  }

  function addBlossom() {
    blossoms = [...blossoms, ''];
  }

  function removeBlossom(index: number) {
    blossoms = blossoms.filter((_, i) => i !== index);
  }

  function updateBlossom(index: number, value: string) {
    const newBlossoms = [...blossoms];
    newBlossoms[index] = value;
    blossoms = newBlossoms;
  }

  function addTag() {
    tags = [...tags, ''];
  }

  function removeTag(index: number) {
    tags = tags.filter((_, i) => i !== index);
  }

  function updateTag(index: number, value: string) {
    const newTags = [...tags];
    newTags[index] = value;
    tags = newTags;
  }

  function addDocumentation() {
    documentation = [...documentation, ''];
  }

  function removeDocumentation(index: number) {
    documentation = documentation.filter((_, i) => i !== index);
  }

  function updateDocumentation(index: number, value: string) {
    const newDocs = [...documentation];
    newDocs[index] = value;
    documentation = newDocs;
  }

  async function lookupDocumentation(index: number) {
    const doc = documentation[index]?.trim();
    if (!doc) {
      lookupError[`doc-${index}`] = 'Please enter a naddr to lookup';
      return;
    }

    const lookupKey = `doc-${index}`;
    lookupLoading[lookupKey] = true;
    lookupError[lookupKey] = null;
    lookupResults[lookupKey] = null;

    try {
      // Try to decode as naddr
      let decoded;
      try {
        decoded = nip19.decode(doc);
      } catch {
        lookupError[lookupKey] = 'Invalid naddr format';
        return;
      }

      if (decoded.type !== 'naddr') {
        lookupError[lookupKey] = 'Please enter a valid naddr (naddr1...)';
        return;
      }

      // Extract the components from the decoded naddr
      const { pubkey, kind, identifier, relays } = decoded.data as {
        pubkey: string;
        kind: number;
        identifier: string;
        relays?: string[];
      };

      if (!pubkey || !kind || !identifier) {
        lookupError[lookupKey] = 'Invalid naddr: missing required components';
        return;
      }

      // Convert to the format needed for documentation tag: kind:pubkey:identifier
      // If there's a relay hint, we can optionally add it, but the standard format is kind:pubkey:identifier
      const docFormat = `${kind}:${pubkey}:${identifier}`;
      
      // Update the documentation field with the converted format
      const newDocs = [...documentation];
      newDocs[index] = docFormat;
      documentation = newDocs;

      // Also fetch the event to show some info
      const relaysToUse = relays && relays.length > 0 ? relays : getSearchRelays();
      const client = new NostrClient(relaysToUse);
      
      const events = await client.fetchEvents([
        {
          kinds: [kind],
          authors: [pubkey],
          '#d': [identifier],
          limit: 1
        }
      ]);

      if (events.length > 0) {
        lookupResults[lookupKey] = events;
        lookupError[lookupKey] = null;
      } else {
        // Still update the field even if we can't fetch the event
        lookupError[lookupKey] = 'Documentation address converted, but event not found on relays';
      }
    } catch (err) {
      lookupError[lookupKey] = `Lookup failed: ${String(err)}`;
    } finally {
      lookupLoading[lookupKey] = false;
    }
  }

  /**
   * Publish event with retry logic
   * Retries failed relays up to maxRetries times
   */
  async function publishWithRetry(
    client: NostrClient,
    event: NostrEvent,
    relays: string[],
    maxRetries: number = 2
  ): Promise<{ success: string[]; failed: Array<{ relay: string; error: string }> }> {
    let result = await client.publishEvent(event, relays);
    
    // If we have some successes, return early
    if (result.success.length > 0) {
      return result;
    }
    
    // If all failed and we have retries left, retry only the failed relays
    if (result.failed.length > 0 && maxRetries > 0) {
      console.log(`Retrying publish to ${result.failed.length} failed relay(s)...`);
      const failedRelays = result.failed.map((f: { relay: string; error: string }) => f.relay);
      
      // Wait a bit before retry
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      const retryResult = await client.publishEvent(event, failedRelays);
      
      // Merge results
      return {
        success: [...result.success, ...retryResult.success],
        failed: retryResult.failed
      };
    }
    
    return result;
  }

  async function handleWebUrlHover(index: number, url: string) {
    // Clear any existing timeout
    if (previewTimeout) {
      clearTimeout(previewTimeout);
    }

    // Only preview if URL looks valid
    if (!url.trim() || !isValidUrl(url.trim())) {
      return;
    }

    // Delay preview to avoid showing on quick mouse movements
    previewTimeout = setTimeout(async () => {
      previewingUrlIndex = index;
      previewUrl = url.trim();
      previewError = null;
      previewLoading = true;

      // Try to verify the URL exists by attempting to fetch it
      // Note: CORS may prevent this, but we'll still show the iframe preview
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout
        
        const response = await fetch(url.trim(), {
          method: 'HEAD',
          mode: 'no-cors',
          cache: 'no-cache',
          signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        // With no-cors mode, we can't read the status, but if it doesn't throw, proceed
        previewError = null;
      } catch (err) {
        // If fetch fails, it might be CORS, network error, or 404
        // The iframe will show the actual error to the user
        if (err instanceof Error && err.name === 'AbortError') {
          previewError = 'Request timed out - URL may be slow or unreachable';
        } else {
          previewError = 'Unable to verify URL - preview may show an error if URL is invalid';
        }
      } finally {
        previewLoading = false;
      }
    }, 500); // 500ms delay before showing preview
  }

  function handleWebUrlLeave() {
    if (previewTimeout) {
      clearTimeout(previewTimeout);
    }
    previewingUrlIndex = null;
    previewUrl = null;
    previewError = null;
    previewLoading = false;
  }

  function isValidUrl(url: string): boolean {
    try {
      const urlObj = new URL(url);
      return urlObj.protocol === 'http:' || urlObj.protocol === 'https:';
    } catch {
      return false;
    }
  }

  // Validation functions
  function validateCloneUrl(url: string): string | null {
    if (!url.trim()) return null; // Empty is OK
    const trimmed = url.trim();
    
    // Allow Tor .onion URLs (they use http:// not https://)
    if (trimmed.includes('.onion')) {
      if (!trimmed.startsWith('http://')) {
        return 'Tor .onion URLs must use http:// (not https://)';
      }
      // .onion URLs are valid if they contain a path (they don't need to end with .git)
      if (!trimmed.includes('/')) {
        return 'Tor .onion URL must include a path';
      }
      return null;
    }
    
    // Validate regular URLs
    if (!isValidUrl(trimmed)) {
      return 'Invalid URL format. Must start with http:// or https://';
    }
    
    // For regular URLs, check if it ends with .git or contains a path
    if (!trimmed.endsWith('.git') && !trimmed.includes('/')) {
      return 'Clone URL should end with .git or be a valid repository URL';
    }
    
    return null;
  }

  function validateWebUrl(url: string): string | null {
    if (!url.trim()) return null; // Empty is OK
    if (!isValidUrl(url.trim())) {
      return 'Invalid URL format. Must start with http:// or https://';
    }
    return null;
  }

  function validateMaintainer(maintainer: string): string | null {
    if (!maintainer.trim()) return null; // Empty is OK
    // Check if it's a valid npub or hex pubkey
    try {
      if (maintainer.startsWith('npub')) {
        nip19.decode(maintainer);
        return null;
      } else if (maintainer.length === 64 && /^[0-9a-f]+$/i.test(maintainer)) {
        return null; // Valid hex pubkey
      } else {
        return 'Invalid maintainer format. Use npub1... or 64-character hex pubkey';
      }
    } catch {
      return 'Invalid maintainer format. Use npub1... or 64-character hex pubkey';
    }
  }

  function validateDocumentation(doc: string): string | null {
    if (!doc.trim()) return null; // Empty is OK
    // Check if it's in naddr format or 30618:pubkey:identifier format
    if (doc.startsWith('naddr')) {
      try {
        const decoded = nip19.decode(doc);
        if (decoded.type === 'naddr') return null;
      } catch {
        return 'Invalid naddr format';
      }
    } else if (/^\d+:[0-9a-f]{64}:[a-zA-Z0-9_-]+$/.test(doc)) {
      return null; // Valid kind:pubkey:identifier format
    }
    return 'Invalid documentation format. Use naddr1... or kind:pubkey:identifier';
  }

  function validateImageUrl(url: string): string | null {
    if (!url.trim()) return null; // Empty is OK
    if (!isValidUrl(url.trim())) {
      return 'Invalid URL format. Must start with http:// or https://';
    }
    // Check if it's likely an image URL
    const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'];
    const lowerUrl = url.toLowerCase();
    if (!imageExtensions.some(ext => lowerUrl.includes(ext)) && !lowerUrl.includes('image') && !lowerUrl.includes('img')) {
      return 'Warning: URL does not appear to be an image';
    }
    return null;
  }

  // Lookup functions
  // Use only default search relays for lookups to avoid connecting to random/unreachable user relays
  function getSearchRelays(): string[] {
    return DEFAULT_NOSTR_SEARCH_RELAYS;
  }

  async function lookupRepoAnnouncement(query: string, fieldName: string) {
    const lookupKey = `repo-${fieldName}`;
    lookupLoading[lookupKey] = true;
    lookupError[lookupKey] = null;
    lookupResults[lookupKey] = null;

    try {
      const relays = await getSearchRelays();
      const client = new NostrClient(relays);

      // Try to decode as naddr, nevent, or hex
      const decoded = decodeNostrAddress(query.trim());
      let events: NostrEvent[] = [];

      if (decoded) {
        if (decoded.type === 'note' && decoded.id) {
          events = await client.fetchEvents([{ ids: [decoded.id], limit: 10 }]);
        } else if (decoded.type === 'nevent' && decoded.id) {
          events = await client.fetchEvents([{ ids: [decoded.id], limit: 10 }]);
        } else if (decoded.type === 'naddr' && decoded.pubkey && decoded.kind && decoded.identifier) {
          events = await client.fetchEvents([
            {
              kinds: [decoded.kind],
              authors: [decoded.pubkey],
              '#d': [decoded.identifier],
              limit: 10
            }
          ]);
        }
      }

      // Also search by name or d-tag if query doesn't look like an address
      if (events.length === 0 && !query.startsWith('naddr') && !query.startsWith('nevent') && !/^[0-9a-f]{64}$/i.test(query)) {
        const repoEvents = await client.fetchEvents([
          {
            kinds: [KIND.REPO_ANNOUNCEMENT],
            limit: 20
          }
        ]);

        const searchLower = query.toLowerCase();
        let filteredEvents = repoEvents.filter(event => {
          const name = event.tags.find(t => t[0] === 'name')?.[1] || '';
          const dTag = event.tags.find(t => t[0] === 'd')?.[1] || '';
          return name.toLowerCase().includes(searchLower) || dTag.toLowerCase().includes(searchLower);
        });
        
        // Filter out private repos unless user is authenticated and has access
        // For signup page, we'll only show public repos or repos the user owns
        let userPubkeyHex: string | null = null;
        if (nip07Available) {
          try {
            const userPubkey = await getPublicKeyWithNIP07();
            try {
              const decoded = nip19.decode(userPubkey);
              if (decoded.type === 'npub') {
                userPubkeyHex = decoded.data as string;
              } else {
                userPubkeyHex = userPubkey;
              }
            } catch {
              userPubkeyHex = userPubkey;
            }
          } catch {
            // User not authenticated, continue with filtering
          }
        }
        
        // Filter repos by visibility
        const filteredPrivateEvents = await Promise.all(
          filteredEvents.map(async (event): Promise<NostrEvent | null> => {
            // Check visibility tag
            const visibilityTag = event.tags.find(t => t[0] === 'visibility' && t[1]);
            let repoVisibility: 'public' | 'unlisted' | 'restricted' | 'private' = 'public';
            if (visibilityTag && visibilityTag[1]) {
              const vis = visibilityTag[1].toLowerCase();
              if (['public', 'unlisted', 'restricted', 'private'].includes(vis)) {
                repoVisibility = vis as typeof repoVisibility;
              }
            }
            
            // Backward compatibility: check for old private tag
            if (!visibilityTag) {
              const isPrivate = event.tags.some(t => 
                (t[0] === 'private' && t[1] === 'true') || 
                (t[0] === 't' && t[1] === 'private')
              );
              if (isPrivate) repoVisibility = 'restricted';
            }
            
            // Public and unlisted repos are always visible
            // Use array includes to avoid TypeScript narrowing issues
            if (['public', 'unlisted'].includes(repoVisibility)) return event;
            
            // Restricted and private repos: only show if user is owner
            if (userPubkeyHex && event.pubkey === userPubkeyHex) {
              return event;
            }
            
            // For other private repos, check access via API
            if (userPubkeyHex) {
              try {
                const dTag = event.tags.find(t => t[0] === 'd')?.[1];
                if (!dTag) return null;
                
                // Get npub from pubkey
                const ownerNpub = nip19.npubEncode(event.pubkey);
                const accessResponse = await fetch(`/api/repos/${ownerNpub}/${dTag}/access`, {
                  headers: { 'X-User-Pubkey': userPubkeyHex }
                });
                
                if (accessResponse.ok) {
                  const accessData = await accessResponse.json();
                  if (accessData.canView) {
                    return event;
                  }
                }
              } catch {
                // Access check failed, don't show
              }
            }
            
            return null;
          })
        );
        events = filteredPrivateEvents.filter((e): e is NostrEvent => e !== null);
      }

      if (events.length === 0) {
        lookupError[lookupKey] = 'No repository announcements found';
      } else {
        lookupResults[lookupKey] = events;
      }
    } catch (err) {
      lookupError[lookupKey] = `Lookup failed: ${String(err)}`;
    } finally {
      lookupLoading[lookupKey] = false;
    }
  }

  async function lookupNpub(query: string, fieldName: string, index?: number) {
    const lookupKey = index !== undefined ? `npub-${fieldName}-${index}` : `npub-${fieldName}`;
    lookupLoading[lookupKey] = true;
    lookupError[lookupKey] = null;
    lookupResults[lookupKey] = null;

    try {
      // Try to decode as npub
      let pubkey: string | null = null;
      try {
        if (query.startsWith('npub')) {
          const decoded = nip19.decode(query);
          if (decoded.type === 'npub') {
            pubkey = decoded.data as string;
          }
        } else if (query.length === 64 && /^[0-9a-f]+$/i.test(query)) {
          pubkey = query;
        }
      } catch {
        // Invalid format
      }

      if (!pubkey) {
        // Search for npubs by name (requires kind 0 metadata)
        const relays = getSearchRelays();
        const client = new NostrClient(relays);
        
        // Search for profiles
        const profileEvents = await client.fetchEvents([
          {
            kinds: [0], // Metadata events
            limit: 20
          }
        ]);

        const searchLower = query.toLowerCase();
        const matches = profileEvents.filter(event => {
          try {
            const content = JSON.parse(event.content);
            const name = content.name || content.display_name || '';
            return name.toLowerCase().includes(searchLower);
          } catch {
            return false;
          }
        });

        if (matches.length > 0) {
          lookupResults[lookupKey] = matches.map(e => {
            try {
              const content = JSON.parse(e.content);
              return {
                pubkey: e.pubkey,
                npub: nip19.npubEncode(e.pubkey),
                name: content.name || content.display_name || 'Unknown',
                about: content.about || '',
                picture: content.picture || ''
              };
            } catch {
              return {
                pubkey: e.pubkey,
                npub: nip19.npubEncode(e.pubkey),
                name: 'Unknown'
              };
            }
          });
        } else {
          lookupError[lookupKey] = 'No profiles found matching the query';
        }
      } else {
        // Valid pubkey, try to fetch profile
        const relays = getSearchRelays();
        const client = new NostrClient(relays);
        const profileEvents = await client.fetchEvents([
          {
            kinds: [0],
            authors: [pubkey],
            limit: 1
          }
        ]);

        let profileData: {
          pubkey: string;
          npub: string;
          name?: string;
          about?: string;
          picture?: string;
        } = {
          pubkey,
          npub: query.startsWith('npub') ? query : nip19.npubEncode(pubkey)
        };

        if (profileEvents.length > 0) {
          try {
            const content = JSON.parse(profileEvents[0].content);
            profileData.name = content.name || content.display_name || '';
            profileData.about = content.about || '';
            profileData.picture = content.picture || '';
          } catch {
            // Invalid JSON, use defaults
          }
        }

        lookupResults[lookupKey] = [profileData];
      }
    } catch (err) {
      lookupError[lookupKey] = `Lookup failed: ${String(err)}`;
    } finally {
      lookupLoading[lookupKey] = false;
    }
  }

  async function lookupNevent(query: string, fieldName: string) {
    const lookupKey = `nevent-${fieldName}`;
    lookupLoading[lookupKey] = true;
    lookupError[lookupKey] = null;
    lookupResults[lookupKey] = null;

    try {
      const relays = await getSearchRelays();
      const client = new NostrClient(relays);

      let events: NostrEvent[] = [];
      const decoded = decodeNostrAddress(query.trim());

      if (decoded && decoded.id) {
        events = await client.fetchEvents([{ ids: [decoded.id], limit: 10 }]);
      } else if (/^[0-9a-f]{64}$/i.test(query.trim())) {
        // Hex event ID
        events = await client.fetchEvents([{ ids: [query.trim()], limit: 10 }]);
      }

      if (events.length === 0) {
        lookupError[lookupKey] = 'No events found';
      } else {
        lookupResults[lookupKey] = events;
      }
    } catch (err) {
      lookupError[lookupKey] = `Lookup failed: ${String(err)}`;
    } finally {
      lookupLoading[lookupKey] = false;
    }
  }

  function selectRepoResult(result: NostrEvent, fieldName: string) {
    if (fieldName === 'existingRepoRef') {
      existingRepoRef = result.id;
      loadExistingRepo();
    } else if (fieldName === 'forkOriginalRepo') {
      // Convert to naddr format if possible
      const dTag = result.tags.find(t => t[0] === 'd')?.[1];
      if (dTag) {
        try {
          const naddr = nip19.naddrEncode({
            pubkey: result.pubkey,
            kind: result.kind,
            identifier: dTag,
            relays: []
          });
          forkOriginalRepo = naddr;
        } catch {
          forkOriginalRepo = `${result.kind}:${result.pubkey}:${dTag}`;
        }
      }
    }
    lookupResults[`repo-${fieldName}`] = null;
  }

  function selectNpubResult(result: { pubkey: string; npub: string; name?: string; about?: string; picture?: string }, fieldName: string, index?: number) {
    if (fieldName === 'maintainers' && index !== undefined) {
      // Store hex pubkey instead of npub for maintainers
      updateMaintainer(index, result.pubkey);
    }
    const lookupKey = index !== undefined ? `npub-${fieldName}-${index}` : `npub-${fieldName}`;
    lookupResults[lookupKey] = null;
  }

  function clearLookupResults(key: string) {
    lookupResults[key] = null;
    lookupError[key] = null;
  }

  async function loadExistingRepo() {
    if (!existingRepoRef.trim()) return;

    loadingExisting = true;
    error = null;

    try {
      const decoded = decodeNostrAddress(existingRepoRef.trim());
      if (!decoded) {
        error = 'Invalid format. Please provide a hex event ID, nevent, or naddr.';
        loadingExisting = false;
        return;
      }

      let event: NostrEvent | null = null;

      if (decoded.type === 'note' && decoded.id) {
        // Fetch by event ID
        const events = await nostrClient.fetchEvents([{ ids: [decoded.id], limit: 1 }]);
        event = events[0] || null;
      } else if (decoded.type === 'nevent' && decoded.id) {
        // Fetch by event ID
        const events = await nostrClient.fetchEvents([{ ids: [decoded.id], limit: 1 }]);
        event = events[0] || null;
      } else if (decoded.type === 'naddr' && decoded.pubkey && decoded.kind && decoded.identifier) {
        // Fetch parameterized replaceable event
        const events = await nostrClient.fetchEvents([
          {
            kinds: [decoded.kind],
            authors: [decoded.pubkey],
            '#d': [decoded.identifier],
            limit: 1
          }
        ]);
        event = events[0] || null;
      }

      if (!event) {
        error = 'Repository announcement not found. Make sure it exists on the relays.';
        loadingExisting = false;
        return;
      }

      if (event.kind !== KIND.REPO_ANNOUNCEMENT) {
        error = `The provided event is not a repository announcement (kind ${KIND.REPO_ANNOUNCEMENT}).`;
        loadingExisting = false;
        return;
      }

      // Populate form with existing data
      const dTag = event.tags.find(t => t[0] === 'd')?.[1] || '';
      const nameTag = event.tags.find(t => t[0] === 'name')?.[1] || '';
      const descTag = event.tags.find(t => t[0] === 'description')?.[1] || '';
      const imageTag = event.tags.find(t => t[0] === 'image')?.[1] || '';
      const bannerTag = event.tags.find(t => t[0] === 'banner')?.[1] || '';
      // Read visibility tag (defaults to 'public')
      const visibilityTag = event.tags.find(t => t[0] === 'visibility' && t[1]);
      if (visibilityTag && visibilityTag[1]) {
        const vis = visibilityTag[1].toLowerCase();
        if (['public', 'unlisted', 'restricted', 'private'].includes(vis)) {
          visibility = vis as typeof visibility;
        }
      }
      
      // Read project-relay tags
      const projectRelayTags = event.tags.filter(t => t[0] === 'project-relay');
      if (projectRelayTags.length > 0) {
        projectRelays = projectRelayTags.flatMap(t => t.slice(1)).filter(r => r && typeof r === 'string');
        if (projectRelays.length === 0) projectRelays = [''];
      }
      
      // Backward compatibility: check for old private tag
      const privateTag = event.tags.find(t => (t[0] === 'private' && t[1] === 'true') || (t[0] === 't' && t[1] === 'private'));
      if (privateTag && !visibilityTag) {
        visibility = 'restricted'; // Map old private to restricted
      }

      repoName = nameTag || dTag;
      description = descTag;
      imageUrl = imageTag;
      bannerUrl = bannerTag;

      // Extract clone URLs - handle both formats: separate tags and multiple values in one tag
      const urls: string[] = [];
      for (const tag of event.tags) {
        if (tag[0] === 'clone') {
          for (let i = 1; i < tag.length; i++) {
            const url = tag[i];
            if (url && typeof url === 'string' && url.trim()) {
              urls.push(url.trim());
            }
          }
        }
      }
      cloneUrls = urls.length > 0 ? urls : [''];

      // Extract web URLs - handle both formats
      const webUrlsList: string[] = [];
      for (const tag of event.tags) {
        if (tag[0] === 'web') {
          for (let i = 1; i < tag.length; i++) {
            const url = tag[i];
            if (url && typeof url === 'string' && url.trim()) {
              webUrlsList.push(url.trim());
            }
          }
        }
      }
      webUrls = webUrlsList.length > 0 ? webUrlsList : [''];

      // Extract maintainers - handle both formats
      const maintainersList: string[] = [];
      for (const tag of event.tags) {
        if (tag[0] === 'maintainers') {
          for (let i = 1; i < tag.length; i++) {
            const maintainer = tag[i];
            if (maintainer && typeof maintainer === 'string' && maintainer.trim()) {
              maintainersList.push(maintainer.trim());
            }
          }
        }
      }
      maintainers = maintainersList.length > 0 ? maintainersList : [''];

      // Extract relays
      const relaysList: string[] = [];
      for (const tag of event.tags) {
        if (tag[0] === 'relays') {
          for (let i = 1; i < tag.length; i++) {
            const relay = tag[i];
            if (relay && typeof relay === 'string' && relay.trim()) {
              relaysList.push(relay.trim());
            }
          }
        }
      }
      relays = relaysList.length > 0 ? relaysList : [''];

      // Extract blossoms
      const blossomsList: string[] = [];
      for (const tag of event.tags) {
        if (tag[0] === 'blossoms') {
          for (let i = 1; i < tag.length; i++) {
            const blossom = tag[i];
            if (blossom && typeof blossom === 'string' && blossom.trim()) {
              blossomsList.push(blossom.trim());
            }
          }
        }
      }
      blossoms = blossomsList.length > 0 ? blossomsList : [''];

      // Extract tags/labels
      const tagsList: string[] = [];
      for (const tag of event.tags) {
        if (tag[0] === 't' && tag[1] && tag[1] !== 'private' && tag[1] !== 'fork') {
          tagsList.push(tag[1]);
        }
      }
      tags = tagsList.length > 0 ? tagsList : [''];

      // Extract documentation - handle relay hints correctly
      // Only treat values as multiple entries if they are in the same format
      // If a value looks like a relay URL (wss:// or ws://), it's a relay hint for the previous value
      const docsList: string[] = [];
      const isRelayUrl = (value: string): boolean => {
        return typeof value === 'string' && (value.startsWith('wss://') || value.startsWith('ws://'));
      };
      
      const getDocFormat = (value: string): string | null => {
        // Check if it's naddr format (starts with naddr1)
        if (value.startsWith('naddr1')) return 'naddr';
        // Check if it's kind:pubkey:identifier format
        if (/^\d+:[0-9a-f]{64}:[a-zA-Z0-9_-]+$/.test(value)) return 'kind:pubkey:identifier';
        return null;
      };
      
      for (const tag of event.tags) {
        if (tag[0] === 'documentation') {
          let i = 1;
          
          while (i < tag.length) {
            const value = tag[i];
            if (!value || typeof value !== 'string' || !value.trim()) {
              i++;
              continue;
            }
            
            const trimmed = value.trim();
            
            // Skip relay URLs (they're hints, not entries)
            if (isRelayUrl(trimmed)) {
              i++;
              continue;
            }
            
            // Check if this is a documentation reference
            const format = getDocFormat(trimmed);
            if (!format) {
              i++;
              continue; // Skip invalid formats
            }
            
            // Check if next value is a relay URL (hint for this entry)
            const nextValue = i + 1 < tag.length ? tag[i + 1] : null;
            if (nextValue && typeof nextValue === 'string' && isRelayUrl(nextValue.trim())) {
              // Current value has a relay hint - store just the doc reference, skip the relay
              docsList.push(trimmed);
              i += 2; // Skip both the doc and the relay hint
              continue;
            }
            
            // Check if we have multiple entries in the same format
            // Collect all consecutive entries of the same format
            const sameFormatEntries: string[] = [trimmed];
            let j = i + 1;
            while (j < tag.length) {
              const nextVal = tag[j];
              if (!nextVal || typeof nextVal !== 'string' || !nextVal.trim()) {
                j++;
                continue;
              }
              
              const nextTrimmed = nextVal.trim();
              
              // Stop if we hit a relay URL (it's a hint for the previous entry)
              if (isRelayUrl(nextTrimmed)) {
                break;
              }
              
              // Check if it's the same format
              const nextFormat = getDocFormat(nextTrimmed);
              if (nextFormat === format) {
                sameFormatEntries.push(nextTrimmed);
                j++;
              } else {
                // Different format - stop collecting
                break;
              }
            }
            
            // If we have multiple entries in the same format, add them all
            // Otherwise, just add the single entry
            docsList.push(...sameFormatEntries);
            i = j; // Move to the next unprocessed value
          }
        }
      }
      documentation = docsList.length > 0 ? docsList : [''];

      // Extract alt tag
      const altTag = event.tags.find(t => t[0] === 'alt');
      alt = altTag?.[1] || '';

      // Extract fork information
      const aTag = event.tags.find(t => t[0] === 'a' && t[1]?.startsWith('30617:'));
      if (aTag?.[1]) {
        forkOriginalRepo = aTag[1];
        isFork = true;
      } else {
        // Check if marked as fork via tag
        isFork = event.tags.some(t => t[0] === 't' && t[1] === 'fork');
        if (isFork) {
          // Try to construct from p tag if available
          const pTag = event.tags.find(t => t[0] === 'p' && t[1] && t[1] !== event.pubkey);
          if (pTag?.[1] && dTag) {
            // Construct a tag format: 30617:owner:repo
            forkOriginalRepo = `${KIND.REPO_ANNOUNCEMENT}:${pTag[1]}:${dTag}`;
          }
        }
      }

      // Extract earliest unique commit
      const rTag = event.tags.find(t => t[0] === 'r' && t[2] === 'euc');
      earliestCommit = rTag?.[1] || '';

      // Check if client tag exists
      addClientTag = !event.tags.some(t => t[0] === 'client' && t[1] === 'gitrepublic-web');

    } catch (e) {
      error = `Failed to load repository: ${String(e)}`;
    } finally {
      loadingExisting = false;
    }
  }

  async function submit() {
    if (!nip07Available) {
      error = 'NIP-07 extension is required. Please install a Nostr browser extension.';
      return;
    }

    if (!repoName.trim()) {
      error = 'Repository name is required.';
      return;
    }

    // Validate repo name format (alphanumeric, hyphens, underscores, spaces - will be normalized to d-tag)
    const repoNameTrimmed = repoName.trim();
    if (repoNameTrimmed.length === 0) {
      error = 'Repository name cannot be empty.';
      return;
    }
    if (repoNameTrimmed.length > 100) {
      error = 'Repository name is too long (maximum 100 characters).';
      return;
    }
    // Check for invalid characters that can't be normalized
    if (!/^[\w\s-]+$/.test(repoNameTrimmed)) {
      error = 'Repository name contains invalid characters. Use only letters, numbers, spaces, hyphens, and underscores.';
      return;
    }

    // Validate all fields
    const validationErrors: string[] = [];

    // Validate clone URLs
    for (let i = 0; i < cloneUrls.length; i++) {
      const urlError = validateCloneUrl(cloneUrls[i]);
      if (urlError) {
        validationErrors.push(`Clone URL ${i + 1}: ${urlError}`);
      }
    }

    // Validate web URLs
    for (let i = 0; i < webUrls.length; i++) {
      const urlError = validateWebUrl(webUrls[i]);
      if (urlError) {
        validationErrors.push(`Web URL ${i + 1}: ${urlError}`);
      }
    }

    // Validate maintainers
    for (let i = 0; i < maintainers.length; i++) {
      const maintainerError = validateMaintainer(maintainers[i]);
      if (maintainerError) {
        validationErrors.push(`Maintainer ${i + 1}: ${maintainerError}`);
      }
    }

    // Validate documentation
    for (let i = 0; i < documentation.length; i++) {
      const docError = validateDocumentation(documentation[i]);
      if (docError) {
        validationErrors.push(`Documentation ${i + 1}: ${docError}`);
      }
    }

    // Validate image URLs
    if (imageUrl.trim()) {
      const imageError = validateImageUrl(imageUrl);
      if (imageError) {
        validationErrors.push(`Image URL: ${imageError}`);
      }
    }

    if (bannerUrl.trim()) {
      const bannerError = validateImageUrl(bannerUrl);
      if (bannerError) {
        validationErrors.push(`Banner URL: ${bannerError}`);
      }
    }

    if (validationErrors.length > 0) {
      error = 'Validation errors:\n' + validationErrors.join('\n');
      return;
    }

    loading = true;
    error = null;

    try {
      const pubkey = await getPublicKeyWithNIP07();
      const npub = nip19.npubEncode(pubkey);

      // Normalize repo name to d-tag format
      const dTag = repoName
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/\s+/g, '-')
        .replace(/-+/g, '-')
        .replace(/^-+|-+$/g, '');

      // Get git domain from layout data
      const gitDomain = $page.data.gitDomain || 'localhost:6543';
      const isLocalhost = gitDomain.startsWith('localhost') || gitDomain.startsWith('127.0.0.1');
      const protocol = isLocalhost ? 'http' : 'https';
      const gitUrl = `${protocol}://${gitDomain}/${npub}/${dTag}.git`;

      // Try to get Tor .onion address and add it to clone URLs
      let torOnionUrl: string | null = null;
      try {
        const torResponse = await fetch('/api/tor/onion');
        if (torResponse.ok) {
          const torData = await torResponse.json();
          if (torData.available && torData.onion) {
            torOnionUrl = `http://${torData.onion}/${npub}/${dTag}.git`;
          }
        }
      } catch {
        // Tor not available, continue without it
      }

      // Filter user-provided clone URLs (exclude localhost and .onion duplicates)
      const userCloneUrls = cloneUrls.filter(url => {
        const trimmed = url.trim();
        if (!trimmed) return false;
        // Exclude if it's our domain or already a .onion
        if (trimmed.includes(gitDomain)) return false;
        if (trimmed.includes('.onion')) return false;
        return true;
      });

      // Validate: If using localhost, require either Tor .onion URL or at least one other clone URL
      if (isLocalhost && !torOnionUrl && userCloneUrls.length === 0) {
        error = 'Cannot publish with only localhost. You need either:\n' +
          '• A Tor .onion address (configure Tor hidden service and set TOR_ONION_ADDRESS)\n' +
          '• At least one other clone URL (e.g., GitHub, GitLab, or another GitRepublic instance)';
        loading = false;
        return;
      }

      // ============================================
      // COMPREHENSIVE VALIDATION, NORMALIZATION, AND DEDUPLICATION
      // ============================================
      
      // Normalize and deduplicate clone URLs
      const normalizedCloneUrls: string[] = [];
      const seenCloneUrls = new Set<string>();
      
      // Add our domain URL only if it's NOT localhost (explicitly check the URL)
      if (!isLocalhost && !gitUrl.includes('localhost') && !gitUrl.includes('127.0.0.1')) {
        const normalized = gitUrl.trim().toLowerCase();
        if (!seenCloneUrls.has(normalized)) {
          normalizedCloneUrls.push(gitUrl); // Keep original case for display
          seenCloneUrls.add(normalized);
        }
      }
      
      // Add Tor .onion URL if available (skip validation - it's system-generated and already valid)
      if (torOnionUrl) {
        const normalized = torOnionUrl.trim().toLowerCase();
        if (!seenCloneUrls.has(normalized)) {
          normalizedCloneUrls.push(torOnionUrl);
          seenCloneUrls.add(normalized);
        }
      }
      
      // Add and deduplicate user-provided clone URLs
      for (const url of userCloneUrls) {
        const trimmed = url.trim();
        if (!trimmed) continue;
        
        // Skip localhost URLs in user input (they should have been filtered, but double-check)
        if (trimmed.includes('localhost') || trimmed.includes('127.0.0.1')) {
          continue;
        }
        
        // Normalize for comparison (lowercase, remove trailing slashes)
        // For .onion URLs, be careful with normalization to preserve the .onion domain
        const normalized = trimmed.toLowerCase().replace(/\/+$/, '');
        if (!seenCloneUrls.has(normalized)) {
          // Validate format
          const urlError = validateCloneUrl(trimmed);
          if (urlError) {
            error = `Invalid clone URL: ${trimmed}\n${urlError}`;
            loading = false;
            return;
          }
          normalizedCloneUrls.push(trimmed);
          seenCloneUrls.add(normalized);
        }
      }
      
      // Final validation: Ensure we have at least one clone URL
      if (normalizedCloneUrls.length === 0) {
        error = 'At least one clone URL is required.';
        loading = false;
        return;
      }
      
      // Final validation for localhost: If we only have localhost URLs, that's an error
      const hasNonLocalhost = normalizedCloneUrls.some(url => 
        !url.includes('localhost') && !url.includes('127.0.0.1')
      );
      if (isLocalhost && !hasNonLocalhost && !torOnionUrl) {
        error = 'Cannot publish with only localhost URLs. You need either:\n' +
          '• A Tor .onion address (configure Tor hidden service and set TOR_ONION_ADDRESS)\n' +
          '• At least one other public clone URL (e.g., GitHub, GitLab, or another GitRepublic instance)';
        loading = false;
        return;
      }
      
      // Normalize and deduplicate web URLs
      const normalizedWebUrls: string[] = [];
      const seenWebUrls = new Set<string>();
      for (const url of webUrls) {
        const trimmed = url.trim();
        if (!trimmed) continue;
        
        // Normalize for comparison
        const normalized = trimmed.toLowerCase().replace(/\/+$/, '');
        if (!seenWebUrls.has(normalized)) {
          // Validate format
          const urlError = validateWebUrl(trimmed);
          if (urlError) {
            error = `Invalid web URL: ${trimmed}\n${urlError}`;
            loading = false;
            return;
          }
          normalizedWebUrls.push(trimmed);
          seenWebUrls.add(normalized);
        }
      }

      // Normalize, convert, and deduplicate maintainers (convert npubs to hex pubkeys)
      const normalizedMaintainers: string[] = [];
      const seenMaintainers = new Set<string>();
      for (const maintainer of maintainers) {
        const trimmed = maintainer.trim();
        if (!trimmed) continue;
        
        let hexPubkey: string;
        // Convert npub to hex if needed
        if (trimmed.startsWith('npub')) {
          try {
            const decoded = nip19.decode(trimmed);
            if (decoded.type === 'npub') {
              hexPubkey = decoded.data as string;
            } else {
              error = `Invalid maintainer format: ${trimmed}`;
              loading = false;
              return;
            }
          } catch {
            error = `Invalid maintainer npub format: ${trimmed}`;
            loading = false;
            return;
          }
        } else if (trimmed.length === 64 && /^[0-9a-f]+$/i.test(trimmed)) {
          hexPubkey = trimmed.toLowerCase();
        } else {
          error = `Invalid maintainer format: ${trimmed}. Must be npub1... or 64-character hex pubkey`;
          loading = false;
          return;
        }
        
        // Deduplicate by hex pubkey
        if (!seenMaintainers.has(hexPubkey)) {
          normalizedMaintainers.push(hexPubkey);
          seenMaintainers.add(hexPubkey);
        }
      }

      // Normalize and deduplicate relays
      const normalizedRelays: string[] = [];
      const seenRelays = new Set<string>();
      
      // Add user relays first
      for (const relay of relays) {
        const trimmed = relay.trim().toLowerCase();
        if (!trimmed) continue;
        
        // Validate relay URL format
        if (!trimmed.startsWith('ws://') && !trimmed.startsWith('wss://')) {
          error = `Invalid relay URL format: ${relay}. Must start with ws:// or wss://`;
          loading = false;
          return;
        }
        
        if (!seenRelays.has(trimmed)) {
          normalizedRelays.push(relay.trim()); // Keep original case
          seenRelays.add(trimmed);
        }
      }
      
      // Add default relays that aren't already included
      for (const defaultRelay of DEFAULT_NOSTR_RELAYS) {
        const normalized = defaultRelay.toLowerCase();
        if (!seenRelays.has(normalized)) {
          normalizedRelays.push(defaultRelay);
          seenRelays.add(normalized);
        }
      }

      // Normalize and deduplicate blossoms
      const normalizedBlossoms: string[] = [];
      const seenBlossoms = new Set<string>();
      for (const blossom of blossoms) {
        const trimmed = blossom.trim();
        if (!trimmed) continue;
        
        // Validate blossom format (should be a URL or identifier)
        if (!isValidUrl(trimmed) && !/^[a-zA-Z0-9_-]+$/.test(trimmed)) {
          error = `Invalid blossom format: ${trimmed}. Must be a valid URL or identifier`;
          loading = false;
          return;
        }
        
        const normalized = trimmed.toLowerCase();
        if (!seenBlossoms.has(normalized)) {
          normalizedBlossoms.push(trimmed); // Keep original case
          seenBlossoms.add(normalized);
        }
      }

      // Normalize and deduplicate documentation
      const normalizedDocumentation: string[] = [];
      const seenDocumentation = new Set<string>();
      for (const doc of documentation) {
        const trimmed = doc.trim();
        if (!trimmed) continue;
        
        // Validate format
        const docError = validateDocumentation(trimmed);
        if (docError) {
          error = `Invalid documentation format: ${trimmed}\n${docError}`;
          loading = false;
          return;
        }
        
        // Normalize for comparison
        const normalized = trimmed.toLowerCase();
        if (!seenDocumentation.has(normalized)) {
          normalizedDocumentation.push(trimmed); // Keep original case
          seenDocumentation.add(normalized);
        }
      }

      // Normalize and deduplicate tags/labels (excluding 'private' and 'fork')
      const normalizedTags: string[] = [];
      const seenTags = new Set<string>();
      for (const tag of tags) {
        const trimmed = tag.trim().toLowerCase();
        if (!trimmed) continue;
        if (trimmed === 'private' || trimmed === 'fork') continue; // Handled separately
        
        // Validate tag format (alphanumeric, hyphens, underscores)
        if (!/^[a-z0-9_-]+$/.test(trimmed)) {
          error = `Invalid tag format: ${tag}. Tags can only contain lowercase letters, numbers, hyphens, and underscores`;
          loading = false;
          return;
        }
        
        if (!seenTags.has(trimmed)) {
          normalizedTags.push(trimmed);
          seenTags.add(trimmed);
        }
      }

      // Normalize description, alt, and other text fields
      const normalizedDescription = description.trim();
      const normalizedAlt = alt.trim();
      const normalizedImageUrl = imageUrl.trim();
      const normalizedBannerUrl = bannerUrl.trim();
      const normalizedEarliestCommit = earliestCommit.trim();

      // Validate description length
      if (normalizedDescription.length > 1000) {
        error = 'Description is too long (maximum 1000 characters).';
        loading = false;
        return;
      }

      // Validate alt text length
      if (normalizedAlt.length > 500) {
        error = 'Alt text is too long (maximum 500 characters).';
        loading = false;
        return;
      }

      // Validate image URLs if provided
      if (normalizedImageUrl) {
        const imageError = validateImageUrl(normalizedImageUrl);
        if (imageError) {
          error = `Invalid image URL: ${imageError}`;
          loading = false;
          return;
        }
      }

      if (normalizedBannerUrl) {
        const bannerError = validateImageUrl(normalizedBannerUrl);
        if (bannerError) {
          error = `Invalid banner URL: ${bannerError}`;
          loading = false;
          return;
        }
      }

      // Validate earliest commit format if provided
      if (normalizedEarliestCommit && !/^[0-9a-f]{40}$/i.test(normalizedEarliestCommit)) {
        error = `Invalid earliest commit format: ${normalizedEarliestCommit}. Must be a 40-character hex SHA-1 hash`;
        loading = false;
        return;
      }

      // Use normalized and deduplicated data
      const allCloneUrls = normalizedCloneUrls;
      const allWebUrls = normalizedWebUrls;
      const allMaintainers = normalizedMaintainers;
      const allRelays = normalizedRelays;
      const allBlossoms = normalizedBlossoms;
      const allDocumentation = normalizedDocumentation;
      const allTags = normalizedTags;

      // Build event tags - use single tag with multiple values (NIP-34 format)
      // All data has been normalized, deduplicated, and validated above
      const eventTags: string[][] = [
        ['d', dTag],
        ['name', repoName.trim()],
        ...(normalizedDescription ? [['description', normalizedDescription]] : []),
        ...(allCloneUrls.length > 0 ? [['clone', ...allCloneUrls]] : []), // Single tag with all clone URLs
        ...(allWebUrls.length > 0 ? [['web', ...allWebUrls]] : []), // Single tag with all web URLs
        ...(allMaintainers.length > 0 ? [['maintainers', ...allMaintainers]] : []), // Single tag with all maintainers (hex pubkeys)
        ...(allRelays.length > 0 ? [['relays', ...allRelays]] : []), // Single tag with all relays
        ...(allBlossoms.length > 0 ? [['blossoms', ...allBlossoms]] : []), // Single tag with all blossoms
        ...allDocumentation.map(d => ['documentation', d]), // Documentation can have relay hints, so keep separate
        ...allTags.map(t => ['t', t]),
        ...(normalizedImageUrl ? [['image', normalizedImageUrl]] : []),
        ...(normalizedBannerUrl ? [['banner', normalizedBannerUrl]] : []),
        ...(normalizedAlt ? [['alt', normalizedAlt]] : []),
        ...(normalizedEarliestCommit ? [['r', normalizedEarliestCommit, 'euc']] : [])
      ];

      // Add fork tags if this is a fork
      if (isFork && forkOriginalRepo.trim()) {
        let forkAddress = forkOriginalRepo.trim();
        let forkOwnerPubkey: string | null = null;
        let isValidFormat = false;

        // Parse the fork identifier - could be:
        // 1. naddr format (decode to get pubkey and repo)
        // 2. npub/repo format (need to construct a tag)
        // 3. Already in 30617:owner:repo format
        if (forkAddress.startsWith('naddr')) {
          try {
            const decoded = nip19.decode(forkAddress);
            if (decoded.type === 'naddr') {
              const data = decoded.data as { pubkey: string; kind: number; identifier: string };
              if (data.pubkey && data.identifier) {
                forkAddress = `${KIND.REPO_ANNOUNCEMENT}:${data.pubkey}:${data.identifier}`;
                forkOwnerPubkey = data.pubkey;
                isValidFormat = true;
              }
            }
          } catch {
            // Invalid naddr, will be caught by validation below
          }
        } else if (forkAddress.includes('/') && !forkAddress.startsWith('30617:')) {
          // Assume npub/repo format
          const parts = forkAddress.split('/');
          if (parts.length === 2 && parts[1].trim()) {
            try {
              const decoded = nip19.decode(parts[0]);
              if (decoded.type === 'npub') {
                forkOwnerPubkey = decoded.data as string;
                forkAddress = `${KIND.REPO_ANNOUNCEMENT}:${forkOwnerPubkey}:${parts[1].trim()}`;
                isValidFormat = true;
              }
            } catch {
              // Invalid npub, try as hex pubkey
              if (parts[0].length === 64 && /^[0-9a-f]+$/i.test(parts[0])) {
                forkOwnerPubkey = parts[0];
                forkAddress = `${KIND.REPO_ANNOUNCEMENT}:${forkOwnerPubkey}:${parts[1].trim()}`;
                isValidFormat = true;
              }
            }
          }
        } else if (forkAddress.startsWith('30617:')) {
          // Already in correct format, validate and extract owner pubkey
          const parts = forkAddress.split(':');
          if (parts.length >= 3 && parts[1] && parts[2]) {
            forkOwnerPubkey = parts[1];
            isValidFormat = true;
          }
        }

        // Validate the final format: must be 30617:pubkey:repo
        // Always validate regardless of parsing success to catch any edge cases
        const parts = forkAddress.split(':');
        if (parts.length >= 3) {
          const kind = parts[0];
          const pubkey = parts[1];
          const repo = parts[2];

          // Validate format
          if (kind !== String(KIND.REPO_ANNOUNCEMENT)) {
            isValidFormat = false;
          } else if (!pubkey || pubkey.length !== 64 || !/^[0-9a-f]+$/i.test(pubkey)) {
            isValidFormat = false;
          } else if (!repo || !repo.trim()) {
            isValidFormat = false;
          } else {
            // Format is valid, ensure isValidFormat is true
            isValidFormat = true;
          }
        } else {
          isValidFormat = false;
        }

        if (!isValidFormat) {
          error = 'Invalid fork repository format. Please use one of:\n' +
            '• naddr format: naddr1...\n' +
            '• npub/repo format: npub1abc.../repo-name\n' +
            '• Repository address: 30617:owner-pubkey:repo-name';
          loading = false;
          return;
        }

        // Add a tag (required for fork identification)
        eventTags.push(['a', forkAddress]);

        // Add p tag if we have the owner pubkey
        if (forkOwnerPubkey) {
          eventTags.push(['p', forkOwnerPubkey]);
        }

        // Add 'fork' tag if not already in tags
        if (!allTags.includes('fork')) {
          eventTags.push(['t', 'fork']);
        }
      }

      // Add visibility tag
      if (visibility !== 'public') {
        eventTags.push(['visibility', visibility]);
      }
      
      // Add project-relay tags (required for unlisted/restricted, optional for others)
      const normalizedProjectRelays = projectRelays
        .map(r => r.trim())
        .filter(r => r && (r.startsWith('ws://') || r.startsWith('wss://')));
      
      for (const relay of normalizedProjectRelays) {
        eventTags.push(['project-relay', relay]);
      }
      
      // Warn if unlisted/restricted but no project-relay
      if ((visibility === 'unlisted' || visibility === 'restricted') && normalizedProjectRelays.length === 0) {
        error = 'Project relay is required for unlisted and restricted repositories. Please add at least one project-relay.';
        loading = false;
        return;
      }

      // Remove any existing client tags (from other clients) and ensure only our client tag exists
      // Filter out any client tags
      const filteredEventTags = eventTags.filter(tag => tag[0] !== 'client');
      
      // Add our client tag if enabled (ensuring only one client tag exists)
      if (addClientTag) {
        filteredEventTags.push(['client', 'gitrepublic-web']);
      }
      
      // Replace eventTags with filtered version
      eventTags.length = 0;
      eventTags.push(...filteredEventTags);

      // We'll generate the announcement file content after signing (it's just the full event JSON)

      // Build event
      const eventTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
        kind: KIND.REPO_ANNOUNCEMENT,
        pubkey,
        created_at: Math.floor(Date.now() / 1000),
        content: '', // Empty per NIP-34
        tags: eventTags
      };

      // Sign with NIP-07
      console.log('Signing repository announcement event...');
      const signedEvent = await signEventWithNIP07(eventTemplate);
      console.log('Event signed successfully, event ID:', signedEvent.id);

      // Generate announcement file content (just the full signed event JSON)
      // The server will commit this to prove ownership - no need for a separate verification file event
      const { generateVerificationFile } = await import('../../lib/services/nostr/repo-verification.js');
      const announcementFileContent = generateVerificationFile(signedEvent, pubkey);
      
      // Create a signed event (kind 1642) with the announcement file content
      // This allows the server to fetch and commit the client-signed announcement
      const announcementFileEventTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
        kind: 1642, // Custom kind for announcement file
        pubkey,
        created_at: Math.floor(Date.now() / 1000),
        content: announcementFileContent,
        tags: [
          ['e', signedEvent.id, '', 'announcement'],
          ['d', dTag]
        ]
      };
      
      const signedAnnouncementFileEvent = await signEventWithNIP07(announcementFileEventTemplate);

      // Get user's inbox/outbox relays (from kind 10002) using comprehensive relay set
      console.log('Fetching user relays from comprehensive relay set...');
      const { getUserRelays } = await import('../../lib/services/nostr/user-relays.js');
      
      // Use comprehensive relay set including ALL search relays and default relays
      // This ensures we can find the user's kind 10002 event even if it's on a less common relay
      const allSearchRelays = [...new Set([...DEFAULT_NOSTR_SEARCH_RELAYS, ...DEFAULT_NOSTR_RELAYS])];
      console.log('Querying kind 10002 from relays:', allSearchRelays);
      const fullRelayClient = new NostrClient(allSearchRelays);
      
      let userRelays: string[] = [];
      try {
        const { inbox, outbox } = await getUserRelays(pubkey, fullRelayClient);
        console.log('User relays fetched - inbox:', inbox.length, 'outbox:', outbox.length);
        if (inbox.length > 0) console.log('Inbox relays:', inbox);
        if (outbox.length > 0) console.log('Outbox relays:', outbox);
        
        // If we found user relays, use them; otherwise fall back to defaults
        if (outbox.length > 0) {
          // Use user's outbox relays (these are the relays the user prefers for publishing)
          // Combine with defaults as fallback
          userRelays = combineRelays(outbox, DEFAULT_NOSTR_RELAYS);
          console.log('Using user outbox relays for publishing:', outbox);
        } else if (inbox.length > 0) {
          // If no outbox but have inbox, use inbox relays (some users only set inbox)
          userRelays = combineRelays(inbox, DEFAULT_NOSTR_RELAYS);
          console.log('Using user inbox relays for publishing (no outbox found):', inbox);
        } else {
          // No user relays found, use defaults
          userRelays = DEFAULT_NOSTR_RELAYS;
          console.warn('No user relays found in kind 10002, using default relays only');
        }
      } catch (err) {
        console.warn('Failed to fetch user relays, using defaults:', err);
        // Fall back to default relays if user relay fetch fails
        userRelays = DEFAULT_NOSTR_RELAYS;
      }
      
      // Ensure we have at least some relays to publish to
      if (userRelays.length === 0) {
        console.warn('No relays available, using default relays');
        userRelays = DEFAULT_NOSTR_RELAYS;
      }
      
      console.log('Final relay set for publishing:', userRelays);
      
      console.log('Using relays for publishing:', userRelays);

      // Publish announcement file event first (so it's available when server provisions)
      console.log('Publishing announcement file event...');
      await publishWithRetry(nostrClient, signedAnnouncementFileEvent, userRelays, 2).catch(err => {
        console.warn('Failed to publish announcement file event:', err);
        // Continue anyway - server can generate it as fallback
      });

      // Publish repository announcement with retry logic
      let publishResult = await publishWithRetry(nostrClient, signedEvent, userRelays, 2);
      console.log('Publish result:', publishResult);

      if (publishResult.success.length > 0) {
        console.log(`Successfully published to ${publishResult.success.length} relay(s):`, publishResult.success);
        // Create and publish initial ownership proof (self-transfer event)
        const { OwnershipTransferService } = await import('../../lib/services/nostr/ownership-transfer-service.js');
        const ownershipService = new OwnershipTransferService(userRelays);
        
        const initialOwnershipEvent = ownershipService.createInitialOwnershipEvent(pubkey, dTag);
        const signedOwnershipEvent = await signEventWithNIP07(initialOwnershipEvent);
        
        // Publish initial ownership event (don't fail if this fails, announcement is already published)
        await nostrClient.publishEvent(signedOwnershipEvent, userRelays).catch(err => {
          console.warn('Failed to publish initial ownership event:', err);
        });

        success = true;
        // Redirect to the newly created repository page
        // Use invalidateAll to ensure the repos list refreshes
        const userNpub = nip19.npubEncode(pubkey);
        
        // Check if this is a transfer completion (from query params)
        const urlParams = $page.url.searchParams;
        const isTransfer = urlParams.get('transfer') === 'true';
        
        setTimeout(() => {
          // Invalidate all caches and redirect
          if (isTransfer) {
            // After transfer, redirect to repos page to see updated state
            goto('/repos', { invalidateAll: true, replaceState: false });
          } else {
            goto(`/repos/${userNpub}/${dTag}`, { invalidateAll: true, replaceState: false });
          }
        }, 2000);
      } else {
        // Show detailed error information
        const errorDetails = publishResult.failed.map(f => `${f.relay}: ${f.error}`).join('\n');
        error = `Failed to publish to any relays after retries.\n\nRelays attempted: ${userRelays.length}\n\nErrors:\n${errorDetails || 'Unknown error'}\n\nPlease check:\n• Your internet connection\n• Relay availability\n• Try again in a few moments`;
        console.error('Failed to publish repository announcement:', publishResult);
      }

    } catch (e) {
      error = `Failed to create repository announcement: ${String(e)}`;
    } finally {
      loading = false;
    }
  }
</script>

<div class="container">
  <header>
    <h1>Create or Update Repository Announcement</h1>
  </header>

  <main>
    {#if !hasUnlimitedAccess($userStore.userLevel)}
      <div class="warning">
        <p>Only users with unlimited access can create or register repositories.</p>
        <p>Please log in with an account that has write access to this server's associated Nostr relays.</p>
        <button onclick={() => goto('/')} class="button-primary">Go to Home</button>
      </div>
    {:else if !nip07Available}
      <div class="warning">
        <p>NIP-07 browser extension is required to sign repository announcements.</p>
        <p>Please install a Nostr browser extension (like Alby, nos2x, or similar).</p>
      </div>
    {/if}

    {#if error}
      <div class="error">{error}</div>
    {/if}

    {#if success}
      <div class="success">
        Repository announcement published successfully! Redirecting...
      </div>
    {/if}

    <form onsubmit={(e) => { e.preventDefault(); submit(); }}>
      <div class="form-group">
        <label for="existing-repo-ref">
          Load Existing Repository (optional)
          <small>Enter hex event ID, nevent, or naddr to update an existing announcement</small>
        </label>
        <div class="input-group">
          <input
            id="existing-repo-ref"
            type="text"
            bind:value={existingRepoRef}
            placeholder="hex event ID, nevent1..., or naddr1..."
            disabled={loading || loadingExisting}
          />
          <button
            type="button"
            onclick={() => lookupRepoAnnouncement(existingRepoRef || '', 'existingRepoRef')}
            disabled={loading || loadingExisting || !existingRepoRef.trim()}
            class="lookup-button"
            title="Search for repository announcements (supports hex ID, nevent, naddr, or search by name)"
          >
            {#if lookupLoading['repo-existingRepoRef']}
              <span class="loading-text">Loading...</span>
            {:else}
              <img src="/icons/search.svg" alt="Search" class="icon-small" />
            {/if}
          </button>
          <button
            type="button"
            onclick={loadExistingRepo}
            disabled={loading || loadingExisting || !existingRepoRef.trim()}
          >
            {loadingExisting ? 'Loading...' : 'Load'}
          </button>
        </div>
        {#if lookupError['repo-existingRepoRef']}
          <div class="lookup-error">{lookupError['repo-existingRepoRef']}</div>
        {/if}
        {#if lookupResults['repo-existingRepoRef']}
          <div class="lookup-results">
            <div class="lookup-results-header">
              <span>Found {lookupResults['repo-existingRepoRef'].length} repository announcement(s):</span>
              <button
                type="button"
                onclick={() => clearLookupResults('repo-existingRepoRef')}
                class="clear-lookup-button"
                title="Clear results"
              >
                <img src="/icons/x.svg" alt="Clear" class="icon-small" />
              </button>
            </div>
            {#each (lookupResults['repo-existingRepoRef'] || []) as result}
              {#if 'tags' in result}
                {@const event = result as NostrEvent}
                {@const nameTag = event.tags.find((t: string[]) => t[0] === 'name')?.[1]}
                {@const dTag = event.tags.find((t: string[]) => t[0] === 'd')?.[1]}
                {@const descTag = event.tags.find((t: string[]) => t[0] === 'description')?.[1]}
                {@const imageTag = event.tags.find((t: string[]) => t[0] === 'image')?.[1]}
                {@const ownerNpub = nip19.npubEncode(event.pubkey)}
                {@const tags = event.tags.filter((t: string[]) => t[0] === 't' && t[1] && t[1] !== 'private' && t[1] !== 'fork').map((t: string[]) => t[1])}
                <div 
                  class="lookup-result-item repo-result" 
                  role="button"
                  tabindex="0"
                  onclick={() => selectRepoResult(event, 'existingRepoRef')}
                  onkeydown={(e) => {
                    if (e.key === 'Enter' || e.key === ' ') {
                      e.preventDefault();
                      selectRepoResult(event, 'existingRepoRef');
                    }
                  }}
              >
                <div class="result-header">
                  {#if imageTag}
                    <img src={imageTag} alt="" class="result-image" />
                  {/if}
                  <div class="result-info">
                    <strong>{nameTag || dTag || 'Unnamed'}</strong>
                    {#if dTag}
                      <small class="d-tag">d-tag: {dTag}</small>
                    {/if}
                    {#if descTag}
                      <p class="result-description">{descTag}</p>
                    {/if}
                    <div class="result-meta">
                      <small>Owner: {ownerNpub.slice(0, 16)}...</small>
                      <small>Event: {event.id.slice(0, 16)}...</small>
                    </div>
                    {#if tags.length > 0}
                      <div class="result-tags">
                        {#each tags as tag}
                          <span class="tag-badge">#{tag}</span>
                        {/each}
                      </div>
                    {/if}
                  </div>
                </div>
              </div>
              {/if}
            {/each}
          </div>
        {/if}
      </div>

      <div class="form-group">
        <label for="repo-name">
          Repository Name *
          <small>Enter a normal name (e.g., "My Awesome Repo"). It will be automatically converted to a d-tag format (lowercase with hyphens, such as my-awesome-repo).</small>
        </label>
        <input
          id="repo-name"
          type="text"
          bind:value={repoName}
          placeholder="My Awesome Repo"
          required
          disabled={loading}
        />
      </div>

      <div class="form-group">
        <label for="description">
          Description
        </label>
        <textarea
          id="description"
          bind:value={description}
          placeholder="Repository description"
          rows={3}
          disabled={loading}
        ></textarea>
      </div>

      <div class="form-group">
        <div class="label">
          Clone URLs
          <small>
            {#if ($page.data.gitDomain || 'localhost:6543').startsWith('localhost') || ($page.data.gitDomain || 'localhost:6543').startsWith('127.0.0.1')}
              <strong>Note:</strong> Your server is using localhost, which won't work for others. You must add at least one public clone URL (e.g., GitHub, GitLab) or configure a Tor .onion address.
            {:else}
              {$page.data.gitDomain || 'localhost:6543'} will be added automatically, but you can add any existing ones here.
            {/if}
          </small>
        </div>
        {#each cloneUrls as url, index}
          <div class="input-group">
            <input
              type="text"
              value={url}
              oninput={(e) => updateCloneUrl(index, e.currentTarget.value)}
              placeholder="https://github.com/user/repo.git"
              disabled={loading}
            />
            {#if cloneUrls.length > 1}
              <button
                type="button"
                onclick={() => removeCloneUrl(index)}
                disabled={loading}
              >
                Remove
              </button>
            {/if}
          </div>
        {/each}
        <button
          type="button"
          onclick={addCloneUrl}
          disabled={loading}
          class="add-button"
        >
          + Add Clone URL
        </button>
      </div>

      <div class="form-group">
        <div class="label">
          Web URLs (optional)
          <small>Webpage URLs for browsing the repository (e.g., GitHub/GitLab web interface). Hover over a URL to preview it.</small>
        </div>
        {#each webUrls as url, index}
          <div class="input-group url-preview-container">
            <input
              type="text"
              value={url}
              oninput={(e) => updateWebUrl(index, e.currentTarget.value)}
              onmouseenter={() => handleWebUrlHover(index, url)}
              onmouseleave={handleWebUrlLeave}
              placeholder="https://github.com/user/repo"
              disabled={loading}
            />
            {#if webUrls.length > 1}
              <button
                type="button"
                onclick={() => removeWebUrl(index)}
                disabled={loading}
              >
                Remove
              </button>
            {/if}
            {#if previewingUrlIndex === index && previewUrl}
              <div class="url-preview" role="tooltip">
                {#if previewLoading}
                  <div class="preview-loading">Loading preview...</div>
                {:else if previewError}
                  <div class="preview-error">
                    <strong>
                      <img src="/icons/alert-triangle.svg" alt="Warning" class="icon-inline" />
                      Warning:
                    </strong> {previewError}
                  </div>
                {/if}
                <iframe
                  src={previewUrl}
                  title="URL Preview"
                  class="preview-iframe"
                  sandbox="allow-same-origin allow-scripts allow-forms allow-popups"
                ></iframe>
                <div class="preview-url-display">{previewUrl}</div>
              </div>
            {/if}
          </div>
        {/each}
        <button
          type="button"
          onclick={addWebUrl}
          disabled={loading}
          class="add-button"
        >
          + Add Web URL
        </button>
      </div>

      <div class="form-group">
        <div class="label">
          Maintainers (optional)
          <small>Other maintainer pubkeys (npub or hex format). Example: npub1abc... or hex pubkey</small>
        </div>
        {#each maintainers as maintainer, index}
          <div class="input-group">
            <input
              type="text"
              value={maintainer}
              oninput={(e) => updateMaintainer(index, e.currentTarget.value)}
              placeholder="npub1abc... or hex pubkey"
              disabled={loading}
            />
            <button
              type="button"
              onclick={() => lookupNpub(maintainer || '', 'maintainers', index)}
              disabled={loading || !maintainer.trim()}
              class="lookup-button"
              title="Lookup npub or search by name"
            >
              {#if lookupLoading[`npub-maintainers-${index}`]}
                <span class="loading-text">Loading...</span>
              {:else}
                <img src="/icons/search.svg" alt="Search" class="icon-small" />
              {/if}
            </button>
            {#if maintainers.length > 1}
              <button
                type="button"
                onclick={() => removeMaintainer(index)}
                disabled={loading}
              >
                Remove
              </button>
            {/if}
          </div>
          {#if lookupError[`npub-maintainers-${index}`]}
            <div class="lookup-error">{lookupError[`npub-maintainers-${index}`]}</div>
          {/if}
          {#if lookupResults[`npub-maintainers-${index}`]}
            <div class="lookup-results">
              <div class="lookup-results-header">
                <span>Found {(lookupResults[`npub-maintainers-${index}`] || []).length} profile(s):</span>
                <button
                  type="button"
                  onclick={() => clearLookupResults(`npub-maintainers-${index}`)}
                  class="clear-lookup-button"
                  title="Clear results"
                >
                  <img src="/icons/x.svg" alt="Clear" class="icon-small" />
                </button>
              </div>
              {#each (lookupResults[`npub-maintainers-${index}`] || []) as result}
                {#if 'npub' in result}
                  {@const profile = result as ProfileData}
                  <div 
                    class="lookup-result-item profile-result" 
                    role="button"
                    tabindex="0"
                    onclick={() => selectNpubResult(profile, 'maintainers', index)}
                    onkeydown={(e) => {
                      if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault();
                        selectNpubResult(profile, 'maintainers', index);
                      }
                    }}
                  >
                    <div class="result-header">
                      {#if profile.picture}
                        <img src={profile.picture} alt="" class="result-avatar" />
                      {:else}
                        <div class="result-avatar-placeholder">
                          {(profile.name || profile.npub).slice(0, 2).toUpperCase()}
                        </div>
                      {/if}
                      <div class="result-info">
                        <strong>{profile.name || 'Unknown'}</strong>
                        {#if profile.about}
                          <p class="result-description">{profile.about}</p>
                        {/if}
                        <small class="npub-display">{profile.npub}</small>
                      </div>
                    </div>
                  </div>
                {/if}
              {/each}
            </div>
          {/if}
        {/each}
        <button
          type="button"
          onclick={addMaintainer}
          disabled={loading}
          class="add-button"
        >
          + Add Maintainer
        </button>
      </div>

      <div class="form-group">
        <div class="label">
          Relays (optional)
          <small>Nostr relays that this repository will monitor for patches and issues. Default relays will be added automatically.</small>
        </div>
        {#each relays as relay, index}
          <div class="input-group">
            <input
              type="text"
              value={relay}
              oninput={(e) => updateRelay(index, e.currentTarget.value)}
              placeholder="wss://relay.example.com"
              disabled={loading}
            />
            {#if relays.length > 1}
              <button
                type="button"
                onclick={() => removeRelay(index)}
                disabled={loading}
              >
                Remove
              </button>
            {/if}
          </div>
        {/each}
        <button
          type="button"
          onclick={addRelay}
          disabled={loading}
          class="add-button"
        >
          + Add Relay
        </button>
      </div>

      <div class="form-group">
        <div class="label">
          Blossoms (optional)
          <small>Blossom URLs for this repository. These are preserved but not actively used by GitRepublic.</small>
        </div>
        {#each blossoms as blossom, index}
          <div class="input-group">
            <input
              type="text"
              value={blossom}
              oninput={(e) => updateBlossom(index, e.currentTarget.value)}
              placeholder="https://example.com"
              disabled={loading}
            />
            {#if blossoms.length > 1}
              <button
                type="button"
                onclick={() => removeBlossom(index)}
                disabled={loading}
              >
                Remove
              </button>
            {/if}
          </div>
        {/each}
        <button
          type="button"
          onclick={addBlossom}
          disabled={loading}
          class="add-button"
        >
          + Add Blossom
        </button>
      </div>

      <div class="form-group">
        <label for="image-url">
          Repository Image URL (optional)
          <small>URL to a repository image/logo. Example: https://example.com/repo-logo.png</small>
        </label>
        <input
          id="image-url"
          type="url"
          bind:value={imageUrl}
          placeholder="https://example.com/repo-logo.png"
          disabled={loading}
        />
      </div>

      <div class="form-group">
        <label for="banner-url">
          Repository Banner URL (optional)
          <small>URL to a repository banner image. Example: https://example.com/repo-banner.png</small>
        </label>
        <input
          id="banner-url"
          type="url"
          bind:value={bannerUrl}
          placeholder="https://example.com/repo-banner.png"
          disabled={loading}
        />
      </div>

      <div class="form-group">
        <label for="earliest-commit">
          Earliest Unique Commit ID (optional)
          <small>Root commit ID or first commit after a permanent fork. Used to identify forks. Example: abc123def456...</small>
        </label>
        <input
          id="earliest-commit"
          type="text"
          bind:value={earliestCommit}
          placeholder="abc123def456..."
          disabled={loading}
        />
      </div>

      <div class="form-group">
        <div class="label">
          Tags/Labels (optional)
          <small>Hashtags or labels for the repository. Examples: "javascript", "web-app", "personal-fork" (indicates author isn't a maintainer)</small>
        </div>
        {#each tags as tag, index}
          <div class="input-group">
            <input
              type="text"
              value={tag}
              oninput={(e) => updateTag(index, e.currentTarget.value)}
              placeholder="javascript"
              disabled={loading}
            />
            {#if tags.length > 1}
              <button
                type="button"
                onclick={() => removeTag(index)}
                disabled={loading}
              >
                Remove
              </button>
            {/if}
          </div>
        {/each}
        <button
          type="button"
          onclick={addTag}
          disabled={loading}
          class="add-button"
        >
          + Add Tag
        </button>
      </div>

      <div class="form-group">
        <label for="alt">
          Alt Text (optional)
          <small>Alternative text/description for the repository. Example: "git repository: Alexandria"</small>
        </label>
        <input
          id="alt"
          type="text"
          bind:value={alt}
          placeholder="git repository: My Awesome Repo"
          disabled={loading}
        />
      </div>

      <div class="form-group">
        <div class="label">
          Documentation (optional)
          <small>Documentation event addresses (naddr format). Example: 30818:fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1:nkbip-01</small>
        </div>
        {#each documentation as doc, index}
          <div class="input-group">
            <input
              type="text"
              value={doc}
              oninput={(e) => updateDocumentation(index, e.currentTarget.value)}
              placeholder="naddr1... or 30818:pubkey:d-tag"
              disabled={loading}
            />
            <button
              type="button"
              onclick={() => lookupDocumentation(index)}
              disabled={loading || lookupLoading[`doc-${index}`]}
              class="lookup-button"
              title="Lookup naddr and convert to documentation format"
            >
              {lookupLoading[`doc-${index}`] ? '...' : 'Lookup'}
            </button>
            {#if documentation.length > 1}
              <button
                type="button"
                onclick={() => removeDocumentation(index)}
                disabled={loading}
              >
                Remove
              </button>
            {/if}
          </div>
          {#if lookupError[`doc-${index}`]}
            <div class="error-text">{lookupError[`doc-${index}`]}</div>
          {/if}
          {#if lookupResults[`doc-${index}`]}
            <div class="lookup-results">
              <small><img src="/icons/check.svg" alt="Success" class="icon-inline" style="width: 14px; height: 14px; vertical-align: middle; margin-right: 4px;" /> Documentation address converted successfully</small>
            </div>
          {/if}
        {/each}
        <button
          type="button"
          onclick={addDocumentation}
          disabled={loading}
          class="add-button"
        >
          + Add Documentation
        </button>
      </div>

      <div class="form-group">
        <label class="checkbox-label">
          <input
            type="checkbox"
            bind:checked={isFork}
            disabled={loading}
          />
          <div>
            <span>This is a Fork</span>
            <small>Check if this repository is a fork of another repository</small>
          </div>
        </label>
      </div>

      {#if isFork}
        <div class="form-group">
          <label for="fork-original-repo">
            Original Repository *
            <small>Identify the repository this is forked from. You can enter:<br/>
            • naddr format: naddr1...<br/>
            • npub/repo format: npub1abc.../repo-name<br/>
            • Repository address: 30617:owner-pubkey:repo-name</small>
          </label>
          <div class="input-group">
            <input
              id="fork-original-repo"
              type="text"
              bind:value={forkOriginalRepo}
              placeholder="npub1abc.../original-repo or naddr1..."
              required={isFork}
              disabled={loading}
            />
            <button
              type="button"
              onclick={() => lookupRepoAnnouncement(forkOriginalRepo || '', 'forkOriginalRepo')}
              disabled={loading || !forkOriginalRepo.trim()}
              class="lookup-button"
              title="Search for repository announcements"
            >
              {#if lookupLoading['repo-forkOriginalRepo']}
                <span class="loading-text">Loading...</span>
              {:else}
                <img src="/icons/search.svg" alt="Search" class="icon-small" />
              {/if}
            </button>
          </div>
          {#if lookupError['repo-forkOriginalRepo']}
            <div class="lookup-error">{lookupError['repo-forkOriginalRepo']}</div>
          {/if}
          {#if lookupResults['repo-forkOriginalRepo']}
            <div class="lookup-results">
              <div class="lookup-results-header">
                <span>Found {lookupResults['repo-forkOriginalRepo'].length} repository announcement(s):</span>
                <button
                  type="button"
                  onclick={() => clearLookupResults('repo-forkOriginalRepo')}
                  class="clear-lookup-button"
                  title="Clear results"
                >
                  <img src="/icons/x.svg" alt="Clear" class="icon-small" />
                </button>
              </div>
              {#each (lookupResults['repo-forkOriginalRepo'] || []) as result}
                {#if 'tags' in result}
                  {@const event = result as NostrEvent}
                  {@const nameTag = event.tags.find((t: string[]) => t[0] === 'name')?.[1]}
                  {@const dTag = event.tags.find((t: string[]) => t[0] === 'd')?.[1]}
                  {@const descTag = event.tags.find((t: string[]) => t[0] === 'description')?.[1]}
                  {@const imageTag = event.tags.find((t: string[]) => t[0] === 'image')?.[1]}
                  {@const ownerNpub = nip19.npubEncode(event.pubkey)}
                  {@const tags = event.tags.filter((t: string[]) => t[0] === 't' && t[1] && t[1] !== 'private' && t[1] !== 'fork').map((t: string[]) => t[1])}
                  <div 
                    class="lookup-result-item repo-result" 
                    role="button"
                    tabindex="0"
                    onclick={() => selectRepoResult(event, 'forkOriginalRepo')}
                    onkeydown={(e) => {
                      if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault();
                        selectRepoResult(event, 'forkOriginalRepo');
                      }
                    }}
                >
                  <div class="result-header">
                    {#if imageTag}
                      <img src={imageTag} alt="" class="result-image" />
                    {/if}
                    <div class="result-info">
                      <strong>{nameTag || dTag || 'Unnamed'}</strong>
                      {#if dTag}
                        <small class="d-tag">d-tag: {dTag}</small>
                      {/if}
                      {#if descTag}
                        <p class="result-description">{descTag}</p>
                      {/if}
                      <div class="result-meta">
                        <small>Owner: {ownerNpub.slice(0, 16)}...</small>
                        <small>Event: {event.id.slice(0, 16)}...</small>
                      </div>
                      {#if tags.length > 0}
                        <div class="result-tags">
                          {#each tags as tag}
                            <span class="tag-badge">#{tag}</span>
                          {/each}
                        </div>
                      {/if}
                    </div>
                  </div>
                </div>
              {/if}
              {/each}
            </div>
          {/if}
        </div>
      {/if}

      <div class="form-group">
        <label for="visibility">
          Repository Visibility *
          <small>
            <strong>Public:</strong> Repository and events are published to all relays and project relay.<br/>
            <strong>Unlisted:</strong> Repository is public but events are only published to project relay.<br/>
            <strong>Restricted:</strong> Repository is private, events are only published to project relay.<br/>
            <strong>Private:</strong> Repository is private, events are not published to relays (git-only).
          </small>
        </label>
        <select
          id="visibility"
          bind:value={visibility}
          disabled={loading}
          required
        >
          <option value="public">Public</option>
          <option value="unlisted">Unlisted</option>
          <option value="restricted">Restricted</option>
          <option value="private">Private</option>
        </select>
      </div>

      {#if visibility === 'unlisted' || visibility === 'restricted' || visibility === 'private'}
        <div class="form-group">
          <div class="label">
            Project Relay(s) {#if visibility === 'unlisted' || visibility === 'restricted'}*{/if}
            <small>
              {#if visibility === 'unlisted' || visibility === 'restricted'}
                Required for unlisted and restricted repositories. Events will be published to these relays only.
              {:else}
                Optional for private repositories. If provided, events will be published to these relays (otherwise git-only).
              {/if}
            </small>
          </div>
          {#each projectRelays as projectRelay, index}
            <div class="input-group">
              <input
                type="text"
                value={projectRelay}
                oninput={(e) => {
                  projectRelays[index] = e.currentTarget.value;
                  projectRelays = [...projectRelays];
                }}
                placeholder="wss://relay.example.com"
                disabled={loading}
                required={visibility === 'unlisted' || visibility === 'restricted'}
              />
              {#if projectRelays.length > 1}
                <button
                  type="button"
                  onclick={() => {
                    projectRelays = projectRelays.filter((_, i) => i !== index);
                  }}
                  disabled={loading}
                >
                  Remove
                </button>
              {/if}
            </div>
          {/each}
          <button
            type="button"
            onclick={() => {
              projectRelays = [...projectRelays, ''];
            }}
            disabled={loading}
            class="add-button"
          >
            + Add Project Relay
          </button>
        </div>
      {/if}

      <div class="form-group">
        <label class="checkbox-label">
          <input
            type="checkbox"
            bind:checked={addClientTag}
            disabled={loading}
          />
          <div>
            <span>Add Client Tag</span>
            <small>Add a client tag to identify this repository as created with GitRepublic (checked by default)</small>
          </div>
        </label>
      </div>

      <div class="form-actions">
        <button
          type="submit"
          disabled={loading || !nip07Available || !isLoggedIn($userStore.userLevel) || !hasUnlimitedAccess($userStore.userLevel)}
        >
          {loading ? 'Publishing...' : 'Publish Repository Announcement'}
        </button>
        <a href="/" class="cancel-link">Cancel</a>
      </div>
    </form>
  </main>
</div>

