"use client";

import type { CSSProperties, ReactNode } from "react";
import { useEffect, useState } from "react";
import Link from "next/link";
import {
  DndContext,
  PointerSensor,
  closestCenter,
  useSensor,
  useSensors,
  DragEndEvent,
  useDroppable,
} from "@dnd-kit/core";
import {
  SortableContext,
  verticalListSortingStrategy,
  useSortable,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useEditorStore } from "@/features/editor/state/editorStore";
import type {
  Block,
  TextBlockProps,
  ButtonBlockProps,
  ImageBlockProps,
  SectionBlockProps,
  DividerBlockProps,
  ListBlockProps,
  FormBlockProps,
} from "@/features/editor/state/editorStore";
import { ButtonPropertiesPanel } from "./panels/ButtonPropertiesPanel";
import { TextPropertiesPanel } from "./panels/TextPropertiesPanel";
import { ListPropertiesPanel } from "./panels/ListPropertiesPanel";
import { DividerPropertiesPanel } from "./panels/DividerPropertiesPanel";
import { ImagePropertiesPanel } from "./panels/ImagePropertiesPanel";
import { SectionPropertiesPanel } from "./panels/SectionPropertiesPanel";

export default function EditorPage() {
  const blocks = useEditorStore((state) => state.blocks);
  const selectedBlockId = useEditorStore((state) => state.selectedBlockId);
  const addTextBlock = useEditorStore((state) => state.addTextBlock);
  const addButtonBlock = useEditorStore((state) => state.addButtonBlock);
  const addImageBlock = useEditorStore((state) => state.addImageBlock);
  const addDividerBlock = useEditorStore((state) => state.addDividerBlock);
  const addListBlock = useEditorStore((state) => state.addListBlock);
  const addSectionBlock = useEditorStore((state) => state.addSectionBlock);
  const addFormBlock = useEditorStore((state) => state.addFormBlock);
  const addHeroTemplateSection = useEditorStore((state) => state.addHeroTemplateSection);
  const addFeaturesTemplateSection = useEditorStore((state) => state.addFeaturesTemplateSection);
  const addCtaTemplateSection = useEditorStore((state) => state.addCtaTemplateSection);
  const addFaqTemplateSection = useEditorStore((state) => state.addFaqTemplateSection);
  const addPricingTemplateSection = useEditorStore((state) => state.addPricingTemplateSection);
  const addTestimonialsTemplateSection = useEditorStore((state) => state.addTestimonialsTemplateSection);
  const addBlogTemplateSection = useEditorStore((state) => state.addBlogTemplateSection);
  const addTeamTemplateSection = useEditorStore((state) => state.addTeamTemplateSection);
  const addFooterTemplateSection = useEditorStore((state) => state.addFooterTemplateSection);
  const updateBlock = useEditorStore((state) => state.updateBlock);
  const selectBlock = useEditorStore((state) => state.selectBlock);
  const replaceBlocks = useEditorStore((state) => state.replaceBlocks);
  const reorderBlocks = useEditorStore((state) => state.reorderBlocks);
  const moveBlock = useEditorStore((state) => state.moveBlock);
  const undo = useEditorStore((state) => state.undo);
  const redo = useEditorStore((state) => state.redo);
  const removeBlock = useEditorStore((state) => state.removeBlock);
  const duplicateBlock = useEditorStore((state) => state.duplicateBlock);

  const [editingBlockId, setEditingBlockId] = useState<string | null>(null);
  const [editingText, setEditingText] = useState("");

  const [exportJson, setExportJson] = useState("");
  const [importJson, setImportJson] = useState("");

  const [projectTitle, setProjectTitle] = useState("");
  const [projectSlug, setProjectSlug] = useState("");
  const [loadSlug, setLoadSlug] = useState("");
  const [projectMessage, setProjectMessage] = useState("");

  const [menuOpen, setMenuOpen] = useState(false);
  const [activeModal, setActiveModal] = useState<"project" | "json" | null>(null);

  const sensors = useSensors(useSensor(PointerSensor));

  const startEditing = (id: string, initialText: string) => {
    selectBlock(id);
    setEditingBlockId(id);
    setEditingText(initialText);
  };

  const commitEditing = () => {
    if (!editingBlockId) return;
    updateBlock(editingBlockId, { text: editingText });
    setEditingBlockId(null);
  };

  const cancelEditing = () => {
    if (!editingBlockId) return;
    const current = blocks.find((b) => b.id === editingBlockId);
    if (current && current.type === "text") {
      const textProps = current.props as TextBlockProps;
      setEditingText(textProps.text);
    }
    setEditingBlockId(null);
  };

  const handleExportJson = () => {
    try {
      const payload = JSON.stringify(blocks, null, 2);
      setExportJson(payload);
      setImportJson(payload);
    } catch {
      // JSON 직렬화 실패 시는 조용히 무시 (순수 클라이언트 상태라 치명적이지 않음)
    }
  };

  const handleClearCanvas = () => {
    replaceBlocks([]);
    setExportJson("");
    setImportJson("");
  };

  const handleApplyImportJson = () => {
    if (!importJson.trim()) return;
    try {
      const parsed = JSON.parse(importJson);
      if (!Array.isArray(parsed)) return;
      // 최소한의 구조 검증: id, type, props.text 가 있는 블록만 사용
      const safeBlocks = parsed.filter((b) => b && typeof b.id === "string" && b.type === "text" && b.props && typeof b.props.text === "string");
      replaceBlocks(safeBlocks);
      // 내보내기 영역도 최신 상태로 업데이트
      setExportJson(JSON.stringify(safeBlocks, null, 2));
    } catch {
      // 파싱 에러는 무시 (추후에는 토스트 등으로 피드백 가능)
    }
  };

  const handleSaveProject = async () => {
    const slug = projectSlug.trim();
    if (!slug) {
      setProjectMessage("프로젝트 주소를 입력해 주세요.");
      return;
    }

    try {
      // 1) 로컬스토리지에 현재 상태 저장 (슬러그 기준)
      if (typeof window !== "undefined") {
        const localKey = `pb:project:${slug}`;
        const payload = {
          title: projectTitle.trim() || "제목 없음",
          contentJson: blocks,
        };
        window.localStorage.setItem(localKey, JSON.stringify(payload));
      }

      // 2) 서버에도 저장 (백업/공유용)
      const response = await fetch("/api/projects", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          title: projectTitle.trim() || "제목 없음",
          slug,
          contentJson: blocks,
        }),
      });

      if (!response.ok) {
        setProjectMessage("프로젝트 저장에 실패했습니다.");
        return;
      }

      const data = await response.json();
      setProjectMessage(`프로젝트가 저장되었습니다: ${data.slug}`);
    } catch (error) {
      console.error(error);
      setProjectMessage("프로젝트 저장 중 오류가 발생했습니다.");
    }
  };

  const handleLoadProject = async () => {
    const slug = loadSlug.trim() || projectSlug.trim();
    if (!slug) {
      setProjectMessage("불러올 주소를 입력해 주세요.");
      return;
    }

    try {
      // 1) 로컬스토리지에서 먼저 조회
      if (typeof window !== "undefined") {
        const localKey = `pb:project:${slug}`;
        const raw = window.localStorage.getItem(localKey);
        if (raw) {
          try {
            const parsed = JSON.parse(raw);
            if (parsed && Array.isArray(parsed.contentJson)) {
              replaceBlocks(parsed.contentJson as any);
              setProjectMessage(`로컬에서 프로젝트를 불러왔습니다: ${slug}`);
              return;
            }
          } catch {
            // 로컬 파싱 에러는 무시하고 서버 시도
          }
        }
      }

      // 2) 로컬에 없으면 서버에서 조회
      const response = await fetch(`/api/projects/${slug}`);
      if (!response.ok) {
        setProjectMessage("프로젝트를 불러오지 못했습니다.");
        return;
      }

      const data = await response.json();
      if (data && Array.isArray(data.contentJson)) {
        replaceBlocks(data.contentJson as any);
        setProjectMessage(`프로젝트를 불러왔습니다: ${slug}`);
      } else {
        setProjectMessage("프로젝트 데이터 형식이 올바르지 않습니다.");
      }
    } catch (error) {
      console.error(error);
      setProjectMessage("프로젝트 불러오기 중 오류가 발생했습니다.");
    }
  };

  const handleDragEnd = (event: DragEndEvent) => {
    const { active, over } = event;
    if (!over || active.id === over.id) return;
    const activeId = String(active.id);
    const overId = String(over.id);

    // 컬럼 droppable에 드롭한 경우: 해당 섹션/컬럼으로 이동만 수행한다.
    if (overId.startsWith("column:")) {
      const [, sectionId, columnId] = overId.split(":");
      moveBlock(activeId, sectionId || null, columnId || null);
      return;
    }

    const activeBlock = blocks.find((b) => b.id === activeId);
    const overBlock = blocks.find((b) => b.id === overId);

    if (!activeBlock || !overBlock) {
      reorderBlocks(activeId, overId);
      return;
    }

    const activeSectionId = activeBlock.sectionId ?? null;
    const activeColumnId = activeBlock.columnId ?? null;
    const overSectionId = overBlock.sectionId ?? null;
    const overColumnId = overBlock.columnId ?? null;

    // 같은 섹션/컬럼 내에서는 순서만 변경한다.
    if (activeSectionId === overSectionId && activeColumnId === overColumnId) {
      reorderBlocks(activeId, overId);
      return;
    }

    // 다른 컬럼/섹션으로 이동: 위치를 변경한 뒤, 블록 기준 드롭이면 순서 재배치까지 진행한다.
    moveBlock(activeId, overSectionId, overColumnId);
    reorderBlocks(activeId, overId);
  };

  const rootBlocks = blocks.filter((block) => !block.sectionId);

  // 에디터 전역에서 Undo/Redo 단축키(Cmd/Ctrl+Z, Cmd/Ctrl+Shift+Z),
  // Delete/Backspace 기반 삭제, Cmd/Ctrl+D 기반 복제,
  // ArrowUp/ArrowDown을 이용한 선택 블록 이동을 처리한다.
  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      // 방향키로 선택된 블록을 위/아래로 이동 (순서 변경이 아니라 선택만 이동)
      if (event.key === "ArrowDown" || event.key === "ArrowUp") {
        const { blocks: currentBlocks, selectedBlockId: currentSelectedId, selectBlock: storeSelectBlock } =
          (useEditorStore as any).getState();

        if (!currentBlocks.length) return;

        const currentIndex = currentSelectedId
          ? currentBlocks.findIndex((b: Block) => b.id === currentSelectedId)
          : -1;

        let nextIndex = currentIndex;

        if (event.key === "ArrowDown") {
          nextIndex = currentIndex < currentBlocks.length - 1 ? currentIndex + 1 : currentIndex;
        } else if (event.key === "ArrowUp") {
          nextIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex;
        }

        if (nextIndex !== -1 && nextIndex !== currentIndex) {
          event.preventDefault();
          const nextId = currentBlocks[nextIndex]?.id;
          if (nextId) {
            storeSelectBlock(nextId);
          }
        }

        return;
      }

      const isMac = navigator.platform.toLowerCase().includes("mac");
      const metaKey = isMac ? event.metaKey : event.ctrlKey;

      // Delete / Backspace 로 선택된 블록 삭제
      if (event.key === "Delete" || event.key === "Backspace") {
        const { selectedBlockId: currentSelectedId } = (useEditorStore as any).getState();
        if (currentSelectedId) {
          event.preventDefault();
          removeBlock(currentSelectedId);
        }
        return;
      }

      // Cmd/Ctrl 없는 경우에는 여기서 종료한다.
      if (!metaKey) return;

      // Cmd/Ctrl + D → 현재 선택된 블록 복제
      if (event.key.toLowerCase() === "d") {
        const { selectedBlockId: currentSelectedId } = (useEditorStore as any).getState();
        if (currentSelectedId) {
          event.preventDefault();
          duplicateBlock(currentSelectedId);
        }
        return;
      }

      // Cmd/Ctrl + Shift + Z → Redo
      if (event.key.toLowerCase() === "z" && event.shiftKey) {
        event.preventDefault();
        redo();
        return;
      }

      // Cmd/Ctrl + Z → Undo
      if (event.key.toLowerCase() === "z") {
        event.preventDefault();
        undo();
      }
    };

    window.addEventListener("keydown", handleKeyDown);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [undo, redo]);

  const renderBlocks = (targetBlocks: Block[]) =>
    targetBlocks.map((block) => {
      const isSelected = block.id === selectedBlockId;
      const isEditing = block.id === editingBlockId;

      return (
        <SortableEditorBlock
          key={block.id}
          block={block}
          isSelected={isSelected}
          isEditing={isEditing}
          editingText={editingText}
          startEditing={startEditing}
          commitEditing={commitEditing}
          cancelEditing={cancelEditing}
          setEditingText={setEditingText}
          selectBlock={selectBlock}
          allBlocks={blocks}
          renderBlocks={renderBlocks}
          updateBlock={updateBlock}
        />
      );
    });

  return (
    <main className="min-h-screen flex flex-col bg-slate-950 text-slate-50">
      <header className="border-b border-slate-800 px-6 py-4 flex items-center justify-between relative z-20">
        <div className="flex items-baseline gap-3">
          <h1 className="text-xl font-semibold">Page Editor</h1>
          <p className="text-xs text-slate-400">빌더 MVP용 에디터 페이지 골격</p>
        </div>
        <div className="flex items-center gap-2 text-xs">
          <Link
            href="/preview"
            className="inline-flex items-center rounded border border-slate-700 bg-slate-900 px-3 py-1 text-slate-100 hover:bg-slate-800"
          >
            프리뷰 열기
          </Link>
          <div className="relative">
            <button
              type="button"
              className="inline-flex items-center gap-1 rounded border border-slate-700 bg-slate-900 px-3 py-1 text-slate-100 hover:bg-slate-800"
              onClick={() => setMenuOpen((prev) => !prev)}
            >
              메뉴
              <span className="text-[9px]">▼</span>
            </button>
            {menuOpen && (
              <div className="absolute right-0 mt-1 w-48 rounded border border-slate-700 bg-slate-900 shadow-lg text-[11px] z-30">
                <button
                  type="button"
                  className="w-full px-3 py-2 text-left hover:bg-slate-800 text-slate-100"
                  onClick={() => {
                    setMenuOpen(false);
                    setActiveModal("project");
                  }}
                >
                  프로젝트 저장/불러오기
                </button>
                <button
                  type="button"
                  className="w-full px-3 py-2 text-left hover:bg-slate-800 text-slate-100 border-t border-slate-800"
                  onClick={() => {
                    setMenuOpen(false);
                    setActiveModal("json");
                  }}
                >
                  JSON 내보내기/불러오기
                </button>
              </div>
            )}
          </div>
        </div>
      </header>
      <section className="flex flex-1 overflow-hidden">
        <aside className="w-60 border-r border-slate-800 p-4 text-sm space-y-3">
          <h2 className="font-medium">블록</h2>
          <button
            type="button"
            className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
            onClick={addTextBlock}
          >
            텍스트 블록 추가
          </button>
          <button
            type="button"
            className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
            onClick={addButtonBlock}
          >
            버튼 블록 추가
          </button>
          <button
            type="button"
            className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
            onClick={addImageBlock}
          >
            이미지 블록 추가
          </button>
          <button
            type="button"
            className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
            onClick={addDividerBlock}
          >
            구분선 블록 추가
          </button>
          <button
            type="button"
            className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
            onClick={addListBlock}
          >
            리스트 블록 추가
          </button>
          <button
            type="button"
            className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
            onClick={addSectionBlock}
          >
            섹션 블록 추가
          </button>
          <button
            type="button"
            className="w-full rounded border border-emerald-700 bg-emerald-950 px-3 py-2 text-left text-xs text-emerald-100 hover:bg-emerald-900"
            onClick={addFormBlock}
          >
            폼 블록 추가
          </button>
          <div className="pt-3 border-t border-slate-800 mt-3 space-y-2">
            <h3 className="text-[11px] font-medium text-slate-300">템플릿</h3>
            <button
              type="button"
              className="w-full rounded border border-sky-700 bg-sky-950 px-3 py-2 text-left text-xs text-sky-100 hover:bg-sky-900"
              onClick={addHeroTemplateSection}
            >
              Hero 템플릿 추가
            </button>
            <button
              type="button"
              className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
              onClick={addFeaturesTemplateSection}
            >
              Features 템플릿 추가
            </button>
            <button
              type="button"
              className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
              onClick={addCtaTemplateSection}
            >
              CTA 템플릿 추가
            </button>
            <button
              type="button"
              className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
              onClick={addFaqTemplateSection}
            >
              FAQ 템플릿 추가
            </button>
            <button
              type="button"
              className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
              onClick={addPricingTemplateSection}
            >
              Pricing 템플릿 추가
            </button>
            <button
              type="button"
              className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
              onClick={addTestimonialsTemplateSection}
            >
              Testimonials 템플릿 추가
            </button>
            <button
              type="button"
              className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
              onClick={addBlogTemplateSection}
            >
              Blog 템플릿 추가
            </button>
            <button
              type="button"
              className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
              onClick={addTeamTemplateSection}
            >
              Team 템플릿 추가
            </button>
            <button
              type="button"
              className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
              onClick={addFooterTemplateSection}
            >
              Footer 템플릿 추가
            </button>
          </div>
          <p className="text-xs text-slate-500">
            Text / Image / Button / Divider / List / Section 블록을 이 영역에서 추가할 수 있습니다.
          </p>
        </aside>
        <div
          className="flex-1 p-4 flex flex-col gap-2 text-sm text-slate-200 border-r border-slate-800 overflow-auto"
          data-testid="editor-canvas"
        >
          {blocks.length === 0 ? (
            <div className="flex-1 flex items-center justify-center text-slate-500 text-xs">
              왼쪽에서 "텍스트 블록 추가" 버튼을 눌러 블록을 추가해 보세요.
            </div>
          ) : (
            <DndContext
              sensors={sensors}
              collisionDetection={closestCenter}
              onDragEnd={handleDragEnd}
            >
              <SortableContext
                items={blocks.map((b) => b.id)}
                strategy={verticalListSortingStrategy}
              >
                {renderBlocks(rootBlocks)}
              </SortableContext>
            </DndContext>
          )}
        </div>
        <aside className="w-80 p-4 text-sm border-l border-slate-800 flex flex-col gap-4 overflow-auto">
          <h2 className="font-medium mb-2">속성 패널</h2>
          {selectedBlockId ? (
            <div className="space-y-3">
              <div className="flex gap-2 text-[11px]">
                <button
                  type="button"
                  className="flex-1 rounded border border-slate-700 bg-slate-900 px-2 py-1 text-slate-100 hover:bg-red-900/60 hover:border-red-700"
                  onClick={() => removeBlock(selectedBlockId)}
                >
                  블록 삭제
                </button>
                <button
                  type="button"
                  className="flex-1 rounded border border-slate-700 bg-slate-900 px-2 py-1 text-slate-100 hover:bg-slate-800"
                  onClick={() => duplicateBlock(selectedBlockId)}
                >
                  블록 복제
                </button>
              </div>
              {(() => {
                const selectedBlock = blocks.find((b) => b.id === selectedBlockId);
                if (!selectedBlock) return null;

                if (selectedBlock.type === "text") {
                  const textProps = selectedBlock.props as TextBlockProps;

                  return (
                    <TextPropertiesPanel
                      textProps={textProps}
                      selectedBlockId={selectedBlockId}
                      updateBlock={(id, partial) => updateBlock(id, partial as any)}
                      editingBlockId={editingBlockId}
                      setEditingText={setEditingText}
                    />
                  );
                }

                if (selectedBlock.type === "divider") {
                  const dividerProps = selectedBlock.props as DividerBlockProps;

                  return (
                    <DividerPropertiesPanel
                      dividerProps={dividerProps}
                      selectedBlockId={selectedBlockId}
                      updateBlock={(id, partial) => updateBlock(id, partial as any)}
                    />
                  );
                }

                if (selectedBlock.type === "image") {
                  const imageProps = selectedBlock.props as ImageBlockProps;

                  return (
                    <ImagePropertiesPanel
                      imageProps={imageProps}
                      selectedBlockId={selectedBlockId}
                      updateBlock={(id, partial) => updateBlock(id, partial as any)}
                    />
                  );
                }

                if (selectedBlock.type === "section") {
                  const sectionProps = selectedBlock.props as SectionBlockProps;

                  return (
                    <SectionPropertiesPanel
                      sectionProps={sectionProps}
                      selectedBlockId={selectedBlockId}
                      updateBlock={(id, partial) => updateBlock(id, partial as any)}
                    />
                  );
                }

                if (selectedBlock.type === "list") {
                  const listProps = selectedBlock.props as ListBlockProps;

                  return (
                    <ListPropertiesPanel
                      listProps={listProps}
                      selectedBlockId={selectedBlockId}
                      updateBlock={(id, partial) => updateBlock(id, partial as any)}
                    />
                  );
                }

                if (selectedBlock.type === "button") {
                  const buttonProps = selectedBlock.props as ButtonBlockProps;

                  return (
                    <ButtonPropertiesPanel
                      buttonProps={buttonProps}
                      selectedBlockId={selectedBlockId}
                      updateBlock={(id, partial) => updateBlock(id, partial as any)}
                    />
                  );
                }

                if (selectedBlock.type === "form") {
                  const formProps = selectedBlock.props as FormBlockProps;

                  return (
                    <div className="space-y-3 text-xs">
                      <p className="text-slate-400">
                        이 폼은 퍼블릭 페이지에서 /api/forms/submit 엔드포인트로 전송됩니다.
                      </p>
                      <label className="flex flex-col gap-1">
                        <span className="text-slate-400">성공 메시지</span>
                        <input
                          className="w-full rounded border border-slate-700 bg-slate-950 px-2 py-1 text-xs outline-none focus:border-sky-500"
                          value={formProps.successMessage ?? "성공적으로 전송되었습니다."}
                          onChange={(e) =>
                            updateBlock(selectedBlockId, {
                              successMessage: e.target.value,
                            } as any)
                          }
                        />
                      </label>
                      <label className="flex flex-col gap-1">
                        <span className="text-slate-400">실패 메시지</span>
                        <input
                          className="w-full rounded border border-slate-700 bg-slate-950 px-2 py-1 text-xs outline-none focus:border-sky-500"
                          value={formProps.errorMessage ?? "전송 중 오류가 발생했습니다."}
                          onChange={(e) =>
                            updateBlock(selectedBlockId, {
                              errorMessage: e.target.value,
                            } as any)
                          }
                        />
                      </label>
                    </div>
                  );
                }

                return null;
              })()}
            </div>
          ) : (
            <p className="text-xs text-slate-500">
              캔버스에서 블록을 선택하면 이곳에서 내용을 수정하고 스타일을 바꿀 수 있습니다.
            </p>
          )}
        </aside>
      </section>

      {activeModal === "project" && (
        <div className="fixed inset-0 z-30 flex items-center justify-center bg-black/60">
          <div className="w-full max-w-md rounded-lg border border-slate-700 bg-slate-900 p-4 text-xs text-slate-100 shadow-xl">
            <div className="flex items-center justify-between mb-3">
              <h3 className="text-sm font-medium">프로젝트 저장 / 불러오기</h3>
              <button
                type="button"
                className="text-slate-400 hover:text-slate-100 text-sm"
                onClick={() => setActiveModal(null)}
              >
                ✕
              </button>
            </div>
            <div className="space-y-2">
              <label className="flex flex-col gap-1">
                <span className="text-slate-400">프로젝트 제목</span>
                <input
                  className="w-full rounded border border-slate-700 bg-slate-950 px-2 py-1 text-xs outline-none focus:border-sky-500"
                  value={projectTitle}
                  onChange={(e) => setProjectTitle(e.target.value)}
                />
              </label>
              <label className="flex flex-col gap-1">
                <span className="text-slate-400">프로젝트 주소 (예: my-landing)</span>
                <input
                  className="w-full rounded border border-slate-700 bg-slate-950 px-2 py-1 text-xs outline-none focus:border-sky-500"
                  value={projectSlug}
                  onChange={(e) => setProjectSlug(e.target.value)}
                />
              </label>
              <div className="flex gap-2">
                <button
                  type="button"
                  className="flex-1 rounded border border-sky-700 bg-sky-950 px-2 py-1 text-sky-100 hover:bg-sky-900"
                  onClick={handleSaveProject}
                >
                  저장 (로컬 + 서버)
                </button>
                <button
                  type="button"
                  className="flex-1 rounded border border-slate-700 bg-slate-950 px-2 py-1 text-slate-100 hover:bg-slate-800"
                  onClick={handleLoadProject}
                >
                  불러오기
                </button>
              </div>
              <label className="flex flex-col gap-1">
                <span className="text-slate-400">불러올 주소 (비우면 위 주소 사용)</span>
                <input
                  className="w-full rounded border border-slate-700 bg-slate-950 px-2 py-1 text-xs outline-none focus:border-sky-500"
                  value={loadSlug}
                  onChange={(e) => setLoadSlug(e.target.value)}
                />
              </label>
              {projectMessage && (
                <p className="text-[11px] text-slate-300 mt-1">{projectMessage}</p>
              )}
            </div>
          </div>
        </div>
      )}

      {activeModal === "json" && (
        <div className="fixed inset-0 z-30 flex items-center justify-center bg-black/60">
          <div className="w-full max-w-2xl rounded-lg border border-slate-700 bg-slate-900 p-4 text-xs text-slate-100 shadow-xl">
            <div className="flex items-center justify-between mb-3">
              <h3 className="text-sm font-medium">JSON Export / Import</h3>
              <button
                type="button"
                className="text-slate-400 hover:text-slate-100 text-sm"
                onClick={() => setActiveModal(null)}
              >
                ✕
              </button>
            </div>
            <div className="space-y-3">
              <div className="flex gap-2 mb-1">
                <button
                  type="button"
                  className="flex-1 rounded border border-slate-700 bg-slate-950 px-2 py-1 hover:bg-slate-800"
                  onClick={handleExportJson}
                >
                  JSON 내보내기
                </button>
                <button
                  type="button"
                  className="rounded border border-red-700 bg-red-950 px-2 py-1 text-red-100 hover:bg-red-900"
                  onClick={handleClearCanvas}
                >
                  캔버스 초기화
                </button>
              </div>
              <div className="grid grid-cols-2 gap-3">
                <label className="flex flex-col gap-1">
                  <span className="text-slate-400">에디터 상태 JSON</span>
                  <textarea
                    className="w-full h-48 rounded border border-slate-700 bg-slate-950 px-2 py-1 text-[10px] font-mono outline-none focus:border-sky-500"
                    value={exportJson}
                    readOnly
                  />
                </label>
                <label className="flex flex-col gap-1">
                  <span className="text-slate-400">JSON에서 불러오기</span>
                  <textarea
                    className="w-full h-48 rounded border border-slate-700 bg-slate-950 px-2 py-1 text-[10px] font-mono outline-none focus:border-sky-500"
                    value={importJson}
                    onChange={(e) => setImportJson(e.target.value)}
                  />
                  <button
                    type="button"
                    className="mt-1 w-full rounded border border-slate-700 bg-slate-950 px-2 py-1 hover:bg-slate-800"
                    onClick={handleApplyImportJson}
                  >
                    JSON 적용하기
                  </button>
                </label>
              </div>
            </div>
          </div>
        </div>
      )}
    </main>
  );
}

interface TextInlineToolbarProps {
  align: TextBlockProps["align"];
  size: TextBlockProps["size"];
  onChangeAlign: (value: TextBlockProps["align"]) => void;
  onChangeSize: (value: TextBlockProps["size"]) => void;
}

function TextInlineToolbar({ align, size, onChangeAlign, onChangeSize }: TextInlineToolbarProps) {
  return (
    <div className="flex items-center justify-between gap-2 text-[10px] text-slate-300">
      <div className="inline-flex items-center gap-1">
        <span className="text-slate-500">정렬</span>
        <button
          type="button"
          className={`px-1 py-0.5 rounded border text-[10px] ${
            align === "left"
              ? "border-sky-500 bg-sky-900/40 text-sky-100"
              : "border-slate-700 bg-slate-900 text-slate-300"
          }`}
          aria-label="왼쪽 정렬"
          onMouseDown={(e) => e.preventDefault()}
          onClick={() => onChangeAlign("left")}
        >
          좌
        </button>
        <button
          type="button"
          className={`px-1 py-0.5 rounded border text-[10px] ${
            align === "center"
              ? "border-sky-500 bg-sky-900/40 text-sky-100"
              : "border-slate-700 bg-slate-900 text-slate-300"
          }`}
          aria-label="가운데 정렬"
          onMouseDown={(e) => e.preventDefault()}
          onClick={() => onChangeAlign("center")}
        >
          중
        </button>
        <button
          type="button"
          className={`px-1 py-0.5 rounded border text-[10px] ${
            align === "right"
              ? "border-sky-500 bg-sky-900/40 text-sky-100"
              : "border-slate-700 bg-slate-900 text-slate-300"
          }`}
          aria-label="오른쪽 정렬"
          onMouseDown={(e) => e.preventDefault()}
          onClick={() => onChangeAlign("right")}
        >
          우
        </button>
      </div>
      <div className="inline-flex items-center gap-1">
        <span className="text-slate-500">크기</span>
        <button
          type="button"
          className={`px-1 py-0.5 rounded border text-[10px] ${
            size === "sm"
              ? "border-sky-500 bg-sky-900/40 text-sky-100"
              : "border-slate-700 bg-slate-900 text-slate-300"
          }`}
          aria-label="글자 작게"
          onMouseDown={(e) => e.preventDefault()}
          onClick={() => onChangeSize("sm")}
        >
          작게
        </button>
        <button
          type="button"
          className={`px-1 py-0.5 rounded border text-[10px] ${
            size === "base"
              ? "border-sky-500 bg-sky-900/40 text-sky-100"
              : "border-slate-700 bg-slate-900 text-slate-300"
          }`}
          aria-label="글자 보통"
          onMouseDown={(e) => e.preventDefault()}
          onClick={() => onChangeSize("base")}
        >
          보통
        </button>
        <button
          type="button"
          className={`px-1 py-0.5 rounded border text-[10px] ${
            size === "lg"
              ? "border-sky-500 bg-sky-900/40 text-sky-100"
              : "border-slate-700 bg-slate-900 text-slate-300"
          }`}
          aria-label="글자 크게"
          onMouseDown={(e) => e.preventDefault()}
          onClick={() => onChangeSize("lg")}
        >
          크게
        </button>
      </div>
    </div>
  );
}

interface SortableEditorBlockProps {
  block: Block;
  isSelected: boolean;
  isEditing: boolean;
  editingText: string;
  startEditing: (id: string, initialText: string) => void;
  commitEditing: () => void;
  cancelEditing: () => void;
  setEditingText: (value: string) => void;
  selectBlock: (id: string) => void;
  allBlocks: Block[];
  renderBlocks: (targetBlocks: Block[]) => ReactNode;
  updateBlock: (id: string, partial: Partial<TextBlockProps & ButtonBlockProps & FormBlockProps>) => void;
}

function SortableEditorBlock({
  block,
  isSelected,
  isEditing,
  editingText,
  startEditing,
  commitEditing,
  cancelEditing,
  setEditingText,
  selectBlock,
  allBlocks,
  renderBlocks,
  updateBlock,
}: SortableEditorBlockProps) {
  const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
    id: block.id,
  });

  const baseStyle: CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
  };

  let alignClass = "";
  let sizeClass = "";
  let leadingClass = "";
  let weightClass = "";
  let colorClass = "";
  let maxWidthClass = "";
  let decorationClass = "";
  const textStyleOverrides: CSSProperties = {};

  if (block.type === "text") {
    const textProps = block.props as TextBlockProps;

    // 정렬: pb-text-*
    alignClass =
      textProps.align === "center"
        ? "pb-text-center"
        : textProps.align === "right"
        ? "pb-text-right"
        : "pb-text-left";

    // 폰트 크기: scale/custom + 기존 size 값 fallback
    const fontSizeMode = textProps.fontSizeMode ?? "scale";
    const fallbackScale =
      textProps.size === "sm" ? "sm" : textProps.size === "lg" ? "lg" : "base";
    const fontSizeScale = textProps.fontSizeScale ?? fallbackScale;

    if (fontSizeMode === "scale") {
      sizeClass = `pb-text-${fontSizeScale}`;
    } else if (fontSizeMode === "custom" && textProps.fontSizeCustom) {
      textStyleOverrides.fontSize = textProps.fontSizeCustom;
    }

    // 줄 간격: scale/custom
    const lineHeightMode = textProps.lineHeightMode ?? "scale";
    const lineHeightScale = textProps.lineHeightScale ?? "normal";
    if (lineHeightMode === "scale") {
      leadingClass = `pb-leading-${lineHeightScale}`;
    } else if (lineHeightMode === "custom" && textProps.lineHeightCustom) {
      textStyleOverrides.lineHeight = textProps.lineHeightCustom;
    }

    // 굵기: scale/custom
    const fontWeightMode = textProps.fontWeightMode ?? "scale";
    const fontWeightScale = textProps.fontWeightScale ?? "normal";
    if (fontWeightMode === "scale") {
      weightClass = `pb-font-${fontWeightScale}`;
    } else if (fontWeightMode === "custom" && textProps.fontWeightCustom) {
      textStyleOverrides.fontWeight = textProps.fontWeightCustom as CSSProperties["fontWeight"];
    }

    // 색상: colorCustom이 있으면 항상 우선 사용, 없으면 팔레트
    const colorPalette = textProps.colorPalette ?? "default";
    if (textProps.colorCustom && textProps.colorCustom.trim() !== "") {
      textStyleOverrides.color = textProps.colorCustom;
    } else {
      colorClass = `pb-text-color-${colorPalette}`;
    }

    // 최대 너비: 커스텀 값이 있으면 우선 사용, 없으면 scale 프리셋
    const maxWidthScale = textProps.maxWidthScale ?? "none";
    if (textProps.maxWidthCustom && textProps.maxWidthCustom.trim() !== "") {
      textStyleOverrides.maxWidth = textProps.maxWidthCustom;
    } else if (maxWidthScale === "prose") {
      maxWidthClass = "pb-text-maxw-prose";
    } else if (maxWidthScale === "narrow") {
      maxWidthClass = "pb-text-maxw-narrow";
    }

    // 글자 간격: 커스텀 값이 있으면 em 단위 그대로 사용
    if (textProps.letterSpacingCustom && textProps.letterSpacingCustom.trim() !== "") {
      textStyleOverrides.letterSpacing = textProps.letterSpacingCustom;
    }

    // 텍스트 장식: 밑줄/가운데줄/이탤릭
    if (textProps.underline) {
      decorationClass += " pb-underline";
    }
    if (textProps.strike) {
      decorationClass += " pb-line-through";
    }
    if (textProps.italic) {
      decorationClass += " pb-italic";
    }
  } else if (block.type === "button") {
    const buttonProps = block.props as ButtonBlockProps;
    const align = buttonProps.align ?? "left";
    alignClass =
      align === "center" ? "pb-text-center" : align === "right" ? "pb-text-right" : "pb-text-left";
  } else if (block.type === "image") {
    alignClass = "pb-text-left";
  } else if (block.type === "divider") {
    const dividerProps = block.props as DividerBlockProps;
    alignClass =
      dividerProps.align === "center"
        ? "pb-text-center"
        : dividerProps.align === "right"
        ? "pb-text-right"
        : "pb-text-left";
  } else if (block.type === "list") {
    const listProps = block.props as ListBlockProps;
    alignClass =
      listProps.align === "center"
        ? "pb-text-center"
        : listProps.align === "right"
        ? "pb-text-right"
        : "pb-text-left";
  } else if (block.type === "section") {
    // 섹션 블록: 텍스트 크기 대신 섹션 자체에 패딩/배경을 입히므로 텍스트 클래스는 기본값만
    alignClass = "pb-text-left";
  } else if (block.type === "form") {
    // 폼 블록은 텍스트 클래스 대신 기본 정렬만 사용한다.
    alignClass = "pb-text-left";
  }

  return (
    <div
      ref={setNodeRef}
      style={{ ...baseStyle, ...textStyleOverrides }}
      data-testid="editor-block"
      data-selected={isSelected ? "true" : "false"}
      aria-selected={isSelected ? "true" : "false"}
      className={`rounded border px-3 py-2 cursor-text transition-colors ${alignClass} ${sizeClass} ${leadingClass} ${weightClass} ${colorClass} ${maxWidthClass} ${decorationClass} ${
        isSelected ? "border-sky-500 bg-slate-900/80" : "border-slate-700 bg-slate-900"
      }`}
      onClick={() => selectBlock(block.id)}
    >
      <div className="flex items-start gap-2">
        <button
          type="button"
          className="mt-0.5 h-4 w-4 rounded border border-slate-700 bg-slate-900 text-[10px] flex items-center justify-center text-slate-400 hover:bg-slate-800"
          aria-label="블록 드래그 핸들"
          {...listeners}
          {...attributes}
        >
          ≡
        </button>
        <div className="flex-1">
          {block.type === "text" && (
            isEditing ? (
              (() => {
                const textProps = block.props as TextBlockProps;
                return (
                  <div className="flex flex-col gap-1">
                    <textarea
                      className="w-full bg-transparent outline-none resize-none text-sm"
                      aria-label="텍스트 블록 내용 편집"
                      value={editingText}
                      onChange={(e) => setEditingText(e.target.value)}
                      onBlur={commitEditing}
                      onKeyDown={(e) => {
                        if (e.key === "Enter" && !e.shiftKey) {
                          e.preventDefault();
                          commitEditing();
                        }
                        if (e.key === "Escape") {
                          e.preventDefault();
                          cancelEditing();
                        }
                      }}
                      rows={1}
                      autoFocus
                    />
                  </div>
                );
              })()
            ) : (
              <div className="whitespace-pre-wrap">{(block.props as TextBlockProps).text}</div>
            )
          )}
          {block.type === "button" && (() => {
            const buttonProps = block.props as ButtonBlockProps;
            const size = buttonProps.size ?? "md";
            const variant = buttonProps.variant ?? "solid";
            const colorPalette = buttonProps.colorPalette ?? "primary";
            const radius = buttonProps.borderRadius ?? "md";
            const fullWidth = buttonProps.fullWidth ?? false;

            const sizeClassBtn =
              size === "xs"
                ? "pb-btn-size-xs"
                : size === "sm"
                ? "pb-btn-size-sm"
                : size === "lg"
                ? "pb-btn-size-lg"
                : size === "xl"
                ? "pb-btn-size-xl"
                : "pb-btn-size-md";
            const radiusClassBtn =
              radius === "none"
                ? "pb-btn-radius-none"
                : radius === "sm"
                ? "pb-btn-radius-sm"
                : radius === "lg"
                ? "pb-btn-radius-lg"
                : radius === "full"
                ? "pb-btn-radius-full"
                : "pb-btn-radius-md";
            const variantClassBtn = `pb-btn-variant-${variant}-${colorPalette}`;

            const buttonStyle: CSSProperties = {};
            if (buttonProps.fontSizeCustom && buttonProps.fontSizeCustom.trim() !== "") {
              buttonStyle.fontSize = buttonProps.fontSizeCustom;
            }
            if (buttonProps.lineHeightCustom && buttonProps.lineHeightCustom.trim() !== "") {
              buttonStyle.lineHeight = buttonProps.lineHeightCustom;
            }
            if (buttonProps.letterSpacingCustom && buttonProps.letterSpacingCustom.trim() !== "") {
              buttonStyle.letterSpacing = buttonProps.letterSpacingCustom;
            }
            if (buttonProps.fillColorCustom && buttonProps.fillColorCustom.trim() !== "") {
              buttonStyle.backgroundColor = buttonProps.fillColorCustom;
            }
            if (buttonProps.strokeColorCustom && buttonProps.strokeColorCustom.trim() !== "") {
              buttonStyle.borderColor = buttonProps.strokeColorCustom;
            }
            if (buttonProps.textColorCustom && buttonProps.textColorCustom.trim() !== "") {
              buttonStyle.color = buttonProps.textColorCustom;
            }

            return (
              <div className={fullWidth ? "w-full" : "inline-block"}>
                <button
                  type="button"
                  className={`pb-btn-base ${sizeClassBtn} ${radiusClassBtn} ${variantClassBtn} w-full`}
                  style={buttonStyle}
                  aria-label={buttonProps.label}
                  onClick={(e) => {
                    // 에디터 내 버튼 클릭은 선택만 유지하고 실제 이동은 막는다.
                    e.stopPropagation();
                  }}
                >
                  <span className="whitespace-pre-wrap">{buttonProps.label}</span>
                </button>
              </div>
            );
          })()}
          {block.type === "image" && (() => {
            const imageProps = block.props as ImageBlockProps;
            const hasSrc = imageProps.src.trim().length > 0;
            return (
              <div className="w-full border border-dashed border-slate-700 bg-slate-900/40 rounded flex items-center justify-center overflow-hidden">
                {hasSrc ? (
                  // eslint-disable-next-line @next/next/no-img-element
                  <img
                    src={imageProps.src}
                    alt={imageProps.alt}
                    className="max-w-full h-auto object-contain"
                  />
                ) : (
                  <span className="text-[10px] text-slate-500 px-2 py-4">
                    이미지 URL을 속성 패널에서 입력하세요.
                  </span>
                )}
              </div>
            );
          })()}
          {block.type === "divider" && (() => {
            const dividerProps = block.props as DividerBlockProps;
            const thicknessClass = dividerProps.thickness === "medium" ? "border-t-2" : "border-t";

            return (
              <div className="w-full py-2">
                <div className={`border-slate-700 ${thicknessClass}`} />
              </div>
            );
          })()}
          {block.type === "list" && (() => {
            const listProps = block.props as ListBlockProps;
            const items = listProps.items && listProps.items.length > 0 ? listProps.items : ["리스트 아이템 1"];
            const ListTag = (listProps.ordered ? "ol" : "ul") as "ol" | "ul";

            return (
              <div className="w-full py-1">
                <ListTag className="list-inside list-disc space-y-1 text-xs">
                  {items.map((item, index) => (
                    <li key={index}>{item}</li>
                  ))}
                </ListTag>
              </div>
            );
          })()}
          {block.type === "section" && (() => {
            const sectionProps = block.props as SectionBlockProps;

            const bgClass =
              sectionProps.background === "muted"
                ? "bg-slate-950/40"
                : sectionProps.background === "primary"
                ? "bg-sky-950/40 border-sky-900/60"
                : "bg-slate-900/60";

            const pyClass =
              sectionProps.paddingY === "sm"
                ? "py-4"
                : sectionProps.paddingY === "lg"
                ? "py-10"
                : "py-6";

            const columns = sectionProps.columns && sectionProps.columns.length > 0
              ? sectionProps.columns
              : [{ id: `${block.id}_col_fallback`, span: 12 }];

            return (
              <div className={`w-full ${bgClass} ${pyClass} rounded border border-dashed border-slate-700`}
              >
                <div className="flex gap-3">
                  {columns.map((col) => {
                    const basis = `${(col.span / 12) * 100}%`;
                    const columnBlocks = allBlocks.filter(
                      (b) => b.sectionId === block.id && b.columnId === col.id,
                    );
                    const droppableId = `column:${block.id}:${col.id}`;
                    return (
                      <ColumnDroppable
                        key={col.id}
                        id={droppableId}
                        sectionId={block.id}
                        columnId={col.id}
                      >
                        <div
                          className="border border-slate-800/80 bg-slate-950/40 rounded flex flex-col gap-2 items-stretch justify-start px-2 py-2"
                          style={{ flexBasis: basis }}
                        >
                          {columnBlocks.length === 0 ? (
                            <span className="text-[11px] text-slate-500 px-2 text-center">
                              컬럼 영역 (span {col.span}/12)
                            </span>
                          ) : (
                            <div className="flex flex-col gap-2">
                              {renderBlocks(columnBlocks)}
                            </div>
                          )}
                        </div>
                      </ColumnDroppable>
                    );
                  })}
                </div>
              </div>
            );
          })()}
        </div>
      </div>
    </div>
  );
}

interface ColumnDroppableProps {
  id: string;
  sectionId: string;
  columnId: string;
  children: ReactNode;
}

function ColumnDroppable({ id, sectionId, columnId, children }: ColumnDroppableProps) {
  const { setNodeRef } = useDroppable({ id });

  return (
    <div
      ref={setNodeRef}
      data-section-id={sectionId}
      data-column-id={columnId}
      className="flex-1"
    >
      {children}
    </div>
  );
}
