import { Fragment, useEffect, useLayoutEffect, useRef, useState } from 'react';
import type { KeyboardEvent, ReactElement, SyntheticEvent } from 'react';
import type { Conversation, Message, PublicUser } from '@altricade/core';
import {
  PaperclipIcon,
  MicIcon,
  VideoIcon,
  StopIcon,
  SendIcon,
  CloseIcon,
  CheckIcon,
  DoubleCheckIcon,
  EditIcon,
  TrashIcon,
  UsersIcon,
} from '../../../shared/ui';
import { useConversationMessages } from '../model';
import { useRecorder } from '../recorder';
import { MediaMessage, isRoundVideo } from './MediaMessage';
import { MediaViewerProvider } from './MediaViewer';

const QUICK_REACTIONS = ['👍', '❤️', '😂', '🎉'];
const GROUP_WINDOW_MS = 5 * 60 * 1000;

const formatElapsed = (ms: number): string => {
  const total = Math.floor(ms / 1000);
  const minutes = Math.floor(total / 60);
  const seconds = total % 60;
  return `${String(minutes)}:${seconds < 10 ? '0' : ''}${String(seconds)}`;
};

const clock = (iso: string): string =>
  new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });

const dayKey = (iso: string): string => new Date(iso).toDateString();

const dayLabel = (iso: string): string => {
  const then = new Date(iso);
  const now = new Date();
  if (then.toDateString() === now.toDateString()) {
    return 'Today';
  }
  const yesterday = new Date(now);
  yesterday.setDate(now.getDate() - 1);
  if (then.toDateString() === yesterday.toDateString()) {
    return 'Yesterday';
  }
  return then.toLocaleDateString([], { day: 'numeric', month: 'long' });
};

// Two messages belong to the same visual group when they share a sender and
// day and land within the grouping window (order-agnostic).
const inSameGroup = (base: Message, other: Message | undefined): boolean => {
  if (other === undefined) {
    return false;
  }
  return (
    other.senderId === base.senderId &&
    dayKey(other.createdAt) === dayKey(base.createdAt) &&
    Math.abs(new Date(base.createdAt).getTime() - new Date(other.createdAt).getTime()) <
      GROUP_WINDOW_MS
  );
};

interface Props {
  conversation: Conversation;
  me: PublicUser;
  onlineMap: Record<string, boolean>;
}

const headerTitle = (conversation: Conversation): string => {
  if (conversation.type === 'group') {
    return conversation.title ?? 'Group';
  }
  if (conversation.peer === null) {
    return 'Direct';
  }
  return conversation.peer.displayName;
};

const ChatHeader = ({
  conversation,
  online,
  typing,
}: {
  conversation: Conversation;
  online: boolean;
  typing: boolean;
}): ReactElement => {
  const peer = conversation.peer;
  const isGroup = conversation.type === 'group';
  const subtitle = typing
    ? 'typing…'
    : isGroup
      ? 'Group'
      : online
        ? 'online'
        : 'offline';
  return (
    <header className="chat-header">
      {peer !== null && peer.avatarUrl !== null ? (
        <img src={peer.avatarUrl} alt="" className="avatar chat-header-avatar" />
      ) : (
        <span className="avatar chat-header-avatar avatar-placeholder">
          {isGroup ? <UsersIcon size={20} /> : headerTitle(conversation).charAt(0)}
        </span>
      )}
      <div className="chat-header-text">
        <span className="chat-header-title">{headerTitle(conversation)}</span>
        <span className={typing ? 'chat-header-sub typing-sub' : 'chat-header-sub'}>
          {!isGroup && online && !typing ? <span className="online-inline" /> : null}
          {subtitle}
        </span>
      </div>
    </header>
  );
};

export const ChatView = ({ conversation, me, onlineMap }: Props): ReactElement => {
  const {
    messages,
    loading,
    typingUserIds,
    peerReadSeq,
    send,
    sendMedia,
    edit,
    remove,
    toggleReaction,
    notifyTyping,
  } = useConversationMessages(conversation, me);
  const [text, setText] = useState('');
  const [editingId, setEditingId] = useState<string | null>(null);
  const [draft, setDraft] = useState('');
  const recorder = useRecorder((file) => {
    void sendMedia(file, '');
  });
  const previewRef = useRef<HTMLVideoElement>(null);
  const scrollRef = useRef<HTMLDivElement>(null);
  const atBottomRef = useRef(true);
  const isGroup = conversation.type === 'group';
  const peerOnline = conversation.peer !== null && onlineMap[conversation.peer.id] === true;

  // Mirror the live camera stream into the in-composer preview while recording.
  useEffect(() => {
    const element = previewRef.current;
    if (element !== null) {
      element.srcObject = recorder.previewStream;
    }
  }, [recorder.previewStream]);

  // Jump to the newest message when opening a conversation.
  useEffect(() => {
    atBottomRef.current = true;
  }, [conversation.id]);

  // Keep the scrollback pinned to the newest message — but only when the reader
  // is already near the bottom, so scrolling up through history isn't yanked.
  useLayoutEffect(() => {
    const node = scrollRef.current;
    if (node !== null && atBottomRef.current) {
      node.scrollTop = node.scrollHeight;
    }
  }, [messages.length, typingUserIds.length]);

  const onScroll = (): void => {
    const node = scrollRef.current;
    if (node !== null) {
      atBottomRef.current = node.scrollHeight - node.scrollTop - node.clientHeight < 80;
    }
  };

  const submit = async (event: SyntheticEvent): Promise<void> => {
    event.preventDefault();
    const trimmed = text.trim();
    if (trimmed === '') {
      return;
    }
    setText('');
    await send(trimmed);
  };

  const onComposerKey = (event: KeyboardEvent<HTMLTextAreaElement>): void => {
    if (event.key === 'Enter' && !event.shiftKey) {
      event.preventDefault();
      void submit(event);
    }
  };

  const onAttach = (event: SyntheticEvent<HTMLInputElement>): void => {
    const input = event.currentTarget;
    const file = input.files?.[0];
    if (file !== undefined) {
      void sendMedia(file, '');
    }
    input.value = '';
  };

  const startEdit = (message: Message): void => {
    setEditingId(message.id);
    setDraft(message.content);
  };

  const commitEdit = async (messageId: string): Promise<void> => {
    const next = draft.trim();
    setEditingId(null);
    if (next !== '') {
      await edit(messageId, next);
    }
  };

  const tick = (message: Message): ReactElement | null => {
    if (conversation.type !== 'direct' || message.senderId !== me.id || message.deletedAt !== null) {
      return null;
    }
    const read = peerReadSeq >= message.seq;
    return (
      <span className={read ? 'tick tick-read' : 'tick'}>
        {read ? <DoubleCheckIcon size={15} /> : <CheckIcon size={15} />}
      </span>
    );
  };

  return (
    <MediaViewerProvider>
      <section className="chat-pane">
        <ChatHeader
          conversation={conversation}
          online={peerOnline}
          typing={typingUserIds.length > 0}
        />

      <div className="message-scroll" ref={scrollRef} onScroll={onScroll}>
        {loading ? <p className="chat-loading muted">Loading…</p> : null}
        <div className="message-list">
          {messages.map((message, index) => {
            const prev = index > 0 ? messages[index - 1] : undefined;
            const next = index < messages.length - 1 ? messages[index + 1] : undefined;
            const mine = message.senderId === me.id;
            const newDay = prev === undefined || dayKey(prev.createdAt) !== dayKey(message.createdAt);
            const groupStart = newDay || !inSameGroup(message, prev);
            const groupEnd = !inSameGroup(message, next);
            const showAvatar = !mine && isGroup;
            const deleted = message.deletedAt !== null;
            const editing = editingId === message.id;
            const media = message.media;
            // Round video notes render without bubble chrome (like Telegram).
            const bareMedia =
              media !== null && !deleted && media.kind === 'video' && isRoundVideo(media.name);
            const hasMedia = media !== null && !deleted && !bareMedia;

            const lineClass = [
              'msg-line',
              mine ? 'mine' : 'theirs',
              groupStart ? 'group-start' : '',
              groupEnd ? 'group-end' : '',
            ]
              .filter((token) => token !== '')
              .join(' ');

            const bubbleClass = [
              'bubble',
              mine ? 'bubble-mine' : 'bubble-theirs',
              groupStart ? 'is-start' : '',
              groupEnd ? 'is-end' : '',
              bareMedia ? 'bubble-bare' : '',
              hasMedia ? 'has-media' : '',
            ]
              .filter((token) => token !== '')
              .join(' ');

            return (
              <Fragment key={message.id}>
                {newDay ? (
                  <div className="day-sep">
                    <span>{dayLabel(message.createdAt)}</span>
                  </div>
                ) : null}
                <div className={lineClass}>
                  {showAvatar ? (
                    groupEnd ? (
                      message.sender.avatarUrl !== null ? (
                        <img src={message.sender.avatarUrl} alt="" className="avatar msg-avatar" />
                      ) : (
                        <span className="avatar msg-avatar avatar-placeholder">
                          {message.sender.displayName.charAt(0)}
                        </span>
                      )
                    ) : (
                      <span className="msg-avatar-spacer" />
                    )
                  ) : null}

                  <div className="bubble-wrap">
                    {showAvatar && groupStart && !deleted ? (
                      <span className="msg-sender">{message.sender.displayName}</span>
                    ) : null}

                    <div className={bubbleClass}>
                      {editing ? (
                        <div className="bubble-edit">
                          <textarea
                            className="edit-input"
                            value={draft}
                            autoFocus
                            onChange={(event) => {
                              setDraft(event.target.value);
                            }}
                            onKeyDown={(event) => {
                              if (event.key === 'Enter' && !event.shiftKey) {
                                event.preventDefault();
                                void commitEdit(message.id);
                              }
                              if (event.key === 'Escape') {
                                setEditingId(null);
                              }
                            }}
                          />
                          <div className="edit-actions">
                            <button
                              type="button"
                              className="icon-btn edit-cancel"
                              aria-label="Cancel"
                              onClick={() => {
                                setEditingId(null);
                              }}
                            >
                              <CloseIcon size={17} />
                            </button>
                            <button
                              type="button"
                              className="icon-btn accent edit-save"
                              aria-label="Save"
                              onClick={() => {
                                void commitEdit(message.id);
                              }}
                            >
                              <CheckIcon size={17} />
                            </button>
                          </div>
                        </div>
                      ) : deleted ? (
                        <span className="bubble-deleted">Message deleted</span>
                      ) : (
                        <>
                          {message.media !== null ? (
                            <MediaMessage
                              conversationId={conversation.id}
                              message={message}
                              mine={mine}
                            />
                          ) : null}
                          {message.content.length > 0 ? (
                            <span className="bubble-text">{message.content}</span>
                          ) : null}
                          <span className="bubble-meta">
                            {message.editedAt !== null ? (
                              <span className="bubble-edited">edited</span>
                            ) : null}
                            <span className="bubble-time">{clock(message.createdAt)}</span>
                            {tick(message)}
                          </span>
                        </>
                      )}

                      {!deleted && !editing ? (
                        <div className="msg-tools">
                          {QUICK_REACTIONS.map((emoji) => (
                            <button
                              key={emoji}
                              type="button"
                              className="tool-react"
                              aria-label={`React ${emoji}`}
                              onClick={() => {
                                void toggleReaction(message, emoji);
                              }}
                            >
                              {emoji}
                            </button>
                          ))}
                          {mine ? (
                            <>
                              <span className="tool-divider" />
                              <button
                                type="button"
                                className="tool-btn"
                                aria-label="Edit"
                                onClick={() => {
                                  startEdit(message);
                                }}
                              >
                                <EditIcon size={16} />
                              </button>
                              <button
                                type="button"
                                className="tool-btn tool-danger"
                                aria-label="Delete"
                                onClick={() => {
                                  void remove(message.id);
                                }}
                              >
                                <TrashIcon size={16} />
                              </button>
                            </>
                          ) : null}
                        </div>
                      ) : null}
                    </div>

                    {message.reactions.length > 0 ? (
                      <div className="reactions">
                        {message.reactions.map((reaction) => (
                          <button
                            key={reaction.emoji}
                            type="button"
                            className={reaction.mine ? 'reaction is-mine' : 'reaction'}
                            onClick={() => {
                              void toggleReaction(message, reaction.emoji);
                            }}
                          >
                            <span className="reaction-emoji">{reaction.emoji}</span>
                            <span className="reaction-count">{reaction.count}</span>
                          </button>
                        ))}
                      </div>
                    ) : null}
                  </div>
                </div>
              </Fragment>
            );
          })}
        </div>
      </div>

      {recorder.recording !== null ? (
        <div className="composer recording-bar">
          {recorder.recording === 'video' ? (
            <video ref={previewRef} className="record-preview" autoPlay muted playsInline />
          ) : null}
          <span className="record-dot" aria-hidden="true" />
          <span className="record-label">
            {recorder.recording === 'video' ? 'Recording video' : 'Recording voice'} ·{' '}
            {formatElapsed(recorder.elapsedMs)}
          </span>
          <button type="button" className="icon-btn" title="Cancel" onClick={recorder.cancel}>
            <CloseIcon />
          </button>
          <button
            type="button"
            className="icon-btn accent"
            title="Stop and send"
            onClick={recorder.finish}
          >
            <StopIcon />
          </button>
        </div>
      ) : (
        <form className="composer" onSubmit={(event) => void submit(event)}>
          <label className="icon-btn" title="Attach file">
            <PaperclipIcon />
            <input type="file" hidden onChange={onAttach} />
          </label>
          <textarea
            className="composer-input"
            rows={1}
            value={text}
            placeholder="Message…"
            onChange={(event) => {
              setText(event.target.value);
              notifyTyping();
            }}
            onKeyDown={onComposerKey}
          />
          <button
            type="button"
            className="icon-btn"
            title="Record voice message"
            onClick={() => {
              recorder.start('voice');
            }}
          >
            <MicIcon />
          </button>
          <button
            type="button"
            className="icon-btn"
            title="Record video message"
            onClick={() => {
              recorder.start('video');
            }}
          >
            <VideoIcon />
          </button>
          <button
            type="submit"
            className="icon-btn accent composer-send"
            title="Send"
            disabled={text.trim() === ''}
          >
            <SendIcon />
          </button>
        </form>
      )}
      </section>
    </MediaViewerProvider>
  );
};
