import { useState, useRef, useMemo, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { sortFilesByOrder } from '../../lib/fileDisplay';
import { decodeRoutineStageDescription } from '../../lib/routineMilestone';
import {
  newPeriodEntry,
  parseMilestonePeriods,
  type MilestonePeriodEntry,
} from '../../lib/milestonePeriods';
import type { FileRecord, Milestone, MilestoneLink, TeamMember } from '../../types';

export interface StageFormData {
  title: string;
  subtitle: string;
  periodEntries: MilestonePeriodEntry[];
  progress: number;
  links: MilestoneLink[];
  pmMemberId: string;
  assigneeMemberIds: string[];
}

export interface PendingFileUpload {
  key: string;
  file: File;
  displayName: string;
  sortOrder: number;
}

export interface ExistingFileEdit {
  id: string;
  displayName: string;
  sortOrder: number;
}

export interface FileReplacement {
  id: string;
  file: File;
}

export interface StageFileSavePayload {
  uploads: PendingFileUpload[];
  existingEdits: ExistingFileEdit[];
  deletedFileIds: string[];
  replacements: FileReplacement[];
}

interface StageModalProps {
  mode: 'add' | 'edit';
  variant?: 'project' | 'routine';
  milestone?: Milestone;
  existingFiles?: FileRecord[];
  teamMembers?: TeamMember[];
  onSave: (data: StageFormData, files: StageFileSavePayload) => Promise<void>;
  onClose: () => void;
  saving?: boolean;
}

type FileRow =
  | { kind: 'existing'; id: string; displayName: string; fileName: string; sortOrder: number }
  | { kind: 'pending'; key: string; displayName: string; fileName: string; sortOrder: number; file: File };

type EditTarget =
  | { type: 'file'; key: string }
  | { type: 'link'; index: number }
  | null;

function parseLinks(raw: string | null | undefined): MilestoneLink[] {
  if (!raw) return [];
  try {
    const parsed = JSON.parse(raw) as MilestoneLink[];
    return Array.isArray(parsed) ? parsed.filter((l) => l.url) : [];
  } catch {
    return [];
  }
}

function reindexSortOrders<T extends { sortOrder: number }>(items: T[]): T[] {
  return items.map((item, i) => ({ ...item, sortOrder: i }));
}

function ListActions({
  onEdit,
  onDelete,
}: {
  onEdit: () => void;
  onDelete: () => void;
}) {
  return (
    <div className="flex shrink-0 items-center gap-1">
      <button
        type="button"
        title="편집"
        onClick={onEdit}
        className="rounded px-2 py-0.5 text-xs font-bold text-slate-500 hover:bg-slate-100"
      >
        ✏️
      </button>
      <button
        type="button"
        title="삭제"
        onClick={onDelete}
        className="rounded px-2 py-0.5 text-xs font-bold text-red-400 hover:bg-red-50 hover:text-red-600"
      >
        🗑
      </button>
    </div>
  );
}

let pendingKeySeq = 0;

export function StageModal({
  mode,
  variant = 'project',
  milestone,
  existingFiles = [],
  teamMembers = [],
  onSave,
  onClose,
  saving,
}: StageModalProps) {
  const isRoutine = variant === 'routine';
  const sortedExisting = useMemo(() => sortFilesByOrder(existingFiles), [existingFiles]);

  const [form, setForm] = useState<StageFormData>(() => {
    const legacyOverview = isRoutine
      ? decodeRoutineStageDescription(milestone?.description).overview
      : '';
    return {
      title: milestone?.title ?? '',
      subtitle: milestone?.subtitle?.trim() ?? legacyOverview,
      periodEntries: parseMilestonePeriods(milestone),
      progress: milestone?.progress ?? 0,
      links: parseLinks(milestone?.links),
      pmMemberId: milestone?.pmMemberId ?? milestone?.pmMember?.id ?? '',
      assigneeMemberIds: milestone?.assigneeMembers?.map((m) => m.id) ?? [],
    };
  });

  const [fileRows, setFileRows] = useState<FileRow[]>([]);
  const [deletedFileIds, setDeletedFileIds] = useState<string[]>([]);
  const [replacements, setReplacements] = useState<Record<string, File>>({});

  const [fileLabel, setFileLabel] = useState('');
  const [linkLabel, setLinkLabel] = useState('');
  const [linkUrl, setLinkUrl] = useState('');
  const [editTarget, setEditTarget] = useState<EditTarget>(null);
  const [editDisplayName, setEditDisplayName] = useState('');
  const [editOrder, setEditOrder] = useState(1);
  const [editLinkLabel, setEditLinkLabel] = useState('');
  const [editLinkUrl, setEditLinkUrl] = useState('');

  const fileInputRef = useRef<HTMLInputElement>(null);
  const replaceInputRef = useRef<HTMLInputElement>(null);
  const replaceTargetId = useRef<string | null>(null);

  useEffect(() => {
    setFileRows(
      sortedExisting.map((f, i) => ({
        kind: 'existing' as const,
        id: f.id,
        displayName: f.displayName ?? '',
        fileName: f.originalName,
        sortOrder: f.sortOrder ?? i,
      })),
    );
    setDeletedFileIds([]);
    setReplacements({});
  }, [sortedExisting]);

  const orderedFiles = useMemo(
    () => [...fileRows].sort((a, b) => a.sortOrder - b.sortOrder),
    [fileRows],
  );

  const set = <K extends keyof StageFormData>(key: K, value: StageFormData[K]) =>
    setForm((prev) => ({ ...prev, [key]: value }));

  const toggleAssignee = (memberId: string) => {
    setForm((prev) => {
      const has = prev.assigneeMemberIds.includes(memberId);
      return {
        ...prev,
        assigneeMemberIds: has
          ? prev.assigneeMemberIds.filter((id) => id !== memberId)
          : [...prev.assigneeMemberIds, memberId],
      };
    });
  };

  const addPeriodEntry = () => {
    setForm((prev) => ({
      ...prev,
      periodEntries: [...prev.periodEntries, newPeriodEntry()],
    }));
  };

  const updatePeriodEntry = (id: string, patch: Partial<MilestonePeriodEntry>) => {
    setForm((prev) => ({
      ...prev,
      periodEntries: prev.periodEntries.map((entry) =>
        entry.id === id ? { ...entry, ...patch } : entry,
      ),
    }));
  };

  const removePeriodEntry = (id: string) => {
    setForm((prev) => ({
      ...prev,
      periodEntries: prev.periodEntries.filter((entry) => entry.id !== id),
    }));
  };

  const modalTitle = isRoutine
    ? mode === 'add'
      ? '업무 단계 추가'
      : '업무 단계 수정'
    : mode === 'add'
      ? '업무 일정 추가'
      : '업무 일정 수정';

  const clearEdit = () => {
    setEditTarget(null);
    setEditDisplayName('');
    setEditOrder(1);
    setEditLinkLabel('');
    setEditLinkUrl('');
    replaceTargetId.current = null;
  };

  const addFile = (file: File) => {
    const key = `p-${++pendingKeySeq}`;
    setFileRows((prev) =>
      reindexSortOrders([
        ...prev,
        {
          kind: 'pending',
          key,
          file,
          displayName: fileLabel.trim(),
          fileName: file.name,
          sortOrder: prev.length,
        },
      ]),
    );
    setFileLabel('');
  };

  const handleFilePick = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    addFile(file);
    e.target.value = '';
  };

  const handleReplacePick = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    const targetKey = replaceTargetId.current;
    if (!file || !targetKey) return;

    setFileRows((prev) =>
      prev.map((row) => {
        const rowKey = row.kind === 'existing' ? row.id : row.key;
        if (rowKey !== targetKey) return row;
        if (row.kind === 'existing') {
          setReplacements((r) => ({ ...r, [row.id]: file }));
          return { ...row, fileName: file.name };
        }
        return { ...row, file, fileName: file.name };
      }),
    );
    e.target.value = '';
    replaceTargetId.current = null;
  };

  const removeFile = (key: string) => {
    const row = fileRows.find((r) => (r.kind === 'existing' ? r.id : r.key) === key);
    if (row?.kind === 'existing') {
      setDeletedFileIds((prev) => [...prev, row.id]);
    }
    setFileRows((prev) => reindexSortOrders(prev.filter((r) => (r.kind === 'existing' ? r.id : r.key) !== key)));
    if (editTarget?.type === 'file' && editTarget.key === key) clearEdit();
  };

  const startEditFile = (key: string) => {
    const row = orderedFiles.find((r) => (r.kind === 'existing' ? r.id : r.key) === key);
    if (!row) return;
    const order = orderedFiles.findIndex((r) => (r.kind === 'existing' ? r.id : r.key) === key) + 1;
    setEditTarget({ type: 'file', key });
    setEditDisplayName(row.displayName);
    setEditOrder(order);
  };

  const applyFileEdit = () => {
    if (editTarget?.type !== 'file') return;
    const key = editTarget.key;
    const newOrder = Math.min(Math.max(1, editOrder), orderedFiles.length);

    setFileRows((prev) => {
      const sorted = [...prev].sort((a, b) => a.sortOrder - b.sortOrder);
      const idx = sorted.findIndex((r) => (r.kind === 'existing' ? r.id : r.key) === key);
      if (idx < 0) return prev;

      const item = { ...sorted[idx], displayName: editDisplayName.trim() };
      sorted.splice(idx, 1);
      sorted.splice(newOrder - 1, 0, item);
      return reindexSortOrders(sorted);
    });
    clearEdit();
  };

  const addLink = () => {
    const url = linkUrl.trim();
    if (!url) return;
    set('links', [...form.links, { label: linkLabel.trim() || url, url }]);
    setLinkLabel('');
    setLinkUrl('');
  };

  const startEditLink = (index: number) => {
    const link = form.links[index];
    setEditTarget({ type: 'link', index });
    setEditLinkLabel(link.label);
    setEditLinkUrl(link.url);
  };

  const applyLinkEdit = () => {
    if (editTarget?.type !== 'link') return;
    const url = editLinkUrl.trim();
    if (!url) return;
    set('links', form.links.map((l, i) =>
      i === editTarget.index
        ? { label: editLinkLabel.trim() || url, url }
        : l,
    ));
    clearEdit();
  };

  const removeLink = (index: number) => {
    set('links', form.links.filter((_, i) => i !== index));
    if (editTarget?.type === 'link' && editTarget.index === index) clearEdit();
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!form.title.trim()) return;
    if (editTarget?.type === 'file') applyFileEdit();
    if (editTarget?.type === 'link') applyLinkEdit();

    let saveForm = form;
    const pendingUrl = linkUrl.trim();
    if (editTarget?.type !== 'link' && pendingUrl) {
      saveForm = {
        ...form,
        links: [...form.links, { label: linkLabel.trim() || pendingUrl, url: pendingUrl }],
      };
    }

    const sorted = [...fileRows].sort((a, b) => a.sortOrder - b.sortOrder);
    const uploads: PendingFileUpload[] = [];
    const existingEdits: ExistingFileEdit[] = [];

    sorted.forEach((row, i) => {
      if (row.kind === 'pending') {
        uploads.push({
          key: row.key,
          file: row.file,
          displayName: row.displayName,
          sortOrder: i,
        });
      } else {
        existingEdits.push({
          id: row.id,
          displayName: row.displayName,
          sortOrder: i,
        });
      }
    });

    await onSave(saveForm, {
      uploads,
      existingEdits,
      deletedFileIds,
      replacements: Object.entries(replacements).map(([id, file]) => ({ id, file })),
    });
  };

  const editingFileRow =
    editTarget?.type === 'file'
      ? orderedFiles.find((r) => (r.kind === 'existing' ? r.id : r.key) === editTarget.key)
      : null;

  return createPortal(
    <div
      className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 p-4"
      onMouseDown={onClose}
    >
      <form
        className="flex max-h-[90vh] w-full max-w-lg flex-col overflow-hidden rounded-2xl bg-white shadow-2xl"
        onMouseDown={(e) => e.stopPropagation()}
        onSubmit={handleSubmit}
      >
        <div className="shrink-0 border-b border-slate-100 px-6 py-4">
          <h2 className="text-xl font-black text-slate-800">{modalTitle}</h2>
        </div>

        <div className="space-y-4 overflow-y-auto px-6 py-4">
          <label className="block">
            <span className="mb-1 block text-sm font-bold text-slate-500">제목 *</span>
            <input
              required
              value={form.title}
              onChange={(e) => set('title', e.target.value)}
              className="w-full rounded-lg border border-slate-200 px-3 py-2 text-lg font-semibold focus:border-emerald-400 focus:outline-none"
              placeholder="단계 제목"
            />
          </label>

          {isRoutine && (
            <label className="block">
              <span className="mb-1 block text-sm font-bold text-slate-500">부제목</span>
              <input
                value={form.subtitle}
                onChange={(e) => set('subtitle', e.target.value)}
                className="w-full rounded-lg border border-slate-200 px-3 py-2 text-base focus:border-emerald-400 focus:outline-none"
                placeholder="업무명 아래 표시 (선택)"
              />
            </label>
          )}

          <label className="block">
            <span className="mb-1 flex items-center justify-between text-sm font-bold text-slate-500">
              <span>진행률</span>
              <span className="text-lg font-black text-emerald-600">{form.progress}%</span>
            </span>
            <input
              type="range"
              min={0}
              max={100}
              step={5}
              value={form.progress}
              onChange={(e) => set('progress', Number(e.target.value))}
              className="w-full accent-emerald-600"
            />
          </label>

          <div>
            <div className="mb-2 flex items-center justify-between">
              <span className="text-sm font-bold text-slate-500">수행 기간</span>
              <button
                type="button"
                onClick={addPeriodEntry}
                className="rounded-lg px-2 py-1 text-xs font-bold text-emerald-600 transition hover:bg-emerald-50 hover:text-emerald-700"
              >
                + 기간 추가
              </button>
            </div>
            {form.periodEntries.length === 0 ? (
              <p className="rounded-lg border border-dashed border-slate-200 px-4 py-3 text-center text-sm text-slate-400">
                등록된 기간이 없습니다. 보류 후 재개·분기별 수행 등 기간을 추가하세요.
              </p>
            ) : (
              <div className="space-y-2">
                {form.periodEntries.map((entry, index) => (
                  <div
                    key={entry.id}
                    className="space-y-2 rounded-xl border border-slate-200 bg-slate-50/60 p-3"
                  >
                    <div className="flex items-center justify-between gap-2">
                      <span className="text-xs font-bold text-slate-500">기간 {index + 1}</span>
                      <button
                        type="button"
                        onClick={() => removePeriodEntry(entry.id)}
                        className="rounded-lg px-2 py-1 text-xs font-bold text-red-500 transition hover:bg-red-50 hover:text-red-600"
                      >
                        삭제
                      </button>
                    </div>
                    <div className="grid grid-cols-2 gap-2">
                      <label className="block">
                        <span className="mb-1 block text-xs font-semibold text-slate-500">시작일</span>
                        <input
                          type="date"
                          value={entry.startDate}
                          onChange={(e) => updatePeriodEntry(entry.id, { startDate: e.target.value })}
                          className="w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm focus:border-emerald-400 focus:outline-none"
                        />
                      </label>
                      <label className="block">
                        <span className="mb-1 block text-xs font-semibold text-slate-500">종료일</span>
                        <input
                          type="date"
                          value={entry.dueDate}
                          onChange={(e) => updatePeriodEntry(entry.id, { dueDate: e.target.value })}
                          className="w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm focus:border-emerald-400 focus:outline-none"
                        />
                      </label>
                    </div>
                    <label className="block">
                      <span className="mb-1 block text-xs font-semibold text-slate-500">업무내용</span>
                      <textarea
                        value={entry.note}
                        onChange={(e) => updatePeriodEntry(entry.id, { note: e.target.value })}
                        rows={2}
                        className="w-full resize-none rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm focus:border-emerald-400 focus:outline-none"
                        placeholder="이 기간에 수행한 내용"
                      />
                    </label>
                  </div>
                ))}
              </div>
            )}
          </div>

          {isRoutine && teamMembers.length > 0 && (
            <div className="space-y-3 rounded-xl border border-emerald-100 bg-emerald-50/40 p-4">
              <div>
                <label className="mb-1 block text-sm font-bold text-slate-500">PM</label>
                <select
                  value={form.pmMemberId}
                  onChange={(e) => set('pmMemberId', e.target.value)}
                  className="w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm outline-none focus:border-emerald-400"
                >
                  <option value="">선택 안 함</option>
                  {teamMembers.map((m) => (
                    <option key={m.id} value={m.id}>
                      {m.name}
                      {m.rank ? ` · ${m.rank}` : ''}
                    </option>
                  ))}
                </select>
              </div>
              <div>
                <label className="mb-1.5 block text-sm font-bold text-slate-500">담당자 (복수 선택)</label>
                <div className="flex flex-wrap gap-2">
                  {teamMembers.map((m) => {
                    const checked = form.assigneeMemberIds.includes(m.id);
                    return (
                      <label
                        key={m.id}
                        className={`inline-flex cursor-pointer select-none items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-semibold transition ${
                          checked
                            ? 'border-emerald-600 bg-emerald-600 text-white'
                            : 'border-slate-200 bg-white text-slate-600 hover:border-emerald-300'
                        }`}
                      >
                        <input
                          type="checkbox"
                          className="sr-only"
                          checked={checked}
                          onChange={() => toggleAssignee(m.id)}
                        />
                        {m.name}
                      </label>
                    );
                  })}
                </div>
              </div>
            </div>
          )}

          {/* 첨부 자료 */}
          <div>
            <span className="mb-1 block text-sm font-bold text-slate-500">첨부 자료</span>
            <p className="mb-2 text-xs text-slate-400">저장 후 결과물 프리뷰에 순서대로 표시됩니다</p>

            {editTarget?.type !== 'file' && (
              <div className="flex gap-2">
                <input
                  value={fileLabel}
                  onChange={(e) => setFileLabel(e.target.value)}
                  placeholder="표시명"
                  className="w-1/3 rounded-lg border border-slate-200 px-2 py-2 text-sm focus:border-emerald-400 focus:outline-none"
                />
                <input ref={fileInputRef} type="file" className="hidden" onChange={handleFilePick} />
                <button
                  type="button"
                  onClick={() => fileInputRef.current?.click()}
                  className="min-w-0 flex-1 rounded-lg bg-slate-100 px-3 text-sm font-bold text-slate-600 hover:bg-slate-200"
                >
                  📎 파일 첨부
                </button>
              </div>
            )}

            {editTarget?.type === 'file' && editingFileRow && (
              <div className="rounded-lg border border-emerald-200 bg-emerald-50/50 p-3 space-y-3">
                <p className="text-sm font-bold text-emerald-800">첨부 자료 편집</p>
                <label className="block">
                  <span className="mb-1 block text-xs font-bold text-slate-500">표시명</span>
                  <input
                    value={editDisplayName}
                    onChange={(e) => setEditDisplayName(e.target.value)}
                    placeholder={editingFileRow.fileName}
                    className="w-full rounded-lg border border-slate-200 px-2 py-2 text-sm focus:border-emerald-400 focus:outline-none"
                  />
                </label>
                <div>
                  <span className="mb-1 block text-xs font-bold text-slate-500">첨부 파일</span>
                  <div className="flex items-center gap-2">
                    <span className="min-w-0 flex-1 truncate text-sm text-slate-600">{editingFileRow.fileName}</span>
                    <input ref={replaceInputRef} type="file" className="hidden" onChange={handleReplacePick} />
                    <button
                      type="button"
                      onClick={() => {
                        replaceTargetId.current = editTarget.key;
                        replaceInputRef.current?.click();
                      }}
                      className="shrink-0 rounded-lg bg-white px-3 py-1.5 text-xs font-bold text-slate-600 ring-1 ring-slate-200 hover:bg-slate-50"
                    >
                      파일 변경
                    </button>
                  </div>
                </div>
                <label className="block">
                  <span className="mb-1 block text-xs font-bold text-slate-500">순서</span>
                  <input
                    type="number"
                    min={1}
                    max={orderedFiles.length}
                    value={editOrder}
                    onChange={(e) => setEditOrder(Number(e.target.value))}
                    className="w-20 rounded-lg border border-slate-200 px-2 py-2 text-sm focus:border-emerald-400 focus:outline-none"
                  />
                  <span className="ml-2 text-xs text-slate-400">/ {orderedFiles.length}</span>
                </label>
                <div className="flex justify-end gap-2">
                  <button type="button" onClick={clearEdit} className="rounded-lg px-3 py-1.5 text-sm font-bold text-slate-500 hover:bg-white">
                    취소
                  </button>
                  <button type="button" onClick={applyFileEdit} className="rounded-lg bg-emerald-600 px-4 py-1.5 text-sm font-bold text-white hover:bg-emerald-700">
                    적용
                  </button>
                </div>
              </div>
            )}

            {orderedFiles.length > 0 && editTarget?.type !== 'file' && (
              <ul className="mt-2 space-y-1 rounded-lg bg-slate-50 px-3 py-2">
                {orderedFiles.map((row, i) => {
                  const key = row.kind === 'existing' ? row.id : row.key;
                  const label = row.displayName.trim() || row.fileName;
                  return (
                    <li key={key} className="flex items-center justify-between gap-2 text-sm">
                      <span className="flex min-w-0 items-center gap-1.5 truncate text-slate-700" title={row.fileName}>
                        <span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-[10px] font-black text-emerald-700">
                          {i + 1}
                        </span>
                        <span className="shrink-0">📄</span>
                        <span className="truncate font-semibold">{label}</span>
                      </span>
                      <ListActions onEdit={() => startEditFile(key)} onDelete={() => removeFile(key)} />
                    </li>
                  );
                })}
              </ul>
            )}

            <p className="mt-1 text-xs text-slate-400">표시명을 비우면 파일명이 사용됩니다</p>
          </div>

          {/* 웹 링크 */}
          <div>
            <span className="mb-1 block text-sm font-bold text-slate-500">웹 링크</span>
            <p className="mb-2 text-xs text-slate-400">저장 후 결과물 프리뷰 우측에 클립 아이콘으로 표시됩니다</p>

            {editTarget?.type !== 'link' && (
              <div className="flex gap-2">
                <input
                  value={linkLabel}
                  onChange={(e) => setLinkLabel(e.target.value)}
                  placeholder="표시명"
                  className="w-1/3 rounded-lg border border-slate-200 px-2 py-2 text-sm focus:outline-none"
                />
                <input
                  value={linkUrl}
                  onChange={(e) => setLinkUrl(e.target.value)}
                  placeholder="https://..."
                  className="min-w-0 flex-1 rounded-lg border border-slate-200 px-2 py-2 text-sm focus:outline-none"
                />
                <button
                  type="button"
                  onClick={addLink}
                  className="shrink-0 rounded-lg bg-slate-100 px-3 text-sm font-bold text-slate-600 hover:bg-slate-200"
                >
                  추가
                </button>
              </div>
            )}

            {editTarget?.type === 'link' && (
              <div className="rounded-lg border border-sky-200 bg-sky-50/50 p-3 space-y-3">
                <p className="text-sm font-bold text-sky-800">웹 링크 편집</p>
                <label className="block">
                  <span className="mb-1 block text-xs font-bold text-slate-500">표시명</span>
                  <input
                    value={editLinkLabel}
                    onChange={(e) => setEditLinkLabel(e.target.value)}
                    className="w-full rounded-lg border border-slate-200 px-2 py-2 text-sm focus:outline-none"
                  />
                </label>
                <label className="block">
                  <span className="mb-1 block text-xs font-bold text-slate-500">URL</span>
                  <input
                    value={editLinkUrl}
                    onChange={(e) => setEditLinkUrl(e.target.value)}
                    placeholder="https://..."
                    className="w-full rounded-lg border border-slate-200 px-2 py-2 text-sm focus:outline-none"
                  />
                </label>
                <div className="flex justify-end gap-2">
                  <button type="button" onClick={clearEdit} className="rounded-lg px-3 py-1.5 text-sm font-bold text-slate-500 hover:bg-white">
                    취소
                  </button>
                  <button type="button" onClick={applyLinkEdit} className="rounded-lg bg-sky-600 px-4 py-1.5 text-sm font-bold text-white hover:bg-sky-700">
                    적용
                  </button>
                </div>
              </div>
            )}

            {form.links.length > 0 && editTarget?.type !== 'link' && (
              <ul className="mt-2 space-y-1 rounded-lg bg-slate-50 px-3 py-2">
                {form.links.map((l, i) => (
                  <li key={l.url + i} className="flex items-center justify-between gap-2 text-sm">
                    <span className="flex min-w-0 items-center gap-1.5 truncate text-slate-700">
                      <span className="shrink-0">🔗</span>
                      <span className="truncate font-semibold">{l.label}</span>
                    </span>
                    <ListActions onEdit={() => startEditLink(i)} onDelete={() => removeLink(i)} />
                  </li>
                ))}
              </ul>
            )}
          </div>
        </div>

        <div className="flex shrink-0 justify-end gap-2 border-t border-slate-100 px-6 py-4">
          <button type="button" onClick={onClose} disabled={saving} className="rounded-lg px-4 py-2 text-sm font-bold text-slate-500 hover:bg-slate-50">
            취소
          </button>
          <button
            type="submit"
            disabled={saving || !form.title.trim()}
            className="rounded-lg bg-emerald-600 px-5 py-2 text-sm font-bold text-white hover:bg-emerald-700 disabled:opacity-50"
          >
            {saving ? '저장 중…' : mode === 'add' ? '추가' : '저장'}
          </button>
        </div>
      </form>
    </div>,
    document.body,
  );
}

export function parseMilestoneLinks(raw: string | null | undefined): MilestoneLink[] {
  return parseLinks(raw);
}
