/* eslint-disable react-hooks/refs -- pre-existing pattern flagged by the React
   Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is
   out of scope for the lint toolchain upgrade. */
import React, { useCallback, useEffect, useRef, useState } from 'react'
import type { Channel } from 'stream-chat'

import { useMessaging } from '../../hooks/useMessaging'
import type { MessagingShellProps } from '../../types'
import { ChannelView } from '../ChannelView'

import { ErrorState } from './ErrorState'
import { LoadingState } from './LoadingState'

/**
 * Direct-conversation surface for one specific participant.
 *
 * Renders a single ChannelView for the channel between the connected user and
 * `initialParticipantFilter`. If no channel exists yet and
 * `initialParticipantData` is supplied, the configured StreamChatService
 * channel creator is invoked to create one.
 */
export const MessagingShell: React.FC<MessagingShellProps> = ({
  capabilities = {},
  renderMessageInputActions,
  renderConversationFooter,
  onChannelSelect,
  onExitConversation,
  initialParticipantFilter,
  initialParticipantData,
  CustomChannelEmptyState,
  onBlockParticipantClick,
  onReportParticipantClick,
  dmAgentEnabled,
  onMessageSent,
  viewerLanguage,
  renderChannelBanner,
  customChannelActions,
  renderChannelActions,
  onParticipantNameClick,
  onOutboundClick,
  resolveOutbound,
  renderMessage,
  onMessageLinkClick,
  showChannelInfo,
  composerInput,
  attachmentPreviewList,
}) => {
  const {
    client,
    isConnected,
    isLoading,
    error,
    refreshConnection,
    service,
    debug,
  } = useMessaging()

  const [selectedChannel, setSelectedChannel] = useState<Channel | null>(null)
  const [directConversationError, setDirectConversationError] = useState<
    string | null
  >(null)
  const [didExit, setDidExit] = useState(false)

  const { showDeleteConversation = true } = capabilities

  // Stash consumer props that are unstable when passed inline (object
  // literals, arrow-function callbacks) so the load effect's deps stay
  // identity-stable. Without this, the documented usage pattern in the
  // README — passing `initialParticipantData={{ ... }}` and
  // `onChannelSelect={(ch) => ...}` — re-fires the effect on every render
  // and triggers a queryChannels call each time.
  const initialParticipantDataRef = useRef(initialParticipantData)
  initialParticipantDataRef.current = initialParticipantData
  const onChannelSelectRef = useRef(onChannelSelect)
  onChannelSelectRef.current = onChannelSelect

  // Track the direct-conversation load to prevent repeated/concurrent loads.
  // Identity-stable deps (above) stop the common re-fire, but legitimate dep
  // changes (e.g. `service`/`client` settling during connect) can still re-run
  // this effect before the just-created channel is indexed, each firing another
  // startChannelWithParticipant call and creating duplicate welcome messages.
  const directConversationLoadRef = useRef<string | null>(null)

  // Mirror the currently-selected channel into a ref so the async load can read
  // the latest value rather than the (possibly stale) effect-closure value.
  const selectedChannelRef = useRef<Channel | null>(null)
  useEffect(() => {
    selectedChannelRef.current = selectedChannel
  }, [selectedChannel])

  useEffect(() => {
    if (!client || !isConnected) return

    const userId = client.userID
    if (!userId) return

    // Only load once per viewer ↔ participant pair. Set synchronously (before
    // the async work) so re-runs triggered by changing dependency identities
    // bail out instead of issuing another channel-create request.
    const loadKey = `${userId}::${initialParticipantFilter}`
    if (directConversationLoadRef.current === loadKey) return
    directConversationLoadRef.current = loadKey

    // Release the guard only if it still belongs to this load. A newer load for
    // a different pair may have taken ownership while this one was in flight, so
    // clearing unconditionally could wipe the newer guard and let a duplicate
    // load/create slip through.
    const releaseLoadGuard = () => {
      if (directConversationLoadRef.current === loadKey) {
        directConversationLoadRef.current = null
      }
    }

    const loadInitialChannel = async () => {
      try {
        if (debug) {
          console.log(
            '[MessagingShell] Loading initial conversation with:',
            initialParticipantFilter
          )
        }

        const channels = await client.queryChannels(
          {
            type: 'messaging',
            members: { $eq: [userId, initialParticipantFilter] },
          },
          {},
          { limit: 1 }
        )

        if (channels.length > 0) {
          setSelectedChannel(channels[0])
          setDirectConversationError(null)
          onChannelSelectRef.current?.(channels[0])

          if (debug) {
            console.log(
              '[MessagingShell] Initial conversation loaded:',
              channels[0].id
            )
          }
          return
        }

        const participantData = initialParticipantDataRef.current
        if (!participantData || !service) {
          // Allow a retry once participant data / service become available.
          releaseLoadGuard()
          setDirectConversationError('No conversation found with this account')
          if (debug) {
            console.log(
              '[MessagingShell] No conversation found for:',
              initialParticipantFilter
            )
          }
          return
        }

        try {
          const channel = await service.startChannelWithParticipant({
            id: participantData.id,
            name: participantData.name,
            phone: participantData.phone,
          })
          setSelectedChannel(channel)
          setDirectConversationError(null)
          onChannelSelectRef.current?.(channel)

          if (debug) {
            console.log(
              '[MessagingShell] Channel created and loaded:',
              channel.id
            )
          }
        } catch (createErr) {
          console.error(
            '[MessagingShell] Failed to create conversation:',
            createErr
          )
          // Allow a retry for this pair after a transient failure.
          releaseLoadGuard()
          setDirectConversationError('Failed to create conversation')
        }
      } catch (err) {
        console.error(
          '[MessagingShell] Failed to load initial conversation:',
          err
        )
        // Allow a retry for this pair after a transient failure.
        releaseLoadGuard()
        // Don't replace an already-loaded conversation with an error screen.
        // Read the latest selected channel via ref to avoid acting on a stale
        // closure value when the channel was selected mid-flight.
        if (!selectedChannelRef.current) {
          setDirectConversationError('Failed to load conversation')
        }
      }
    }

    void loadInitialChannel()
  }, [initialParticipantFilter, client, isConnected, service, debug])

  // Leave / block clears the selected channel and notifies the consumer.
  // The consumer owns what happens next — typically unmounting MessagingShell
  // or re-rendering with new participant data. When no callback is provided,
  // the shell renders an "ended" state rather than an indefinite spinner so
  // the surface remains honest about why no conversation is shown.
  const onExitConversationRef = useRef(onExitConversation)
  onExitConversationRef.current = onExitConversation
  const handleExitConversation = useCallback(() => {
    setSelectedChannel(null)
    setDidExit(true)
    onExitConversationRef.current?.()
  }, [])

  if (isLoading) {
    return <LoadingState />
  }

  if (error) {
    return <ErrorState message={error} onBack={refreshConnection} />
  }

  if (!isConnected || !client) {
    return (
      <ErrorState
        message="Not connected to messaging service"
        onBack={refreshConnection}
      />
    )
  }

  if (directConversationError) {
    return <ErrorState message={directConversationError} />
  }

  if (didExit && !selectedChannel) {
    return <ErrorState message="Conversation ended" />
  }

  if (!selectedChannel) {
    return <LoadingState />
  }

  return (
    <div className="messaging-shell h-full bg-background-primary overflow-hidden">
      <div className="flex h-full min-h-0 flex-col">
        <ChannelView
          channel={selectedChannel}
          key={selectedChannel.id}
          renderMessageInputActions={renderMessageInputActions}
          renderConversationFooter={renderConversationFooter}
          renderChannelBanner={renderChannelBanner}
          onLeaveConversation={handleExitConversation}
          onBlockParticipant={handleExitConversation}
          CustomChannelEmptyState={CustomChannelEmptyState}
          showDeleteConversation={showDeleteConversation}
          onBlockParticipantClick={onBlockParticipantClick}
          onReportParticipantClick={onReportParticipantClick}
          dmAgentEnabled={dmAgentEnabled}
          onMessageSent={onMessageSent}
          viewerLanguage={viewerLanguage}
          customChannelActions={customChannelActions}
          renderChannelActions={renderChannelActions}
          onParticipantNameClick={onParticipantNameClick}
          onOutboundClick={onOutboundClick}
          resolveOutbound={resolveOutbound}
          renderMessage={renderMessage}
          onMessageLinkClick={onMessageLinkClick}
          showChannelInfo={showChannelInfo}
          composerInput={composerInput}
          attachmentPreviewList={attachmentPreviewList}
        />
      </div>
    </div>
  )
}
