import React, { createContext, useContext } from 'react';
import { MsgType } from 'matrix-js-sdk';
import { HTMLReactParserOptions } from 'html-react-parser';
import { Opts } from 'linkifyjs';
import { config } from 'folds';
import {
  AudioContent,
  DownloadFile,
  FileContent,
  ImageContent,
  MAudio,
  MBadEncrypted,
  MEmote,
  MFile,
  MImage,
  MLocation,
  MNotice,
  MText,
  MVideo,
  MVoice,
  ReadPdfFile,
  ReadTextFile,
  RenderBody,
  RenderImageContentProps,
  RenderVideoContentProps,
  StreamMediaCaption,
  StreamMediaImage,
  StreamMediaVideo,
  ThumbnailContent,
  UnsupportedContent,
  VideoContent,
  VoiceContent,
} from './message';
import { UrlPreviewCard, UrlPreviewHolder } from './url-preview';
import { Image, MediaControl, Video } from './media';
import { ImageViewer } from './image-viewer';
import { PdfViewer } from './Pdf-viewer';
import { TextViewer } from './text-viewer';
import { testMatrixTo } from '../plugins/matrix-to';
import { IAudioContent, IImageContent, isVoiceMessageContent } from '../../types/matrix/common';
import { logMedia } from './message/attachment/streamMediaDebug';

// Threads the bubble/channel layout's mediaMode info from Message.tsx down to the
// image / video rendering branches below. Non-null only for media messages
// in the timeline; pin-menu / message-search leave it null and fall back
// to the legacy MImage / MVideo Attachment chrome.
export type StreamMediaContextValue = {
  // Only `own` survives — it drives the bubble's asymmetric notch corner. The
  // sender nick used to be overlaid on the media via this context, but it's
  // now rendered ABOVE the media by the Stream name header (like text).
  own: boolean;
};
export const StreamMediaContext = createContext<StreamMediaContextValue | null>(null);
export const useStreamMediaContext = (): StreamMediaContextValue | null =>
  useContext(StreamMediaContext);

type RenderMessageContentProps = {
  displayName: string;
  // Voice bubble: the sender's id + resolved avatar URL so VoiceContent can draw
  // the avatar. Non-timeline callers (pin-menu, search) may omit them.
  senderId?: string;
  senderAvatarUrl?: string;
  // True when the surrounding layout ALREADY draws a per-message avatar (channel
  // layout / thread drawer) — VoiceContent then skips its own to avoid doubling.
  hideVoiceAvatar?: boolean;
  msgType: string;
  ts: number;
  edited?: boolean;
  getContent: <T>() => T;
  mediaAutoLoad?: boolean;
  urlPreview?: boolean;
  highlightRegex?: RegExp;
  htmlReactParserOptions: HTMLReactParserOptions;
  linkifyOpts: Opts;
  outlineAttachment?: boolean;
  // Threaded into `ImageContent` so its onClick can open the new
  // atom-driven horseshoe media viewer instead of the legacy
  // `<Overlay>` modal. Set by `RoomTimeline`; non-Room callers
  // (pin-menu, message search) leave it undefined and stay on the
  // legacy modal until they're migrated.
  eventId?: string;
};
export function RenderMessageContent({
  displayName,
  senderId,
  senderAvatarUrl,
  hideVoiceAvatar,
  msgType,
  ts,
  edited,
  getContent,
  mediaAutoLoad,
  urlPreview,
  highlightRegex,
  htmlReactParserOptions,
  linkifyOpts,
  outlineAttachment,
  eventId,
}: RenderMessageContentProps) {
  const streamMedia = useStreamMediaContext();
  const renderUrlsPreview = (urls: string[]) => {
    const filteredUrls = urls.filter((url) => !testMatrixTo(url));
    if (filteredUrls.length === 0) return undefined;
    return (
      <UrlPreviewHolder>
        {filteredUrls.map((url) => (
          <UrlPreviewCard key={url} url={url} ts={ts} />
        ))}
      </UrlPreviewHolder>
    );
  };
  const renderCaption = () => {
    const content: IImageContent = getContent();
    if (content.filename && content.filename !== content.body) {
      const captionNode = (
        <MText
          style={streamMedia ? undefined : { marginTop: config.space.S200 }}
          edited={edited}
          content={content}
          renderBody={(props) => (
            <RenderBody
              {...props}
              highlightRegex={highlightRegex}
              htmlReactParserOptions={htmlReactParserOptions}
              linkifyOpts={linkifyOpts}
            />
          )}
          renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined}
        />
      );
      if (streamMedia) {
        return <div className={StreamMediaCaption}>{captionNode}</div>;
      }
      return captionNode;
    }
    return null;
  };

  const renderFile = () => (
    <>
      <MFile
        content={getContent()}
        renderFileContent={({ body, mimeType, info, encInfo, url }) => (
          <FileContent
            body={body}
            mimeType={mimeType}
            renderAsPdfFile={() => (
              <ReadPdfFile
                body={body}
                mimeType={mimeType}
                url={url}
                encInfo={encInfo}
                renderViewer={(p) => <PdfViewer {...p} />}
              />
            )}
            renderAsTextFile={() => (
              <ReadTextFile
                body={body}
                mimeType={mimeType}
                url={url}
                encInfo={encInfo}
                renderViewer={(p) => <TextViewer {...p} />}
              />
            )}
          >
            <DownloadFile body={body} mimeType={mimeType} url={url} encInfo={encInfo} info={info} />
          </FileContent>
        )}
        outlined={outlineAttachment}
      />
      {renderCaption()}
    </>
  );

  if (msgType === MsgType.Text) {
    return (
      <MText
        edited={edited}
        content={getContent()}
        renderBody={(props) => (
          <RenderBody
            {...props}
            highlightRegex={highlightRegex}
            htmlReactParserOptions={htmlReactParserOptions}
            linkifyOpts={linkifyOpts}
          />
        )}
        renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined}
      />
    );
  }

  if (msgType === MsgType.Emote) {
    return (
      <MEmote
        displayName={displayName}
        edited={edited}
        content={getContent()}
        renderBody={(props) => (
          <RenderBody
            {...props}
            highlightRegex={highlightRegex}
            htmlReactParserOptions={htmlReactParserOptions}
            linkifyOpts={linkifyOpts}
          />
        )}
        renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined}
      />
    );
  }

  if (msgType === MsgType.Notice) {
    return (
      <MNotice
        edited={edited}
        content={getContent()}
        renderBody={(props) => (
          <RenderBody
            {...props}
            highlightRegex={highlightRegex}
            htmlReactParserOptions={htmlReactParserOptions}
            linkifyOpts={linkifyOpts}
          />
        )}
        renderUrlsPreview={urlPreview ? renderUrlsPreview : undefined}
      />
    );
  }

  if (msgType === MsgType.Image) {
    logMedia('RenderMessageContent', {
      msgType,
      streamMediaPresent: !!streamMedia,
      branch: streamMedia ? 'StreamMediaImage' : 'MImage(legacy)',
    });
    const renderImageInside = (props: RenderImageContentProps) => (
      <ImageContent
        {...props}
        autoPlay={mediaAutoLoad}
        eventId={eventId}
        renderImage={(p) => <Image {...p} loading="lazy" decoding="async" />}
        renderViewer={(p) => <ImageViewer {...p} />}
      />
    );
    return (
      <>
        {streamMedia ? (
          <StreamMediaImage
            content={getContent()}
            own={streamMedia.own}
            renderImageContent={renderImageInside}
          />
        ) : (
          <MImage
            content={getContent()}
            renderImageContent={renderImageInside}
            outlined={outlineAttachment}
          />
        )}
        {renderCaption()}
      </>
    );
  }

  if (msgType === MsgType.Video) {
    logMedia('RenderMessageContent', {
      msgType,
      streamMediaPresent: !!streamMedia,
      branch: streamMedia ? 'StreamMediaVideo' : 'MVideo(legacy)',
    });
    const renderVideoInside = ({ body, info, ...props }: RenderVideoContentProps) => (
      <VideoContent
        body={body}
        info={info}
        {...props}
        eventId={eventId}
        renderThumbnail={
          mediaAutoLoad
            ? () => (
                <ThumbnailContent
                  info={info}
                  renderImage={(src) => (
                    <Image alt={body} title={body} src={src} loading="lazy" decoding="async" />
                  )}
                />
              )
            : undefined
        }
        renderVideo={(p) => <Video {...p} />}
      />
    );
    return (
      <>
        {streamMedia ? (
          <StreamMediaVideo
            content={getContent()}
            own={streamMedia.own}
            renderAsFile={renderFile}
            renderVideoContent={renderVideoInside}
          />
        ) : (
          <MVideo
            content={getContent()}
            renderAsFile={renderFile}
            renderVideoContent={renderVideoInside}
            outlined={outlineAttachment}
          />
        )}
        {renderCaption()}
      </>
    );
  }

  if (msgType === MsgType.Audio) {
    // Voice notes (MSC3245) — both Vojo-native and Telegram-bridged — render as
    // the Dawn voice bubble; plain audio files keep the generic player.
    const audioContent = getContent<IAudioContent & Record<string, unknown>>();
    if (isVoiceMessageContent(audioContent)) {
      return (
        <MVoice
          content={getContent()}
          renderAsFile={renderFile}
          renderVoiceContent={(props) => (
            <VoiceContent
              {...props}
              senderId={senderId}
              senderAvatarUrl={senderAvatarUrl}
              senderName={displayName}
              hideAvatar={hideVoiceAvatar}
            />
          )}
        />
      );
    }
    return (
      <>
        <MAudio
          content={getContent()}
          renderAsFile={renderFile}
          renderAudioContent={(props) => (
            <AudioContent {...props} renderMediaControl={(p) => <MediaControl {...p} />} />
          )}
          outlined={outlineAttachment}
        />
        {renderCaption()}
      </>
    );
  }

  if (msgType === MsgType.File) {
    const fileMime = (getContent() as { info?: { mimetype?: string } }).info?.mimetype;
    logMedia('RenderMessageContent', {
      msgType,
      branch: 'MFile(legacy)',
      fileMime,
    });
    return renderFile();
  }

  if (msgType === MsgType.Location) {
    return <MLocation content={getContent()} />;
  }

  if (msgType === 'm.bad.encrypted') {
    return <MBadEncrypted />;
  }

  return <UnsupportedContent />;
}
