import { useEffect, useState } from 'react';
import {
  EventStatus,
  EventType,
  MatrixEvent,
  Room,
  RoomEvent,
  RoomEventHandlerMap,
} from 'matrix-js-sdk';
import { color } from 'folds';
import { useMatrixClient } from './useMatrixClient';
import { MessageDeliveryStatus, useMessageStatus } from './useMessageStatus';

// `prominent` is true for the «state» dots (green = read, gold = mention, red
// = failed) and false for the neutral gray. The Stream layout draws prominent
// dots 1.1× the size of the neutral ones so they catch the eye on the rail.
export type DotColor = { color: string; opacity: number; prominent: boolean };

// Gray rail dot — every PEER (incoming) dot. Flat colour + opacity so the
// peer side reads as one uniform gray. Theme-aware CSS var (light/dark in
// src/index.css).
const DOT_NEUTRAL = 'var(--vojo-dot-neutral)';

// Own rail dot — «white» (black light / white dark), matching the own nick.
// Reuses the own name token so the two can't drift: my colour is white
// everywhere on the rail (except the green/red/gold status overrides).
const DOT_OWN = 'var(--vojo-stream-name-own)';

// Event types that count as a «message from the peer» for the read-already-
// answered transition. A reaction / membership / redaction from the peer is
// NOT a reply, so it must not demote the green dot. Encrypted events are
// counted before they decrypt (they're still a message the peer sent).
const PEER_MESSAGE_TYPES = new Set<string>([
  EventType.RoomMessage,
  EventType.Sticker,
  EventType.RoomMessageEncrypted,
]);

// Timestamp of the most recent peer (not-me) message in the room's live
// timeline, or 0 if none. Scans from the tail and stops at the first match,
// so it's O(1) in the common case (the peer's last message is near the end).
function getLatestPeerMessageTs(room: Room, myUserId: string): number {
  const events = room.getLiveTimeline().getEvents();
  for (let i = events.length - 1; i >= 0; i -= 1) {
    const ev = events[i];
    const sender = ev.getSender();
    if (sender && sender !== myUserId && !ev.isRedacted() && PEER_MESSAGE_TYPES.has(ev.getType())) {
      return ev.getTs();
    }
  }
  return 0;
}

// DM «VS Code chat» dot decision tree
// (docs/plans/dm_stream_vscode_redesign.md §5):
//   * mention-highlight (push-actions tweaks.highlight) → gold (Warning), full — override
//   * own NOT_SENT / CANCELLED                          → red (Critical), full
//   * own read by peer, no later peer reply             → green (Success), full
//   * own in-flight / sent / answered                   → white (own colour)
//   * incoming (peer)                                   → uniform gray
//
// Per-side colours: OWN dots are white (matching the own nick), except a
// read-and-not-yet-answered one which is prominent green (it demotes back to
// white once the peer replies — a reply is itself proof of reading, like
// WhatsApp/Telegram dropping the read check on answered messages; tracked
// reactively via RoomEvent.Timeline). PEER (incoming) dots are gray. Gold
// (mention) / red (failed) still override either side.
//
// The dot does not carry the author's mxid-hash colour — white/gray/green/gold/
// red only, so the rail reads as a delivery/read ledger (like the VS Code
// reference), not an author legend.
//
// Mention detection goes through the canonical `mx.getPushActionsForEvent`
// path (m.mentions, display-name match, configured keywords, room-mentions via
// power-level) — same as element-web. Don't roll our own mentionsMe() helper.
//
// `replacingEvent()` is consulted so a non-mention edit of a mention drops the
// gold dot and a mention added via edit lights one up.
//
// `hideReadReceipts` mirrors the legacy MessageStatus checkmark suppression:
// when the user opted into «Hide Typing & Read Receipts» own messages never
// flip to green — they stay the uniform gray like everything else.
export function useDotColor(
  room: Room,
  mEvent: MatrixEvent,
  enabled = true,
  hideReadReceipts = false
): DotColor {
  const mx = useMatrixClient();
  const myUserId = mx.getSafeUserId();
  const senderId = mEvent.getSender() ?? '';
  const isOwn = senderId === myUserId;

  const status = useMessageStatus(room, mEvent);

  // Latest peer-reply timestamp — only meaningful for own messages, so the
  // listener is skipped entirely on incoming rows. Subscribes to live
  // timeline events so an own «read» dot demotes from green the instant the
  // peer answers (back-pagination events are ignored — they never add a
  // newer reply).
  const [latestPeerTs, setLatestPeerTs] = useState<number>(() =>
    enabled && isOwn ? getLatestPeerMessageTs(room, myUserId) : 0
  );

  useEffect(() => {
    if (!enabled || !isOwn) return undefined;
    setLatestPeerTs(getLatestPeerMessageTs(room, myUserId));
    const handler: RoomEventHandlerMap[RoomEvent.Timeline] = (_event, r, toStartOfTimeline) => {
      // The listener is bound to THIS room, so only skip when the SDK hands us
      // a *different* room — `r` is typed optional and can arrive undefined,
      // and an `r?.roomId !== room.roomId` guard would then wrongly drop the
      // update, leaving a just-answered dot stuck as the big green one.
      if (r && r.roomId !== room.roomId) return;
      if (toStartOfTimeline) return;
      setLatestPeerTs(getLatestPeerMessageTs(room, myUserId));
    };
    room.on(RoomEvent.Timeline, handler);
    return () => {
      room.removeListener(RoomEvent.Timeline, handler);
    };
  }, [room, myUserId, isOwn, enabled]);

  if (!enabled) {
    return { color: color.Primary.Main, opacity: 1.0, prominent: false };
  }

  const pushActions = mx.getPushActionsForEvent(mEvent.replacingEvent() ?? mEvent);
  if (pushActions?.tweaks?.highlight) {
    return { color: color.Warning.Main, opacity: 1.0, prominent: true };
  }

  if (isOwn) {
    if (mEvent.status === EventStatus.NOT_SENT || mEvent.status === EventStatus.CANCELLED) {
      return { color: color.Critical.Main, opacity: 1.0, prominent: true };
    }
    // Read AND still awaiting a reply (peer hasn't messaged since) → prominent
    // green. Privacy mode (`hideReadReceipts`) suppresses it so we never reveal
    // peer-read state via colour. Everything else (in-flight / sent / answered)
    // is the own white below.
    if (
      !hideReadReceipts &&
      status === MessageDeliveryStatus.Read &&
      latestPeerTs <= mEvent.getTs()
    ) {
      return { color: color.Success.Main, opacity: 1.0, prominent: true };
    }
    return { color: DOT_OWN, opacity: 1.0, prominent: false };
  }

  // Incoming (peer): gray.
  return { color: DOT_NEUTRAL, opacity: 1.0, prominent: false };
}
