import {
  muteDurationToIso,
  setConversationArchived,
  setConversationMutedUntil,
} from '@chat-app/shared/chat';
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';

import { supabase } from '../lib/supabase';
import { ArchiveIcon, BellIcon, BellOffIcon, MoreVerticalIcon } from './icons';

interface Props {
  conversationId: string;
  archived: boolean;
  mutedUntil: string | null;
}

interface MuteOption {
  key: string;
  labelKey: string;
  labelDefault: string;
  minutes: number | null;
}

// Muted-forever sentinel ≈ 100 years. UI treats any future timestamp as muted
// until that moment; 100y is indistinguishable from "forever" at the UX level
// without requiring a dedicated `bool muted_forever` column.
const FOREVER_MINUTES = 100 * 365 * 24 * 60;

const MUTE_OPTIONS: MuteOption[] = [
  { key: '1h', labelKey: 'app:chats.mute_1h', labelDefault: '1 Stunde', minutes: 60 },
  { key: '8h', labelKey: 'app:chats.mute_8h', labelDefault: '8 Stunden', minutes: 8 * 60 },
  { key: '24h', labelKey: 'app:chats.mute_24h', labelDefault: '24 Stunden', minutes: 24 * 60 },
  { key: '1w', labelKey: 'app:chats.mute_1w', labelDefault: '1 Woche', minutes: 7 * 24 * 60 },
  {
    key: 'forever',
    labelKey: 'app:chats.mute_forever',
    labelDefault: 'Bis auf Weiteres',
    minutes: FOREVER_MINUTES,
  },
];

interface MenuPos {
  top: number;
  left: number;
}

// Per-conversation row context-menu. Renders via portal so the submenu can
// escape the sidebar's `overflow-y-auto` clipping context. Position is
// computed from the trigger's bounding rect — menu anchors right-aligned
// under the trigger so it doesn't push off-screen on narrow windows.
export function ConversationRowMenu({ conversationId, archived, mutedUntil }: Props) {
  const { t } = useTranslation(['app']);
  const [open, setOpen] = useState(false);
  const [submenuOpen, setSubmenuOpen] = useState<'mute' | null>(null);
  const [menuPos, setMenuPos] = useState<MenuPos | null>(null);
  const [submenuPos, setSubmenuPos] = useState<MenuPos | null>(null);
  const triggerRef = useRef<HTMLButtonElement>(null);
  const menuRef = useRef<HTMLDivElement>(null);
  const muteItemRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    if (!open) return;
    function onDocClick(e: MouseEvent) {
      const target = e.target as Node;
      if (triggerRef.current?.contains(target)) return;
      if (menuRef.current?.contains(target)) return;
      setOpen(false);
      setSubmenuOpen(null);
    }
    function onEsc(e: KeyboardEvent) {
      if (e.key === 'Escape') {
        setOpen(false);
        setSubmenuOpen(null);
      }
    }
    document.addEventListener('mousedown', onDocClick);
    document.addEventListener('keydown', onEsc);
    return () => {
      document.removeEventListener('mousedown', onDocClick);
      document.removeEventListener('keydown', onEsc);
    };
  }, [open]);

  useEffect(() => {
    if (!open) {
      setMenuPos(null);
      setSubmenuPos(null);
      return;
    }
    const rect = triggerRef.current?.getBoundingClientRect();
    if (!rect) return;
    // Anchor: right edge aligns with trigger's right edge, menu hangs below.
    const menuWidth = 208;
    setMenuPos({
      top: rect.bottom + 4,
      left: Math.max(8, rect.right - menuWidth),
    });
  }, [open]);

  useEffect(() => {
    if (submenuOpen !== 'mute') {
      setSubmenuPos(null);
      return;
    }
    const rect = muteItemRef.current?.getBoundingClientRect();
    if (!rect) return;
    const submenuWidth = 192;
    const viewportWidth = window.innerWidth;
    // Prefer right of the item. Flip to left when it would overflow viewport.
    const wantLeft = rect.right + 4;
    const flip = wantLeft + submenuWidth > viewportWidth - 8;
    setSubmenuPos({
      top: rect.top,
      left: flip ? rect.left - submenuWidth - 4 : wantLeft,
    });
  }, [submenuOpen]);

  const isMuted =
    mutedUntil !== null && new Date(mutedUntil).getTime() > Date.now();

  const handleArchive = useCallback(
    async (next: boolean) => {
      setOpen(false);
      try {
        await setConversationArchived(supabase, conversationId, next);
      } catch (err: unknown) {
        console.error('archive toggle failed', err);
      }
    },
    [conversationId],
  );

  const handleMute = useCallback(
    async (minutes: number | null) => {
      setOpen(false);
      setSubmenuOpen(null);
      try {
        await setConversationMutedUntil(supabase, conversationId, muteDurationToIso(minutes));
      } catch (err: unknown) {
        console.error('mute toggle failed', err);
      }
    },
    [conversationId],
  );

  return (
    <>
      <button
        ref={triggerRef}
        type="button"
        aria-label={t('app:chats.row_menu', { defaultValue: 'Aktionen' })}
        onClick={(e) => {
          e.preventDefault();
          e.stopPropagation();
          setOpen((v) => !v);
        }}
        onMouseDown={(e) => e.stopPropagation()}
        className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md text-fg-muted opacity-0 transition group-hover:opacity-100 hover:bg-surface-3 hover:text-fg focus:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 dark:hover:bg-[#383a40]"
      >
        <MoreVerticalIcon className="h-4 w-4" />
      </button>

      {open &&
        menuPos &&
        createPortal(
          <div
            ref={menuRef}
            role="menu"
            style={{ top: menuPos.top, left: menuPos.left }}
            className="fixed z-50 w-52 rounded-lg border border-line bg-surface-3 p-1 shadow-xl dark:bg-[#313338]"
          >
            <MenuItem
              icon={<ArchiveIcon className="h-4 w-4" />}
              label={
                archived
                  ? t('app:chats.unarchive', { defaultValue: 'Entarchivieren' })
                  : t('app:chats.archive', { defaultValue: 'Archivieren' })
              }
              onClick={() => void handleArchive(!archived)}
            />
            <MenuItem
              ref={muteItemRef}
              icon={
                isMuted ? <BellOffIcon className="h-4 w-4" /> : <BellIcon className="h-4 w-4" />
              }
              label={
                isMuted
                  ? t('app:chats.unmute', { defaultValue: 'Stummschaltung aufheben' })
                  : t('app:chats.mute', { defaultValue: 'Stummschalten' })
              }
              onClick={() => {
                if (isMuted) void handleMute(null);
                else setSubmenuOpen((v) => (v === 'mute' ? null : 'mute'));
              }}
              hasSubmenu={!isMuted}
            />
          </div>,
          document.body,
        )}

      {open &&
        submenuOpen === 'mute' &&
        submenuPos &&
        createPortal(
          <div
            role="menu"
            style={{ top: submenuPos.top, left: submenuPos.left }}
            className="fixed z-50 w-48 rounded-lg border border-line bg-surface-3 p-1 shadow-xl dark:bg-[#313338]"
          >
            {MUTE_OPTIONS.map((opt) => (
              <MenuItem
                key={opt.key}
                label={t(opt.labelKey, { defaultValue: opt.labelDefault })}
                onClick={() => void handleMute(opt.minutes)}
              />
            ))}
          </div>,
          document.body,
        )}
    </>
  );
}

interface MenuItemProps {
  icon?: React.ReactNode;
  label: string;
  onClick: () => void;
  hasSubmenu?: boolean;
}

// React 18 requires forwardRef for function components to receive refs —
// without it the `ref` prop is stripped before reaching the component and
// measurement-dependent submenus never position.
const MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(
  ({ icon, label, onClick, hasSubmenu }, ref) => (
    <button
      ref={ref}
      type="button"
      role="menuitem"
      onClick={(e) => {
        e.preventDefault();
        e.stopPropagation();
        onClick();
      }}
      onMouseDown={(e) => e.stopPropagation()}
      className="flex w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left text-sm text-fg transition hover:bg-surface-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 dark:hover:bg-[#383a40]"
    >
      {icon && <span className="shrink-0 text-fg-muted">{icon}</span>}
      <span className="min-w-0 flex-1 truncate">{label}</span>
      {hasSubmenu && <span className="shrink-0 text-xs text-fg-muted">›</span>}
    </button>
  ),
);
MenuItem.displayName = 'MenuItem';
