"use client";

import Link from "next/link";
import { Suspense, useEffect, useMemo, useState, type ReactNode } from "react";
import type { Message, ThreadParticipant, ThreadSummary } from "@slackwsh/contracts";
import { AppShell, useWorkspaceIdParam } from "../../components/AppShell";
import {
  IconAt,
  IconClip,
  IconLock,
  IconMessages,
  IconMic,
  IconMoreVertical,
  IconSend,
  IconSmile,
  IconVideo,
} from "../../components/icons";
import { RichTextMessage } from "../../components/RichTextMessage";
import { ForwardedMessageCard, forwardedMessageFromBlocks } from "../../components/ForwardedMessageCard";
import { UserAvatar } from "../../components/UserAvatar";
import { api } from "../../lib/api";
import { avatarColor } from "../../lib/avatar";
import { formatWhenLabel } from "../../lib/datetime";

function renderMentionToken(token: string, key: string) {
  const clean = token.slice(1);
  const special = clean === "here" || clean === "channel" || clean === "everyone";
  return (
    <span className={special ? "msg-mention special" : "msg-mention"} key={key}>
      {token}
    </span>
  );
}

function renderTextWithMentions(text: string): ReactNode {
  const parts: ReactNode[] = [];
  const pattern = /(`[^`\n]+`|\*\*[^*\n]+\*\*|~~[^~\n]+~~|<u>[^<\n]+<\/u>|_[^_\n]+_|@(here|channel|everyone|[a-zA-Z0-9._-]{1,80}))/g;
  let lastIndex = 0;
  for (const match of text.matchAll(pattern)) {
    const index = match.index ?? 0;
    if (index > lastIndex) parts.push(text.slice(lastIndex, index));
    const token = match[0];
    const key = `${index}-${token}`;
    if (token.startsWith("@")) {
      parts.push(renderMentionToken(token, key));
    } else if (token.startsWith("**")) {
      parts.push(<strong className="msg-bold" key={key}>{renderTextWithMentions(token.slice(2, -2))}</strong>);
    } else if (token.startsWith("~~")) {
      parts.push(<s className="msg-strike" key={key}>{renderTextWithMentions(token.slice(2, -2))}</s>);
    } else if (token.startsWith("<u>")) {
      parts.push(<u className="msg-underline" key={key}>{renderTextWithMentions(token.slice(3, -4))}</u>);
    } else if (token.startsWith("_")) {
      parts.push(<em className="msg-italic" key={key}>{renderTextWithMentions(token.slice(1, -1))}</em>);
    } else {
      parts.push(<code className="msg-code" key={key}>{token.slice(1, -1)}</code>);
    }
    lastIndex = index + match[0].length;
  }
  if (lastIndex < text.length) parts.push(text.slice(lastIndex));
  return parts.length > 0 ? parts : text;
}

function participantFor(message: Message, participants: ThreadParticipant[]) {
  return participants.find((participant) => String(participant.userId) === String(message.authorId));
}

function authorName(message: Message, participants: ThreadParticipant[]) {
  return participantFor(message, participants)?.name ?? `User ${message.authorId}`;
}

function participantSummary(threads: ThreadSummary[]) {
  const names = threads
    .flatMap((thread) => thread.participants.map((participant) => participant.name))
    .filter((name, index, allNames) => allNames.indexOf(name) === index);
  if (names.length === 0) return "Followed by you";
  if (names.length === 1) return names[0];
  if (names.length === 2) return `${names[0]} and ${names[1]}`;
  if (names.length === 3) return `${names[0]}, ${names[1]}, and ${names[2]}`;
  return `${names[0]}, ${names[1]}, and ${names.length - 2} others`;
}

function ThreadMessage({
  message,
  participants,
  root = false,
}: {
  message: Message;
  participants: ThreadParticipant[];
  root?: boolean;
}) {
  const participant = participantFor(message, participants);
  const name = authorName(message, participants);
  const color = avatarColor(String(message.authorId));
  const forwarded = forwardedMessageFromBlocks(message.blocks);
  return (
    <div className={root ? "threads-slack-message root" : "threads-slack-message"}>
      <UserAvatar
        className="threads-slack-avatar"
        userId={message.authorId}
        name={name}
        avatarUrl={participant?.avatarUrl}
        style={{ background: color.bg, color: color.fg }}
      />
      <div className="threads-slack-message-body">
        <div className="threads-slack-meta">
          <strong>{name}</strong>
          <span>{formatWhenLabel(message.createdAt)}</span>
        </div>
        {message.text && (
          <RichTextMessage
            blocks={message.blocks}
            fallback={message.text}
            className="threads-slack-text"
            renderText={renderTextWithMentions}
          />
        )}
        {forwarded && <ForwardedMessageCard snapshot={forwarded} workspaceId={message.workspaceId} renderText={renderTextWithMentions} />}
      </div>
    </div>
  );
}

function ThreadReplyComposer({
  channelName,
  disabled,
  draft,
  onDraftChange,
  onSubmit,
}: {
  channelName: string;
  disabled: boolean;
  draft: string;
  onDraftChange: (value: string) => void;
  onSubmit: () => void;
}) {
  return (
    <form
      className="threads-slack-composer"
      onSubmit={(event) => {
        event.preventDefault();
        onSubmit();
      }}
    >
      <div className="threads-slack-formatbar" aria-hidden="true">
        <span>B</span>
        <span><em>I</em></span>
        <span><u>U</u></span>
        <span><s>S</s></span>
        <i />
        <span>1.</span>
        <span>-</span>
        <span>{"</>"}</span>
      </div>
      <textarea
        value={draft}
        onChange={(event) => onDraftChange(event.target.value)}
        placeholder="Reply..."
        rows={2}
        aria-label={`Reply in ${channelName}`}
      />
      <div className="threads-slack-composer-foot">
        <div className="threads-slack-tools" aria-hidden="true">
          <span>+</span>
          <span>Aa</span>
          <IconSmile />
          <IconAt />
          <IconVideo />
          <IconMic />
          <IconClip />
        </div>
        <button type="submit" aria-label="Send reply" disabled={disabled || !draft.trim()}>
          <IconSend />
        </button>
      </div>
    </form>
  );
}

function ThreadsView() {
  const workspaceId = useWorkspaceIdParam();
  const [threads, setThreads] = useState<ThreadSummary[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [drafts, setDrafts] = useState<Record<string, string>>({});
  const [sendingId, setSendingId] = useState<string | null>(null);
  const [openMenuId, setOpenMenuId] = useState<string | null>(null);

  useEffect(() => {
    if (!workspaceId) return;
    let cancelled = false;
    let refreshing = false;
    const refresh = () => {
      if (refreshing) return;
      refreshing = true;
      void api.followedThreads(workspaceId).then((res) => {
        if (!cancelled) {
          setThreads(res.threads);
          setError(null);
        }
      }).catch(() => {
        if (!cancelled) setError("Could not load followed threads.");
      }).finally(() => {
        refreshing = false;
        if (!cancelled) setLoading(false);
      });
    };
    const onRealtime = (event: Event) => {
      const detail = (event as CustomEvent<{
        workspaceId?: string | number;
        message?: { workspaceId?: string | number; parentId?: string | number | null };
        status?: { workspaceId?: string | number };
      }>).detail;
      const changedWorkspaceId = detail?.workspaceId ?? detail?.message?.workspaceId ?? detail?.status?.workspaceId;
      if (changedWorkspaceId == null || String(changedWorkspaceId) === workspaceId) refresh();
    };
    refresh();
    window.addEventListener("slackwsh:message", onRealtime);
    window.addEventListener("slackwsh:thread-subscription-updated", onRealtime);
    return () => {
      cancelled = true;
      window.removeEventListener("slackwsh:message", onRealtime);
      window.removeEventListener("slackwsh:thread-subscription-updated", onRealtime);
    };
  }, [workspaceId]);

  const groupedThreads = useMemo(() => {
    const groups = new Map<string, ThreadSummary[]>();
    for (const thread of threads) {
      const key = thread.channelName || "conversation";
      groups.set(key, [...(groups.get(key) ?? []), thread]);
    }
    return [...groups.entries()].map(([channelName, items]) => ({ channelName, items }));
  }, [threads]);

  async function unfollow(thread: ThreadSummary) {
    if (!workspaceId) return;
    const previous = threads;
    setThreads((current) => current.filter((item) => item.rootMessage.id !== thread.rootMessage.id));
    try {
      await api.setThreadFollowing(
        workspaceId,
        String(thread.rootMessage.channelId),
        thread.rootMessage.id,
        false,
      );
    } catch {
      setThreads(previous);
      setError("Could not unfollow the thread.");
    }
  }

  async function sendReply(thread: ThreadSummary) {
    if (!workspaceId || sendingId) return;
    const rootId = String(thread.rootMessage.id);
    const text = drafts[rootId]?.trim();
    if (!text) return;
    setSendingId(rootId);
    try {
      await api.sendMessage(workspaceId, String(thread.rootMessage.channelId), {
        clientMsgId: crypto.randomUUID(),
        text,
        parentId: rootId,
      });
      setDrafts((current) => ({ ...current, [rootId]: "" }));
      const res = await api.followedThreads(workspaceId);
      setThreads(res.threads);
      setError(null);
    } catch {
      setError("Could not send the reply.");
    } finally {
      setSendingId(null);
    }
  }

  return (
    <AppShell active="messages" title="Threads">
      <div className="screen-body threads-slack-body">
        {error && <p className="error-text">{error}</p>}
        <div className="threads-slack-list">
          {groupedThreads.map((group) => (
            <section className="threads-slack-channel" key={group.channelName}>
              <div className="threads-slack-channel-head">
                <span><IconLock size={13} />{group.channelName}</span>
                <small>{participantSummary(group.items)}</small>
              </div>

              {group.items.map((thread) => {
                const latest = thread.latestReply;
                const route = `/channel?workspaceId=${workspaceId}&channelId=${thread.rootMessage.channelId}&threadRootId=${thread.rootMessage.id}`;
                const hiddenReplies = Math.max(0, thread.rootMessage.threadReplyCount - (latest ? 1 : 0));
                const rootId = String(thread.rootMessage.id);
                return (
                  <article className="threads-slack-card" key={thread.rootMessage.id}>
                    <div className="threads-slack-card-toolbar">
                      <Link href={route} aria-label="Open thread in conversation" title="Open in conversation">
                        <IconMessages size={16} />
                      </Link>
                      <button
                        type="button"
                        aria-label="More thread actions"
                        title="More actions"
                        aria-haspopup="menu"
                        aria-expanded={openMenuId === rootId}
                        onClick={() => setOpenMenuId((current) => current === rootId ? null : rootId)}
                      >
                        <IconMoreVertical size={16} />
                      </button>
                      {openMenuId === rootId && (
                        <div className="threads-slack-action-menu" role="menu" aria-label="Thread actions">
                          <Link href={route} role="menuitem" onClick={() => setOpenMenuId(null)}>
                            Open in conversation
                          </Link>
                          <button
                            type="button"
                            role="menuitem"
                            onClick={() => {
                              setOpenMenuId(null);
                              void unfollow(thread);
                            }}
                          >
                            Unfollow thread
                          </button>
                        </div>
                      )}
                    </div>
                    <ThreadMessage message={thread.rootMessage} participants={thread.participants} root />

                    {hiddenReplies > 0 && (
                      <Link className="threads-slack-more" href={route}>
                        Show {hiddenReplies} more {hiddenReplies === 1 ? "reply" : "replies"}
                      </Link>
                    )}

                    {latest && thread.unreadReplyCount > 0 && (
                      <div className="threads-slack-new-divider">
                        <i />
                        <span>New</span>
                      </div>
                    )}

                    {latest && <ThreadMessage message={latest} participants={thread.participants} />}

                    <ThreadReplyComposer
                      channelName={group.channelName}
                      disabled={sendingId === rootId}
                      draft={drafts[rootId] ?? ""}
                      onDraftChange={(value) => setDrafts((current) => ({ ...current, [rootId]: value }))}
                      onSubmit={() => void sendReply(thread)}
                    />
                  </article>
                );
              })}
            </section>
          ))}
          {loading && <div className="empty-state">Loading threads...</div>}
          {!loading && threads.length === 0 && (
            <div className="empty-state">No followed threads yet. Reply to a message or select Follow in a thread.</div>
          )}
        </div>
      </div>
    </AppShell>
  );
}

export default function ThreadsPage() {
  return (
    <Suspense fallback={<main className="page"><div className="empty-state">Loading threads...</div></main>}>
      <ThreadsView />
    </Suspense>
  );
}
