import { DownOutlined, RightOutlined } from '@ant-design/icons';
import { Button, Empty, Space, Tag, Typography } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { renderEditorBlock } from './renderers';
import './CodexDiffPreviewer.css';

const { Text } = Typography;

export type CodexDiffFileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'binary' | 'unknown';

export type CodexDiffSection = {
  key: string;
  path: string;
  previousPath: string | null;
  status: CodexDiffFileStatus;
  diff: string;
};

export const CODEX_DIFF_STATUS_LABEL_MAP: Record<CodexDiffFileStatus, string> = {
  added: '신규',
  modified: '변경',
  deleted: '삭제',
  renamed: '이동',
  binary: '바이너리',
  unknown: '기타',
};

type CodexDiffBlockProps = {
  diffText?: string | null;
  summary?: string;
  emptyDescription?: string;
  showToolbar?: boolean;
  className?: string;
};

function inferDiffSectionStatus(lines: string[]): CodexDiffFileStatus {
  if (lines.some((line) => line.startsWith('rename from '))) {
    return 'renamed';
  }

  if (lines.some((line) => line.startsWith('new file mode '))) {
    return 'added';
  }

  if (lines.some((line) => line.startsWith('deleted file mode '))) {
    return 'deleted';
  }

  if (lines.some((line) => /^Binary files\b/.test(line) || line.startsWith('GIT binary patch'))) {
    return 'binary';
  }

  if (lines.some((line) => line.startsWith('@@ ') || line.startsWith('@@@ '))) {
    return 'modified';
  }

  return 'unknown';
}

function resolveDiffPath(lines: string[]) {
  const renameTo = lines.find((line) => line.startsWith('rename to '))?.slice('rename to '.length).trim();
  const renameFrom = lines.find((line) => line.startsWith('rename from '))?.slice('rename from '.length).trim() ?? null;
  const plusLine = lines.find((line) => line.startsWith('+++ '));
  const diffLine = lines.find((line) => line.startsWith('diff --git '));

  if (renameTo) {
    return {
      path: renameTo,
      previousPath: renameFrom,
    };
  }

  if (plusLine) {
    const rawPath = plusLine.slice(4).trim();
    if (rawPath !== '/dev/null') {
      return {
        path: rawPath.replace(/^b\//, ''),
        previousPath: renameFrom,
      };
    }
  }

  if (diffLine) {
    const match = diffLine.match(/^diff --git a\/(.+?) b\/(.+)$/);
    if (match) {
      return {
        path: match[2],
        previousPath: match[1] === match[2] ? renameFrom : match[1],
      };
    }
  }

  return {
    path: renameTo ?? renameFrom ?? 'raw.diff',
    previousPath: renameFrom && renameFrom !== renameTo ? renameFrom : null,
  };
}

function isUnifiedDiffFileHeaderStart(line: string, nextLine: string | undefined) {
  return line.startsWith('--- ') && Boolean(nextLine?.startsWith('+++ '));
}

function hasResolvedDiffHeader(lines: string[]) {
  return lines.some((line) => line.startsWith('diff --git ') || line.startsWith('+++ '));
}

export function parseCodexDiffSections(diffText: string | null | undefined): CodexDiffSection[] {
  if (!diffText?.trim()) {
    return [];
  }

  const normalized = diffText.replace(/\r\n/g, '\n');
  const lines = normalized.split('\n');
  const sections: CodexDiffSection[] = [];
  let current: string[] = [];

  const flush = () => {
    if (!current.length) {
      return;
    }

    const { path, previousPath } = resolveDiffPath(current);
    sections.push({
      key: `${sections.length}:${path}`,
      path,
      previousPath,
      status: inferDiffSectionStatus(current),
      diff: current.join('\n').trimEnd(),
    });
    current = [];
  };

  for (let index = 0; index < lines.length; index += 1) {
    const line = lines[index] ?? '';
    const nextLine = lines[index + 1];

    if (line.startsWith('diff --git ') && current.length) {
      flush();
    }

    if (current.length && isUnifiedDiffFileHeaderStart(line, nextLine) && hasResolvedDiffHeader(current)) {
      flush();
    }

    current.push(line);
  }

  flush();

  return sections.length
    ? sections
    : [
        {
          key: '0:raw.diff',
          path: 'raw.diff',
          previousPath: null,
          status: 'unknown',
          diff: normalized.trimEnd(),
        },
      ];
}

function resolveTagColor(status: CodexDiffFileStatus) {
  if (status === 'added') {
    return 'green';
  }

  if (status === 'modified') {
    return 'blue';
  }

  if (status === 'deleted') {
    return 'red';
  }

  if (status === 'renamed') {
    return 'gold';
  }

  return 'default';
}

export function CodexDiffBlock({
  diffText,
  summary,
  emptyDescription = '표시할 diff가 없습니다.',
  showToolbar = true,
  className,
}: CodexDiffBlockProps) {
  const diffSections = useMemo(() => parseCodexDiffSections(diffText), [diffText]);
  const hasToolbar = Boolean(summary) || showToolbar;
  const [expandedDiffKey, setExpandedDiffKey] = useState<string | null>(() => diffSections[0]?.key ?? null);
  const [isAllExpanded, setIsAllExpanded] = useState(false);

  useEffect(() => {
    setExpandedDiffKey((currentKey) => {
      if (currentKey && diffSections.some((section) => section.key === currentKey)) {
        return currentKey;
      }

      return diffSections[0]?.key ?? null;
    });
  }, [diffSections]);
  if (!diffSections.length) {
    return <Empty description={emptyDescription} />;
  }

  return (
    <div
      className={[
        className ?? 'codex-diff-previewer__diff-list',
        isAllExpanded ? 'codex-diff-previewer__diff-list--expand-all' : '',
      ]
        .filter(Boolean)
        .join(' ')}
    >
      {hasToolbar ? (
        <div className="codex-diff-previewer__diff-toolbar">
          {summary ? <Text type="secondary">{summary}</Text> : <span />}
          {showToolbar ? (
            <Space size={8} wrap>
              <Button
                size="small"
                onClick={() => {
                  setIsAllExpanded(true);
                }}
              >
                전체 펼치기
              </Button>
              <Button
                size="small"
                onClick={() => {
                  setIsAllExpanded(false);
                  setExpandedDiffKey(diffSections[0]?.key ?? null);
                }}
              >
                첫 파일 열기
              </Button>
              <Button
                size="small"
                onClick={() => {
                  setIsAllExpanded(false);
                  setExpandedDiffKey(null);
                }}
              >
                전체 접기
              </Button>
            </Space>
          ) : null}
        </div>
      ) : null}

      {diffSections.map((section) => {
        const isExpanded = isAllExpanded || expandedDiffKey === section.key;
        const displayPath = section.previousPath ? `${section.previousPath} -> ${section.path}` : section.path;

        return (
          <section className="codex-diff-previewer__diff-section" key={section.key}>
            <button
              type="button"
              className="codex-diff-previewer__diff-toggle"
              onClick={() => {
                if (isAllExpanded) {
                  setIsAllExpanded(false);
                  setExpandedDiffKey(section.key);
                  return;
                }

                setExpandedDiffKey((currentKey) => (currentKey === section.key ? null : section.key));
              }}
            >
              <Space align="center" size={10} className="codex-diff-previewer__diff-toggle-main">
                {isExpanded ? <DownOutlined /> : <RightOutlined />}
                <span className="codex-diff-previewer__file-name">{section.path.split('/').pop() ?? section.path}</span>
                <Tag bordered={false} color={resolveTagColor(section.status)}>
                  {CODEX_DIFF_STATUS_LABEL_MAP[section.status]}
                </Tag>
              </Space>
              <Text type="secondary" className="codex-diff-previewer__file-path">
                {displayPath}
              </Text>
            </button>

            {isExpanded ? (
              <div className="codex-diff-previewer__diff-body">
                {renderEditorBlock(section.diff, 'diff', 'code')}
              </div>
            ) : null}
          </section>
        );
      })}
    </div>
  );
}
