import {
  CopyOutlined,
  DownloadOutlined,
  FullscreenExitOutlined,
  FullscreenOutlined,
  SearchOutlined,
} from '@ant-design/icons';
import { Button, Empty, Input, Select, message } from 'antd';
import type { ReactNode } from 'react';
import { useEffect, useRef, useState } from 'react';
import { InlineImage } from '../common/InlineImage';
import { copyTextToClipboard } from '../../utils/clipboard';
import { CodexDiffBlock } from './CodexDiffBlock';
import type { PreviewerUIProps } from './types';
import { inferCodeLanguage, renderEditorBlock } from './renderers';
import './PreviewerUI.css';

function stringifyValue(value: unknown) {
  if (typeof value === 'string') {
    return value;
  }

  if (value === undefined || value === null) {
    return '';
  }

  try {
    return JSON.stringify(value, null, 2);
  } catch {
    return String(value);
  }
}

function normalizeJsonValue(value: unknown) {
  if (typeof value !== 'string') {
    return value;
  }

  try {
    return JSON.parse(value);
  } catch {
    return value;
  }
}

function renderMarkdown(markdown: string) {
  const lines = markdown.split('\n');
  const blocks: ReactNode[] = [];
  let index = 0;

  while (index < lines.length) {
    const line = lines[index].trimEnd();

    if (!line.trim()) {
      index += 1;
      continue;
    }

    if (line.startsWith('### ')) {
      blocks.push(<h3 key={`h3-${index}`}>{line.slice(4)}</h3>);
      index += 1;
      continue;
    }

    if (line.startsWith('## ')) {
      blocks.push(<h2 key={`h2-${index}`}>{line.slice(3)}</h2>);
      index += 1;
      continue;
    }

    if (line.startsWith('# ')) {
      blocks.push(<h1 key={`h1-${index}`}>{line.slice(2)}</h1>);
      index += 1;
      continue;
    }

    if (line.startsWith('- ')) {
      const items: string[] = [];

      while (index < lines.length && lines[index].trim().startsWith('- ')) {
        items.push(lines[index].trim().slice(2));
        index += 1;
      }

      blocks.push(
        <ul key={`list-${index}`}>
          {items.map((item, itemIndex) => (
            <li key={`${item}-${itemIndex}`}>{item}</li>
          ))}
        </ul>,
      );
      continue;
    }

    if (line.startsWith('```')) {
      const codeLanguage = inferCodeLanguage(line.slice(3).trim() || 'text');
      const codeLines: string[] = [];
      index += 1;

      while (index < lines.length && !lines[index].trim().startsWith('```')) {
        codeLines.push(lines[index]);
        index += 1;
      }

      index += 1;
      blocks.push(
        codeLanguage === 'diff' ? (
          <div key={`code-${index}`}>
            <CodexDiffBlock diffText={codeLines.join('\n')} />
          </div>
        ) : (
          <div key={`code-${index}`}>{renderEditorBlock(codeLines.join('\n'), codeLanguage, 'code')}</div>
        ),
      );
      continue;
    }

    blocks.push(<p key={`p-${index}`}>{line}</p>);
    index += 1;
  }

  return blocks;
}

function downloadBlob(content: BlobPart, fileName: string, mimeType = 'text/plain;charset=utf-8') {
  if (typeof document === 'undefined') {
    throw new Error('다운로드를 사용할 수 없습니다.');
  }

  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 resolveCopyValue({ type, value }: Pick<PreviewerUIProps, 'type' | 'value'>) {
  switch (type) {
    case 'json':
      return stringifyValue(normalizeJsonValue(value));
    case 'image':
      return String(value ?? '');
    default:
      return stringifyValue(value);
  }
}

function resolveDownloadValue({
  type,
  value,
  downloadValue,
}: Pick<PreviewerUIProps, 'type' | 'value' | 'downloadValue'>) {
  if (typeof downloadValue === 'string') {
    return downloadValue;
  }

  if (type === 'image') {
    return String(value ?? '');
  }

  return resolveCopyValue({ type, value });
}

function resolveDownloadFileName({
  type,
  language,
  downloadFileName,
}: Pick<PreviewerUIProps, 'type' | 'language' | 'downloadFileName'>) {
  if (downloadFileName?.trim()) {
    return downloadFileName.trim();
  }

  switch (type) {
    case 'json':
      return 'preview.json';
    case 'markdown':
      return 'preview.md';
    case 'code':
      return `preview.${language || 'txt'}`;
    case 'image':
      return 'preview';
    default:
      return 'preview.txt';
  }
}

function renderContent({
  type,
  value,
  language,
  format,
  imageAlt,
}: Pick<PreviewerUIProps, 'type' | 'value' | 'language' | 'format' | 'imageAlt'>) {
  const textValue = stringifyValue(value);
  const lines = textValue.split('\n').map((line) => line.trim()).filter(Boolean);
  const detectedFormat =
    format && format !== 'auto'
      ? format
      : lines.length > 0 && lines.every((line) => /^([A-Za-z]:\\|\.{0,2}\/|\/)?[\w@./-]+(?:\/[\w@./-]+)*$/.test(line))
        ? 'paths'
        : lines.length > 0 &&
            lines.filter((line) => /^(?:[$>#]\s*)?[a-z0-9_.-]+(?:\s+.+)?$/i.test(line) || /^#/.test(line)).length >=
              Math.max(1, Math.ceil(lines.length * 0.6))
          ? 'terminal'
          : 'plain';

  switch (type) {
    case 'text':
      if (detectedFormat === 'terminal') {
        return renderEditorBlock(textValue, 'bash', 'code');
      }

      if (detectedFormat === 'paths') {
        return renderEditorBlock(textValue, 'paths', 'code');
      }

      return <div className="previewer-ui__text">{textValue}</div>;
    case 'json':
      return renderEditorBlock(stringifyValue(normalizeJsonValue(value)), 'json', 'json');
    case 'code':
      return renderEditorBlock(textValue, language ?? 'text', 'code');
    case 'image':
      return (
        <InlineImage
          className="previewer-ui__image"
          src={String(value ?? '')}
          alt={imageAlt ?? 'preview'}
          fallbackText="이미지 preview를 불러오지 못했습니다."
        />
      );
    case 'markdown':
      return <div className="previewer-ui__markdown">{renderMarkdown(textValue)}</div>;
    case 'empty':
      return (
        <div className="previewer-ui__empty">
          <Empty description={textValue || '표시할 데이터가 없습니다.'} />
        </div>
      );
    default:
      return <div className="previewer-ui__text">{textValue}</div>;
  }
}

export function PreviewerUI({
  type,
  title = 'Preview',
  description,
  showHeader = true,
  value,
  copyValue,
  copyable = true,
  downloadable = true,
  downloadValue,
  downloadUrl,
  downloadFileName,
  maximizable = true,
  language = 'text',
  format = 'auto',
  languageOptions,
  onLanguageChange,
  imageAlt,
  height = 280,
  toolbar,
  className,
}: PreviewerUIProps) {
  const [messageApi, contextHolder] = message.useMessage();
  const [isExpanded, setIsExpanded] = useState(false);
  const [isFindOpen, setIsFindOpen] = useState(false);
  const [findQuery, setFindQuery] = useState('');
  const findInputRef = useRef<HTMLInputElement | null>(null);
  const hasLanguageSelector = type === 'code' && languageOptions && languageOptions.length > 0;
  const resolvedCopyValue = copyValue ?? resolveCopyValue({ type, value });
  const resolvedDownloadValue = resolveDownloadValue({ type, value, downloadValue });
  const resolvedDownloadFileName = resolveDownloadFileName({ type, language, downloadFileName });
  const canCopy = copyable && resolvedCopyValue.trim().length > 0;
  const canDownload = downloadable && (Boolean(downloadUrl) || resolvedDownloadValue.trim().length > 0);
  const canFind = type !== 'image' && type !== 'empty';
  const shouldShowActions = hasLanguageSelector || canCopy || canDownload || maximizable || canFind || Boolean(toolbar);
  const usesLightExpandedSurface = type === 'text' || type === 'json' || type === 'markdown' || type === 'empty';

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

    const previousOverflow = document.body.style.overflow;
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        setIsExpanded(false);
      }
    };

    document.body.style.overflow = 'hidden';
    document.addEventListener('keydown', handleKeyDown);

    return () => {
      document.body.style.overflow = previousOverflow;
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isExpanded]);

  useEffect(() => {
    if (!isExpanded) {
      setIsFindOpen(false);
      setFindQuery('');
      return;
    }

    if (isFindOpen) {
      window.setTimeout(() => findInputRef.current?.focus(), 0);
    }
  }, [isExpanded, isFindOpen]);

  async function handleCopy() {
    if (!canCopy) {
      return;
    }

    try {
      await copyTextToClipboard(resolvedCopyValue);
      messageApi.success('복사했습니다.');
    } catch {
      messageApi.error('복사에 실패했습니다.');
    }
  }

  async function toggleFullscreen() {
    if (!maximizable) {
      return;
    }

    setIsExpanded((previous) => !previous);
  }

  function handleFind(direction: 'forward' | 'backward' = 'forward') {
    const keyword = findQuery.trim();
    if (!keyword) {
      messageApi.info('찾을 단어를 입력해주세요.');
      return;
    }

    const browserWindow = typeof window === 'undefined' ? null : (window as Window & {
      find?: (
        text: string,
        caseSensitive?: boolean,
        backwards?: boolean,
        wrapAround?: boolean,
        wholeWord?: boolean,
        searchInFrames?: boolean,
        showDialog?: boolean,
      ) => boolean;
    });

    if (!browserWindow || typeof browserWindow.find !== 'function') {
      messageApi.error('이 브라우저에서는 단어 찾기를 지원하지 않습니다.');
      return;
    }

    const matched = browserWindow.find(keyword, false, direction === 'backward', true, false, false, false);
    if (!matched) {
      messageApi.info('일치하는 단어를 찾지 못했습니다.');
    }
  }

  function toggleFind() {
    if (!canFind) {
      return;
    }

    setIsFindOpen((previous) => !previous);
  }

  function handleDownload() {
    if (!canDownload) {
      return;
    }

    try {
      if (downloadUrl) {
        if (typeof document === 'undefined') {
          throw new Error('다운로드를 사용할 수 없습니다.');
        }

        const link = document.createElement('a');
        link.href = downloadUrl;
        link.download = resolvedDownloadFileName;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      } else {
        const mimeType =
          type === 'json'
            ? 'application/json;charset=utf-8'
            : type === 'markdown'
              ? 'text/markdown;charset=utf-8'
              : 'text/plain;charset=utf-8';
        downloadBlob(resolvedDownloadValue, resolvedDownloadFileName, mimeType);
      }
    } catch {
      messageApi.error('다운로드에 실패했습니다.');
    }
  }

  const actionContent = (
    <>
      {hasLanguageSelector ? (
        <Select
          size="small"
          value={language}
          options={languageOptions}
          onChange={onLanguageChange}
          className="previewer-ui__language-select"
        />
      ) : null}
      {canCopy ? (
        <Button
          size="small"
          type="text"
          className="previewer-ui__action-button"
          aria-label="복사"
          icon={<CopyOutlined />}
          onClick={() => void handleCopy()}
        />
      ) : null}
      {canDownload ? (
        <Button
          size="small"
          type="text"
          className="previewer-ui__action-button"
          aria-label="다운로드"
          icon={<DownloadOutlined />}
          onClick={handleDownload}
        />
      ) : null}
      {maximizable ? (
        <Button
          size="small"
          type="text"
          className="previewer-ui__action-button"
          aria-label={isExpanded ? '최대화 해제' : '최대화'}
          icon={isExpanded ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
          onClick={() => void toggleFullscreen()}
        />
      ) : null}
      {canFind ? (
        <Button
          size="small"
          type="text"
          className="previewer-ui__action-button"
          aria-label="단어 찾기"
          icon={<SearchOutlined />}
          onClick={toggleFind}
        />
      ) : null}
      {toolbar}
    </>
  );

  return (
    <>
      {contextHolder}
      <div
        className={[
          'previewer-ui',
          !showHeader ? 'previewer-ui--headerless' : '',
          isExpanded ? 'previewer-ui--expanded' : '',
          usesLightExpandedSurface ? 'previewer-ui--light-surface' : '',
          className ?? '',
        ]
          .filter(Boolean)
          .join(' ')}
      >
        {showHeader ? (
          <div className="previewer-ui__header">
            <div className="previewer-ui__meta">
              <div className="previewer-ui__title">{title}</div>
              {description ? <div className="previewer-ui__description">{description}</div> : null}
            </div>
            {shouldShowActions ? <div className="previewer-ui__toolbar">{actionContent}</div> : null}
          </div>
        ) : null}
        <div className="previewer-ui__body previewer-ui__scroll" style={{ height }}>
          {!showHeader && shouldShowActions ? <div className="previewer-ui__floating-toolbar">{actionContent}</div> : null}
          {isExpanded && isFindOpen ? (
            <div className="previewer-ui__findbar">
              <Input
                ref={(node) => {
                  findInputRef.current = node?.input ?? null;
                }}
                size="small"
                value={findQuery}
                placeholder="단어 찾기"
                allowClear
                onChange={(event) => setFindQuery(event.target.value)}
                onPressEnter={(event) => handleFind(event.shiftKey ? 'backward' : 'forward')}
              />
              <Button size="small" onClick={() => handleFind('backward')}>
                이전
              </Button>
              <Button size="small" type="primary" onClick={() => handleFind('forward')}>
                다음
              </Button>
            </div>
          ) : null}
          {renderContent({
            type,
            value,
            language,
            format,
            imageAlt,
          })}
        </div>
      </div>
    </>
  );
}
