'use client';

import { useState, useEffect, useCallback, useRef } from 'react';
import { createPortal } from 'react-dom';
import {
  ArrowRight,
  Calendar,
  CalendarCheck,
  CalendarX,
  Clock,
  MapPin,
  Users,
  Loader2,
  Check,
  HelpCircle,
  X,
  AlertCircle,
  ChevronDown,
  ChevronUp,
} from 'lucide-react';
import { useTranslations, useFormatter } from 'next-intl';
import { useRouter } from '@/i18n/navigation';
import { useAuthStore } from '@/stores/auth-store';
import { useCalendarStore } from '@/stores/calendar-store';
import { useSettingsStore } from '@/stores/settings-store';
import type { Email, CalendarEvent } from '@/lib/jmap/types';
import {
  findCalendarAttachment,
  getInvitationMethod,
  getInvitationActorSummary,
  getInvitationTrustAssessment,
  formatEventSummary,
  findParticipantByEmail,
  extractMethodFromRawIcs,
  type InvitationMethod,
  type InvitationTrustAssessment,
} from '@/lib/calendar-invitation';
import { cn } from '@/lib/utils';
import { sanitizeColor } from '@/components/calendar/event-card';
import { RecipientPopover } from './recipient-popover';

interface InvitationChangeItem {
  label: string;
  before: string;
  after: string;
}

function getBannerTitle(t: ReturnType<typeof useTranslations>, method: InvitationMethod): string {
  switch (method) {
    case 'publish':
      return t('published_title');
    case 'reply':
      return t('response_title');
    case 'add':
      return t('update_title');
    case 'counter':
      return t('counter_title');
    case 'refresh':
      return t('refresh_title');
    case 'declinecounter':
      return t('declined_counter_title');
    case 'cancel':
      return t('cancelled_title');
    case 'request':
    case 'unknown':
    default:
      return t('title');
  }
}

function getActorMessage(
  t: ReturnType<typeof useTranslations>,
  method: InvitationMethod,
  actorName: string,
  actorStatus: string | null,
): string | null {
  switch (method) {
    case 'reply':
      return actorStatus
        ? t('actor_response_info', { name: actorName, status: actorStatus })
        : t('actor_sent_info', { name: actorName });
    case 'counter':
      return t('actor_counter_info', { name: actorName });
    case 'refresh':
      return t('actor_refresh_info', { name: actorName });
    case 'declinecounter':
      return t('actor_declined_counter_info', { name: actorName });
    case 'request':
    case 'publish':
    case 'add':
    case 'cancel':
      return t('actor_sent_info', { name: actorName });
    default:
      return null;
  }
}

function getBannerInfo(
  t: ReturnType<typeof useTranslations>,
  method: InvitationMethod,
  userIsOrganizer: boolean,
  supportsCalendar: boolean,
): string | null {
  if (!supportsCalendar) {
    return t('no_calendar');
  }

  switch (method) {
    case 'request':
      return t('request_info');
    case 'publish':
      return t('published_info');
    case 'reply':
      return t(userIsOrganizer ? 'response_info_organizer' : 'response_info');
    case 'add':
      return t('update_info');
    case 'cancel':
      return t('cancel_info');
    case 'counter':
      return t(userIsOrganizer ? 'counter_info_organizer' : 'counter_info');
    case 'refresh':
      return t(userIsOrganizer ? 'refresh_info_organizer' : 'refresh_info');
    case 'declinecounter':
      return t('declined_counter_info');
    default:
      return null;
  }
}

function getTrustMessage(
  t: ReturnType<typeof useTranslations>,
  trustAssessment: InvitationTrustAssessment,
): string | null {
  switch (trustAssessment.reason) {
    case 'authentication_failed':
      return t('authentication_failed_info');
    case 'authentication_missing':
      return t('authentication_missing_info');
    case 'sender_mismatch':
      return t('sender_mismatch_info', {
        sender: trustAssessment.senderEmail ?? '',
        organizer: trustAssessment.organizerEmail ?? '',
      });
    case 'sender_mismatch_unverified':
      return t('sender_mismatch_unverified_info', {
        sender: trustAssessment.senderEmail ?? '',
        organizer: trustAssessment.organizerEmail ?? '',
      });
    default:
      return null;
  }
}

function getParticipationLabel(
  t: ReturnType<typeof useTranslations>,
  status: string | null,
): string | null {
  switch (status) {
    case 'accepted':
      return t('response_accepted');
    case 'tentative':
      return t('response_tentative');
    case 'declined':
      return t('response_declined');
    case 'delegated':
      return t('response_delegated');
    case 'needs-action':
      return t('response_needed');
    default:
      return null;
  }
}

function getParticipationTone(status: string | null): string {
  switch (status) {
    case 'accepted':
      return 'bg-success/15 text-success';
    case 'tentative':
      return 'bg-warning/15 text-warning';
    case 'declined':
      return 'bg-destructive/15 text-destructive';
    default:
      return 'bg-muted text-muted-foreground';
  }
}

function hasMeaningfulDifference<T>(left: T | null | undefined, right: T | null | undefined): boolean {
  return JSON.stringify(left ?? null) !== JSON.stringify(right ?? null);
}

function getViewActionLabel(
  t: ReturnType<typeof useTranslations>,
  method: InvitationMethod,
  userIsOrganizer: boolean,
): string {
  if (method === 'counter' && userIsOrganizer) {
    return t('review_proposal');
  }

  if (method === 'refresh' && userIsOrganizer) {
    return t('review_request');
  }

  return t('view_in_calendar');
}

function buildProposalPatch(
  currentEvent: Partial<CalendarEvent> | null,
  proposedEvent: Partial<CalendarEvent> | null,
): Partial<CalendarEvent> | null {
  if (!currentEvent || !proposedEvent) {
    return null;
  }

  const patch: Partial<CalendarEvent> = {};

  if (typeof proposedEvent.title === 'string' && proposedEvent.title !== currentEvent.title) {
    patch.title = proposedEvent.title;
  }

  if (typeof proposedEvent.description === 'string' && proposedEvent.description !== currentEvent.description) {
    patch.description = proposedEvent.description;
  }

  if (typeof proposedEvent.descriptionContentType === 'string' && proposedEvent.descriptionContentType !== currentEvent.descriptionContentType) {
    patch.descriptionContentType = proposedEvent.descriptionContentType;
  }

  if (typeof proposedEvent.start === 'string' && proposedEvent.start !== currentEvent.start) {
    patch.start = proposedEvent.start;
  }

  if (typeof proposedEvent.duration === 'string' && proposedEvent.duration !== currentEvent.duration) {
    patch.duration = proposedEvent.duration;
  }

  if ((proposedEvent.timeZone ?? null) !== (currentEvent.timeZone ?? null)) {
    patch.timeZone = proposedEvent.timeZone ?? null;
  }

  if ((proposedEvent.showWithoutTime ?? false) !== (currentEvent.showWithoutTime ?? false)) {
    patch.showWithoutTime = proposedEvent.showWithoutTime ?? false;
  }

  if (hasMeaningfulDifference(proposedEvent.locations, currentEvent.locations)) {
    patch.locations = proposedEvent.locations ?? null;
  }

  if (hasMeaningfulDifference(proposedEvent.virtualLocations, currentEvent.virtualLocations)) {
    patch.virtualLocations = proposedEvent.virtualLocations ?? null;
  }

  return Object.keys(patch).length > 0 ? patch : null;
}

function buildInvitationChangeItems(
  t: ReturnType<typeof useTranslations>,
  currentEvent: Partial<CalendarEvent> | null,
  proposedEvent: Partial<CalendarEvent> | null,
  formatDateTime: (dateStr: string | null) => string,
): InvitationChangeItem[] {
  if (!currentEvent || !proposedEvent) {
    return [];
  }

  const currentSummary = formatEventSummary(currentEvent);
  const proposedSummary = formatEventSummary(proposedEvent);
  const changes: InvitationChangeItem[] = [];

  if (currentSummary.title !== proposedSummary.title && proposedSummary.title) {
    changes.push({
      label: t('change_title'),
      before: currentSummary.title || t('change_empty'),
      after: proposedSummary.title,
    });
  }

  const currentSchedule = currentSummary.start
    ? `${formatDateTime(currentSummary.start)}${currentSummary.end ? ` - ${formatDateTime(currentSummary.end)}` : ''}`
    : t('change_empty');
  const proposedSchedule = proposedSummary.start
    ? `${formatDateTime(proposedSummary.start)}${proposedSummary.end ? ` - ${formatDateTime(proposedSummary.end)}` : ''}`
    : t('change_empty');
  if (currentSchedule !== proposedSchedule && proposedSummary.start) {
    changes.push({
      label: t('change_time'),
      before: currentSchedule,
      after: proposedSchedule,
    });
  }

  if ((currentSummary.location ?? '') !== (proposedSummary.location ?? '') && proposedSummary.location) {
    changes.push({
      label: t('change_location'),
      before: currentSummary.location || t('change_empty'),
      after: proposedSummary.location,
    });
  }

  if ((currentEvent.description ?? '') !== (proposedEvent.description ?? '') && proposedEvent.description) {
    changes.push({
      label: t('change_description'),
      before: currentEvent.description || t('change_empty'),
      after: proposedEvent.description,
    });
  }

  return changes;
}

function buildParticipantsForRsvp(
  event: Partial<CalendarEvent>,
  participantId: string,
  status: 'accepted' | 'tentative' | 'declined',
): Record<string, NonNullable<CalendarEvent['participants']>[string]> | null {
  if (!event.participants) {
    return null;
  }

  return Object.fromEntries(
    Object.entries(event.participants).map(([id, participant]) => [
      id,
      {
        ...participant,
        participationStatus: id === participantId ? status : participant.participationStatus,
      },
    ]),
  );
}

function getMethodIconTone(method: InvitationMethod, actorStatus?: string | null): string {
  switch (method) {
    case 'cancel':
    case 'declinecounter':
      return 'bg-destructive/15 text-destructive';
    case 'counter':
      return 'bg-warning/15 text-warning';
    case 'reply':
      switch (actorStatus) {
        case 'accepted': return 'bg-success/15 text-success';
        case 'tentative': return 'bg-warning/15 text-warning';
        case 'declined': return 'bg-destructive/15 text-destructive';
        default: return 'bg-primary/15 text-primary';
      }
    case 'request':
    case 'add':
    case 'publish':
      return 'bg-primary/15 text-primary';
    default:
      return 'bg-muted text-muted-foreground';
  }
}

interface CalendarInvitationBannerProps {
  email: Email;
}

type BannerState = 'loading' | 'parsed' | 'rsvp-sent' | 'imported' | 'error';

export function CalendarInvitationBanner({ email }: CalendarInvitationBannerProps) {
  const t = useTranslations('email_viewer.calendar_invitation');
  const format = useFormatter();
  const router = useRouter();
  const client = useAuthStore((s) => s.client);
  const currentUserEmail = useAuthStore((s) => s.primaryIdentity?.email);
  const calendarInvitationParsingEnabled = useSettingsStore((s) => s.calendarInvitationParsingEnabled);
  const timeFormat = useSettingsStore((s) => s.timeFormat);
  const { calendars, supportsCalendar, importEvents, rsvpEvent, updateEvent, events: storeEvents, setSelectedDate } = useCalendarStore();

  const [state, setState] = useState<BannerState>('loading');
  const [parsedEvent, setParsedEvent] = useState<Partial<CalendarEvent> | null>(null);
  const [rsvpStatus, setRsvpStatus] = useState<string | null>(null);
  const [actionNotice, setActionNotice] = useState<string | null>(null);
  const [actionError, setActionError] = useState<string | null>(null);
  const [isProcessing, setIsProcessing] = useState(false);
  const [showCalendarPicker, setShowCalendarPicker] = useState(false);
  const [pickerPosition, setPickerPosition] = useState<{ top: number; left: number } | null>(null);
  const pickerTriggerRef = useRef<HTMLButtonElement>(null);
  const [selectedCalendarId, setSelectedCalendarId] = useState<string>('');
  const [rawIcsMethod, setRawIcsMethod] = useState<InvitationMethod>('unknown');
  const [isCollapsed, setIsCollapsed] = useState(false);

  const attachment = findCalendarAttachment(email);

  const parseEvent = useCallback(async () => {
    if (!client || !attachment || !calendarInvitationParsingEnabled) return;
    setState('loading');
    setActionNotice(null);
    setActionError(null);
    try {
      const events = await client.parseCalendarEvents(client.getCalendarsAccountId(), attachment.blobId);
      if (events.length > 0) {
        const parsed = events[0];
        setParsedEvent(parsed);

        // JMAP strips parameters from Content-Type (RFC 8621), so method=REQUEST
        // is lost. Fetch raw ICS to extract METHOD as a reliable fallback.
        try {
          const blob = await client.fetchBlob(attachment.blobId, 'invite.ics', 'text/calendar');
          const rawText = await blob.text();
          const icsMethod = extractMethodFromRawIcs(rawText);
          if (icsMethod !== 'unknown') {
            setRawIcsMethod(icsMethod);
          }
        } catch { /* ignore - fall back to heuristic detection */ }

        if (parsed.uid && supportsCalendar) {
          const storeHasIt = useCalendarStore.getState().events.some((e) => e.uid === parsed.uid);
          if (!storeHasIt) {
            try {
              const serverEvents = await client.queryCalendarEvents({});
              const matching = serverEvents.filter((e) => e.uid === parsed.uid);
              if (matching.length > 0) {
                useCalendarStore.setState((s) => {
                  const existingIds = new Set(s.events.map((e) => e.id));
                  const newEvents = matching.filter((e) => !existingIds.has(e.id));
                  return newEvents.length > 0 ? { events: [...s.events, ...newEvents] } : s;
                });
              }
            } catch { /* ignore lookup failure */ }
          }
        }
        setState('parsed');
      } else {
        setState('error');
      }
    } catch {
      setState('error');
    }
  }, [calendarInvitationParsingEnabled, client, attachment, supportsCalendar]);

  useEffect(() => {
    if (attachment && calendarInvitationParsingEnabled) {
      parseEvent();
    }
  }, [calendarInvitationParsingEnabled, email.id]); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    if (calendars.length > 0 && !selectedCalendarId) {
      const defaultCal = calendars.find((c) => c.isDefault) || calendars[0];
      setSelectedCalendarId(defaultCal.id);
    }
  }, [calendars, selectedCalendarId]);

  useEffect(() => {
    if (!showCalendarPicker) return;
    const close = () => setShowCalendarPicker(false);
    window.addEventListener('scroll', close, true);
    window.addEventListener('resize', close);
    return () => {
      window.removeEventListener('scroll', close, true);
      window.removeEventListener('resize', close);
    };
  }, [showCalendarPicker]);

  if (!attachment || !calendarInvitationParsingEnabled) return null;

  const detectedMethod = parsedEvent ? getInvitationMethod(parsedEvent, { email, attachment }) : 'unknown';
  const method = detectedMethod !== 'unknown' ? detectedMethod : rawIcsMethod;
  const summary = parsedEvent ? formatEventSummary(parsedEvent) : null;
  const isCancellation = method === 'cancel';
  const isResponseOnly = method === 'reply' || method === 'refresh' || method === 'counter' || method === 'declinecounter';
  const showDetails = !isCollapsed;
  const allowsRsvp = method === 'request';
  const allowsImport = method === 'request' || method === 'publish' || method === 'add' || method === 'unknown';

  const existingEvent = parsedEvent?.uid
    ? storeEvents.find((e) => e.uid === parsedEvent.uid)
    : null;

  const currentUserParticipant = existingEvent && currentUserEmail
    ? findParticipantByEmail(existingEvent, currentUserEmail)
    : null;

  const fallbackParsedParticipant = !currentUserParticipant && parsedEvent && currentUserEmail
    ? findParticipantByEmail(parsedEvent, currentUserEmail)
    : null;

  const participantForUser = currentUserParticipant || fallbackParsedParticipant;
  // Accept participant if they have attendee role OR if they are not exclusively an organizer
  // Some JMAP servers may not set roles.attendee explicitly in CalendarEvent/parse results
  const isOnlyOrganizer = participantForUser?.participant.roles
    ? (participantForUser.participant.roles.owner || participantForUser.participant.roles.chair)
      && !participantForUser.participant.roles.attendee
    : false;
  const myParticipant = participantForUser && !isOnlyOrganizer ? participantForUser : null;

  const currentRsvp = rsvpStatus
    || myParticipant?.participant.participationStatus
    || null;
  const userIsOrganizer = Boolean(isOnlyOrganizer);
  const userIsSender = Boolean(
    currentUserEmail && email.from?.[0]?.email?.toLowerCase() === currentUserEmail.toLowerCase()
  );
  const bannerTitle = getBannerTitle(t, method);
  const bannerInfo = getBannerInfo(t, method, userIsOrganizer, supportsCalendar);
  const trustAssessment = parsedEvent && !userIsSender ? getInvitationTrustAssessment(parsedEvent, email, method) : null;
  const trustMessage = trustAssessment ? getTrustMessage(t, trustAssessment) : null;
  const participationLabel = getParticipationLabel(t, currentRsvp);
  const actorSummary = parsedEvent ? getInvitationActorSummary(parsedEvent, method) : null;
  const actorName = actorSummary?.name || actorSummary?.email || t('actor_unknown');
  const actorStatus = getParticipationLabel(t, actorSummary?.participationStatus ?? null);
  const actorMessage = actorSummary ? getActorMessage(t, method, actorName, actorStatus) : null;
  // For REQUEST method, allow RSVP even if we can't find the user in participants:
  // the email was sent TO the user, so they are an attendee. handleRsvp handles
  // the import-then-find-participant flow for this case.
  const canRespond = supportsCalendar && allowsRsvp && !isResponseOnly && !userIsOrganizer
    && (Boolean(myParticipant) || method === 'request');
  const proposalPatch = existingEvent && parsedEvent ? buildProposalPatch(existingEvent, parsedEvent) : null;

  const resolveExistingEventForRsvp = async () => {
    if (!client || !existingEvent) {
      return existingEvent;
    }

    if (existingEvent.participants && Object.keys(existingEvent.participants).length > 0) {
      return existingEvent;
    }

    const hydratedEvent = await client.getCalendarEvent(existingEvent.id);
    if (!hydratedEvent) {
      return existingEvent;
    }

    useCalendarStore.setState((state) => ({
      events: state.events.map((event) => event.id === hydratedEvent.id ? hydratedEvent : event),
    }));

    return hydratedEvent;
  };

  const handleRsvp = async (status: 'accepted' | 'tentative' | 'declined') => {
    if (!client || !parsedEvent || isProcessing) return;
    const calId = selectedCalendarId || calendars.find((c) => c.isDefault)?.id || calendars[0]?.id;
    setActionNotice(null);
    setActionError(null);
    setIsProcessing(true);
    const replyToForRsvp = parsedEvent.replyTo
      || (parsedEvent.organizerCalendarAddress ? { imip: parsedEvent.organizerCalendarAddress } : null);

    const imipStatus = status.toUpperCase() as 'ACCEPTED' | 'TENTATIVE' | 'DECLINED';
    const organizerEmail = parsedEvent?.replyTo?.imip?.replace('mailto:', '')
      || parsedEvent?.organizerCalendarAddress?.replace('mailto:', '')
      || summary?.organizerEmail
      || null;

    // Send the iMIP REPLY email to the organizer (client-side scheduling).
    // Called after updating the local calendar event. Best-effort - if it
    // fails we still report the RSVP as sent since the calendar was updated.
    const sendImipReply = async () => {
      if (!organizerEmail || !parsedEvent?.uid || !currentUserEmail) {
        return;
      }

      try {
        await client.sendImipReply({
          organizerEmail,
          organizerName: summary?.organizer || undefined,
          attendeeEmail: currentUserEmail,
          attendeeName: myParticipant?.participant.name || undefined,
          uid: parsedEvent.uid,
          summary: parsedEvent.title,
          dtStart: parsedEvent.start || undefined,
          dtEnd: summary?.end || undefined,
          timeZone: parsedEvent.timeZone || undefined,
          isAllDay: parsedEvent.showWithoutTime || false,
          sequence: parsedEvent.sequence,
          status: imipStatus,
        });
      } catch {
        // Best-effort: don't block the RSVP success notification
      }
    };

    try {
      const eventForRsvp = existingEvent
        ? await resolveExistingEventForRsvp()
        : null;
      const existingEventParticipant = eventForRsvp && currentUserEmail
        ? findParticipantByEmail(eventForRsvp, currentUserEmail)
        : null;
      const canFallbackToParsedParticipant = Boolean(
        existingEvent
        && myParticipant
        && (!eventForRsvp?.participants || Object.keys(eventForRsvp.participants).length === 0)
      );
      if (eventForRsvp && existingEventParticipant) {
        await rsvpEvent(client, eventForRsvp.id, existingEventParticipant.id, status, replyToForRsvp);
        await sendImipReply();
        setRsvpStatus(status);
        setActionNotice(t('rsvp_sent'));
        setState('parsed');
      } else if (eventForRsvp && canFallbackToParsedParticipant && myParticipant) {
        const repairedParticipants = buildParticipantsForRsvp(parsedEvent, myParticipant.id, status);

        if (!repairedParticipants) {
          setActionError(t('action_failed'));
        } else {
          await updateEvent(client, eventForRsvp.id, {
            participants: repairedParticipants,
            replyTo: replyToForRsvp ?? undefined,
          }, true);
          await sendImipReply();
          setRsvpStatus(status);
          setActionNotice(t('rsvp_sent'));
          setState('parsed');
        }
      } else if (eventForRsvp) {
        setActionError(t('action_failed'));
      } else if (calId) {
        const imported = await importEvents(client, [parsedEvent], calId);
        if (imported > 0) {
          const newEvent = useCalendarStore.getState().events.find(
            (e) => e.uid === parsedEvent.uid
          );
          const participant = myParticipant
            || (newEvent && currentUserEmail ? findParticipantByEmail(newEvent, currentUserEmail) : null);
          if (newEvent && participant) {
            await rsvpEvent(client, newEvent.id, participant.id, status, replyToForRsvp);
            await sendImipReply();
            setRsvpStatus(status);
            setActionNotice(t('rsvp_sent'));
          } else {
            setActionNotice(t('added'));
          }
          setState('parsed');
        } else {
          setActionError(t('action_failed'));
        }
      } else {
        setActionError(t('action_failed'));
      }
    } catch (err) {
      console.error('[CalendarInvitation] RSVP failed:', err);
      setActionError(t('action_failed'));
    } finally {
      setIsProcessing(false);
    }
  };

  const handleViewInCalendar = () => {
    const targetDate = existingEvent?.utcStart
      || existingEvent?.start
      || parsedEvent?.utcStart
      || parsedEvent?.start;

    if (targetDate) {
      const parsedDate = new Date(targetDate);
      if (!Number.isNaN(parsedDate.getTime())) {
        setSelectedDate(parsedDate);
      }
    }

    setShowCalendarPicker(false);
    router.push('/calendar');
  };

  const handleImport = async (calendarId?: string) => {
    const calId = calendarId || selectedCalendarId || calendars.find((c) => c.isDefault)?.id || calendars[0]?.id;
    if (!client || !parsedEvent || !calId || isProcessing) {
      if (!calId) setActionError(t('action_failed'));
      return;
    }
    setActionNotice(null);
    setActionError(null);
    setIsProcessing(true);
    try {
      const count = await importEvents(client, [parsedEvent], calId);
      if (count > 0) {
        setActionNotice(t('added'));
        setState('parsed');
      } else {
        setActionError(t('action_failed'));
      }
    } catch {
      setActionError(t('action_failed'));
    } finally {
      setIsProcessing(false);
    }
  };

  const isAllDayEvent = parsedEvent?.showWithoutTime ?? false;

  const formatDateTime = (dateStr: string | null) => {
    if (!dateStr) return '';
    const date = new Date(dateStr);
    if (isNaN(date.getTime())) return dateStr;
    if (isAllDayEvent) {
      return format.dateTime(date, {
        weekday: 'short',
        month: 'short',
        day: 'numeric',
      });
    }
    return format.dateTime(date, {
      weekday: 'short',
      month: 'short',
      day: 'numeric',
      hour: 'numeric',
      minute: '2-digit',
      hour12: timeFormat === '12h',
    });
  };

  const proposedChanges = method === 'counter' && existingEvent && parsedEvent
    ? buildInvitationChangeItems(t, existingEvent, parsedEvent, formatDateTime)
    : [];
  const canApplyProposal = Boolean(
    client
    && existingEvent?.id
    && userIsOrganizer
    && method === 'counter'
    && proposalPatch
  );
  const viewActionLabel = getViewActionLabel(t, method, userIsOrganizer);

  const handleApplyProposal = async () => {
    if (!client || !existingEvent?.id || !proposalPatch || isProcessing) {
      return;
    }

    setActionNotice(null);
    setActionError(null);
    setIsProcessing(true);

    try {
      await updateEvent(client, existingEvent.id, proposalPatch, true);
      setActionNotice(t('proposal_applied'));
      setState('parsed');
      setRsvpStatus(null);
    } catch {
      setActionError(t('action_failed'));
    } finally {
      setIsProcessing(false);
    }
  };

  if (state === 'loading') {
    return (
      <div className="flex items-center gap-3">
        <div className="w-10 h-10 rounded-full bg-muted text-muted-foreground flex items-center justify-center flex-shrink-0 shadow-sm">
          <Calendar className="w-5 h-5" />
        </div>
        <div className="flex items-center gap-2 text-sm text-muted-foreground">
          <Loader2 className="w-3.5 h-3.5 animate-spin" />
          <span>{t('loading')}</span>
        </div>
      </div>
    );
  }

  if (state === 'error') {
    return (
      <div className="flex items-center gap-3">
        <div className="w-10 h-10 rounded-full bg-destructive/15 text-destructive flex items-center justify-center flex-shrink-0 shadow-sm">
          <AlertCircle className="w-5 h-5" />
        </div>
        <span className="text-sm text-destructive">{t('parse_error')}</span>
      </div>
    );
  }

  const iconTone = getMethodIconTone(method, actorSummary?.participationStatus);

  const hasStatusPills = Boolean(
    existingEvent
    || userIsOrganizer
    || (participationLabel && myParticipant)
    || actionNotice
    || (parsedEvent?.status && parsedEvent.status !== 'confirmed')
  );

  const showActionsRow = showDetails && (
    canRespond
    || (supportsCalendar && !existingEvent && allowsImport && !isResponseOnly && !isCancellation)
    || canApplyProposal
    || (supportsCalendar && (existingEvent || parsedEvent))
    || !supportsCalendar
  );

  return (
    <div
      className={cn(
        "flex items-start gap-3 rounded-md transition-colors",
        isCollapsed && "cursor-pointer hover:bg-muted/50",
      )}
      onClick={isCollapsed ? () => setIsCollapsed(false) : undefined}
      onKeyDown={isCollapsed ? (e) => {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          setIsCollapsed(false);
        }
      } : undefined}
      role={isCollapsed ? 'button' : undefined}
      tabIndex={isCollapsed ? 0 : undefined}
      aria-expanded={isCollapsed ? false : undefined}
    >
      {/* Avatar-style icon */}
      <div className={cn(
        "w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 shadow-sm",
        iconTone,
      )}>
        {isCancellation ? (
          <CalendarX className="w-5 h-5" />
        ) : (
          <Calendar className="w-5 h-5" />
        )}
      </div>

      {/* Content column */}
      <div className="flex-1 min-w-0 space-y-2">
        {/* Eyebrow + title + collapse */}
        <div className="flex items-start justify-between gap-2">
          <div className="min-w-0 flex-1">
            <div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
              {bannerTitle}
            </div>
            {summary?.title && (
              <h3 className={cn(
                "text-sm font-semibold leading-snug break-words",
                isCancellation ? "line-through text-muted-foreground" : "text-foreground",
              )}>
                {summary.title}
              </h3>
            )}
          </div>

          <div className="flex items-center gap-1.5 flex-shrink-0">
            {parsedEvent?.sequence != null && parsedEvent.sequence > 0 && (
              <span className="rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground whitespace-nowrap">
                {t('event_updated', { sequence: parsedEvent.sequence })}
              </span>
            )}
            <button
              type="button"
              onClick={(e) => {
                e.stopPropagation();
                setIsCollapsed((prev) => !prev);
              }}
              aria-expanded={!isCollapsed}
              className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors"
            >
              {isCollapsed ? <ChevronDown className="w-3 h-3" /> : <ChevronUp className="w-3 h-3" />}
              {isCollapsed ? t('expand') : t('collapse')}
            </button>
          </div>
        </div>

        {/* Meta rows */}
        {showDetails && summary && (summary.start || summary.location || summary.attendeeCount > 0) && (
          <div className="flex flex-col gap-1 text-sm text-muted-foreground sm:flex-row sm:flex-wrap sm:items-center sm:gap-x-4 sm:gap-y-1">
            {summary.start && (
              <span className="flex items-center gap-1.5 min-w-0">
                <Clock className="w-3.5 h-3.5 flex-shrink-0" />
                <span className="truncate">
                  {formatDateTime(summary.start)}
                  {summary.end && ` – ${formatDateTime(summary.end)}`}
                </span>
              </span>
            )}
            {summary.location && (
              <span className="flex items-center gap-1.5 min-w-0">
                <MapPin className="w-3.5 h-3.5 flex-shrink-0" />
                <span className="truncate">{summary.location}</span>
              </span>
            )}
            {summary.attendeeCount > 0 && (
              <span className="text-muted-foreground/80">{t('attendees', { count: summary.attendeeCount })}</span>
            )}
          </div>
        )}

        {/* Organizer row (clickable, left-aligned) */}
        {showDetails && summary?.organizer && (
          <div className="flex items-center gap-1.5 text-sm text-muted-foreground min-w-0">
            <Users className="w-3.5 h-3.5 flex-shrink-0" />
            <span className="flex-shrink-0">{t('organizer_label')}</span>
            {summary.organizerEmail ? (
              <RecipientPopover
                name={summary.organizer}
                email={summary.organizerEmail}
                className="text-sm truncate"
              />
            ) : (
              <span className="truncate text-foreground">{summary.organizer}</span>
            )}
          </div>
        )}

        {/* Status pills */}
        {showDetails && hasStatusPills && (
          <div className="flex flex-wrap items-center gap-1.5">
            {parsedEvent?.status && parsedEvent.status !== 'confirmed' && (
              <span className={cn(
                "rounded-full px-2 py-0.5 text-[11px] font-medium",
                parsedEvent.status === 'cancelled'
                  ? "bg-destructive/15 text-destructive"
                  : "bg-warning/15 text-warning",
              )}>
                {t(`event_status_${parsedEvent.status}`)}
              </span>
            )}
            {existingEvent && (
              <span className="rounded-full bg-success/15 px-2 py-0.5 text-[11px] font-medium text-success">
                {t('already_in_calendar')}
              </span>
            )}
            {userIsOrganizer && (
              <span className="rounded-full bg-info/15 px-2 py-0.5 text-[11px] font-medium text-info">
                {t('organizer_role')}
              </span>
            )}
            {participationLabel && myParticipant && (
              <span className={cn(
                'rounded-full px-2 py-0.5 text-[11px] font-medium',
                getParticipationTone(currentRsvp),
              )}>
                {t('your_response', { status: participationLabel })}
              </span>
            )}
            {actionNotice && (
              <span className="rounded-full bg-success/15 px-2 py-0.5 text-[11px] font-medium text-success">
                {actionNotice}
              </span>
            )}
          </div>
        )}

        {/* Info / actor messages */}
        {showDetails && (bannerInfo || actorMessage || actorSummary?.participationComment) && (
          <div className="space-y-0.5 text-xs text-muted-foreground">
            {bannerInfo && <p className="leading-relaxed">{bannerInfo}</p>}
            {actorMessage && <p>{actorMessage}</p>}
            {actorSummary?.participationComment && (
              <p className="italic">{t('actor_note', { comment: actorSummary.participationComment })}</p>
            )}
          </div>
        )}

        {/* Trust warning */}
        {showDetails && trustMessage && trustAssessment && (
          <div className={cn(
            'flex items-start gap-2 text-sm rounded-md px-3 py-2 border',
            trustAssessment.level === 'warning'
              ? 'bg-destructive/10 text-destructive border-destructive/30'
              : 'bg-warning/10 text-warning border-warning/30',
          )}>
            <AlertCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
            <span className="flex-1">{trustMessage}</span>
          </div>
        )}

        {/* Proposed changes */}
        {showDetails && proposedChanges.length > 0 && (
          <div className="rounded-md border border-border bg-muted/30 px-3 py-2.5 text-xs">
            <div className="font-medium text-foreground mb-1.5">{t('proposed_changes')}</div>
            <div className="space-y-1.5">
              {proposedChanges.map((change) => (
                <div key={change.label}>
                  <span className="font-medium text-foreground">{change.label}: </span>
                  <span className="text-muted-foreground">{t('change_from_to', { before: change.before, after: change.after })}</span>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Action error */}
        {showDetails && actionError && (
          <div className="flex items-start gap-2 text-sm rounded-md px-3 py-2 bg-destructive/10 text-destructive border border-destructive/30">
            <AlertCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
            <span className="flex-1">{actionError}</span>
          </div>
        )}

        {/* Actions */}
        {showActionsRow && (
          <div className="flex flex-wrap items-center gap-1.5 pt-0.5">
          {canRespond && (
            <>
              <button
                onClick={() => handleRsvp('accepted')}
                disabled={isProcessing}
                aria-pressed={currentRsvp === 'accepted'}
                className={cn(
                  "inline-flex items-center gap-1.5 text-sm font-medium px-3 py-1.5 rounded-md transition-colors min-h-[36px] disabled:opacity-50 border",
                  currentRsvp === 'accepted'
                    ? "bg-success/15 text-success border-success/30"
                    : "text-muted-foreground hover:text-success border-border hover:border-success/30 hover:bg-success/10",
                )}
              >
                <Check className="w-3.5 h-3.5" />
                {t('accept')}
              </button>
              <button
                onClick={() => handleRsvp('tentative')}
                disabled={isProcessing}
                aria-pressed={currentRsvp === 'tentative'}
                className={cn(
                  "inline-flex items-center gap-1.5 text-sm font-medium px-3 py-1.5 rounded-md transition-colors min-h-[36px] disabled:opacity-50 border",
                  currentRsvp === 'tentative'
                    ? "bg-warning/15 text-warning border-warning/30"
                    : "text-muted-foreground hover:text-warning border-border hover:border-warning/30 hover:bg-warning/10",
                )}
              >
                <HelpCircle className="w-3.5 h-3.5" />
                {t('maybe')}
              </button>
              <button
                onClick={() => handleRsvp('declined')}
                disabled={isProcessing}
                aria-pressed={currentRsvp === 'declined'}
                className={cn(
                  "inline-flex items-center gap-1.5 text-sm font-medium px-3 py-1.5 rounded-md transition-colors min-h-[36px] disabled:opacity-50 border",
                  currentRsvp === 'declined'
                    ? "bg-destructive/15 text-destructive border-destructive/30"
                    : "text-muted-foreground hover:text-destructive border-border hover:border-destructive/30 hover:bg-destructive/10",
                )}
              >
                <X className="w-3.5 h-3.5" />
                {t('decline')}
              </button>
              <div className="w-px h-5 bg-border mx-1" />
            </>
          )}

          {supportsCalendar && !existingEvent && allowsImport && !isResponseOnly && !isCancellation && (
            <>
              <button
                ref={pickerTriggerRef}
                onClick={() => {
                  if (calendars.length <= 1) {
                    handleImport();
                    return;
                  }
                  if (showCalendarPicker) {
                    setShowCalendarPicker(false);
                    return;
                  }
                  if (pickerTriggerRef.current) {
                    const rect = pickerTriggerRef.current.getBoundingClientRect();
                    setPickerPosition({ top: rect.bottom + 4, left: rect.left });
                  }
                  setShowCalendarPicker(true);
                }}
                disabled={isProcessing}
                className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground px-3 py-1.5 rounded-md border border-border hover:bg-muted transition-colors min-h-[36px] disabled:opacity-50"
              >
                <CalendarCheck className="w-3.5 h-3.5" />
                {t('add_to_calendar')}
                {calendars.length > 1 && <ChevronDown className="w-3 h-3" />}
              </button>

              {showCalendarPicker && calendars.length > 1 && pickerPosition && typeof document !== 'undefined' && createPortal(
                <div
                  className="fixed w-52 bg-background rounded-lg shadow-lg border border-border z-50 py-1"
                  style={{ top: pickerPosition.top, left: pickerPosition.left }}
                >
                  <div className="px-3 py-1.5 text-xs font-medium text-muted-foreground">
                    {t('select_calendar')}
                  </div>
                  {calendars.map((cal) => (
                    <button
                      key={cal.id}
                      onClick={() => {
                        setShowCalendarPicker(false);
                        handleImport(cal.id);
                      }}
                      className="w-full px-3 py-1.5 text-sm text-left hover:bg-muted flex items-center gap-2"
                    >
                      <span
                        className="w-3 h-3 rounded-full flex-shrink-0"
                        style={{ backgroundColor: sanitizeColor(cal.color) }}
                      />
                      <span className="truncate text-foreground">{cal.name}</span>
                    </button>
                  ))}
                </div>,
                document.body,
              )}
            </>
          )}

          {canApplyProposal && (
            <button
              onClick={handleApplyProposal}
              disabled={isProcessing}
              className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground px-3 py-1.5 rounded-md border border-border hover:bg-muted transition-colors min-h-[36px] disabled:opacity-50"
            >
              <CalendarCheck className="w-3.5 h-3.5" />
              {t('apply_proposal')}
            </button>
          )}

          {supportsCalendar && (existingEvent || parsedEvent) && (
            <button
              onClick={handleViewInCalendar}
              className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground px-3 py-1.5 rounded-md hover:bg-muted transition-colors min-h-[36px]"
            >
              <Calendar className="w-3.5 h-3.5" />
              {viewActionLabel}
              <ArrowRight className="w-3 h-3" />
            </button>
          )}

          {!supportsCalendar && (
            <span className="text-xs text-muted-foreground italic">{t('no_calendar')}</span>
          )}

          {isProcessing && (
            <Loader2 className="w-4 h-4 animate-spin text-muted-foreground ml-auto" />
          )}
        </div>
        )}
      </div>
    </div>
  );
}
