import {
  CodeOutlined,
  CopyOutlined,
  DownloadOutlined,
  DownOutlined,
  FullscreenExitOutlined,
  FullscreenOutlined,
  UpOutlined,
} from '@ant-design/icons';
import { Button, Empty, Spin, Typography, message as antdMessage } from 'antd';
import { useEffect, useState, type ReactNode } from 'react';
import { InlineImage } from '../../../../components/common/InlineImage';
import { CodexDiffBlock } from '../../../../components/previewer';
import {
  ChatPreviewBody,
  resolveChatPreviewGlyph,
  resolveChatPreviewKindLabel,
  type ChatPreviewKind,
  type ChatPreviewTarget,
} from '../../mainChatPanel/ChatPreviewBody';
import { extractAutoDetectedPreviewUrls } from '../../mainChatPanel/inlinePreviewUrls';
import { extractHiddenPreviewUrls, stripHiddenPreviewTags } from '../../mainChatPanel/previewMarkers';
import { normalizeChatResourceUrl } from '../../mainChatPanel/chatResourceUrl';
import { copyPreviewContent, copyText } from '../../mainChatPanel/chatUtils';
import type { ChatConversationRequest, ChatMessage } from '../../mainChatPanel/types';

const { Text } = Typography;

type ConversationRoomPaneProps = {
  sessionId: string;
  messages: ChatMessage[];
  requests: ChatConversationRequest[];
  isLoading: boolean;
  loadingLabel: string;
  errorMessage: string;
};

const MARKDOWN_IMAGE_LINE_PATTERN = /^\s*!\[([^\]]*)\]\(([^)]+)\)\s*$/;
const MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/g;
const DIFF_CODE_BLOCK_PATTERN = /```diff[^\n]*\n([\s\S]*?)```/g;
const COLLAPSIBLE_MESSAGE_LINE_COUNT = 6;
const COLLAPSIBLE_MESSAGE_CHAR_COUNT = 280;

type MessageRenderPayload = {
  previewSourceText: string;
  visibleText: string;
  diffBlocks: string[];
};

function formatChatTimestamp(timestamp: string) {
  const normalized = String(timestamp ?? '').trim();

  if (!normalized) {
    return '';
  }

  const parsed = new Date(normalized);

  if (Number.isNaN(parsed.getTime())) {
    return normalized;
  }

  return new Intl.DateTimeFormat('sv-SE', {
    timeZone: 'Asia/Seoul',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false,
  })
    .format(parsed)
    .replace(',', '');
}

function classifyInlinePreviewKind(url: string): ChatPreviewKind | 'file' {
  const pathname = url.toLowerCase().split('?')[0] ?? '';

  if (/\.(png|jpe?g|gif|webp|svg|bmp|ico)$/i.test(pathname)) {
    return 'image';
  }

  if (/\.(mp4|webm|mov|m4v|ogg)$/i.test(pathname)) {
    return 'video';
  }

  if (/\.(md|markdown)$/i.test(pathname)) {
    return 'markdown';
  }

  if (/\.(diff|patch)$/i.test(pathname)) {
    return 'diff';
  }

  if (/\.(ts|tsx|js|jsx|json|css|scss|html|xml|java|kt|sql|sh|py|go|rs|c|cpp|h|hpp|yml|yaml)$/i.test(pathname)) {
    return 'code';
  }

  if (/\.(txt|log|csv)$/i.test(pathname)) {
    return 'document';
  }

  if (/\.pdf$/i.test(pathname)) {
    return 'pdf';
  }

  return 'file';
}

function buildInlinePreviewLabel(url: string) {
  try {
    const parsed = new URL(url);
    return parsed.pathname.split('/').filter(Boolean).at(-1) || parsed.hostname;
  } catch {
    return url;
  }
}

function downloadTextFile(content: string, fileName: string, mimeType = 'text/plain;charset=utf-8') {
  if (typeof document === 'undefined') {
    return;
  }

  const blob = new Blob([content], { type: mimeType });
  const objectUrl = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = objectUrl;
  link.download = fileName;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  URL.revokeObjectURL(objectUrl);
}

function extractInlinePreviewTargets(text: string): ChatPreviewTarget[] {
  const matches = [...extractAutoDetectedPreviewUrls(text), ...extractHiddenPreviewUrls(text)];
  const seen = new Set<string>();
  const targets: ChatPreviewTarget[] = [];

  for (const matchedUrl of matches) {
    const normalizedUrl = normalizeChatResourceUrl(matchedUrl);
    const kind = classifyInlinePreviewKind(normalizedUrl);

    if (kind === 'file' || seen.has(normalizedUrl)) {
      continue;
    }

    seen.add(normalizedUrl);
    targets.push({
      url: normalizedUrl,
      label: buildInlinePreviewLabel(normalizedUrl),
      kind,
    });
  }

  return targets;
}

function renderMessageInlineParts(line: string): ReactNode[] {
  const renderedParts: ReactNode[] = [];
  let cursor = 0;

  for (const match of line.matchAll(MARKDOWN_LINK_PATTERN)) {
    const [fullMatch, label, rawHref] = match;
    const start = match.index ?? 0;

    if (start > cursor) {
      renderedParts.push(line.slice(cursor, start));
    }

    const href = normalizeChatResourceUrl(rawHref.trim());
    renderedParts.push(
      <a key={`${href}-${start}`} href={href} target="_blank" rel="noreferrer">
        {label.trim() || href}
      </a>,
    );
    cursor = start + fullMatch.length;
  }

  if (cursor < line.length) {
    renderedParts.push(line.slice(cursor));
  }

  return renderedParts.length > 0 ? renderedParts : [line];
}

function renderMessageBody(text: string) {
  return text.split('\n').map((line, index) => {
    const imageMatch = line.match(MARKDOWN_IMAGE_LINE_PATTERN);

    if (imageMatch) {
      const [, alt, rawSrc] = imageMatch;
      const src = normalizeChatResourceUrl(rawSrc.trim());

      return (
        <div key={`img-${index}`} className="app-chat-message__block app-chat-message__block--image">
          <InlineImage
            src={src}
            alt={alt.trim() || 'chat image'}
            className="app-chat-message__inline-image markdown-preview__image"
            fallbackText="이미지 preview를 불러오지 못했습니다."
          />
        </div>
      );
    }

    if (!line.length) {
      return <div key={`space-${index}`} className="app-chat-message__block app-chat-message__block--spacer" aria-hidden="true" />;
    }

    return (
      <div key={`line-${index}`} className="app-chat-message__block">
        {renderMessageInlineParts(line)}
      </div>
    );
  });
}

function extractMessageRenderPayload(text: string): MessageRenderPayload {
  const diffBlocks = Array.from(text.matchAll(DIFF_CODE_BLOCK_PATTERN))
    .map((match) => match[1]?.trim())
    .filter((value): value is string => Boolean(value));

  const previewSourceText = text.replace(DIFF_CODE_BLOCK_PATTERN, '');
  const visibleText = stripHiddenPreviewTags(previewSourceText);

  return { previewSourceText, visibleText, diffBlocks };
}

function isLikelyCollapsibleMessage(text: string) {
  const normalizedText = String(text ?? '').trim();

  if (!normalizedText) {
    return false;
  }

  if (normalizedText.length > COLLAPSIBLE_MESSAGE_CHAR_COUNT) {
    return true;
  }

  return normalizedText
    .split('\n')
    .map((line) => line.trim())
    .filter(Boolean).length > COLLAPSIBLE_MESSAGE_LINE_COUNT;
}

async function createPreviewFetchError(response: Response) {
  const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
  let responseMessage = '';

  try {
    responseMessage = contentType.includes('application/json')
      ? String(((await response.json()) as { message?: string }).message ?? '').trim()
      : (await response.text()).trim();
  } catch {
    responseMessage = '';
  }

  const statusLabel =
    response.status === 403
      ? '이 문서는 현재 권한으로 열 수 없습니다.'
      : response.status === 404
        ? '이 문서를 찾을 수 없습니다.'
        : response.status === 401
          ? '이 문서를 열기 위한 인증이 필요합니다.'
          : `preview 요청이 실패했습니다. (${response.status})`;

  return new Error(responseMessage ? `${statusLabel} ${responseMessage}` : statusLabel);
}

function InlineMessagePreview({
  target,
  isExpanded,
  onToggle,
}: {
  target: ChatPreviewTarget;
  isExpanded: boolean;
  onToggle: () => void;
}) {
  const [previewText, setPreviewText] = useState('');
  const [isPreviewLoading, setIsPreviewLoading] = useState(false);
  const [previewError, setPreviewError] = useState('');
  const [previewContentType, setPreviewContentType] = useState('');

  useEffect(() => {
    if (!isExpanded || target.kind === 'image' || target.kind === 'video' || target.kind === 'pdf') {
      return;
    }

    const controller = new AbortController();
    setIsPreviewLoading(true);
    setPreviewError('');
    setPreviewContentType('');

    fetch(target.url, { cache: 'no-store', signal: controller.signal })
      .then(async (response) => {
        if (!response.ok) {
          throw await createPreviewFetchError(response);
        }

        setPreviewContentType(response.headers.get('content-type') ?? '');
        setPreviewText((await response.text()).slice(0, 1600));
      })
      .catch((error: unknown) => {
        if (controller.signal.aborted) {
          return;
        }

        setPreviewText('');
        setPreviewContentType('');
        setPreviewError(error instanceof Error ? error.message : 'preview를 불러오지 못했습니다.');
      })
      .finally(() => {
        if (!controller.signal.aborted) {
          setIsPreviewLoading(false);
        }
      });

    return () => {
      controller.abort();
    };
  }, [isExpanded, target.kind, target.url]);

  return (
    <section className={`app-chat-preview-card${isExpanded ? ' app-chat-preview-card--expanded' : ' app-chat-preview-card--collapsed'}`}>
      <div className="app-chat-preview-card__header">
        <div className="app-chat-preview-card__meta">
          <span className="app-chat-preview-card__glyph" aria-hidden="true">
            {resolveChatPreviewGlyph(target.kind)}
          </span>
          <div className="app-chat-preview-card__titles">
            <span className="app-chat-preview-card__label">{target.label}</span>
            <span className="app-chat-preview-card__kind">{resolveChatPreviewKindLabel(target.kind)}</span>
          </div>
        </div>
        <div className="app-chat-preview-card__actions">
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={<CopyOutlined />}
            aria-label="preview 내용 복사"
            onClick={() => {
              void copyPreviewContent({
                kind: target.kind,
                url: target.url,
                fallbackText: previewText,
              })
                .then((result) => {
                  if (result === 'image') {
                    antdMessage.success('preview 이미지를 복사했습니다.');
                    return;
                  }

                  if (result === 'url') {
                    antdMessage.success('preview 이미지 URL을 복사했습니다.');
                    return;
                  }

                  antdMessage.success('preview 내용을 복사했습니다.');
                })
                .catch((error: unknown) =>
                  antdMessage.error(error instanceof Error ? error.message : 'preview 내용을 복사하지 못했습니다.'),
                );
            }}
          />
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={isExpanded ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
            aria-label={isExpanded ? 'preview 최대화 해제' : 'preview 최대화'}
            onClick={onToggle}
          />
          <Button
            type="link"
            size="small"
            className="app-chat-preview-card__toggle"
            icon={isExpanded ? <UpOutlined /> : <DownOutlined />}
            aria-label={isExpanded ? 'preview 접기' : 'preview 펼치기'}
            onClick={onToggle}
          />
        </div>
      </div>

      {isExpanded ? (
        <div className="app-chat-preview-card__body">
          <ChatPreviewBody
            target={target}
            previewText={previewText}
            isPreviewLoading={isPreviewLoading}
            previewError={previewError}
            previewContentType={previewContentType}
            maxMarkdownBlocks={12}
          />
        </div>
      ) : null}
    </section>
  );
}

function DiffMessagePreview({
  diffText,
  fileCount,
  isExpanded,
  isFullscreen,
  onToggle,
  onToggleFullscreen,
}: {
  diffText: string;
  fileCount: number;
  isExpanded: boolean;
  isFullscreen: boolean;
  onToggle: () => void;
  onToggleFullscreen: () => void;
}) {
  return (
    <section
      className={`app-chat-preview-card${isExpanded || isFullscreen ? ' app-chat-preview-card--expanded' : ' app-chat-preview-card--collapsed'}${
        isFullscreen ? ' app-chat-preview-card--fullscreen' : ''
      }`}
    >
      <div className="app-chat-preview-card__header">
        <div className="app-chat-preview-card__meta">
          <span className="app-chat-preview-card__glyph" aria-hidden="true">
            <CodeOutlined />
          </span>
          <div className="app-chat-preview-card__titles">
            <span className="app-chat-preview-card__label">Codex Diff</span>
            <span className="app-chat-preview-card__kind">{`diff preview · 파일 ${fileCount}개`}</span>
          </div>
        </div>
        <div className="app-chat-preview-card__actions">
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={<CopyOutlined />}
            aria-label="diff 복사"
            onClick={() => {
              void copyText(diffText)
                .then(() => antdMessage.success('diff를 복사했습니다.'))
                .catch((error: unknown) => antdMessage.error(error instanceof Error ? error.message : 'diff를 복사하지 못했습니다.'));
            }}
          />
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={isFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
            aria-label={isFullscreen ? 'diff 최대화 해제' : 'diff 최대화'}
            onClick={onToggleFullscreen}
          />
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={<DownloadOutlined />}
            aria-label="diff 다운로드"
            onClick={() => {
              downloadTextFile(diffText, 'codex-result.diff', 'text/x-diff;charset=utf-8');
            }}
          />
          <Button
            type="link"
            size="small"
            className="app-chat-preview-card__toggle"
            icon={isExpanded || isFullscreen ? <UpOutlined /> : <DownOutlined />}
            aria-label={isExpanded || isFullscreen ? 'diff 접기' : 'diff 펼치기'}
            onClick={onToggle}
          />
        </div>
      </div>
      {isExpanded || isFullscreen ? (
        <div className="app-chat-preview-card__body">
          <div className="app-chat-panel__preview-rich app-chat-panel__preview-rich--code">
            <CodexDiffBlock
              diffText={diffText}
              showToolbar={false}
              expandAll={isFullscreen}
              summary={`파일 ${fileCount}개 diff preview`}
            />
          </div>
        </div>
      ) : null}
    </section>
  );
}

export function ConversationRoomPane({
  sessionId,
  messages,
  requests,
  isLoading,
  loadingLabel,
  errorMessage,
}: ConversationRoomPaneProps) {
  const [expandedMessageIds, setExpandedMessageIds] = useState<number[]>([]);
  const [expandedPreviewKey, setExpandedPreviewKey] = useState<string | null>(null);
  const [fullscreenPreviewKey, setFullscreenPreviewKey] = useState<string | null>(null);

  useEffect(() => {
    if (typeof document === 'undefined') {
      return;
    }

    const { body, documentElement } = document;
    const previousBodyOverflow = body.style.overflow;
    const previousHtmlOverflow = documentElement.style.overflow;

    if (fullscreenPreviewKey) {
      body.style.overflow = 'hidden';
      documentElement.style.overflow = 'hidden';
    }

    return () => {
      body.style.overflow = previousBodyOverflow;
      documentElement.style.overflow = previousHtmlOverflow;
    };
  }, [fullscreenPreviewKey]);

  if (!sessionId) {
    return (
      <section className="chat-v2__pane chat-v2__pane--room">
        <div className="chat-v2__state">
          <Empty description="채팅방을 선택해 주세요." />
        </div>
      </section>
    );
  }

  if (isLoading) {
    return (
      <section className="chat-v2__pane chat-v2__pane--room">
        <div className="chat-v2__state">
          <Spin />
          <Text type="secondary">{loadingLabel}</Text>
        </div>
      </section>
    );
  }

  if (errorMessage) {
    return (
      <section className="chat-v2__pane chat-v2__pane--room">
        <div className="chat-v2__state">
          <Text type="danger">{errorMessage}</Text>
        </div>
      </section>
    );
  }

  return (
    <section className="chat-v2__pane chat-v2__pane--room">
      <div className="chat-v2__pane-header">
        <div>
          <Text strong>{sessionId}</Text>
          <br />
          <Text type="secondary">
            메시지 {messages.length}개 · 요청 {requests.length}개
          </Text>
        </div>
      </div>

      <div className="chat-v2__room-stream">
        {messages.length === 0 ? (
          <div className="chat-v2__state">
            <Empty description="메시지가 없습니다." />
          </div>
        ) : (
          messages.map((message) => {
            const canCollapseMessage = isLikelyCollapsibleMessage(message.text);
            const isExpandedMessage = expandedMessageIds.includes(message.id);
            const shouldTruncateMessage = canCollapseMessage && !isExpandedMessage;
            const messageBodyClassName = `app-chat-message__body${shouldTruncateMessage ? ' app-chat-message__body--collapsed' : ''}`;
            const { previewSourceText, visibleText, diffBlocks } = extractMessageRenderPayload(message.text);
            const inlinePreviewTargets = extractInlinePreviewTargets(previewSourceText);
            const hasPreviewCards = diffBlocks.length > 0 || inlinePreviewTargets.length > 0;
            const shouldRenderStandalonePreview =
              hasPreviewCards && !visibleText && (message.author === 'codex' || message.author === 'system');
            const stackClassName = [
              `app-chat-message-stack app-chat-message-stack--${message.author}`,
              shouldRenderStandalonePreview ? 'app-chat-message-stack--artifact-only' : '',
            ]
              .filter(Boolean)
              .join(' ');

            return (
              <div key={message.id} className={stackClassName}>
                {shouldRenderStandalonePreview ? null : (
                  <article className={`app-chat-message app-chat-message--${message.author}`}>
                    <div className="app-chat-message__header">
                      <div className="app-chat-message__header-meta">
                        <strong>{message.author === 'codex' ? 'Codex' : message.author === 'user' ? 'You' : 'System'}</strong>
                        <span>{formatChatTimestamp(message.timestamp)}</span>
                      </div>
                      {message.author !== 'system' ? (
                        <Button
                          type="text"
                          size="small"
                          className="app-chat-message__header-action"
                          icon={<CopyOutlined />}
                          aria-label="메시지 복사"
                          onClick={() => {
                            void copyText(message.text)
                              .then(() => antdMessage.success('메시지를 복사했습니다.'))
                              .catch((error: unknown) =>
                                antdMessage.error(error instanceof Error ? error.message : '메시지를 복사하지 못했습니다.'),
                              );
                          }}
                        />
                      ) : null}
                    </div>
                    <div className={messageBodyClassName}>{visibleText ? renderMessageBody(visibleText) : null}</div>
                    {canCollapseMessage ? (
                      <Button
                        type="text"
                        size="small"
                        className="app-chat-message__expand"
                        icon={isExpandedMessage ? <UpOutlined /> : <DownOutlined />}
                        aria-label={isExpandedMessage ? '메시지 접기' : '메시지 펼치기'}
                        onClick={() => {
                          setExpandedMessageIds((current) =>
                            current.includes(message.id) ? current.filter((id) => id !== message.id) : [...current, message.id],
                          );
                        }}
                      >
                        {isExpandedMessage ? '접기' : '펼치기'}
                      </Button>
                    ) : null}
                  </article>
                )}
                {hasPreviewCards ? (
                  <div className="app-chat-message-stack__previews">
                    {diffBlocks.map((diffText, index) => {
                      const previewKey = `${message.id}-diff-${index}`;

                      return (
                        <DiffMessagePreview
                          key={previewKey}
                          diffText={diffText}
                          fileCount={Math.max(1, Array.from(diffText.matchAll(/^diff --git /gm)).length)}
                          isExpanded={expandedPreviewKey === previewKey}
                          isFullscreen={fullscreenPreviewKey === previewKey}
                          onToggle={() => {
                            setExpandedPreviewKey((current) => {
                              if (fullscreenPreviewKey === previewKey) {
                                setFullscreenPreviewKey(null);
                                return null;
                              }

                              return current === previewKey ? null : previewKey;
                            });
                          }}
                          onToggleFullscreen={() => {
                            setFullscreenPreviewKey((current) => {
                              const nextKey = current === previewKey ? null : previewKey;

                              if (nextKey) {
                                setExpandedPreviewKey(previewKey);
                              }

                              return nextKey;
                            });
                          }}
                        />
                      );
                    })}
                    {inlinePreviewTargets.map((target) => {
                      const previewKey = `${message.id}-${target.url}`;
                      return (
                        <InlineMessagePreview
                          key={previewKey}
                          target={target}
                          isExpanded={expandedPreviewKey === previewKey}
                          onToggle={() => {
                            setExpandedPreviewKey((current) => (current === previewKey ? null : previewKey));
                          }}
                        />
                      );
                    })}
                  </div>
                ) : null}
              </div>
            );
          })
        )}
      </div>
    </section>
  );
}
