import React, { useCallback, useEffect, useState } from 'react';
import { Box, Text } from 'folds';
import { RelationsEvent } from 'matrix-js-sdk/lib/models/relations';
import { RoomEvent } from 'matrix-js-sdk';
import { useMatrixClient } from '../../../hooks/useMatrixClient';

type PollTextValue = Array<{ body: string }> | string;

function extractText(val: PollTextValue | undefined): string {
  if (!val) return '';
  if (typeof val === 'string') return val;
  return val[0]?.body ?? '';
}

type PollAnswer = {
  'm.id'?: string;
  id?: string;
  'm.text'?: PollTextValue;
  'org.matrix.msc3381.poll.answer'?: { body: string };
};

type PollData = {
  question?: { body?: string; 'm.text'?: PollTextValue };
  answers?: PollAnswer[];
};

type VoteState = {
  counts: Map<string, number>;
  myVote: string | null;
  total: number;
};

function computeVotes(
  mx: ReturnType<typeof useMatrixClient>,
  roomId: string,
  eventId: string,
  isStable: boolean,
): VoteState {
  const empty: VoteState = { counts: new Map(), myVote: null, total: 0 };
  const room = mx.getRoom(roomId);
  if (!room) return empty;

  const timelineSet = room.getUnfilteredTimelineSet();
  const stableRels = timelineSet.relations.getChildEventsForEvent(
    eventId,
    'm.reference',
    'm.poll.response',
  );
  const unstableRels = timelineSet.relations.getChildEventsForEvent(
    eventId,
    'org.matrix.msc3381.poll.response' as any,
    'org.matrix.msc3381.poll.response',
  );

  // Collect all response events; per-sender keep only latest
  const latestBySender = new Map<string, { ts: number; answerId: string }>();
  const myUserId = mx.getSafeUserId();

  const processRelations = (rels: typeof stableRels, stable: boolean) => {
    const events = rels?.getRelations() ?? [];
    for (const ev of events) {
      if (ev.isRedacted()) continue;
      const sender = ev.getSender();
      if (!sender) continue;
      const content = ev.getContent();
      let answerId: string | undefined;
      if (stable) {
        answerId = (content['m.selections'] as string[] | undefined)?.[0];
      } else {
        answerId = (
          (content['org.matrix.msc3381.poll.response'] as any)?.answers as string[] | undefined
        )?.[0];
      }
      if (!answerId) continue;
      const ts = ev.getTs();
      const existing = latestBySender.get(sender);
      if (!existing || ts > existing.ts) {
        latestBySender.set(sender, { ts, answerId });
      }
    }
  };

  processRelations(stableRels, true);
  processRelations(unstableRels, false);

  const counts = new Map<string, number>();
  let myVote: string | null = null;
  for (const [sender, { answerId }] of latestBySender) {
    counts.set(answerId, (counts.get(answerId) ?? 0) + 1);
    if (sender === myUserId) myVote = answerId;
  }

  return { counts, myVote, total: latestBySender.size };
}

export function PollContent({
  content,
  roomId,
  eventId,
}: {
  content: Record<string, unknown>;
  roomId?: string;
  eventId?: string;
}) {
  const mx = useMatrixClient();
  const isStable = !!content['m.poll'];

  const poll = (content['m.poll'] ?? content['org.matrix.msc3381.poll.start']) as
    | PollData
    | undefined;

  const [votes, setVotes] = useState<VoteState>(() => {
    if (!roomId || !eventId) return { counts: new Map(), myVote: null, total: 0 };
    return computeVotes(mx, roomId, eventId, isStable);
  });

  // Refresh votes whenever Relations events fire
  const refresh = useCallback(() => {
    if (!roomId || !eventId) return;
    setVotes(computeVotes(mx, roomId, eventId, isStable));
  }, [mx, roomId, eventId, isStable]);

  useEffect(() => {
    if (!roomId || !eventId) return;
    const room = mx.getRoom(roomId);
    if (!room) return;
    const timelineSet = room.getUnfilteredTimelineSet();

    const stableRels = timelineSet.relations.getChildEventsForEvent(
      eventId,
      'm.reference',
      'm.poll.response',
    );
    const unstableRels = timelineSet.relations.getChildEventsForEvent(
      eventId,
      'org.matrix.msc3381.poll.response' as any,
      'org.matrix.msc3381.poll.response',
    );

    stableRels?.on(RelationsEvent.Add, refresh);
    stableRels?.on(RelationsEvent.Remove, refresh);
    stableRels?.on(RelationsEvent.Redaction, refresh);
    unstableRels?.on(RelationsEvent.Add, refresh);
    unstableRels?.on(RelationsEvent.Remove, refresh);
    unstableRels?.on(RelationsEvent.Redaction, refresh);
    // Also listen at room level: if no votes exist yet, the Relations object is null
    // and the listeners above are no-ops. The room timeline event catches the first vote.
    const onTimeline = (ev: any) => {
      const type = ev.getType?.();
      const relatesTo = ev.getContent?.()?.['m.relates_to'];
      if (
        (type === 'm.poll.response' || type === 'org.matrix.msc3381.poll.response') &&
        relatesTo?.event_id === eventId
      ) {
        refresh();
      }
    };
    const room2 = mx.getRoom(roomId);
    room2?.on(RoomEvent.Timeline, onTimeline);

    return () => {
      stableRels?.off(RelationsEvent.Add, refresh);
      stableRels?.off(RelationsEvent.Remove, refresh);
      stableRels?.off(RelationsEvent.Redaction, refresh);
      unstableRels?.off(RelationsEvent.Add, refresh);
      unstableRels?.off(RelationsEvent.Remove, refresh);
      unstableRels?.off(RelationsEvent.Redaction, refresh);
      room2?.off(RoomEvent.Timeline, onTimeline);
    };
  }, [mx, roomId, eventId, refresh]);

  if (!poll) {
    return (
      <Text style={{ opacity: 0.6 }}>
        <i>Poll (unreadable format)</i>
      </Text>
    );
  }

  const questionText =
    extractText((poll.question as any)?.['m.text']) ||
    (poll.question as any)?.body ||
    'Untitled poll';

  const canVote = !!roomId && !!eventId;
  const { counts, myVote, total } = votes;

  const handleVote = (answerId: string) => {
    if (!roomId || !eventId) return;
    // Optimistic local update
    setVotes((prev) => {
      const next = new Map(prev.counts);
      if (prev.myVote) {
        const prevCount = next.get(prev.myVote) ?? 1;
        if (prevCount <= 1) next.delete(prev.myVote);
        else next.set(prev.myVote, prevCount - 1);
      }
      next.set(answerId, (next.get(answerId) ?? 0) + 1);
      return { counts: next, myVote: answerId, total: prev.myVote ? prev.total : prev.total + 1 };
    });
    if (isStable) {
      mx.sendEvent(roomId, 'm.poll.response' as any, {
        'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
        'm.selections': [answerId],
      });
    } else {
      mx.sendEvent(roomId, 'org.matrix.msc3381.poll.response' as any, {
        'm.relates_to': { rel_type: 'm.reference', event_id: eventId },
        'org.matrix.msc3381.poll.response': { answers: [answerId] },
      });
    }
  };

  const answers = poll.answers ?? [];
  const maxVotes = answers.reduce((m, a, i) => {
    const id = a['m.id'] ?? a.id ?? String(i);
    return Math.max(m, counts.get(id) ?? 0);
  }, 0);

  return (
    <Box
      direction="Column"
      gap="200"
      style={{ maxWidth: '340px', paddingTop: '2px', paddingBottom: '4px' }}
    >
      <Box
        alignItems="Center"
        gap="100"
        style={{
          fontSize: '0.68rem',
          fontWeight: 700,
          letterSpacing: '0.12em',
          textTransform: 'uppercase',
          opacity: 0.55,
          marginBottom: '2px',
        }}
      >
        ◉ Poll
      </Box>
      <Text size="T400" style={{ fontWeight: 600 }}>
        {questionText}
      </Text>
      <Box direction="Column" gap="100" style={{ marginTop: '2px' }}>
        {answers.map((answer, i) => {
          const text =
            extractText((answer as any)['m.text']) ||
            (answer as any)['org.matrix.msc3381.poll.answer']?.body ||
            `Option ${i + 1}`;
          const id = answer['m.id'] ?? answer.id ?? String(i);
          const selected = myVote === id;
          const voteCount = counts.get(id) ?? 0;
          const pct = total > 0 ? Math.round((voteCount / total) * 100) : 0;
          return (
            <button
              key={id}
              type="button"
              onClick={canVote ? () => handleVote(id) : undefined}
              style={{
                padding: '7px 12px',
                borderRadius: '8px',
                background: selected ? 'var(--bg-surface-active)' : 'var(--bg-surface-low)',
                border: `1px solid ${
                  selected ? 'var(--text-primary)' : 'var(--bg-surface-border)'
                }`,
                fontSize: '0.88rem',
                lineHeight: 1.4,
                textAlign: 'left',
                cursor: canVote ? 'pointer' : 'default',
                color: 'var(--text-primary)',
                display: 'flex',
                flexDirection: 'column',
                gap: '4px',
                width: '100%',
                position: 'relative',
                overflow: 'hidden',
              }}
            >
              {/* vote progress bar */}
              {total > 0 && (
                <span
                  aria-hidden
                  style={{
                    position: 'absolute',
                    inset: 0,
                    width: `${pct}%`,
                    background: selected ? 'rgba(255,255,255,0.10)' : 'rgba(255,255,255,0.05)',
                    borderRadius: '8px',
                    pointerEvents: 'none',
                  }}
                />
              )}
              <span
                style={{ display: 'flex', alignItems: 'center', gap: '8px', position: 'relative' }}
              >
                <span style={{ flexGrow: 1 }}>{text}</span>
                {selected && (
                  <span style={{ opacity: 0.8, fontSize: '1rem', flexShrink: 0 }}>✓</span>
                )}
                {total > 0 && (
                  <span style={{ opacity: 0.6, fontSize: '0.78rem', flexShrink: 0 }}>{pct}%</span>
                )}
              </span>
            </button>
          );
        })}
      </Box>
      <Text size="T200" style={{ opacity: 0.5, marginTop: '2px' }}>
        <i>
          {total > 0 ? `${total} vote${total === 1 ? '' : 's'} — ` : ''}
          {canVote
            ? myVote
              ? 'click another to change'
              : 'click an option to vote'
            : 'voting not available'}
        </i>
      </Text>
    </Box>
  );
}
