import { useState, useEffect, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient, getApiErrorMessage } from '../lib/apiClient';
import { onDualMonitorEvent, getPersistedTaskId, getPersistedStageId } from '../lib/dualMonitor';
import { ContextMenu } from '../components/common/ContextMenu';
import { FeedbackModal, type FeedbackFormData } from '../components/detail/FeedbackModal';
import { OverviewEditModal, type OverviewFormData } from '../components/detail/OverviewEditModal';
import { ResultPreview } from '../components/detail/ResultPreview';
import {
  StageModal,
  parseMilestoneLinks,
  type StageFileSavePayload,
  type StageFormData,
} from '../components/detail/StageModal';
import { sortFilesByOrder } from '../lib/fileDisplay';
import { useAuth } from '../contexts/AuthContext';
import { formatSectionDisplay } from '../lib/sections';
import { getTaskPeopleHeaderParts } from '../lib/teamStatus';
import { isRoutineTask, isProjectTask } from '../lib/taskType';
import { RoutineDetailShell } from '../components/detail/RoutineDetailView';
import { MilestoneTimeline, type TimelineViewMode } from '../components/detail/MilestoneTimeline';
import { MilestoneContentList } from '../components/detail/MilestoneContentList';
import {
  taskTimelineFallback,
  sortMilestonesForTimeline,
} from '../lib/milestoneTimeline';
import { resolveTaskNearestMilestoneId } from '../lib/milestoneWeekFocus';
import { fmtMilestonePeriodSummary, serializePeriodEntries } from '../lib/milestonePeriods';
import type { Task, Milestone, FileRecord, TaskDetail } from '../types';
import { useSocket } from '../contexts/SocketContext';
import { invalidateTaskCaches } from '../lib/taskQueryCache';

const STATUS_CONFIG: Record<string, { label: string }> = {
  IN_PROGRESS: { label: '진행중' },
  REVIEW:      { label: '보류' },
  TODO:        { label: '대기' },
  DONE:        { label: '완료' },
  CANCELLED:   { label: '취소' },
};

type TaskWithRelations = Task & {
  files: FileRecord[];
  details: TaskDetail[];
  milestones: Milestone[];
};

function fmtDate(iso: string | null | undefined) {
  if (!iso) return '';
  const d = new Date(iso);
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}

function fmtShort(iso: string | null | undefined) {
  if (!iso) return '';
  const d = new Date(iso);
  return `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
}

function fmtStageRange(m: Milestone) {
  const summary = fmtMilestonePeriodSummary(m);
  if (summary) return summary;
  return fmtShort(m.createdAt);
}

function sortByIsoDesc<T>(items: T[], pick: (item: T) => string) {
  return [...items].sort((a, b) => new Date(pick(b)).getTime() - new Date(pick(a)).getTime());
}

/** 시작일이 빠른 단계가 상단 (시작일 없으면 종료일 → 생성일 순) */
function sortStagesByStartAsc(stages: Milestone[]) {
  return sortMilestonesForTimeline(stages);
}

function defaultStageId(milestones: Milestone[]): string | null {
  if (milestones.length === 0) return null;
  const sorted = sortStagesByStartAsc(milestones);
  const nearest = resolveTaskNearestMilestoneId({ milestones }, new Date());
  if (nearest && sorted.some((m) => m.id === nearest)) return nearest;
  return sorted[0]?.id ?? null;
}

function feedbackAuthorName(detail: TaskDetail) {
  return detail.authorName?.trim() || detail.author?.name || '—';
}

function milestoneProgress(m: Milestone) {
  if (m.completedAt) return 100;
  const p = m.progress ?? 0;
  return Math.min(100, Math.max(0, p));
}

function OverviewMoreIcon({ expanded }: { expanded: boolean }) {
  return (
    <svg
      width="12"
      height="12"
      viewBox="0 0 12 12"
      aria-hidden
      className={`detail-overview__chevrons${expanded ? ' is-up' : ''}`}
    >
      <path
        d="M2 4.5 L6 7.5 L10 4.5"
        fill="none"
        stroke="currentColor"
        strokeWidth="1.4"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M2 7 L6 10 L10 7"
        fill="none"
        stroke="currentColor"
        strokeWidth="1.4"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

function PanelLabel({ children, sub }: { children: React.ReactNode; sub?: string }) {
  return (
    <div className="detail-section-head">
      <h3 className="detail-section-label">{children}</h3>
      {sub && <span className="detail-section-label-sub truncate">{sub}</span>}
    </div>
  );
}

function LeftSection({
  children,
  onContextMenu,
}: {
  children: React.ReactNode;
  onContextMenu?: (e: React.MouseEvent) => void;
}) {
  return (
    <section
      className="flex min-h-0 flex-col overflow-hidden border-b border-[#e8edf2] px-4 py-3 last:border-b-0"
      onContextMenu={onContextMenu}
    >
      {children}
    </section>
  );
}

function WaitingScreen() {
  return (
    <div className="flex h-full flex-col items-center justify-center gap-6">
      <div className="flex h-20 w-20 animate-pulse items-center justify-center rounded-full bg-[#eef5fc] text-4xl">←</div>
      <div className="text-center">
        <p className="detail-body-title">카드를 선택하세요</p>
        <p className="detail-body-muted mt-2">
          대시보드에서 업무 카드를 클릭하면<br />이곳에 상세 내용이 표시됩니다.
        </p>
      </div>
    </div>
  );
}

function DetailHeader({ task }: { task: Task }) {
  const status = STATUS_CONFIG[task.status] ?? STATUS_CONFIG.TODO;
  const period =
    task.startDate || task.dueDate
      ? `${fmtDate(task.startDate)} ~ ${fmtDate(task.dueDate)}`
      : '—';
  const { pmName, assigneeNames } = getTaskPeopleHeaderParts(task);
  const hasPeople = !!pmName || assigneeNames.length > 0;

  return (
    <header className="detail-page-header">
      <h1 className="detail-page-header__title">{task.title}</h1>
      <div className="detail-page-header__meta">
        {pmName && (
          <span>
            <span className="detail-page-header__meta-label">PM </span>
            <span className="detail-page-header__meta-value">{pmName}</span>
          </span>
        )}
        {assigneeNames.length > 0 && (
          <span>
            <span className="detail-page-header__meta-label">담당 </span>
            <span className="detail-page-header__meta-value">{assigneeNames.join(' · ')}</span>
          </span>
        )}
        {!hasPeople && (
          <span>
            <span className="detail-page-header__meta-label">담당 </span>
            <span className="detail-page-header__meta-value">{task.assignee?.name ?? '—'}</span>
          </span>
        )}
        <span className="detail-page-header__divider" aria-hidden="true" />
        <span>
          <span className="detail-page-header__meta-label">수행기간 </span>
          <span className="detail-page-header__meta-value">{period}</span>
        </span>
        <span className="detail-page-header__divider" aria-hidden="true" />
        <span>
          <span className="detail-page-header__meta-label">부문 </span>
          <span className="detail-page-header__meta-value">{formatSectionDisplay(task.section)}</span>
        </span>
        <span className="detail-page-header__badge">{status.label}</span>
      </div>
    </header>
  );
}

function DetailView({
  task,
  initialStageId,
}: {
  task: TaskWithRelations;
  initialStageId?: string | null;
}) {
  const qc = useQueryClient();
  const { user } = useAuth();
  const [stageSaving, setStageSaving] = useState(false);
  const [feedbackSaving, setFeedbackSaving] = useState(false);
  const [stageModal, setStageModal] = useState<{ mode: 'add' | 'edit'; milestone?: Milestone } | null>(null);
  const [feedbackModal, setFeedbackModal] = useState<{ mode: 'add' | 'edit'; detail?: TaskDetail } | null>(null);
  const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number; stageId: string } | null>(null);
  const [contentCtx, setContentCtx] = useState<{ x: number; y: number } | null>(null);
  const [feedbackCtx, setFeedbackCtx] = useState<{ x: number; y: number; detailId?: string } | null>(null);
  const [overviewCtx, setOverviewCtx] = useState<{ x: number; y: number } | null>(null);
  const [overviewModalOpen, setOverviewModalOpen] = useState(false);
  const [overviewSaving, setOverviewSaving] = useState(false);
  const [overviewExpanded, setOverviewExpanded] = useState(false);
  const [timelineView, setTimelineView] = useState<TimelineViewMode>('focus');

  const milestones = task.milestones ?? [];
  const files = task.files ?? [];
  const details = task.details ?? [];

  const sortedStages = useMemo(
    () => sortStagesByStartAsc(milestones),
    [milestones],
  );

  const { data: quarterTasks = [] } = useQuery({
    queryKey: ['tasks', { quarter: task.quarter }],
    queryFn: async () => {
      const { data } = await apiClient.get<Task[]>('/tasks', { params: { quarter: task.quarter } });
      return data;
    },
    enabled: timelineView === 'portfolio',
    staleTime: 30_000,
  });

  const quarterProjectTasks = useMemo(
    () => quarterTasks.filter((t) => isProjectTask(t.taskType)),
    [quarterTasks],
  );

  const portfolioTasks = useMemo(
    () =>
      quarterProjectTasks.map((t) => ({
        id: t.id,
        title: t.title,
        milestones: sortMilestonesForTimeline((t.milestones ?? []) as Milestone[]),
      })),
    [quarterProjectTasks],
  );

  const showGanttPanel = timelineView !== 'focus';
  const showPortfolioFull = timelineView === 'portfolio';

  const [selectedId, setSelectedId] = useState<string | null>(() => {
    if (initialStageId && milestones.some((m) => m.id === initialStageId)) return initialStageId;
    return defaultStageId(milestones);
  });

  useEffect(() => {
    if (initialStageId && sortedStages.some((s) => s.id === initialStageId)) {
      setSelectedId(initialStageId);
      return;
    }
    setSelectedId(defaultStageId(sortedStages));
  }, [task.id, initialStageId, sortedStages]);

  useEffect(() => {
    setOverviewExpanded(false);
    setTimelineView('focus');
  }, [task.id]);

  const selected = sortedStages.find((m) => m.id === selectedId) ?? sortedStages[0] ?? null;

  const stageDetails = useMemo(
    () => (selectedId ? details.filter((d) => d.milestoneId === selectedId) : []),
    [details, selectedId],
  );

  const sortedFeedbacks = useMemo(
    () => sortByIsoDesc(stageDetails, (d) => d.createdAt),
    [stageDetails],
  );

  const stageFiles = useMemo(
    () =>
      sortFilesByOrder(
        selectedId ? files.filter((f) => f.milestoneId === selectedId) : [],
      ),
    [files, selectedId],
  );

  const stageLinks = useMemo(
    () => (selected ? parseMilestoneLinks(selected.links) : []),
    [selected],
  );

  const deleteMs = useMutation({
    mutationFn: (id: string) => apiClient.delete(`/milestones/item/${id}`),
    onSuccess: () => invalidateTaskCaches(qc, task.id),
  });

  const uploadFiles = async (milestoneId: string, filePayload: StageFileSavePayload['uploads']) => {
    for (const item of filePayload) {
      const form = new FormData();
      form.append('file', item.file);
      form.append('milestoneId', milestoneId);
      form.append('sortOrder', String(item.sortOrder));
      if (item.displayName.trim()) {
        form.append('displayName', item.displayName.trim());
      }
      await apiClient.post(`/files/upload/${task.id}`, form);
    }
  };

  const handleStageSave = async (data: StageFormData, filePayload: StageFileSavePayload) => {
    setStageSaving(true);
    try {
      const payload = {
        title: data.title.trim(),
        periodEntries: serializePeriodEntries(data.periodEntries),
        progress: data.progress,
        links: data.links,
      };

      let milestoneId: string;

      if (stageModal?.mode === 'add') {
        const { data: created } = await apiClient.post<Milestone>(`/milestones/${task.id}`, payload);
        milestoneId = created.id;
        setSelectedId(created.id);
      } else if (stageModal?.milestone) {
        const { data: updated } = await apiClient.patch<Milestone>(
          `/milestones/item/${stageModal.milestone.id}`,
          payload,
        );
        milestoneId = updated.id;
        setSelectedId(updated.id);
      } else {
        return;
      }

      try {
        for (const id of filePayload.deletedFileIds) {
          await apiClient.delete(`/files/${id}`);
        }
        for (const rep of filePayload.replacements) {
          const form = new FormData();
          form.append('file', rep.file);
          await apiClient.post(`/files/${rep.id}/replace`, form);
        }
        for (const edit of filePayload.existingEdits) {
          const original = files.find((f) => f.id === edit.id);
          if (!original) continue;
          const prevName = (original.displayName ?? '').trim();
          const nextName = edit.displayName.trim();
          const prevOrder = original.sortOrder ?? 0;
          if (nextName !== prevName || edit.sortOrder !== prevOrder) {
            await apiClient.patch(`/files/${edit.id}`, {
              displayName: nextName || null,
              sortOrder: edit.sortOrder,
            });
          }
        }
        if (filePayload.uploads.length > 0) {
          await uploadFiles(milestoneId, filePayload.uploads);
        }
      } catch (err: unknown) {
        alert(`단계는 저장됐지만 ${getApiErrorMessage(err, '파일 처리에 실패했습니다.')}`);
      }

      await invalidateTaskCaches(qc, task.id);
      setStageModal(null);
    } catch (err: unknown) {
      alert(getApiErrorMessage(err, '단계 저장에 실패했습니다.'));
    } finally {
      setStageSaving(false);
    }
  };

  const handleFeedbackSave = async (data: FeedbackFormData) => {
    if (!selectedId && feedbackModal?.mode === 'add') {
      alert('피드백을 추가할 업무 일정을 먼저 선택하세요.');
      return;
    }

    setFeedbackSaving(true);
    try {
      if (feedbackModal?.mode === 'add') {
        await apiClient.post(`/details/${task.id}`, {
          content: data.content.trim(),
          authorName: data.authorName.trim(),
          milestoneId: selectedId,
        });
      } else if (feedbackModal?.detail) {
        await apiClient.patch(`/details/item/${feedbackModal.detail.id}`, {
          content: data.content.trim(),
          authorName: data.authorName.trim() || null,
        });
      }
      await invalidateTaskCaches(qc, task.id);
      setFeedbackModal(null);
    } catch (err: unknown) {
      alert(getApiErrorMessage(err, '피드백 저장에 실패했습니다.'));
    } finally {
      setFeedbackSaving(false);
    }
  };

  const deleteFeedback = useMutation({
    mutationFn: (id: string) => apiClient.delete(`/details/item/${id}`),
    onSuccess: () => invalidateTaskCaches(qc, task.id),
  });

  const overviewRaw = task.description?.trim() ?? '';
  const overviewDisplay = overviewRaw || '등록된 개요가 없습니다.';
  const detailRaw = task.detailDescription?.trim() ?? '';
  const hasDetailContent = detailRaw.length > 0;

  const handleOverviewSave = async (data: OverviewFormData) => {
    setOverviewSaving(true);
    try {
      await apiClient.patch(`/tasks/${task.id}`, {
        description: data.description.trim() || null,
        detailDescription: data.detailDescription.trim() || null,
      });
      await invalidateTaskCaches(qc, task.id);
      setOverviewModalOpen(false);
    } catch (err: unknown) {
      alert(getApiErrorMessage(err, '개요 저장에 실패했습니다.'));
    } finally {
      setOverviewSaving(false);
    }
  };

  return (
    <div
      className={`grid h-full min-h-0 grid-rows-1 ${showPortfolioFull ? 'detail-page-grid--portfolio' : 'grid-cols-[1fr_3fr]'}`}
    >
      {!showPortfolioFull && (
      <aside className="detail-aside detail-aside--project grid h-full min-h-0 overflow-hidden">
        <LeftSection
          onContextMenu={(e) => {
            e.preventDefault();
            setOverviewCtx({ x: e.clientX, y: e.clientY });
          }}
        >
          <PanelLabel>개요</PanelLabel>
          <div className={`detail-overview${hasDetailContent ? ' detail-overview--has-detail' : ''}`}>
            <p
              className={`detail-body-text detail-overview__text${hasDetailContent ? ' is-clamped' : ''}`}
            >
              {overviewDisplay}
            </p>
            {hasDetailContent && (
              <button
                type="button"
                className="detail-overview__more"
                aria-expanded={overviewExpanded}
                aria-label={overviewExpanded ? '상세내용 접기' : '상세내용 펼치기'}
                title={overviewExpanded ? '상세내용 접기' : '상세내용 펼치기'}
                onClick={() => setOverviewExpanded((v) => !v)}
              >
                <OverviewMoreIcon expanded={overviewExpanded} />
              </button>
            )}
          </div>
          {hasDetailContent && (
            <div
              className={`detail-overview-detail${overviewExpanded ? ' is-expanded' : ''}`}
              aria-hidden={!overviewExpanded}
            >
              <p className="detail-body-text detail-overview-detail__text">{detailRaw}</p>
            </div>
          )}
        </LeftSection>

        <LeftSection>
          <div className="detail-section-head">
            <h3 className="detail-section-label">업무 일정</h3>
            <button
              type="button"
              title="업무 일정 추가"
              onClick={() => setStageModal({ mode: 'add' })}
              className="detail-add-btn"
            >
              +
            </button>
          </div>
          <div className="detail-stage-list flex flex-col gap-2 pr-1">
            {sortedStages.length === 0 && (
              <p className="detail-body-muted">+ 버튼으로 단계를 추가하세요.</p>
            )}
            {sortedStages.map((stage) => {
              const isSelected = stage.id === selectedId;
              const progress = milestoneProgress(stage);
              return (
                <button
                  key={stage.id}
                  type="button"
                  onClick={() => setSelectedId(stage.id)}
                  onContextMenu={(e) => {
                    e.preventDefault();
                    setSelectedId(stage.id);
                    setCtxMenu({ x: e.clientX, y: e.clientY, stageId: stage.id });
                  }}
                  className={`detail-stage-card shrink-0 rounded-lg border px-3 py-2 text-left transition-colors ${
                    isSelected ? 'is-selected ring-1 ring-[#7eb3e8]' : 'hover:border-slate-300 hover:bg-white/80'
                  }`}
                >
                  <div className="flex items-start justify-between gap-2">
                    <div className="min-w-0 flex-1">
                      <p className="detail-body-title truncate">{stage.title}</p>
                      <p className="detail-body-caption mt-0.5">{fmtStageRange(stage)}</p>
                    </div>
                    <span
                      className={`shrink-0 text-[20px] font-bold tracking-[-0.2px] ${
                        progress >= 100 ? 'text-[#2563ab]' : progress > 0 ? 'text-[#4a90d9]' : 'text-slate-300'
                      }`}
                    >
                      {progress}%
                    </span>
                  </div>
                </button>
              );
            })}
          </div>
        </LeftSection>

        <LeftSection>
          <MilestoneContentList
            milestone={selected}
            emptyMessage={selected ? '수행 기간에 업무내용을 입력하세요.' : '단계를 선택하세요.'}
            onContextMenu={(e) => {
              if (!selected) return;
              e.preventDefault();
              setContentCtx({ x: e.clientX, y: e.clientY });
            }}
          />
        </LeftSection>

        <LeftSection>
          <div className="detail-section-head">
            <h3 className="detail-section-label">피드백</h3>
            <button
              type="button"
              title="피드백 추가"
              disabled={!selectedId}
              onClick={() => setFeedbackModal({ mode: 'add' })}
              className="detail-add-btn"
            >
              +
            </button>
          </div>
          <div className="flex min-h-0 flex-1 flex-col overflow-y-auto pr-1">
            {sortedFeedbacks.length === 0 ? (
              <p className="detail-body-muted">+ 버튼으로 피드백을 추가하세요.</p>
            ) : (
              <div className="mb-2 min-h-0 flex-1 space-y-2">
                {sortedFeedbacks.map((f) => (
                  <div
                    key={f.id}
                    className="rounded-lg bg-white/50 px-3 py-2"
                    onContextMenu={(e) => {
                      e.preventDefault();
                      e.stopPropagation();
                      setFeedbackCtx({ x: e.clientX, y: e.clientY, detailId: f.id });
                    }}
                  >
                    <p className="detail-body-content truncate">
                      {f.content}
                      <span className="detail-body-caption font-semibold"> — {feedbackAuthorName(f)}</span>
                    </p>
                  </div>
                ))}
              </div>
            )}
          </div>
        </LeftSection>
      </aside>
      )}

      {/* 우 3/4 */}
      <div
        className={`detail-main-panel flex h-full min-h-0 min-w-0 flex-col${showGanttPanel ? ' detail-panel--gantt' : ''}${showPortfolioFull ? ' detail-panel--portfolio' : ''}`}
      >
        {timelineView === 'focus' && (
          <ResultPreview
            files={stageFiles}
            links={stageLinks}
            hasSelectedStage={!!selectedId}
          />
        )}

        <MilestoneTimeline
          variant="project"
          milestones={sortedStages}
          portfolioTasks={portfolioTasks}
          fallback={taskTimelineFallback(task)}
          selectedId={selectedId}
          onSelect={setSelectedId}
          ownerTaskId={task.id}
          viewMode={timelineView}
          onViewModeChange={setTimelineView}
          emptyMessage="기간을 설정한 업무 일정만 타임라인에 표시됩니다."
        />
      </div>

      {stageModal && (
        <StageModal
          mode={stageModal.mode}
          milestone={stageModal.milestone}
          existingFiles={
            stageModal.milestone
              ? sortFilesByOrder(files.filter((f) => f.milestoneId === stageModal.milestone!.id))
              : []
          }
          saving={stageSaving}
          onClose={() => setStageModal(null)}
          onSave={handleStageSave}
        />
      )}

      {feedbackModal && (
        <FeedbackModal
          mode={feedbackModal.mode}
          detail={feedbackModal.detail}
          defaultAuthorName={user?.name ?? ''}
          saving={feedbackSaving}
          onClose={() => setFeedbackModal(null)}
          onSave={handleFeedbackSave}
        />
      )}

      {ctxMenu && (
        <ContextMenu
          x={ctxMenu.x}
          y={ctxMenu.y}
          onClose={() => setCtxMenu(null)}
          items={[
            {
              label: '수정',
              icon: '✏️',
              onClick: () => {
                const ms = milestones.find((m) => m.id === ctxMenu.stageId);
                if (ms) {
                  setSelectedId(ms.id);
                  setStageModal({ mode: 'edit', milestone: ms });
                }
              },
            },
            {
              label: '삭제',
              icon: '🗑',
              danger: true,
              onClick: () => {
                if (window.confirm('이 단계를 삭제하시겠습니까?')) {
                  deleteMs.mutate(ctxMenu.stageId);
                  if (selectedId === ctxMenu.stageId) setSelectedId(null);
                }
              },
            },
          ]}
        />
      )}

      {contentCtx && selected && (
        <ContextMenu
          x={contentCtx.x}
          y={contentCtx.y}
          onClose={() => setContentCtx(null)}
          items={[
            {
              label: '수정',
              icon: '✏️',
              onClick: () => setStageModal({ mode: 'edit', milestone: selected }),
            },
          ]}
        />
      )}

      {overviewCtx && (
        <ContextMenu
          x={overviewCtx.x}
          y={overviewCtx.y}
          onClose={() => setOverviewCtx(null)}
          items={[
            {
              label: '수정',
              icon: '✏️',
              onClick: () => setOverviewModalOpen(true),
            },
          ]}
        />
      )}

      {overviewModalOpen && (
        <OverviewEditModal
          initial={{
            description: task.description ?? '',
            detailDescription: task.detailDescription ?? '',
          }}
          saving={overviewSaving}
          onClose={() => setOverviewModalOpen(false)}
          onSave={handleOverviewSave}
        />
      )}

      {feedbackCtx?.detailId && (
        <ContextMenu
          x={feedbackCtx.x}
          y={feedbackCtx.y}
          onClose={() => setFeedbackCtx(null)}
          items={[
            {
              label: '수정',
              icon: '✏️',
              onClick: () => {
                const d = details.find((item) => item.id === feedbackCtx.detailId);
                if (d) setFeedbackModal({ mode: 'edit', detail: d });
              },
            },
            {
              label: '삭제',
              icon: '🗑',
              danger: true,
              onClick: () => {
                if (window.confirm('이 피드백을 삭제하시겠습니까?')) {
                  deleteFeedback.mutate(feedbackCtx.detailId!);
                }
              },
            },
          ]}
        />
      )}
    </div>
  );
}

export function TaskDetailShell({
  taskId,
  initialStageId,
}: {
  taskId: string | null;
  initialStageId?: string | null;
}) {
  const qc = useQueryClient();
  const socket = useSocket();

  const { data: task, isLoading, isError, error } = useQuery({
    queryKey: ['task', taskId],
    queryFn: async () => {
      const { data } = await apiClient.get<TaskWithRelations>(`/tasks/${taskId}`);
      return data;
    },
    enabled: !!taskId,
    staleTime: 10_000,
    retry: 2,
  });

  useEffect(() => {
    if (!socket || !taskId) return;
    socket.emit('join:task', taskId);
    const refresh = () => invalidateTaskCaches(qc, taskId);
    socket.on('task:updated', refresh);
    return () => {
      socket.emit('leave:task', taskId);
      socket.off('task:updated', refresh);
    };
  }, [socket, taskId, qc]);

  return (
    <div className="h-full min-h-0 w-full overflow-hidden">
      {isLoading ? (
        <div className="detail-body-muted flex h-full items-center justify-center">불러오는 중...</div>
      ) : isError ? (
        <div className="flex h-full flex-col items-center justify-center gap-3 text-center">
          <p className="detail-body-title text-red-500">상세 정보를 불러오지 못했습니다.</p>
          <p className="detail-body-muted">{getApiErrorMessage(error, '서버 연결을 확인해 주세요.')}</p>
        </div>
      ) : !task ? (
        <WaitingScreen />
      ) : isRoutineTask(task.taskType) ? (
        <RoutineDetailShell task={task} initialStageId={initialStageId} />
      ) : (
        <div className="detail-page-shell flex h-full min-h-0 flex-col overflow-hidden">
          <DetailHeader task={task} />
          <div className="h-full min-h-0 flex-1">
            <DetailView task={task} initialStageId={initialStageId} />
          </div>
        </div>
      )}
    </div>
  );
}

export default function DetailPage() {
  const { taskId: routeTaskId } = useParams<{ taskId?: string }>();
  const isPopupView =
    typeof window !== 'undefined' &&
    (window.location.search.includes('view=detail') || window.name === 'eene_detail');
  const [taskId, setTaskId] = useState<string | null>(
    () => routeTaskId ?? getPersistedTaskId(),
  );
  const [stageId, setStageId] = useState<string | null>(() => getPersistedStageId());

  useEffect(() => {
    if (routeTaskId) setTaskId(routeTaskId);
  }, [routeTaskId]);

  useEffect(() => {
    const unsub = onDualMonitorEvent(
      (evt) => {
        if (evt.type === 'TASK_SELECTED') {
          setTaskId(evt.taskId);
          setStageId(evt.stageId ?? null);
        }
        if (evt.type === 'TASK_DESELECTED') {
          setTaskId(null);
          setStageId(null);
        }
      },
      { isPopupView },
    );
    return unsub;
  }, [isPopupView]);

  if (isPopupView) {
    return (
      <div className="detail-popup-view flex h-screen w-screen overflow-hidden">
        <aside className="app-right detail-open flex h-full min-h-0 w-full flex-col overflow-hidden">
          <TaskDetailShell taskId={taskId} initialStageId={stageId} />
        </aside>
      </div>
    );
  }

  return <TaskDetailShell taskId={taskId} initialStageId={stageId} />;
}
