// Per-submission track-context menu. Lives behind a kebab button on
// every track row. The menu's contents adapt to the viewer's role:
//
//   • Everyone (logged in) — "Künstler öffnen" + "Zu Playlist
//     hinzufügen" (with inline submenu of the user's playlists).
//   • Admins additionally see — status overrides, copyright skip,
//     metadata edits, regenerate lyrics/waveform, cover swap,
//     delete. Everything that mutates the track itself.
//
// All actions go through the same shared `guarded()` handler which
// confirms + refreshes + toasts. The menu renders into <body> via
// createPortal so it escapes any ancestor overflow:hidden — the
// submission card clips its cover via overflow:hidden, we don't
// want it clipping our dropdown too.
//
// (Name kept as "AdminMenu" for stability of imports — the visible
// behaviour is now broader than admins-only.)

function AdminMenu({ sub, refresh }) {
  const [open, setOpen] = React.useState(false);
  // Two views inside the menu: the main list, and a playlist-picker
  // submenu the user enters by clicking "Zu Playlist hinzufügen".
  // Reset to 'main' every time the menu reopens.
  const [view, setView] = React.useState('main');
  React.useEffect(() => { if (!open) setView('main'); }, [open]);

  // Role lookup. Admins get the full menu; everyone else gets the
  // small navigation + playlist subset.
  const me = typeof Store !== 'undefined' ? Store.get().me : null;
  const isAdmin = me?.group === 'admin';
  const playlists = useMyPlaylists();
  // Pre-crop image for the "Cover ändern" flow. Picking a file from
  // the hidden <input> sets this; the ImageCropper modal renders
  // while it's non-null and clears it on cancel or after a successful
  // upload below. We use a ref on the input + programmatic .click()
  // because a <label>-wrapped <button> doesn't reliably open the
  // native file picker in all browsers.
  const [rawCoverFile, setRawCoverFile] = React.useState(null);
  const coverInputRef = React.useRef(null);
  // Anchor rect — recomputed every time the menu opens (and on
  // scroll/resize while open) so the dropdown stays glued to the
  // kebab even if the page below it shifts.
  const [rect, setRect] = React.useState(null);
  const buttonRef = React.useRef(null);
  const menuRef = React.useRef(null);

  const reposition = React.useCallback(() => {
    if (!buttonRef.current) return;
    setRect(buttonRef.current.getBoundingClientRect());
  }, []);

  // Click-outside closes. Both refs count as "inside" — the button
  // for toggle support, the menu so a click inside it doesn't auto-
  // close before the action handler fires.
  React.useEffect(() => {
    if (!open) return;
    reposition();
    const onClick = (e) => {
      if (buttonRef.current?.contains(e.target)) return;
      if (menuRef.current?.contains(e.target)) return;
      setOpen(false);
    };
    const onScroll = () => reposition();
    setTimeout(() => document.addEventListener('click', onClick), 0);
    window.addEventListener('scroll', onScroll, true);
    window.addEventListener('resize', onScroll);
    return () => {
      document.removeEventListener('click', onClick);
      window.removeEventListener('scroll', onScroll, true);
      window.removeEventListener('resize', onScroll);
    };
  }, [open, reposition]);

  const guarded = (label, fn) => async () => {
    setOpen(false);
    if (!confirm(label)) return;
    try {
      await fn();
      // `refresh` is optional — non-admin contexts (Profile's
      // Recents/Likes lists) don't pass it, and the actions
      // available to non-admins (playlist-add, künstler-open)
      // don't mutate track state anyway. Only admins reach the
      // guarded branches that NEED a refresh.
      if (refresh) await refresh();
    } catch (e) { toast.error(e.message); }
  };

  const onDelete = guarded(
    'Diesen Track KOMPLETT löschen?\n\nDB-Eintrag, CDN-Dateien und (falls veröffentlicht) der Eintrag im FiveM-Katalog werden entfernt. Nicht rückgängig.',
    async () => {
      await Api.deleteSubmission(sub.id);
      AudioService.stop();
      toast.success('Track gelöscht.');
    },
  );

  const overrideTo = (target, label) => guarded(
    `Status auf „${label}" ändern?` + (target === 'pending' ? '\n\nDer Track geht zurück in die Prüfung.' : ''),
    async () => {
      if (AudioService.get().currentId === sub.id) AudioService.stop();
      await Api.overrideStatus(sub.id, target);
      toast.success(`Status: ${label}.`);
    },
  );

  const onCopyrightSkip = guarded(
    'AudD-Copyright-Check für diesen Track überspringen?\n\n' +
    'Falls der Track noch geprüft wird, geht er sofort in die Voting-Queue.\n' +
    'Falls er bereits geflaggt war, wird er zurück in die Queue gestellt.\n\n' +
    'Damit übernimmst du als Admin die rechtliche Verantwortung.',
    async () => {
      await Api.copyrightSkip(sub.id);
      toast.success('Copyright-Check übersprungen.');
    },
  );

  const onRegenLyrics = guarded(
    'Lyrics neu generieren?\n\nDer alte Songtext wird verworfen, der Track wandert in die Transkriptions-Warteschlange. Die neue Version erscheint in 15-60 Sekunden ingame.',
    async () => {
      await Api.regenerateLyrics(sub.id);
      // Bust the BigPlayer's per-track lyrics cache so the next
      // open of the player shows the fresh transcription rather
      // than the stale one we may have shown earlier.
      window.invalidateLyrics?.(sub.id);
      toast.success('Lyrics werden neu generiert.');
    },
  );

  const onRegenWaveform = guarded(
    'Waveform neu generieren?\n\nDer Track wird vom CDN nachgeladen und neu vermessen. Dauert 1-3 Sekunden.',
    async () => {
      await Api.regenerateWaveform(sub.id);
      toast.success('Waveform aktualisiert.');
    },
  );

  // Add this track to one of the user's playlists. Picks via the
  // inline submenu (view === 'playlists') so we don't have to spawn
  // a second popover. After a successful add the menu closes and a
  // toast confirms.
  const onAddToPlaylist = async (playlistId, playlistName) => {
    setOpen(false);
    try {
      await MyPlaylists.addTrack(playlistId, sub.id);
      toast.success(`Zu „${playlistName}" hinzugefügt.`);
    } catch (e) {
      toast.error('Hinzufügen fehlgeschlagen: ' + e.message);
    }
  };

  // Inline "neue Playlist" flow from the picker submenu. Creates,
  // then immediately adds the current track to it.
  const onCreatePlaylistAndAdd = async () => {
    setOpen(false);
    const name = prompt('Neue Playlist · wie soll sie heißen?');
    if (!name || !name.trim()) return;
    try {
      const fresh = await MyPlaylists.create(name.trim());
      const created = (fresh || []).find(p => p.name === name.trim());
      if (created) {
        await MyPlaylists.addTrack(created.id, sub.id);
        toast.success(`Playlist „${name.trim()}" angelegt mit Track.`);
      }
    } catch (e) {
      toast.error('Anlegen fehlgeschlagen: ' + e.message);
    }
  };

  const onEdit = async () => {
    setOpen(false);
    const newTitle  = prompt('Neuer Titel:', sub.title);
    if (newTitle === null) return;
    const newArtist = prompt('Neuer Artist-Name:', sub.artist);
    if (newArtist === null) return;
    const fields = {};
    if (newTitle.trim()  && newTitle.trim()  !== sub.title)  fields.title  = newTitle.trim();
    if (newArtist.trim() && newArtist.trim() !== sub.artist) fields.artist = newArtist.trim();
    if (Object.keys(fields).length === 0) return;
    try {
      await Api.editSubmission(sub.id, fields);
      await refresh();
      toast.success('Metadaten aktualisiert.');
    } catch (e) {
      toast.error(e.message);
    }
  };

  const MENU_WIDTH = 240;
  // Estimate menu height so we can decide whether to open downward or
  // flip upward. The menu's intrinsic height depends on how many
  // status-override items render (varies with sub.status) plus whether
  // the copyright section is visible. Roughly: 9 items × 36px + 4
  // label rows × 24px + 4 dividers × 9px + padding ≈ 460. Slight
  // over-estimate is fine — better to flip a hair too eagerly than
  // to overflow.
  const MENU_HEIGHT_ESTIMATE = 460;
  const flipUp = rect &&
    rect.bottom + 6 + MENU_HEIGHT_ESTIMATE > window.innerHeight &&
    // Only flip if there's MORE space above than below (otherwise the
    // upward menu would also clip — better to leave it downward and
    // let the user scroll).
    rect.top > window.innerHeight - rect.bottom;

  const menu = open && rect ? (
    <div
      ref={menuRef}
      style={{
        position: 'fixed',
        // Right-aligned to the kebab, clamped to 8px from viewport edge.
        left:  Math.max(8, rect.right - MENU_WIDTH),
        // Anchor to bottom when flipping so the menu grows upward from
        // just above the kebab. Anchor to top normally.
        ...(flipUp
          ? { bottom: window.innerHeight - rect.top + 6 }
          : { top:    rect.bottom + 6 }),
        width: MENU_WIDTH,
        zIndex: 1000,
        background: 'rgba(28,14,55,0.94)',
        backdropFilter: 'blur(20px) saturate(140%)',
        WebkitBackdropFilter: 'blur(20px) saturate(140%)',
        border: '1px solid var(--border-medium)',
        borderRadius: 14, padding: 6,
        boxShadow: 'var(--shadow-pop)',
        display: 'flex', flexDirection: 'column', gap: 2,
      }}
    >
      {view === 'playlists' ? (
        // ─── Playlist-picker submenu ───────────────────────────
        // Reached via "Zu Playlist hinzufügen" on the main view.
        // Lists the user's playlists + a "neue Playlist" footer.
        // "Zurück" item at the top returns to the main menu.
        <>
          <MenuItem icon="chevron-left" color="var(--fg-3)"
            onClick={() => setView('main')}>
            Zurück
          </MenuItem>
          <MenuDivider/>
          <MenuLabel>Playlist wählen</MenuLabel>
          {/* Scroll the playlist list when the user has more than
              fits — keeps the menu height bounded even with dozens
              of playlists. The "Neue Playlist…" item below the
              divider stays visible (outside the scroll area). */}
          <div style={{ maxHeight: 240, overflowY: 'auto', minHeight: 0 }}>
            {playlists.length === 0 ? (
              <div style={{ fontSize: 12, color: 'var(--fg-3)', padding: '6px 10px', textAlign: 'center' }}>
                Noch keine Playlists.
              </div>
            ) : playlists.map(p => (
              <MenuItem key={p.id} icon="list" color="var(--lila-300)"
                onClick={() => onAddToPlaylist(p.id, p.name)}>
                <span style={{ flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                  {p.name}
                </span>
                <span style={{ fontSize: 10.5, color: 'var(--fg-4)', fontVariantNumeric: 'tabular-nums' }}>
                  {p.trackCount}
                </span>
              </MenuItem>
            ))}
          </div>
          <MenuDivider/>
          <MenuItem icon="plus" color="var(--success)" onClick={onCreatePlaylistAndAdd}>
            Neue Playlist…
          </MenuItem>
        </>
      ) : (
        // ─── Main view ─────────────────────────────────────────
        <>
          {/* Always shown — Navigation block. The artist-link gives
              regular users a one-click path to the uploader's
              profile; the playlist-add is the single most-used
              non-admin action. */}
          <MenuLabel>Aktionen</MenuLabel>
          <MenuItem icon="user" color="var(--lila-300)"
            onClick={() => { setOpen(false); window.openArtist?.(sub.identifier); }}>
            Künstler öffnen
          </MenuItem>
          <MenuItem icon="plus" color="var(--success)"
            onClick={() => setView('playlists')}>
            Zu Playlist hinzufügen
          </MenuItem>

          {/* Admin-only items below. Gated so non-admins get a clean
              short menu with just the two items above. */}
          {isAdmin && (
            <>
              <MenuDivider/>
              <MenuLabel>Status ändern</MenuLabel>
              {sub.status !== 'pending'  && <MenuItem icon="clock" color="var(--warning)" onClick={overrideTo('pending',  'In Prüfung')}>Zurück zur Prüfung</MenuItem>}
              {sub.status !== 'accepted' && <MenuItem icon="check" color="var(--success)" onClick={overrideTo('accepted', 'Veröffentlicht')}>Veröffentlichen</MenuItem>}
              {sub.status !== 'rejected' && <MenuItem icon="x"     color="var(--danger)"  onClick={overrideTo('rejected', 'Abgelehnt')}>Ablehnen</MenuItem>}

              {sub.copyrightStatus && sub.copyrightStatus !== 'clean' && (
                <>
                  <MenuDivider/>
                  <MenuLabel>Copyright</MenuLabel>
                  <MenuItem icon="shield" color="var(--warning)" onClick={onCopyrightSkip}>
                    Rechtlichen Check überspringen
                  </MenuItem>
                </>
              )}

              <MenuDivider/>
              <MenuLabel>Metadaten</MenuLabel>
              <MenuItem icon="pencil" color="var(--lila-300)" onClick={onEdit}>Titel / Artist bearbeiten</MenuItem>
              <MenuItem icon="image" color="var(--coral)"
                onClick={() => {
                  setOpen(false);
                  setTimeout(() => coverInputRef.current?.click(), 0);
                }}>
                Cover ändern
              </MenuItem>

              <MenuDivider/>
              <MenuLabel>Neu generieren</MenuLabel>
              <MenuItem icon="music" color="var(--info)"      onClick={onRegenLyrics}>Lyrics neu machen</MenuItem>
              <MenuItem icon="wave"  color="var(--lila-300)"  onClick={onRegenWaveform}>Waveform neu machen</MenuItem>

              <MenuDivider/>
              <MenuItem icon="x" color="var(--danger)" danger onClick={onDelete}>Komplett löschen</MenuItem>
            </>
          )}
        </>
      )}
    </div>
  ) : null;

  // After the cropper hands us the square WebP, upload it via the
  // admin cover-edit endpoint. Same toast/refresh dance as the other
  // guarded actions in this file.
  const onCoverCropped = async (cropped) => {
    setRawCoverFile(null);
    try {
      await Api.editCover(sub.id, cropped);
      await refresh();
      toast.success('Cover aktualisiert.');
    } catch (e) {
      toast.error('Cover-Update fehlgeschlagen: ' + e.message);
    }
  };

  return (
    <div style={{ position: 'relative', display: 'inline-flex' }}>
      <button
        ref={buttonRef}
        onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }}
        title="Aktionen"
        aria-label="Track-Aktionen"
        style={{
          width: 32, height: 32, borderRadius: 10,
          background: open ? 'rgba(255,194,103,0.18)' : 'rgba(196,154,255,0.08)',
          border: open ? '1px solid rgba(255,194,103,0.5)' : '1px solid var(--border-soft)',
          color: open ? 'var(--warning)' : 'var(--fg-2)',
          cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
          padding: 0,
        }}
      >
        <ApIcon name="ellipsis-vertical" size={14}/>
      </button>
      {menu && ReactDOM.createPortal(menu, document.body)}
      {/* Hidden file input — clicked programmatically by the "Cover
          ändern" menu item. Lives here (not inside the portal'd menu)
          so it's still in the DOM after the menu closes and the
          onChange can fire reliably. */}
      <input ref={coverInputRef} type="file" accept="image/*" hidden onChange={(e) => {
        const f = e.target.files?.[0];
        if (f) setRawCoverFile(f);
        e.target.value = '';
      }}/>
      {rawCoverFile && (
        <ImageCropper
          file={rawCoverFile}
          onCancel={() => setRawCoverFile(null)}
          onCrop={onCoverCropped}
        />
      )}
    </div>
  );
}

function MenuLabel({ children }) {
  return (
    <div style={{
      fontSize: 10, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase',
      color: 'var(--fg-4)', padding: '8px 10px 2px',
    }}>{children}</div>
  );
}

function MenuDivider() {
  return <div style={{ height: 1, background: 'var(--border-hairline)', margin: '4px 0' }}/>;
}

function MenuItem({ icon, color, danger, onClick, children }) {
  const [hover, setHover] = React.useState(false);
  return (
    <button
      onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '8px 10px', borderRadius: 10,
        background: hover
          ? (danger ? 'rgba(255,110,138,0.15)' : 'rgba(196,154,255,0.10)')
          : 'transparent',
        border: 'none', cursor: 'pointer',
        color: 'inherit', fontSize: 13, fontWeight: 500, textAlign: 'left',
        fontFamily: 'inherit',
      }}
    >
      <ApIcon name={icon} size={14} color={color}/>
      <span style={{ color: danger ? 'var(--danger)' : 'var(--foam)', flex: 1 }}>{children}</span>
    </button>
  );
}

window.AdminMenu = AdminMenu;
