import React, { useEffect, useMemo, useRef, useState } from 'react';

import { buildMemberColorMap } from '@renderer/utils/memberHelpers';

import { ActivityItem, isNoiseMessage } from './ActivityItem';
import { groupTimelineItems, isLeadThought, LeadThoughtsGroupRow } from './LeadThoughtsGroup';

import type { TimelineItem } from './LeadThoughtsGroup';
import type { InboxMessage, ResolvedTeamMember } from '@shared/types';

interface ActivityTimelineProps {
  messages: InboxMessage[];
  teamName: string;
  members?: ResolvedTeamMember[];
  /**
   * When provided, unread is derived from this set and getMessageKey.
   * When omitted, unread is derived from message.read.
   */
  readState?: { readSet: Set<string>; getMessageKey: (message: InboxMessage) => string };
  onCreateTaskFromMessage?: (subject: string, description: string) => void;
  onReplyToMessage?: (message: InboxMessage) => void;
  onMemberClick?: (member: ResolvedTeamMember) => void;
  /** Called when a message enters the viewport (for marking as read). */
  onMessageVisible?: (message: InboxMessage) => void;
  /** Called when a task ID link (e.g. #10) is clicked in message text. */
  onTaskIdClick?: (taskId: string) => void;
  /** Called when the user clicks "Restart team" on an auth error message. */
  onRestartTeam?: () => void;
  /** When true, collapse all message bodies — show only headers with expand chevrons. */
  allCollapsed?: boolean;
}

const VIEWPORT_THRESHOLD = 0.15;
const MESSAGES_PAGE_SIZE = 30;

const MessageRowWithObserver = ({
  message,
  teamName,
  memberRole,
  memberColor,
  recipientColor,
  isUnread,
  isNew,
  zebraShade,
  memberColorMap,
  onMemberNameClick,
  onCreateTask,
  onReply,
  onVisible,
  onTaskIdClick,
  onRestartTeam,
  forceCollapsed,
}: {
  message: InboxMessage;
  teamName: string;
  memberRole?: string;
  memberColor?: string;
  recipientColor?: string;
  isUnread?: boolean;
  isNew?: boolean;
  zebraShade?: boolean;
  memberColorMap?: Map<string, string>;
  onMemberNameClick?: (name: string) => void;
  onCreateTask?: (subject: string, description: string) => void;
  onReply?: (message: InboxMessage) => void;
  onVisible?: (message: InboxMessage) => void;
  onTaskIdClick?: (taskId: string) => void;
  onRestartTeam?: () => void;
  forceCollapsed?: boolean;
}): React.JSX.Element => {
  const ref = useRef<HTMLDivElement>(null);
  const reportedRef = useRef(false);
  const messageRef = useRef(message);
  const onVisibleRef = useRef(onVisible);

  useEffect(() => {
    messageRef.current = message;
    onVisibleRef.current = onVisible;
  }, [message, onVisible]);

  useEffect(() => {
    if (!onVisible) return;
    const el = ref.current;
    if (!el) return;
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (!entry?.isIntersecting) return;
        if (reportedRef.current) return;
        const cb = onVisibleRef.current;
        const msg = messageRef.current;
        if (!cb) return;
        reportedRef.current = true;
        cb(msg);
      },
      { threshold: VIEWPORT_THRESHOLD, rootMargin: '0px' }
    );
    observer.observe(el);
    return () => observer.disconnect();
  }, [onVisible]);

  return (
    <div ref={ref} className={isNew ? 'message-enter-animate min-h-px' : 'min-h-px'}>
      <ActivityItem
        message={message}
        teamName={teamName}
        memberRole={memberRole}
        memberColor={memberColor}
        recipientColor={recipientColor}
        isUnread={isUnread}
        zebraShade={zebraShade}
        memberColorMap={memberColorMap}
        onMemberNameClick={onMemberNameClick}
        onCreateTask={onCreateTask}
        onReply={onReply}
        onTaskIdClick={onTaskIdClick}
        onRestartTeam={onRestartTeam}
        forceCollapsed={forceCollapsed}
      />
    </div>
  );
};

export const ActivityTimeline = ({
  messages,
  teamName,
  members,
  readState,
  onCreateTaskFromMessage,
  onReplyToMessage,
  onMemberClick,
  onMessageVisible,
  onTaskIdClick,
  onRestartTeam,
  allCollapsed,
}: ActivityTimelineProps): React.JSX.Element => {
  const [visibleCount, setVisibleCount] = useState(MESSAGES_PAGE_SIZE);

  // --- New-message animation tracking ---
  const knownKeysRef = useRef<Set<string>>(new Set<string>());
  const isInitializedRef = useRef(false);
  const prevVisibleCountRef = useRef(visibleCount);

  const colorMap = members ? buildMemberColorMap(members) : new Map<string, string>();
  const memberInfo = new Map<string, { role?: string; color?: string }>();
  if (members) {
    for (const m of members) {
      const info = {
        role: m.role ?? (m.agentType !== 'general-purpose' ? m.agentType : undefined),
        color: colorMap.get(m.name),
      };
      memberInfo.set(m.name, info);
      if (m.agentType && m.agentType !== m.name) {
        memberInfo.set(m.agentType, info);
      }
    }
    // Map "user" to team-lead's resolved color and role
    const leadMember = members.find(
      (m) => m.agentType === 'team-lead' || m.role?.toLowerCase().includes('lead')
    );
    if (leadMember) {
      const leadInfo = memberInfo.get(leadMember.name);
      if (leadInfo) {
        memberInfo.set('user', { role: undefined, color: colorMap.get('user') });
      }
    }
  }

  const handleMemberNameClick = (name: string): void => {
    const member = members?.find((m) => m.name === name || m.agentType === name);
    if (member) onMemberClick?.(member);
  };

  // Pagination counts only significant (non-thought) messages so that lead thoughts
  // don't consume the page limit — they collapse into a single visual group anyway.
  const { visibleMessages, hiddenCount } = useMemo(() => {
    const total = messages.length;
    if (total === 0) return { visibleMessages: messages, hiddenCount: 0 };

    let significantSeen = 0;
    let cutoff = total;
    for (let i = 0; i < total; i++) {
      if (!isLeadThought(messages[i])) {
        significantSeen++;
        if (significantSeen > visibleCount) {
          cutoff = i;
          break;
        }
      }
    }

    const significantTotal =
      significantSeen +
      (cutoff < total ? messages.slice(cutoff).filter((m) => !isLeadThought(m)).length : 0);
    const hidden = Math.max(0, significantTotal - visibleCount);
    return {
      visibleMessages: cutoff < total ? messages.slice(0, cutoff) : messages,
      hiddenCount: hidden,
    };
  }, [messages, visibleCount]);

  // Group consecutive lead thoughts into collapsible blocks.
  const timelineItems = useMemo(() => groupTimelineItems(visibleMessages), [visibleMessages]);

  // Zebra striping: alternate shade on non-noise (full card) items only.
  const zebraShadeSet = useMemo(() => {
    const result = new Set<number>();
    let cardCount = 0;
    for (let i = 0; i < timelineItems.length; i++) {
      const item = timelineItems[i];
      if (item.type === 'lead-thoughts') {
        // Thought groups count as one card for striping
        if (cardCount % 2 === 1) result.add(i);
        cardCount++;
      } else {
        if (isNoiseMessage(item.message.text)) continue;
        if (cardCount % 2 === 1) result.add(i);
        cardCount++;
      }
    }
    return result;
  }, [timelineItems]);

  // Determine which items are "new" (should animate).
  /* eslint-disable react-hooks/refs -- intentional ref access during render for animation tracking */

  const newItemKeys = useMemo(() => {
    const getItemKey = (item: TimelineItem): string => {
      if (item.type === 'lead-thoughts') {
        // Stable key: identify group by its first thought, not by count (which changes)
        return `thoughts-${item.group.thoughts[0].messageId ?? item.originalIndices[0]}`;
      }
      const msg = item.message;
      return `${msg.messageId ?? item.originalIndex}-${msg.timestamp}-${msg.from}`;
    };

    const allKeys: string[] = [];
    for (const item of timelineItems) {
      allKeys.push(getItemKey(item));
    }

    // First render: seed known keys, no animations
    if (!isInitializedRef.current) {
      isInitializedRef.current = true;
      for (const key of allKeys) {
        knownKeysRef.current.add(key);
      }
      prevVisibleCountRef.current = visibleCount;
      return new Set<string>();
    }

    // Pagination expansion ("Show more" / "Show all"): add keys silently
    const isPaginationExpansion = visibleCount > prevVisibleCountRef.current;
    prevVisibleCountRef.current = visibleCount;

    if (isPaginationExpansion) {
      for (const key of allKeys) {
        knownKeysRef.current.add(key);
      }
      return new Set<string>();
    }

    // Normal update: unknown keys are new items
    const newKeys = new Set<string>();
    for (const key of allKeys) {
      if (!knownKeysRef.current.has(key)) {
        newKeys.add(key);
        knownKeysRef.current.add(key);
      }
    }
    return newKeys;
  }, [timelineItems, visibleCount]);
  /* eslint-enable react-hooks/refs -- end animation tracking block */

  const handleShowMore = (): void => {
    setVisibleCount((prev) => prev + MESSAGES_PAGE_SIZE);
  };

  const handleShowAll = (): void => {
    setVisibleCount(Infinity);
  };

  if (messages.length === 0) {
    return (
      <div className="rounded-md border border-[var(--color-border)] p-3 text-xs text-[var(--color-text-muted)]">
        <p>No messages</p>
        <p className="mt-1 text-[11px]">Send a message to a member to see activity.</p>
      </div>
    );
  }

  const getItemSessionId = (item: TimelineItem): string | undefined =>
    item.type === 'lead-thoughts'
      ? item.group.thoughts[0].leadSessionId
      : item.message.leadSessionId;

  // Pin the newest thought group (if first) so it stays at the top and doesn't jump.
  const pinnedThoughtGroup = timelineItems[0]?.type === 'lead-thoughts' ? timelineItems[0] : null;
  const startIndex = pinnedThoughtGroup ? 1 : 0;

  return (
    <div className="space-y-1">
      {/* Pinned (newest) thought group — always at top */}
      {pinnedThoughtGroup &&
        (() => {
          const { group } = pinnedThoughtGroup;
          const firstThought = group.thoughts[0];
          const info = memberInfo.get(firstThought.from);
          const itemKey = `thoughts-${firstThought.messageId ?? pinnedThoughtGroup.originalIndices[0]}`;
          return (
            <LeadThoughtsGroupRow
              key={itemKey}
              group={group}
              memberColor={info?.color}
              canBeLive={true}
              isNew={newItemKeys.has(itemKey)}
              onVisible={onMessageVisible}
              zebraShade={zebraShadeSet.has(0)}
            />
          );
        })()}

      {/* Remaining items */}
      {timelineItems.slice(startIndex).map((item, index) => {
        const realIndex = index + startIndex;

        // Session boundary separator (messages sorted desc — new on top)
        let sessionSeparator: React.JSX.Element | null = null;
        if (realIndex > 0) {
          const prevSessionId = getItemSessionId(timelineItems[realIndex - 1]);
          const currSessionId = getItemSessionId(item);
          if (prevSessionId && currSessionId && prevSessionId !== currSessionId) {
            sessionSeparator = (
              <div
                className="flex items-center gap-3"
                style={{ paddingTop: 90, paddingBottom: 90 }}
              >
                <div className="h-px flex-1 bg-blue-400/30" />
                <span className="whitespace-nowrap text-[11px] text-blue-400">New session</span>
                <div className="h-px flex-1 bg-blue-400/30" />
              </div>
            );
          }
        }

        if (item.type === 'lead-thoughts') {
          const { group } = item;
          const firstThought = group.thoughts[0];
          const info = memberInfo.get(firstThought.from);
          const itemKey = `thoughts-${firstThought.messageId ?? item.originalIndices[0]}`;
          return (
            <React.Fragment key={itemKey}>
              {sessionSeparator}
              <LeadThoughtsGroupRow
                group={group}
                memberColor={info?.color}
                canBeLive={false}
                isNew={newItemKeys.has(itemKey)}
                onVisible={onMessageVisible}
                zebraShade={zebraShadeSet.has(realIndex)}
              />
            </React.Fragment>
          );
        }

        const { message } = item;
        const info = memberInfo.get(message.from);
        const recipientInfo = message.to ? memberInfo.get(message.to) : undefined;
        const recipientColor =
          recipientInfo?.color ?? (message.to ? colorMap.get(message.to) : undefined);
        const messageKey = `${message.messageId ?? item.originalIndex}-${message.timestamp}-${message.from}`;
        const isUnread = readState
          ? !message.read && !readState.readSet.has(readState.getMessageKey(message))
          : !message.read;
        return (
          <React.Fragment key={messageKey}>
            {sessionSeparator}
            <MessageRowWithObserver
              message={message}
              teamName={teamName}
              memberRole={info?.role}
              memberColor={info?.color}
              recipientColor={recipientColor}
              isUnread={isUnread}
              isNew={newItemKeys.has(messageKey)}
              zebraShade={zebraShadeSet.has(realIndex)}
              memberColorMap={colorMap}
              onMemberNameClick={onMemberClick ? handleMemberNameClick : undefined}
              onCreateTask={onCreateTaskFromMessage}
              onReply={onReplyToMessage}
              onVisible={onMessageVisible}
              onTaskIdClick={onTaskIdClick}
              onRestartTeam={onRestartTeam}
              forceCollapsed={allCollapsed}
            />
          </React.Fragment>
        );
      })}
      {hiddenCount > 0 && (
        <div className="relative flex justify-center pb-3 pt-1">
          {/* Bottom-up shadow gradient: darkest at bottom edge, fades upward */}
          <div
            className="pointer-events-none absolute inset-x-0 -top-24"
            style={{
              bottom: '-1.6rem',
              background:
                'linear-gradient(to top, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0.25) 25%, rgba(0, 0, 0, 0.1) 50%, rgba(0, 0, 0, 0.03) 75%, transparent 100%)',
            }}
          />
          <div
            className="relative z-[1] flex items-center gap-3 rounded-full px-4 py-1.5"
            style={{
              backgroundColor: 'var(--color-surface-raised)',
              boxShadow:
                '0 0 12px 4px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.04)',
              border: '1px solid var(--color-border-emphasis)',
            }}
          >
            <span className="text-[11px] tabular-nums text-[var(--color-text-muted)]">
              +{hiddenCount} older
            </span>
            <span className="h-3 w-px bg-blue-400/30" />
            <button
              onClick={handleShowMore}
              className="rounded-full px-2.5 py-0.5 text-[11px] font-medium text-[var(--color-text-secondary)] transition-all hover:bg-[rgba(255,255,255,0.08)] hover:text-[var(--color-text)]"
            >
              Show {Math.min(MESSAGES_PAGE_SIZE, hiddenCount)} more
            </button>
            {hiddenCount > MESSAGES_PAGE_SIZE && (
              <>
                <span className="h-3 w-px bg-blue-400/30" />
                <button
                  onClick={handleShowAll}
                  className="rounded-full px-2.5 py-0.5 text-[11px] text-[var(--color-text-muted)] transition-all hover:bg-[rgba(255,255,255,0.08)] hover:text-[var(--color-text-secondary)]"
                >
                  Show all
                </button>
              </>
            )}
          </div>
        </div>
      )}
    </div>
  );
};
