// MusicRoom - Channel Sync module
// WebSocket connection and server synchronization

(function() {
  const M = window.MusicRoom;
  
  // Load available channels and connect to saved or default
  M.loadChannels = async function() {
    try {
      const res = await fetch("/api/channels");
      const channels = await res.json();
      if (channels.length === 0) {
        M.setTrackTitle("No channels available");
        return;
      }
      M.channels = channels;
      M.renderChannelList();
      // Try saved channel first, fall back to default
      const savedChannelId = M.getSavedChannelId();
      const savedChannel = savedChannelId && channels.find(c => c.id === savedChannelId);
      const targetChannel = savedChannel || channels.find(c => c.isDefault) || channels[0];
      M.connectChannel(targetChannel.id);
    } catch (e) {
      M.setTrackTitle("Server unavailable");
      M.$("#status").textContent = "Local (offline)";
      M.synced = false;
      M.updateUI();
    }
  };
  
  // Create a new channel
  M.createChannel = async function(name, description) {
    try {
      const res = await fetch("/api/channels", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name, description })
      });
      if (!res.ok) {
        const err = await res.json();
        M.showToast(err.error || "Failed to create channel");
        return null;
      }
      const channel = await res.json();
      M.showToast(`Channel "${channel.name}" created`);
      return channel;
    } catch (e) {
      M.showToast("Failed to create channel");
      return null;
    }
  };
  
  // Delete a channel
  M.deleteChannel = async function(channelId) {
    const channel = M.channels?.find(c => c.id === channelId);
    if (!channel) return;
    if (channel.isDefault) {
      M.showToast("Cannot delete default channel");
      return;
    }
    M.showConfirmToast(`Delete channel "${channel.name}"?`, async () => {
      try {
        const res = await fetch(`/api/channels/${channelId}`, { method: "DELETE" });
        if (!res.ok) {
          const err = await res.json();
          M.showToast(err.error || "Failed to delete channel", "error");
          return;
        }
        if (M.getSavedChannelId() === channelId) {
          M.clearRememberedChannel();
        }
        M.showToast(`Channel "${channel.name}" deleted`);
      } catch (e) {
        M.showToast("Failed to delete channel", "error");
      }
    }, { confirmText: "Delete", duration: 15000 });
  };
  
  // New channel creation with slideout input
  M.createNewChannel = async function() {
    if (!M.canCreateUserContent()) {
      M.showToast("Sign in to create channels", "warning");
      return;
    }

    const header = M.$("#channels-panel .panel-header");
    const btn = M.$("#btn-new-channel");
    
    // Already in edit mode?
    if (header.querySelector(".new-channel-input")) return;
    
    // Hide button, show input
    btn.style.display = "none";
    
    const input = document.createElement("input");
    input.type = "text";
    input.className = "new-channel-input";
    input.placeholder = "Channel name...";
    input.maxLength = 64;
    
    const submit = document.createElement("button");
    submit.className = "btn-submit-channel";
    submit.textContent = "›";
    
    header.appendChild(input);
    header.appendChild(submit);
    input.focus();
    
    const cleanup = () => {
      input.remove();
      submit.remove();
      btn.style.display = "";
    };
    
    const doCreate = async () => {
      const name = input.value.trim();
      if (!name) {
        cleanup();
        return;
      }
      try {
        await M.createChannel(name);
      } catch (e) {
        M.showToast("Failed to create channel");
      }
      cleanup();
    };
    
    submit.onclick = doCreate;
    input.onkeydown = (e) => {
      if (e.key === "Enter") doCreate();
      if (e.key === "Escape") cleanup();
    };
    input.onblur = (e) => {
      if (e.relatedTarget !== submit) cleanup();
    };
  };

  // New channel button handler
  document.addEventListener("DOMContentLoaded", () => {
    const btn = M.$("#btn-new-channel");
    if (btn) {
      btn.onclick = () => M.createNewChannel();
    }
  });
  
  // Render channel list in sidebar
  M.renderChannelList = function() {
    const container = M.$("#channels-list");
    if (!container) return;
    container.innerHTML = "";
    for (const ch of M.channels || []) {
      const div = document.createElement("div");
      div.className = "channel-item" + (ch.id === M.currentChannelId ? " active" : "");
      const listeners = ch.listeners || [];
      // Count occurrences of each user
      const counts = {};
      for (const name of listeners) {
        counts[name] = (counts[name] || 0) + 1;
      }
      const listenersHtml = Object.entries(counts).map(([name, count]) => 
        `<div class="listener">${name}${count > 1 ? ` <span class="listener-mult">x${count}</span>` : ""}</div>`
      ).join("");
      
      // Show delete button for non-default channels if user is admin or creator
      const canDelete = !ch.isDefault && M.currentUser && 
        (M.currentUser.isAdmin || ch.createdBy === M.currentUser.id);
      const deleteBtn = canDelete ? `<button class="btn-delete-channel" title="Delete channel">×</button>` : "";
      
      // Show rename button for signed-in non-guests
      const canRename = M.currentUser && !M.currentUser.isGuest;
      const renameBtn = canRename ? `<button class="btn-rename-channel" title="Rename channel">✏️</button>` : "";
      
      div.innerHTML = `
        <div class="channel-header">
          <span class="channel-name">${ch.name}</span>
          <input class="channel-name-input" type="text" value="${ch.name.replace(/"/g, '&quot;')}" style="display:none;">
          ${renameBtn}
          ${deleteBtn}
          <span class="listener-count">${ch.listenerCount}</span>
        </div>
        <div class="channel-listeners">${listenersHtml}</div>
      `;
      const headerEl = div.querySelector(".channel-header");
      const nameSpan = headerEl.querySelector(".channel-name");
      const nameInput = headerEl.querySelector(".channel-name-input");
      nameSpan.onclick = () => M.switchChannel(ch.id);
      
      const renBtn = headerEl.querySelector(".btn-rename-channel");
      if (renBtn) {
        renBtn.onclick = (e) => {
          e.stopPropagation();
          nameSpan.style.display = "none";
          renBtn.style.display = "none";
          nameInput.style.display = "inline-block";
          nameInput.focus();
          nameInput.select();
        };
      }
      
      // Handle inline rename
      const submitRename = async () => {
        const newName = nameInput.value.trim();
        if (newName && newName !== ch.name) {
          await M.renameChannel(ch.id, newName);
        } else {
          // Restore original display
          nameSpan.style.display = "";
          if (renBtn) renBtn.style.display = "";
          nameInput.style.display = "none";
        }
      };
      nameInput.onblur = submitRename;
      nameInput.onkeydown = (e) => {
        if (e.key === "Enter") {
          e.preventDefault();
          nameInput.blur();
        } else if (e.key === "Escape") {
          nameInput.value = ch.name;
          nameInput.blur();
        }
      };
      
      const delBtn = headerEl.querySelector(".btn-delete-channel");
      if (delBtn) {
        delBtn.onclick = (e) => {
          e.stopPropagation();
          M.deleteChannel(ch.id);
        };
      }
      container.appendChild(div);
    }
  };
  
  // Switch to a different channel via WebSocket
  M.switchChannel = function(channelId) {
    if (channelId === M.currentChannelId) return;
    if (M.ws && M.ws.readyState === WebSocket.OPEN) {
      M.ws.send(JSON.stringify({ action: "switch", channelId }));
    }
  };
  
  // Connect to a channel via WebSocket
  M.connectChannel = function(id) {
    if (M.ws) {
      const oldWs = M.ws;
      M.ws = null;
      oldWs.onclose = null;
      oldWs.onerror = null;
      oldWs.close();
    }
    M.currentChannelId = id;
    M.rememberChannel(id);
    const proto = location.protocol === "https:" ? "wss:" : "ws:";
    M.ws = new WebSocket(proto + "//" + location.host + "/api/channels/" + id + "/ws");
    
    // Track if we've ever connected successfully
    let wasConnected = false;
    
    M.ws.onmessage = (e) => {
      const data = JSON.parse(e.data);
      // Handle channel list updates
      if (data.type === "channel_list") {
        M.channels = data.channels;
        M.renderChannelList();
        return;
      }
      // Handle channel switch confirmation
      if (data.type === "switched") {
        M.currentChannelId = data.channelId;
        M.rememberChannel(data.channelId);
        M.renderChannelList();
        return;
      }
      // Handle kick command
      if (data.type === "kick") {
        M.showToast("Disconnected: " + (data.reason || "Kicked by another session"));
        M.wantSync = false;
        M.synced = false;
        M.audio.pause();
        if (M.ws) {
          const oldWs = M.ws;
          M.ws = null;
          oldWs.onclose = null;
          oldWs.onerror = null;
          oldWs.close();
        }
        M.updateUI();
        return;
      }
      // Handle library updates
      if (data.type === "track_added") {
        M.showToast(`"${data.track.title}" is now available`);
        if (data.library) {
          M.library = data.library;
          M.renderLibrary();
        }
        return;
      }
      if (data.type === "track_removed") {
        M.showToast(`"${data.track.title}" was removed`);
        if (data.library) {
          M.library = data.library;
          M.renderLibrary();
        }
        return;
      }
      // Handle scan progress
      if (data.type === "scan_progress") {
        const el = M.$("#scan-progress");
        const wasScanning = !el.classList.contains("complete") && !el.classList.contains("hidden");
        
        if (data.scanning) {
          el.innerHTML = `<span class="spinner"></span>Scanning: ${data.processed}/${data.total} files`;
          el.classList.remove("hidden");
          el.classList.remove("complete");
        } else {
          // Show track count when not scanning
          const count = M.library.length;
          el.innerHTML = `${count} song${count !== 1 ? 's' : ''} in library`;
          el.classList.remove("hidden");
          el.classList.add("complete");
          
          // Show toast if scan just finished
          if (wasScanning) {
            M.showToast("Scanning complete!");
          }
        }
        return;
      }
      // Handle server-sent toasts
      if (data.type === "toast") {
        M.showToast(data.message, data.toastType || "info");
        return;
      }
      // Handle fetch progress from ytdlp
      if (data.type && data.type.startsWith("fetch_")) {
        if (M.handleFetchProgress) {
          M.handleFetchProgress(data);
        }
        return;
      }
      // Normal channel state update
      M.handleUpdate(data);
    };
    
    M.ws.onerror = () => {
      console.log("[WS] Connection error");
    };
    
    M.ws.onclose = () => {
      M.synced = false;
      M.ws = null;
      M.$("#sync-indicator").classList.add("disconnected");
      M.updateUI();
      // Auto-reconnect if user wants to be synced
      // Use faster retry (2s) if never connected, slower (3s) if disconnected after connecting
      if (M.wantSync) {
        const delay = wasConnected ? 3000 : 2000;
        setTimeout(() => M.connectChannel(id), delay);
      }
    };
    
    M.ws.onopen = () => {
      wasConnected = true;
      M.synced = true;
      M.$("#sync-indicator").classList.remove("disconnected");
      M.updateUI();
    };
  };
  
  // Handle channel state update from server
  M.handleUpdate = async function(data) {
    console.log("[WS] State update:", { 
      track: data.track?.title, 
      timestamp: data.currentTimestamp?.toFixed(1), 
      paused: data.paused,
      currentIndex: data.currentIndex 
    });
    
    M.$("#channel-name").textContent = data.channelName || "";
    
    // Update queue if provided (do this before early return for no track)
    if (data.queue) {
      M.queue = data.queue;
      M.currentIndex = data.currentIndex ?? 0;
      M.renderQueue();
    } else if (data.currentIndex !== undefined && data.currentIndex !== M.currentIndex) {
      M.currentIndex = data.currentIndex;
      M.renderQueue();
    }
    
    // Update playback mode if provided
    if (data.playbackMode && data.playbackMode !== M.playbackMode) {
      M.playbackMode = data.playbackMode;
      if (M.updateModeButton) M.updateModeButton();
    }
    
    if (!data.track) {
      M.setTrackTitle("No tracks");
      return;
    }
    M.serverTimestamp = data.currentTimestamp;
    M.serverTrackDuration = data.track.duration;
    M.lastServerUpdate = Date.now();
    const wasServerPaused = M.serverPaused;
    M.serverPaused = data.paused ?? true;

    // Cache track info for local mode - use track.id (content hash) as the identifier
    const trackId = data.track.id || data.track.filename; // Fallback for compatibility
    const isNewTrack = trackId !== M.currentTrackId;
    if (isNewTrack) {
      M.currentTrackId = trackId;
      M.setTrackTitle(data.track.title);
      M.loadingSegments.clear();
      
      // Auto-scroll queue to current track
      setTimeout(() => M.scrollToCurrentTrack(), 100);
      
      // Debug: log cache state for this track
      const trackCache = M.trackCaches.get(trackId);
      console.log("[Playback] Starting track:", data.track.title, {
        trackId: trackId,
        segments: trackCache ? [...trackCache] : [],
        segmentCount: trackCache ? trackCache.size : 0,
        inCachedTracks: M.cachedTracks.has(trackId),
        bulkStarted: M.bulkDownloadStarted.get(trackId) || false,
        hasBlobUrl: M.trackBlobs.has(trackId)
      });
      
      // Check if this track already has all segments cached
      M.checkAndCacheComplete(trackId);
    }

    if (M.synced) {
      if (!M.serverPaused) {
        // Server is playing - ensure we're playing and synced
        if (isNewTrack || !M.audio.src) {
          // Try cache first
          const cachedUrl = await M.loadTrackBlob(M.currentTrackId);
          M.audio.src = cachedUrl || M.getTrackUrl(M.currentTrackId);
          M.audio.currentTime = data.currentTimestamp;
          M.audio.play().catch(() => {});
        } else if (M.audio.paused) {
          M.audio.currentTime = data.currentTimestamp;
          M.audio.play().catch(() => {});
        } else {
          // Check drift
          const drift = Math.abs(M.audio.currentTime - data.currentTimestamp);
          if (drift >= 2) {
            console.log("[Sync] Correcting drift:", drift.toFixed(1), "s");
            M.audio.currentTime = data.currentTimestamp;
          }
        }
      } else {
        // Server is paused - ensure we're paused too
        if (!M.audio.paused) {
          M.audio.pause();
        }
        // Sync to paused position
        if (isNewTrack || !M.audio.src) {
          const cachedUrl = await M.loadTrackBlob(M.currentTrackId);
          M.audio.src = cachedUrl || M.getTrackUrl(M.currentTrackId);
          M.audio.currentTime = data.currentTimestamp;
        }
      }
    }
    M.updateUI();
  };

  // Rename channel
  M.renameChannel = async function(channelId, newName) {
    try {
      const res = await fetch(`/api/channels/${channelId}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name: newName.trim() })
      });
      const data = await res.json();
      if (!res.ok) {
        M.showToast(data.error || "Failed to rename channel", "error");
        return;
      }
      M.showToast(`Channel renamed to "${data.name}"`);
    } catch (e) {
      M.showToast("Failed to rename channel", "error");
    }
  };
})();
