/**
 * Speaker Management Handler for GM Sidebar
 * Handles all speaker (NPC) related operations including add, remove, set active, and journal integration
 */

import { extractParentElement } from '../../../utils/element-utils.mjs';

/**
 * Extract a display name from an image file path by stripping the directory and extension.
 */
function nameFromPath(path) {
  if (!path) return '';
  const filename = path.split('/').pop().split('\\').pop();
  return filename.replace(/\.[^.]+$/, '').replace(/[_-]/g, ' ').trim();
}

/**
 * Add a speaker from an image file picker
 */
export async function onAddSpeakerFromImage(_event, _target, _sidebar) {
  new FilePicker({
    type: 'image',
    callback: async (path) => {
      const defaultName = nameFromPath(path);
      // Place the dialog above the sidebar so it isn't hidden behind it
      const sidebarEl = _sidebar?.element;
      const sidebarZ = sidebarEl ? parseInt(window.getComputedStyle(sidebarEl).zIndex) || 0 : 0;
      const label = await foundry.applications.api.DialogV2.prompt({
        window: { title: game.i18n.localize('STORYFRAME.Dialogs.EnterNPCName.Title') },
        position: { zIndex: Math.max(sidebarZ + 1, 100001) },
        content: `<input type="text" name="label" value="${defaultName}" placeholder="${game.i18n.localize('STORYFRAME.Dialogs.EnterNPCName.Label')}" autofocus>`,
        ok: {
          label: game.i18n.localize('STORYFRAME.Dialogs.AddSpeaker.Button'),
          callback: (event, button, _dialog) => button.form.elements.label.value,
        },
        rejectClose: false,
      });

      if (label) {
        await game.storyframe.socketManager.requestAddSpeaker({
          imagePath: path,
          label,
        });
      }
    },
  }).render(true);
}

/**
 * Set a speaker as active
 */
export async function onSetSpeaker(_event, target, _sidebar) {
  const speakerId = target.closest('[data-speaker-id]')?.dataset.speakerId;
  if (speakerId) {
    await game.storyframe.socketManager.requestSetActiveSpeaker(speakerId);
    _updateActiveSpeakerInDOM(speakerId);
  }
}

/**
 * Edit a speaker's name and voice settings
 */
export async function onEditSpeaker(event, target) {
  event.stopPropagation();
  const speakerId = target.closest('[data-speaker-id]')?.dataset.speakerId;
  if (!speakerId) return;

  const state = game.storyframe.stateManager.getState();
  const speaker = state?.speakers?.find((s) => s.id === speakerId);
  if (!speaker) return;

  const currentName = speaker.label || '';
  const tts = game.storyframe.tts;
  const voices = tts?.getVoices() ?? [];
  const currentVoice = speaker.voiceConfig || {};

  // Build voice options grouped by language
  let voiceOptions = '<option value="">— None —</option>';
  if (voices.length > 0) {
    const byLang = new Map();
    for (const v of voices) {
      const lang = v.lang;
      if (!byLang.has(lang)) byLang.set(lang, []);
      byLang.get(lang).push(v);
    }
    for (const [lang, langVoices] of byLang) {
      voiceOptions += `<optgroup label="${lang}">`;
      for (const v of langVoices) {
        const sel = v.name === currentVoice.voiceName ? ' selected' : '';
        voiceOptions += `<option value="${v.name}"${sel}>${v.name}</option>`;
      }
      voiceOptions += '</optgroup>';
    }
  }

  const content = `
    <div class="sf-edit-speaker-form">
      <div class="sf-form-group">
        <label>Name</label>
        <input type="text" name="label" value="${currentName}" placeholder="${game.i18n.localize('STORYFRAME.Dialogs.EditNPCName.Label')}" autofocus>
      </div>
      <fieldset class="sf-form-fieldset">
        <legend><i class="fas fa-microphone"></i> Voice</legend>
        <div class="sf-form-group">
          <label>Voice</label>
          <select name="voiceName">${voiceOptions}</select>
        </div>
        <div class="sf-form-row">
          <div class="sf-form-group">
            <label>Pitch <span class="sf-range-value" data-for="pitch">${currentVoice.pitch ?? 1.0}</span></label>
            <input type="range" name="pitch" min="0.1" max="2" step="0.1" value="${currentVoice.pitch ?? 1.0}">
          </div>
          <div class="sf-form-group">
            <label>Rate <span class="sf-range-value" data-for="rate">${currentVoice.rate ?? 1.0}</span></label>
            <input type="range" name="rate" min="0.5" max="2" step="0.1" value="${currentVoice.rate ?? 1.0}">
          </div>
        </div>
        <button type="button" class="sf-preview-voice-btn"><i class="fas fa-play"></i> Preview</button>
      </fieldset>
    </div>
  `;

  const result = await foundry.applications.api.DialogV2.prompt({
    window: { title: game.i18n.localize('STORYFRAME.Dialogs.EditNPCName.Title') },
    content,
    ok: {
      label: game.i18n.localize('STORYFRAME.Dialogs.EditNPCName.Button'),
      callback: (_event, button, _dialog) => {
        const form = button.form.elements;
        return {
          label: form.label.value,
          voiceName: form.voiceName.value,
          pitch: parseFloat(form.pitch.value),
          rate: parseFloat(form.rate.value),
        };
      },
    },
    rejectClose: false,
    render: (_event, html) => {
      // Live range value display
      html.querySelectorAll('input[type="range"]').forEach(slider => {
        slider.addEventListener('input', () => {
          const label = html.querySelector(`.sf-range-value[data-for="${slider.name}"]`);
          if (label) label.textContent = slider.value;
        });
      });
      // Preview button
      const previewBtn = html.querySelector('.sf-preview-voice-btn');
      previewBtn?.addEventListener('click', () => {
        const voiceName = html.querySelector('[name="voiceName"]').value;
        const pitch = parseFloat(html.querySelector('[name="pitch"]').value);
        const rate = parseFloat(html.querySelector('[name="rate"]').value);
        if (!voiceName || !tts) return;
        tts.stop();
        const utterance = new SpeechSynthesisUtterance(currentName || 'Hello adventurers');
        utterance.voice = voices.find(v => v.name === voiceName);
        utterance.pitch = pitch;
        utterance.rate = rate;
        window.speechSynthesis.speak(utterance);
      });
    },
  });

  if (!result) return;

  // Build voice config (only if a voice was selected)
  const voiceConfig = result.voiceName
    ? { voiceName: result.voiceName, pitch: result.pitch, rate: result.rate }
    : null;

  // Update speaker
  const nameChanged = result.label && result.label !== currentName;
  const voiceChanged = JSON.stringify(voiceConfig) !== JSON.stringify(speaker.voiceConfig || null);

  if (nameChanged || voiceChanged) {
    const updatedSpeakers = state.speakers.map((s) => {
      if (s.id === speakerId) {
        const updated = { ...s };
        if (nameChanged) updated.label = result.label;
        if (voiceChanged) updated.voiceConfig = voiceConfig;
        return updated;
      }
      return s;
    });

    await game.storyframe.socketManager.requestUpdateSpeakers(updatedSpeakers);

    // Update TTS manager with new voice config
    if (tts && voiceConfig) {
      tts.setVoice(speakerId, voiceConfig);
    } else if (tts && !voiceConfig) {
      tts.removeVoice(speakerId);
    }
  }
}

/**
 * Remove a speaker
 */
export async function onRemoveSpeaker(event, target) {
  event.stopPropagation();
  const speakerId = target.closest('[data-speaker-id]')?.dataset.speakerId;
  if (!speakerId) return;

  await game.storyframe.socketManager.requestRemoveSpeaker(speakerId);
}

/**
 * Toggle speaker name visibility for players
 */
export async function onToggleSpeakerVisibility(event, target) {
  event.stopPropagation();
  const speakerId = target.closest('[data-speaker-id]')?.dataset.speakerId;
  if (!speakerId) return;

  await game.storyframe.socketManager.requestToggleSpeakerVisibility(speakerId);
}

/**
 * Toggle speaker hidden from player view entirely
 */
export async function onToggleSpeakerHidden(event, target) {
  event.stopPropagation();
  const speakerId = target.closest('[data-speaker-id]')?.dataset.speakerId;
  if (!speakerId) return;

  await game.storyframe.socketManager.requestToggleSpeakerHidden(speakerId);
}

/**
 * Build the full ordered image list for a speaker.
 * Actor portrait and token image are always present (derived live from the actor).
 * Custom images (altImages) are appended after.
 */
async function _buildSpeakerImageList(speaker) {
  const images = [];
  if (speaker.actorUuid) {
    const actor = await fromUuid(speaker.actorUuid);
    if (actor) {
      images.push(actor.img);
      const tokenImg = actor.prototypeToken?.texture?.src;
      if (tokenImg && tokenImg !== actor.img) images.push(tokenImg);
    }
  } else if (speaker.imagePath) {
    images.push(speaker.imagePath);
  }
  (speaker.altImages || []).forEach((img) => { if (!images.includes(img)) images.push(img); });
  return images;
}

/**
 * Toggle active class on speaker thumbnails without full re-render.
 */
function _updateActiveSpeakerInDOM(speakerId) {
  document.querySelectorAll('.speaker-thumbnail.active').forEach((el) => el.classList.remove('active'));
  const thumb = document.querySelector(`.speaker-thumbnail[data-speaker-id="${speakerId}"]`);
  if (thumb) thumb.classList.add('active');
}

/**
 * Swap speaker thumbnail image in DOM without full re-render.
 */
function _updateSpeakerImageInDOM(speakerId, imagePath) {
  const thumb = document.querySelector(`.speaker-thumbnail[data-speaker-id="${speakerId}"]`);
  if (thumb) {
    const img = thumb.querySelector('img');
    if (img) img.src = imagePath;
    const nav = thumb.querySelector('.speaker-image-nav');
    if (nav) nav.dataset.imagePath = imagePath;
  }
  const filmstripCard = document.querySelector(`.filmstrip-speaker[data-speaker-id="${speakerId}"]`);
  if (filmstripCard) {
    const img = filmstripCard.querySelector('img');
    if (img) img.src = imagePath;
  }
}

/**
 * Cycle to next alt image for a speaker
 */
export async function onCycleSpeakerImageNext(event, target) {
  event.stopPropagation();
  const speakerId = target.closest('[data-speaker-id]')?.dataset.speakerId;
  if (!speakerId) return;

  const state = game.storyframe.stateManager.getState();
  const speaker = state?.speakers?.find((s) => s.id === speakerId);
  if (!speaker) return;

  const images = await _buildSpeakerImageList(speaker);
  if (images.length <= 1) return;

  const currentIdx = images.indexOf(speaker.imagePath || images[0]);
  const nextImg = images[(currentIdx + 1) % images.length];
  await game.storyframe.socketManager.requestSetSpeakerImage(speakerId, nextImg);
  _updateSpeakerImageInDOM(speakerId, nextImg);
}

/**
 * Cycle to previous alt image for a speaker
 */
export async function onCycleSpeakerImagePrev(event, target) {
  event.stopPropagation();
  const speakerId = target.closest('[data-speaker-id]')?.dataset.speakerId;
  if (!speakerId) return;

  const state = game.storyframe.stateManager.getState();
  const speaker = state?.speakers?.find((s) => s.id === speakerId);
  if (!speaker) return;

  const images = await _buildSpeakerImageList(speaker);
  if (images.length <= 1) return;

  const currentIdx = images.indexOf(speaker.imagePath || images[0]);
  const prevImg = images[(currentIdx - 1 + images.length) % images.length];
  await game.storyframe.socketManager.requestSetSpeakerImage(speakerId, prevImg);
  _updateSpeakerImageInDOM(speakerId, prevImg);
}

/**
 * Add a custom alternate image to a speaker via file picker
 */
export async function onAddSpeakerAltImage(event, target) {
  event.stopPropagation();
  const speakerId = target.closest('[data-speaker-id]')?.dataset.speakerId;
  if (!speakerId) return;

  new FilePicker({
    type: 'image',
    callback: async (path) => {
      await game.storyframe.socketManager.requestAddSpeakerAltImage(speakerId, path);
    },
  }).render(true);
}

/**
 * Remove the current custom alternate image from a speaker
 */
export async function onRemoveSpeakerAltImage(event, target) {
  event.stopPropagation();
  const speakerId = target.closest('[data-speaker-id]')?.dataset.speakerId;
  const img = target.closest('[data-image-path]')?.dataset.imagePath;
  if (!speakerId || !img) return;

  await game.storyframe.socketManager.requestRemoveSpeakerAltImage(speakerId, img);
}

/**
 * Clear the active speaker
 */
export async function onClearSpeaker(_event, _target) {
  await game.storyframe.socketManager.requestSetActiveSpeaker(null);
  _updateActiveSpeakerInDOM(null);
}

/**
 * Clear all speakers
 */
export async function onClearAllSpeakers(_event, _target) {
  const state = game.storyframe.stateManager.getState();
  if (!state?.speakers?.length) return;

  const confirmed = await foundry.applications.api.DialogV2.confirm({
    window: { title: game.i18n.localize('STORYFRAME.Dialogs.ClearAllNPCs.Title') },
    content: '<p>Remove all NPCs?</p>',
    yes: { label: game.i18n.localize('STORYFRAME.Dialogs.ClearAllPCs.Button') },
    no: { label: game.i18n.localize('STORYFRAME.Dialogs.Cancel'), default: true },
    rejectClose: false,
  });

  if (confirmed) {
    await game.storyframe.socketManager.requestUpdateSpeakers([]);
    game.storyframe.gmSidebar?.render();
  }
}

/**
 * Set an image from journal as speaker
 */
export async function onSetImageAsSpeaker(_event, target, _sidebar) {
  const imageSrc = target.dataset.imageSrc;
  if (!imageSrc) return;

  const defaultName = nameFromPath(imageSrc);
  const label = await foundry.applications.api.DialogV2.prompt({
    window: { title: game.i18n.localize('STORYFRAME.Dialogs.EnterNPCName.Title') },
    content: `<input type="text" name="label" value="${defaultName}" placeholder="${game.i18n.localize('STORYFRAME.Dialogs.EnterNPCName.Label')}" autofocus>`,
    ok: {
      label: game.i18n.localize('STORYFRAME.Dialogs.AddSpeaker.Button'),
      callback: (event, button, _dialog) => button.form.elements.label.value,
    },
    rejectClose: false,
  });

  if (label) {
    await game.storyframe.socketManager.requestAddSpeaker({
      imagePath: imageSrc,
      label,
    });
  }
}

/**
 * Set an actor from journal as speaker
 */
export async function onSetActorAsSpeaker(event, target, _sidebar) {
  const actorId = target.dataset.actorId;
  if (!actorId) return;

  const actor = game.actors.get(actorId);
  if (!actor) {
    ui.notifications.error(game.i18n.localize('STORYFRAME.Notifications.Scene.ActorNotFound'));
    return;
  }

  // Check if ALT key is held to hide name from players
  const isNameHidden = event.altKey;

  // Token image is derived dynamically at render time — no need to store it
  await game.storyframe.socketManager.requestAddSpeaker({
    actorUuid: actor.uuid,
    imagePath: actor.img,
    label: actor.name,
    isNameHidden,
  });
}

/**
 * Extract images from parent journal content
 */
export function extractJournalImages(sidebar) {
  const element = extractParentElement(sidebar.parentInterface);
  if (!element) return [];

  const isMEJ = sidebar.parentInterface.constructor.name === 'EnhancedJournal';

  // Get ALL page content elements (supports multi-page view)
  let contentElements = element.querySelectorAll('.journal-page-content');

  // Fallback for Monk's Enhanced Journal (.content > section)
  if (contentElements.length === 0) {
    contentElements = element.querySelectorAll('.enhanced-journal .content > section');
  }

  // Broader MEJ fallback — try form.content or just section inside content
  if (contentElements.length === 0 && isMEJ) {
    contentElements = element.querySelectorAll('form.content > section');
  }
  if (contentElements.length === 0 && isMEJ) {
    contentElements = element.querySelectorAll('.enhanced-journal-body .content');
  }

  if (contentElements.length === 0) return [];

  // Get current speakers to filter out (normalize paths for comparison)
  const state = game.storyframe.stateManager.getState();
  const speakerImages = new Set();
  if (state?.speakers) {
    state.speakers.forEach((speaker) => {
      if (speaker.imagePath) speakerImages.add(normalizeImagePath(speaker.imagePath));
    });
  }

  const images = [];

  // Query images from ALL page content elements
  contentElements.forEach((content) => {
    const imgElements = content.querySelectorAll('img');
    imgElements.forEach((img) => {
      const normalizedSrc = normalizeImagePath(img.src);
      if (img.src && !img.src.includes('icons/svg/mystery-man') && !speakerImages.has(normalizedSrc)) {
        images.push({
          src: img.src,
          alt: img.alt || 'Image',
          id: foundry.utils.randomID(),
        });
      }
    });
  });

  // Remove duplicates by src
  const unique = [];
  const seen = new Set();
  images.forEach((img) => {
    if (!seen.has(img.src)) {
      seen.add(img.src);
      unique.push(img);
    }
  });

  return unique;
}

/**
 * Extract actors from parent journal content
 */
export function extractJournalActors(sidebar) {
  const element = extractParentElement(sidebar.parentInterface);
  if (!element) return [];

  const isMEJ = sidebar.parentInterface.constructor.name === 'EnhancedJournal';

  // Get ALL page content elements (supports multi-page view)
  let contentElements = element.querySelectorAll('.journal-page-content');
  // Fallback for Monk's Enhanced Journal (.content > section)
  if (contentElements.length === 0) {
    contentElements = element.querySelectorAll('.enhanced-journal .content > section');
  }
  // Broader MEJ fallbacks
  if (contentElements.length === 0 && isMEJ) {
    contentElements = element.querySelectorAll('form.content > section');
  }
  if (contentElements.length === 0 && isMEJ) {
    contentElements = element.querySelectorAll('.enhanced-journal-body .content');
  }
  if (contentElements.length === 0) return [];

  // Get current speakers to filter out (extract actor IDs for comparison)
  // This handles UUID format mismatches (e.g., "Actor.id" vs "Compendium.xxx.Actor.id")
  const state = game.storyframe.stateManager.getState();
  const speakerActorIds = new Set();
  if (state?.speakers) {
    state.speakers.forEach((speaker) => {
      if (speaker.actorUuid) {
        const actorId = extractActorIdFromUuid(speaker.actorUuid);
        if (actorId) speakerActorIds.add(actorId);
      }
    });
  }

  const actors = [];

  // Query actor links from ALL page content elements
  contentElements.forEach((content) => {
    // Find all content links that reference actors
    // Handles both @Actor[id] and @UUID[Actor.id] formats
    const actorLinks = content.querySelectorAll('a.content-link[data-uuid^="Actor."], a.content-link[data-uuid*="Actor."]');

    actorLinks.forEach((link) => {
      const uuid = link.dataset.uuid;

      // Extract actor ID from UUID (handles both "Actor.id" and full UUIDs)
      const actorId = extractActorIdFromUuid(uuid);

      if (!actorId) return;

      const actorName = link.textContent || 'Unknown';

      // Compare by actor ID to handle UUID format mismatches
      if (!speakerActorIds.has(actorId)) {
        const actor = game.actors.get(actorId);
        if (actor && actor.type !== 'loot' && actor.type !== 'hazard') {
          actors.push({
            id: actorId,
            name: actorName,
            img: actor.img,
          });
        }
      }
    });
  });

  // Remove duplicates by id
  const unique = [];
  const seen = new Set();
  actors.forEach((actor) => {
    if (!seen.has(actor.id)) {
      seen.add(actor.id);
      unique.push(actor);
    }
  });

  return unique;
}

/**
 * Show enlarged image in a popup
 */
export function showEnlargedImage(src, name) {
  // Remove existing popup if any
  document.querySelector('.storyframe-image-popup')?.remove();

  const popup = document.createElement('div');
  popup.className = 'storyframe-image-popup';
  popup.innerHTML = `
    <div class="popup-backdrop"></div>
    <div class="popup-content">
      <img src="${src}" alt="${name}">
      <div class="popup-name">${name}</div>
    </div>
  `;

  // Close on click anywhere
  popup.addEventListener('click', () => popup.remove());

  // Close on escape key
  const escHandler = (e) => {
    if (e.key === 'Escape') {
      popup.remove();
      document.removeEventListener('keydown', escHandler);
    }
  };
  document.addEventListener('keydown', escHandler);

  document.body.appendChild(popup);
}

/**
 * Attach right-click context menu for image enlargement
 */
export function attachImageContextMenu(sidebar) {
  // NPC speaker thumbnails
  const images = sidebar.element.querySelectorAll('.speaker-thumbnail img');
  images.forEach((img) => {
    img.addEventListener('contextmenu', (e) => {
      e.preventDefault();
      e.stopPropagation();
      const name =
        img.alt ||
        img.closest('.speaker-thumbnail')?.querySelector('.speaker-name')?.textContent ||
        'NPC';
      showEnlargedImage(img.src, name);
    });
  });

  // Journal images
  const journalImages = sidebar.element.querySelectorAll('.journal-image-item-thumb img');
  journalImages.forEach((img) => {
    img.addEventListener('contextmenu', (e) => {
      e.preventDefault();
      e.stopPropagation();
      const name = img.alt || 'Image';
      showEnlargedImage(img.src, name);
    });
  });

  // Journal actors
  const journalActors = sidebar.element.querySelectorAll('.journal-actor-item-thumb img');
  journalActors.forEach((img) => {
    img.addEventListener('contextmenu', (e) => {
      e.preventDefault();
      e.stopPropagation();
      const name = img.alt || 'Actor';
      showEnlargedImage(img.src, name);
    });
  });
}

/**
 * Attach right-click handler to open player windows button (to close)
 */
export function attachPlayerWindowsContextMenu(sidebar) {
  const btn = sidebar.element.querySelector('[data-action="openPlayerWindows"]');
  if (btn) {
    btn.addEventListener('contextmenu', (e) => {
      e.preventDefault();
      e.stopPropagation();
      onClosePlayerWindows(e, btn, sidebar);
    });
  }
}

export function attachPlayerSidebarsContextMenu(sidebar) {
  const btn = sidebar.element.querySelector('[data-action="openPlayerSidebars"]');
  if (btn) {
    btn.addEventListener('contextmenu', (e) => {
      e.preventDefault();
      e.stopPropagation();
      onClosePlayerSidebars(e, btn, sidebar);
    });
  }
}

/**
 * Open player windows
 */
export async function onOpenPlayerWindows(_event, _target, sidebar) {
  game.storyframe.socketManager.openAllPlayerViewers();
  ui.notifications.info(game.i18n.localize('STORYFRAME.Notifications.Speaker.OpeningForPlayers'));
  const btn = sidebar?.element?.querySelector('[data-action="openPlayerWindows"]');
  if (btn) btn.classList.add('active');
}

/**
 * Close player windows
 */
export async function onClosePlayerWindows(_event, _target, sidebar) {
  game.storyframe.socketManager.closeAllPlayerViewers();
  ui.notifications.info(game.i18n.localize('STORYFRAME.Notifications.Speaker.ClosingForPlayers'));
  const btn = sidebar?.element?.querySelector('[data-action="openPlayerWindows"]');
  if (btn) btn.classList.remove('active');
}

/**
 * Open player sidebars
 */
export async function onOpenPlayerSidebars(_event, _target, sidebar) {
  game.storyframe.socketManager.openAllPlayerSidebars();
  ui.notifications.info(game.i18n.localize('STORYFRAME.Notifications.Speaker.OpeningSidebarsForPlayers'));
  const btn = sidebar?.element?.querySelector('[data-action="openPlayerSidebars"]');
  if (btn) btn.classList.add('active');
}

/**
 * Close player sidebars
 */
export async function onClosePlayerSidebars(_event, _target, sidebar) {
  game.storyframe.socketManager.closeAllPlayerSidebars();
  ui.notifications.info(game.i18n.localize('STORYFRAME.Notifications.Speaker.ClosingSidebarsForPlayers'));
  const btn = sidebar?.element?.querySelector('[data-action="openPlayerSidebars"]');
  if (btn) btn.classList.remove('active');
}

/**
 * Open the Scene Gatherer dialog to gather tokens from the current FoundryVTT scene
 */
export async function onGatherScene(_event, _target, sidebar) {
  const { SceneGathererDialog } = await import('../../scene-gatherer-dialog.mjs');
  const result = await SceneGathererDialog.open();

  if (result) {
    sidebar.render();
  }
}

// --- Helper Functions ---

/**
 * Normalize an image path for comparison by extracting just the path portion.
 * Handles both relative paths (modules/foo/bar.webp) and absolute URLs (http://localhost/modules/foo/bar.webp).
 * @param {string} path - The image path or URL
 * @returns {string} - Normalized path for comparison
 */
function normalizeImagePath(path) {
  if (!path) return '';
  try {
    // If it's a full URL, extract pathname
    const url = new URL(path, window.location.origin);
    // Remove leading slash for consistent comparison
    return url.pathname.replace(/^\//, '');
  } catch {
    // If URL parsing fails, just clean up the path
    return path.replace(/^\//, '');
  }
}

/**
 * Extract actor ID from a UUID string.
 * Handles both "Actor.id" and "Compendium.module.pack.Actor.id" formats.
 * @param {string} uuid - The UUID string
 * @returns {string|null} - The actor ID or null if not found
 */
function extractActorIdFromUuid(uuid) {
  if (!uuid || !uuid.includes('Actor.')) return null;
  return uuid.split('Actor.')[1]?.split('.')[0] || null;
}

/**
 * Save current speakers as a scene
 */
export async function onSaveCurrentSpeakers(_event, _target, sidebar) {
  const state = game.storyframe.stateManager.getState();
  const speakers = state?.speakers || [];

  if (speakers.length === 0) {
    ui.notifications.warn(game.i18n.localize('STORYFRAME.Notifications.Speaker.NoSpeakersToSave'));
    return;
  }

  // Get journal element for images/actors if available
  const journalElement = document.querySelector('.journal-entry-pages');

  // Open scene editor with current speakers
  const { showSceneEditor } = await import('../../../scene-editor.mjs');
  await showSceneEditor({
    speakers: [...speakers], // Clone speakers array
    journalElement,
  });

  // Re-render sidebar to show manage button if needed
  sidebar.render();
}

/**
 * Manage speaker scenes - delegates to UIHelpers popup
 */
export async function onManageScenes(event, target, sidebar) {
  // Import UIHelpers dynamically to avoid circular dependencies
  const UIHelpers = await import('./ui-helpers.mjs');
  await UIHelpers.onShowSavedScenes(event, target, sidebar);
}
