<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{% block title %}DiscordClone{% endblock %}</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
  <style>
    :root {
      --dc-bg: #1e1f22;
      --dc-sidebar: #2b2d31;
      --dc-panel: #232428;
      --dc-accent: #5865f2;
      --dc-accent-hover: #4752c4;
      --dc-text: #dcddde;
      --dc-muted: #8a8c92;
      --dc-online: #23a55a;
      --dc-mod: #f0b232;
      --dc-admin: #ed4245;
      --dc-border: #3f4147;
      --dc-msg-bg: #2e3035;
      --dc-input: #383a40;
    }
    * { box-sizing: border-box; }
    body {
      background: var(--dc-bg);
      color: var(--dc-text);
      font-family: 'Segoe UI', system-ui, sans-serif;
      height: 100vh;
      overflow: hidden;
    }
    /* ── Layout ── */
    .app-layout { display: flex; height: 100vh; }
    .sidebar-channels {
      width: 240px;
      min-width: 240px;
      background: var(--dc-sidebar);
      display: flex;
      flex-direction: column;
      border-right: 1px solid var(--dc-border);
    }
    .sidebar-users {
      width: 200px;
      min-width: 200px;
      background: var(--dc-sidebar);
      display: flex;
      flex-direction: column;
      border-left: 1px solid var(--dc-border);
    }
    .main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
    /* ── Sidebar items ── */
    .sidebar-header {
      padding: 16px;
      font-weight: 700;
      font-size: 0.95rem;
      border-bottom: 1px solid var(--dc-border);
      letter-spacing: .5px;
    }
    .sidebar-section {
      padding: 16px 8px 4px 8px;
      font-size: 0.7rem;
      font-weight: 700;
      letter-spacing: 1px;
      color: var(--dc-muted);
      text-transform: uppercase;
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
    .channel-item {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 6px 8px;
      margin: 1px 4px;
      border-radius: 4px;
      cursor: pointer;
      color: var(--dc-muted);
      text-decoration: none;
      font-size: 0.9rem;
      transition: background .15s, color .15s;
    }
    .channel-item:hover, .channel-item.active {
      background: rgba(255,255,255,0.08);
      color: #fff;
    }
    .channel-item .bi { font-size: 1rem; }
    /* ── User bar ── */
    .user-bar {
      padding: 10px 12px;
      background: #232428;
      border-top: 1px solid var(--dc-border);
      display: flex;
      align-items: center;
      gap: 10px;
      margin-top: auto;
    }
    .user-avatar {
      width: 32px; height: 32px;
      border-radius: 50%;
      object-fit: cover;
      background: var(--dc-accent);
      display: flex; align-items: center; justify-content: center;
      font-size: .8rem; font-weight: 700; color: #fff;
    }
    .user-avatar img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; }
    .role-badge {
      font-size: 0.65rem;
      padding: 1px 5px;
      border-radius: 3px;
      font-weight: 600;
    }
    .role-admin { background: var(--dc-admin); color: #fff; }
    .role-moderator { background: var(--dc-mod); color: #000; }
    .role-user { background: #4e5058; color: #fff; }
    /* ── Chat area ── */
    .chat-header {
      padding: 12px 20px;
      background: var(--dc-panel);
      border-bottom: 1px solid var(--dc-border);
      font-weight: 700;
      font-size: 1rem;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .messages-container {
      flex: 1;
      overflow-y: auto;
      padding: 16px 20px;
      display: flex;
      flex-direction: column;
      gap: 4px;
    }
    .messages-container::-webkit-scrollbar { width: 6px; }
    .messages-container::-webkit-scrollbar-thumb { background: var(--dc-border); border-radius: 3px; }
    .message-group {
      display: flex;
      gap: 14px;
      padding: 4px 8px;
      border-radius: 4px;
      transition: background .1s;
    }
    .message-group:hover { background: rgba(0,0,0,0.1); }
    .msg-avatar {
      width: 40px; height: 40px;
      border-radius: 50%;
      object-fit: cover;
      flex-shrink: 0;
      background: var(--dc-accent);
      display: flex; align-items: center; justify-content: center;
      font-weight: 700; color: #fff; font-size: .85rem;
    }
    .msg-avatar img { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; }
    .msg-body { flex: 1; min-width: 0; }
    .msg-meta { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; }
    .msg-author { font-weight: 600; font-size: .9rem; }
    .msg-time { font-size: 0.72rem; color: var(--dc-muted); }
    .msg-text { font-size: 0.9rem; word-break: break-word; line-height: 1.4; }
    .msg-file img { max-width: 300px; max-height: 200px; border-radius: 6px; margin-top: 6px; }
    .msg-file audio { margin-top: 6px; }
    .msg-file a { color: var(--dc-accent); }
    .msg-actions { margin-left: auto; display: none; gap: 4px; }
    .message-group:hover .msg-actions { display: flex; }
    /* ── Input area ── */
    .input-area {
      padding: 12px 20px 16px;
      background: var(--dc-panel);
    }
    .input-box {
      background: var(--dc-input);
      border-radius: 8px;
      padding: 0 12px;
      display: flex;
      align-items: center;
      gap: 8px;
    }
    .input-box input[type="text"] {
      flex: 1;
      background: transparent;
      border: none;
      outline: none;
      color: var(--dc-text);
      padding: 12px 0;
      font-size: .95rem;
    }
    .input-box input[type="text"]::placeholder { color: var(--dc-muted); }
    .input-box button {
      background: none;
      border: none;
      color: var(--dc-muted);
      font-size: 1.2rem;
      cursor: pointer;
      transition: color .15s;
    }
    .input-box button:hover { color: var(--dc-accent); }
    /* ── Forms / pages ── */
    .auth-wrapper {
      min-height: 100vh;
      background: var(--dc-bg);
      display: flex;
      align-items: center;
      justify-content: center;
      overflow: auto;
    }
    .auth-card {
      background: var(--dc-panel);
      border-radius: 8px;
      padding: 32px 36px;
      width: 100%;
      max-width: 420px;
      box-shadow: 0 8px 40px rgba(0,0,0,.6);
    }
    .auth-card h1 { font-size: 1.5rem; font-weight: 700; text-align: center; margin-bottom: 4px; }
    .auth-card p.sub { text-align: center; color: var(--dc-muted); font-size: .85rem; margin-bottom: 24px; }
    .form-control {
      background: var(--dc-input) !important;
      border-color: var(--dc-border) !important;
      color: var(--dc-text) !important;
    }
    .form-control:focus { border-color: var(--dc-accent) !important; box-shadow: 0 0 0 2px rgba(88,101,242,.3) !important; }
    .btn-primary {
      background: var(--dc-accent) !important;
      border-color: var(--dc-accent) !important;
    }
    .btn-primary:hover { background: var(--dc-accent-hover) !important; border-color: var(--dc-accent-hover) !important; }
    .online-dot {
      width: 10px; height: 10px;
      background: var(--dc-online);
      border-radius: 50%;
      display: inline-block;
    }
    .offline-dot {
      width: 10px; height: 10px;
      background: var(--dc-muted);
      border-radius: 50%;
      display: inline-block;
    }
    .voice-members-list {
      margin-left: 24px;
      padding-bottom: 6px;
      display: flex;
      flex-direction: column;
      gap: 4px;
    }
    .voice-member-item {
      display: flex;
      align-items: center;
      gap: 6px;
      padding: 2px 6px;
      font-size: 0.8rem;
      color: var(--dc-muted);
    }
    .voice-member-item img {
      width: 18px;
      height: 18px;
      border-radius: 50%;
      object-fit: cover;
    }
    .voice-member-avatar-fallback {
      width: 18px;
      height: 18px;
      border-radius: 50%;
      background: var(--dc-accent);
      color: #fff;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 0.6rem;
      font-weight: bold;
    }
    /* ── Voice status panel ── */
    .voice-status-panel {
      padding: 10px 12px;
      background: #111214;
      border-top: 1px solid var(--dc-border);
      display: none;
      align-items: center;
      gap: 10px;
      font-size: 0.8rem;
    }
    .voice-status-info {
      flex: 1;
      min-width: 0;
    }
    .voice-status-title {
      font-weight: 600;
      color: var(--dc-online);
    }
    .voice-status-channel {
      font-size: 0.72rem;
      color: var(--dc-muted);
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .voice-status-actions {
      display: flex;
      gap: 4px;
    }
    .voice-status-actions button {
      background: none;
      border: none;
      color: var(--dc-text);
      cursor: pointer;
      font-size: 1.1rem;
      padding: 4px;
      border-radius: 4px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .voice-status-actions button:hover {
      background: rgba(255,255,255,0.08);
    }
    .voice-status-actions button.active {
      color: var(--dc-admin);
    }
    /* ── Scrollbar ── */
    ::-webkit-scrollbar { width: 6px; height: 6px; }
    ::-webkit-scrollbar-track { background: transparent; }
    ::-webkit-scrollbar-thumb { background: var(--dc-border); border-radius: 3px; }
  </style>
</head>
<body>
<div id="app-root">
{% block body %}{% endblock %}
</div>

<!-- Build version badge -->
<div id="app-version" style="position:fixed;bottom:4px;left:4px;font-size:0.6rem;color:rgba(255,255,255,0.25);z-index:9999;pointer-events:none;font-family:monospace;">v2026.05.24-b</div>
<!-- Voice connected status panel -->
<div class="voice-status-panel" id="voice-status-panel">
  <div class="voice-status-info">
    <div class="voice-status-title"><i class="bi bi-broadcast me-1"></i>Voice Connected</div>
    <div class="voice-status-channel" id="voice-status-channel-name">Channel Name</div>
  </div>
  <div class="voice-status-actions">
    <button id="voice-mute-btn" title="Mute Microphone"><i class="bi bi-mic-fill"></i></button>
    <button id="voice-deafen-btn" title="Deafen Audio"><i class="bi bi-volume-mute-fill"></i></button>
    <button id="voice-disconnect-btn" class="text-danger" title="Disconnect"><i class="bi bi-telephone-x-fill"></i></button>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>

<script id="base-script">
(function() {
  const currentUserId = {% if request.user.is_authenticated %}{{ request.user.id }}{% else %}null{% endif %};
  const currentUsername = "{% if request.user.is_authenticated %}{{ request.user.username|escapejs }}{% endif %}";
  
  if (!currentUserId) return; // Not logged in
  
  // Voice state
  let localStream = null;
  let pcs = {};
  let voiceWs = null;
  let cloudflareIceServers = [];
  let isMuted = false;
  let isDeafened = false;
  let currentVoiceChannelId = null;
  
  // Connect global updates WebSocket
  const wsProto = location.protocol === 'https:' ? 'wss:' : 'ws:';
  const globalWs = new WebSocket(`${wsProto}//${location.host}/ws/global/`);
  
  globalWs.onmessage = function(e) {
    const event = JSON.parse(e.data);
    
    if (event.type === 'voice_presence_change') {
      const channelId = event.channel_id;
      const userId = event.user_id;
      const username = event.username;
      const avatar = event.avatar;
      
      // Clean up user from any other voice channel list in DOM first
      document.querySelectorAll(`.voice-member-item[data-user-id="${userId}"]`).forEach(el => el.remove());
      
      if (event.action === 'joined') {
        const container = document.getElementById(`voice_members_${channelId}`);
        if (container) {
          const div = document.createElement('div');
          div.className = 'voice-member-item';
          div.dataset.userId = userId;
          
          const avatarHtml = avatar 
            ? `<img src="${avatar}" alt="av">`
            : `<div class="voice-member-avatar-fallback">${username[0].toUpperCase()}</div>`;
            
          div.innerHTML = `${avatarHtml}<span>${username}</span>`;
          container.appendChild(div);
        }
      }
    } else if (event.type === 'user_presence_change') {
      const userId = event.user_id;
      const online = event.online;
      
      const dmLink = document.getElementById(`dm_link_${userId}`);
      if (dmLink) {
        const dot = dmLink.querySelector('.online-dot, .offline-dot');
        if (dot) {
          dot.className = online ? 'online-dot' : 'offline-dot';
        }
      }
      
      document.querySelectorAll('.sidebar-users .channel-item').forEach(el => {
        if (el.textContent.includes(event.username)) {
          const dot = el.querySelector('.online-dot, .offline-dot');
          if (dot) {
            dot.className = online ? 'online-dot' : 'offline-dot';
          }
        }
      });
    }
  };
  
  // Voice Controls UI
  const statusPanel = document.getElementById('voice-status-panel');
  const channelNameLabel = document.getElementById('voice-status-channel-name');
  const muteBtn = document.getElementById('voice-mute-btn');
  const deafenBtn = document.getElementById('voice-deafen-btn');
  const disconnectBtn = document.getElementById('voice-disconnect-btn');
  
  muteBtn.onclick = function() {
    isMuted = !isMuted;
    if (localStream) {
      localStream.getAudioTracks().forEach(track => track.enabled = !isMuted);
    }
    muteBtn.classList.toggle('active', isMuted);
    muteBtn.querySelector('i').className = isMuted ? 'bi bi-mic-mute-fill' : 'bi bi-mic-fill';
  };
  
  deafenBtn.onclick = function() {
    isDeafened = !isDeafened;
    if (localStream) {
      localStream.getAudioTracks().forEach(track => track.enabled = !isDeafened && !isMuted);
    }
    muteBtn.classList.toggle('active', isDeafened || isMuted);
    muteBtn.querySelector('i').className = (isDeafened || isMuted) ? 'bi bi-mic-mute-fill' : 'bi bi-mic-fill';
    
    document.querySelectorAll('audio[id^="audio_peer_"]').forEach(audio => {
      audio.muted = isDeafened;
    });
    
    deafenBtn.classList.toggle('active', isDeafened);
    deafenBtn.querySelector('i').className = isDeafened ? 'bi bi-volume-mute-fill' : 'bi bi-volume-up-fill';
  };
  
  disconnectBtn.onclick = function() {
    disconnectVoice();
  };
  
  async function joinVoiceChannel(channelId, channelName) {
    if (currentVoiceChannelId === channelId) return;
    if (currentVoiceChannelId) disconnectVoice();
    
    currentVoiceChannelId = channelId;
    channelNameLabel.textContent = channelName;
    statusPanel.style.display = 'flex';
    
    try {
      const credsRes = await fetch('/channel/voice/credentials/');
      const credsData = await credsRes.json();
      cloudflareIceServers = credsData.iceServers || [];
      
      localStream = await navigator.mediaDevices.getUserMedia({ audio: true });
      if (isMuted || isDeafened) {
        localStream.getAudioTracks().forEach(track => track.enabled = false);
      }
      
      voiceWs = new WebSocket(`${wsProto}//${location.host}/ws/voice/${channelId}/`);
      
      voiceWs.onmessage = function(e) {
        const event = JSON.parse(e.data);
        
        if (event.type === 'voice_user_joined') {
          const pc = getOrCreatePC(event.user_id, event.username);
          pc.createOffer()
            .then(offer => pc.setLocalDescription(offer))
            .then(() => {
              voiceWs.send(JSON.stringify({
                action: 'signal',
                target_id: event.user_id,
                data: { sdp: pc.localDescription }
              }));
            });
        } else if (event.type === 'voice_user_left') {
          cleanupPeer(event.user_id);
        } else if (event.type === 'voice_signal_relay') {
          const senderId = event.sender_id;
          const data = event.data;
          
          if (data.sdp) {
            const pc = getOrCreatePC(senderId, event.sender_username);
            pc.setRemoteDescription(new RTCSessionDescription(data.sdp))
              .then(() => {
                if (data.sdp.type === 'offer') {
                  pc.createAnswer()
                    .then(answer => pc.setLocalDescription(answer))
                    .then(() => {
                      voiceWs.send(JSON.stringify({
                        action: 'signal',
                        target_id: senderId,
                        data: { sdp: pc.localDescription }
                      }));
                    });
                }
              });
          } else if (data.candidate) {
            const pc = getOrCreatePC(senderId, event.sender_username);
            pc.addIceCandidate(new RTCIceCandidate(data.candidate))
              .catch(err => console.error("Error adding ice candidate:", err));
          }
        }
      };
      
    } catch (err) {
      console.error("Failed to join voice channel:", err);
      disconnectVoice();
      alert("Could not access microphone or connect to voice server.");
    }
  }
  
  function getOrCreatePC(userId, username) {
    if (pcs[userId]) return pcs[userId];
    
    const pc = new RTCPeerConnection({ iceServers: cloudflareIceServers });
    pcs[userId] = pc;
    
    if (localStream) {
      localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
    }
    
    pc.onicecandidate = function(e) {
      if (e.candidate) {
        voiceWs.send(JSON.stringify({
          action: 'signal',
          target_id: userId,
          data: { candidate: e.candidate }
        }));
      }
    };
    
    pc.ontrack = function(e) {
      let audio = document.getElementById(`audio_peer_${userId}`);
      if (!audio) {
        audio = document.createElement('audio');
        audio.id = `audio_peer_${userId}`;
        audio.autoplay = true;
        audio.style.display = 'none';
        document.body.appendChild(audio);
      }
      audio.srcObject = e.streams[0];
      audio.muted = isDeafened;
    };
    
    pc.onconnectionstatechange = function() {
      if (pc.connectionState === 'disconnected' || pc.connectionState === 'failed' || pc.connectionState === 'closed') {
        cleanupPeer(userId);
      }
    };
    
    return pc;
  }
  
  function cleanupPeer(userId) {
    if (pcs[userId]) {
      pcs[userId].close();
      delete pcs[userId];
    }
    const audio = document.getElementById(`audio_peer_${userId}`);
    if (audio) audio.remove();
  }
  
  function disconnectVoice() {
    if (voiceWs) {
      voiceWs.close();
      voiceWs = null;
    }
    for (let id in pcs) {
      cleanupPeer(id);
    }
    pcs = {};
    if (localStream) {
      localStream.getTracks().forEach(track => track.stop());
      localStream = null;
    }
    currentVoiceChannelId = null;
    statusPanel.style.display = 'none';
  }
  
  document.body.addEventListener('click', function(e) {
    const vc = e.target.closest('.voice-channel');
    if (vc) {
      const channelId = vc.dataset.channelId;
      const channelName = vc.dataset.channelName;
      joinVoiceChannel(channelId, channelName);
    }
  });
  
  // AJAX Shell Navigation
  // Pages that should always do a full-page load (non-SPA layouts, auth pages)
  const FULL_PAGE_KEYWORDS = ['create', 'profile', 'admin', 'login', 'register', 'logout', 'delete', 'block'];
  
  function shouldFullPageNavigate(href) {
    if (!href || href.startsWith('http') || href.startsWith('#') || href.startsWith('javascript')) return true;
    return FULL_PAGE_KEYWORDS.some(kw => href.includes(kw));
  }
  
  document.body.addEventListener('click', function(e) {
    const link = e.target.closest('a');
    if (!link) return;
    
    const href = link.getAttribute('href');
    if (shouldFullPageNavigate(href)) return;
    if (link.target === '_blank') return;
    
    e.preventDefault();
    loadPage(href);
  });
  
  function loadPage(url) {
    // Close existing channel/DM WebSocket before navigating
    if (window.channelWs) {
      window.channelWs.close();
      window.channelWs = null;
    }
    
    fetch(url)
      .then(res => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.text();
      })
      .then(html => {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        
        const newRoot = doc.getElementById('app-root');
        const currentRoot = document.getElementById('app-root');
        
        if (newRoot && currentRoot) {
          currentRoot.replaceWith(newRoot);
        } else {
          // Fallback: full page load if structure doesn't match
          window.location.href = url;
          return;
        }
        
        history.pushState({ url }, '', url);
        
        // Execute only PAGE-SPECIFIC scripts (those inside the scripts block).
        // SKIP the base-script IIFE — it's already running and re-executing
        // it would duplicate global WebSocket connections and event handlers.
        // Wrap each script in an IIFE to prevent 'const' redeclaration errors
        // when navigating between channels (each has const channelId, etc.).
        doc.querySelectorAll('script').forEach(script => {
          if (script.id === 'base-script') return;
          if (script.src) return;
          if (!script.textContent.trim()) return;
          const newScript = document.createElement('script');
          newScript.textContent = `(function(){${script.textContent}})();`;
          document.body.appendChild(newScript);
          newScript.remove();
        });
      })
      .catch(err => {
        console.error("AJAX navigation failed, falling back to full redirect:", err);
        window.location.href = url;
      });
  }
  
  window.onpopstate = function(e) {
    if (e.state && e.state.url) {
      loadPage(e.state.url);
    } else {
      loadPage(location.pathname);
    }
  };
  
  // AJAX form submit handler — only for file upload forms inside chat/DM views.
  // Forms for auth, profile, channel create, admin actions submit natively.
  document.body.addEventListener('submit', function(e) {
    const form = e.target.closest('form');
    if (!form) return;
    
    const action = form.getAttribute('action') || '';
    // Let these forms submit natively (full page reload)
    if (FULL_PAGE_KEYWORDS.some(kw => action.includes(kw))) return;
    
    // Only AJAX-submit file upload forms (they have enctype=multipart)
    if (!form.enctype || form.enctype !== 'multipart/form-data') return;
    
    e.preventDefault();
    const formData = new FormData(form);
    const submitUrl = action || location.pathname;
    
    fetch(submitUrl, {
      method: 'POST',
      body: formData,
      headers: {
        'X-Requested-With': 'XMLHttpRequest'
      }
    })
    .then(res => {
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      // Reload current page to show the uploaded file
      loadPage(location.pathname);
    })
    .catch(err => console.error("File upload failed:", err));
  });
})();
</script>

{% block scripts %}{% endblock %}
</body>
</html>
