import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import type { ThemeName, ThemeOption } from "../theme/themes";
import type { SessionSummary } from "../types";

const DRAWER_SWIPE_INTENT_PX = 12;
const DRAWER_SWIPE_CLOSE_THRESHOLD_PX = 52;
const DRAWER_SWIPE_DIRECTION_RATIO = 1.15;
const SESSION_DRAWER_MAX_WIDTH_PX = 336;
const SESSION_DRAWER_GUTTER_PX = 28;
const SESSION_LONG_PRESS_MS = 460;
const SESSION_LONG_PRESS_MOVE_PX = 14;

interface DrawerSwipeState {
  identifier: number;
  x: number;
  y: number;
}

function CloseIcon() {
  return (
    <svg viewBox="0 0 24 24" aria-hidden="true">
      <path
        d="M6 6L18 18M18 6L6 18"
        fill="none"
        stroke="currentColor"
        stroke-linecap="round"
        stroke-width="1.9"
      />
    </svg>
  );
}

function formatUpdatedAt(timestamp: string): string {
  const parsed = new Date(timestamp);
  if (Number.isNaN(parsed.getTime())) return "";
  return parsed.toLocaleString(undefined, {
    month: "short",
    day: "numeric",
    hour: "numeric",
    minute: "2-digit",
  });
}

function findTouchById(touches: TouchList, identifier: number): Touch | null {
  for (let i = 0; i < touches.length; i += 1) {
    const touch = touches.item(i);
    if (touch && touch.identifier === identifier) return touch;
  }
  return null;
}

function hasSwipeIntent(dx: number, dy: number): boolean {
  return Math.abs(dx) >= DRAWER_SWIPE_INTENT_PX || Math.abs(dy) >= DRAWER_SWIPE_INTENT_PX;
}

function isVerticalSwipeDominant(dx: number, dy: number): boolean {
  return Math.abs(dx) < Math.abs(dy) * DRAWER_SWIPE_DIRECTION_RATIO;
}

function getDrawerWidth(): number {
  const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 1;
  return Math.max(
    1,
    Math.min(SESSION_DRAWER_MAX_WIDTH_PX, viewportWidth - SESSION_DRAWER_GUTTER_PX),
  );
}

function useSessionDrawerCloseSwipe(open: boolean, onClose: () => void) {
  const [swipe, setSwipe] = useState<DrawerSwipeState | null>(null);
  const [closeProgress, setCloseProgress] = useState(0);

  const clearSwipe = useCallback(() => {
    setSwipe(null);
    setCloseProgress(0);
  }, []);

  useEffect(() => {
    if (!open) clearSwipe();
  }, [clearSwipe, open]);

  const onTouchStart = useCallback(
    (e: Event) => {
      if (!open) return;
      const te = e as TouchEvent;
      if (te.touches.length !== 1) return;
      const touch = te.touches.item(0);
      if (!touch) return;
      setSwipe({
        identifier: touch.identifier,
        x: touch.clientX,
        y: touch.clientY,
      });
    },
    [open],
  );

  const onTouchMove = useCallback(
    (e: Event) => {
      if (!open || !swipe) return;
      const te = e as TouchEvent;
      const touch = findTouchById(te.touches, swipe.identifier);
      if (!touch) return;

      const dx = swipe.x - touch.clientX;
      const dy = touch.clientY - swipe.y;
      if (!hasSwipeIntent(dx, dy)) return;
      if (isVerticalSwipeDominant(dx, dy) || dx <= 0) {
        clearSwipe();
        return;
      }

      if (te.cancelable) te.preventDefault();
      setCloseProgress(Math.max(0, Math.min(1, dx / getDrawerWidth())));
    },
    [clearSwipe, open, swipe],
  );

  const onTouchEnd = useCallback(
    (e: Event) => {
      if (!swipe) {
        clearSwipe();
        return;
      }

      const te = e as TouchEvent;
      const touch = findTouchById(te.changedTouches, swipe.identifier);
      if (!touch) {
        clearSwipe();
        return;
      }

      const dx = swipe.x - touch.clientX;
      const shouldClose = dx >= DRAWER_SWIPE_CLOSE_THRESHOLD_PX;
      clearSwipe();
      if (shouldClose) onClose();
    },
    [clearSwipe, onClose, swipe],
  );

  return {
    closeProgress,
    dragging: closeProgress > 0,
    onTouchStart,
    onTouchMove,
    onTouchEnd,
    onTouchCancel: clearSwipe,
  };
}

interface SessionDrawerProps {
  open: boolean;
  progress: number;
  dragging: boolean;
  sessions: SessionSummary[];
  activeSessionId: string;
  busy: boolean;
  onClose(): void;
  onCreate(): Promise<void> | void;
  onSelect(chatId: string): Promise<void> | void;
  onRename(chatId: string, title: string): Promise<void> | void;
  onDelete(chatId: string): Promise<void> | void;
  activeTheme: ThemeName;
  themeOptions: ThemeOption[];
  onSelectTheme(themeName: ThemeName): void;
}

function useMetaOverflow() {
  const ref = useRef<HTMLDivElement | null>(null);
  const [compact, setCompact] = useState(false);

  useEffect(() => {
    const node = ref.current;
    if (!node) return;

    let frame = 0;
    const measure = () => {
      frame = 0;
      setCompact((current) => {
        const tooWide = node.scrollWidth > node.clientWidth + 1;
        if (!current) return tooWide;

        // Hysteresis: once compact, require real spare room before switching back.
        const hasSpareRoom = node.scrollWidth <= node.clientWidth - 12;
        return !hasSpareRoom;
      });
    };
    const scheduleMeasure = () => {
      if (frame) cancelAnimationFrame(frame);
      frame = requestAnimationFrame(measure);
    };

    scheduleMeasure();
    const observer = new ResizeObserver(scheduleMeasure);
    observer.observe(node);

    return () => {
      if (frame) cancelAnimationFrame(frame);
      observer.disconnect();
    };
  }, []);

  return { ref, compact };
}

function SessionDrawerItem({
  session,
  active,
  busy,
  onSelect,
  onOpenMenu,
}: {
  session: SessionSummary;
  active: boolean;
  busy: boolean;
  onSelect(chatId: string): Promise<void> | void;
  onOpenMenu(session: SessionSummary): void;
}) {
  const meta = useMetaOverflow();
  const holdTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const holdStartRef = useRef<{ x: number; y: number } | null>(null);
  const longPressTriggeredRef = useRef(false);

  const clearHoldTimer = useCallback(() => {
    if (holdTimerRef.current) {
      clearTimeout(holdTimerRef.current);
      holdTimerRef.current = null;
    }
    holdStartRef.current = null;
  }, []);

  const scheduleLongPress = useCallback(
    (x: number, y: number) => {
      if (busy) return;
      longPressTriggeredRef.current = false;
      holdStartRef.current = { x, y };
      if (holdTimerRef.current) clearTimeout(holdTimerRef.current);
      holdTimerRef.current = setTimeout(() => {
        holdTimerRef.current = null;
        longPressTriggeredRef.current = true;
        onOpenMenu(session);
      }, SESSION_LONG_PRESS_MS);
    },
    [busy, onOpenMenu, session],
  );

  useEffect(() => clearHoldTimer, [clearHoldTimer]);

  return (
    <button
      class={`session-drawer-item${active ? " active" : ""}`}
      type="button"
      aria-disabled={busy ? "true" : undefined}
      onClick={(e) => {
        if (longPressTriggeredRef.current) {
          e.preventDefault();
          e.stopPropagation();
          longPressTriggeredRef.current = false;
          return;
        }
        if (busy || active) return;
        void onSelect(session.chat_id);
      }}
      onContextMenu={(e) => {
        e.preventDefault();
        if (busy) return;
        longPressTriggeredRef.current = true;
        onOpenMenu(session);
      }}
      onTouchStart={(e) => {
        if (e.touches.length !== 1) return;
        const touch = e.touches.item(0);
        if (!touch) return;
        scheduleLongPress(touch.clientX, touch.clientY);
      }}
      onTouchMove={(e) => {
        const touch = e.touches.item(0);
        const start = holdStartRef.current;
        if (!touch || !start) return;
        const dx = touch.clientX - start.x;
        const dy = touch.clientY - start.y;
        if (Math.hypot(dx, dy) > SESSION_LONG_PRESS_MOVE_PX) clearHoldTimer();
      }}
      onTouchEnd={() => {
        clearHoldTimer();
      }}
      onTouchCancel={() => {
        clearHoldTimer();
      }}
    >
      <div class="session-drawer-item-title">{session.title || "New conversation"}</div>
      {session.preview ? <div class="session-drawer-item-preview">{session.preview}</div> : null}
      <div ref={meta.ref} class={`session-drawer-item-meta${meta.compact ? " compact" : ""}`}>
        <span>{formatUpdatedAt(session.updated_at)}</span>
        <span>{session.message_count} msgs</span>
      </div>
    </button>
  );
}

function SessionActionMenu({
  session,
  busy,
  onClose,
  onRename,
  onDelete,
}: {
  session: SessionSummary;
  busy: boolean;
  onClose(): void;
  onRename(chatId: string, title: string): Promise<void> | void;
  onDelete(chatId: string): Promise<void> | void;
}) {
  return (
    <>
      <button
        id="session-action-backdrop"
        type="button"
        aria-label="Close conversation actions"
        onClick={onClose}
      />
      <div id="session-action-sheet" data-no-swipe="1">
        <div id="session-action-title">{session.title || "New conversation"}</div>
        <button
          class="session-action-btn"
          type="button"
          disabled={busy}
          onClick={() => {
            const nextTitle = window.prompt("Rename conversation", session.title || "");
            if (nextTitle === null) return;
            void onRename(session.chat_id, nextTitle.trim());
            onClose();
          }}
        >
          Rename
        </button>
        <button
          class="session-action-btn destructive"
          type="button"
          disabled={busy}
          onClick={() => {
            const confirmed = window.confirm(`Delete "${session.title || "New conversation"}"?`);
            if (!confirmed) return;
            void onDelete(session.chat_id);
            onClose();
          }}
        >
          Delete
        </button>
        <button class="session-action-btn" type="button" onClick={onClose}>
          Cancel
        </button>
      </div>
    </>
  );
}

function SessionThemePicker({
  activeTheme,
  themeOptions,
  onSelectTheme,
}: {
  activeTheme: ThemeName;
  themeOptions: ThemeOption[];
  onSelectTheme(themeName: ThemeName): void;
}) {
  return (
    <div id="session-drawer-theme">
      <div id="session-drawer-theme-label">Theme</div>
      <div id="session-drawer-theme-options">
        {themeOptions.map((theme) => (
          <button
            key={theme.name}
            type="button"
            class={`session-drawer-theme-option${theme.name === activeTheme ? " active" : ""}`}
            aria-pressed={theme.name === activeTheme}
            onClick={() => onSelectTheme(theme.name)}
          >
            <span
              class="session-drawer-theme-swatch"
              aria-hidden="true"
              style={{ background: theme.swatch }}
            />
            <span>{theme.label}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

export function SessionDrawer({
  open,
  progress,
  dragging,
  sessions,
  activeSessionId,
  busy,
  onClose,
  onCreate,
  onSelect,
  onRename,
  onDelete,
  activeTheme,
  themeOptions,
  onSelectTheme,
}: SessionDrawerProps) {
  const closeSwipe = useSessionDrawerCloseSwipe(open, onClose);
  const [actionSession, setActionSession] = useState<SessionSummary | null>(null);

  useEffect(() => {
    if (!open) setActionSession(null);
  }, [open]);

  useEffect(() => {
    setActionSession((current) => {
      if (!current) return current;
      return current.chat_id === activeSessionId ? current : null;
    });
  }, [activeSessionId]);

  const visible = open || progress > 0 || closeSwipe.closeProgress > 0;
  if (!visible) return null;

  const drawerProgress = open ? 1 - closeSwipe.closeProgress : progress;
  const inMotion = dragging || closeSwipe.dragging;
  const backdropStyle = {
    opacity: String(drawerProgress),
    transition: inMotion ? "none" : "opacity 0.2s ease",
    pointerEvents: open ? "auto" : "none",
  };
  const drawerStyle = {
    transform: `translateX(${(drawerProgress - 1) * 100}%)`,
    transition: inMotion ? "none" : "transform 0.2s ease",
  };
  const actionMenuOpen = actionSession !== null;

  return (
    <div id="session-drawer-root" data-no-swipe="1">
      <button
        id="session-drawer-backdrop"
        type="button"
        aria-label="Close conversations"
        style={backdropStyle}
        onClick={onClose}
      />
      <aside
        id="session-drawer"
        aria-label="Conversations"
        style={drawerStyle}
        onTouchStart={closeSwipe.onTouchStart}
        onTouchMove={closeSwipe.onTouchMove}
        onTouchEnd={closeSwipe.onTouchEnd}
        onTouchCancel={closeSwipe.onTouchCancel}
      >
        <div id="session-drawer-header">
          <div>
            <div id="session-drawer-eyebrow">Nanobot</div>
            <h2 id="session-drawer-title">Conversations</h2>
          </div>
          <button
            id="session-drawer-close"
            type="button"
            aria-label="Close conversations"
            onClick={onClose}
          >
            <CloseIcon />
          </button>
        </div>

        <button
          id="session-drawer-new"
          type="button"
          disabled={busy}
          onClick={() => {
            void onCreate();
          }}
        >
          New conversation
        </button>

        <SessionThemePicker
          activeTheme={activeTheme}
          themeOptions={themeOptions}
          onSelectTheme={onSelectTheme}
        />

        <div id="session-drawer-list">
          {sessions.map((session) => {
            const active = session.chat_id === activeSessionId;
            return (
              <SessionDrawerItem
                key={session.chat_id}
                session={session}
                active={active}
                busy={busy}
                onSelect={onSelect}
                onOpenMenu={setActionSession}
              />
            );
          })}
        </div>
      </aside>
      {actionMenuOpen ? (
        <SessionActionMenu
          session={actionSession}
          busy={busy}
          onClose={() => setActionSession(null)}
          onRename={onRename}
          onDelete={onDelete}
        />
      ) : null}
    </div>
  );
}
