import { isOfficialChannel } from '@linktr.ee/messaging-taxonomy'
import classNames from 'classnames'
import React, { useCallback, useEffect, useRef } from 'react'
import { Channel as ChannelType } from 'stream-chat'
import {
  Channel,
  Window,
  MessageList,
  useMessageContext,
  useChannelStateContext,
  WithComponents,
  MessageUIComponentProps,
} from 'stream-chat-react'

import type { ChannelViewProps } from '../types'

import ChannelHeader from './ChannelHeader'
import { CustomDateSeparator } from './CustomDateSeparator'
import { CustomMessage } from './CustomMessage'
import { CustomMessageActions } from './CustomMessage/CustomMessageActions'
import {
  OutboundContext,
  useOutboundsByRecordId,
} from './CustomMessage/OutboundContext'
import { CustomMessageInput } from './CustomMessageInput'
import { CustomSystemMessage } from './CustomSystemMessage'
import CustomTypingIndicator from './CustomTypingIndicator'
import { DmAgentEnabledContext } from './CustomTypingIndicator/DmAgentContext'
import { ChannelEmptyState } from './MessagingShell/ChannelEmptyState'
import { LoadingState } from './MessagingShell/LoadingState'

/**
 * Inner component that has access to channel context
 */
const ChannelViewInner: React.FC<{
  onBack?: () => void
  showBackButton: boolean
  renderMessageInputActions?: (channel: ChannelType) => React.ReactNode
  renderMessageInputFooter?: (channel: ChannelType) => React.ReactNode
  renderConversationFooter?: (channel: ChannelType) => React.ReactNode
  onLeaveConversation?: (channel: ChannelType) => void
  onBlockParticipant?: (participantId?: string) => void
  CustomChannelEmptyState?: React.ComponentType
  showDeleteConversation?: boolean
  onDeleteConversationClick?: () => void
  onBlockParticipantClick?: () => void
  onReportParticipantClick?: () => void
  showBlockParticipant?: boolean
  showReportParticipant?: boolean
  composerDisabled?: boolean
  composerDisabledReason?: string
  showStarButton?: boolean
  renderChannelBanner?: () => React.ReactNode
  customChannelActions?: React.ReactNode
  renderChannelActions?: React.ReactNode
  renderMessage?: (
    messageNode: React.ReactElement,
    message: NonNullable<MessageUIComponentProps['message']>
  ) => React.ReactNode
  viewerLanguage?: string
  showChannelInfo?: boolean
  onParticipantNameClick?: () => void
  onOutboundClick?: ChannelViewProps['onOutboundClick']
  resolveOutbound?: ChannelViewProps['resolveOutbound']
  composerInput?: ChannelViewProps['composerInput']
}> = ({
  onBack,
  showBackButton,
  renderMessageInputActions,
  renderMessageInputFooter,
  renderConversationFooter,
  onLeaveConversation,
  onBlockParticipant,
  showDeleteConversation = true,
  onDeleteConversationClick,
  onBlockParticipantClick,
  onReportParticipantClick,
  showBlockParticipant = true,
  showReportParticipant = true,
  composerDisabled = false,
  composerDisabledReason,
  showStarButton = false,
  renderChannelBanner,
  customChannelActions,
  renderChannelActions,
  renderMessage,
  viewerLanguage,
  showChannelInfo = true,
  onParticipantNameClick,
  onOutboundClick,
  resolveOutbound,
  composerInput,
}) => {
  const { channel, messages } = useChannelStateContext()
  const onOutboundClickRef = useRef(onOutboundClick)
  useEffect(() => {
    onOutboundClickRef.current = onOutboundClick
  }, [onOutboundClick])
  const stableOnOutboundClick = useCallback<
    NonNullable<ChannelViewProps['onOutboundClick']>
  >((outbound) => {
    onOutboundClickRef.current?.(outbound)
  }, [])
  const messageOutboundClick = onOutboundClick
    ? stableOnOutboundClick
    : undefined
  // Resolve outside memoized Stream rows so current automation details publish
  // without remounting the rows.
  const outboundsByRecordId = useOutboundsByRecordId(messages, resolveOutbound)

  const hasVisibleMessages = channel.state.messages.some(
    (message) => message.type !== 'deleted'
  )
  const channelBanner = renderChannelBanner ? (
    <React.Fragment key="lt-channel-banner">
      {renderChannelBanner()}
    </React.Fragment>
  ) : null
  const channelHeader = (
    <ChannelHeader
      className="-mx-[var(--str-chat\_\_spacing-2)] px-[var(--str-chat\_\_spacing-2)] md:-mx-[min(var(--str-chat\_\_spacing-10),4%)] md:px-[min(var(--str-chat\_\_spacing-10),4%)]"
      onBack={onBack}
      showBackButton={showBackButton}
      showChannelInfo={showChannelInfo}
      showStarButton={showStarButton}
      onLeaveConversation={onLeaveConversation}
      onBlockParticipant={onBlockParticipant}
      showDeleteConversation={showDeleteConversation}
      showBlockParticipant={showBlockParticipant}
      showReportParticipant={showReportParticipant}
      onDeleteConversationClick={onDeleteConversationClick}
      onBlockParticipantClick={onBlockParticipantClick}
      onReportParticipantClick={onReportParticipantClick}
      customChannelActions={customChannelActions}
      renderChannelActions={renderChannelActions}
      onParticipantNameClick={onParticipantNameClick}
    />
  )

  // Prevents all message instances from unmounting when ChannelViewInner re-renders
  const MessageOverride = useCallback(
    (props: MessageUIComponentProps) => {
      // eslint-disable-next-line react-hooks/rules-of-hooks
      const { message } = useMessageContext('ChannelView')
      const messageNode = (
        <CustomMessage
          {...props}
          viewerLanguage={viewerLanguage}
          onOutboundClick={messageOutboundClick}
        />
      )

      if (!renderMessage || !message) {
        return messageNode
      }

      return renderMessage(messageNode, message)
    },
    [messageOutboundClick, renderMessage, viewerLanguage]
  )

  return (
    <OutboundContext.Provider value={outboundsByRecordId}>
      <WithComponents
        overrides={{
          Message: MessageOverride,
          MessageActions: CustomMessageActions,
          UnreadMessagesNotification: () => null,
          UnreadMessagesSeparator: () => null,
        }}
      >
        <Window>
          {composerInput ? (
            <div className="messaging-channel-layout relative flex min-h-0 flex-1 flex-col">
              {/* Message List */}
              <div
                key="lt-channel-message-list"
                className="messaging-channel-message-list messaging-channel-message-list--floating relative min-h-0 flex-1 overflow-hidden"
              >
                {/* Stream only renders MessageList.head when there are messages, so
                empty channels need the header and banner in normal flow. */}
                {!hasVisibleMessages && (
                  <div className="str-chat__list">
                    <div className="str-chat__message-list-scroll">
                      {channelHeader}
                      {channelBanner}
                    </div>
                  </div>
                )}

                <MessageList
                  head={
                    hasVisibleMessages ? (
                      <div className="sticky top-0 z-10">
                        {channelHeader}
                        {channelBanner}
                      </div>
                    ) : undefined
                  }
                  hideDeletedMessages
                  hideNewMessageSeparator={false}
                />
              </div>

              {/* Message Input — conversation footer is rendered inside the
              floating chrome (via renderHeader) so it stays above the absolute
              composer and remains interactive. */}
              <CustomMessageInput
                key="lt-channel-message-input"
                {...(renderMessageInputActions && {
                  renderActions: () => renderMessageInputActions?.(channel),
                })}
                {...(renderConversationFooter && {
                  renderHeader: () => renderConversationFooter(channel),
                })}
                renderFooter={() => renderMessageInputFooter?.(channel)}
                disabled={composerDisabled}
                disabledReason={composerDisabledReason}
                composerInput={composerInput}
              />
            </div>
          ) : (
            <>
              {/* Message List */}
              <div
                key="lt-channel-message-list"
                className="flex-1 overflow-hidden relative"
              >
                {/* Stream only renders MessageList.head when there are messages, so
                empty channels need the header and banner in normal flow. */}
                {!hasVisibleMessages && (
                  <div className="str-chat__list">
                    <div className="str-chat__message-list-scroll">
                      {channelHeader}
                      {channelBanner}
                    </div>
                  </div>
                )}

                <MessageList
                  head={
                    hasVisibleMessages ? (
                      <div className="sticky top-0 z-10">
                        {channelHeader}
                        {channelBanner}
                      </div>
                    ) : undefined
                  }
                  hideDeletedMessages
                  hideNewMessageSeparator={false}
                />
              </div>

              {renderConversationFooter ? (
                <React.Fragment key="lt-channel-conversation-footer">
                  {renderConversationFooter(channel)}
                </React.Fragment>
              ) : null}

              {/* Message Input */}
              <CustomMessageInput
                key="lt-channel-message-input"
                {...(renderMessageInputActions && {
                  renderActions: () => renderMessageInputActions?.(channel),
                })}
                renderFooter={() => renderMessageInputFooter?.(channel)}
                disabled={composerDisabled}
                disabledReason={composerDisabledReason}
                composerInput={composerInput}
              />
            </>
          )}
        </Window>
      </WithComponents>
    </OutboundContext.Provider>
  )
}

/**
 * Channel view component with message list and input
 */
export const ChannelView = React.memo<ChannelViewProps>(
  ({
    channel,
    onBack,
    showBackButton = false,
    renderMessageInputActions,
    renderMessageInputFooter,
    renderConversationFooter,
    onLeaveConversation,
    onBlockParticipant,
    className,
    CustomChannelEmptyState = ChannelEmptyState,
    showDeleteConversation = true,
    onDeleteConversationClick,
    onBlockParticipantClick,
    onReportParticipantClick,
    showBlockParticipant = true,
    showReportParticipant = true,
    composerDisabled = false,
    composerDisabledReason,
    dmAgentEnabled,
    messageMetadata,
    onMessageSent,
    showStarButton = false,
    renderChannelBanner,
    customChannelActions,
    renderChannelActions,
    renderMessage,
    onMessageLinkClick,
    sendButton,
    attachmentPreviewList,
    viewerLanguage,
    showChannelInfo = true,
    onParticipantNameClick,
    onOutboundClick,
    resolveOutbound,
    composerInput,
  }) => {
    // Custom send message handler that:
    // 1. Applies messageMetadata if provided
    // 2. Adds skip_push and silent when DM agent is active
    // 3. Calls onMessageSent callback with full response
    // Read chatbot_paused inside callback to get current value at send time (not stale closure)
    const doSendMessageRequest = useCallback(
      async (
        _channel: ChannelType,
        message: Parameters<ChannelType['sendMessage']>[0],
        options?: Parameters<ChannelType['sendMessage']>[1]
      ) => {
        const agentPaused = channel.data?.chatbot_paused === true
        const shouldSuppressNotifications = dmAgentEnabled && !agentPaused

        // Build final message with nested metadata
        const finalMessage = {
          ...message,
          ...(shouldSuppressNotifications && { silent: true }),
          ...(messageMetadata && {
            metadata: {
              ...(message.metadata ?? {}),
              ...messageMetadata,
            },
          }),
        }

        // Build final options
        const finalOptions = {
          ...options,
          ...(shouldSuppressNotifications && { skip_push: true }),
        }

        const response = await channel.sendMessage(finalMessage, finalOptions)

        // Fire callback with full response (includes message.id)
        onMessageSent?.(response)

        return response
      },
      [channel, dmAgentEnabled, messageMetadata, onMessageSent]
    )

    const containerRef = useRef<HTMLDivElement>(null)
    useEffect(() => {
      if (!onMessageLinkClick) return
      const container = containerRef.current
      if (!container) return
      const handler = (event: MouseEvent) => {
        const target = event.target as HTMLElement | null
        const anchor = target?.closest('a[href]')
        const href = anchor?.getAttribute('href')
        if (!href) return
        const messageWrapper = anchor?.closest<HTMLElement>('[data-message-id]')
        const messageId = messageWrapper?.getAttribute('data-message-id')
        const message = messageId
          ? channel.state.messages.find((m) => m.id === messageId)
          : undefined
        onMessageLinkClick(href, message)
      }
      container.addEventListener('click', handler)
      return () => container.removeEventListener('click', handler)
    }, [onMessageLinkClick, channel])

    // The official channel is read-only (no composer), so the design gives its content a
    // wider column. Publish the width as a CSS var on the channel root; every card/bubble
    // inside reads `w-[var(--lt-msg-card-width,…)]` and widens. Other channels leave the var
    // unset and keep their default widths.
    const messageCardWidthStyle = isOfficialChannel(channel.type)
      ? ({ '--lt-msg-card-width': '368px' } as React.CSSProperties)
      : undefined

    return (
      <div
        ref={containerRef}
        className={classNames(
          'messaging-channel-view h-full flex flex-col',
          className
        )}
        style={messageCardWidthStyle}
      >
        <DmAgentEnabledContext.Provider value={dmAgentEnabled ?? false}>
          <Channel
            channel={channel}
            MessageSystem={CustomSystemMessage}
            EmptyStateIndicator={CustomChannelEmptyState}
            LoadingIndicator={LoadingState}
            DateSeparator={CustomDateSeparator}
            TypingIndicator={CustomTypingIndicator}
            doSendMessageRequest={doSendMessageRequest}
            {...(sendButton ? { SendButton: sendButton } : {})}
            {...(attachmentPreviewList
              ? { AttachmentPreviewList: attachmentPreviewList }
              : {})}
          >
            <ChannelViewInner
              onBack={onBack}
              showBackButton={showBackButton}
              renderMessageInputActions={renderMessageInputActions}
              renderMessageInputFooter={renderMessageInputFooter}
              renderConversationFooter={renderConversationFooter}
              onLeaveConversation={onLeaveConversation}
              onBlockParticipant={onBlockParticipant}
              CustomChannelEmptyState={CustomChannelEmptyState}
              showDeleteConversation={showDeleteConversation}
              onDeleteConversationClick={onDeleteConversationClick}
              onBlockParticipantClick={onBlockParticipantClick}
              onReportParticipantClick={onReportParticipantClick}
              showBlockParticipant={showBlockParticipant}
              showReportParticipant={showReportParticipant}
              composerDisabled={composerDisabled}
              composerDisabledReason={composerDisabledReason}
              showStarButton={showStarButton}
              renderChannelBanner={renderChannelBanner}
              customChannelActions={customChannelActions}
              renderChannelActions={renderChannelActions}
              renderMessage={renderMessage}
              viewerLanguage={viewerLanguage}
              showChannelInfo={showChannelInfo}
              onParticipantNameClick={onParticipantNameClick}
              onOutboundClick={onOutboundClick}
              resolveOutbound={resolveOutbound}
              composerInput={composerInput}
            />
          </Channel>
        </DmAgentEnabledContext.Provider>
      </div>
    )
  }
)
ChannelView.displayName = 'ChannelView'
