import {
  ArrowLeftOutlined,
  CheckSquareOutlined,
  CloseOutlined,
  CopyOutlined,
  DeleteOutlined,
  DownOutlined,
  EyeOutlined,
  FileTextOutlined,
  LinkOutlined,
  PaperClipOutlined,
  PlayCircleOutlined,
  PlusOutlined,
  SaveOutlined,
  ShrinkOutlined,
  ArrowsAltOutlined,
  UpOutlined,
  UploadOutlined,
} from '@ant-design/icons';
import { Button, Card, Checkbox, Empty, Flex, Input, List, Segmented, Select, Space, Spin, Tag, Typography, message } from 'antd';
import type { ChangeEvent, RefObject } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { uploadChatComposerFile } from '../../app/main/mainChatPanel';
import {
  buildAutomationTypeOptions,
  resolveAutomationTypeLabel,
  useAutomationTypeRegistry,
} from '../../app/main/automationTypeAccess';
import { MarkdownPreviewContent } from '../../components/markdownPreview';
import { copyTextToClipboard } from '../../utils/clipboard';
import {
  createBoardPost,
  deleteBoardPost,
  fetchBoardPosts,
  receiveBoardPostAutomation,
  setupBoard,
  updateBoardPost,
} from './api';
import type { BoardAttachment, BoardDraft, BoardPost, BoardRequestExecutionMode } from './types';

const { Paragraph, Text, Title } = Typography;
const { TextArea } = Input;

const REQUEST_EXECUTION_MODE_OPTIONS: Array<{ value: BoardRequestExecutionMode; label: string }> = [
  { value: 'all_at_once', label: '즉시 일괄 등록' },
  { value: 'after_previous_finished', label: '이전 완료 후 다음 등록' },
  { value: 'after_previous_success', label: '이전 성공 후 다음 등록' },
];

function createBoardAttachmentSessionId() {
  const randomValue =
    typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
      ? crypto.randomUUID()
      : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;

  return `board-draft-${randomValue}`;
}

function createBoardRequestDraftItem(title = '', content = '') {
  const key =
    typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
      ? crypto.randomUUID()
      : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;

  return { key, title, content };
}

const EMPTY_DRAFT: BoardDraft = {
  id: null,
  title: '',
  content: '',
  attachments: [],
  automationType: 'none',
  requestExecutionMode: 'all_at_once',
  requestItems: [createBoardRequestDraftItem()],
};

function formatDateTime(value: string) {
  return new Date(value).toLocaleString('ko-KR', {
    timeZone: 'Asia/Seoul',
  });
}

function formatBytes(value: number) {
  if (!Number.isFinite(value) || value <= 0) {
    return '0 B';
  }

  if (value >= 1024 * 1024) {
    return `${(value / (1024 * 1024)).toFixed(1)} MB`;
  }

  if (value >= 1024) {
    return `${(value / 1024).toFixed(1)} KB`;
  }

  return `${Math.round(value)} B`;
}

function mergeBoardAttachments(current: BoardAttachment[], next: BoardAttachment[]) {
  const merged = [...current];
  const existingPaths = new Set(current.map((item) => item.path));

  next.forEach((item) => {
    if (existingPaths.has(item.path)) {
      return;
    }

    existingPaths.add(item.path);
    merged.push(item);
  });

  return merged;
}

function resolveBoardAttachmentSessionId(
  draftId: number | null,
  draftAttachmentSessionIdRef: RefObject<string>,
) {
  if (draftId) {
    return `board-post-${draftId}`;
  }

  return draftAttachmentSessionIdRef.current;
}

function hasBoardPostAutomation(item: BoardPost | null | undefined) {
  if (!item) {
    return false;
  }

  return item.requestItems.some((requestItem) => requestItem.planItemId || requestItem.automationReceivedAt);
}

function buildRequestSummaryText(item: BoardPost | null | undefined) {
  if (!item) {
    return '요청 없음';
  }

  const { total, completed, failed, running, queued, waiting, blocked } = item.requestSummary;
  const parts = [
    `완료 ${completed}/${total}`,
    failed > 0 ? `실패 ${failed}` : null,
    running > 0 ? `진행 ${running}` : null,
    queued > 0 ? `대기열 ${queued}` : null,
    waiting > 0 ? `미등록 ${waiting}` : null,
    blocked > 0 ? `차단 ${blocked}` : null,
  ].filter(Boolean);

  return parts.join(' · ');
}

function resolveBoardAutomationStatus(
  draftId: number | null,
  selectedItem: BoardPost | null,
  draftDirty: boolean,
  errorMessage: string | null,
) {
  if (errorMessage) {
    return {
      color: 'error',
      label: '접수 실패',
      description: errorMessage,
    };
  }

  if (!draftId) {
    return {
      color: 'default',
      label: '저장 필요',
      description: '저장 후 자동화 접수 가능',
    };
  }

  if (draftDirty) {
    return {
      color: 'warning',
      label: '저장 필요',
      description: '변경 내용을 저장한 뒤 접수',
    };
  }

  if (hasBoardPostAutomation(selectedItem)) {
    return {
      color: 'processing',
      label: '접수중',
      description: buildRequestSummaryText(selectedItem),
    };
  }

  return {
    color: 'default',
    label: '대기',
    description: '자동화 접수 전',
  };
}

function getBoardPostAutomationReceiveError(item: BoardPost, dirtyDraftId: number | null) {
  if (item.id === dirtyDraftId) {
    return '변경 내용을 저장한 뒤 자동화 접수하세요.';
  }

  if (hasBoardPostAutomation(item)) {
    return '이미 자동화 접수된 게시글입니다.';
  }

  return null;
}

function mapBoardPostToDraft(item: BoardPost): BoardDraft {
  return {
    id: item.id,
    title: item.title,
    content: item.content,
    attachments: item.attachments,
    automationType: item.automationType,
    requestExecutionMode: item.requestExecutionMode,
    requestItems:
      item.requestItems.length > 0
        ? item.requestItems.map((requestItem) => createBoardRequestDraftItem(requestItem.title, requestItem.content))
        : [createBoardRequestDraftItem()],
  };
}

function normalizeDraftRequestItems(draft: BoardDraft) {
  return draft.requestItems
    .map((requestItem) => ({
      title: requestItem.title.trim(),
      content: requestItem.content.trim(),
    }))
    .filter((requestItem) => requestItem.title || requestItem.content);
}

function areDraftRequestsEqual(draft: BoardDraft, item: BoardPost | null) {
  if (!item) {
    return false;
  }

  const normalizedDraftRequests = normalizeDraftRequestItems(draft);
  const normalizedItemRequests = item.requestItems.map((requestItem) => ({
    title: requestItem.title.trim(),
    content: requestItem.content.trim(),
  }));

  return JSON.stringify(normalizedDraftRequests) === JSON.stringify(normalizedItemRequests);
}

export function BoardPage() {
  const [messageApi, contextHolder] = message.useMessage();
  const { automationTypes } = useAutomationTypeRegistry();
  const attachmentInputRef = useRef<HTMLInputElement | null>(null);
  const draftAttachmentSessionIdRef = useRef<string>(createBoardAttachmentSessionId());
  const [items, setItems] = useState<BoardPost[]>([]);
  const [selectedId, setSelectedId] = useState<number | null>(null);
  const [checkedIds, setCheckedIds] = useState<number[]>([]);
  const [draft, setDraft] = useState<BoardDraft>(EMPTY_DRAFT);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [deleting, setDeleting] = useState(false);
  const [attachmentUploading, setAttachmentUploading] = useState(false);
  const [automationReceiving, setAutomationReceiving] = useState(false);
  const [automationReceiveError, setAutomationReceiveError] = useState<string | null>(null);
  const [errorMessage, setErrorMessage] = useState<string | null>(null);
  const [detailMode, setDetailMode] = useState<'list' | 'detail'>('list');
  const [isMobileViewport, setIsMobileViewport] = useState(false);
  const [mobileView, setMobileView] = useState<'edit' | 'preview'>('edit');
  const [maximizedPane, setMaximizedPane] = useState<'none' | 'edit' | 'preview'>('none');
  const [attachmentsExpanded, setAttachmentsExpanded] = useState(false);

  useEffect(() => {
    let cancelled = false;

    const load = async () => {
      setLoading(true);
      setErrorMessage(null);

      try {
        await setupBoard();
        const nextItems = await fetchBoardPosts();

        if (cancelled) {
          return;
        }

        setItems(nextItems);
        setSelectedId((previous) => previous ?? nextItems[0]?.id ?? null);
      } catch (error) {
        if (!cancelled) {
          setErrorMessage(error instanceof Error ? error.message : '게시판을 불러오지 못했습니다.');
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };

    void load();

    return () => {
      cancelled = true;
    };
  }, []);

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

    const mediaQuery = window.matchMedia('(max-width: 960px)');
    const update = () => {
      setIsMobileViewport(mediaQuery.matches);
      if (!mediaQuery.matches) {
        setMobileView('edit');
      }
      setAttachmentsExpanded(!mediaQuery.matches);
    };

    update();
    mediaQuery.addEventListener('change', update);

    return () => {
      mediaQuery.removeEventListener('change', update);
    };
  }, []);

  const selectedItem = useMemo(
    () => items.find((item) => item.id === selectedId) ?? null,
    [items, selectedId],
  );
  const automationReceived = hasBoardPostAutomation(selectedItem);
  const isDraftLocked = automationReceived;
  const draftDirty = Boolean(
    selectedItem
      && (
        draft.title !== selectedItem.title
        || draft.content !== selectedItem.content
        || draft.automationType !== selectedItem.automationType
        || draft.requestExecutionMode !== selectedItem.requestExecutionMode
        || !areDraftRequestsEqual(draft, selectedItem)
      ),
  );
  const dirtyDraftId = draftDirty && draft.id ? draft.id : null;
  const automationStatus = resolveBoardAutomationStatus(draft.id, selectedItem, draftDirty, automationReceiveError);
  const automationTypeOptions = useMemo(
    () => buildAutomationTypeOptions(automationTypes, draft.automationType),
    [automationTypes, draft.automationType],
  );
  const automationTypeLabel = useMemo(
    () => resolveAutomationTypeLabel(automationTypes, draft.automationType),
    [automationTypes, draft.automationType],
  );
  const isPaneMaximized = maximizedPane !== 'none';
  const receivableIds = useMemo(
    () =>
      items
        .filter((item) => !getBoardPostAutomationReceiveError(item, dirtyDraftId))
        .map((item) => item.id),
    [dirtyDraftId, items],
  );
  const receivableIdSet = useMemo(() => new Set(receivableIds), [receivableIds]);
  const checkedReceivableCount = checkedIds.filter((id) => receivableIdSet.has(id)).length;
  const allReceivableChecked = receivableIds.length > 0 && checkedReceivableCount === receivableIds.length;
  const partiallyChecked = checkedReceivableCount > 0 && checkedReceivableCount < receivableIds.length;
  const primaryPlanItemId = selectedItem?.requestItems.find((requestItem) => requestItem.planItemId)?.planItemId ?? null;

  useEffect(() => {
    if (selectedItem) {
      setDraft(mapBoardPostToDraft(selectedItem));
      setAutomationReceiveError(null);
      return;
    }

    setDraft(EMPTY_DRAFT);
    setAutomationReceiveError(null);
  }, [selectedItem]);

  useEffect(() => {
    const itemIdSet = new Set(items.map((item) => item.id));
    setCheckedIds((previous) => previous.filter((id) => itemIdSet.has(id)));
  }, [items]);

  const handleCreateDraft = () => {
    draftAttachmentSessionIdRef.current = createBoardAttachmentSessionId();
    setSelectedId(null);
    setDraft({
      ...EMPTY_DRAFT,
      requestItems: [createBoardRequestDraftItem()],
    });
    setAutomationReceiveError(null);
    setMaximizedPane('none');
    setMobileView('edit');
    setDetailMode('detail');
  };

  const handleOpenDetail = (itemId: number) => {
    setSelectedId(itemId);
    setAutomationReceiveError(null);
    setMaximizedPane('none');
    setMobileView('edit');
    setDetailMode('detail');
  };

  const handleCloseDetail = () => {
    setAutomationReceiveError(null);
    setMaximizedPane('none');
    setDetailMode('list');
  };

  const handleAttachmentFilesPicked = async (files: File[]) => {
    if (files.length === 0 || attachmentUploading || isDraftLocked) {
      return;
    }

    setAttachmentUploading(true);

    try {
      const sessionId = resolveBoardAttachmentSessionId(draft.id, draftAttachmentSessionIdRef);
      const uploadResults = await Promise.allSettled(files.map((file) => uploadChatComposerFile(sessionId, file)));
      const uploadedItems: BoardAttachment[] = [];
      const failedFileNames: string[] = [];

      uploadResults.forEach((result, index) => {
        if (result.status === 'fulfilled') {
          uploadedItems.push(result.value);
          return;
        }

        failedFileNames.push(files[index]?.name || `파일 ${index + 1}`);
      });

      if (uploadedItems.length > 0) {
        setDraft((previous) => ({
          ...previous,
          attachments: mergeBoardAttachments(previous.attachments, uploadedItems),
        }));
        messageApi.success(`첨부 파일 ${uploadedItems.length}건을 추가했습니다.`);
      }

      if (failedFileNames.length > 0) {
        messageApi.error(`업로드 실패: ${failedFileNames.join(', ')}`);
      }
    } finally {
      setAttachmentUploading(false);
    }
  };

  const handleAttachmentInputChange = (event: ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files ?? []);
    event.target.value = '';
    void handleAttachmentFilesPicked(files);
  };

  const handleRemoveAttachment = (attachmentId: string) => {
    if (isDraftLocked) {
      return;
    }

    setDraft((previous) => ({
      ...previous,
      attachments: previous.attachments.filter((attachment) => attachment.id !== attachmentId),
    }));
  };

  const handleSave = async () => {
    const normalizedTitle = draft.title.trim();
    const normalizedContent = draft.content.trim();
    const normalizedRequestItems = normalizeDraftRequestItems(draft);

    if (!normalizedTitle) {
      messageApi.warning('제목을 입력하세요.');
      return;
    }

    if (!normalizedRequestItems.length) {
      messageApi.warning('하위 요청을 1개 이상 입력하세요.');
      return;
    }

    if (normalizedRequestItems.some((requestItem) => !requestItem.title || !requestItem.content)) {
      messageApi.warning('하위 요청 제목과 본문을 모두 입력하세요.');
      return;
    }

    setSaving(true);
    setAutomationReceiveError(null);

    try {
      const payload = {
        ...draft,
        title: normalizedTitle,
        content: normalizedContent,
        requestItems: draft.requestItems
          .map((requestItem) => ({
            ...requestItem,
            title: requestItem.title.trim(),
            content: requestItem.content.trim(),
          }))
          .filter((requestItem) => requestItem.title || requestItem.content),
      };
      const savedItem = draft.id ? await updateBoardPost(payload) : await createBoardPost(payload);

      setItems((previous) => {
        const filtered = previous.filter((item) => item.id !== savedItem.id);
        return [savedItem, ...filtered];
      });
      setSelectedId(savedItem.id);
      setDetailMode('detail');
      messageApi.success(draft.id ? '작업 요청을 수정했습니다.' : '작업 요청을 등록했습니다.');
    } catch (error) {
      messageApi.error(error instanceof Error ? error.message : '게시글 저장에 실패했습니다.');
    } finally {
      setSaving(false);
    }
  };

  const handleDelete = async () => {
    if (!draft.id) {
      handleCreateDraft();
      return;
    }

    setDeleting(true);

    try {
      await deleteBoardPost(draft.id);
      setItems((previous) => previous.filter((item) => item.id !== draft.id));
      setSelectedId((previous) => (previous === draft.id ? null : previous));
      setDraft({
        ...EMPTY_DRAFT,
        requestItems: [createBoardRequestDraftItem()],
      });
      setDetailMode('list');
      messageApi.success('게시글을 삭제했습니다.');
    } catch (error) {
      messageApi.error(error instanceof Error ? error.message : '게시글 삭제에 실패했습니다.');
    } finally {
      setDeleting(false);
    }
  };

  const handleCopyContent = async () => {
    if (!draft.content.trim()) {
      messageApi.warning('복사할 공통 메모가 없습니다.');
      return;
    }

    try {
      await copyTextToClipboard(draft.content);
      messageApi.success('공통 메모를 복사했습니다.');
    } catch (error) {
      messageApi.error(error instanceof Error ? error.message : '공통 메모 복사에 실패했습니다.');
    }
  };

  const handleAutomationReceive = async () => {
    if (!draft.id) {
      messageApi.warning('저장된 게시글만 자동화 접수할 수 있습니다.');
      return;
    }

    if (draftDirty) {
      messageApi.warning('변경 내용을 저장한 뒤 자동화 접수하세요.');
      return;
    }

    if (automationReceived) {
      messageApi.info('이미 자동화 접수된 게시글입니다.');
      return;
    }

    setAutomationReceiving(true);
    setAutomationReceiveError(null);

    try {
      const result = await receiveBoardPostAutomation(draft.id);
      if (result.item) {
        setItems((previous) => previous.map((item) => (item.id === result.item.id ? result.item : item)));
      }
      messageApi.success(
        result.alreadyReceived
          ? '이미 자동화 접수된 게시글입니다.'
          : `자동화 접수했습니다. ${result.planItemIds.length}건 등록`,
      );
    } catch (error) {
      const errorMessageText = error instanceof Error ? error.message : '자동화 접수에 실패했습니다.';
      setAutomationReceiveError(errorMessageText);
      messageApi.error(errorMessageText);
    } finally {
      setAutomationReceiving(false);
    }
  };

  const handleBulkAutomationReceive = async () => {
    if (!checkedIds.length) {
      messageApi.warning('접수할 게시글을 선택하세요.');
      return;
    }

    const targetItems = items.filter((item) => checkedIds.includes(item.id));
    const receivableItems = targetItems.filter((item) => !getBoardPostAutomationReceiveError(item, dirtyDraftId));

    if (!receivableItems.length) {
      messageApi.warning('선택한 게시글 중 자동화 접수할 수 있는 항목이 없습니다.');
      return;
    }

    setAutomationReceiving(true);
    setAutomationReceiveError(null);

    const updatedItems = new Map<number, BoardPost>();
    const alreadyReceivedIds: number[] = [];
    const failedItems: Array<{ id: number; message: string }> = [];

    try {
      for (const item of receivableItems) {
        try {
          const result = await receiveBoardPostAutomation(item.id);
          if (result.item) {
            updatedItems.set(result.item.id, result.item);
          }

          if (result.alreadyReceived) {
            alreadyReceivedIds.push(item.id);
          }
        } catch (error) {
          failedItems.push({
            id: item.id,
            message: error instanceof Error ? error.message : '자동화 접수에 실패했습니다.',
          });
        }
      }

      if (updatedItems.size > 0) {
        setItems((previous) => previous.map((item) => updatedItems.get(item.id) ?? item));
      }

      setCheckedIds((previous) => previous.filter((id) => !updatedItems.has(id)));

      const receivedCount = updatedItems.size - alreadyReceivedIds.length;
      const summaryParts = [
        receivedCount > 0 ? `${receivedCount}건 접수` : null,
        alreadyReceivedIds.length > 0 ? `${alreadyReceivedIds.length}건 기존 접수` : null,
        failedItems.length > 0 ? `${failedItems.length}건 실패` : null,
      ].filter(Boolean);

      if (failedItems.length > 0) {
        messageApi.warning(`선택 접수 결과: ${summaryParts.join(', ')}.`);
      } else {
        messageApi.success(`선택한 게시글을 처리했습니다. ${summaryParts.join(', ')}.`);
      }

      if (selectedItem) {
        const selectedFailure = failedItems.find((entry) => entry.id === selectedItem.id);
        setAutomationReceiveError(selectedFailure?.message ?? null);
      }
    } finally {
      setAutomationReceiving(false);
    }
  };

  return (
    <div
      className={`board-page${detailMode === 'detail' ? ' board-page--detail' : ''}${
        isPaneMaximized ? ' board-page--pane-maximized' : ''
      }`}
    >
      {contextHolder}
      <input
        ref={attachmentInputRef}
        type="file"
        multiple
        className="board-page__hidden-file-input"
        onChange={handleAttachmentInputChange}
      />
      <Space direction="vertical" size={16} className="board-page__stack">
        {detailMode === 'list' ? (
          <Card className="board-page__card board-page__overview-card" bordered={false}>
            <Flex justify="space-between" align="center" gap={16} wrap>
              <div>
                <Title level={4} className="board-page__title">
                  작업 요청
                </Title>
                <Paragraph className="board-page__copy">
                  작업 요청 1건 안에 여러 하위 요청을 담고, 각 요청별 자동화 진행 상태를 추적합니다.
                </Paragraph>
              </div>
              <Space wrap>
                <Button icon={<PlusOutlined />} onClick={handleCreateDraft}>
                  새 글
                </Button>
              </Space>
            </Flex>
          </Card>
        ) : null}

        {errorMessage && detailMode === 'list' ? (
          <Card className="board-page__card" bordered={false}>
            <Text type="danger">{errorMessage}</Text>
          </Card>
        ) : null}

        {detailMode === 'list' ? (
          <Card
            title={`게시글 목록 (${items.length})`}
            className="board-page__card board-page__list-card"
            bordered={false}
            extra={
              <Space size={8} wrap>
                <Button icon={<PlusOutlined />} onClick={handleCreateDraft}>
                  새 글
                </Button>
                {loading ? <Spin size="small" /> : null}
                <Text type="secondary" className="board-page__bulk-count">
                  선택 {checkedReceivableCount}건
                </Text>
                <Checkbox
                  indeterminate={partiallyChecked}
                  checked={allReceivableChecked}
                  disabled={!receivableIds.length}
                  onChange={(event) => {
                    setCheckedIds((previous) => {
                      if (event.target.checked) {
                        return Array.from(new Set([...previous, ...receivableIds]));
                      }

                      return previous.filter((id) => !receivableIdSet.has(id));
                    });
                  }}
                >
                  대기 전체
                </Checkbox>
                <Button
                  icon={<CheckSquareOutlined />}
                  loading={automationReceiving}
                  disabled={!checkedReceivableCount}
                  onClick={() => {
                    void handleBulkAutomationReceive();
                  }}
                >
                  선택 접수
                </Button>
              </Space>
            }
          >
            {loading ? (
              <div className="board-page__loading">
                <Spin />
              </div>
            ) : items.length ? (
              <List
                dataSource={items}
                renderItem={(item) => (
                  <List.Item
                    className={item.id === selectedId ? 'board-page__list-item is-active' : 'board-page__list-item'}
                    onClick={() => {
                      handleOpenDetail(item.id);
                    }}
                  >
                    <List.Item.Meta
                      avatar={
                        <Checkbox
                          checked={checkedIds.includes(item.id)}
                          disabled={Boolean(getBoardPostAutomationReceiveError(item, dirtyDraftId))}
                          onClick={(event) => {
                            event.stopPropagation();
                          }}
                          onChange={(event) => {
                            setCheckedIds((previous) =>
                              event.target.checked
                                ? Array.from(new Set([...previous, item.id]))
                                : previous.filter((checkedId) => checkedId !== item.id),
                            );
                          }}
                        />
                      }
                      title={
                        <Flex justify="space-between" align="center" gap={8}>
                          <Flex align="center" gap={8} className="board-page__list-title">
                            <FileTextOutlined className="board-page__list-icon" />
                            <Text strong>{item.title}</Text>
                          </Flex>
                          <Space size={6} wrap>
                            {item.id === dirtyDraftId ? <Tag color="warning">저장 필요</Tag> : null}
                            {item.attachments.length ? <Tag color="blue">첨부 {item.attachments.length}</Tag> : null}
                            <Tag color={hasBoardPostAutomation(item) ? 'processing' : 'default'}>
                              {hasBoardPostAutomation(item) ? buildRequestSummaryText(item) : '대기'}
                            </Tag>
                          </Space>
                        </Flex>
                      }
                      description={
                        <Space direction="vertical" size={4}>
                          <Text type="secondary" className="board-page__list-preview">
                            {item.preview || '공통 메모가 없습니다.'}
                          </Text>
                          <Text type="secondary">
                            하위 요청 {item.requestSummary.total}건 · 수정 {formatDateTime(item.updatedAt)}
                          </Text>
                        </Space>
                      }
                    />
                  </List.Item>
                )}
              />
            ) : (
              <Empty description="등록된 게시글이 없습니다." />
            )}
          </Card>
        ) : (
          <div className="board-page__editor-column">
            <Card
              title={draft.id ? `게시글 #${draft.id}` : '새 게시글'}
              className={`board-page__card board-page__editor-card${isPaneMaximized ? ' board-page__editor-card--pane-maximized' : ''}`}
              bordered={false}
              extra={
                <Space wrap className="board-page__header-actions">
                  <Button icon={<ArrowLeftOutlined />} aria-label="목록으로" title="목록으로" onClick={handleCloseDetail} />
                  <Button icon={<PlusOutlined />} onClick={handleCreateDraft} aria-label="새 글" title="새 글" />
                  <Button
                    type="primary"
                    icon={<SaveOutlined />}
                    aria-label="저장"
                    title="저장"
                    loading={saving}
                    disabled={isDraftLocked}
                    onClick={() => {
                      void handleSave();
                    }}
                  />
                  {draft.id ? <Tag color={automationStatus.color}>{automationStatus.label}</Tag> : null}
                  {draft.id && primaryPlanItemId ? (
                    <Button
                      icon={<LinkOutlined />}
                      aria-label="연결 자동화 열기"
                      title="연결 자동화 열기"
                      href={`/plans/all?planId=${primaryPlanItemId}`}
                      target="_blank"
                      rel="noreferrer"
                    />
                  ) : null}
                  {draft.id ? (
                    <Button
                      icon={<PlayCircleOutlined />}
                      aria-label={automationReceived ? '자동화 접수됨' : automationReceiveError ? '접수 재시도' : '자동화 접수'}
                      title={automationReceived ? '자동화 접수됨' : automationReceiveError ? '접수 재시도' : '자동화 접수'}
                      loading={automationReceiving}
                      disabled={automationReceived && !automationReceiveError}
                      onClick={() => {
                        void handleAutomationReceive();
                      }}
                    />
                  ) : null}
                  <Button
                    danger
                    icon={<DeleteOutlined />}
                    aria-label="삭제"
                    title="삭제"
                    loading={deleting}
                    disabled={!draft.id || isDraftLocked}
                    onClick={() => {
                      void handleDelete();
                    }}
                  />
                </Space>
              }
            >
              <div className="board-page__editor">
                {errorMessage ? <Text type="danger">{errorMessage}</Text> : null}
                <div className="board-page__editor-scroll">
                  <div className={`board-page__meta-stack${isPaneMaximized ? ' board-page__meta-stack--hidden' : ''}`}>
                    <div className="board-page__hero">
                      <div className="board-page__hero-main">
                        <div className="board-page__field-label-row">
                          <Text strong>요청 제목</Text>
                          <Flex gap={8} wrap>
                            <Tag color={automationStatus.color}>{automationStatus.label}</Tag>
                            {automationStatus.description ? <Text type="secondary">{automationStatus.description}</Text> : null}
                          </Flex>
                        </div>
                        <Input
                          size="large"
                          placeholder="예: 작업요청 입력 폼을 전면 개편하고 첨부 자동 전달 연결"
                          value={draft.title}
                          readOnly={isDraftLocked}
                          onChange={(event) => {
                            setDraft((previous) => ({
                              ...previous,
                              title: event.target.value,
                            }));
                          }}
                        />
                      </div>
                      <div className="board-page__hero-side">
                        <div className="board-page__automation-field">
                          <div className="board-page__field-label-row">
                            <Text strong>자동화 처리</Text>
                            {automationReceived ? <Tag color="processing">접수 후 읽기전용</Tag> : null}
                          </div>
                          {automationReceived ? (
                            <div className="board-page__automation-readonly" aria-readonly="true">
                              <Text>{automationTypeLabel}</Text>
                            </div>
                          ) : (
                            <Select
                              className="board-page__automation-select"
                              value={draft.automationType}
                              options={automationTypeOptions}
                              popupClassName="board-page__automation-select-popup"
                              getPopupContainer={(triggerNode) => triggerNode.parentElement ?? document.body}
                              disabled={isDraftLocked}
                              onChange={(automationType) => {
                                setDraft((previous) => ({
                                  ...previous,
                                  automationType,
                                }));
                              }}
                            />
                          )}
                        </div>
                        <div className="board-page__automation-field">
                          <div className="board-page__field-label-row">
                            <Text strong>등록 순서 옵션</Text>
                          </div>
                          {automationReceived ? (
                            <div className="board-page__automation-readonly" aria-readonly="true">
                              <Text>{REQUEST_EXECUTION_MODE_OPTIONS.find((option) => option.value === draft.requestExecutionMode)?.label}</Text>
                            </div>
                          ) : (
                            <Select
                              className="board-page__automation-select"
                              value={draft.requestExecutionMode}
                              options={REQUEST_EXECUTION_MODE_OPTIONS}
                              disabled={isDraftLocked}
                              onChange={(requestExecutionMode) => {
                                setDraft((previous) => ({
                                  ...previous,
                                  requestExecutionMode,
                                }));
                              }}
                            />
                          )}
                        </div>
                      </div>
                    </div>

                    <div className="board-page__attachment-panel">
                      <Flex justify="space-between" align="center" gap={12} wrap>
                        <div>
                          <div className="board-page__field-label-row">
                            <Text strong>하위 요청</Text>
                            <Tag color="blue">{draft.requestItems.length}건</Tag>
                          </div>
                          <Text type="secondary">각 하위 요청이 개별 자동화 작업으로 등록되고 상태를 따로 추적합니다.</Text>
                        </div>
                        {!isDraftLocked ? (
                          <Button
                            icon={<PlusOutlined />}
                            onClick={() => {
                              setDraft((previous) => ({
                                ...previous,
                                requestItems: [...previous.requestItems, createBoardRequestDraftItem()],
                              }));
                            }}
                          >
                            요청 추가
                          </Button>
                        ) : null}
                      </Flex>
                      <Space direction="vertical" size={12} style={{ width: '100%' }}>
                        {draft.requestItems.map((requestItem, index) => {
                          const requestStatus = selectedItem?.requestItems[index] ?? null;
                          return (
                            <div key={requestItem.key} className="board-page__request-card">
                              <Flex justify="space-between" align="center" gap={12} wrap>
                                <Space size={8} wrap>
                                  <Tag color="blue">요청 {index + 1}</Tag>
                                  {requestStatus ? <Tag color={requestStatus.status === 'completed' ? 'success' : requestStatus.status === 'failed' ? 'error' : requestStatus.status === 'blocked' ? 'default' : 'processing'}>{requestStatus.statusLabel}</Tag> : null}
                                  {requestStatus?.planItemId ? (
                                    <Button
                                      size="small"
                                      icon={<LinkOutlined />}
                                      href={`/plans/all?planId=${requestStatus.planItemId}`}
                                      target="_blank"
                                      rel="noreferrer"
                                    />
                                  ) : null}
                                </Space>
                                {!isDraftLocked ? (
                                  <Space size={6}>
                                    <Button
                                      size="small"
                                      icon={<UpOutlined />}
                                      disabled={index === 0}
                                      onClick={() => {
                                        setDraft((previous) => {
                                          const nextRequestItems = [...previous.requestItems];
                                          [nextRequestItems[index - 1], nextRequestItems[index]] = [nextRequestItems[index], nextRequestItems[index - 1]];
                                          return {
                                            ...previous,
                                            requestItems: nextRequestItems,
                                          };
                                        });
                                      }}
                                    />
                                    <Button
                                      size="small"
                                      icon={<DownOutlined />}
                                      disabled={index === draft.requestItems.length - 1}
                                      onClick={() => {
                                        setDraft((previous) => {
                                          const nextRequestItems = [...previous.requestItems];
                                          [nextRequestItems[index], nextRequestItems[index + 1]] = [nextRequestItems[index + 1], nextRequestItems[index]];
                                          return {
                                            ...previous,
                                            requestItems: nextRequestItems,
                                          };
                                        });
                                      }}
                                    />
                                    <Button
                                      size="small"
                                      danger
                                      icon={<CloseOutlined />}
                                      disabled={draft.requestItems.length <= 1}
                                      onClick={() => {
                                        setDraft((previous) => ({
                                          ...previous,
                                          requestItems: previous.requestItems.filter((_, requestIndex) => requestIndex !== index),
                                        }));
                                      }}
                                    />
                                  </Space>
                                ) : null}
                              </Flex>
                              <Input
                                placeholder={`하위 요청 ${index + 1} 제목`}
                                value={requestItem.title}
                                readOnly={isDraftLocked}
                                onChange={(event) => {
                                  setDraft((previous) => ({
                                    ...previous,
                                    requestItems: previous.requestItems.map((candidate, requestIndex) =>
                                      requestIndex === index
                                        ? {
                                            ...candidate,
                                            title: event.target.value,
                                          }
                                        : candidate,
                                    ),
                                  }));
                                }}
                              />
                              <TextArea
                                value={requestItem.content}
                                placeholder="이 요청에서 실제로 처리할 내용을 적어 주세요."
                                readOnly={isDraftLocked}
                                autoSize={{ minRows: 4, maxRows: 10 }}
                                onChange={(event) => {
                                  setDraft((previous) => ({
                                    ...previous,
                                    requestItems: previous.requestItems.map((candidate, requestIndex) =>
                                      requestIndex === index
                                        ? {
                                            ...candidate,
                                            content: event.target.value,
                                          }
                                        : candidate,
                                    ),
                                  }));
                                }}
                              />
                              {requestStatus?.lastError ? <Text type="danger">{requestStatus.lastError}</Text> : null}
                            </div>
                          );
                        })}
                      </Space>
                    </div>

                    <div className="board-page__attachment-panel">
                      <Flex justify="space-between" align="center" gap={12} wrap>
                        <div>
                          <div className="board-page__field-label-row">
                            <Text strong>첨부 파일</Text>
                            <Tag color={draft.attachments.length ? 'blue' : 'default'}>
                              {draft.attachments.length}건
                            </Tag>
                          </div>
                          <Text type="secondary">모든 하위 요청의 자동화 메모에 아래 첨부 파일 경로가 공통으로 포함됩니다.</Text>
                        </div>
                        <Space size={8}>
                          <Button
                            type="text"
                            icon={attachmentsExpanded ? <UpOutlined /> : <DownOutlined />}
                            aria-label={attachmentsExpanded ? '첨부 파일 접기' : '첨부 파일 펼치기'}
                            title={attachmentsExpanded ? '첨부 파일 접기' : '첨부 파일 펼치기'}
                            onClick={() => {
                              setAttachmentsExpanded((current) => !current);
                            }}
                          />
                          <Button
                            icon={<UploadOutlined />}
                            aria-label="파일 추가"
                            title="파일 추가"
                            loading={attachmentUploading}
                            disabled={isDraftLocked}
                            onClick={() => {
                              attachmentInputRef.current?.click();
                            }}
                          />
                        </Space>
                      </Flex>
                      {attachmentsExpanded ? draft.attachments.length ? (
                        <div className="board-page__attachment-grid">
                          {draft.attachments.map((attachment) => (
                            <div key={attachment.id} className="board-page__attachment-card">
                              <Flex justify="space-between" align="start" gap={12}>
                                <Flex vertical gap={6} className="board-page__attachment-copy">
                                  <Space size={8} wrap>
                                    <PaperClipOutlined className="board-page__attachment-icon" />
                                    <Text strong ellipsis={{ tooltip: attachment.name }}>
                                      {attachment.name}
                                    </Text>
                                  </Space>
                                  <Text type="secondary">{formatBytes(attachment.size)}</Text>
                                  <Text type="secondary" className="board-page__attachment-path">
                                    {attachment.path}
                                  </Text>
                                </Flex>
                                <Space size={6}>
                                  <Button
                                    size="small"
                                    icon={<LinkOutlined />}
                                    href={attachment.publicUrl}
                                    target="_blank"
                                    rel="noreferrer"
                                  />
                                  <Button
                                    size="small"
                                    icon={<CloseOutlined />}
                                    disabled={isDraftLocked}
                                    onClick={() => {
                                      handleRemoveAttachment(attachment.id);
                                    }}
                                  />
                                </Space>
                              </Flex>
                            </div>
                          ))}
                        </div>
                      ) : (
                        <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="첨부 파일이 없습니다." />
                      ) : null}
                    </div>
                  </div>

                  <div className="board-page__markdown-field">
                    <Text strong className={`board-page__field-label${isPaneMaximized ? ' board-page__field-label--hidden' : ''}`}>
                      공통 메모
                    </Text>
                    <div className="board-page__markdown-editor">
                      <div className="board-page__editor-toolbar">
                        {isMobileViewport ? (
                          <Segmented
                            className="board-page__mobile-toggle"
                            options={[
                              { label: '편집', value: 'edit', icon: <FileTextOutlined /> },
                              { label: '미리보기', value: 'preview', icon: <EyeOutlined /> },
                            ]}
                            value={mobileView}
                            onChange={(value) => {
                              setMobileView(value as 'edit' | 'preview');
                              setMaximizedPane('none');
                            }}
                          />
                        ) : (
                          <Space size={8} wrap className="board-page__desktop-toolbar">
                            <Button
                              type={maximizedPane === 'edit' ? 'primary' : 'default'}
                              icon={maximizedPane === 'edit' ? <ShrinkOutlined /> : <ArrowsAltOutlined />}
                              aria-label={maximizedPane === 'edit' ? '편집 축소' : '편집 최대화'}
                              title={maximizedPane === 'edit' ? '편집 축소' : '편집 최대화'}
                              onClick={() => {
                                setMaximizedPane((current) => (current === 'edit' ? 'none' : 'edit'));
                              }}
                            />
                            <Button
                              type={maximizedPane === 'preview' ? 'primary' : 'default'}
                              icon={maximizedPane === 'preview' ? <ShrinkOutlined /> : <ArrowsAltOutlined />}
                              aria-label={maximizedPane === 'preview' ? '미리보기 축소' : '미리보기 최대화'}
                              title={maximizedPane === 'preview' ? '미리보기 축소' : '미리보기 최대화'}
                              onClick={() => {
                                setMaximizedPane((current) => (current === 'preview' ? 'none' : 'preview'));
                              }}
                            />
                          </Space>
                        )}
                      </div>
                      <div
                        className={`board-page__preview-grid${
                          isPaneMaximized ? ' board-page__preview-grid--maximized' : ''
                        }`}
                      >
                        <div
                          className={`board-page__pane${
                            mobileView === 'preview' ? ' board-page__pane--mobile-hidden' : ''
                          }${maximizedPane === 'preview' ? ' board-page__pane--desktop-hidden' : ''}`}
                        >
                          <div className="board-page__pane-header">
                            <Text type="secondary">편집</Text>
                            <Space size={8}>
                              <Button
                                size="small"
                                icon={<CopyOutlined />}
                                aria-label="공통 메모 복사"
                                title="공통 메모 복사"
                                onClick={() => {
                                  void handleCopyContent();
                                }}
                              />
                              <Button
                                size="small"
                                type={maximizedPane === 'edit' ? 'primary' : 'default'}
                                icon={maximizedPane === 'edit' ? <ShrinkOutlined /> : <ArrowsAltOutlined />}
                                aria-label={maximizedPane === 'edit' ? '편집 축소' : '편집 최대화'}
                                title={maximizedPane === 'edit' ? '편집 축소' : '편집 최대화'}
                                onClick={() => {
                                  setMaximizedPane((current) => (current === 'edit' ? 'none' : 'edit'));
                                }}
                              />
                            </Space>
                          </div>
                          <TextArea
                            value={draft.content}
                            placeholder={'이 작업요청 전체에 공통으로 적용되는 메모를 적어 주세요.\n\n예: 선행 조건, 공통 참고 링크, 첨부 설명'}
                            readOnly={isDraftLocked}
                            onChange={(event) => {
                              setDraft((previous) => ({
                                ...previous,
                                content: event.target.value,
                              }));
                            }}
                            className="board-page__textarea"
                          />
                        </div>
                        <div
                          className={`board-page__pane${
                            mobileView === 'edit' ? ' board-page__pane--mobile-hidden' : ''
                          }${maximizedPane === 'edit' ? ' board-page__pane--desktop-hidden' : ''}`}
                        >
                          <div className="board-page__preview">
                            <div className="board-page__pane-header">
                              <Text type="secondary">미리보기</Text>
                              <Space size={8}>
                                <Button
                                  size="small"
                                  icon={<CopyOutlined />}
                                  aria-label="공통 메모 복사"
                                  title="공통 메모 복사"
                                  onClick={() => {
                                    void handleCopyContent();
                                  }}
                                />
                                <Button
                                  size="small"
                                  type={maximizedPane === 'preview' ? 'primary' : 'default'}
                                  icon={maximizedPane === 'preview' ? <ShrinkOutlined /> : <ArrowsAltOutlined />}
                                  aria-label={maximizedPane === 'preview' ? '미리보기 축소' : '미리보기 최대화'}
                                  title={maximizedPane === 'preview' ? '미리보기 축소' : '미리보기 최대화'}
                                  onClick={() => {
                                    setMaximizedPane((current) => (current === 'preview' ? 'none' : 'preview'));
                                  }}
                                />
                              </Space>
                            </div>
                            <div className="board-page__preview-content">
                              {draft.content.trim() ? (
                                <MarkdownPreviewContent content={draft.content} />
                              ) : (
                                <Empty description="미리보기할 공통 메모가 없습니다." image={Empty.PRESENTED_IMAGE_SIMPLE} />
                              )}
                            </div>
                          </div>
                        </div>
                      </div>
                      {isDraftLocked ? (
                        <Text type="secondary">자동화 접수된 작업메모는 게시판에서 수정하거나 삭제할 수 없습니다.</Text>
                      ) : null}
                    </div>
                  </div>
                </div>
                {isPaneMaximized ? (
                  <div className="board-page__floating-toolbar">
                    <Space size={8}>
                      <Button
                        size="small"
                        icon={<CopyOutlined />}
                        aria-label="공통 메모 복사"
                        title="공통 메모 복사"
                        onClick={() => {
                          void handleCopyContent();
                        }}
                      />
                      <Button
                        size="small"
                        icon={<ShrinkOutlined />}
                        aria-label="편집 보기로 복귀"
                        title="편집 보기로 복귀"
                        onClick={() => {
                          setMaximizedPane('none');
                        }}
                      />
                    </Space>
                  </div>
                ) : null}
              </div>
            </Card>
          </div>
        )}
      </Space>
    </div>
  );
}
