"use client";

import { useCallback, useEffect, useRef, useState, useTransition } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
  DndContext,
  closestCenter,
  KeyboardSensor,
  PointerSensor,
  TouchSensor,
  useSensor,
  useSensors,
  type DragEndEvent,
} from "@dnd-kit/core";
import {
  SortableContext,
  sortableKeyboardCoordinates,
  verticalListSortingStrategy,
  arrayMove,
} from "@dnd-kit/sortable";
import {
  ArrowLeft,
  Eye,
  LayoutGrid,
  Settings2,
  Layers,
  PartyPopper,
} from "lucide-react";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { Badge } from "@/components/ui/Badge";
import { QuestionLibrary } from "@/components/features/surveys/QuestionLibrary";
import { SortableQuestionCard } from "@/components/features/surveys/QuestionCard";
import { QuestionSettingsPanel } from "@/components/features/surveys/QuestionSettingsPanel";
import { SurveyLayoutPanel } from "@/components/features/surveys/SurveyLayoutPanel";
import { SurveyThankYouPanel } from "@/components/features/surveys/SurveyThankYouPanel";
import { useDebouncedAutosave } from "@/hooks/useDebouncedAutosave";
import {
  addQuestion,
  updateQuestion,
  deleteQuestion,
  reorderQuestions,
  upsertLogicRule,
  updateSurveyWelcome,
  updateSurveyDisplay,
  updateSurveyThankYou,
} from "@/lib/actions/questions";
import { publishSurvey, transitionSurveyStatus } from "@/lib/actions/surveys";
import { swalConfirm, swalAlert } from "@/lib/utils/swal";
import { QUESTION_TYPES } from "@/config/question-types";
import type { BuilderSurvey, BuilderQuestion } from "@/types/builder";
import type { QuestionType, SurveyStatus, QuestionOptionsConfig, QuestionValidationConfig } from "@/types";
import { parseJsonColumn } from "@/lib/utils/parse-json-column";
import {
  DEFAULT_DISPLAY_CONFIG,
  normalizeDisplayConfig,
  splitQuestionsByPageBreaks,
} from "@/lib/utils/survey-display";
import { normalizeThankYouConfig } from "@/lib/utils/thank-you-screen";
import { cn } from "@/lib/utils/cn";

type MobilePanel = "canvas" | "library" | "settings";

function normalizeBuilderQuestions(questions: BuilderQuestion[]): BuilderQuestion[] {
  return questions.map((q) => ({
    ...q,
    optionsConfig: parseJsonColumn<QuestionOptionsConfig>(q.optionsConfig),
    validationConfig: parseJsonColumn<QuestionValidationConfig>(q.validationConfig),
  }));
}

interface SurveyBuilderProps {
  initialSurvey: BuilderSurvey;
}

export function SurveyBuilder({ initialSurvey }: SurveyBuilderProps) {
  const router = useRouter();
  const [isPending, startTransition] = useTransition();
  const [survey, setSurvey] = useState(initialSurvey);
  const [questions, setQuestions] = useState(() =>
    normalizeBuilderQuestions(initialSurvey.questions)
  );
  const [selectedId, setSelectedId] = useState<string | null>(
    initialSurvey.questions[0]?.id ?? null
  );
  const [mobilePanel, setMobilePanel] = useState<MobilePanel>("canvas");
  const [saveStatus, setSaveStatus] = useState<"saved" | "saving" | "error" | "idle">(
    "idle"
  );
  const [statusPending, startStatusTransition] = useTransition();
  const [statusDropdownOpen, setStatusDropdownOpen] = useState(false);
  const statusRef = useRef<HTMLDivElement>(null);

  // Debounced per-question persistence — prevents rapid keystrokes (e.g. typing
  // into an option field) from firing overlapping server writes that can race
  // and land out of order, silently reverting saved data after a refresh.
  const questionPatchTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
  const questionPendingPatch = useRef<Map<string, Partial<BuilderQuestion>>>(new Map());
  const logicPatchTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
  const logicPendingPatch = useRef<
    Map<
      string,
      {
        conditionType: "equals" | "not_equals" | "any_answer";
        conditionValue: string | null;
        action: "skip_to_question" | "end_survey";
        targetQuestionId: string | null;
      }
    >
  >(new Map());
  const [layoutOpen, setLayoutOpen] = useState(false);
  const [thankYouOpen, setThankYouOpen] = useState(false);
  const thankYouTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const [welcomeTitle, setWelcomeTitle] = useState(
    initialSurvey.welcomeScreenConfig?.title ?? initialSurvey.title
  );
  const [welcomeDesc, setWelcomeDesc] = useState(
    initialSurvey.welcomeScreenConfig?.description ??
      "Help us improve. This survey takes about 2 minutes."
  );

  const selectedQuestion =
    questions.find((q) => q.id === selectedId) ?? null;

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
    useSensor(TouchSensor, {
      activationConstraint: { delay: 200, tolerance: 5 },
    }),
    useSensor(KeyboardSensor, {
      coordinateGetter: sortableKeyboardCoordinates,
    })
  );

  const displayConfig = normalizeDisplayConfig(
    survey.displayConfig ?? DEFAULT_DISPLAY_CONFIG
  );

  const thankYouConfig = normalizeThankYouConfig(survey.thankYouScreenConfig);

  const persistThankYou = useCallback(
    (next: ReturnType<typeof normalizeThankYouConfig>) => {
      setSurvey((s) => ({ ...s, thankYouScreenConfig: next }));
      setSaveStatus("saving");
      if (thankYouTimer.current) clearTimeout(thankYouTimer.current);
      thankYouTimer.current = setTimeout(() => {
        void (async () => {
          try {
            const result = await updateSurveyThankYou({
              surveyId: survey.id,
              emoji: next.emoji,
              title: next.title ?? "Thank you!",
              description: next.description,
              variants: next.variants,
            });
            if (!result.success) {
              setSaveStatus("error");
              setTimeout(() => setSaveStatus("idle"), 4000);
              return;
            }
            setSaveStatus("saved");
            setTimeout(() => setSaveStatus("idle"), 2000);
          } catch {
            setSaveStatus("error");
            setTimeout(() => setSaveStatus("idle"), 4000);
          }
        })();
      }, 600);
    },
    [survey.id]
  );

  const persistDisplay = useCallback(
    async (next: typeof displayConfig) => {
      const previous = normalizeDisplayConfig(
        survey.displayConfig ?? DEFAULT_DISPLAY_CONFIG
      );
      setSurvey((s) => ({ ...s, displayConfig: next }));
      setSaveStatus("saving");
      try {
        const result = await updateSurveyDisplay({
          surveyId: survey.id,
          layout: next.layout,
          pageBreakAfterIds: next.pageBreakAfterIds,
        });
        if (!result.success) {
          setSurvey((s) => ({
            ...s,
            displayConfig: previous,
          }));
          setSaveStatus("error");
          setTimeout(() => setSaveStatus("idle"), 4000);
          return;
        }
        setSaveStatus("saved");
        setTimeout(() => setSaveStatus("idle"), 2000);
      } catch {
        setSurvey((s) => ({
          ...s,
          displayConfig: previous,
        }));
        setSaveStatus("error");
        setTimeout(() => setSaveStatus("idle"), 4000);
      }
    },
    [survey.id, survey.displayConfig]
  );

  const persistWelcome = useCallback(async () => {
    setSaveStatus("saving");
    try {
      await updateSurveyWelcome({
        surveyId: survey.id,
        title: welcomeTitle.trim() || "Untitled Survey",
        description: welcomeDesc,
      });
      setSurvey((s) => ({ ...s, title: welcomeTitle }));
      setSaveStatus("saved");
      setTimeout(() => setSaveStatus("idle"), 2000);
    } catch {
      setSaveStatus("error");
      setTimeout(() => setSaveStatus("idle"), 4000);
    }
  }, [survey.id, welcomeTitle, welcomeDesc]);

  useDebouncedAutosave(persistWelcome, [welcomeTitle, welcomeDesc], 1500);

  function flushThankYouSave() {
    if (!thankYouTimer.current) return;
    clearTimeout(thankYouTimer.current);
    thankYouTimer.current = null;
    const next = normalizeThankYouConfig(survey.thankYouScreenConfig);
    void updateSurveyThankYou({
      surveyId: survey.id,
      emoji: next.emoji,
      title: next.title ?? "Thank you!",
      description: next.description,
      variants: next.variants,
    });
  }

  function handlePreview() {
    // Make sure any in-progress edit is written before showing the live preview.
    if (selectedId) flushAllForQuestion(selectedId);
    flushThankYouSave();
    window.open(`/app/surveys/${survey.id}/preview`, "_blank");
  }

  async function handleAddQuestion(type: QuestionType) {
    const config = QUESTION_TYPES.find((q) => q.type === type);
    startTransition(async () => {
      const result = await addQuestion(survey.id, type);
      if (result.success && result.data) {
        const newQ: BuilderQuestion = {
          id: result.data.questionId,
          type,
          title: config?.defaultTitle ?? "New question",
          position: questions.length,
          isRequired: false,
          validationConfig: null,
          optionsConfig: (config?.defaultOptions ?? null) as BuilderQuestion["optionsConfig"],
          randomizeOptions: false,
          logicRules: [],
        };
        setQuestions((prev) => [...prev, newQ]);
        setSelectedId(result.data.questionId);
        setMobilePanel("canvas");
      }
    });
  }

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

    setQuestions((items) => {
      const oldIndex = items.findIndex((q) => q.id === active.id);
      const newIndex = items.findIndex((q) => q.id === over.id);
      const reordered = arrayMove(items, oldIndex, newIndex).map((q, i) => ({
        ...q,
        position: i,
      }));

      startTransition(async () => {
        await reorderQuestions(
          survey.id,
          reordered.map((q) => q.id)
        );
      });

      return reordered;
    });
  }

  /** Send the latest merged patch for a question to the server, right now. */
  const flushQuestionSave = useCallback(
    (questionId: string) => {
      const timer = questionPatchTimers.current.get(questionId);
      if (timer) {
        clearTimeout(timer);
        questionPatchTimers.current.delete(questionId);
      }
      const patch = questionPendingPatch.current.get(questionId);
      if (!patch) return;
      questionPendingPatch.current.delete(questionId);

      startTransition(async () => {
        const result = await updateQuestion({
          surveyId: survey.id,
          questionId,
          ...patch,
        });
        if (result.success) {
          setSaveStatus("saved");
          setTimeout(() => setSaveStatus("idle"), 2000);
        } else {
          setSaveStatus("error");
          setTimeout(() => setSaveStatus("idle"), 4000);
        }
      });
    },
    [survey.id]
  );

  function handleQuestionUpdate(
    questionId: string,
    patch: Partial<BuilderQuestion>
  ) {
    // Optimistic local update — UI always reflects the latest edit instantly.
    setQuestions((prev) =>
      prev.map((q) => (q.id === questionId ? { ...q, ...patch } : q))
    );
    setSaveStatus("saving");

    // Merge into whatever is already pending for this question, then debounce
    // the actual network write so a burst of edits (e.g. typing an option)
    // collapses into a single save with the final value.
    const merged = {
      ...(questionPendingPatch.current.get(questionId) ?? {}),
      ...patch,
    };
    questionPendingPatch.current.set(questionId, merged);

    const existingTimer = questionPatchTimers.current.get(questionId);
    if (existingTimer) clearTimeout(existingTimer);
    questionPatchTimers.current.set(
      questionId,
      setTimeout(() => flushQuestionSave(questionId), 700)
    );
  }

  function handleDeleteQuestion(questionId: string) {
    void (async () => {
      const ok = await swalConfirm({
        title: "Delete this question?",
        text: "This action cannot be undone.",
        confirmText: "Delete",
        cancelText: "Cancel",
        danger: true,
      });
      if (!ok) return;

      // Discard any pending debounced saves — the question is gone, don't write to it.
      const qTimer = questionPatchTimers.current.get(questionId);
      if (qTimer) clearTimeout(qTimer);
      questionPatchTimers.current.delete(questionId);
      questionPendingPatch.current.delete(questionId);
      const lTimer = logicPatchTimers.current.get(questionId);
      if (lTimer) clearTimeout(lTimer);
      logicPatchTimers.current.delete(questionId);
      logicPendingPatch.current.delete(questionId);

      startTransition(async () => {
        await deleteQuestion(survey.id, questionId);
        setQuestions((prev) =>
          prev
            .filter((q) => q.id !== questionId)
            // Mirror the DB's ON DELETE SET NULL: any rule that skipped to the
            // now-deleted question should fall back to "next question" locally too.
            .map((q) =>
              q.logicRules[0]?.targetQuestionId === questionId
                ? {
                    ...q,
                    logicRules: [{ ...q.logicRules[0], targetQuestionId: null }],
                  }
                : q
            )
        );
        const nextBreaks = displayConfig.pageBreakAfterIds.filter(
          (id) => id !== questionId
        );
        if (nextBreaks.length !== displayConfig.pageBreakAfterIds.length) {
          void persistDisplay({
            ...displayConfig,
            pageBreakAfterIds: nextBreaks,
          });
        }
        const nextVariants = (thankYouConfig.variants ?? []).filter(
          (v) => v.questionId !== questionId
        );
        if (nextVariants.length !== (thankYouConfig.variants ?? []).length) {
          persistThankYou({ ...thankYouConfig, variants: nextVariants });
        }
        if (selectedId === questionId) {
          setSelectedId(questions.find((q) => q.id !== questionId)?.id ?? null);
        }
      });
    })();
  }

  /** Send the latest branching-logic rule for a question to the server, right now. */
  const flushLogicSave = useCallback(
    (questionId: string) => {
      const timer = logicPatchTimers.current.get(questionId);
      if (timer) {
        clearTimeout(timer);
        logicPatchTimers.current.delete(questionId);
      }
      const logic = logicPendingPatch.current.get(questionId);
      if (!logic) return;
      logicPendingPatch.current.delete(questionId);

      startTransition(async () => {
        const result = await upsertLogicRule({
          surveyId: survey.id,
          questionId,
          ...logic,
        });
        if (!result.success) {
          setSaveStatus("error");
          setTimeout(() => setSaveStatus("idle"), 4000);
        }
      });
    },
    [survey.id]
  );

  function handleLogicUpdate(
    questionId: string,
    logic: {
      conditionType: "equals" | "not_equals" | "any_answer";
      conditionValue: string | null;
      action: "skip_to_question" | "end_survey";
      targetQuestionId: string | null;
    }
  ) {
    // Optimistic local update — the branching-logic UI reflects the choice instantly.
    setQuestions((prev) =>
      prev.map((q) =>
        q.id === questionId
          ? {
              ...q,
              logicRules: [
                {
                  id: q.logicRules[0]?.id ?? "temp",
                  ...logic,
                },
              ],
            }
          : q
      )
    );

    // Debounce the write per-question so quickly changing "If / Then / Skip to"
    // in succession can't race and leave a half-applied rule in the database.
    logicPendingPatch.current.set(questionId, logic);
    const existingTimer = logicPatchTimers.current.get(questionId);
    if (existingTimer) clearTimeout(existingTimer);
    // Dropdowns aren't typed — persist quickly so Preview / fill sees the rule.
    logicPatchTimers.current.set(
      questionId,
      setTimeout(() => flushLogicSave(questionId), 150)
    );
  }

  /** Flush any pending debounced saves for a question immediately (e.g. before switching away). */
  const flushAllForQuestion = useCallback(
    (questionId: string) => {
      flushQuestionSave(questionId);
      flushLogicSave(questionId);
    },
    [flushQuestionSave, flushLogicSave]
  );

  /** Select a question, first flushing any unsaved edits on the previously selected one. */
  const selectQuestion = useCallback(
    (id: string | null) => {
      if (selectedId && selectedId !== id) {
        flushAllForQuestion(selectedId);
      }
      setSelectedId(id);
    },
    [selectedId, flushAllForQuestion]
  );

  // Flush every pending save on unmount (e.g. navigating away) so no edit is lost.
  useEffect(() => {
    return () => {
      questionPendingPatch.current.forEach((_, id) => flushQuestionSave(id));
      logicPendingPatch.current.forEach((_, id) => flushLogicSave(id));
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  function handleStatusChange(next: SurveyStatus) {
    setStatusDropdownOpen(false);
    if (next === survey.status) return;

    // Publishing/unpublishing should never race with an in-progress edit.
    if (selectedId) flushAllForQuestion(selectedId);

    const confirmOptions: Record<SurveyStatus, { title: string; text: string; confirmText: string; danger?: boolean }> = {
      draft: {
        title: "Unpublish this survey?",
        text: "It will stop collecting responses and go back to draft.",
        confirmText: "Unpublish",
        danger: true,
      },
      closed: {
        title: "Close this survey?",
        text: "It will permanently stop accepting new responses.",
        confirmText: "Close Survey",
        danger: true,
      },
      active: {
        title: survey.status === "draft" ? "Publish this survey?" : "Reopen this survey?",
        text: survey.status === "draft"
          ? "It will go live and start collecting responses."
          : "It will start accepting responses again.",
        confirmText: survey.status === "draft" ? "Publish" : "Reopen",
      },
    };

    void (async () => {
      const opts = confirmOptions[next];
      const ok = await swalConfirm(opts);
      if (!ok) return;

      startStatusTransition(async () => {
        let result;
        if (next === "active" && survey.status === "draft") {
          result = await publishSurvey(survey.id);
          if (result.success) {
            setSurvey((s) => ({ ...s, status: "active" }));
            router.push(`/app/surveys/${survey.id}/distribute`);
            router.refresh();
            return;
          }
        } else {
          result = await transitionSurveyStatus(survey.id, next);
          if (result.success) {
            setSurvey((s) => ({ ...s, status: next }));
            router.refresh();
          }
        }
        if (!result.success) {
          void swalAlert(result.error ?? "Failed to change status", undefined, "error");
        }
      });
    })();
  }

  const saveLabel =
    saveStatus === "saving"
      ? "Saving…"
      : saveStatus === "saved"
        ? "✓ All changes saved"
        : saveStatus === "error"
          ? "⚠ Save failed — check connection"
          : "All changes saved";

  return (
    <div className="flex h-dvh max-h-dvh flex-col overflow-hidden bg-bg">
      {/* Top bar */}
      <header className="flex shrink-0 flex-wrap items-center justify-between gap-3 border-b border-border bg-white px-4 py-3 sm:px-[26px]">
        <div className="flex min-w-0 items-center gap-3">
          <Link href="/app/surveys">
            <Button variant="icon" size="icon" type="button">
              <ArrowLeft className="h-[15px] w-[15px]" />
            </Button>
          </Link>
          <div className="min-w-0">
            <h1 className="truncate text-sm font-bold sm:text-[14.5px]">
              {welcomeTitle}
            </h1>
            <div className={cn(
              "text-[11px]",
              saveStatus === "error" ? "text-danger" : "text-muted"
            )}>
              {saveLabel}
            </div>
          </div>
        </div>
        <div className="flex items-center gap-2">
          <Button
            variant="outline"
            size="sm"
            type="button"
            onClick={() => setLayoutOpen(true)}
            title="Choose one question per page, all on one page, or custom pages"
          >
            <LayoutGrid className="mr-1 h-3.5 w-3.5" />
            <span className="hidden sm:inline">
              {displayConfig.layout === "single_page"
                ? "All on one page"
                : displayConfig.layout === "grouped"
                  ? "Custom pages"
                  : "One per page"}
            </span>
            <span className="sm:hidden">Layout</span>
          </Button>
          <Button
            variant="outline"
            size="sm"
            type="button"
            onClick={() => setThankYouOpen(true)}
            title="Customize the thank-you screen, including different messages for good or bad answers"
          >
            <PartyPopper className="mr-1 h-3.5 w-3.5" />
            <span className="hidden sm:inline">End screen</span>
            <span className="sm:hidden">End</span>
          </Button>
          <Button
            variant="outline"
            size="sm"
            type="button"
            onClick={handlePreview}
            title="Open live preview in new tab"
          >
            <Eye className="mr-1 h-3.5 w-3.5" />
            <span className="hidden sm:inline">Preview</span>
          </Button>

          {/* Status Control */}
          <div className="relative" ref={statusRef}>
            <button
              type="button"
              disabled={statusPending}
              onClick={() => setStatusDropdownOpen((o) => !o)}
              className="flex items-center gap-1.5 rounded-[7px] border border-border bg-white px-2.5 py-1.5 text-xs font-semibold transition-colors hover:bg-[#F1F5F9] disabled:opacity-50"
            >
              <Badge status={survey.status as SurveyStatus} />
              <svg className={cn("h-3 w-3 text-muted transition-transform", statusDropdownOpen && "rotate-180")} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
                <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
              </svg>
            </button>

            {statusDropdownOpen && (
              <>
                {/* Backdrop */}
                <button
                  type="button"
                  aria-label="Close"
                  className="fixed inset-0 z-10"
                  onClick={() => setStatusDropdownOpen(false)}
                />
                {/* Dropdown */}
                <div className="absolute right-0 top-full z-20 mt-1 w-52 overflow-hidden rounded-[10px] border border-border bg-white shadow-lg">
                  <div className="px-3 py-2 text-[10.5px] font-bold uppercase tracking-wider text-muted">
                    Change Status
                  </div>

                  {survey.status !== "active" && (
                    <button
                      type="button"
                      onClick={() => handleStatusChange("active")}
                      className="flex w-full items-center gap-2.5 px-3 py-2.5 text-left text-sm hover:bg-[#F8FAFC]"
                    >
                      <span className="flex h-2 w-2 rounded-full bg-success" />
                      <div>
                        <div className="font-semibold text-navy">
                          {survey.status === "draft" ? "Publish" : "Reopen"}
                        </div>
                        <div className="text-[11px] text-muted">
                          {survey.status === "draft"
                            ? "Go live — collect responses"
                            : "Start accepting responses again"}
                        </div>
                      </div>
                    </button>
                  )}

                  {survey.status === "active" && (
                    <button
                      type="button"
                      onClick={() => handleStatusChange("draft")}
                      className="flex w-full items-center gap-2.5 px-3 py-2.5 text-left text-sm hover:bg-[#F8FAFC]"
                    >
                      <span className="flex h-2 w-2 rounded-full bg-muted" />
                      <div>
                        <div className="font-semibold text-navy">Unpublish</div>
                        <div className="text-[11px] text-muted">
                          Back to draft — pause responses
                        </div>
                      </div>
                    </button>
                  )}

                  {survey.status !== "closed" && (
                    <button
                      type="button"
                      onClick={() => handleStatusChange("closed")}
                      className="flex w-full items-center gap-2.5 px-3 py-2.5 text-left text-sm hover:bg-[#FFF5F5]"
                    >
                      <span className="flex h-2 w-2 rounded-full bg-danger" />
                      <div>
                        <div className="font-semibold text-danger">Close Survey</div>
                        <div className="text-[11px] text-muted">
                          Stop accepting responses permanently
                        </div>
                      </div>
                    </button>
                  )}

                  <div className="border-t border-border px-3 py-2 text-[10.5px] text-muted">
                    Current:{" "}
                    <span className="font-semibold capitalize">{survey.status}</span>
                  </div>
                </div>
              </>
            )}
          </div>
        </div>
      </header>

      {/* Desktop 3-column layout — each pane scrolls on its own */}
      <div className="hidden min-h-0 flex-1 overflow-hidden lg:grid lg:grid-cols-[230px_minmax(0,1fr)_270px] lg:border-t lg:border-border">
        <div className="min-h-0 overflow-y-auto overflow-x-hidden overscroll-contain border-r border-border">
          <QuestionLibrary onAdd={handleAddQuestion} disabled={isPending} />
        </div>

        <BuilderCanvas
          welcomeTitle={welcomeTitle}
          welcomeDesc={welcomeDesc}
          onWelcomeTitleChange={setWelcomeTitle}
          onWelcomeDescChange={setWelcomeDesc}
          questions={questions}
          selectedId={selectedId}
          onSelect={selectQuestion}
          onDelete={handleDeleteQuestion}
          sensors={sensors}
          onDragEnd={handleDragEnd}
          displayConfig={displayConfig}
        />

        <div className="min-h-0 overflow-y-auto overflow-x-hidden overscroll-contain border-l border-border">
          <QuestionSettingsPanel
            question={selectedQuestion}
            allQuestions={questions}
            onUpdate={(patch) =>
              selectedId && handleQuestionUpdate(selectedId, patch)
            }
            onLogicUpdate={(logic) =>
              selectedId && handleLogicUpdate(selectedId, logic)
            }
          />
        </div>
      </div>

      {/* Mobile layout */}
      <div className="flex min-h-0 flex-1 flex-col overflow-hidden lg:hidden">
        {mobilePanel === "library" && (
          <div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain">
            <QuestionLibrary
              onAdd={handleAddQuestion}
              disabled={isPending}
            />
          </div>
        )}

        {mobilePanel === "canvas" && (
          <BuilderCanvas
            welcomeTitle={welcomeTitle}
            welcomeDesc={welcomeDesc}
            onWelcomeTitleChange={setWelcomeTitle}
            onWelcomeDescChange={setWelcomeDesc}
            questions={questions}
            selectedId={selectedId}
            onSelect={(id) => {
              selectQuestion(id);
              setMobilePanel("settings");
            }}
            onDelete={handleDeleteQuestion}
            sensors={sensors}
            onDragEnd={handleDragEnd}
            className="min-h-0 flex-1"
            displayConfig={displayConfig}
          />
        )}

        {mobilePanel === "settings" && (
          <div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain">
            <QuestionSettingsPanel
              question={selectedQuestion}
              allQuestions={questions}
              onUpdate={(patch) =>
                selectedId && handleQuestionUpdate(selectedId, patch)
              }
              onLogicUpdate={(logic) =>
                selectedId && handleLogicUpdate(selectedId, logic)
              }
            />
          </div>
        )}

        {/* Mobile bottom nav */}
        <nav className="flex shrink-0 border-t border-border bg-white">
          {(
            [
              { id: "library" as const, label: "Library", icon: Layers },
              { id: "canvas" as const, label: "Canvas", icon: LayoutGrid },
              { id: "settings" as const, label: "Settings", icon: Settings2 },
            ] as const
          ).map(({ id, label, icon: Icon }) => (
            <button
              key={id}
              type="button"
              onClick={() => setMobilePanel(id)}
              className={cn(
                "flex flex-1 flex-col items-center gap-1 py-3 text-[11px] font-semibold transition-colors",
                mobilePanel === id
                  ? "text-primary"
                  : "text-muted hover:text-text"
              )}
            >
              <Icon className="h-5 w-5" strokeWidth={2} />
              {label}
            </button>
          ))}
        </nav>
      </div>

      <SurveyLayoutPanel
        open={layoutOpen}
        onClose={() => setLayoutOpen(false)}
        questions={questions}
        displayConfig={displayConfig}
        onSave={(next) => void persistDisplay(next)}
        saving={saveStatus === "saving"}
      />
      <SurveyThankYouPanel
        open={thankYouOpen}
        onClose={() => {
          flushThankYouSave();
          setThankYouOpen(false);
        }}
        questions={questions}
        config={thankYouConfig}
        onChange={persistThankYou}
        saving={saveStatus === "saving"}
      />
    </div>
  );
}

interface BuilderCanvasProps {
  welcomeTitle: string;
  welcomeDesc: string;
  onWelcomeTitleChange: (v: string) => void;
  onWelcomeDescChange: (v: string) => void;
  questions: BuilderQuestion[];
  selectedId: string | null;
  onSelect: (id: string) => void;
  onDelete: (id: string) => void;
  sensors: ReturnType<typeof useSensors>;
  onDragEnd: (event: DragEndEvent) => void;
  className?: string;
  displayConfig: ReturnType<typeof normalizeDisplayConfig>;
}

function BuilderCanvas({
  welcomeTitle,
  welcomeDesc,
  onWelcomeTitleChange,
  onWelcomeDescChange,
  questions,
  selectedId,
  onSelect,
  onDelete,
  sensors,
  onDragEnd,
  className,
  displayConfig,
}: BuilderCanvasProps) {
  const pages =
    displayConfig.layout === "grouped"
      ? splitQuestionsByPageBreaks(questions, displayConfig.pageBreakAfterIds)
      : [questions];
  const pageByQuestionId = new Map<string, number>();
  pages.forEach((page, idx) => {
    page.forEach((q) => pageByQuestionId.set(q.id, idx));
  });

  return (
    <div
      className={cn(
        "min-h-0 overflow-y-auto overflow-x-hidden overscroll-contain bg-[#F8FAFC]",
        "bg-[linear-gradient(#EDF2F7_1px,transparent_1px),linear-gradient(90deg,#EDF2F7_1px,transparent_1px)]",
        "bg-[length:100%_26px,26px_100%]",
        className
      )}
    >
      <div className="mx-auto w-full max-w-[560px] px-4 py-6 sm:px-[30px] sm:py-[30px]">
        <div className="mb-4 rounded-xl border border-border border-t-4 border-t-primary bg-white p-5 shadow sm:p-[22px]">
          <Input
            value={welcomeTitle}
            onChange={(e) => onWelcomeTitleChange(e.target.value)}
            className="mb-1.5 border-none p-0 text-lg font-extrabold shadow-none focus:ring-0 sm:text-[19px]"
            placeholder="Survey title"
          />
          <Input
            value={welcomeDesc}
            onChange={(e) => onWelcomeDescChange(e.target.value)}
            className="border-none p-0 text-[13px] text-muted shadow-none focus:ring-0"
            placeholder="Survey description"
          />
        </div>

        {displayConfig.layout !== "one_per_page" && (
          <p className="mb-3 rounded-[10px] border border-border bg-white px-3 py-2 text-[12px] text-muted">
            {displayConfig.layout === "single_page"
              ? "Respondents will see every question on one page. Skip logic still hides questions."
              : "Respondents move page by page. Use the Layout button to choose how many questions sit on each page."}
          </p>
        )}

        <DndContext
          sensors={sensors}
          collisionDetection={closestCenter}
          onDragEnd={onDragEnd}
        >
          <SortableContext
            items={questions.map((q) => q.id)}
            strategy={verticalListSortingStrategy}
          >
            {questions.map((q, i) => {
              const pageIdx = pageByQuestionId.get(q.id) ?? 0;
              const isPageStart =
                displayConfig.layout === "grouped" &&
                questions[i - 1] &&
                pageByQuestionId.get(questions[i - 1].id) !== pageIdx;
              return (
                <div key={q.id}>
                  {(i === 0 && displayConfig.layout === "grouped") || isPageStart ? (
                    <div className="mb-2 mt-1 flex items-center gap-2">
                      <span className="rounded-full bg-primary/10 px-2.5 py-0.5 text-[11px] font-bold text-primary">
                        Page {pageIdx + 1}
                      </span>
                      <span className="h-px flex-1 bg-border" />
                    </div>
                  ) : null}
                  <SortableQuestionCard
                    question={q}
                    index={i}
                    selected={selectedId === q.id}
                    onSelect={() => onSelect(q.id)}
                    onDelete={() => onDelete(q.id)}
                  />
                </div>
              );
            })}
          </SortableContext>
        </DndContext>

        {questions.length === 0 && (
          <p className="py-16 text-center text-[13.5px] text-muted">
            Tap a block in the library to add your first question →
          </p>
        )}
      </div>
    </div>
  );
}
