<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import { page } from '$app/stores';
  import { goto } from '$app/navigation';
  import CodeEditor from '$lib/components/CodeEditor.svelte';
  import PRDetail from '$lib/components/PRDetail.svelte';
  import UserBadge from '$lib/components/UserBadge.svelte';
  import EventCopyButton from '$lib/components/EventCopyButton.svelte';
  import RepoHeaderEnhanced from '$lib/components/RepoHeaderEnhanced.svelte';
  import TabsMenu from '$lib/components/TabsMenu.svelte';
  import NostrLinkRenderer from '$lib/components/NostrLinkRenderer.svelte';
  import '$lib/styles/repo.css';
  import { getPublicKeyWithNIP07, isNIP07Available, signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js';
  import { NostrClient } from '$lib/services/nostr/nostr-client.js';
  import { DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_SEARCH_RELAYS, combineRelays } from '$lib/config.js';
  import { getUserRelays } from '$lib/services/nostr/user-relays.js';
  import { BookmarksService } from '$lib/services/nostr/bookmarks-service.js';
  import { KIND } from '$lib/types/nostr.js';
  import { nip19 } from 'nostr-tools';
  import { userStore } from '$lib/stores/user-store.js';
  import { settingsStore } from '$lib/services/settings-store.js';
  // Note: Announcements are now stored in nostr/repo-events.jsonl, not .nostr-announcement
  import type { NostrEvent } from '$lib/types/nostr.js';
  import { hasUnlimitedAccess } from '$lib/utils/user-access.js';
  import { fetchUserEmail, fetchUserName } from '$lib/utils/user-profile.js';

  // Get page data for OpenGraph metadata - use $derived to make it reactive
  const pageData = $derived($page.data as {
    title?: string;
    description?: string;
    image?: string;
    banner?: string;
    repoName?: string;
    repoDescription?: string;
    repoUrl?: string;
    repoCloneUrls?: string[];
    repoMaintainers?: string[];
    repoOwnerPubkey?: string;
    repoLanguage?: string;
    repoTopics?: string[];
    repoWebsite?: string;
    repoIsPrivate?: boolean;
    gitDomain?: string;
  });

  const npub = ($page.params as { npub?: string; repo?: string }).npub || '';
  const repo = ($page.params as { npub?: string; repo?: string }).repo || '';

  let loading = $state(true);
  let error = $state<string | null>(null);
  let repoNotFound = $state(false); // Track if repository doesn't exist
  let files = $state<Array<{ name: string; path: string; type: 'file' | 'directory'; size?: number }>>([]);
  let currentPath = $state('');
  let currentFile = $state<string | null>(null);
  let fileContent = $state('');
  let fileLanguage = $state<'markdown' | 'asciidoc' | 'text'>('text');
  let editedContent = $state('');
  let hasChanges = $state(false);
  let saving = $state(false);
  let branches = $state<Array<string | { name: string; commit?: any }>>([]);
  let currentBranch = $state<string | null>(null);
  let defaultBranch = $state<string | null>(null);
  let commitMessage = $state('');
  let userPubkey = $state<string | null>(null);
  let userPubkeyHex = $state<string | null>(null);
  let showCommitDialog = $state(false);
  let activeTab = $state<'files' | 'history' | 'tags' | 'issues' | 'prs' | 'docs' | 'discussions' | 'patches'>('files');
  let showRepoMenu = $state(false);
  
  // Tabs will be defined as derived after issues and prs are declared
  
  // Auto-save
  let autoSaveInterval: ReturnType<typeof setInterval> | null = null;

  // Load maintainers when page data changes (only once per repo, with guard)
  let lastRepoKey = $state<string | null>(null);
  let maintainersEffectRan = $state(false);
  
  $effect(() => {
    const data = $page.data as typeof pageData;
    const currentRepoKey = `${npub}/${repo}`;
    
    // Reset flags if repo changed
    if (currentRepoKey !== lastRepoKey) {
      maintainersLoaded = false;
      maintainersEffectRan = false;
      lastRepoKey = currentRepoKey;
    }
    
    // Only load if:
    // 1. We have page data
    // 2. Effect hasn't run yet for this repo
    // 3. We're not currently loading
    if ((data.repoOwnerPubkey || (data.repoMaintainers && data.repoMaintainers.length > 0)) && 
        !maintainersEffectRan && 
        !loadingMaintainers) {
      maintainersEffectRan = true; // Mark as ran to prevent re-running
      maintainersLoaded = true; // Set flag before loading to prevent concurrent calls
      loadAllMaintainers().catch(err => {
        maintainersLoaded = false; // Reset on error so we can retry
        maintainersEffectRan = false; // Allow retry
        console.warn('Failed to load maintainers:', err);
      });
    }
  });

  // Watch for auto-save setting changes
  $effect(() => {
    // Check auto-save setting and update interval (async, but don't await)
    settingsStore.getSettings().then(settings => {
      if (settings.autoSave && !autoSaveInterval) {
        // Auto-save was enabled, set it up
        setupAutoSave();
      } else if (!settings.autoSave && autoSaveInterval) {
        // Auto-save was disabled, clear interval
        if (autoSaveInterval) {
          clearInterval(autoSaveInterval);
          autoSaveInterval = null;
        }
      }
    }).catch(err => {
      console.warn('Failed to check auto-save setting:', err);
    });
  });

  // Sync with userStore
  $effect(() => {
    const currentUser = $userStore;
    const wasLoggedIn = userPubkey !== null || userPubkeyHex !== null;
    
    if (currentUser.userPubkey && currentUser.userPubkeyHex) {
      const wasDifferent = userPubkey !== currentUser.userPubkey || userPubkeyHex !== currentUser.userPubkeyHex;
      userPubkey = currentUser.userPubkey;
      userPubkeyHex = currentUser.userPubkeyHex;
      
      // Reload data when user logs in or pubkey changes
      if (wasDifferent) {
        // Reset repoNotFound flag when user logs in, so we can retry loading
        repoNotFound = false;
        // Clear cached email and name when user changes
        cachedUserEmail = null;
        cachedUserName = null;
        
        checkMaintainerStatus().catch(err => console.warn('Failed to reload maintainer status after login:', err));
        loadBookmarkStatus().catch(err => console.warn('Failed to reload bookmark status after login:', err));
        // Reset flags to allow reload
        maintainersLoaded = false;
        maintainersEffectRan = false;
        lastRepoKey = null;
        loadAllMaintainers().catch(err => console.warn('Failed to reload maintainers after login:', err));
        // Recheck clone status after login (force refresh) - delay slightly to ensure auth headers are ready
        setTimeout(() => {
          checkCloneStatus(true).catch(err => console.warn('Failed to recheck clone status after login:', err));
        }, 100);
        // Reload all repository data with the new user context
        if (!loading) {
          loadBranches().catch(err => console.warn('Failed to reload branches after login:', err));
          loadFiles().catch(err => console.warn('Failed to reload files after login:', err));
          loadReadme().catch(err => console.warn('Failed to reload readme after login:', err));
          loadTags().catch(err => console.warn('Failed to reload tags after login:', err));
          // Reload discussions when user logs in (needs user context for relay selection)
          loadDiscussions().catch(err => console.warn('Failed to reload discussions after login:', err));
        }
      }
    } else {
      userPubkey = null;
      userPubkeyHex = null;
      // Clear cached email and name when user logs out
      cachedUserEmail = null;
      cachedUserName = null;
      
      // Reload data when user logs out to hide private content
      if (wasLoggedIn) {
        checkMaintainerStatus().catch(err => console.warn('Failed to reload maintainer status after logout:', err));
        loadBookmarkStatus().catch(err => console.warn('Failed to reload bookmark status after logout:', err));
        // Reset flags to allow reload
        maintainersLoaded = false;
        maintainersEffectRan = false;
        lastRepoKey = null;
        loadAllMaintainers().catch(err => console.warn('Failed to reload maintainers after logout:', err));
        // If repo is private and user logged out, reload to trigger access check
        if (!loading && activeTab === 'files') {
          loadFiles().catch(err => console.warn('Failed to reload files after logout:', err));
        }
      }
    }
  });

  // Navigation stack for directories
  let pathStack = $state<string[]>([]);

  // New file creation
  let showCreateFileDialog = $state(false);
  let newFileName = $state('');
  let newFileContent = $state('');

  // Branch creation
  let showCreateBranchDialog = $state(false);
  let newBranchName = $state('');
  let newBranchFrom = $state<string | null>(null);
  let defaultBranchName = $state('master'); // Default branch from settings

  // Commit history
  let commits = $state<Array<{ hash: string; message: string; author: string; date: string; files: string[] }>>([]);
  let loadingCommits = $state(false);
  let selectedCommit = $state<string | null>(null);
  let showDiff = $state(false);
  let diffData = $state<Array<{ file: string; additions: number; deletions: number; diff: string }>>([]);

  // Tags
  let tags = $state<Array<{ name: string; hash: string; message?: string }>>([]);
  let showCreateTagDialog = $state(false);
  let newTagName = $state('');
  let newTagMessage = $state('');
  let newTagRef = $state('HEAD');

  // Maintainer status
  let isMaintainer = $state(false);
  let loadingMaintainerStatus = $state(false);
  
  // All maintainers (including owner) for display
  let allMaintainers = $state<Array<{ pubkey: string; isOwner: boolean }>>([]);
  let loadingMaintainers = $state(false);
  let maintainersLoaded = $state(false); // Guard to prevent repeated loads
  
  // Clone status
  let isRepoCloned = $state<boolean | null>(null); // null = unknown, true = cloned, false = not cloned
  let checkingCloneStatus = $state(false);
  let cloning = $state(false);
  
  // Word wrap toggle
  let wordWrap = $state(false);
  
  // Function to toggle word wrap and refresh highlighting
  async function toggleWordWrap() {
    wordWrap = !wordWrap;
    console.log('Word wrap toggled:', wordWrap);
    // Force DOM update by accessing the element
    await new Promise(resolve => {
      requestAnimationFrame(() => {
        requestAnimationFrame(resolve);
      });
    });
    // Re-apply syntax highlighting to refresh the display
    if (currentFile && fileContent) {
      const ext = currentFile.split('.').pop() || '';
      await applySyntaxHighlighting(fileContent, ext);
    }
  }
  let copyingCloneUrl = $state(false);
  
  // Helper: Check if repo needs to be cloned for write operations
  const needsClone = $derived(isRepoCloned === false);
  const cloneTooltip = 'Please clone this repo to use this feature.';
  
  // Copy clone URL to clipboard
  async function copyCloneUrl() {
    if (copyingCloneUrl) return;
    
    copyingCloneUrl = true;
    try {
      // Use the current page URL to get the correct host and port
      // This ensures we use the same domain/port the user is currently viewing
      const currentUrl = $page.url;
      const host = currentUrl.host; // Includes port if present (e.g., "localhost:5173")
      const protocol = currentUrl.protocol.slice(0, -1); // Remove trailing ":"
      
      // Use /api/git/ format for better compatibility with commit signing hook
      const cloneUrl = `${protocol}://${host}/api/git/${npub}/${repo}.git`;
      const cloneCommand = `git clone ${cloneUrl}`;
      
      // Try to use the Clipboard API
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(cloneCommand);
        alert(`Clone command copied to clipboard!\n\n${cloneCommand}`);
      } else {
        // Fallback: create a temporary textarea
        const textarea = document.createElement('textarea');
        textarea.value = cloneCommand;
        textarea.style.position = 'fixed';
        textarea.style.opacity = '0';
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);
        alert(`Clone command copied to clipboard!\n\n${cloneCommand}`);
      }
    } catch (err) {
      console.error('Failed to copy clone command:', err);
      alert('Failed to copy clone command to clipboard');
    } finally {
      copyingCloneUrl = false;
    }
  }
  
  // Verification status
  let verificationStatus = $state<{ 
    verified: boolean; 
    error?: string; 
    message?: string;
    cloneVerifications?: Array<{ url: string; verified: boolean; ownerPubkey: string | null; error?: string }>;
  } | null>(null);
  let showVerificationDialog = $state(false);
  let verificationFileContent = $state<string | null>(null);
  let loadingVerification = $state(false);

  // Deletion request
  let deletingAnnouncement = $state(false);
  let announcementEventId = $state<string | null>(null);

  // Issues
  let issues = $state<Array<{ id: string; subject: string; content: string; status: string; author: string; created_at: number; kind: number }>>([]);
  let loadingIssues = $state(false);
  let showCreateIssueDialog = $state(false);
  let newIssueSubject = $state('');
  let newIssueContent = $state('');
  let newIssueLabels = $state<string[]>(['']);
  let updatingIssueStatus = $state<Record<string, boolean>>({});

  // Pull Requests
  let prs = $state<Array<{ id: string; subject: string; content: string; status: string; author: string; created_at: number; commitId?: string; kind: number }>>([]);
  let loadingPRs = $state(false);
  let showCreatePRDialog = $state(false);
  let newPRSubject = $state('');
  let newPRContent = $state('');
  let newPRCommitId = $state('');
  let newPRBranchName = $state('');
  let newPRLabels = $state<string[]>(['']);
  let selectedPR = $state<string | null>(null);
  
  // Tabs menu - defined after issues and prs
  // Order: Files, Issues, PRs, Patches, Discussion, History, Tags, Docs
  const tabs = $derived([
    { id: 'files', label: 'Files', icon: '/icons/file-text.svg' },
    { id: 'issues', label: 'Issues', icon: '/icons/alert-circle.svg' },
    { id: 'prs', label: 'Pull Requests', icon: '/icons/git-pull-request.svg' },
    { id: 'patches', label: 'Patches', icon: '/icons/clipboard-list.svg' },
    { id: 'discussions', label: 'Discussions', icon: '/icons/message-circle.svg' },
    { id: 'history', label: 'Commit History', icon: '/icons/git-commit.svg' },
    { id: 'tags', label: 'Tags', icon: '/icons/tag.svg' },
    { id: 'docs', label: 'Docs', icon: '/icons/book.svg' }
  ]);

  // Patches
  let patches = $state<Array<{ id: string; subject: string; content: string; author: string; created_at: number; kind: number }>>([]);
  let loadingPatches = $state(false);
  let selectedPatch = $state<string | null>(null);
  let showCreatePatchDialog = $state(false);
  let newPatchContent = $state('');
  let newPatchSubject = $state('');
  let creatingPatch = $state(false);

  // Documentation
  let documentationContent = $state<string | null>(null);
  let documentationHtml = $state<string | null>(null);
  let documentationKind = $state<number | null>(null);
  let loadingDocs = $state(false);

  // Discussion threads
  let showCreateThreadDialog = $state(false);
  let newThreadTitle = $state('');
  let newThreadContent = $state('');
  let creatingThread = $state(false);
  
  // Thread replies
  let expandedThreads = $state<Set<string>>(new Set());
  let showReplyDialog = $state(false);
  let replyingToThread = $state<{ id: string; kind?: number; pubkey?: string; author: string } | null>(null);
  let replyingToComment = $state<{ id: string; kind?: number; pubkey?: string; author: string } | null>(null);
  let replyContent = $state('');
  let creatingReply = $state(false);

  // Discussions
  let selectedDiscussion = $state<string | null>(null);
  let discussions = $state<Array<{ 
    type: 'thread' | 'comments'; 
    id: string; 
    title: string; 
    content: string; 
    author: string; 
    createdAt: number;
    kind?: number;
    pubkey?: string;
    comments?: Array<{ 
      id: string; 
      content: string; 
      author: string; 
      createdAt: number;
      kind?: number;
      pubkey?: string;
      replies?: Array<{
        id: string;
        content: string;
        author: string;
        createdAt: number;
        kind?: number;
        pubkey?: string;
        replies?: Array<{
          id: string;
          content: string;
          author: string;
          createdAt: number;
          kind?: number;
          pubkey?: string;
        }>;
      }>;
    }> 
  }>>([]);
  let loadingDiscussions = $state(false);

  // Discussion events cache for reply/quote blurbs
  let discussionEvents = $state<Map<string, NostrEvent>>(new Map());
  
  // Nostr link cache for embedded events and profiles
  let nostrLinkEvents = $state<Map<string, NostrEvent>>(new Map());
  let nostrLinkProfiles = $state<Map<string, string>>(new Map()); // npub -> pubkey hex

  // Parse nostr: links from content and extract IDs/pubkeys
  function parseNostrLinks(content: string): Array<{ type: 'nevent' | 'naddr' | 'note1' | 'npub' | 'profile'; value: string; start: number; end: number }> {
    const links: Array<{ type: 'nevent' | 'naddr' | 'note1' | 'npub' | 'profile'; value: string; start: number; end: number }> = [];
    const nostrLinkRegex = /nostr:(nevent1|naddr1|note1|npub1|profile1)[a-zA-Z0-9]+/g;
    let match;
    
    while ((match = nostrLinkRegex.exec(content)) !== null) {
      const fullMatch = match[0];
      const prefix = match[1];
      let type: 'nevent' | 'naddr' | 'note1' | 'npub' | 'profile';
      
      if (prefix === 'nevent1') type = 'nevent';
      else if (prefix === 'naddr1') type = 'naddr';
      else if (prefix === 'note1') type = 'note1';
      else if (prefix === 'npub1') type = 'npub';
      else if (prefix === 'profile1') type = 'profile';
      else continue;
      
      links.push({
        type,
        value: fullMatch,
        start: match.index,
        end: match.index + fullMatch.length
      });
    }
    
    return links;
  }

  // Load events/profiles from nostr: links
  async function loadNostrLinks(content: string) {
    const links = parseNostrLinks(content);
    if (links.length === 0) return;

    const eventIds: string[] = [];
    const aTags: string[] = [];
    const npubs: string[] = [];

    for (const link of links) {
      try {
        if (link.type === 'nevent' || link.type === 'note1') {
          const decoded = nip19.decode(link.value.replace('nostr:', ''));
          if (decoded.type === 'nevent') {
            eventIds.push(decoded.data.id);
          } else if (decoded.type === 'note') {
            eventIds.push(decoded.data as string);
          }
        } else if (link.type === 'naddr') {
          const decoded = nip19.decode(link.value.replace('nostr:', ''));
          if (decoded.type === 'naddr') {
            const aTag = `${decoded.data.kind}:${decoded.data.pubkey}:${decoded.data.identifier}`;
            aTags.push(aTag);
          }
        } else if (link.type === 'npub' || link.type === 'profile') {
          const decoded = nip19.decode(link.value.replace('nostr:', ''));
          if (decoded.type === 'npub') {
            npubs.push(link.value);
            nostrLinkProfiles.set(link.value, decoded.data as string);
          }
        }
      } catch {
        // Invalid nostr link, skip
      }
    }

    // Fetch events
    if (eventIds.length > 0) {
      try {
        const events = await Promise.race([
          nostrClient.fetchEvents([{ ids: eventIds, limit: eventIds.length }]),
          new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 10000))
        ]);
        
        for (const event of events) {
          nostrLinkEvents.set(event.id, event);
        }
      } catch {
        // Ignore fetch errors
      }
    }

    // Fetch a-tag events
    if (aTags.length > 0) {
      for (const aTag of aTags) {
        const parts = aTag.split(':');
        if (parts.length === 3) {
          try {
            const kind = parseInt(parts[0]);
            const pubkey = parts[1];
            const dTag = parts[2];
            const events = await Promise.race([
              nostrClient.fetchEvents([{ kinds: [kind], authors: [pubkey], '#d': [dTag], limit: 1 }]),
              new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 10000))
            ]);
            
            if (events.length > 0) {
              nostrLinkEvents.set(events[0].id, events[0]);
            }
          } catch {
            // Ignore fetch errors
          }
        }
      }
    }
  }

  // Get event from nostr: link
  function getEventFromNostrLink(link: string): NostrEvent | undefined {
    try {
      if (link.startsWith('nostr:nevent1') || link.startsWith('nostr:note1')) {
        const decoded = nip19.decode(link.replace('nostr:', ''));
        if (decoded.type === 'nevent') {
          return nostrLinkEvents.get(decoded.data.id);
        } else if (decoded.type === 'note') {
          return nostrLinkEvents.get(decoded.data as string);
        }
      } else if (link.startsWith('nostr:naddr1')) {
        const decoded = nip19.decode(link.replace('nostr:', ''));
        if (decoded.type === 'naddr') {
          const eventId = `${decoded.data.kind}:${decoded.data.pubkey}:${decoded.data.identifier}`;
          return Array.from(nostrLinkEvents.values()).find(e => {
            const dTag = e.tags.find(t => t[0] === 'd')?.[1];
            return e.kind === decoded.data.kind && 
                   e.pubkey === decoded.data.pubkey && 
                   dTag === decoded.data.identifier;
          });
        }
      }
    } catch {
      // Invalid link
    }
    return undefined;
  }

  // Get pubkey from nostr: npub/profile link
  function getPubkeyFromNostrLink(link: string): string | undefined {
    return nostrLinkProfiles.get(link);
  }

  // Process content with nostr links into parts for rendering
  function processContentWithNostrLinks(content: string): Array<{ type: 'text' | 'event' | 'profile' | 'placeholder'; value: string; event?: NostrEvent; pubkey?: string }> {
    const links = parseNostrLinks(content);
    if (links.length === 0) {
      return [{ type: 'text', value: content }];
    }

    const parts: Array<{ type: 'text' | 'event' | 'profile' | 'placeholder'; value: string; event?: NostrEvent; pubkey?: string }> = [];
    let lastIndex = 0;

    for (const link of links) {
      // Add text before link
      if (link.start > lastIndex) {
        const textPart = content.slice(lastIndex, link.start);
        if (textPart) {
          parts.push({ type: 'text', value: textPart });
        }
      }

      // Add link
      const event = getEventFromNostrLink(link.value);
      const pubkey = getPubkeyFromNostrLink(link.value);
      if (event) {
        parts.push({ type: 'event', value: link.value, event });
      } else if (pubkey) {
        parts.push({ type: 'profile', value: link.value, pubkey });
      } else {
        parts.push({ type: 'placeholder', value: link.value });
      }

      lastIndex = link.end;
    }

    // Add remaining text
    if (lastIndex < content.length) {
      const textPart = content.slice(lastIndex);
      if (textPart) {
        parts.push({ type: 'text', value: textPart });
      }
    }

    return parts;
  }

  // Load full events for discussions and comments to get tags for blurbs
  async function loadDiscussionEvents(discussionsList: typeof discussions) {
    const eventIds = new Set<string>();
    
    // Collect all event IDs
    for (const discussion of discussionsList) {
      if (discussion.id) {
        eventIds.add(discussion.id);
      }
      if (discussion.comments) {
        for (const comment of discussion.comments) {
          if (comment.id) {
            eventIds.add(comment.id);
          }
          if (comment.replies) {
            for (const reply of comment.replies) {
              if (reply.id) {
                eventIds.add(reply.id);
              }
              if (reply.replies) {
                for (const nestedReply of reply.replies) {
                  if (nestedReply.id) {
                    eventIds.add(nestedReply.id);
                  }
                }
              }
            }
          }
        }
      }
    }

    if (eventIds.size === 0) return;

    try {
      const events = await Promise.race([
        nostrClient.fetchEvents([{ ids: Array.from(eventIds), limit: eventIds.size }]),
        new Promise<NostrEvent[]>((resolve) => setTimeout(() => resolve([]), 10000))
      ]);
      
      for (const event of events) {
        discussionEvents.set(event.id, event);
      }
    } catch {
      // Ignore fetch errors
    }
  }

  // Get discussion event by ID
  function getDiscussionEvent(eventId: string): NostrEvent | undefined {
    return discussionEvents.get(eventId);
  }

  // Get referenced event from discussion event (e-tag, a-tag, q-tag)
  function getReferencedEventFromDiscussion(event: NostrEvent): NostrEvent | undefined {
    // Check e-tag
    const eTag = event.tags.find(t => t[0] === 'e' && t[1])?.[1];
    if (eTag) {
      const referenced = discussionEvents.get(eTag);
      if (referenced) return referenced;
    }
    
    // Check a-tag
    const aTag = event.tags.find(t => t[0] === 'a' && t[1])?.[1];
    if (aTag) {
      const parts = aTag.split(':');
      if (parts.length === 3) {
        const kind = parseInt(parts[0]);
        const pubkey = parts[1];
        const dTag = parts[2];
        return Array.from(discussionEvents.values()).find(e => 
          e.kind === kind && 
          e.pubkey === pubkey && 
          e.tags.find(t => t[0] === 'd' && t[1] === dTag)
        );
      }
    }
    
    // Check q-tag
    const qTag = event.tags.find(t => t[0] === 'q' && t[1])?.[1];
    if (qTag) {
      return discussionEvents.get(qTag);
    }
    
    return undefined;
  }

  // Format time for discussions
  function formatDiscussionTime(timestamp: number): string {
    const date = new Date(timestamp * 1000);
    const now = new Date();
    const diffMs = now.getTime() - date.getTime();
    const diffMins = Math.floor(diffMs / 60000);
    const diffHours = Math.floor(diffMs / 3600000);
    const diffDays = Math.floor(diffMs / 86400000);

    if (diffMins < 1) return 'just now';
    if (diffMins < 60) return `${diffMins}m ago`;
    if (diffHours < 24) return `${diffHours}h ago`;
    if (diffDays < 7) return `${diffDays}d ago`;
    return date.toLocaleDateString();
  }

  // Create a nostrClient instance for fetching events
  let nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS);

  // README
  let readmeContent = $state<string | null>(null);
  let readmePath = $state<string | null>(null);
  let readmeIsMarkdown = $state(false);
  let loadingReadme = $state(false);
  let readmeHtml = $state<string>('');
  let highlightedFileContent = $state<string>('');

  // Fork
  let forkInfo = $state<{ isFork: boolean; originalRepo: { npub: string; repo: string } | null } | null>(null);
  let forking = $state(false);

  // Bookmarks
  let isBookmarked = $state(false);
  let loadingBookmark = $state(false);
  let bookmarksService: BookmarksService | null = null;
  let repoAddress = $state<string | null>(null);

  // Repository images
  let repoImage = $state<string | null>(null);
  let repoBanner = $state<string | null>(null);

  // Repository owner pubkey (decoded from npub)
  let repoOwnerPubkey = $state<string | null>(null);

  // Mobile view toggle for file list/file viewer
  let showFileListOnMobile = $state(true);
  // Mobile view toggle for all left/right panels (issues, PRs, patches, discussions, docs, history, tags)
  let showLeftPanelOnMobile = $state(true);
  // Mobile collapse for clone URLs
  let cloneUrlsExpanded = $state(false);
  
  // Guard to prevent README auto-load loop
  let readmeAutoLoadAttempted = $state(false);
  let readmeAutoLoadTimeout: ReturnType<typeof setTimeout> | null = null;

  async function loadReadme() {
    if (repoNotFound) return;
    loadingReadme = true;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/readme?ref=${currentBranch}`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        const data = await response.json();
        if (data.found) {
          readmeContent = data.content;
          readmePath = data.path;
          readmeIsMarkdown = data.isMarkdown;
          
          // Render markdown if needed
          if (readmeIsMarkdown && readmeContent) {
            try {
              const MarkdownIt = (await import('markdown-it')).default;
              const hljsModule = await import('highlight.js');
              const hljs = hljsModule.default || hljsModule;
              
              const md = new MarkdownIt({
                html: true, // Enable HTML tags in source
                linkify: true, // Autoconvert URL-like text to links
                typographer: true, // Enable some language-neutral replacement + quotes beautification
                breaks: true, // Convert '\n' in paragraphs into <br>
                highlight: function (str: string, lang: string): string {
                  if (lang && hljs.getLanguage(lang)) {
                    try {
                      return '<pre class="hljs"><code>' +
                             hljs.highlight(str, { language: lang }).value +
                             '</code></pre>';
                    } catch (err) {
                      // Fallback to escaped HTML if highlighting fails
                      // This is expected for unsupported languages
                    }
                  }
                  return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
                }
              });
              
              readmeHtml = md.render(readmeContent);
              console.log('[README] Markdown rendered successfully, HTML length:', readmeHtml.length);
            } catch (err) {
              console.error('[README] Error rendering markdown:', err);
              // Fallback: show as plain text if rendering fails
              readmeHtml = '';
            }
          } else {
            // Clear HTML if not markdown
            readmeHtml = '';
          }
        }
      }
    } catch (err) {
      console.error('Error loading README:', err);
    } finally {
      loadingReadme = false;
    }
  }

  // Map file extensions to highlight.js language names
  function getHighlightLanguage(ext: string): string {
    const langMap: Record<string, string> = {
      'js': 'javascript',
      'ts': 'typescript',
      'jsx': 'javascript',
      'tsx': 'typescript',
      'json': 'json',
      'css': 'css',
      'html': 'xml',
      'xml': 'xml',
      'yaml': 'yaml',
      'yml': 'yaml',
      'py': 'python',
      'rb': 'ruby',
      'go': 'go',
      'rs': 'rust',
      'java': 'java',
      'c': 'c',
      'cpp': 'cpp',
      'h': 'c',
      'hpp': 'cpp',
      'sh': 'bash',
      'bash': 'bash',
      'zsh': 'bash',
      'sql': 'sql',
      'php': 'php',
      'swift': 'swift',
      'kt': 'kotlin',
      'scala': 'scala',
      'r': 'r',
      'm': 'objectivec',
      'mm': 'objectivec',
      'vue': 'xml',
      'svelte': 'xml',
      'dockerfile': 'dockerfile',
      'toml': 'toml',
      'ini': 'ini',
      'conf': 'ini',
      'log': 'plaintext',
      'txt': 'plaintext',
      'md': 'markdown',
      'markdown': 'markdown',
      'mdown': 'markdown',
      'mkdn': 'markdown',
      'mkd': 'markdown',
      'mdwn': 'markdown',
      'adoc': 'asciidoc',
      'asciidoc': 'asciidoc',
      'ad': 'asciidoc',
    };
    return langMap[ext.toLowerCase()] || 'plaintext';
  }

  async function applySyntaxHighlighting(content: string, ext: string) {
    try {
      const hljsModule = await import('highlight.js');
      // highlight.js v11+ uses default export
      const hljs = hljsModule.default || hljsModule;
      const lang = getHighlightLanguage(ext);
      
      // Register Markdown language if needed (not in highlight.js by default)
      if (lang === 'markdown' && !hljs.getLanguage('markdown')) {
        hljs.registerLanguage('markdown', function(hljs) {
          return {
            name: 'Markdown',
            aliases: ['md', 'mkdown', 'mkd'],
            contains: [
              // Headers
              {
                className: 'section',
                begin: /^#{1,6}\s+/,
                relevance: 10
              },
              // Bold
              {
                className: 'strong',
                begin: /\*\*[^*]+\*\*/,
                relevance: 0
              },
              {
                className: 'strong',
                begin: /__[^_]+__/,
                relevance: 0
              },
              // Italic
              {
                className: 'emphasis',
                begin: /\*[^*]+\*/,
                relevance: 0
              },
              {
                className: 'emphasis',
                begin: /_[^_]+_/,
                relevance: 0
              },
              // Inline code
              {
                className: 'code',
                begin: /`[^`]+`/,
                relevance: 0
              },
              // Code blocks
              {
                className: 'code',
                begin: /^```[\w]*/,
                end: /^```$/,
                contains: [{ begin: /./ }]
              },
              // Links
              {
                className: 'link',
                begin: /\[/,
                end: /\]/,
                contains: [
                  {
                    className: 'string',
                    begin: /\(/,
                    end: /\)/
                  }
                ]
              },
              // Images
              {
                className: 'string',
                begin: /!\[/,
                end: /\]/
              },
              // Lists
              {
                className: 'bullet',
                begin: /^(\s*)([*+-]|\d+\.)\s+/,
                relevance: 0
              },
              // Blockquotes
              {
                className: 'quote',
                begin: /^>\s+/,
                relevance: 0
              },
              // Horizontal rules
              {
                className: 'horizontal_rule',
                begin: /^(\*{3,}|-{3,}|_{3,})$/,
                relevance: 0
              }
            ]
          };
        });
      }
      
      // Register AsciiDoc language if needed (not in highlight.js by default)
      if (lang === 'asciidoc' && !hljs.getLanguage('asciidoc')) {
        hljs.registerLanguage('asciidoc', function(hljs) {
          return {
            name: 'AsciiDoc',
            aliases: ['adoc', 'asciidoc', 'ad'],
            contains: [
              // Headers
              {
                className: 'section',
                begin: /^={1,6}\s+/,
                relevance: 10
              },
              // Bold
              {
                className: 'strong',
                begin: /\*\*[^*]+\*\*/,
                relevance: 0
              },
              // Italic
              {
                className: 'emphasis',
                begin: /_[^_]+_/,
                relevance: 0
              },
              // Inline code
              {
                className: 'code',
                begin: /`[^`]+`/,
                relevance: 0
              },
              // Code blocks
              {
                className: 'code',
                begin: /^----+$/,
                end: /^----+$/,
                contains: [{ begin: /./ }]
              },
              // Lists
              {
                className: 'bullet',
                begin: /^(\*+|\.+|-+)\s+/,
                relevance: 0
              },
              // Links
              {
                className: 'link',
                begin: /link:/,
                end: /\[/,
                contains: [{ begin: /\[/, end: /\]/ }]
              },
              // Comments
              {
                className: 'comment',
                begin: /^\/\/.*$/,
                relevance: 0
              },
              // Attributes
              {
                className: 'attr',
                begin: /^:.*:$/,
                relevance: 0
              }
            ]
          };
        });
      }
      
      // Apply highlighting
      if (lang === 'plaintext') {
        highlightedFileContent = `<pre><code class="hljs">${hljs.highlight(content, { language: 'plaintext' }).value}</code></pre>`;
      } else if (hljs.getLanguage(lang)) {
        highlightedFileContent = `<pre><code class="hljs language-${lang}">${hljs.highlight(content, { language: lang }).value}</code></pre>`;
      } else {
        // Fallback to auto-detection
        highlightedFileContent = `<pre><code class="hljs">${hljs.highlightAuto(content).value}</code></pre>`;
      }
    } catch (err) {
      console.error('Error applying syntax highlighting:', err);
      // Fallback to plain text
      highlightedFileContent = `<pre><code class="hljs">${content}</code></pre>`;
    }
  }

  async function loadForkInfo() {
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/fork`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        forkInfo = await response.json();
      }
    } catch (err) {
      console.error('Error loading fork info:', err);
    }
  }

  // Helper function to count all replies recursively (including nested ones)
  function countAllReplies(comments: Array<{ replies?: Array<any> }> | undefined): number {
    if (!comments || comments.length === 0) {
      return 0;
    }
    let count = comments.length;
    for (const comment of comments) {
      if (comment.replies && comment.replies.length > 0) {
        count += countAllReplies(comment.replies);
      }
    }
    return count;
  }

  async function checkCloneStatus(force: boolean = false) {
    if (checkingCloneStatus || (!force && isRepoCloned !== null)) return;
    
    checkingCloneStatus = true;
    try {
      // Check if repo exists locally by trying to fetch branches
      // 404 = repo not cloned, 403 = repo exists but access denied (cloned), 200 = cloned and accessible
      const response = await fetch(`/api/repos/${npub}/${repo}/branches`, {
        headers: buildApiHeaders()
      });
      // If response is 403, repo exists (cloned) but user doesn't have access
      // If response is 404, repo doesn't exist (not cloned)
      // If response is 200, repo exists and is accessible (cloned)
      const wasCloned = response.status !== 404;
      isRepoCloned = wasCloned;
      console.log(`[Clone Status] Repo ${wasCloned ? 'is cloned' : 'is not cloned'} (status: ${response.status})`);
    } catch (err) {
      // On error, assume not cloned
      console.warn('[Clone Status] Error checking clone status:', err);
      isRepoCloned = false;
    } finally {
      checkingCloneStatus = false;
    }
  }

  async function cloneRepository() {
    if (cloning) return;
    
    cloning = true;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/clone`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...buildApiHeaders()
        }
      });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({ message: response.statusText }));
        throw new Error(errorData.message || `Failed to clone repository: ${response.statusText}`);
      }

      const data = await response.json();
      
      if (data.alreadyExists) {
        alert('Repository already exists locally.');
        // Force refresh clone status
        await checkCloneStatus(true);
      } else {
        alert('Repository cloned successfully! The repository is now available on this server.');
        // Force refresh clone status
        await checkCloneStatus(true);
        // Reload data to use the cloned repo instead of API
        await Promise.all([
          loadBranches(),
          loadFiles(currentPath),
          loadReadme(),
          loadTags(),
          loadCommitHistory()
        ]);
      }
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Failed to clone repository';
      alert(`Error: ${errorMessage}`);
      console.error('Error cloning repository:', err);
    } finally {
      cloning = false;
    }
  }

  async function forkRepository() {
    if (!userPubkey) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    forking = true;
    error = null;

    try {
      // Security: Truncate npub in logs
      const truncatedNpub = npub.length > 16 ? `${npub.slice(0, 12)}...` : npub;
      console.log(`[Fork UI] Starting fork of ${truncatedNpub}/${repo}...`);
      const response = await fetch(`/api/repos/${npub}/${repo}/fork`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...buildApiHeaders()
        },
        body: JSON.stringify({ userPubkey })
      });

      const data = await response.json();
      
      if (response.ok && data.success !== false) {
        const message = data.message || `Repository forked successfully! Published to ${data.fork?.publishedTo?.announcement || 0} relay(s).`;
        console.log(`[Fork UI] ✓ ${message}`);
        // Security: Truncate npub in logs
        const truncatedForkNpub = data.fork.npub.length > 16 ? `${data.fork.npub.slice(0, 12)}...` : data.fork.npub;
        console.log(`[Fork UI]   - Fork location: /repos/${truncatedForkNpub}/${data.fork.repo}`);
        console.log(`[Fork UI]   - Announcement ID: ${data.fork.announcementId}`);
        console.log(`[Fork UI]   - Ownership Transfer ID: ${data.fork.ownershipTransferId}`);
        
        alert(`✓ ${message}\n\nRedirecting to your fork...`);
        goto(`/repos/${data.fork.npub}/${data.fork.repo}`);
      } else {
        const errorMessage = data.error || 'Failed to fork repository';
        const errorDetails = data.details ? `\n\nDetails: ${data.details}` : '';
        const fullError = `${errorMessage}${errorDetails}`;
        
        console.error(`[Fork UI] ✗ Fork failed: ${errorMessage}`);
        if (data.details) {
          console.error(`[Fork UI] Details: ${data.details}`);
        }
        if (data.eventName) {
          console.error(`[Fork UI] Failed event: ${data.eventName}`);
        }
        
        error = fullError;
        alert(`✗ Fork failed!\n\n${fullError}`);
      }
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Failed to fork repository';
      console.error(`[Fork UI] ✗ Unexpected error: ${errorMessage}`, err);
      error = errorMessage;
      alert(`✗ Fork failed!\n\n${errorMessage}`);
    } finally {
      forking = false;
    }
  }

  async function loadDiscussions() {
    if (repoNotFound) return;
    loadingDiscussions = true;
    error = null;
    try {
      const decoded = nip19.decode(npub);
      if (decoded.type !== 'npub') {
        throw new Error('Invalid npub format');
      }
      const repoOwnerPubkey = decoded.data as string;

      // Fetch repo announcement to get chat-relay tags and announcement ID
      const client = new NostrClient(DEFAULT_NOSTR_RELAYS);
      const events = await client.fetchEvents([
        {
          kinds: [KIND.REPO_ANNOUNCEMENT],
          authors: [repoOwnerPubkey],
          '#d': [repo],
          limit: 1
        }
      ]);

      if (events.length === 0) {
        discussions = [];
        return;
      }

      const announcement = events[0];
      const chatRelays = announcement.tags
        .filter(t => t[0] === 'chat-relay')
        .flatMap(t => t.slice(1))
        .filter(url => url && typeof url === 'string') as string[];

      // Get default relays
      const { getGitUrl } = await import('$lib/config.js');
      const { DiscussionsService } = await import('$lib/services/nostr/discussions-service.js');
      
      // Get user's relays if available
      let userRelays: string[] = [];
      const currentUserPubkey = $userStore.userPubkey || userPubkey;
      if (currentUserPubkey) {
        try {
          const { outbox } = await getUserRelays(currentUserPubkey, client);
          userRelays = outbox;
        } catch (err) {
          console.warn('Failed to get user relays, using defaults:', err);
        }
      }

      // Combine all available relays: default + search + chat + user relays
      const allRelays = [...new Set([
        ...DEFAULT_NOSTR_RELAYS,
        ...DEFAULT_NOSTR_SEARCH_RELAYS,
        ...chatRelays,
        ...userRelays
      ])];
      
      console.log('[Discussions] Using all available relays for threads:', allRelays);
      console.log('[Discussions] Chat relays from announcement:', chatRelays);

      const discussionsService = new DiscussionsService(allRelays);
      const discussionEntries = await discussionsService.getDiscussions(
        repoOwnerPubkey,
        repo,
        announcement.id,
        announcement.pubkey,
        allRelays, // Use all relays for threads
        allRelays  // Use all relays for comments too
      );
      
      console.log('[Discussions] Found', discussionEntries.length, 'discussion entries');

      discussions = discussionEntries.map(entry => ({
        type: entry.type,
        id: entry.id,
        title: entry.title,
        content: entry.content,
        author: entry.author,
        createdAt: entry.createdAt,
        kind: entry.kind,
        pubkey: entry.pubkey,
        comments: entry.comments
      }));

      // Fetch full events for discussions and comments to get tags for blurbs
      await loadDiscussionEvents(discussions);
      
      // Fetch nostr: links from discussion content
      for (const discussion of discussions) {
        if (discussion.content) {
          await loadNostrLinks(discussion.content);
        }
        if (discussion.comments) {
          for (const comment of discussion.comments) {
            if (comment.content) {
              await loadNostrLinks(comment.content);
            }
            if (comment.replies) {
              for (const reply of comment.replies) {
                if (reply.content) {
                  await loadNostrLinks(reply.content);
                }
                if (reply.replies) {
                  for (const nestedReply of reply.replies) {
                    if (nestedReply.content) {
                      await loadNostrLinks(nestedReply.content);
                    }
                  }
                }
              }
            }
          }
        }
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to load discussions';
      console.error('Error loading discussions:', err);
    } finally {
      loadingDiscussions = false;
    }
  }


  async function createDiscussionThread() {
    if (!userPubkey || !userPubkeyHex) {
      error = 'You must be logged in to create a discussion thread';
      return;
    }

    if (!newThreadTitle.trim()) {
      error = 'Thread title is required';
      return;
    }

    creatingThread = true;
    error = null;

    try {
      const decoded = nip19.decode(npub);
      if (decoded.type !== 'npub') {
        throw new Error('Invalid npub format');
      }
      const repoOwnerPubkey = decoded.data as string;

      // Get repo announcement to get the repo address
      const client = new NostrClient(DEFAULT_NOSTR_RELAYS);
      const events = await client.fetchEvents([
        {
          kinds: [KIND.REPO_ANNOUNCEMENT],
          authors: [repoOwnerPubkey],
          '#d': [repo],
          limit: 1
        }
      ]);

      if (events.length === 0) {
        throw new Error('Repository announcement not found');
      }

      const announcement = events[0];
      const repoAddress = `${KIND.REPO_ANNOUNCEMENT}:${repoOwnerPubkey}:${repo}`;

      // Get chat relays from announcement, or use default relays
      const chatRelays = announcement.tags
        .filter(t => t[0] === 'chat-relay')
        .flatMap(t => t.slice(1))
        .filter(url => url && typeof url === 'string') as string[];

      // Combine all available relays
      let allRelays = [...DEFAULT_NOSTR_RELAYS, ...DEFAULT_NOSTR_SEARCH_RELAYS, ...chatRelays];
      if (userPubkey) {
        try {
          const { outbox } = await getUserRelays(userPubkey, client);
          allRelays = [...allRelays, ...outbox];
        } catch (err) {
          console.warn('Failed to get user relays:', err);
        }
      }
      allRelays = [...new Set(allRelays)]; // Deduplicate

      // Create kind 11 thread event
      const threadEventTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
        kind: KIND.THREAD,
        pubkey: userPubkeyHex,
        created_at: Math.floor(Date.now() / 1000),
        tags: [
          ['a', repoAddress],
          ['title', newThreadTitle.trim()],
          ['t', 'repo']
        ],
        content: newThreadContent.trim() || ''
      };

      // Sign the event using NIP-07
      const signedEvent = await signEventWithNIP07(threadEventTemplate);

      // Publish to all available relays
      const publishClient = new NostrClient(allRelays);
      const result = await publishClient.publishEvent(signedEvent, allRelays);

      if (result.failed.length > 0 && result.success.length === 0) {
        throw new Error('Failed to publish thread to all relays');
      }

      // Clear form and close dialog
      newThreadTitle = '';
      newThreadContent = '';
      showCreateThreadDialog = false;

      // Reload discussions
      await loadDiscussions();
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to create discussion thread';
      console.error('Error creating discussion thread:', err);
    } finally {
      creatingThread = false;
    }
  }

  async function createThreadReply() {
    if (!userPubkey || !userPubkeyHex) {
      error = 'You must be logged in to reply';
      return;
    }

    if (!replyContent.trim()) {
      error = 'Reply content is required';
      return;
    }

    if (!replyingToThread && !replyingToComment) {
      error = 'Must reply to either a thread or a comment';
      return;
    }

    creatingReply = true;
    error = null;

    try {
      const decoded = nip19.decode(npub);
      if (decoded.type !== 'npub') {
        throw new Error('Invalid npub format');
      }
      const repoOwnerPubkey = decoded.data as string;

      // Get repo announcement to get the repo address and relays
      const client = new NostrClient(DEFAULT_NOSTR_RELAYS);
      nostrClient = client; // Store for use in other functions
      const events = await client.fetchEvents([
        {
          kinds: [KIND.REPO_ANNOUNCEMENT],
          authors: [repoOwnerPubkey],
          '#d': [repo],
          limit: 1
        }
      ]);

      if (events.length === 0) {
        throw new Error('Repository announcement not found');
      }

      const announcement = events[0];
      
      // Get chat relays from announcement, or use default relays
      const chatRelays = announcement.tags
        .filter(t => t[0] === 'chat-relay')
        .flatMap(t => t.slice(1))
        .filter(url => url && typeof url === 'string') as string[];

      // Combine all available relays
      let allRelays = [...DEFAULT_NOSTR_RELAYS, ...DEFAULT_NOSTR_SEARCH_RELAYS, ...chatRelays];
      if (userPubkey) {
        try {
          const { outbox } = await getUserRelays(userPubkey, client);
          allRelays = [...allRelays, ...outbox];
        } catch (err) {
          console.warn('Failed to get user relays:', err);
        }
      }
      allRelays = [...new Set(allRelays)]; // Deduplicate

      let rootEventId: string;
      let rootKind: number;
      let rootPubkey: string;
      let parentEventId: string;
      let parentKind: number;
      let parentPubkey: string;

      if (replyingToComment) {
        // Replying to a comment - use the comment object we already have
        const comment = replyingToComment;
        
        // Determine root: if we have a thread, use it as root; otherwise use announcement
        if (replyingToThread) {
          rootEventId = replyingToThread.id;
          rootKind = replyingToThread.kind || KIND.THREAD;
          rootPubkey = replyingToThread.pubkey || replyingToThread.author;
        } else {
          // Comment is directly on announcement (in "Comments" pseudo-thread)
          rootEventId = announcement.id;
          rootKind = KIND.REPO_ANNOUNCEMENT;
          rootPubkey = announcement.pubkey;
        }

        // Parent is the comment we're replying to
        parentEventId = comment.id;
        parentKind = comment.kind || KIND.COMMENT;
        parentPubkey = comment.pubkey || comment.author;
      } else if (replyingToThread) {
        // Replying directly to a thread - use the thread object we already have
        rootEventId = replyingToThread.id;
        rootKind = replyingToThread.kind || KIND.THREAD;
        rootPubkey = replyingToThread.pubkey || replyingToThread.author;
        parentEventId = replyingToThread.id;
        parentKind = replyingToThread.kind || KIND.THREAD;
        parentPubkey = replyingToThread.pubkey || replyingToThread.author;
      } else {
        throw new Error('Must specify thread or comment to reply to');
      }

      // Create kind 1111 comment event
      const commentEventTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
        kind: KIND.COMMENT,
        pubkey: userPubkeyHex,
        created_at: Math.floor(Date.now() / 1000),
        tags: [
          ['e', parentEventId, '', 'reply'], // Parent event
          ['k', parentKind.toString()], // Parent kind
          ['p', parentPubkey], // Parent pubkey
          ['E', rootEventId], // Root event
          ['K', rootKind.toString()], // Root kind
          ['P', rootPubkey] // Root pubkey
        ],
        content: replyContent.trim()
      };

      // Sign the event using NIP-07
      const signedEvent = await signEventWithNIP07(commentEventTemplate);

      // Publish to all available relays
      const publishClient = new NostrClient(allRelays);
      const result = await publishClient.publishEvent(signedEvent, allRelays);

      if (result.failed.length > 0 && result.success.length === 0) {
        throw new Error('Failed to publish reply to all relays');
      }

      // Save thread ID before clearing (for expanding after reload)
      const threadIdToExpand = replyingToThread?.id;

      // Clear form and close dialog
      replyContent = '';
      showReplyDialog = false;
      replyingToThread = null;
      replyingToComment = null;

      // Reload discussions to show the new reply
      await loadDiscussions();
      
      // Expand the thread if we were replying to a thread
      if (threadIdToExpand) {
        expandedThreads.add(threadIdToExpand);
        expandedThreads = new Set(expandedThreads); // Trigger reactivity
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to create reply';
      console.error('Error creating reply:', err);
    } finally {
      creatingReply = false;
    }
  }

  function toggleThread(threadId: string) {
    if (expandedThreads.has(threadId)) {
      expandedThreads.delete(threadId);
    } else {
      expandedThreads.add(threadId);
    }
    // Trigger reactivity
    expandedThreads = new Set(expandedThreads);
  }

  async function loadDocumentation() {
    if (loadingDocs) return;
    // Reset documentation when reloading
    documentationHtml = null;
    documentationContent = null;
    documentationKind = null;
    
    loadingDocs = true;
    try {
      // Check if repo is private and user has access
      const data = $page.data as typeof pageData;
      if (data.repoIsPrivate) {
        // Check access via API
        const accessResponse = await fetch(`/api/repos/${npub}/${repo}/access`, {
          headers: buildApiHeaders()
        });
        if (accessResponse.ok) {
          const accessData = await accessResponse.json();
          if (!accessData.canView) {
            // User doesn't have access, don't load documentation
            loadingDocs = false;
            return;
          }
        } else {
          // Access check failed, don't load documentation
          loadingDocs = false;
          return;
        }
      }
      
      const decoded = nip19.decode(npub);
      if (decoded.type === 'npub') {
        const repoOwnerPubkey = decoded.data as string;
        const client = new NostrClient(DEFAULT_NOSTR_RELAYS);
        
        // First, get the repo announcement to find the documentation tag
        const announcementEvents = await client.fetchEvents([
          {
            kinds: [KIND.REPO_ANNOUNCEMENT],
            authors: [repoOwnerPubkey],
            '#d': [repo],
            limit: 1
          }
        ]);

        if (announcementEvents.length === 0) {
          loadingDocs = false;
          return;
        }

        const announcement = announcementEvents[0];
        
        // Look for documentation tag in the announcement
        const documentationTag = announcement.tags.find(t => t[0] === 'documentation');
        
        documentationKind = null;
        
        if (documentationTag && documentationTag[1]) {
          // Parse the a-tag format: kind:pubkey:identifier
          const docAddress = documentationTag[1];
          const parts = docAddress.split(':');
          
          if (parts.length >= 3) {
            documentationKind = parseInt(parts[0]);
            const docPubkey = parts[1];
            const docIdentifier = parts.slice(2).join(':'); // In case identifier contains ':'
            
            // Fetch the documentation event
            const docEvents = await client.fetchEvents([
              {
                kinds: [documentationKind],
                authors: [docPubkey],
                '#d': [docIdentifier],
                limit: 1
              }
            ]);
            
            if (docEvents.length > 0) {
              documentationContent = docEvents[0].content || null;
            } else {
              console.warn('Documentation event not found:', docAddress);
              documentationContent = null;
            }
          } else {
            console.warn('Invalid documentation tag format:', docAddress);
            documentationContent = null;
          }
        } else {
          // No documentation tag, try to use announcement content as fallback
          documentationContent = announcement.content || null;
          // Announcement is kind 30617, not a doc kind, so keep documentationKind as null
        }
        
        // Render content based on kind: AsciiDoc for 30041 or 30818, Markdown otherwise
        if (documentationContent) {
          // Check if we should use AsciiDoc parser (kinds 30041 or 30818)
          const useAsciiDoc = documentationKind === 30041 || documentationKind === 30818;
          
          if (useAsciiDoc) {
            // Use AsciiDoc parser
            const Asciidoctor = (await import('@asciidoctor/core')).default;
            const asciidoctor = Asciidoctor();
            const converted = asciidoctor.convert(documentationContent, {
              safe: 'safe',
              attributes: {
                'source-highlighter': 'highlight.js'
              }
            });
            // Convert to string if it's a Document object
            documentationHtml = typeof converted === 'string' ? converted : String(converted);
          } else {
            // Use Markdown parser
            const MarkdownIt = (await import('markdown-it')).default;
            const hljsModule = await import('highlight.js');
            const hljs = hljsModule.default || hljsModule;
            
            const md = new MarkdownIt({
              highlight: function (str: string, lang: string): string {
                if (lang && hljs.getLanguage(lang)) {
                  try {
                    return hljs.highlight(str, { language: lang }).value;
                  } catch (__) {}
                }
                return '';
              }
            });
            
            documentationHtml = md.render(documentationContent);
          }
        } else {
          // No content found, clear HTML
          documentationHtml = null;
        }
      }
    } catch (err) {
      console.error('Error loading documentation:', err);
      documentationHtml = null;
    } finally {
      loadingDocs = false;
    }
  }

  async function loadRepoImages() {
    try {
      // Get images from page data (loaded from announcement)
      // Use $page.data directly to ensure we get the latest data
      const data = $page.data as typeof pageData;
      if (data.image) {
        repoImage = data.image;
        console.log('[Repo Images] Loaded image from pageData:', repoImage);
      }
      if (data.banner) {
        repoBanner = data.banner;
        console.log('[Repo Images] Loaded banner from pageData:', repoBanner);
      }

      // Also fetch from announcement directly as fallback (only if not private or user has access)
      if (!repoImage && !repoBanner) {
        const data = $page.data as typeof pageData;
        // Check access for private repos
        if (data.repoIsPrivate) {
          const headers: Record<string, string> = {};
          if (userPubkey) {
            try {
              const decoded = nip19.decode(userPubkey);
              if (decoded.type === 'npub') {
                headers['X-User-Pubkey'] = decoded.data as string;
              } else {
                headers['X-User-Pubkey'] = userPubkey;
              }
            } catch {
              headers['X-User-Pubkey'] = userPubkey;
            }
          }
          
          const accessResponse = await fetch(`/api/repos/${npub}/${repo}/access`, { headers });
          if (!accessResponse.ok) {
            // Access check failed, don't fetch images
            return;
          }
          const accessData = await accessResponse.json();
          if (!accessData.canView) {
            // User doesn't have access, don't fetch images
            return;
          }
        }
        
        const decoded = nip19.decode(npub);
        if (decoded.type === 'npub') {
          const repoOwnerPubkey = decoded.data as string;
          const client = new NostrClient(DEFAULT_NOSTR_RELAYS);
          const events = await client.fetchEvents([
            {
              kinds: [30617], // REPO_ANNOUNCEMENT
              authors: [repoOwnerPubkey],
              '#d': [repo],
              limit: 1
            }
          ]);

          if (events.length > 0) {
            const announcement = events[0];
            const imageTag = announcement.tags.find((t: string[]) => t[0] === 'image');
            const bannerTag = announcement.tags.find((t: string[]) => t[0] === 'banner');
            
            if (imageTag?.[1]) {
              repoImage = imageTag[1];
              console.log('[Repo Images] Loaded image from announcement:', repoImage);
            }
            if (bannerTag?.[1]) {
              repoBanner = bannerTag[1];
              console.log('[Repo Images] Loaded banner from announcement:', repoBanner);
            }
          } else {
            console.log('[Repo Images] No announcement found');
          }
        }
      }
      
      if (!repoImage && !repoBanner) {
        console.log('[Repo Images] No images found in announcement');
      }
    } catch (err) {
      console.error('Error loading repo images:', err);
    }
  }

  // Reactively update images when pageData changes (only once, when data becomes available)
  $effect(() => {
    const data = $page.data as typeof pageData;
    // Only update if we have new data and don't already have the images set
    if (data.image && data.image !== repoImage) {
      repoImage = data.image;
      console.log('[Repo Images] Updated image from pageData (reactive):', repoImage);
    }
    if (data.banner && data.banner !== repoBanner) {
      repoBanner = data.banner;
      console.log('[Repo Images] Updated banner from pageData (reactive):', repoBanner);
    }
  });

  onMount(async () => {
    // Initialize bookmarks service
    bookmarksService = new BookmarksService(DEFAULT_NOSTR_SEARCH_RELAYS);
    
    // Decode npub to get repo owner pubkey for bookmark address
    try {
      const decoded = nip19.decode(npub);
      if (decoded.type === 'npub') {
        repoOwnerPubkey = decoded.data as string;
        repoAddress = `${KIND.REPO_ANNOUNCEMENT}:${repoOwnerPubkey}:${repo}`;
      }
    } catch (err) {
      console.warn('Failed to decode npub for bookmark address:', err);
    }

    // Close menu when clicking outside (handled by RepoHeaderEnhanced component)
    function handleClickOutside(event: MouseEvent) {
      const target = event.target as HTMLElement;
      if (showRepoMenu && !target.closest('.repo-header')) {
        showRepoMenu = false;
      }
    }

    document.addEventListener('click', handleClickOutside);

    await loadBranches();
    // Skip other API calls if repository doesn't exist
    if (repoNotFound) {
      loading = false;
      return;
    }
    // loadBranches() already handles setting currentBranch to the default branch
    await loadFiles();
    await checkAuth();
    await loadTags();
    await checkMaintainerStatus();
    await loadBookmarkStatus();
    await loadAllMaintainers();
    
    // Check clone status (needed to disable write operations)
    await checkCloneStatus();
    await checkVerification();
    await loadReadme();
    await loadForkInfo();
    await loadRepoImages();
    
    // Set up auto-save if enabled
    setupAutoSave().catch(err => console.warn('Failed to setup auto-save:', err));
  });
  
  // Cleanup on destroy
  onDestroy(() => {
    if (autoSaveInterval) {
      clearInterval(autoSaveInterval);
      autoSaveInterval = null;
    }
    if (readmeAutoLoadTimeout) {
      clearTimeout(readmeAutoLoadTimeout);
      readmeAutoLoadTimeout = null;
    }
  });

  async function checkAuth() {
    // Check userStore first
    const currentUser = $userStore;
    if (currentUser.userPubkey && currentUser.userPubkeyHex) {
      userPubkey = currentUser.userPubkey;
      userPubkeyHex = currentUser.userPubkeyHex;
      // Recheck maintainer status and bookmark status after auth
      await checkMaintainerStatus();
      await loadBookmarkStatus();
      return;
    }
    
    // Fallback: try NIP-07 if store doesn't have it
    try {
      if (isNIP07Available()) {
        const pubkey = await getPublicKeyWithNIP07();
        userPubkey = pubkey;
        // Convert to hex if needed
        if (/^[0-9a-f]{64}$/i.test(pubkey)) {
          userPubkeyHex = pubkey.toLowerCase();
        } else {
          try {
            const decoded = nip19.decode(pubkey);
            if (decoded.type === 'npub') {
              userPubkeyHex = decoded.data as string;
            }
          } catch {
            userPubkeyHex = pubkey;
          }
        }
        // Recheck maintainer status and bookmark status after auth
        await checkMaintainerStatus();
        await loadBookmarkStatus();
      }
    } catch (err) {
      console.log('NIP-07 not available or user not connected');
      userPubkey = null;
      userPubkeyHex = null;
    }
  }

  async function login() {
    // Check userStore first
    const currentUser = $userStore;
    if (currentUser.userPubkey && currentUser.userPubkeyHex) {
      userPubkey = currentUser.userPubkey;
      userPubkeyHex = currentUser.userPubkeyHex;
      // Re-check maintainer status and bookmark status after login
      await checkMaintainerStatus();
      await loadBookmarkStatus();
      // Check for pending transfers (user is already logged in via store)
      if (userPubkeyHex) {
        try {
          const response = await fetch('/api/transfers/pending', {
            headers: {
              'X-User-Pubkey': userPubkeyHex
            }
          });
          if (response.ok) {
            const data = await response.json();
            if (data.pendingTransfers && data.pendingTransfers.length > 0) {
              window.dispatchEvent(new CustomEvent('pendingTransfers', { 
                detail: { transfers: data.pendingTransfers } 
              }));
            }
          }
        } catch (err) {
          console.error('Failed to check for pending transfers:', err);
        }
      }
      return;
    }
    
    // Fallback: try NIP-07 - need to check write access and update store
    try {
      if (!isNIP07Available()) {
        alert('NIP-07 extension not found. Please install a Nostr extension like Alby or nos2x.');
        return;
      }
      const pubkey = await getPublicKeyWithNIP07();
      let pubkeyHex: string;
      // Convert to hex if needed
      if (/^[0-9a-f]{64}$/i.test(pubkey)) {
        pubkeyHex = pubkey.toLowerCase();
        userPubkey = pubkey;
      } else {
        try {
          const decoded = nip19.decode(pubkey);
          if (decoded.type === 'npub') {
            pubkeyHex = decoded.data as string;
            userPubkey = pubkey;
          } else {
            throw new Error('Invalid pubkey format');
          }
        } catch {
          error = 'Invalid public key format';
          return;
        }
      }
      
      userPubkeyHex = pubkeyHex;
      
      // Check write access and update user store
      const { determineUserLevel } = await import('$lib/services/nostr/user-level-service.js');
      const levelResult = await determineUserLevel(userPubkey, userPubkeyHex);
      
      // Update user store with write access level
      userStore.setUser(
        levelResult.userPubkey,
        levelResult.userPubkeyHex,
        levelResult.level,
        levelResult.error || null
      );
      
      // Update activity tracking
      const { updateActivity } = await import('$lib/services/activity-tracker.js');
      updateActivity();
      
      // Check for pending transfer events
      if (userPubkeyHex) {
        try {
          const response = await fetch('/api/transfers/pending', {
            headers: {
              'X-User-Pubkey': userPubkeyHex
            }
          });
          if (response.ok) {
            const data = await response.json();
            if (data.pendingTransfers && data.pendingTransfers.length > 0) {
              window.dispatchEvent(new CustomEvent('pendingTransfers', { 
                detail: { transfers: data.pendingTransfers } 
              }));
            }
          }
        } catch (err) {
          console.error('Failed to check for pending transfers:', err);
        }
      }
      
      // Re-check maintainer status and bookmark status after login
      await checkMaintainerStatus();
      await loadBookmarkStatus();
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to connect';
      console.error('Login error:', err);
    }
  }


  async function loadBookmarkStatus() {
    if (!userPubkey || !repoAddress || !bookmarksService) return;
    
    try {
      isBookmarked = await bookmarksService.isBookmarked(userPubkey, repoAddress);
    } catch (err) {
      console.warn('Failed to load bookmark status:', err);
    }
  }

  async function toggleBookmark() {
    if (!userPubkey || !repoAddress || !bookmarksService || loadingBookmark) return;
    
    loadingBookmark = true;
    try {
      // Get user's relays for publishing
      const { getUserRelays } = await import('$lib/services/nostr/user-relays.js');
      const allSearchRelays = [...new Set([...DEFAULT_NOSTR_SEARCH_RELAYS, ...DEFAULT_NOSTR_RELAYS])];
      const fullRelayClient = new NostrClient(allSearchRelays);
      const { outbox, inbox } = await getUserRelays(userPubkey, fullRelayClient);
      const userRelays = combineRelays(outbox.length > 0 ? outbox : inbox, DEFAULT_NOSTR_RELAYS);
      
      let success = false;
      if (isBookmarked) {
        success = await bookmarksService.removeBookmark(userPubkey, repoAddress, userRelays);
      } else {
        success = await bookmarksService.addBookmark(userPubkey, repoAddress, userRelays);
      }
      
      if (success) {
        isBookmarked = !isBookmarked;
      } else {
        alert(`Failed to ${isBookmarked ? 'remove' : 'add'} bookmark. Please try again.`);
      }
    } catch (err) {
      console.error('Failed to toggle bookmark:', err);
      alert(`Failed to ${isBookmarked ? 'remove' : 'add'} bookmark: ${String(err)}`);
    } finally {
      loadingBookmark = false;
    }
  }

  async function copyEventId() {
    if (!repoAddress || !repoOwnerPubkey) {
      alert('Repository address not available');
      return;
    }

    try {
      // Parse the repo address: kind:pubkey:identifier
      const parts = repoAddress.split(':');
      if (parts.length < 3) {
        throw new Error('Invalid repository address format');
      }

      const kind = parseInt(parts[0]);
      const pubkey = parts[1];
      const identifier = parts.slice(2).join(':'); // In case identifier contains ':'

      // Generate naddr synchronously
      const naddr = nip19.naddrEncode({
        kind,
        pubkey,
        identifier,
        relays: [] // Optional: could include relays if available
      });

      // Copy naddr to clipboard immediately (while we have user activation)
      try {
        await navigator.clipboard.writeText(naddr);
      } catch (clipboardErr) {
        // Fallback: use execCommand for older browsers or if clipboard API fails
        const textArea = document.createElement('textarea');
        textArea.value = naddr;
        textArea.style.position = 'fixed';
        textArea.style.opacity = '0';
        document.body.appendChild(textArea);
        textArea.select();
        try {
          document.execCommand('copy');
          textArea.remove();
        } catch (execErr) {
          textArea.remove();
          throw new Error('Failed to copy to clipboard. Please copy manually: ' + naddr);
        }
      }
      
      // Show message with naddr
      alert(`Event ID copied to clipboard!\n\nnaddr (repository address):\n${naddr}`);
    } catch (err) {
      console.error('Failed to copy event ID:', err);
      alert(`Failed to copy event ID: ${err instanceof Error ? err.message : String(err)}`);
    }
  }

  async function checkMaintainerStatus() {
    if (repoNotFound || !userPubkey) {
      isMaintainer = false;
      return;
    }

    loadingMaintainerStatus = true;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/maintainers?userPubkey=${encodeURIComponent(userPubkey)}`);
      if (response.ok) {
        const data = await response.json();
        isMaintainer = data.isMaintainer || false;
      }
    } catch (err) {
      console.error('Failed to check maintainer status:', err);
      isMaintainer = false;
    } finally {
      loadingMaintainerStatus = false;
    }
  }

  async function loadAllMaintainers() {
    if (repoNotFound || loadingMaintainers) return;
    
    loadingMaintainers = true;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/maintainers`);
      if (response.ok) {
        const data = await response.json();
        const owner = data.owner;
        const maintainers = data.maintainers || [];
        
        // Create array with all maintainers, marking the owner
        const allMaintainersList: Array<{ pubkey: string; isOwner: boolean }> = [];
        const seen = new Set<string>();
        const ownerLower = owner?.toLowerCase();
        
        // Process all maintainers, marking owner and deduplicating
        for (const maintainer of maintainers) {
          const maintainerLower = maintainer.toLowerCase();
          
          // Skip if we've already added this pubkey (case-insensitive check)
          if (seen.has(maintainerLower)) {
            continue;
          }
          
          // Mark as seen
          seen.add(maintainerLower);
          
          // Determine if this is the owner
          const isOwner = ownerLower && maintainerLower === ownerLower;
          
          // Add to list
          allMaintainersList.push({ 
            pubkey: maintainer, 
            isOwner: !!isOwner
          });
        }
        
        // Sort: owner first, then other maintainers
        allMaintainersList.sort((a, b) => {
          if (a.isOwner && !b.isOwner) return -1;
          if (!a.isOwner && b.isOwner) return 1;
          return 0;
        });
        
        // Ensure owner is always included (in case they weren't in maintainers list)
        if (owner && !seen.has(ownerLower)) {
          allMaintainersList.unshift({ pubkey: owner, isOwner: true });
        }
        
        allMaintainers = allMaintainersList;
      }
    } catch (err) {
      console.error('Failed to load maintainers:', err);
      maintainersLoaded = false; // Reset flag on error
      // Fallback to pageData if available
      if (pageData.repoOwnerPubkey) {
        allMaintainers = [{ pubkey: pageData.repoOwnerPubkey, isOwner: true }];
        if (pageData.repoMaintainers) {
          for (const maintainer of pageData.repoMaintainers) {
            if (maintainer.toLowerCase() !== pageData.repoOwnerPubkey.toLowerCase()) {
              allMaintainers.push({ pubkey: maintainer, isOwner: false });
            }
          }
        }
      }
    } finally {
      loadingMaintainers = false;
    }
  }

  async function checkVerification() {
    if (repoNotFound) return;
    loadingVerification = true;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/verify`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        const data = await response.json();
        console.log('[Verification] Response:', data);
        verificationStatus = data;
      } else {
        console.warn('[Verification] Response not OK:', response.status, response.statusText);
        verificationStatus = { verified: false, error: `Verification check failed: ${response.status}` };
      }
    } catch (err) {
      console.error('[Verification] Failed to check verification:', err);
      verificationStatus = { verified: false, error: 'Failed to check verification' };
    } finally {
      loadingVerification = false;
      console.log('[Verification] Status after check:', verificationStatus);
    }
  }

  async function generateAnnouncementFileForRepo() {
    if (!pageData.repoOwnerPubkey || !userPubkeyHex) {
      error = 'Unable to generate announcement file: missing repository or user information';
      return;
    }

    try {
      // Fetch the repository announcement event
      const nostrClient = new NostrClient([...new Set([...DEFAULT_NOSTR_RELAYS, ...DEFAULT_NOSTR_SEARCH_RELAYS])]);
      const events = await nostrClient.fetchEvents([
        {
          kinds: [KIND.REPO_ANNOUNCEMENT],
          authors: [pageData.repoOwnerPubkey],
          '#d': [repo],
          limit: 1
        }
      ]);

      if (events.length === 0) {
        error = 'Repository announcement not found. Please ensure the repository is registered on Nostr.';
        return;
      }

      const announcement = events[0] as NostrEvent;
      // Generate announcement event JSON (for download/reference)
      verificationFileContent = JSON.stringify(announcement, null, 2) + '\n';
      showVerificationDialog = true;
    } catch (err) {
      console.error('Failed to generate announcement file:', err);
      error = `Failed to generate announcement file: ${err instanceof Error ? err.message : String(err)}`;
    }
  }

  function copyVerificationToClipboard() {
    if (!verificationFileContent) return;
    
    navigator.clipboard.writeText(verificationFileContent).then(() => {
      alert('Verification file content copied to clipboard!');
    }).catch((err) => {
      console.error('Failed to copy:', err);
      alert('Failed to copy to clipboard. Please select and copy manually.');
    });
  }

  async function deleteAnnouncement() {
    if (!userPubkey || !userPubkeyHex) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    if (!pageData.repoOwnerPubkey || userPubkeyHex !== pageData.repoOwnerPubkey) {
      alert('Only the repository owner can delete the announcement');
      return;
    }

    if (!confirm('Are you sure you want to send a deletion request for this repository announcement? This will request relays to delete the announcement event. This action cannot be undone.')) {
      return;
    }

    deletingAnnouncement = true;
    error = null;

    try {
      // Fetch the repository announcement to get its event ID
      const nostrClient = new NostrClient([...new Set([...DEFAULT_NOSTR_RELAYS, ...DEFAULT_NOSTR_SEARCH_RELAYS])]);
      const events = await nostrClient.fetchEvents([
        {
          kinds: [KIND.REPO_ANNOUNCEMENT],
          authors: [pageData.repoOwnerPubkey],
          '#d': [repo],
          limit: 1
        }
      ]);

      if (events.length === 0) {
        throw new Error('Repository announcement not found');
      }

      const announcement = events[0];
      announcementEventId = announcement.id;

      // Get user relays
      const { outbox } = await getUserRelays(userPubkeyHex, nostrClient);
      const combinedRelays = combineRelays(outbox);

      // Create deletion request (NIP-09)
      const deletionRequestTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
        kind: KIND.DELETION_REQUEST,
        pubkey: userPubkeyHex,
        created_at: Math.floor(Date.now() / 1000),
        content: `Requesting deletion of repository announcement for ${repo}`,
        tags: [
          ['e', announcement.id], // Reference to the announcement event
          ['a', `${KIND.REPO_ANNOUNCEMENT}:${pageData.repoOwnerPubkey}:${repo}`], // Repository address
          ['k', KIND.REPO_ANNOUNCEMENT.toString()] // Kind of event being deleted
        ]
      };

      // Sign with NIP-07
      const signedDeletionRequest = await signEventWithNIP07(deletionRequestTemplate);

      // Publish to relays
      const publishResult = await nostrClient.publishEvent(signedDeletionRequest, combinedRelays);

      if (publishResult.success.length > 0) {
        alert(`Deletion request published successfully to ${publishResult.success.length} relay(s).`);
      } else {
        throw new Error(`Failed to publish deletion request to any relay. Errors: ${publishResult.failed.map(f => `${f.relay}: ${f.error}`).join('; ')}`);
      }
    } catch (err) {
      console.error('Failed to delete announcement:', err);
      error = err instanceof Error ? err.message : 'Failed to send deletion request';
      alert(error);
    } finally {
      deletingAnnouncement = false;
    }
  }

  function downloadVerificationFile() {
    if (!verificationFileContent) return;
    
    const blob = new Blob([verificationFileContent], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'announcement-event.json';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }

  // Helper function to build headers with user pubkey
  function buildApiHeaders(): Record<string, string> {
    const headers: Record<string, string> = {};
    // Use $userStore directly to ensure we get the latest value
    const currentUserPubkeyHex = $userStore.userPubkeyHex || userPubkeyHex;
    if (currentUserPubkeyHex) {
      headers['X-User-Pubkey'] = currentUserPubkeyHex;
      // Debug logging (remove in production)
      console.debug('[API Headers] Sending X-User-Pubkey:', currentUserPubkeyHex.substring(0, 16) + '...');
    } else {
      console.debug('[API Headers] No user pubkey available, sending request without X-User-Pubkey header');
    }
    return headers;
  }

  async function loadBranches() {
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/branches`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        branches = await response.json();
        if (branches.length > 0) {
          // Branches can be an array of objects with .name property or array of strings
          const branchNames = branches.map((b: any) => typeof b === 'string' ? b : b.name);
          
          // Fetch the actual default branch from the API
          try {
            const defaultBranchResponse = await fetch(`/api/repos/${npub}/${repo}/default-branch`, {
              headers: buildApiHeaders()
            });
            if (defaultBranchResponse.ok) {
              const defaultBranchData = await defaultBranchResponse.json();
              defaultBranch = defaultBranchData.defaultBranch || defaultBranchData.branch || null;
            }
          } catch (err) {
            console.warn('Failed to fetch default branch, using fallback logic:', err);
          }
          
          // Fallback: Detect default branch: prefer master, then main, then first branch
          if (!defaultBranch) {
            if (branchNames.includes('master')) {
              defaultBranch = 'master';
            } else if (branchNames.includes('main')) {
              defaultBranch = 'main';
            } else {
              defaultBranch = branchNames[0];
            }
          }
          
          // Only update currentBranch if it's not set or if the current branch doesn't exist
          if (!currentBranch || !branchNames.includes(currentBranch)) {
            currentBranch = defaultBranch;
          }
        }
      } else if (response.status === 404) {
        // Repository not provisioned yet - set error message and flag
        repoNotFound = true;
        error = `Repository not found. This repository exists in Nostr but hasn't been provisioned on this server yet. The server will automatically provision it soon, or you can contact the server administrator.`;
      } else if (response.status === 403) {
        // Access denied - don't set repoNotFound, allow retry after login
        const errorText = await response.text().catch(() => response.statusText);
        error = `Access denied: ${errorText}. You may need to log in or you may not have permission to view this repository.`;
        console.warn('[Branches] Access denied, user may need to log in');
      }
    } catch (err) {
      console.error('Failed to load branches:', err);
    }
  }

  async function loadFiles(path: string = '') {
    // Skip if repository doesn't exist
    if (repoNotFound) return;
    
    loading = true;
    error = null;
    try {
      const url = `/api/repos/${npub}/${repo}/tree?ref=${currentBranch}&path=${encodeURIComponent(path)}`;
      const response = await fetch(url, {
        headers: buildApiHeaders()
      });
      
      if (!response.ok) {
        if (response.status === 404) {
          repoNotFound = true;
          throw new Error(`Repository not found. This repository exists in Nostr but hasn't been provisioned on this server yet. The server will automatically provision it soon, or you can contact the server administrator.`);
        } else if (response.status === 403) {
          // 403 means access denied - don't set repoNotFound, just show error
          // This allows retry after login
          const accessDeniedError = new Error(`Access denied: ${response.statusText}. You may need to log in or you may not have permission to view this repository.`);
          // Log as info since this is normal client behavior (not logged in or no access)
          console.info('Access denied (normal behavior):', accessDeniedError.message);
          throw accessDeniedError;
        }
        throw new Error(`Failed to load files: ${response.statusText}`);
      }

      files = await response.json();
      currentPath = path;
      
      // Auto-load README if we're in the root directory and no file is currently selected
      // Only attempt once per path to prevent loops
      if (path === '' && !currentFile && !readmeAutoLoadAttempted) {
        const readmeFile = findReadmeFile(files);
        if (readmeFile) {
          readmeAutoLoadAttempted = true;
          // Clear any existing timeout
          if (readmeAutoLoadTimeout) {
            clearTimeout(readmeAutoLoadTimeout);
          }
          // Small delay to ensure UI is ready
          readmeAutoLoadTimeout = setTimeout(() => {
            loadFile(readmeFile.path).catch(err => {
              // If load fails (e.g., 429 rate limit), reset the flag after a delay
              // so we can retry later, but not immediately
              if (err instanceof Error && err.message.includes('Too Many Requests')) {
                console.warn('[README] Rate limited, will retry later');
                setTimeout(() => {
                  readmeAutoLoadAttempted = false;
                }, 5000); // Retry after 5 seconds
              } else {
                // For other errors, reset immediately
                readmeAutoLoadAttempted = false;
              }
            });
            readmeAutoLoadTimeout = null;
          }, 100);
        }
      } else if (path !== '' || currentFile) {
        // Reset flag when navigating away from root or when a file is selected
        readmeAutoLoadAttempted = false;
        if (readmeAutoLoadTimeout) {
          clearTimeout(readmeAutoLoadTimeout);
          readmeAutoLoadTimeout = null;
        }
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to load files';
      // Only log as error if it's not a 403 (access denied), which is normal behavior
      if (err instanceof Error && err.message.includes('Access denied')) {
        // Already logged as info above, don't log again
      } else {
        console.error('Error loading files:', err);
      }
    } finally {
      loading = false;
    }
  }

  // Helper function to find README file in file list
  function findReadmeFile(fileList: Array<{ name: string; path: string; type: 'file' | 'directory' }>): { name: string; path: string; type: 'file' | 'directory' } | null {
    // Priority order for README files (most common first)
    const readmeExtensions = ['md', 'markdown', 'txt', 'adoc', 'asciidoc', 'rst', 'org'];
    
    // First, try to find README with extensions (prioritized order)
    for (const ext of readmeExtensions) {
      const readmeFile = fileList.find(file => 
        file.type === 'file' && 
        file.name.toLowerCase() === `readme.${ext}`
      );
      if (readmeFile) {
        return readmeFile;
      }
    }
    
    // Then check for README without extension
    const readmeNoExt = fileList.find(file => 
      file.type === 'file' && 
      file.name.toLowerCase() === 'readme'
    );
    if (readmeNoExt) {
      return readmeNoExt;
    }
    
    // Finally, check for any file starting with "readme." (case-insensitive)
    const readmeAny = fileList.find(file => 
      file.type === 'file' && 
      file.name.toLowerCase().startsWith('readme.')
    );
    if (readmeAny) {
      return readmeAny;
    }
    
    return null;
  }

  async function loadFile(filePath: string) {
    loading = true;
    error = null;
    try {
      // Ensure currentBranch is a string (branch name), not an object
      // If currentBranch is not set, use the first available branch or 'master' as fallback
      const branchName = typeof currentBranch === 'string' 
        ? currentBranch 
        : (typeof currentBranch === 'object' && currentBranch !== null && 'name' in currentBranch 
          ? (currentBranch as { name: string }).name 
          : (branches.length > 0 
            ? (typeof branches[0] === 'string' ? branches[0] : branches[0].name)
            : 'master'));
      const url = `/api/repos/${npub}/${repo}/file?path=${encodeURIComponent(filePath)}&ref=${branchName}`;
      const response = await fetch(url, {
        headers: buildApiHeaders()
      });
      
      if (!response.ok) {
        // Handle rate limiting specifically to prevent loops
        if (response.status === 429) {
          const error = new Error(`Failed to load file: Too Many Requests`);
          console.warn('[File Load] Rate limited, please wait before retrying');
          throw error;
        }
        throw new Error(`Failed to load file: ${response.statusText}`);
      }

      const data = await response.json();
      fileContent = data.content;
      editedContent = data.content;
      currentFile = filePath;
      hasChanges = false;
      
      // Reset README auto-load flag when a file is successfully loaded
      if (filePath && filePath.toLowerCase().includes('readme')) {
        readmeAutoLoadAttempted = false;
      }

      // Determine language from file extension
      const ext = filePath.split('.').pop()?.toLowerCase();
      if (ext === 'md' || ext === 'markdown') {
        fileLanguage = 'markdown';
      } else if (ext === 'adoc' || ext === 'asciidoc') {
        fileLanguage = 'asciidoc';
      } else {
        fileLanguage = 'text';
      }
      
      // Apply syntax highlighting for read-only view (non-maintainers)
      if (fileContent && !isMaintainer) {
        await applySyntaxHighlighting(fileContent, ext || '');
      }
      
      // Apply syntax highlighting to file content if not in editor
      if (fileContent && !isMaintainer) {
        // For read-only view, apply highlight.js
        await applySyntaxHighlighting(fileContent, ext || '');
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to load file';
      console.error('Error loading file:', err);
    } finally {
      loading = false;
    }
  }

  function handleContentChange(value: string) {
    editedContent = value;
    hasChanges = value !== fileContent;
  }

  function handleFileClick(file: { name: string; path: string; type: 'file' | 'directory' }) {
    if (file.type === 'directory') {
      pathStack.push(currentPath);
      loadFiles(file.path);
    } else {
      loadFile(file.path);
      // On mobile, switch to file viewer when a file is clicked
      if (window.innerWidth <= 768) {
        showFileListOnMobile = false;
      }
    }
  }

  function handleBack() {
    if (pathStack.length > 0) {
      const parentPath = pathStack.pop() || '';
      loadFiles(parentPath);
    } else {
      loadFiles('');
    }
  }

  // Cache for user profile email and name
  let cachedUserEmail = $state<string | null>(null);
  let cachedUserName = $state<string | null>(null);
  let fetchingUserEmail = $state(false);
  let fetchingUserName = $state(false);

  async function getUserEmail(): Promise<string> {
    // Check settings store first
    try {
      const settings = await settingsStore.getSettings();
      if (settings.userEmail && settings.userEmail.trim()) {
        cachedUserEmail = settings.userEmail.trim();
        return cachedUserEmail;
      }
    } catch (err) {
      console.warn('Failed to get userEmail from settings:', err);
    }

    // Return cached email if available
    if (cachedUserEmail) {
      return cachedUserEmail;
    }

    // If no user pubkey, can't proceed
    if (!userPubkeyHex) {
      throw new Error('User not authenticated');
    }

    // Prevent concurrent fetches
    if (fetchingUserEmail) {
      // Wait a bit and retry (shouldn't happen, but just in case)
      await new Promise(resolve => setTimeout(resolve, 100));
      if (cachedUserEmail) {
        return cachedUserEmail;
      }
    }

    fetchingUserEmail = true;
    let prefillEmail: string;
    
    try {
      // Fetch from kind 0 event (cache or relays)
      prefillEmail = await fetchUserEmail(userPubkeyHex, userPubkey || undefined, DEFAULT_NOSTR_RELAYS);
    } catch (err) {
      console.warn('Failed to fetch user profile for email:', err);
      // Fallback to shortenednpub@gitrepublic.web
      const npubFromPubkey = userPubkeyHex ? nip19.npubEncode(userPubkeyHex) : (userPubkey || 'unknown');
      const shortenedNpub = npubFromPubkey.substring(0, 20);
      prefillEmail = `${shortenedNpub}@gitrepublic.web`;
    } finally {
      fetchingUserEmail = false;
    }
    
    // Prompt user for email address
    const userEmail = prompt(
      'Please enter your email address for git commits.\n\n' +
      'This will be used as the author email in your commits.\n' +
      'You can use any email address you prefer.',
      prefillEmail
    );

    if (userEmail && userEmail.trim()) {
      // Basic email validation
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      if (emailRegex.test(userEmail.trim())) {
        cachedUserEmail = userEmail.trim();
        // Save to settings store
        settingsStore.setSetting('userEmail', cachedUserEmail).catch(console.error);
        return cachedUserEmail;
      } else {
        alert('Invalid email format. Using fallback email address.');
      }
    }

    // Use fallback if user cancelled or entered invalid email
    cachedUserEmail = prefillEmail;
    return cachedUserEmail;
  }

  async function getUserName(): Promise<string> {
    // Check settings store first
    try {
      const settings = await settingsStore.getSettings();
      if (settings.userName && settings.userName.trim()) {
        cachedUserName = settings.userName.trim();
        return cachedUserName;
      }
    } catch (err) {
      console.warn('Failed to get userName from settings:', err);
    }

    // Return cached name if available
    if (cachedUserName) {
      return cachedUserName;
    }

    // If no user pubkey, can't proceed
    if (!userPubkeyHex) {
      throw new Error('User not authenticated');
    }

    // Prevent concurrent fetches
    if (fetchingUserName) {
      // Wait a bit and retry (shouldn't happen, but just in case)
      await new Promise(resolve => setTimeout(resolve, 100));
      if (cachedUserName) {
        return cachedUserName;
      }
    }

    fetchingUserName = true;
    let prefillName: string;
    
    try {
      // Fetch from kind 0 event (cache or relays)
      prefillName = await fetchUserName(userPubkeyHex, userPubkey || undefined, DEFAULT_NOSTR_RELAYS);
    } catch (err) {
      console.warn('Failed to fetch user profile for name:', err);
      // Fallback to shortened npub (20 chars)
      const npubFromPubkey = userPubkeyHex ? nip19.npubEncode(userPubkeyHex) : (userPubkey || 'unknown');
      prefillName = npubFromPubkey.substring(0, 20);
    } finally {
      fetchingUserName = false;
    }
    
    // Prompt user for name
    const userName = prompt(
      'Please enter your name for git commits.\n\n' +
      'This will be used as the author name in your commits.\n' +
      'You can use any name you prefer.',
      prefillName
    );

    if (userName && userName.trim()) {
      cachedUserName = userName.trim();
      // Save to settings store
      settingsStore.setSetting('userName', cachedUserName).catch(console.error);
      return cachedUserName;
    }

    // Use fallback if user cancelled
    cachedUserName = prefillName;
    return cachedUserName;
  }

  async function setupAutoSave() {
    // Clear existing interval if any
    if (autoSaveInterval) {
      clearInterval(autoSaveInterval);
      autoSaveInterval = null;
    }
    
    // Check if auto-save is enabled
    try {
      const settings = await settingsStore.getSettings();
      if (!settings.autoSave) {
        return; // Auto-save disabled
      }
    } catch (err) {
      console.warn('Failed to check auto-save setting:', err);
      return;
    }
    
    // Set up interval to auto-save every 10 minutes
    autoSaveInterval = setInterval(async () => {
      await autoSaveFile();
    }, 10 * 60 * 1000); // 10 minutes
  }
  
  async function autoSaveFile() {
    // Only auto-save if:
    // 1. There are changes
    // 2. A file is open
    // 3. User is logged in
    // 4. User is a maintainer
    // 5. Not currently saving
    // 6. Not in clone state
    if (!hasChanges || !currentFile || !userPubkey || !isMaintainer || saving || needsClone) {
      return;
    }
    
    // Check auto-save setting again (in case it changed)
    try {
      const settings = await settingsStore.getSettings();
      if (!settings.autoSave) {
        // Auto-save was disabled, clear interval
        if (autoSaveInterval) {
          clearInterval(autoSaveInterval);
          autoSaveInterval = null;
        }
        return;
      }
    } catch (err) {
      console.warn('Failed to check auto-save setting:', err);
      return;
    }
    
    // Generate a default commit message
    const autoCommitMessage = `Auto-save: ${new Date().toLocaleString()}`;
    
    try {
      // Get user email and name from settings
      const authorEmail = await getUserEmail();
      const authorName = await getUserName();
      
      // Sign commit with NIP-07 (client-side)
      let commitSignatureEvent: NostrEvent | null = null;
      if (isNIP07Available()) {
        try {
          const { KIND } = await import('$lib/types/nostr.js');
          const timestamp = Math.floor(Date.now() / 1000);
          const eventTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
            kind: KIND.COMMIT_SIGNATURE,
            pubkey: '', // Will be filled by NIP-07
            created_at: timestamp,
            tags: [
              ['author', authorName, authorEmail],
              ['message', autoCommitMessage]
            ],
            content: `Signed commit: ${autoCommitMessage}`
          };
          commitSignatureEvent = await signEventWithNIP07(eventTemplate);
        } catch (err) {
          console.warn('Failed to sign commit with NIP-07:', err);
          // Continue without signature if signing fails
        }
      }
      
      const response = await fetch(`/api/repos/${npub}/${repo}/file`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...buildApiHeaders()
        },
        body: JSON.stringify({
          path: currentFile,
          content: editedContent,
          commitMessage: autoCommitMessage,
          authorName: authorName,
          authorEmail: authorEmail,
          branch: currentBranch,
          userPubkey: userPubkey,
          commitSignatureEvent: commitSignatureEvent
        })
      });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({ message: response.statusText }));
        console.warn('Auto-save failed:', errorData.message || 'Failed to save file');
        return;
      }

      // Reload file to get updated content
      await loadFile(currentFile);
      // Note: We don't show an alert for auto-save, it's silent
      console.log('Auto-saved file:', currentFile);
    } catch (err) {
      console.warn('Error during auto-save:', err);
      // Don't show error to user, it's silent
    }
  }

  async function saveFile() {
    if (!currentFile || !commitMessage.trim()) {
      alert('Please enter a commit message');
      return;
    }

    if (!userPubkey) {
      alert('Please connect your NIP-07 extension to save files');
      return;
    }

    // Validate branch selection
    if (!currentBranch || typeof currentBranch !== 'string') {
      alert('Please select a branch before saving the file');
      return;
    }

    saving = true;
    error = null;

    try {
      // Get user email and name (from profile or prompt)
      const authorEmail = await getUserEmail();
      const authorName = await getUserName();
      
      // Sign commit with NIP-07 (client-side)
      let commitSignatureEvent: NostrEvent | null = null;
      if (isNIP07Available()) {
        try {
          const { KIND } = await import('$lib/types/nostr.js');
          const timestamp = Math.floor(Date.now() / 1000);
          const eventTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
            kind: KIND.COMMIT_SIGNATURE,
            pubkey: '', // Will be filled by NIP-07
            created_at: timestamp,
            tags: [
              ['author', authorName, authorEmail],
              ['message', commitMessage.trim()]
            ],
            content: `Signed commit: ${commitMessage.trim()}`
          };
          commitSignatureEvent = await signEventWithNIP07(eventTemplate);
        } catch (err) {
          console.warn('Failed to sign commit with NIP-07:', err);
          // Continue without signature if signing fails
        }
      }
      
      const response = await fetch(`/api/repos/${npub}/${repo}/file`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...buildApiHeaders()
        },
        body: JSON.stringify({
          path: currentFile,
          content: editedContent,
          commitMessage: commitMessage.trim(),
          authorName: authorName,
          authorEmail: authorEmail,
          branch: currentBranch,
          userPubkey: userPubkey,
          commitSignatureEvent: commitSignatureEvent // Send the signed event to server
        })
      });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({ message: response.statusText }));
        const errorMessage = errorData.message || errorData.error || 'Failed to save file';
        throw new Error(errorMessage);
      }

      // Reload file to get updated content
      await loadFile(currentFile);
      commitMessage = '';
      showCommitDialog = false;
      alert('File saved successfully!');
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to save file';
      console.error('Error saving file:', err);
    } finally {
      saving = false;
    }
  }

  function handleBranchChangeDirect(branch: string) {
    currentBranch = branch;
    // Create a synthetic event for the existing handler
    const syntheticEvent = {
      target: { value: branch }
    } as unknown as Event;
    handleBranchChange(syntheticEvent);
  }

  async function handleBranchChange(event: Event) {
    const target = event.target as HTMLSelectElement;
    currentBranch = target.value;
    
    // Reload all branch-dependent data
    const reloadPromises: Promise<void>[] = [];
    
    // Always reload files (and current file if open)
    if (currentFile) {
      reloadPromises.push(loadFile(currentFile).catch(err => console.warn('Failed to reload file after branch change:', err)));
    } else {
      reloadPromises.push(loadFiles(currentPath).catch(err => console.warn('Failed to reload files after branch change:', err)));
    }
    
    // Reload README (branch-specific)
    reloadPromises.push(loadReadme().catch(err => console.warn('Failed to reload README after branch change:', err)));
    
    // Reload commit history if history tab is active
    if (activeTab === 'history') {
      reloadPromises.push(loadCommitHistory().catch(err => console.warn('Failed to reload commit history after branch change:', err)));
    }
    
    // Reload documentation if docs tab is active (might be branch-specific)
    if (activeTab === 'docs') {
      // Reset documentation to force reload
      documentationHtml = null;
      documentationContent = null;
      documentationKind = null;
      reloadPromises.push(loadDocumentation().catch(err => console.warn('Failed to reload documentation after branch change:', err)));
    }
    
    // Wait for all reloads to complete
    await Promise.all(reloadPromises);
  }

  async function createFile() {
    if (!newFileName.trim()) {
      alert('Please enter a file name');
      return;
    }

    if (!userPubkey) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    // Validate branch selection
    if (!currentBranch || typeof currentBranch !== 'string') {
      alert('Please select a branch before creating the file');
      return;
    }

    saving = true;
    error = null;

    try {
      // Get user email and name (from profile or prompt)
      const authorEmail = await getUserEmail();
      const authorName = await getUserName();
      const filePath = currentPath ? `${currentPath}/${newFileName}` : newFileName;
      const commitMsg = `Create ${newFileName}`;
      
      // Sign commit with NIP-07 (client-side)
      let commitSignatureEvent: NostrEvent | null = null;
      if (isNIP07Available()) {
        try {
          const { KIND } = await import('$lib/types/nostr.js');
          const timestamp = Math.floor(Date.now() / 1000);
          const eventTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
            kind: KIND.COMMIT_SIGNATURE,
            pubkey: '', // Will be filled by NIP-07
            created_at: timestamp,
            tags: [
              ['author', authorName, authorEmail],
              ['message', commitMsg]
            ],
            content: `Signed commit: ${commitMsg}`
          };
          commitSignatureEvent = await signEventWithNIP07(eventTemplate);
        } catch (err) {
          console.warn('Failed to sign commit with NIP-07:', err);
          // Continue without signature if signing fails
        }
      }
      
      const response = await fetch(`/api/repos/${npub}/${repo}/file`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...buildApiHeaders()
        },
        body: JSON.stringify({
          path: filePath,
          content: newFileContent,
          commitMessage: commitMsg,
          authorName: authorName,
          authorEmail: authorEmail,
          branch: currentBranch,
          action: 'create',
          userPubkey: userPubkey,
          commitSignatureEvent: commitSignatureEvent // Send the signed event to server
        })
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Failed to create file');
      }

      showCreateFileDialog = false;
      newFileName = '';
      newFileContent = '';
      await loadFiles(currentPath);
      alert('File created successfully!');
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to create file';
    } finally {
      saving = false;
    }
  }

  async function deleteFile(filePath: string) {
    if (!confirm(`Are you sure you want to delete ${filePath}?`)) {
      return;
    }

    if (!userPubkey) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    // Validate branch selection
    if (!currentBranch || typeof currentBranch !== 'string') {
      alert('Please select a branch before deleting the file');
      return;
    }

    saving = true;
    error = null;

    try {
      // Get user email and name (from profile or prompt)
      const authorEmail = await getUserEmail();
      const authorName = await getUserName();
      const commitMsg = `Delete ${filePath}`;
      
      // Sign commit with NIP-07 (client-side)
      let commitSignatureEvent: NostrEvent | null = null;
      if (isNIP07Available()) {
        try {
          const { KIND } = await import('$lib/types/nostr.js');
          const timestamp = Math.floor(Date.now() / 1000);
          const eventTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
            kind: KIND.COMMIT_SIGNATURE,
            pubkey: '', // Will be filled by NIP-07
            created_at: timestamp,
            tags: [
              ['author', authorName, authorEmail],
              ['message', commitMsg]
            ],
            content: `Signed commit: ${commitMsg}`
          };
          commitSignatureEvent = await signEventWithNIP07(eventTemplate);
        } catch (err) {
          console.warn('Failed to sign commit with NIP-07:', err);
          // Continue without signature if signing fails
        }
      }
      
      const response = await fetch(`/api/repos/${npub}/${repo}/file`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...buildApiHeaders()
        },
        body: JSON.stringify({
          path: filePath,
          commitMessage: commitMsg,
          authorName: authorName,
          authorEmail: authorEmail,
          branch: currentBranch,
          action: 'delete',
          userPubkey: userPubkey,
          commitSignatureEvent: commitSignatureEvent // Send the signed event to server
        })
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Failed to delete file');
      }

      if (currentFile === filePath) {
        currentFile = null;
      }
      await loadFiles(currentPath);
      alert('File deleted successfully!');
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to delete file';
    } finally {
      saving = false;
    }
  }

  async function createBranch() {
    if (!newBranchName.trim()) {
      alert('Please enter a branch name');
      return;
    }

    saving = true;
    error = null;

    try {
      // If no branches exist, use default branch from settings
      let fromBranch = newBranchFrom || currentBranch;
      if (!fromBranch && branches.length === 0) {
        const settings = await settingsStore.getSettings();
        fromBranch = settings.defaultBranch || 'master';
      }

      const response = await fetch(`/api/repos/${npub}/${repo}/branches`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...buildApiHeaders()
        },
        body: JSON.stringify({
          branchName: newBranchName,
          fromBranch: fromBranch || 'master' // Final fallback
        })
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Failed to create branch');
      }

      showCreateBranchDialog = false;
      newBranchName = '';
      await loadBranches();
      alert('Branch created successfully!');
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to create branch';
    } finally {
      saving = false;
    }
  }

  async function deleteBranch(branchName: string) {
    if (!confirm(`Are you sure you want to delete the branch "${branchName}"? This action cannot be undone.`)) {
      return;
    }

    if (!userPubkey) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    // Prevent deleting the current branch
    if (branchName === currentBranch) {
      alert('Cannot delete the currently selected branch. Please switch to a different branch first.');
      return;
    }

    saving = true;
    error = null;

    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/branches`, {
        method: 'DELETE',
        headers: {
          'Content-Type': 'application/json',
          ...buildApiHeaders()
        },
        body: JSON.stringify({
          branchName: branchName
        })
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Failed to delete branch');
      }

      await loadBranches();
      alert('Branch deleted successfully!');
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to delete branch';
      alert(error);
    } finally {
      saving = false;
    }
  }

  async function loadCommitHistory() {
    loadingCommits = true;
    error = null;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/commits?branch=${currentBranch}&limit=50`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        const data = await response.json();
        // Normalize commits: API-based commits use 'sha', local commits use 'hash'
        commits = data.map((commit: any) => ({
          hash: commit.hash || commit.sha || '',
          message: commit.message || 'No message',
          author: commit.author || 'Unknown',
          date: commit.date || new Date().toISOString(),
          files: commit.files || []
        })).filter((commit: any) => commit.hash); // Filter out commits without hash
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to load commit history';
    } finally {
      loadingCommits = false;
    }
  }

  async function viewDiff(commitHash: string) {
    loadingCommits = true;
    error = null;
    try {
      // Normalize commit hash (handle both 'hash' and 'sha' properties)
      const getCommitHash = (c: any) => c.hash || c.sha || '';
      const commitIndex = commits.findIndex(c => getCommitHash(c) === commitHash);
      const parentHash = commitIndex >= 0
        ? (commits[commitIndex + 1] ? getCommitHash(commits[commitIndex + 1]) : `${commitHash}^`)
        : `${commitHash}^`;
      
      const response = await fetch(`/api/repos/${npub}/${repo}/diff?from=${parentHash}&to=${commitHash}`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        diffData = await response.json();
        selectedCommit = commitHash;
        showDiff = true;
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to load diff';
    } finally {
      loadingCommits = false;
    }
  }

  async function loadTags() {
    if (repoNotFound) return;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/tags`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        tags = await response.json();
      }
    } catch (err) {
      console.error('Failed to load tags:', err);
    }
  }

  async function createTag() {
    if (!newTagName.trim()) {
      alert('Please enter a tag name');
      return;
    }

    if (!userPubkey) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    saving = true;
    error = null;

    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/tags`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...buildApiHeaders()
        },
        body: JSON.stringify({
          tagName: newTagName,
          ref: newTagRef,
          message: newTagMessage || undefined,
          userPubkey: userPubkey
        })
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Failed to create tag');
      }

      showCreateTagDialog = false;
      newTagName = '';
      newTagMessage = '';
      await loadTags();
      alert('Tag created successfully!');
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to create tag';
    } finally {
      saving = false;
    }
  }

  async function loadIssues() {
    loadingIssues = true;
    error = null;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/issues`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        const data = await response.json();
        issues = data.map((issue: { id: string; tags: string[][]; content: string; status?: string; pubkey: string; created_at: number; kind?: number }) => ({
          id: issue.id,
          subject: issue.tags.find((t: string[]) => t[0] === 'subject')?.[1] || 'Untitled',
          content: issue.content,
          status: issue.status || 'open',
          author: issue.pubkey,
          created_at: issue.created_at,
          kind: issue.kind || KIND.ISSUE
        }));
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to load issues';
    } finally {
      loadingIssues = false;
    }
  }

  async function createIssue() {
    if (!newIssueSubject.trim() || !newIssueContent.trim()) {
      alert('Please enter a subject and content');
      return;
    }

    if (!userPubkey) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    saving = true;
    error = null;

    try {
      const { IssuesService } = await import('$lib/services/nostr/issues-service.js');
      
      const decoded = nip19.decode(npub);
      if (decoded.type !== 'npub') {
        throw new Error('Invalid npub format');
      }
      const repoOwnerPubkey = decoded.data as string;

      // Get user's relays and combine with defaults
      const tempClient = new NostrClient(DEFAULT_NOSTR_RELAYS);
      const { outbox } = await getUserRelays(userPubkey, tempClient);
      const combinedRelays = combineRelays(outbox);

      const issuesService = new IssuesService(combinedRelays);
      const issue = await issuesService.createIssue(
        repoOwnerPubkey,
        repo,
        newIssueSubject.trim(),
        newIssueContent.trim(),
        newIssueLabels.filter(l => l.trim())
      );

      showCreateIssueDialog = false;
      newIssueSubject = '';
      newIssueContent = '';
      newIssueLabels = [''];
      await loadIssues();
      alert('Issue created successfully!');
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to create issue';
      console.error('Error creating issue:', err);
    } finally {
      saving = false;
    }
  }

  async function updateIssueStatus(issueId: string, issueAuthor: string, status: 'open' | 'closed' | 'resolved' | 'draft') {
    if (!userPubkeyHex) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    // Check if user is maintainer or issue author
    const isAuthor = userPubkeyHex === issueAuthor;
    if (!isMaintainer && !isAuthor) {
      alert('Only repository maintainers or issue authors can update issue status');
      return;
    }

    updatingIssueStatus = { ...updatingIssueStatus, [issueId]: true };
    error = null;

    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/issues`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          issueId,
          issueAuthor,
          status
        })
      });

      if (!response.ok) {
        const data = await response.json();
        throw new Error(data.error || 'Failed to update issue status');
      }

      await loadIssues();
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to update issue status';
      console.error('Error updating issue status:', err);
    } finally {
      updatingIssueStatus = { ...updatingIssueStatus, [issueId]: false };
    }
  }

  async function loadPRs() {
    loadingPRs = true;
    error = null;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/prs`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        const data = await response.json();
        prs = data.map((pr: { id: string; tags: string[][]; content: string; status?: string; pubkey: string; created_at: number; commitId?: string; kind?: number }) => ({
          id: pr.id,
          subject: pr.tags.find((t: string[]) => t[0] === 'subject')?.[1] || 'Untitled',
          content: pr.content,
          status: pr.status || 'open',
          author: pr.pubkey,
          created_at: pr.created_at,
          commitId: pr.tags.find((t: string[]) => t[0] === 'c')?.[1],
          kind: pr.kind || KIND.PULL_REQUEST
        }));
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to load pull requests';
    } finally {
      loadingPRs = false;
    }
  }

  async function createPR() {
    if (!newPRSubject.trim() || !newPRContent.trim() || !newPRCommitId.trim()) {
      alert('Please enter a subject, content, and commit ID');
      return;
    }

    if (!userPubkey) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    saving = true;
    error = null;

    try {
      const { PRsService } = await import('$lib/services/nostr/prs-service.js');
      const { getGitUrl } = await import('$lib/config.js');
      
      const decoded = nip19.decode(npub);
      if (decoded.type !== 'npub') {
        throw new Error('Invalid npub format');
      }
      const repoOwnerPubkey = decoded.data as string;

      // Get user's relays and combine with defaults
      const tempClient = new NostrClient(DEFAULT_NOSTR_RELAYS);
      const { outbox } = await getUserRelays(userPubkey, tempClient);
      const combinedRelays = combineRelays(outbox);

      const cloneUrl = getGitUrl(npub, repo);
      const prsService = new PRsService(combinedRelays);
      const pr = await prsService.createPullRequest(
        repoOwnerPubkey,
        repo,
        newPRSubject.trim(),
        newPRContent.trim(),
        newPRCommitId.trim(),
        cloneUrl,
        newPRBranchName.trim() || undefined,
        newPRLabels.filter(l => l.trim())
      );

      showCreatePRDialog = false;
      newPRSubject = '';
      newPRContent = '';
      newPRCommitId = '';
      newPRBranchName = '';
      newPRLabels = [''];
      await loadPRs();
      alert('Pull request created successfully!');
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to create pull request';
      console.error('Error creating PR:', err);
    } finally {
      saving = false;
    }
  }

  async function createPatch() {
    if (!newPatchContent.trim()) {
      alert('Please enter patch content');
      return;
    }

    if (!userPubkey || !userPubkeyHex) {
      alert('Please connect your NIP-07 extension');
      return;
    }

    creatingPatch = true;
    error = null;

    try {
      const decoded = nip19.decode(npub);
      if (decoded.type !== 'npub') {
        throw new Error('Invalid npub format');
      }
      const repoOwnerPubkey = decoded.data as string;
      const repoAddress = `${KIND.REPO_ANNOUNCEMENT}:${repoOwnerPubkey}:${repo}`;

      // Get user's relays and combine with defaults
      const tempClient = new NostrClient(DEFAULT_NOSTR_RELAYS);
      const { outbox } = await getUserRelays(userPubkey, tempClient);
      const combinedRelays = combineRelays(outbox);

      // Create patch event (kind 1617)
      const patchEventTemplate: Omit<NostrEvent, 'sig' | 'id'> = {
        kind: KIND.PATCH,
        pubkey: userPubkeyHex,
        created_at: Math.floor(Date.now() / 1000),
        tags: [
          ['a', repoAddress],
          ['p', repoOwnerPubkey],
          ['t', 'root']
        ],
        content: newPatchContent.trim()
      };

      // Add subject if provided
      if (newPatchSubject.trim()) {
        patchEventTemplate.tags.push(['subject', newPatchSubject.trim()]);
      }

      // Sign the event using NIP-07
      const signedEvent = await signEventWithNIP07(patchEventTemplate);

      // Publish to all available relays
      const publishClient = new NostrClient(combinedRelays);
      const result = await publishClient.publishEvent(signedEvent, combinedRelays);

      if (result.failed.length > 0 && result.success.length === 0) {
        throw new Error('Failed to publish patch to all relays');
      }

      showCreatePatchDialog = false;
      newPatchContent = '';
      newPatchSubject = '';
      alert('Patch created successfully!');
      // Reload patches
      await loadPatches();
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to create patch';
      console.error('Error creating patch:', err);
    } finally {
      creatingPatch = false;
    }
  }

  async function loadPatches() {
    if (repoNotFound) return;
    loadingPatches = true;
    error = null;
    try {
      const response = await fetch(`/api/repos/${npub}/${repo}/patches`, {
        headers: buildApiHeaders()
      });
      if (response.ok) {
        const data = await response.json();
        patches = data.map((patch: { id: string; tags: string[][]; content: string; pubkey: string; created_at: number; kind?: number }) => ({
          id: patch.id,
          subject: patch.tags.find((t: string[]) => t[0] === 'subject')?.[1] || 'Untitled',
          content: patch.content,
          author: patch.pubkey,
          created_at: patch.created_at,
          kind: patch.kind || KIND.PATCH
        }));
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to load patches';
      console.error('Error loading patches:', err);
    } finally {
      loadingPatches = false;
    }
  }

  // Only load tab content when tab actually changes, not on every render
  let lastTab = $state<string | null>(null);
  $effect(() => {
    if (activeTab !== lastTab) {
      lastTab = activeTab;
      if (activeTab === 'files') {
        // Files tab - ensure files are loaded and README is shown if available
        if (files.length === 0 || currentPath !== '') {
          loadFiles('');
        } else if (files.length > 0 && !currentFile) {
          // Files already loaded, ensure README is shown
          const readmeFile = findReadmeFile(files);
          if (readmeFile) {
            setTimeout(() => {
              loadFile(readmeFile.path);
            }, 100);
          }
        }
      } else if (activeTab === 'history') {
        loadCommitHistory();
      } else if (activeTab === 'tags') {
        loadTags();
      } else if (activeTab === 'issues') {
        loadIssues();
      } else if (activeTab === 'prs') {
        loadPRs();
      } else if (activeTab === 'docs') {
        loadDocumentation();
      } else if (activeTab === 'discussions') {
        loadDiscussions();
      } else if (activeTab === 'patches') {
        loadPatches();
      }
    }
  });

  // Reload all branch-dependent data when branch changes
  let lastBranch = $state<string | null>(null);
  $effect(() => {
    if (currentBranch && currentBranch !== lastBranch) {
      lastBranch = currentBranch;
      
      // Reload README (always branch-specific)
      loadReadme().catch(err => console.warn('Failed to reload README after branch change:', err));
      
      // Reload files if files tab is active
      if (activeTab === 'files') {
        if (currentFile) {
          loadFile(currentFile).catch(err => console.warn('Failed to reload file after branch change:', err));
        } else {
          loadFiles(currentPath).catch(err => console.warn('Failed to reload files after branch change:', err));
        }
      }
      
      // Reload commit history if history tab is active
      if (activeTab === 'history') {
        loadCommitHistory().catch(err => console.warn('Failed to reload commit history after branch change:', err));
      }
      
      // Reload documentation if docs tab is active (reset to force reload)
      if (activeTab === 'docs') {
        documentationHtml = null;
        documentationContent = null;
        documentationKind = null;
        loadDocumentation().catch(err => console.warn('Failed to reload documentation after branch change:', err));
      }
    }
  });
</script>

<svelte:head>
  <title>{pageData.title || `${repo} - Repository`}</title>
  <meta name="description" content={pageData.description || `Repository: ${repo}`} />
  
  <!-- OpenGraph / Facebook -->
  <meta property="og:type" content="website" />
  <meta property="og:title" content={pageData.title || `${pageData.repoName || repo} - Repository`} />
  <meta property="og:description" content={pageData.description || pageData.repoDescription || `Repository: ${pageData.repoName || repo}`} />
  <meta property="og:url" content={pageData.repoUrl || `https://${$page.url.host}${$page.url.pathname}`} />
  {#if (pageData.image || repoImage) && String(pageData.image || repoImage).trim()}
    <meta property="og:image" content={pageData.image || repoImage} />
  {/if}
  {#if (pageData.banner || repoBanner) && String(pageData.banner || repoBanner).trim()}
    <meta property="og:image:width" content="1200" />
    <meta property="og:image:height" content="630" />
  {/if}
  
  <!-- Twitter Card -->
  <meta name="twitter:card" content={repoBanner || repoImage ? "summary_large_image" : "summary"} />
  <meta name="twitter:title" content={pageData.title || `${pageData.repoName || repo} - Repository`} />
  <meta name="twitter:description" content={pageData.description || pageData.repoDescription || `Repository: ${pageData.repoName || repo}`} />
  {#if pageData.banner || repoBanner}
    <meta name="twitter:image" content={pageData.banner || repoBanner} />
  {:else if pageData.image || repoImage}
    <meta name="twitter:image" content={pageData.image || repoImage} />
  {/if}
</svelte:head>

<div class="container">
  <!-- Banner hidden on mobile, shown on desktop -->
  {#if repoBanner}
    <div class="repo-banner desktop-only">
      <img src={repoBanner} alt="" onerror={(e) => { 
        console.error('[Repo Images] Failed to load banner:', repoBanner); 
        const target = e.target as HTMLImageElement;
        if (target) target.style.display = 'none';
      }} />
    </div>
  {/if}
  
  {#if repoOwnerPubkey}
    <RepoHeaderEnhanced
      repoName={pageData.repoName || repo}
      repoDescription={pageData.repoDescription}
      ownerNpub={npub}
      ownerPubkey={repoOwnerPubkey}
      isMaintainer={isMaintainer}
      isPrivate={pageData.repoIsPrivate || false}
      cloneUrls={pageData.repoCloneUrls || []}
      branches={branches}
      currentBranch={currentBranch}
      topics={pageData.repoTopics || []}
      defaultBranch={defaultBranch}
      isRepoCloned={isRepoCloned}
      copyingCloneUrl={copyingCloneUrl}
      onBranchChange={handleBranchChangeDirect}
      onCopyCloneUrl={copyCloneUrl}
      onDeleteBranch={deleteBranch}
      onMenuToggle={() => showRepoMenu = !showRepoMenu}
      showMenu={showRepoMenu}
      userPubkey={userPubkey}
      isBookmarked={isBookmarked}
      loadingBookmark={loadingBookmark}
      onToggleBookmark={toggleBookmark}
      onFork={forkRepository}
      forking={forking}
      onCloneToServer={cloneRepository}
      cloning={cloning}
      checkingCloneStatus={checkingCloneStatus}
      onCreateIssue={() => showCreateIssueDialog = true}
      onCreatePR={() => showCreatePRDialog = true}
      onCreatePatch={() => showCreatePatchDialog = true}
      onCreateBranch={async () => {
        if (!userPubkey || !isMaintainer || needsClone) return;
        try {
          const settings = await settingsStore.getSettings();
          defaultBranchName = settings.defaultBranch || 'master';
        } catch {
          defaultBranchName = 'master';
        }
        showCreateBranchDialog = true;
      }}
      onSettings={() => goto(`/signup?npub=${npub}&repo=${repo}`)}
      onGenerateVerification={pageData.repoOwnerPubkey && userPubkeyHex === pageData.repoOwnerPubkey && verificationStatus?.verified !== true ? generateAnnouncementFileForRepo : undefined}
      onDeleteAnnouncement={pageData.repoOwnerPubkey && userPubkeyHex === pageData.repoOwnerPubkey ? deleteAnnouncement : undefined}
      deletingAnnouncement={deletingAnnouncement}
      hasUnlimitedAccess={hasUnlimitedAccess($userStore.userLevel)}
      needsClone={needsClone}
      allMaintainers={allMaintainers}
      onCopyEventId={copyEventId}
    />
  {/if}

  <!-- Additional repo metadata (website, clone URLs with verification) -->

  {#if pageData.repoWebsite || (pageData.repoCloneUrls && pageData.repoCloneUrls.length > 0) || pageData.repoLanguage || (pageData.repoTopics && pageData.repoTopics.length > 0) || forkInfo?.isFork}
    <div class="repo-metadata-section">
      {#if pageData.repoWebsite}
        <div class="repo-website">
          <a href={pageData.repoWebsite} target="_blank" rel="noopener noreferrer">
            <img src="/icons/external-link.svg" alt="" class="icon-inline" />
            {pageData.repoWebsite}
          </a>
        </div>
      {/if}
      {#if pageData.repoLanguage}
        <span class="repo-language">
          <img src="/icons/file-text.svg" alt="" class="icon-inline" />
          {pageData.repoLanguage}
        </span>
      {/if}
      {#if forkInfo?.isFork && forkInfo.originalRepo}
        <span class="fork-badge">Forked from <a href={`/repos/${forkInfo.originalRepo.npub}/${forkInfo.originalRepo.repo}`}>{forkInfo.originalRepo.repo}</a></span>
      {/if}
      {#if pageData.repoCloneUrls && pageData.repoCloneUrls.length > 0}
        <div class="repo-clone-urls">
          <button 
            class="clone-label-button"
            onclick={() => cloneUrlsExpanded = !cloneUrlsExpanded}
            aria-expanded={cloneUrlsExpanded}
          >
            <span class="clone-label">Clone URLs:</span>
            <svg class="clone-toggle-icon" class:expanded={cloneUrlsExpanded} width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
              <path d="M6 9l6 6 6-6"/>
            </svg>
          </button>
          <div class="clone-url-list" class:collapsed={!cloneUrlsExpanded}>
            {#each pageData.repoCloneUrls.slice(0, 3) as cloneUrl}
            {@const cloneVerification = verificationStatus?.cloneVerifications?.find(cv => {
              const normalizeUrl = (url: string) => url.replace(/\/$/, '').toLowerCase().replace(/^https?:\/\//, '');
              const normalizedCv = normalizeUrl(cv.url);
              const normalizedClone = normalizeUrl(cloneUrl);
              return normalizedCv === normalizedClone || 
                     normalizedCv.includes(normalizedClone) || 
                     normalizedClone.includes(normalizedCv);
            })}
            <div class="clone-url-wrapper">
              <code class="clone-url">{cloneUrl}</code>
              {#if loadingVerification}
                <span class="verification-badge loading" title="Checking verification...">
                  <span style="opacity: 0.5;">⋯</span>
                </span>
              {:else if cloneVerification !== undefined}
                <span 
                  class="verification-badge" 
                  class:verified={cloneVerification.verified} 
                  class:unverified={!cloneVerification.verified}
                  title={cloneVerification.verified ? 'Verified ownership' : (cloneVerification.error || 'Unverified')}
                >
                  {#if cloneVerification.verified}
                    <img src="/icons/check-circle.svg" alt="Verified" class="icon-inline" />
                  {:else}
                    <img src="/icons/alert-triangle.svg" alt="Unverified" class="icon-inline" />
                  {/if}
                </span>
              {:else if verificationStatus}
                <span class="verification-badge unverified" title="Verification status unknown">
                  <img src="/icons/alert-triangle.svg" alt="Unknown" class="icon-inline" />
                </span>
              {:else}
                <span class="verification-badge unverified" title="Verification not checked">
                  <img src="/icons/alert-triangle.svg" alt="Not checked" class="icon-inline" />
                </span>
              {/if}
            </div>
            {/each}
            {#if pageData.repoCloneUrls.length > 3}
              <span class="clone-more">+{pageData.repoCloneUrls.length - 3} more</span>
            {/if}
          </div>
        </div>
      {/if}
    </div>
  {/if}

  <main class="repo-view">
    {#if error}
      <div class="error">
        Error: {error}
      </div>
    {/if}

    <!-- Tabs -->

    <div class="repo-layout">
      <!-- File Tree Sidebar -->
      {#if activeTab === 'files'}
      <aside class="file-tree" class:hide-on-mobile={!showFileListOnMobile && activeTab === 'files'}>
        <div class="file-tree-header">
          <TabsMenu 
            activeTab={activeTab} 
            {tabs} 
            onTabChange={(tab) => activeTab = tab as typeof activeTab}
          />
          <h2>Files</h2>
          <button 
            onclick={toggleWordWrap} 
            class="word-wrap-button"
            title={wordWrap ? 'Disable word wrap' : 'Enable word wrap'}
            aria-label={wordWrap ? 'Disable word wrap' : 'Enable word wrap'}
          >
            {wordWrap ? 'Wrap' : 'No Wrap'}
          </button>
          <div class="file-tree-actions">
            {#if pathStack.length > 0 || currentPath}
              <button onclick={handleBack} class="back-button">← Back</button>
            {/if}
            {#if userPubkey && isMaintainer}
              <button 
                onclick={() => {
                  if (!userPubkey || !isMaintainer || needsClone) return;
                  showCreateFileDialog = true;
                }} 
                class="create-file-button"
                disabled={needsClone}
                title={needsClone ? cloneTooltip : 'Create a new file'}
              >
                <img src="/icons/plus.svg" alt="New File" class="icon" />
              </button>
            {/if}
            <button 
              onclick={() => showFileListOnMobile = !showFileListOnMobile} 
              class="mobile-toggle-button"
              title="Show file viewer"
            >
              <img src="/icons/arrow-right.svg" alt="Show file viewer" class="icon-inline" />
            </button>
          </div>
        </div>
        {#if loading && !currentFile}
          <div class="loading">Loading files...</div>
        {:else}
          <ul class="file-list">
            {#each files as file}
              <li class="file-item" class:directory={file.type === 'directory'} class:selected={currentFile === file.path}>
                <button onclick={() => handleFileClick(file)} class="file-button">
                  {#if file.type === 'directory'}
                    <img src="/icons/package.svg" alt="Directory" class="icon-inline" />
                  {:else}
                    <img src="/icons/file-text.svg" alt="File" class="icon-inline" />
                  {/if}
                  {file.name}
                  {#if file.size !== undefined}
                    <span class="file-size">({(file.size / 1024).toFixed(1)} KB)</span>
                  {/if}
                </button>
                {#if userPubkey && isMaintainer && file.type === 'file'}
                  <button 
                    onclick={() => {
                      if (needsClone) return;
                      deleteFile(file.path);
                    }} 
                    class="delete-file-button" 
                    disabled={needsClone}
                    title={needsClone ? cloneTooltip : 'Delete file'}
                  >
                    <img src="/icons/x.svg" alt="Delete" class="icon-small" />
                  </button>
                {/if}
              </li>
            {/each}
          </ul>
        {/if}
      </aside>
      {/if}

      <!-- Commit History View -->
      {#if activeTab === 'history'}
      <aside class="history-sidebar" class:hide-on-mobile={!showLeftPanelOnMobile && activeTab === 'history'}>
        <div class="history-header">
          <TabsMenu 
            activeTab={activeTab} 
            {tabs} 
            onTabChange={(tab) => activeTab = tab as typeof activeTab}
          />
          <h2>Commits</h2>
          <button 
            onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
            class="mobile-toggle-button"
            title="Show content"
          >
            <img src="/icons/arrow-right.svg" alt="Show content" class="icon-inline" />
          </button>
        </div>
        {#if loadingCommits}
          <div class="loading">Loading commits...</div>
        {:else if commits.length > 0}
          <ul class="commit-list">
            {#each commits as commit}
              {@const commitHash = commit.hash || (commit as any).sha || ''}
              {#if commitHash}
                <li class="commit-item" class:selected={selectedCommit === commitHash}>
                  <button onclick={() => viewDiff(commitHash)} class="commit-button">
                    <div class="commit-hash">{commitHash.slice(0, 7)}</div>
                    <div class="commit-message">{commit.message || 'No message'}</div>
                    <div class="commit-meta">
                      <span>{commit.author || 'Unknown'}</span>
                      <span>{commit.date ? new Date(commit.date).toLocaleString() : 'Unknown date'}</span>
                    </div>
                  </button>
                </li>
              {/if}
            {/each}
          </ul>
        {/if}
      </aside>
      {/if}

      <!-- Tags View -->
      {#if activeTab === 'tags'}
      <aside class="tags-sidebar" class:hide-on-mobile={!showLeftPanelOnMobile && activeTab === 'tags'}>
        <div class="tags-header">
          <TabsMenu 
            activeTab={activeTab} 
            {tabs} 
            onTabChange={(tab) => activeTab = tab as typeof activeTab}
          />
          <h2>Tags</h2>
          {#if userPubkey && isMaintainer}
            <button 
              onclick={() => {
                if (!userPubkey || !isMaintainer || needsClone) return;
                showCreateTagDialog = true;
              }} 
              class="create-tag-button"
              disabled={needsClone}
              title={needsClone ? cloneTooltip : 'Create a new tag'}
            >
              <img src="/icons/plus.svg" alt="New Tag" class="icon" />
            </button>
          {/if}
          <button 
            onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
            class="mobile-toggle-button"
            title="Show content"
          >
            <img src="/icons/arrow-right.svg" alt="Show content" class="icon-inline" />
          </button>
        </div>
        {#if tags.length > 0}
          <ul class="tag-list">
            {#each tags as tag}
              {@const tagHash = tag.hash || ''}
              {#if tagHash}
                <li class="tag-item">
                  <div class="tag-name">{tag.name}</div>
                  <div class="tag-hash">{tagHash.slice(0, 7)}</div>
                  {#if tag.message}
                    <div class="tag-message">{tag.message}</div>
                  {/if}
                </li>
              {/if}
            {/each}
          </ul>
        {/if}
      </aside>
      {/if}

      <!-- Issues View -->
      {#if activeTab === 'issues'}
      <aside class="issues-sidebar" class:hide-on-mobile={!showLeftPanelOnMobile && activeTab === 'issues'}>
        <div class="issues-header">
          <TabsMenu 
            activeTab={activeTab} 
            {tabs} 
            onTabChange={(tab) => activeTab = tab as typeof activeTab}
          />
          <h2>Issues</h2>
          {#if userPubkey}
            <button onclick={() => {
              if (!userPubkey) return;
              showCreateIssueDialog = true;
            }} class="create-issue-button" title="Create a new issue">
              <img src="/icons/plus.svg" alt="New Issue" class="icon" />
            </button>
          {/if}
          <button 
            onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
            class="mobile-toggle-button"
            title="Show content"
          >
            <img src="/icons/arrow-right.svg" alt="Show content" class="icon-inline" />
          </button>
        </div>
        {#if loadingIssues}
          <div class="loading">Loading issues...</div>
        {:else if issues.length > 0}
          <ul class="issue-list">
            {#each issues as issue}
              <li class="issue-item">
                <div class="issue-header">
                  <span class="issue-status" class:open={issue.status === 'open'} class:closed={issue.status === 'closed'} class:resolved={issue.status === 'resolved'}>
                    {issue.status}
                  </span>
                  <span class="issue-subject">{issue.subject}</span>
                </div>
                <div class="issue-meta">
                  <span>#{issue.id.slice(0, 7)}</span>
                  <span>{new Date(issue.created_at * 1000).toLocaleDateString()}</span>
                  <EventCopyButton eventId={issue.id} kind={issue.kind} pubkey={issue.author} />
                </div>
                {#if userPubkeyHex && (isMaintainer || userPubkeyHex === issue.author)}
                  <div class="issue-actions">
                    {#if issue.status === 'open'}
                      <button onclick={() => updateIssueStatus(issue.id, issue.author, 'closed')} disabled={updatingIssueStatus[issue.id]} class="issue-action-btn close-btn">
                        {updatingIssueStatus[issue.id] ? 'Closing...' : 'Close'}
                      </button>
                      <button onclick={() => updateIssueStatus(issue.id, issue.author, 'resolved')} disabled={updatingIssueStatus[issue.id]} class="issue-action-btn resolve-btn">
                        {updatingIssueStatus[issue.id] ? 'Resolving...' : 'Resolve'}
                      </button>
                    {:else if issue.status === 'closed' || issue.status === 'resolved'}
                      <button onclick={() => updateIssueStatus(issue.id, issue.author, 'open')} disabled={updatingIssueStatus[issue.id]} class="issue-action-btn reopen-btn">
                        {updatingIssueStatus[issue.id] ? 'Reopening...' : 'Reopen'}
                      </button>
                    {/if}
                  </div>
                {/if}
              </li>
            {/each}
          </ul>
        {/if}
      </aside>
      {/if}

      <!-- Pull Requests View -->
      {#if activeTab === 'prs'}
      <aside class="prs-sidebar" class:hide-on-mobile={!showLeftPanelOnMobile && activeTab === 'prs'}>
        <div class="prs-header">
          <TabsMenu 
            activeTab={activeTab} 
            {tabs} 
            onTabChange={(tab) => activeTab = tab as typeof activeTab}
          />
          <h2>Pull Requests</h2>
          {#if userPubkey}
            <button onclick={() => {
              if (!userPubkey) return;
              showCreatePRDialog = true;
            }} class="create-pr-button" title="Create a new pull request">
              <img src="/icons/plus.svg" alt="New PR" class="icon" />
            </button>
          {/if}
          <button 
            onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
            class="mobile-toggle-button"
            title="Show content"
          >
            <img src="/icons/arrow-right.svg" alt="Show content" class="icon-inline" />
          </button>
        </div>
        {#if loadingPRs}
          <div class="loading">Loading pull requests...</div>
        {:else if prs.length > 0}
          <ul class="pr-list">
            {#each prs as pr}
              <li class="pr-item">
                <div class="pr-header">
                  <span class="pr-status" class:open={pr.status === 'open'} class:closed={pr.status === 'closed'} class:merged={pr.status === 'merged'}>
                    {pr.status}
                  </span>
                  <span class="pr-subject">{pr.subject}</span>
                </div>
                <div class="pr-meta">
                  <span>#{pr.id.slice(0, 7)}</span>
                  {#if pr.commitId}
                    <span class="pr-commit">Commit: {pr.commitId.slice(0, 7)}</span>
                  {/if}
                  <span>{new Date(pr.created_at * 1000).toLocaleDateString()}</span>
                  <EventCopyButton eventId={pr.id} kind={pr.kind} pubkey={pr.author} />
                </div>
              </li>
            {/each}
          </ul>
        {/if}
      </aside>
      {/if}

      <!-- Patches View -->
      {#if activeTab === 'patches'}
      <aside class="patches-sidebar" class:hide-on-mobile={!showLeftPanelOnMobile && activeTab === 'patches'}>
        <div class="patches-header">
          <TabsMenu 
            activeTab={activeTab} 
            {tabs} 
            onTabChange={(tab) => activeTab = tab as typeof activeTab}
          />
          <h2>Patches</h2>
          {#if userPubkey}
            <button 
              onclick={() => showCreatePatchDialog = true}
              class="create-patch-button"
              title="Create a new patch"
            >
              <img src="/icons/plus.svg" alt="New Patch" class="icon" />
            </button>
          {/if}
          <button 
            onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
            class="mobile-toggle-button"
            title="Show content"
          >
            <img src="/icons/arrow-right.svg" alt="Show content" class="icon-inline" />
          </button>
        </div>
        {#if loadingPatches}
          <div class="loading">Loading patches...</div>
        {:else if patches.length > 0}
          <ul class="patch-list">
            {#each patches as patch}
              <li class="patch-item" class:selected={selectedPatch === patch.id}>
                <button 
                  onclick={() => selectedPatch = patch.id}
                  class="patch-item-button"
                >
                  <div class="patch-header">
                    <span class="patch-subject">{patch.subject}</span>
                  </div>
                  <div class="patch-meta">
                    <span>#{patch.id.slice(0, 7)}</span>
                    <span>{new Date(patch.created_at * 1000).toLocaleDateString()}</span>
                    <EventCopyButton eventId={patch.id} kind={patch.kind} pubkey={patch.author} />
                  </div>
                </button>
              </li>
            {/each}
          </ul>
        {/if}
      </aside>
      {/if}

      <!-- Discussions View -->
      {#if activeTab === 'discussions'}
      <aside class="discussions-sidebar" class:hide-on-mobile={!showLeftPanelOnMobile && activeTab === 'discussions'}>
        <div class="discussions-header">
          <TabsMenu 
            activeTab={activeTab} 
            {tabs} 
            onTabChange={(tab) => activeTab = tab as typeof activeTab}
          />
          <h2>Discussions</h2>
          {#if userPubkey}
            <button 
              onclick={() => showCreateThreadDialog = true}
              class="create-discussion-button"
              disabled={creatingThread}
              title={creatingThread ? 'Creating...' : 'New Discussion Thread'}
            >
              <img src="/icons/plus.svg" alt="New Discussion" class="icon" />
            </button>
          {/if}
          <button 
            onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
            class="mobile-toggle-button"
            title="Show content"
          >
            <img src="/icons/arrow-right.svg" alt="Show content" class="icon-inline" />
          </button>
        </div>
        {#if loadingDiscussions}
          <div class="loading">Loading discussions...</div>
        {:else if discussions.length > 0}
          <ul class="discussion-list">
            {#each discussions as discussion}
              {@const hasComments = discussion.comments && discussion.comments.length > 0}
              {@const totalReplies = hasComments ? countAllReplies(discussion.comments) : 0}
              <li class="discussion-item" class:selected={selectedDiscussion === discussion.id}>
                <button 
                  onclick={() => selectedDiscussion = discussion.id}
                  class="discussion-item-button"
                >
                  <div class="discussion-header">
                    <span class="discussion-title">{discussion.title}</span>
                  </div>
                  <div class="discussion-meta">
                    {#if discussion.type === 'thread'}
                      <span class="discussion-type">Thread</span>
                      {#if hasComments}
                        <span class="comment-count">{totalReplies} {totalReplies === 1 ? 'reply' : 'replies'}</span>
                      {/if}
                    {:else}
                      <span class="discussion-type">Comments</span>
                    {/if}
                    <span>{new Date(discussion.createdAt * 1000).toLocaleDateString()}</span>
                    <EventCopyButton eventId={discussion.id} kind={discussion.kind} pubkey={discussion.pubkey} />
                  </div>
                </button>
              </li>
            {/each}
          </ul>
        {/if}
      </aside>
      {/if}

      <!-- Docs View -->
      {#if activeTab === 'docs'}
      <aside class="docs-sidebar" class:hide-on-mobile={!showLeftPanelOnMobile && activeTab === 'docs'}>
        <div class="docs-header">
          <TabsMenu 
            activeTab={activeTab} 
            {tabs} 
            onTabChange={(tab) => activeTab = tab as typeof activeTab}
          />
          <h2>Docs</h2>
          <button 
            onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
            class="mobile-toggle-button"
            title="Show content"
          >
            <img src="/icons/arrow-right.svg" alt="Show content" class="icon-inline" />
          </button>
        </div>
      </aside>
      {/if}

      <!-- Editor Area / Diff View / README -->
      <div class="editor-area" class:hide-on-mobile={(showFileListOnMobile && activeTab === 'files') || (showLeftPanelOnMobile && activeTab !== 'files')}>
        {#if activeTab === 'files' && readmeContent && !currentFile}
          <div class="readme-section">
            <div class="readme-header">
              <h3>README</h3>
              <div class="readme-actions">
                <a href={`/api/repos/${npub}/${repo}/raw?path=${readmePath}`} target="_blank" class="raw-link">View Raw</a>
                <a href={`/api/repos/${npub}/${repo}/download?format=zip`} class="download-link">Download ZIP</a>
                <button 
                  onclick={() => showFileListOnMobile = !showFileListOnMobile} 
                  class="mobile-toggle-button"
                  title="Show file list"
                >
                  <img src="/icons/arrow-right.svg" alt="Show file list" class="icon-inline mobile-toggle-left" />
                </button>
              </div>
            </div>
            {#if loadingReadme}
              <div class="loading">Loading README...</div>
            {:else if readmeIsMarkdown && readmeHtml && readmeHtml.trim()}
              <div class="readme-content markdown">
                {@html readmeHtml}
              </div>
            {:else if readmeContent}
              <div class="readme-content">
                <pre><code class="hljs language-text">{readmeContent}</code></pre>
              </div>
            {/if}
          </div>
        {/if}

        {#if activeTab === 'files' && currentFile}
          <div class="editor-header">
            <span class="file-path">{currentFile}</span>
            <div class="editor-actions">
              {#if hasChanges}
                <span class="unsaved-indicator">● Unsaved changes</span>
              {/if}
              {#if isMaintainer}
                <button 
                  onclick={() => {
                    if (!userPubkey || !isMaintainer || needsClone) return;
                    showCommitDialog = true;
                  }} 
                  disabled={!hasChanges || saving || needsClone} 
                  class="save-button"
                  title={needsClone ? cloneTooltip : (hasChanges ? 'Save changes' : 'No changes to save')}
                >
                  {saving ? 'Saving...' : 'Save'}
                </button>
              {:else if userPubkey}
                <span class="non-maintainer-notice">Only maintainers can edit files. Submit a PR instead.</span>
              {/if}
              <button 
                onclick={() => showFileListOnMobile = !showFileListOnMobile} 
                class="mobile-toggle-button"
                title="Show file list"
              >
                <img src="/icons/arrow-right.svg" alt="Show file list" class="icon-inline mobile-toggle-left" />
              </button>
            </div>
          </div>
          
          {#if loading}
            <div class="loading">Loading file...</div>
          {:else}
            <div class="editor-container">
              {#if isMaintainer}
                <CodeEditor 
                  content={editedContent} 
                  language={fileLanguage}
                  onChange={handleContentChange}
                  readOnly={needsClone}
                />
              {:else}
                <div class="read-only-editor" class:word-wrap={wordWrap}>
                  {#if highlightedFileContent}
                    {@html highlightedFileContent}
                  {:else}
                    <pre><code class="hljs">{fileContent}</code></pre>
                  {/if}
                </div>
              {/if}
            </div>
          {/if}
        {:else if activeTab === 'files'}
          <div class="content-header-mobile">
            <button 
              onclick={() => showFileListOnMobile = !showFileListOnMobile} 
              class="mobile-toggle-button"
              title="Show file list"
            >
              <img src="/icons/arrow-right.svg" alt="Show file list" class="icon-inline mobile-toggle-left" />
            </button>
          </div>
          <div class="empty-state">
            <p>Select a file from the sidebar to view and edit it</p>
          </div>
        {/if}

        {#if activeTab === 'history' && showDiff}
          <div class="content-header-mobile">
            <button 
              onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
              class="mobile-toggle-button"
              title="Show list"
            >
              <img src="/icons/arrow-right.svg" alt="Show list" class="icon-inline mobile-toggle-left" />
            </button>
          </div>
          <div class="diff-view">
            <div class="diff-header">
              <h3>Diff for commit {selectedCommit?.slice(0, 7)}</h3>
              <button onclick={() => { showDiff = false; selectedCommit = null; }} class="close-button">×</button>
            </div>
            {#each diffData as diff}
              <div class="diff-file">
                <div class="diff-file-header">
                  <span class="diff-file-name">{diff.file}</span>
                  <span class="diff-stats">
                    <span class="additions">+{diff.additions}</span>
                    <span class="deletions">-{diff.deletions}</span>
                  </span>
                </div>
                <pre class="diff-content"><code>{diff.diff}</code></pre>
              </div>
            {/each}
          </div>
        {:else if activeTab === 'history'}
          <div class="content-header-mobile">
            <button 
              onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
              class="mobile-toggle-button"
              title="Show list"
            >
              <img src="/icons/arrow-right.svg" alt="Show list" class="icon-inline mobile-toggle-left" />
            </button>
          </div>
          <div class="empty-state">
            <p>Select a commit to view its diff</p>
          </div>
        {/if}

        {#if activeTab === 'tags'}
          <div class="content-header-mobile">
            <button 
              onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
              class="mobile-toggle-button"
              title="Show list"
            >
              <img src="/icons/arrow-right.svg" alt="Show list" class="icon-inline mobile-toggle-left" />
            </button>
          </div>
          <div class="empty-state">
            <p>Tags are displayed in the sidebar</p>
          </div>
        {/if}

        {#if activeTab === 'issues'}
          <div class="issues-content" class:hide-on-mobile={showLeftPanelOnMobile && activeTab === 'issues'}>
            <div class="content-header-mobile">
              <button 
                onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
                class="mobile-toggle-button"
                title="Show list"
              >
                <img src="/icons/arrow-right.svg" alt="Show list" class="icon-inline mobile-toggle-left" />
              </button>
            </div>
            {#if issues.length === 0}
              <div class="empty-state">
                <p>No issues found. Create one to get started!</p>
              </div>
            {:else}
              {#each issues as issue}
                <div class="issue-detail">
                  <h3>{issue.subject}</h3>
                  <div class="issue-meta-detail">
                    <span class="issue-status" class:open={issue.status === 'open'} class:closed={issue.status === 'closed'} class:resolved={issue.status === 'resolved'}>
                      {issue.status}
                    </span>
                    <span>Created {new Date(issue.created_at * 1000).toLocaleString()}</span>
                  </div>
                  <div class="issue-body">
                    {@html issue.content.replace(/\n/g, '<br>')}
                  </div>
                </div>
              {/each}
            {/if}
          </div>
        {/if}

        {#if activeTab === 'prs'}
          <div class="prs-content" class:hide-on-mobile={showLeftPanelOnMobile && activeTab === 'prs'}>
            <div class="content-header-mobile">
              <button 
                onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
                class="mobile-toggle-button"
                title="Show list"
              >
                <img src="/icons/arrow-right.svg" alt="Show list" class="icon-inline mobile-toggle-left" />
              </button>
            </div>
            {#if prs.length === 0}
              <div class="empty-state">
                <p>No pull requests found. Create one to get started!</p>
              </div>
            {:else if selectedPR}
              {#each prs.filter(p => p.id === selectedPR) as pr}
                {@const decoded = nip19.decode(npub)}
                {#if decoded.type === 'npub'}
                  {@const repoOwnerPubkey = decoded.data as string}
                  <PRDetail
                    {pr}
                    {npub}
                    {repo}
                    {repoOwnerPubkey}
                    isMaintainer={isMaintainer}
                    userPubkeyHex={userPubkeyHex ?? undefined}
                    onStatusUpdate={loadPRs}
                  />
                  <button onclick={() => selectedPR = null} class="back-btn">← Back to PR List</button>
                {/if}
              {/each}
            {:else}
              {#each prs as pr}
                <div 
                  class="pr-detail" 
                  role="button"
                  tabindex="0"
                  onclick={() => selectedPR = pr.id}
                  onkeydown={(e) => {
                    if (e.key === 'Enter' || e.key === ' ') {
                      e.preventDefault();
                      selectedPR = pr.id;
                    }
                  }}
                  style="cursor: pointer;">
                  <h3>{pr.subject}</h3>
                  <div class="pr-meta-detail">
                    <span class="pr-status" class:open={pr.status === 'open'} class:closed={pr.status === 'closed'} class:merged={pr.status === 'merged'}>
                      {pr.status}
                    </span>
                    {#if pr.commitId}
                      <span>Commit: {pr.commitId.slice(0, 7)}</span>
                    {/if}
                    <span>Created {new Date(pr.created_at * 1000).toLocaleString()}</span>
                  </div>
                  <div class="pr-body">
                    {@html pr.content.replace(/\n/g, '<br>')}
                  </div>
                </div>
              {/each}
            {/if}
          </div>
        {/if}

        {#if activeTab === 'patches'}
          <div class="patches-content" class:hide-on-mobile={showLeftPanelOnMobile && activeTab === 'patches'}>
            <div class="content-header-mobile">
              <button 
                onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
                class="mobile-toggle-button"
                title="Show list"
              >
                <img src="/icons/arrow-right.svg" alt="Show list" class="icon-inline mobile-toggle-left" />
              </button>
            </div>
            {#if patches.length === 0}
              <div class="empty-state">
                <p>No patches found. Create one to get started!</p>
              </div>
            {:else if selectedPatch}
              {#each patches.filter(p => p.id === selectedPatch) as patch}
                <div class="patch-detail">
                  <h3>{patch.subject}</h3>
                  <div class="patch-meta-detail">
                    <span>#{patch.id.slice(0, 7)}</span>
                    <span>Created {new Date(patch.created_at * 1000).toLocaleString()}</span>
                    <EventCopyButton eventId={patch.id} kind={patch.kind} pubkey={patch.author} />
                  </div>
                  <div class="patch-body">
                    <pre class="patch-content">{patch.content}</pre>
                  </div>
                </div>
              {/each}
            {:else}
              <div class="empty-state">
                <p>Select a patch from the sidebar to view it</p>
              </div>
            {/if}
          </div>
        {/if}

        {#if activeTab === 'discussions'}
          <div class="discussions-content" class:hide-on-mobile={showLeftPanelOnMobile && activeTab === 'discussions'}>
            <div class="content-header-mobile">
              <button 
                onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
                class="mobile-toggle-button"
                title="Show list"
              >
                <img src="/icons/arrow-right.svg" alt="Show list" class="icon-inline mobile-toggle-left" />
              </button>
            </div>
            {#if discussions.length === 0}
              <div class="empty-state">
                <p>No discussions found. {#if userPubkey}Create a new discussion thread to get started!{:else}Log in to create a discussion thread.{/if}</p>
              </div>
            {:else if selectedDiscussion}
              {#each discussions.filter(d => d.id === selectedDiscussion) as discussion}
                {@const isExpanded = discussion.type === 'thread' && expandedThreads.has(discussion.id)}
                {@const hasComments = discussion.comments && discussion.comments.length > 0}
                <div class="discussion-detail">
                  <div class="discussion-header-detail">
                    <h3>{discussion.title}</h3>
                    <div class="discussion-meta-detail">
                      {#if discussion.type === 'thread'}
                        <span class="discussion-type">Thread</span>
                        {#if hasComments}
                          {@const totalReplies = countAllReplies(discussion.comments)}
                          <span class="comment-count">{totalReplies} {totalReplies === 1 ? 'reply' : 'replies'}</span>
                        {/if}
                      {:else}
                        <span class="discussion-type">Comments</span>
                      {/if}
                      <span>Created {new Date(discussion.createdAt * 1000).toLocaleString()}</span>
                      <EventCopyButton eventId={discussion.id} kind={discussion.kind} pubkey={discussion.pubkey} />
                      {#if discussion.type === 'thread' && userPubkey}
                        <button 
                          class="create-reply-button"
                          onclick={() => {
                            replyingToThread = { id: discussion.id, kind: discussion.kind, pubkey: discussion.pubkey, author: discussion.author };
                            replyingToComment = null;
                            showReplyDialog = true;
                          }}
                          title="Reply to thread"
                        >
                          <img src="/icons/plus.svg" alt="Reply" class="icon" />
                        </button>
                      {/if}
                    </div>
                  </div>
                  {#if discussion.content}
                    <div class="discussion-body">
                      <p>{discussion.content}</p>
                    </div>
                  {/if}
                  {#if discussion.type === 'thread' && hasComments}
                    {@const totalReplies = countAllReplies(discussion.comments)}
                    <div class="comments-section">
                      <h4>Replies ({totalReplies})</h4>
                      {#each discussion.comments! as comment}
                        <div class="comment-item">
                          <div class="comment-meta">
                            <UserBadge pubkey={comment.author} />
                            <span>{new Date(comment.createdAt * 1000).toLocaleString()}</span>
                            <EventCopyButton eventId={comment.id} kind={comment.kind} pubkey={comment.pubkey} />
                            {#if userPubkey}
                              <button 
                                class="create-reply-button"
                                onclick={() => {
                                  replyingToThread = { id: discussion.id, kind: discussion.kind, pubkey: discussion.pubkey, author: discussion.author };
                                  replyingToComment = { id: comment.id, kind: comment.kind, pubkey: comment.pubkey, author: comment.author };
                                  showReplyDialog = true;
                                }}
                                title="Reply to comment"
                              >
                                <img src="/icons/plus.svg" alt="Reply" class="icon" />
                              </button>
                            {/if}
                          </div>
                          {#if true}
                            {@const commentEvent = getDiscussionEvent(comment.id)}
                            {@const referencedEvent = commentEvent ? getReferencedEventFromDiscussion(commentEvent) : undefined}
                            {@const parts = processContentWithNostrLinks(comment.content)}
                            <div class="comment-content">
                              {#if referencedEvent}
                                <div class="referenced-event">
                                  <div class="referenced-event-header">
                                    <UserBadge pubkey={referencedEvent.pubkey} disableLink={true} />
                                    <span class="referenced-event-time">{formatDiscussionTime(referencedEvent.created_at)}</span>
                                  </div>
                                  <div class="referenced-event-content">{referencedEvent.content || '(No content)'}</div>
                                </div>
                              {/if}
                              <div>
                                {#each parts as part}
                                  {#if part.type === 'text'}
                                    <span>{part.value}</span>
                                  {:else if part.type === 'event' && part.event}
                                    <div class="nostr-link-event">
                                      <div class="nostr-link-event-header">
                                        <UserBadge pubkey={part.event.pubkey} disableLink={true} />
                                        <span class="nostr-link-event-time">{formatDiscussionTime(part.event.created_at)}</span>
                                      </div>
                                      <div class="nostr-link-event-content">{part.event.content || '(No content)'}</div>
                                    </div>
                                  {:else if part.type === 'profile' && part.pubkey}
                                    <UserBadge pubkey={part.pubkey} />
                                  {:else}
                                    <span class="nostr-link-placeholder">{part.value}</span>
                                  {/if}
                                {/each}
                              </div>
                            </div>
                          {/if}
                          {#if comment.replies && comment.replies.length > 0}
                            <div class="nested-replies">
                              {#each comment.replies as reply}
                                <div class="comment-item nested-comment">
                                  <div class="comment-meta">
                                    <UserBadge pubkey={reply.author} />
                                    <span>{new Date(reply.createdAt * 1000).toLocaleString()}</span>
                                    <EventCopyButton eventId={reply.id} kind={reply.kind} pubkey={reply.pubkey} />
                                    {#if userPubkey}
                                      <button 
                                        class="create-reply-button"
                                        onclick={() => {
                                          replyingToThread = { id: discussion.id, kind: discussion.kind, pubkey: discussion.pubkey, author: discussion.author };
                                          replyingToComment = { id: reply.id, kind: reply.kind, pubkey: reply.pubkey, author: reply.author };
                                          showReplyDialog = true;
                                        }}
                                        title="Reply to comment"
                                      >
                                        <img src="/icons/plus.svg" alt="Reply" class="icon" />
                                      </button>
                                    {/if}
                                  </div>
                                  <div class="comment-content">
                                    <p>{reply.content}</p>
                                  </div>
                                  {#if reply.replies && reply.replies.length > 0}
                                    <div class="nested-replies">
                                      {#each reply.replies as nestedReply}
                                        <div class="comment-item nested-comment">
                                          <div class="comment-meta">
                                            <UserBadge pubkey={nestedReply.author} />
                                            <span>{new Date(nestedReply.createdAt * 1000).toLocaleString()}</span>
                                            <EventCopyButton eventId={nestedReply.id} kind={nestedReply.kind} pubkey={nestedReply.pubkey} />
                                            {#if userPubkey}
                                              <button 
                                                class="create-reply-button"
                                                onclick={() => {
                                                  replyingToThread = { id: discussion.id, kind: discussion.kind, pubkey: discussion.pubkey, author: discussion.author };
                                                  replyingToComment = { id: nestedReply.id, kind: nestedReply.kind, pubkey: nestedReply.pubkey, author: nestedReply.author };
                                                  showReplyDialog = true;
                                                }}
                                                title="Reply to comment"
                                              >
                                                <img src="/icons/plus.svg" alt="Reply" class="icon" />
                                              </button>
                                            {/if}
                                          </div>
                                          <div class="comment-content">
                                            <p>{nestedReply.content}</p>
                                          </div>
                                        </div>
                                      {/each}
                                    </div>
                                  {/if}
                                </div>
                              {/each}
                            </div>
                          {/if}
                        </div>
                      {/each}
                    </div>
                  {:else if discussion.type === 'comments' && hasComments}
                    {@const totalReplies = countAllReplies(discussion.comments)}
                    <div class="comments-section">
                      <h4>Comments ({totalReplies})</h4>
                      {#each discussion.comments! as comment}
                        <div class="comment-item">
                          <div class="comment-meta">
                            <UserBadge pubkey={comment.author} />
                            <span>{new Date(comment.createdAt * 1000).toLocaleString()}</span>
                            <EventCopyButton eventId={comment.id} kind={comment.kind} pubkey={comment.pubkey} />
                            {#if userPubkey}
                              <button 
                                class="create-reply-button"
                                onclick={() => {
                                  replyingToThread = { id: discussion.id, kind: discussion.kind, pubkey: discussion.pubkey, author: discussion.author };
                                  replyingToComment = { id: comment.id, kind: comment.kind, pubkey: comment.pubkey, author: comment.author };
                                  showReplyDialog = true;
                                }}
                                title="Reply to comment"
                              >
                                <img src="/icons/plus.svg" alt="Reply" class="icon" />
                              </button>
                            {/if}
                          </div>
                          {#if true}
                            {@const commentEvent = getDiscussionEvent(comment.id)}
                            {@const referencedEvent = commentEvent ? getReferencedEventFromDiscussion(commentEvent) : undefined}
                            {@const parts = processContentWithNostrLinks(comment.content)}
                            <div class="comment-content">
                              {#if referencedEvent}
                                <div class="referenced-event">
                                  <div class="referenced-event-header">
                                    <UserBadge pubkey={referencedEvent.pubkey} disableLink={true} />
                                    <span class="referenced-event-time">{formatDiscussionTime(referencedEvent.created_at)}</span>
                                  </div>
                                  <div class="referenced-event-content">{referencedEvent.content || '(No content)'}</div>
                                </div>
                              {/if}
                              <div>
                                {#each parts as part}
                                  {#if part.type === 'text'}
                                    <span>{part.value}</span>
                                  {:else if part.type === 'event' && part.event}
                                    <div class="nostr-link-event">
                                      <div class="nostr-link-event-header">
                                        <UserBadge pubkey={part.event.pubkey} disableLink={true} />
                                        <span class="nostr-link-event-time">{formatDiscussionTime(part.event.created_at)}</span>
                                      </div>
                                      <div class="nostr-link-event-content">{part.event.content || '(No content)'}</div>
                                    </div>
                                  {:else if part.type === 'profile' && part.pubkey}
                                    <UserBadge pubkey={part.pubkey} />
                                  {:else}
                                    <span class="nostr-link-placeholder">{part.value}</span>
                                  {/if}
                                {/each}
                              </div>
                            </div>
                          {/if}
                        </div>
                      {/each}
                    </div>
                  {/if}
                </div>
              {/each}
            {:else}
              <div class="empty-state">
                <p>Select a discussion from the sidebar to view it</p>
              </div>
            {/if}
          </div>
        {/if}

        {#if activeTab === 'docs'}
          <div class="docs-content" class:hide-on-mobile={showLeftPanelOnMobile && activeTab === 'docs'}>
            <div class="content-header-mobile">
              <button 
                onclick={() => showLeftPanelOnMobile = !showLeftPanelOnMobile} 
                class="mobile-toggle-button"
                title="Show list"
              >
                <img src="/icons/arrow-right.svg" alt="Show list" class="icon-inline mobile-toggle-left" />
              </button>
            </div>
            {#if loadingDocs}
              <div class="loading">Loading documentation...</div>
            {:else if documentationHtml}
              <div class="documentation-body">
                {@html documentationHtml}
              </div>
            {:else if documentationContent === null}
              <div class="empty-state">
                <p>No documentation found for this repository.</p>
              </div>
            {:else}
              <div class="empty-state">
                <p>Documentation content is empty.</p>
              </div>
            {/if}
          </div>
        {/if}
      </div>
    </div>
  </main>

  <!-- Create File Dialog -->
  {#if showCreateFileDialog && userPubkey && isMaintainer}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Create new file"
      onclick={() => showCreateFileDialog = false}
      onkeydown={(e) => e.key === 'Escape' && (showCreateFileDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <h3>Create New File</h3>
        <label>
          File Name:
          <input type="text" bind:value={newFileName} placeholder="filename.md" />
        </label>
        <label>
          Content:
          <textarea bind:value={newFileContent} rows="10" placeholder="File content..."></textarea>
        </label>
        <div class="modal-actions">
          <button onclick={() => showCreateFileDialog = false} class="cancel-button">Cancel</button>
          <button 
            onclick={createFile} 
            disabled={!newFileName.trim() || saving || needsClone} 
            class="save-button"
            title={needsClone ? cloneTooltip : ''}
          >
            {saving ? 'Creating...' : 'Create'}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Create Branch Dialog -->
  {#if showCreateBranchDialog && userPubkey && isMaintainer}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Create new branch"
      onclick={() => showCreateBranchDialog = false}
      onkeydown={(e) => e.key === 'Escape' && (showCreateBranchDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <h3>Create New Branch</h3>
        <label>
          Branch Name:
          <input type="text" bind:value={newBranchName} placeholder="feature/new-feature" />
        </label>
        <label>
          From Branch:
          <select bind:value={newBranchFrom}>
            {#if branches.length === 0}
              <option value={null}>No branches - will create initial branch ({defaultBranchName})</option>
            {:else}
              {#each branches as branch}
                {@const branchName = typeof branch === 'string' ? branch : (branch as { name: string }).name}
                <option value={branchName}>{branchName}</option>
              {/each}
            {/if}
          </select>
        </label>
        <div class="modal-actions">
          <button onclick={() => showCreateBranchDialog = false} class="cancel-button">Cancel</button>
          <button 
            onclick={createBranch} 
            disabled={!newBranchName.trim() || saving || needsClone} 
            class="save-button"
            title={needsClone ? cloneTooltip : ''}
          >
            {saving ? 'Creating...' : 'Create Branch'}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Create Tag Dialog -->
  {#if showCreateTagDialog && userPubkey && isMaintainer}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Create new tag"
      onclick={() => showCreateTagDialog = false}
      onkeydown={(e) => e.key === 'Escape' && (showCreateTagDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <h3>Create New Tag</h3>
        <label>
          Tag Name:
          <input type="text" bind:value={newTagName} placeholder="v1.0.0" />
        </label>
        <label>
          Reference (commit/branch):
          <input type="text" bind:value={newTagRef} placeholder="HEAD" />
        </label>
        <label>
          Message (optional, for annotated tag):
          <textarea bind:value={newTagMessage} rows="3" placeholder="Tag message..."></textarea>
        </label>
        <div class="modal-actions">
          <button onclick={() => showCreateTagDialog = false} class="cancel-button">Cancel</button>
          <button 
            onclick={createTag} 
            disabled={!newTagName.trim() || saving || needsClone} 
            class="save-button"
            title={needsClone ? cloneTooltip : ''}
          >
            {saving ? 'Creating...' : 'Create Tag'}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Create Issue Dialog -->
  {#if showCreateIssueDialog && userPubkey}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Create new issue"
      onclick={() => showCreateIssueDialog = false}
      onkeydown={(e) => e.key === 'Escape' && (showCreateIssueDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <h3>Create New Issue</h3>
        <label>
          Subject:
          <input type="text" bind:value={newIssueSubject} placeholder="Issue title..." />
        </label>
        <label>
          Description:
          <textarea bind:value={newIssueContent} rows="10" placeholder="Describe the issue..."></textarea>
        </label>
        <div class="modal-actions">
          <button onclick={() => showCreateIssueDialog = false} class="cancel-button">Cancel</button>
          <button onclick={createIssue} disabled={!newIssueSubject.trim() || !newIssueContent.trim() || saving} class="save-button">
            {saving ? 'Creating...' : 'Create Issue'}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Create Discussion Thread Dialog -->
  {#if showCreateThreadDialog && userPubkey}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Create new discussion thread"
      onclick={() => showCreateThreadDialog = false}
      onkeydown={(e) => e.key === 'Escape' && (showCreateThreadDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <h3>Create New Discussion Thread</h3>
        <label>
          Title:
          <input type="text" bind:value={newThreadTitle} placeholder="Thread title..." />
        </label>
        <label>
          Content:
          <textarea bind:value={newThreadContent} rows="10" placeholder="Start the discussion..."></textarea>
        </label>
        <div class="modal-actions">
          <button onclick={() => showCreateThreadDialog = false} class="cancel-button">Cancel</button>
          <button onclick={createDiscussionThread} disabled={!newThreadTitle.trim() || creatingThread} class="save-button">
            {creatingThread ? 'Creating...' : 'Create Thread'}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Reply to Thread/Comment Dialog -->
  {#if showReplyDialog && userPubkey && (replyingToThread || replyingToComment)}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Reply to thread"
      onclick={() => {
        showReplyDialog = false;
        replyingToThread = null;
        replyingToComment = null;
        replyContent = '';
      }}
      onkeydown={(e) => e.key === 'Escape' && (showReplyDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <h3>
          {#if replyingToComment}
            Reply to Comment
          {:else if replyingToThread}
            Reply to Thread
          {:else}
            Reply
          {/if}
        </h3>
        <label>
          Your Reply:
          <textarea bind:value={replyContent} rows="8" placeholder="Write your reply..."></textarea>
        </label>
        <div class="modal-actions">
          <button 
            onclick={() => {
              showReplyDialog = false;
              replyingToThread = null;
              replyingToComment = null;
              replyContent = '';
            }} 
            class="cancel-button"
          >
            Cancel
          </button>
          <button 
            onclick={() => createThreadReply()} 
            disabled={!replyContent.trim() || creatingReply} 
            class="save-button"
          >
            {creatingReply ? 'Posting...' : 'Post Reply'}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Create PR Dialog -->
  {#if showCreatePRDialog && userPubkey}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Create new pull request"
      onclick={() => showCreatePRDialog = false}
      onkeydown={(e) => e.key === 'Escape' && (showCreatePRDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <h3>Create New Pull Request</h3>
        <label>
          Subject:
          <input type="text" bind:value={newPRSubject} placeholder="PR title..." />
        </label>
        <label>
          Description:
          <textarea bind:value={newPRContent} rows="8" placeholder="Describe your changes..."></textarea>
        </label>
        <label>
          Commit ID:
          <input type="text" bind:value={newPRCommitId} placeholder="Commit hash..." />
        </label>
        <label>
          Branch Name (optional):
          <input type="text" bind:value={newPRBranchName} placeholder="feature/new-feature" />
        </label>
        <div class="modal-actions">
          <button onclick={() => showCreatePRDialog = false} class="cancel-button">Cancel</button>
          <button onclick={createPR} disabled={!newPRSubject.trim() || !newPRContent.trim() || !newPRCommitId.trim() || saving} class="save-button">
            {saving ? 'Creating...' : 'Create PR'}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Create Patch Dialog -->
  {#if showCreatePatchDialog && userPubkey}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Create new patch"
      onclick={() => showCreatePatchDialog = false}
      onkeydown={(e) => e.key === 'Escape' && (showCreatePatchDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <h3>Create New Patch</h3>
        <p class="help-text">Enter your patch content in git format-patch format. Patches should be under 60KB.</p>
        <label>
          Subject (optional):
          <input type="text" bind:value={newPatchSubject} placeholder="Patch title..." />
        </label>
        <label>
          Patch Content:
          <textarea bind:value={newPatchContent} rows="15" placeholder="Paste your git format-patch output here..."></textarea>
        </label>
        <div class="modal-actions">
          <button onclick={() => showCreatePatchDialog = false} class="cancel-button">Cancel</button>
          <button onclick={createPatch} disabled={!newPatchContent.trim() || creatingPatch} class="save-button">
            {creatingPatch ? 'Creating...' : 'Create Patch'}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Commit Dialog -->
  {#if showCommitDialog && userPubkey && isMaintainer}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Commit changes"
      onclick={() => showCommitDialog = false}
      onkeydown={(e) => e.key === 'Escape' && (showCommitDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <h3>Commit Changes</h3>
        <label>
          Commit Message:
          <textarea 
            bind:value={commitMessage} 
            placeholder="Describe your changes..."
            rows="4"
          ></textarea>
        </label>
        <div class="modal-actions">
          <button onclick={() => showCommitDialog = false} class="cancel-button">Cancel</button>
          <button 
            onclick={saveFile} 
            disabled={!commitMessage.trim() || saving || needsClone} 
            class="save-button"
            title={needsClone ? cloneTooltip : ''}
          >
            {saving ? 'Saving...' : 'Commit & Save'}
          </button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Verification File Dialog -->
  {#if showVerificationDialog && verificationFileContent}
    <div 
      class="modal-overlay" 
      role="dialog"
      aria-modal="true"
      aria-label="Repository verification file"
      onclick={() => showVerificationDialog = false}
      onkeydown={(e) => e.key === 'Escape' && (showVerificationDialog = false)}
      tabindex="-1"
    >
      <!-- svelte-ignore a11y_click_events_have_key_events -->
      <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
      <div 
        class="modal verification-modal" 
        role="document"
        onclick={(e) => e.stopPropagation()}
      >
        <div class="modal-header">
          <h3>Repository Verification File</h3>
        </div>
        <div class="modal-body">
          <p class="verification-instructions">
            The announcement event should be saved to <code>nostr/repo-events.jsonl</code> in your repository.
            You can download the announcement event JSON below for reference.
          </p>
          <div class="verification-file-content">
            <div class="file-header">
              <span class="filename">announcement-event.json</span>
              <div class="file-actions">
                <button onclick={copyVerificationToClipboard} class="copy-button">Copy</button>
                <button onclick={downloadVerificationFile} class="download-button">Download</button>
              </div>
            </div>
            <pre class="file-content"><code>{verificationFileContent}</code></pre>
          </div>
        </div>
        <div class="modal-actions">
          <button onclick={() => showVerificationDialog = false} class="cancel-button">Close</button>
        </div>
      </div>
    </div>
  {/if}
</div>

<style>
  /* Word wrap styles - ensure they apply with highest specificity */
  :global(.read-only-editor.word-wrap) {
    overflow-x: hidden !important;
  }

  :global(.read-only-editor.word-wrap pre) {
    white-space: pre-wrap !important;
    word-wrap: break-word !important;
    overflow-wrap: break-word !important;
    overflow-x: hidden !important;
    overflow-y: visible !important;
    max-width: 100% !important;
  }

  :global(.read-only-editor.word-wrap pre code),
  :global(.read-only-editor.word-wrap pre code.hljs),
  :global(.read-only-editor.word-wrap code.hljs),
  :global(.read-only-editor.word-wrap .hljs) {
    white-space: pre-wrap !important;
    word-wrap: break-word !important;
    overflow-wrap: break-word !important;
    overflow-x: hidden !important;
    overflow-y: visible !important;
    display: block !important;
    max-width: 100% !important;
  }

  :global(.read-only-editor.word-wrap pre code.hljs *),
  :global(.read-only-editor.word-wrap pre code.hljs span),
  :global(.read-only-editor.word-wrap code.hljs *),
  :global(.read-only-editor.word-wrap .hljs *) {
    white-space: pre-wrap !important;
  }
</style>
