"use client";

import Link from "next/link";
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";
import { usePathname } from "next/navigation";
import type { PublicCopy } from "@/content/home";
import type { LandingCMS } from "@/lib/cms/landing";
import {
  LANDING_SECTION_CATALOG,
} from "@/lib/cms/landing-section-schema";
import type {
  LandingCarouselSlide,
  LandingPageSection,
  LandingPageType,
} from "@/lib/cms/landing-section-schema";
import BlogContent from "@/components/blog/BlogContent";
import {
  createEmptyLandingCarouselContent,
  LANDING_CAROUSEL_RATIOS,
  landingVideoEmbedUrl,
  parseLandingCarouselContent,
} from "@/lib/cms/landing-section-schema";

type LandingMediaAsset = {
  id: number;
  originalName: string;
  mimeType: string;
  url: string;
};

type EditablePath =
  | `hero.${keyof LandingCMS["hero"]}`
  | `publicCopy.${keyof PublicCopy}`
  | `actions.${number}.${"title" | "description" | "cta"}`
  | `blogs.${number}.${"title" | "excerpt" | "content" | "category"}`
  | `events.${number}.${"title" | "type" | "description" | "location" | "audience" | "series"}`
  | `eventSections.${string}.${"title" | "content"}`
  | `landingSections.${string}.${"title" | "content"}`
  | `volunteerLanding.${"title" | "description"}`
  | `volunteerCopy.${string}`;

type BlogRecord = {
  id: number;
  slug: string;
  title: string;
  excerpt: string;
  content: string;
  image: string;
  category: string;
  published: boolean;
  publishedAt: string | null;
  pixelEnabled: boolean;
  pixelId: string;
};

type EventRecord = {
  id: number;
  slug: string;
  title: string;
  type: string;
  description: string;
  location: string;
  audience: string;
  series: string;
  date: string | null;
  endDate: string | null;
  startTime: string;
  endTime: string;
  mode: "offline" | "online" | "hybrid";
  meetingLink: string;
  image: string;
  published: boolean;
  eventStatus: string;
  isSample: boolean;
  capacity: number | null;
  isPaid: boolean;
  priceRupiah: number;
  pinned: boolean;
  pixelEnabled: boolean;
  pixelId: string;
};

type VolunteerLandingRecord = {
  customPath: string;
  title: string;
  description: string;
  content: string;
  image: string;
  published: boolean;
  updatedAt: string | null;
};

type EventPageCMS = {
  slug: string;
  sections: Array<{
    id: string;
    type: string;
    title: string;
    content: string;
    enabled: boolean;
  }>;
  copy: Record<string, string>;
};

type InlineCMSData = {
  landing: LandingCMS;
  blogs: BlogRecord[];
  events: EventRecord[];
  eventPage: EventPageCMS | null;
  volunteerLanding: VolunteerLandingRecord | null;
  pageSections: LandingPageSection[];
  pageSectionsScope: string | null;
  pageSectionType: LandingPageType | null;
  pageSectionKey: string;
};

type EditorContextValue = {
  data: InlineCMSData | null;
  editing: boolean;
  session: number;
  getValue: (path: EditablePath) => string | undefined;
  getSavedValue: (path: EditablePath) => string | undefined;
  setValue: (path: EditablePath, value: string) => void;
  setPageSections: (sections: LandingPageSection[]) => void;
};

const LandingEditorContext = createContext<EditorContextValue | null>(null);

export function useLandingEditor() {
  return useContext(LandingEditorContext);
}

function parseVolunteerCopy(value: string): Record<string, unknown> | null {
  try {
    const parsed: unknown = JSON.parse(value);
    return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
      ? parsed as Record<string, unknown>
      : null;
  } catch {
    return null;
  }
}

function readNestedString(value: unknown, keys: string[]): string | undefined {
  let current = value;
  for (const key of keys) {
    if (Array.isArray(current)) current = current[Number(key)];
    else if (typeof current === "object" && current !== null) {
      current = (current as Record<string, unknown>)[key];
    } else return undefined;
  }
  return typeof current === "string" ? current : undefined;
}

function readValue(data: InlineCMSData | null, path: EditablePath): string | undefined {
  if (!data) return undefined;
  const [section, key, field] = path.split(".");

  if (section === "hero") {
    return data.landing.hero[key as keyof LandingCMS["hero"]];
  }

  if (section === "publicCopy") {
    return data.landing.publicCopy[key as keyof PublicCopy];
  }

  if (section === "volunteerCopy") {
    const copy = parseVolunteerCopy(data.landing.publicCopy.volunteerPageCopyJson);
    return copy ? readNestedString(copy, path.split(".").slice(1)) : undefined;
  }

  if (section === "actions") {
    const action = data.landing.actions.find((item) => item.id === Number(key));
    if (!action || (field !== "title" && field !== "description" && field !== "cta")) {
      return undefined;
    }
    return action[field];
  }

  if (section === "volunteerLanding") {
    if (field !== "title" && field !== "description") return undefined;
    return data.volunteerLanding?.[field];
  }

  if (section === "eventSections" && (field === "title" || field === "content")) {
    return data.eventPage?.sections.find((item) => item.id === key)?.[field];
  }

  if (section === "landingSections" && (field === "title" || field === "content")) {
    return data.pageSections.find((item) => item.id === key)?.[field];
  }

  const records = section === "blogs" ? data.blogs : data.events;
  const record = records.find((item) => item.id === Number(key));
  if (!record || !field || !(field in record)) return undefined;
  const value = record[field as keyof typeof record];
  return typeof value === "string" ? value : undefined;
}

function dateTimeLocalValue(value: string) {
  const timestamp = Date.parse(value);
  if (!Number.isFinite(timestamp)) return "";
  const date = new Date(timestamp - new Date(timestamp).getTimezoneOffset() * 60_000);
  return date.toISOString().slice(0, 16);
}

function updateValue(data: InlineCMSData, path: EditablePath, value: string): InlineCMSData {
  const [section, key, field] = path.split(".");

  if (section === "hero") {
    return {
      ...data,
      landing: { ...data.landing, hero: { ...data.landing.hero, [key]: value } },
    };
  }

  if (section === "landingSections" && (field === "title" || field === "content")) {
    return {
      ...data,
      pageSections: data.pageSections.map((item) =>
        item.id === key ? { ...item, [field]: value } : item,
      ),
    };
  }

  if (section === "publicCopy") {
    return {
      ...data,
      landing: { ...data.landing, publicCopy: { ...data.landing.publicCopy, [key]: value } },
    };
  }

  if (section === "volunteerCopy") {
    const copy = parseVolunteerCopy(data.landing.publicCopy.volunteerPageCopyJson);
    const keys = path.split(".").slice(1);
    if (!copy || keys.length === 0) return data;

    const updated = structuredClone(copy);
    let current: unknown = updated;
    for (const key of keys.slice(0, -1)) {
      if (Array.isArray(current)) current = current[Number(key)];
      else if (typeof current === "object" && current !== null) {
        current = (current as Record<string, unknown>)[key];
      } else return data;
    }
    const lastKey = keys[keys.length - 1];
    if (Array.isArray(current)) current[Number(lastKey)] = value;
    else if (typeof current === "object" && current !== null) {
      (current as Record<string, unknown>)[lastKey] = value;
    } else return data;

    return {
      ...data,
      landing: {
        ...data.landing,
        publicCopy: {
          ...data.landing.publicCopy,
          volunteerPageCopyJson: JSON.stringify(updated),
        },
      },
    };
  }

  if (section === "actions") {
    return {
      ...data,
      landing: {
        ...data.landing,
        actions: data.landing.actions.map((action) =>
          action.id === Number(key) && (field === "title" || field === "description" || field === "cta")
            ? { ...action, [field]: value }
            : action,
        ),
      },
    };
  }

  if (section === "volunteerLanding" && (field === "title" || field === "description")) {
    return {
      ...data,
      volunteerLanding: data.volunteerLanding
        ? { ...data.volunteerLanding, [field]: value }
        : null,
    };
  }

  if (section === "eventSections" && (field === "title" || field === "content")) {
    return {
      ...data,
      eventPage: data.eventPage
        ? {
            ...data.eventPage,
            sections: data.eventPage.sections.map((item) =>
              item.id === key ? { ...item, [field]: value } : item,
            ),
          }
        : null,
    };
  }

  if (section === "blogs" && (field === "title" || field === "excerpt" || field === "content" || field === "category")) {
    return {
      ...data,
      blogs: data.blogs.map((blog) =>
        blog.id === Number(key) ? { ...blog, [field]: value } : blog,
      ),
    };
  }

  if (section === "events" && (field === "title" || field === "type" || field === "description" || field === "location" || field === "audience" || field === "series")) {
    return {
      ...data,
      events: data.events.map((event) =>
        event.id === Number(key) ? { ...event, [field]: value } : event,
      ),
    };
  }

  return data;
}

export function InlineText({
  path,
  value,
  as: Tag = "span",
  id,
  className,
  heroTitle = false,
  multiline = false,
}: {
  path: EditablePath;
  value: string;
  as?: "span" | "h1" | "h2" | "h3" | "p" | "strong";
  id?: string;
  className?: string;
  heroTitle?: boolean;
  multiline?: boolean;
}) {
  const editor = useContext(LandingEditorContext);
  const [activeSession, setActiveSession] = useState<number | null>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const currentValue = editor?.editing ? editor.getValue(path) ?? value : value;
  const savedValue = editor?.getSavedValue(path) ?? value;
  const isEditingField =
    Boolean(editor?.editing) && activeSession === editor?.session;

  useEffect(() => {
    if (isEditingField) {
      if (multiline) textareaRef.current?.focus();
      else inputRef.current?.focus();
    }
  }, [isEditingField, multiline]);

  if (!editor) return <Tag id={id} className={className}>{value}</Tag>;

  const displayValue = heroTitle
    ? (() => {
        const separator = currentValue.indexOf("?");
        if (separator < 0) return currentValue;
        return (
          <>
            {currentValue.slice(0, separator + 1)}
            <br />
            <span className="inline-hero-highlight">
              {currentValue.slice(separator + 1).trim()}
            </span>
          </>
        );
      })()
    : currentValue;

  return (
    <Tag
      id={id}
      className={`${className ?? ""}${editor.editing ? " inline-editable-text" : ""}`}
      data-inline-editor-field={editor.editing ? "" : undefined}
      onClick={(event) => {
        if (!editor.editing) return;
        event.preventDefault();
        event.stopPropagation();
        setActiveSession(editor.session);
      }}
    >
      {isEditingField && editor?.editing ? (
        multiline ? (
          <textarea
            ref={textareaRef}
            aria-label="Edit teks halaman"
            className="inline-editor-input inline-editor-textarea"
            rows={6}
            value={currentValue}
            onChange={(event) => editor.setValue(path, event.target.value)}
            onBlur={() => setActiveSession(null)}
            onKeyDown={(event) => {
              if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) setActiveSession(null);
              if (event.key === "Escape") {
                editor.setValue(path, savedValue);
                setActiveSession(null);
              }
            }}
            onClick={(event) => event.stopPropagation()}
          />
        ) : (
          <input
            ref={inputRef}
            aria-label="Edit teks halaman"
            className="inline-editor-input"
            size={Math.max(8, Math.min(70, currentValue.length || 8))}
            value={currentValue}
            onChange={(event) => editor.setValue(path, event.target.value)}
            onBlur={() => setActiveSession(null)}
            onKeyDown={(event) => {
              if (event.key === "Enter") setActiveSession(null);
              if (event.key === "Escape") {
                editor.setValue(path, savedValue);
                setActiveSession(null);
              }
            }}
            onClick={(event) => event.stopPropagation()}
          />
        )
      ) : (
        displayValue
      )}
    </Tag>
  );
}

export function InlineContent({
  path,
  value,
  label,
  children,
}: {
  path: EditablePath;
  value: string;
  label: string;
  children: ReactNode;
}) {
  const editor = useContext(LandingEditorContext);
  const [activeSession, setActiveSession] = useState<number | null>(null);
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const currentValue = editor?.editing ? editor.getValue(path) ?? value : value;
  const savedValue = editor?.getSavedValue(path) ?? value;
  const isEditingField = Boolean(editor?.editing) && activeSession === editor?.session;

  useEffect(() => {
    if (isEditingField) textareaRef.current?.focus();
  }, [isEditingField]);

  if (!editor || !editor.editing) return <>{children}</>;

  if (isEditingField) {
    return (
      <textarea
        ref={textareaRef}
        aria-label={`Edit isi ${label}`}
        className="inline-editor-input inline-editor-textarea inline-editor-content-input"
        rows={6}
        value={currentValue}
        onChange={(event) => editor.setValue(path, event.target.value)}
        onBlur={() => setActiveSession(null)}
        onKeyDown={(event) => {
          if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) setActiveSession(null);
          if (event.key === "Escape") {
            editor.setValue(path, savedValue);
            setActiveSession(null);
          }
        }}
      />
    );
  }

  const previewContent = path.startsWith("blogs.")
    ? <BlogContent content={currentValue} />
    : children;

  return (
    <div className="inline-editable-content">
      <button
        type="button"
        className="inline-editable-content-action"
        aria-label={`Edit isi ${label}`}
        onClick={() => setActiveSession(editor.session)}
      >
        <span aria-hidden="true">✎</span> Edit isi
      </button>
      <div className="inline-editable-content-preview">
        {currentValue.trim()
          ? previewContent
          : <p className="inline-editable-content-empty">Belum ada isi. Pilih Edit isi untuk mulai menulis.</p>}
      </div>
    </div>
  );
}

export default function LandingInlineEditor({
  children,
}: {
  children: ReactNode;
}) {
  const pathname = usePathname();
  const [isAdmin, setIsAdmin] = useState(false);
  const [visualPreviewMode, setVisualPreviewMode] = useState(false);
  const [embeddedPreviewMode, setEmbeddedPreviewMode] = useState(false);
  const [accessError, setAccessError] = useState(false);
  const [data, setData] = useState<InlineCMSData | null>(null);
  const [savedData, setSavedData] = useState<InlineCMSData | null>(null);
  const [editing, setEditing] = useState(false);
  const [session, setSession] = useState(0);
  const [saving, setSaving] = useState(false);
  const [message, setMessage] = useState("");
  const [loading, setLoading] = useState(false);
  const [sectionPanelOpen, setSectionPanelOpen] = useState(false);
  const [sectionPanelTab, setSectionPanelTab] = useState<"suggest" | "layers">("suggest");
  const [selectedSectionId, setSelectedSectionId] = useState("");
  const [mediaPickerTarget, setMediaPickerTarget] = useState<{ sectionId: string; slideId: string } | null>(null);
  const [mediaAssets, setMediaAssets] = useState<LandingMediaAsset[]>([]);
  const [mediaAssetsLoaded, setMediaAssetsLoaded] = useState(false);
  const [mediaAssetsLoading, setMediaAssetsLoading] = useState(false);
  const [customVolunteerPath, setCustomVolunteerPath] = useState("gabung-volunteer");
  const allowUnloadRef = useRef(false);
  const startEditingRef = useRef<() => Promise<void>>(async () => {});
  const saveChangesRef = useRef<() => Promise<void>>(async () => {});
  const cancelEditingRef = useRef<() => void>(() => {});
  const finishEditingRef = useRef<() => void>(() => {});
  const excludedRoutes = ["/admin", "/pengurus", "/dashboard", "/volunteer", "/profil", "/akun", "/preview", "/api"];
  const routeSegments = pathname.split("/").filter(Boolean);
  const routeSlug = routeSegments[0] ?? "";
  const reservedSlugs = [
    "admin", "akun", "blog", "dashboard", "daftar", "ibadah", "kegiatan",
    "lupa-sandi", "masuk", "pengurus", "preview", "profil", "volunteer",
  ];
  const isVolunteerLandingRoute =
    routeSegments.length === 1 &&
    !reservedSlugs.includes(routeSlug) &&
    (routeSlug === "gabung-volunteer" || routeSlug === customVolunteerPath);
  const isUnclassifiedSingleSegmentRoute =
    routeSegments.length === 1 && !reservedSlugs.includes(routeSlug);
  const isEventRoute =
    pathname === "/kegiatan" ||
    (pathname.startsWith("/kegiatan/") && !pathname.startsWith("/kegiatan/key"));
  const eventSlug =
    pathname.startsWith("/kegiatan/") && isEventRoute
      ? decodeURIComponent(pathname.split("/")[2] ?? "")
      : "";
  const isEventLandingRoute =
    pathname.startsWith("/kegiatan/") &&
    !pathname.startsWith("/kegiatan/key") &&
    !pathname.startsWith("/kegiatan/selamat");
  const isKeyLandingRoute = pathname === "/kegiatan/key";
  const pageSectionType: LandingPageType | null =
    pathname === "/"
      ? "home"
      : isKeyLandingRoute
        ? "key"
        : isVolunteerLandingRoute
          ? "volunteer"
          : isEventLandingRoute
            ? "event"
            : null;
  const pageSectionKey =
    pageSectionType === "home"
      ? "home"
      : pageSectionType === "volunteer"
        ? "volunteer"
        : pageSectionType === "key"
          ? "key"
          : pageSectionType === "event"
            ? eventSlug
            : "";
  const pageSectionsScope = pageSectionType
    ? `${pageSectionType}:${pageSectionKey}`
    : null;
  const showToolbar =
    !excludedRoutes.some(
      (route) => pathname === route || pathname.startsWith(`${route}/`),
    ) &&
    (!isUnclassifiedSingleSegmentRoute || isVolunteerLandingRoute);
  const cmsHref = pathname.startsWith("/blog")
    ? "/admin/blog"
    : pathname === "/kegiatan" ||
        (pathname.startsWith("/kegiatan/") && !pathname.startsWith("/kegiatan/key"))
      ? "/admin/events"
      : pathname === "/kegiatan/key"
        ? "/admin/landing/key"
        : pathname.startsWith("/") && !["/", "/masuk", "/daftar", "/lupa-sandi", "/kegiatan/key", "/kegiatan/key/selamat", "/gabung-volunteer/daftar"].includes(pathname)
          ? "/admin/volunteer-landing"
          : "/admin/landing";
  const dirty = useMemo(
    () => data !== null && JSON.stringify(data) !== JSON.stringify(savedData),
    [data, savedData],
  );

  useEffect(() => {
    if (!dirty) return;

    const handleBeforeUnload = (event: BeforeUnloadEvent) => {
      if (allowUnloadRef.current) return;
      event.preventDefault();
    };
    const handleInternalNavigation = (event: MouseEvent) => {
      const target = event.target;
      if (!(target instanceof Element)) return;
      if (editing && target.closest("[data-inline-editor-field]")) return;
      const link = target.closest("a");
      const href = link?.getAttribute("href");
      if (!link || !href || href.startsWith("#") || link.target === "_blank") return;
      if (new URL(link.href, window.location.href).origin !== window.location.origin) return;
      if (!window.confirm("Perubahan belum disimpan. Tinggalkan halaman dan buang perubahan?")) {
        event.preventDefault();
        event.stopImmediatePropagation();
        return;
      }
      allowUnloadRef.current = true;
      setData(savedData);
      setEditing(false);
    };

    window.addEventListener("beforeunload", handleBeforeUnload);
    document.addEventListener("click", handleInternalNavigation, true);
    return () => {
      window.removeEventListener("beforeunload", handleBeforeUnload);
      document.removeEventListener("click", handleInternalNavigation, true);
    };
  }, [dirty, editing, savedData]);

  useEffect(() => {
    let active = true;
    const searchParams = new URLSearchParams(window.location.search);
    const visualPreview = searchParams.get("landingEditor") === "1" && (
      window.location.pathname === "/" ||
      window.location.pathname === "/kegiatan/key" ||
      window.location.pathname.startsWith("/kegiatan/") ||
      (window.location.pathname.split("/").filter(Boolean).length === 1 &&
        !["admin", "akun", "blog", "dashboard", "daftar", "ibadah", "lupa-sandi", "masuk", "pengurus", "preview", "profil", "volunteer"].includes(
          window.location.pathname.split("/").filter(Boolean)[0] ?? "",
        ))
    );
    const embeddedPreview = visualPreview &&
      searchParams.get("landingEditorEmbedded") === "1";

    fetch("/api/auth/me", { cache: "no-store" })
      .then(async (response) => {
        if (!response.ok) throw new Error("Gagal memeriksa sesi.");
        const result = await response.json();
        if (!result.success) throw new Error(result.message || "Gagal memeriksa sesi.");
        if (active) {
          const admin = result.user?.role === "ADMIN" || result.user?.role === "SUPER_ADMIN";
          setIsAdmin(admin);
          setVisualPreviewMode(admin && visualPreview);
          setEmbeddedPreviewMode(admin && embeddedPreview);
          if (admin) {
            try {
              const landingResponse = await fetch("/api/cms/volunteer-landing", { cache: "no-store" });
              const landingResult = await landingResponse.json();
              if (!landingResponse.ok || !landingResult.success) {
                throw new Error(landingResult.error || "Gagal memeriksa path landing volunteer.");
              }
              if (
                active &&
                landingResult.data?.published &&
                typeof landingResult.data.customPath === "string"
              ) {
                setCustomVolunteerPath(landingResult.data.customPath);
              }
            } catch (error) {
              console.error("Landing inline editor route check failed:", error);
            }
          }
        }
      })
      .catch((error: unknown) => {
        console.error("Landing inline editor access check failed:", error);
        if (active) setAccessError(true);
      });

    return () => {
      active = false;
    };
  }, []);

  useEffect(() => {
    if (!isAdmin || !visualPreviewMode || !pageSectionType) return;
    void startEditingRef.current();
  }, [isAdmin, pageSectionType, visualPreviewMode]);

  const setValue = useCallback((path: EditablePath, value: string) => {
    setData((current) => current ? updateValue(current, path, value) : current);
  }, []);

  const setPageSections = useCallback((sections: LandingPageSection[]) => {
    setData((current) => current ? { ...current, pageSections: sections } : current);
  }, []);

  function addLandingSection(type: LandingPageSection["type"]) {
    const definition = LANDING_SECTION_CATALOG.find((item) => item.type === type);
    if (!definition || !data) return;
    if (data.pageSections.length >= 30) {
      setMessage("Maksimal 30 section tambahan per landing page.");
      return;
    }
    const section: LandingPageSection = {
      id: `suggested-${crypto.randomUUID()}`,
      type,
      title: definition.defaultTitle,
      content: type === "carousel"
        ? JSON.stringify(createEmptyLandingCarouselContent())
        : "",
      enabled: true,
    };
    setPageSections([...data.pageSections, section]);
    setSelectedSectionId(section.id);
    setSectionPanelTab("layers");
    setMessage(`${definition.title} ditambahkan ke preview. Lengkapi kontennya sebelum menyimpan.`);
  }

  function updateCarouselContent(
    sectionId: string,
    update: (content: ReturnType<typeof createEmptyLandingCarouselContent>) => ReturnType<typeof createEmptyLandingCarouselContent>,
  ) {
    const current = data?.pageSections.find((section) => section.id === sectionId);
    const content = parseLandingCarouselContent(current?.content ?? "") ?? createEmptyLandingCarouselContent();
    updateLandingSection(sectionId, { content: JSON.stringify(update(content)) });
  }

  function updateCarouselSlide(
    sectionId: string,
    slideId: string,
    patch: Partial<LandingCarouselSlide>,
  ) {
    updateCarouselContent(sectionId, (content) => ({
      ...content,
      slides: content.slides.map((slide) =>
        slide.id === slideId ? { ...slide, ...patch } : slide,
      ),
    }));
  }

  function addCarouselSlide(sectionId: string, mediaType: LandingCarouselSlide["mediaType"]) {
    const section = data?.pageSections.find((item) => item.id === sectionId);
    const content = parseLandingCarouselContent(section?.content ?? "") ?? createEmptyLandingCarouselContent();
    if (content.slides.length >= 24) {
      setMessage("Carousel dapat memuat maksimal 24 slide.");
      return;
    }
    updateCarouselContent(sectionId, (current) => ({
      ...current,
      slides: [
        ...current.slides,
        {
          id: `slide-${crypto.randomUUID()}`,
          mediaType,
          src: "",
          alt: "",
          caption: "",
        },
      ],
    }));
    setMessage(`${mediaType === "image" ? "Foto" : "Video"} ditambahkan. Isi sumber media sebelum menyimpan.`);
  }

  async function openMediaPicker(sectionId: string, slideId: string) {
    setMediaPickerTarget({ sectionId, slideId });
    if (mediaAssetsLoaded || mediaAssetsLoading) return;
    setMediaAssetsLoading(true);
    try {
      const response = await fetch("/api/cms/media", { cache: "no-store" });
      const result = await response.json();
      if (!response.ok || !result.success || !Array.isArray(result.data)) {
        throw new Error(result.error || "Gagal memuat pustaka media.");
      }
      const images = result.data.filter((item: unknown): item is LandingMediaAsset =>
        typeof item === "object" &&
        item !== null &&
        typeof (item as Record<string, unknown>).id === "number" &&
        typeof (item as Record<string, unknown>).originalName === "string" &&
        typeof (item as Record<string, unknown>).mimeType === "string" &&
        typeof (item as Record<string, unknown>).url === "string" &&
        String((item as Record<string, unknown>).mimeType).startsWith("image/"),
      );
      setMediaAssets(images);
      setMediaAssetsLoaded(true);
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Gagal memuat pustaka media.");
      setMediaPickerTarget(null);
    } finally {
      setMediaAssetsLoading(false);
    }
  }

  function chooseMediaAsset(asset: LandingMediaAsset) {
    const section = data?.pageSections.find((item) => item.id === mediaPickerTarget?.sectionId);
    const content = parseLandingCarouselContent(section?.content ?? "");
    const slide = content?.slides.find((item) => item.id === mediaPickerTarget?.slideId);
    if (!section || !slide || !mediaPickerTarget) return;
    updateCarouselSlide(section.id, mediaPickerTarget.slideId, {
      src: asset.url,
      alt: slide.alt || asset.originalName,
    });
    setMediaPickerTarget(null);
    setMessage("Foto ditambahkan ke carousel.");
  }

  function moveCarouselSlide(sectionId: string, slideIndex: number, offset: number) {
    updateCarouselContent(sectionId, (content) => {
      const target = slideIndex + offset;
      if (target < 0 || target >= content.slides.length) return content;
      const slides = [...content.slides];
      [slides[slideIndex], slides[target]] = [slides[target], slides[slideIndex]];
      return { ...content, slides };
    });
  }

  function updateLandingSection(id: string, patch: Partial<LandingPageSection>) {
    if (!data) return;
    setPageSections(data.pageSections.map((section) =>
      section.id === id ? { ...section, ...patch } : section,
    ));
  }

  function moveLandingSection(index: number, offset: number) {
    if (!data) return;
    const target = index + offset;
    if (target < 0 || target >= data.pageSections.length) return;
    const sections = [...data.pageSections];
    [sections[index], sections[target]] = [sections[target], sections[index]];
    setPageSections(sections);
  }

  function removeLandingSection(id: string) {
    if (!data) return;
    setPageSections(data.pageSections.filter((section) => section.id !== id));
    if (selectedSectionId === id) {
      setSelectedSectionId(data.pageSections.find((section) => section.id !== id)?.id ?? "");
    }
  }

  const getValue = useCallback((path: EditablePath) => readValue(data, path), [data]);
  const getSavedValue = useCallback((path: EditablePath) => readValue(savedData, path), [savedData]);
  const contextValue = useMemo(
    () => ({ data, editing, session, getValue, getSavedValue, setValue, setPageSections }),
    [data, editing, session, getValue, getSavedValue, setValue, setPageSections],
  );

  async function saveChanges() {
    if (!data) return;
    setSaving(true);
    setMessage("");

    try {
      if (!savedData || JSON.stringify(data.landing) !== JSON.stringify(savedData.landing)) {
        const response = await fetch("/api/cms/landing", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(data.landing),
        });
        const result = await response.json();
        if (!response.ok || !result.success) {
          throw new Error(result.error || "Gagal menyimpan teks CMS.");
        }
      }

      for (const blog of data.blogs) {
        const savedBlog = savedData?.blogs.find((item) => item.id === blog.id);
        if (!savedBlog) continue;
        for (const field of ["title", "excerpt", "content", "category"] as const) {
          if (blog[field] === savedBlog[field]) continue;
          const response = await fetch("/api/cms/blog", {
            method: "PATCH",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ id: blog.id, field, value: blog[field] }),
          });
          const result = await response.json();
          if (!response.ok || !result.success) {
            throw new Error(result.error || "Gagal menyimpan perubahan artikel.");
          }
        }
      }

      for (const event of data.events) {
        const savedEvent = savedData?.events.find((item) => item.id === event.id);
        if (!savedEvent) continue;
        for (const field of ["title", "type", "description", "location", "audience", "series"] as const) {
          if (event[field] === savedEvent[field]) continue;
          const response = await fetch("/api/cms/events", {
            method: "PATCH",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ id: event.id, field, value: event[field] }),
          });
          const result = await response.json();
          if (!response.ok || !result.success) {
            throw new Error(result.error || "Gagal menyimpan perubahan kegiatan.");
          }
        }
      }

      if (
        data.eventPage &&
        savedData?.eventPage &&
        JSON.stringify(data.eventPage) !== JSON.stringify(savedData.eventPage)
      ) {
        const response = await fetch("/api/cms/event-sections", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            slug: data.eventPage.slug,
            sections: data.eventPage.sections,
            copy: data.eventPage.copy,
          }),
        });
        const result = await response.json();
        if (!response.ok || !result.success) {
          throw new Error(result.error || "Gagal menyimpan konten section kegiatan.");
        }
      }

      if (
        data.volunteerLanding &&
        savedData?.volunteerLanding &&
        JSON.stringify(data.volunteerLanding) !== JSON.stringify(savedData.volunteerLanding)
      ) {
        const response = await fetch("/api/cms/volunteer-landing", {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(data.volunteerLanding),
        });
        const result = await response.json();
        if (!response.ok || !result.success) {
          throw new Error(result.error || "Gagal menyimpan halaman volunteer.");
        }
      }

      if (
        data.pageSectionType &&
        data.pageSectionsScope &&
        JSON.stringify(data.pageSections) !== JSON.stringify(savedData?.pageSections)
      ) {
        const params = new URLSearchParams({ type: data.pageSectionType });
        if (data.pageSectionType === "event") params.set("slug", data.pageSectionKey);
        const response = await fetch(`/api/cms/landing-sections?${params}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ sections: data.pageSections }),
        });
        const result = await response.json();
        if (!response.ok || !result.success) {
          throw new Error(result.error || "Gagal menyimpan section landing page.");
        }
      }

      setSavedData(data);
      setData(data);
      setMessage("Perubahan tersimpan.");
      allowUnloadRef.current = true;
      window.location.reload();
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Gagal menyimpan perubahan.");
    } finally {
      setSaving(false);
    }
  }

  async function startEditing() {
    setMessage("");
    allowUnloadRef.current = false;
    setLoading(true);
    try {
      const response = await fetch("/api/cms/landing", { cache: "no-store" });
      const result = await response.json();
      if (!response.ok || !result.success) {
        throw new Error(result.error || "Gagal memuat data CMS.");
      }

      const landing = result.data as LandingCMS;
      const nextData: InlineCMSData = {
        landing,
        blogs: [],
        events: [],
        eventPage: null,
        volunteerLanding: null,
        pageSections: [],
        pageSectionsScope,
        pageSectionType,
        pageSectionKey,
      };
      if (pathname.startsWith("/blog")) {
        const blogResponse = await fetch("/api/cms/blog", { cache: "no-store" });
        const blogResult = await blogResponse.json();
        if (!blogResponse.ok || !blogResult.success) {
          throw new Error(blogResult.error || "Gagal memuat artikel untuk diedit.");
        }
        nextData.blogs = blogResult.data as BlogRecord[];
      }
      if (isEventRoute) {
        const eventResponse = await fetch("/api/cms/events", { cache: "no-store" });
        const eventResult = await eventResponse.json();
        if (!eventResponse.ok || !eventResult.success) {
          throw new Error(eventResult.error || "Gagal memuat kegiatan untuk diedit.");
        }
        nextData.events = eventResult.data as EventRecord[];
        if (eventSlug) {
          const eventSectionResponse = await fetch(
            `/api/cms/event-sections?slug=${encodeURIComponent(eventSlug)}`,
            { cache: "no-store" },
          );
          const eventSectionResult = await eventSectionResponse.json();
          if (!eventSectionResponse.ok) {
            throw new Error(eventSectionResult.error || "Gagal memuat konten section kegiatan.");
          }
          if (
            typeof eventSectionResult.copyDefaults !== "object" ||
            eventSectionResult.copyDefaults === null ||
            Array.isArray(eventSectionResult.copyDefaults)
          ) {
            throw new Error("Format teks default section kegiatan tidak valid.");
          }
          const event = nextData.events.find((item) => item.slug === eventSlug);
          if (!event) throw new Error("Kegiatan tidak ditemukan di CMS.");
          const copyDefaults = eventSectionResult.copyDefaults as Record<string, string>;
          nextData.eventPage = {
            slug: eventSlug,
            sections: Array.isArray(eventSectionResult.sections)
              ? eventSectionResult.sections
              : [
                  { id: "hero", type: "hero", title: copyDefaults.eventDefaultHeroTitle ?? "", content: "", enabled: true },
                  { id: "description", type: "description", title: copyDefaults.eventDefaultDescriptionTitle ?? "", content: event.description, enabled: true },
                  { id: "schedule", type: "schedule", title: copyDefaults.eventDefaultScheduleTitle ?? "", content: "", enabled: true },
                  { id: "cta", type: "cta", title: copyDefaults.eventDefaultCtaTitle ?? "", content: copyDefaults.eventDefaultCtaDescription ?? "", enabled: true },
                ],
            copy: eventSectionResult.copy ?? {},
          };
        }
      }
      if (isVolunteerLandingRoute) {
        const landingResponse = await fetch("/api/cms/volunteer-landing", { cache: "no-store" });
        const landingResult = await landingResponse.json();
        if (!landingResponse.ok || !landingResult.success || !landingResult.data) {
          throw new Error(landingResult.error || "Gagal memuat halaman volunteer untuk diedit.");
        }
        nextData.volunteerLanding = landingResult.data as VolunteerLandingRecord;
      }

      if (pageSectionType) {
        const params = new URLSearchParams({ type: pageSectionType });
        if (pageSectionType === "event") params.set("slug", pageSectionKey);
        const response = await fetch(`/api/cms/landing-sections?${params}`, { cache: "no-store" });
        const result = await response.json();
        if (!response.ok || !result.success || !Array.isArray(result.sections)) {
          throw new Error(result.error || "Gagal memuat section landing page.");
        }
        nextData.pageSections = result.sections as LandingPageSection[];
      }

      setData(nextData);
      setSavedData(nextData);
      setSelectedSectionId(nextData.pageSections[0]?.id ?? "");
      setSectionPanelOpen(false);
      setSession((current) => current + 1);
      setEditing(true);
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Gagal memuat data CMS.");
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    startEditingRef.current = startEditing;
    saveChangesRef.current = saveChanges;
    cancelEditingRef.current = () => {
      if (savedData) setData(savedData);
      setMessage("");
      setSectionPanelOpen(false);
      setEditing(false);
    };
    finishEditingRef.current = () => {
      if (dirty) return;
      setSectionPanelOpen(false);
      setEditing(false);
    };
  });

  useEffect(() => {
    if (!embeddedPreviewMode) return;

    const handleParentCommand = (event: MessageEvent) => {
      if (
        event.origin !== window.location.origin ||
        event.source !== window.parent ||
        event.data?.source !== "yn-landing-studio" ||
        event.data?.type !== "command"
      ) {
        return;
      }

      switch (event.data.command) {
        case "start":
          void startEditingRef.current();
          break;
        case "toggle-sections":
          setSectionPanelOpen((open) => !open);
          break;
        case "save":
          void saveChangesRef.current();
          break;
        case "cancel":
          cancelEditingRef.current();
          break;
        case "finish":
          finishEditingRef.current();
          break;
      }
    };

    window.addEventListener("message", handleParentCommand);
    return () => window.removeEventListener("message", handleParentCommand);
  }, [embeddedPreviewMode]);

  useEffect(() => {
    if (!visualPreviewMode || window.parent === window) return;
    window.parent.postMessage(
      {
        source: "yn-landing-preview",
        type: "state",
        state: {
          editing,
          dirty,
          loading,
          saving,
          sectionPanelOpen,
          sectionCount: data?.pageSections.length ?? 0,
          message,
        },
      },
      window.location.origin,
    );
  }, [data, dirty, editing, loading, message, saving, sectionPanelOpen, visualPreviewMode]);

  return (
    <LandingEditorContext.Provider value={contextValue}>
      {children}
      {editing && pageSectionType && data && sectionPanelOpen && (
        <aside className={`landing-section-panel${embeddedPreviewMode ? " landing-section-panel-embedded" : ""}`} aria-label="Panel section landing page">
          <header className="landing-section-panel-header">
            <div>
              <span>PAGE BUILDER</span>
              <h2>Susun landing page</h2>
            </div>
            <button
              type="button"
              aria-label="Tutup panel section"
              onClick={() => setSectionPanelOpen(false)}
            >
              ×
            </button>
          </header>
          <div className="landing-section-tabs" role="tablist" aria-label="Mode panel section">
            <button
              type="button"
              role="tab"
              aria-selected={sectionPanelTab === "suggest"}
              onClick={() => setSectionPanelTab("suggest")}
            >
              Saran section
            </button>
            <button
              type="button"
              role="tab"
              aria-selected={sectionPanelTab === "layers"}
              onClick={() => setSectionPanelTab("layers")}
            >
              Susunan ({data.pageSections.length})
            </button>
          </div>
          {sectionPanelTab === "suggest" ? (
            <div className="landing-section-suggestions">
              <p>Pilih pola konten untuk menambah section ke bagian bawah halaman. Hasilnya langsung terlihat di preview.</p>
              {LANDING_SECTION_CATALOG.map((item) => (
                <article key={item.type}>
                  <div>
                    <strong>{item.title}</strong>
                    <p>{item.description}</p>
                  </div>
                  <button
                    type="button"
                    disabled={data.pageSections.length >= 30 || saving || loading}
                    onClick={() => addLandingSection(item.type)}
                    aria-label={`Tambah section ${item.title}`}
                  >
                    +
                  </button>
                </article>
              ))}
            </div>
          ) : (
            <div className="landing-section-layers">
              {data.pageSections.length === 0 ? (
                <p>Belum ada section tambahan. Pilih saran section untuk mulai menyusun halaman.</p>
              ) : data.pageSections.map((section, index) => (
                <article key={section.id} className={selectedSectionId === section.id ? "selected" : ""}>
                  <div className="landing-section-layer-row">
                    <button
                      type="button"
                      className="landing-section-layer-name"
                      onClick={() => setSelectedSectionId(section.id)}
                      aria-pressed={selectedSectionId === section.id}
                    >
                      <span>{String(index + 1).padStart(2, "0")}</span>
                      <span>{section.title || LANDING_SECTION_CATALOG.find((item) => item.type === section.type)?.title}</span>
                    </button>
                    <div>
                      <button type="button" aria-label={`Pindah ${section.title} ke atas`} disabled={index === 0} onClick={() => moveLandingSection(index, -1)}>↑</button>
                      <button type="button" aria-label={`Pindah ${section.title} ke bawah`} disabled={index === data.pageSections.length - 1} onClick={() => moveLandingSection(index, 1)}>↓</button>
                      <button type="button" aria-label={`Hapus ${section.title}`} onClick={() => removeLandingSection(section.id)}>×</button>
                    </div>
                  </div>
                  {selectedSectionId === section.id && (
                    <div className="landing-section-fields">
                      <div className="landing-section-layout-fields">
                        <span>Posisi dan ukuran · grid 12 kolom</span>
                        <p>Geser section di pratinjau untuk mengatur urutan dan kolomnya. Di layar ponsel, section otomatis memenuhi lebar.</p>
                        {(() => {
                          const layout = section.layout ?? { column: 1, columnSpan: 12, rowSpan: 1 };
                          return (
                            <>
                              <label>
                                Kolom mulai
                                <select
                                  value={layout.column}
                                  onChange={(event) => {
                                    const column = Number(event.target.value);
                                    updateLandingSection(section.id, {
                                      layout: {
                                        ...layout,
                                        column,
                                        columnSpan: Math.min(layout.columnSpan, 13 - column),
                                      },
                                    });
                                  }}
                                >
                                  {Array.from({ length: 13 - layout.columnSpan }, (_, index) => index + 1).map((column) => (
                                    <option key={column} value={column}>{column}</option>
                                  ))}
                                </select>
                              </label>
                              <label>
                                Lebar kolom
                                <select
                                  value={layout.columnSpan}
                                  onChange={(event) => {
                                    const columnSpan = Number(event.target.value);
                                    updateLandingSection(section.id, {
                                      layout: {
                                        ...layout,
                                        columnSpan,
                                        column: Math.min(layout.column, 13 - columnSpan),
                                      },
                                    });
                                  }}
                                >
                                  {Array.from({ length: 13 - layout.column }, (_, index) => index + 1).map((span) => (
                                    <option key={span} value={span}>{span} kolom</option>
                                  ))}
                                </select>
                              </label>
                              <label>
                                Tinggi baris
                                <select
                                  value={layout.rowSpan}
                                  onChange={(event) => updateLandingSection(section.id, {
                                    layout: { ...layout, rowSpan: Number(event.target.value) },
                                  })}
                                >
                                  {[1, 2, 3, 4, 5, 6].map((span) => (
                                    <option key={span} value={span}>{span} baris</option>
                                  ))}
                                </select>
                              </label>
                            </>
                          );
                        })()}
                      </div>
                      {section.type === "carousel" ? (
                        <div className="landing-carousel-editor">
                          {(() => {
                            const content = parseLandingCarouselContent(section.content) ?? createEmptyLandingCarouselContent();
                            return (
                              <>
                                <label>
                                  Judul carousel
                                  <input
                                    maxLength={500}
                                    value={section.title}
                                    onChange={(event) => updateLandingSection(section.id, { title: event.target.value })}
                                  />
                                </label>
                                <label>
                                  Bentuk media
                                  <select
                                    value={content.ratio}
                                    onChange={(event) => {
                                      const ratio = LANDING_CAROUSEL_RATIOS.find((item) => item.value === event.target.value);
                                      if (ratio) {
                                        updateCarouselContent(section.id, (current) => ({ ...current, ratio: ratio.value }));
                                      }
                                    }}
                                  >
                                    {LANDING_CAROUSEL_RATIOS.map((ratio) => (
                                      <option key={ratio.value} value={ratio.value}>
                                        {ratio.value} · {ratio.label}
                                      </option>
                                    ))}
                                  </select>
                                  <small>Semua item memakai bentuk yang sama agar perpindahan slide tetap rapi.</small>
                                </label>

                                <div className="landing-carousel-slide-list">
                                  <div className="landing-carousel-list-heading">
                                    <strong>Isi carousel</strong>
                                    <span>{content.slides.length} / 24 slide</span>
                                  </div>
                                  {content.slides.length === 0 && (
                                    <p className="landing-carousel-empty">
                                      Tambahkan foto atau video. Slide akan muncul di halaman setelah sumber media diisi.
                                    </p>
                                  )}
                                  {content.slides.map((slide, slideIndex) => (
                                    <article className="landing-carousel-slide" key={slide.id}>
                                      <div className="landing-carousel-slide-heading">
                                        <strong>{String(slideIndex + 1).padStart(2, "0")} <span>{slide.mediaType === "image" ? "FOTO" : "VIDEO"}</span></strong>
                                        <div>
                                          <button
                                            type="button"
                                            aria-label={`Pindahkan slide ${slideIndex + 1} ke atas`}
                                            disabled={slideIndex === 0}
                                            onClick={() => moveCarouselSlide(section.id, slideIndex, -1)}
                                          >↑</button>
                                          <button
                                            type="button"
                                            aria-label={`Pindahkan slide ${slideIndex + 1} ke bawah`}
                                            disabled={slideIndex === content.slides.length - 1}
                                            onClick={() => moveCarouselSlide(section.id, slideIndex, 1)}
                                          >↓</button>
                                          <button
                                            type="button"
                                            aria-label={`Hapus slide ${slideIndex + 1}`}
                                            onClick={() => updateCarouselContent(section.id, (current) => ({
                                              ...current,
                                              slides: current.slides.filter((item) => item.id !== slide.id),
                                            }))}
                                          >×</button>
                                        </div>
                                      </div>
                                      <label>
                                        Jenis media
                                        <select
                                          value={slide.mediaType}
                                          onChange={(event) => updateCarouselSlide(section.id, slide.id, {
                                            mediaType: event.target.value === "video" ? "video" : "image",
                                            src: "",
                                          })}
                                        >
                                          <option value="image">Foto</option>
                                          <option value="video">Video</option>
                                        </select>
                                      </label>
                                      {slide.mediaType === "image" && (
                                        <button
                                          type="button"
                                          className="landing-carousel-library-button"
                                          onClick={() => void openMediaPicker(section.id, slide.id)}
                                          disabled={mediaAssetsLoading}
                                        >
                                          {mediaAssetsLoading ? "Memuat media..." : "Pilih dari Media Library"}
                                        </button>
                                      )}
                                      <label>
                                        {slide.mediaType === "image" ? "URL foto atau path file" : "URL video"}
                                        <input
                                          maxLength={2000}
                                          type="text"
                                          value={slide.src}
                                          placeholder={slide.mediaType === "image" ? "/uploads/foto.jpg" : "MP4/WebM atau tautan YouTube/Vimeo"}
                                          onChange={(event) => updateCarouselSlide(section.id, slide.id, { src: event.target.value })}
                                        />
                                      </label>
                                      {slide.mediaType === "image" ? (
                                        <label>
                                          Teks alternatif foto
                                          <input
                                            maxLength={500}
                                            value={slide.alt}
                                            placeholder="Jelaskan isi foto secara singkat"
                                            onChange={(event) => updateCarouselSlide(section.id, slide.id, { alt: event.target.value })}
                                          />
                                        </label>
                                      ) : (
                                        <small className="landing-carousel-help">
                                          Video langsung: MP4, WebM, M4V, atau OGV. YouTube dan Vimeo juga didukung.
                                        </small>
                                      )}
                                      <label>
                                        Keterangan (opsional)
                                        <input
                                          maxLength={500}
                                          value={slide.caption}
                                          onChange={(event) => updateCarouselSlide(section.id, slide.id, { caption: event.target.value })}
                                        />
                                      </label>
                                      {slide.src && slide.mediaType === "image" && (
                                        // eslint-disable-next-line @next/next/no-img-element
                                        <img className="landing-carousel-slide-thumb" src={slide.src} alt={slide.alt} />
                                      )}
                                      {slide.src && slide.mediaType === "video" && (
                                        landingVideoEmbedUrl(slide.src) ? (
                                          <iframe
                                            className="landing-carousel-slide-thumb"
                                            title={`Pratinjau video slide ${slideIndex + 1}`}
                                            src={landingVideoEmbedUrl(slide.src) ?? undefined}
                                            allow="fullscreen; picture-in-picture"
                                            loading="lazy"
                                          />
                                        ) : (
                                          <video className="landing-carousel-slide-thumb" src={slide.src} controls preload="metadata" />
                                        )
                                      )}
                                    </article>
                                  ))}
                                  <div className="landing-carousel-add-actions">
                                    <button
                                      type="button"
                                      disabled={content.slides.length >= 24}
                                      onClick={() => addCarouselSlide(section.id, "image")}
                                    >+ Tambah foto</button>
                                    <button
                                      type="button"
                                      disabled={content.slides.length >= 24}
                                      onClick={() => addCarouselSlide(section.id, "video")}
                                    >+ Tambah video</button>
                                  </div>
                                </div>
                                <small>{LANDING_SECTION_CATALOG.find((item) => item.type === section.type)?.contentHint}</small>
                              </>
                            );
                          })()}
                        </div>
                      ) : (
                        <>
                      <label>
                        Judul
                        <input
                          maxLength={500}
                          value={section.title}
                          onChange={(event) => updateLandingSection(section.id, { title: event.target.value })}
                        />
                      </label>
                      <label>
                        {section.type === "countdown" ? "Waktu target" : "Konten"}
                        {section.type === "countdown" ? (
                          <input
                            type="datetime-local"
                            value={dateTimeLocalValue(section.content)}
                            onChange={(event) => {
                              const value = event.target.value;
                              const timestamp = value ? Date.parse(value) : Number.NaN;
                              updateLandingSection(section.id, {
                                content: Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : "",
                              });
                            }}
                          />
                        ) : (
                          <textarea
                            maxLength={20000}
                            rows={6}
                            value={section.content}
                            placeholder={LANDING_SECTION_CATALOG.find((item) => item.type === section.type)?.contentHint}
                            onChange={(event) => updateLandingSection(section.id, { content: event.target.value })}
                          />
                        )}
                        <small>{LANDING_SECTION_CATALOG.find((item) => item.type === section.type)?.contentHint}</small>
                      </label>
                        </>
                      )}
                      <label className="landing-section-enabled">
                        <input
                          type="checkbox"
                          checked={section.enabled}
                          onChange={(event) => updateLandingSection(section.id, { enabled: event.target.checked })}
                        />
                        Tampilkan section
                      </label>
                    </div>
                  )}
                </article>
              ))}
            </div>
          )}
          {mediaPickerTarget && (
            <div className="landing-media-picker" role="dialog" aria-modal="true" aria-labelledby="landing-media-picker-title">
              <header>
                <div>
                  <span>MEDIA LIBRARY</span>
                  <h3 id="landing-media-picker-title">Pilih foto</h3>
                </div>
                <button type="button" aria-label="Tutup pustaka media" onClick={() => setMediaPickerTarget(null)}>×</button>
              </header>
              {mediaAssetsLoading ? (
                <p role="status">Memuat foto tersimpan...</p>
              ) : mediaAssets.length === 0 ? (
                <div className="landing-media-picker-empty">
                  <p>Belum ada foto di Media Library.</p>
                  <Link href="/admin/media" target="_blank">Buka Media Library untuk upload ↗</Link>
                </div>
              ) : (
                <div className="landing-media-picker-grid">
                  {mediaAssets.map((asset) => (
                    <button
                      type="button"
                      key={asset.id}
                      onClick={() => chooseMediaAsset(asset)}
                      title={asset.originalName}
                    >
                      {/* eslint-disable-next-line @next/next/no-img-element */}
                      <img src={asset.url} alt="" loading="lazy" />
                      <span>{asset.originalName}</span>
                    </button>
                  ))}
                </div>
              )}
            </div>
          )}
        </aside>
      )}
      {isAdmin && showToolbar && !embeddedPreviewMode && (
        <aside className="landing-editor-toolbar" aria-label="Editor halaman utama">
          {editing ? (
            <>
              <span className="landing-editor-status" role="status">
                {saving ? "Menyimpan…" : message || (dirty ? "Ada perubahan belum disimpan" : "Mode edit aktif")}
              </span>
              {pageSectionType && (
                <button
                  type="button"
                  disabled={saving || loading}
                  onClick={() => setSectionPanelOpen((open) => !open)}
                  aria-expanded={sectionPanelOpen}
                >
                  {sectionPanelOpen ? "Tutup panel section" : `Sections${data ? ` (${data.pageSections.length})` : ""}`}
                </button>
              )}
              <button type="button" disabled={!dirty || saving || loading} onClick={saveChanges}>
                Simpan
              </button>
              <button
                type="button"
                disabled={saving || loading}
                onClick={() => {
                  if (savedData) setData(savedData);
                  setMessage("");
                  setSectionPanelOpen(false);
                  setEditing(false);
                }}
              >
                Batalkan
              </button>
              <button type="button" disabled={saving || loading || dirty} onClick={() => setEditing(false)}>
                Selesai
              </button>
            </>
          ) : (
            <>
              <span className="landing-editor-status" role="status">
                {loading ? "Memuat CMS…" : message || "Pratinjau CMS"}
              </span>
              <button
                type="button"
                disabled={loading}
                onClick={startEditing}
              >
                Edit teks langsung
              </button>
              {!visualPreviewMode && <Link href={cmsHref}>CMS lengkap</Link>}
            </>
          )}
        </aside>
      )}
      {accessError && (
        <p className="landing-editor-access-error" role="status">
          Mode edit tidak tersedia karena sesi admin tidak dapat diverifikasi.
        </p>
      )}
      <style jsx global>{`
        .landing-section-panel {
          position: fixed;
          z-index: 999;
          top: 16px;
          right: 16px;
          bottom: 94px;
          display: flex;
          width: min(360px, calc(100vw - 32px));
          flex-direction: column;
          overflow: hidden;
          border: 1px solid #334155;
          border-radius: 16px;
          background: #0b1220;
          box-shadow: 0 20px 60px #0009;
          color: #e2e8f0;
          font: 500 13px/1.5 "DM Sans", "Segoe UI", sans-serif;
        }
        .landing-section-panel-embedded { bottom: 16px; }
        .landing-section-panel-header {
          display: flex;
          align-items: flex-start;
          justify-content: space-between;
          gap: 12px;
          padding: 18px;
          border-bottom: 1px solid #263449;
        }
        .landing-section-panel-header span { color: #38bdf8; font-size: 10px; font-weight: 800; letter-spacing: .14em; }
        .landing-section-panel-header h2 { margin: 4px 0 0; color: white; font-size: 18px; }
        .landing-section-panel button {
          min-width: 34px;
          min-height: 34px;
          border: 1px solid #334155;
          border-radius: 8px;
          background: #172033;
          color: #f8fafc;
          font: inherit;
          cursor: pointer;
        }
        .landing-section-panel button:disabled { cursor: not-allowed; opacity: .45; }
        .landing-section-panel button:focus-visible,
        .landing-section-panel input:focus-visible,
        .landing-section-panel textarea:focus-visible { outline: 2px solid #38bdf8; outline-offset: 2px; }
        .landing-section-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 12px 14px; }
        .landing-section-tabs button { color: #aab6c7; }
        .landing-section-tabs button[aria-selected="true"] { border-color: #38bdf8; background: #0c4a6e; color: white; }
        .landing-section-suggestions,
        .landing-section-layers { min-height: 0; flex: 1; overflow: auto; padding: 0 14px 16px; }
        .landing-section-suggestions > p,
        .landing-section-layers > p { margin: 4px 3px 12px; color: #aab6c7; font-size: 12px; }
        .landing-section-suggestions article,
        .landing-section-layers article {
          margin: 8px 0;
          padding: 12px;
          border: 1px solid #263449;
          border-radius: 10px;
          background: #101a2b;
        }
        .landing-section-suggestions article { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
        .landing-section-suggestions strong { color: white; font-size: 13px; }
        .landing-section-suggestions article p { margin: 4px 0 0; color: #9eacc0; font-size: 11px; line-height: 1.45; }
        .landing-section-suggestions article > button { flex: 0 0 36px; color: #7dd3fc; font-size: 20px; }
        .landing-section-layer-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
        .landing-section-layer-row > div { display: flex; gap: 4px; }
        .landing-section-layer-row > div button { min-width: 30px; min-height: 30px; }
        .landing-section-layer-name { display: flex; flex: 1; align-items: center; gap: 9px; border: 0 !important; background: transparent !important; text-align: left; }
        .landing-section-layer-name span:first-child { color: #38bdf8; font-size: 10px; font-weight: 800; }
        .landing-section-layers article.selected { border-color: #38bdf8; }
        .landing-section-fields { display: grid; gap: 12px; margin-top: 14px; }
        .landing-section-layout-fields {
          display: grid;
          grid-template-columns: repeat(2, minmax(0, 1fr));
          gap: 9px;
          padding: 11px;
          border: 1px solid #263449;
          border-radius: 9px;
          background: #101a2b;
        }
        .landing-section-layout-fields > span,
        .landing-section-layout-fields > p { grid-column: 1 / -1; }
        .landing-section-layout-fields > span { color: #7dd3fc; font-size: 11px; font-weight: 800; }
        .landing-section-layout-fields > p { margin: -5px 0 0; color: #9eacc0; font-size: 10px; line-height: 1.45; }
        .landing-section-fields label { display: grid; gap: 6px; color: #cbd5e1; font-size: 11px; font-weight: 700; }
        .landing-section-fields input:not([type="checkbox"]),
        .landing-section-fields textarea,
        .landing-section-fields select {
          width: 100%;
          padding: 9px 10px;
          border: 1px solid #334155;
          border-radius: 7px;
          background: #080e19;
          color: #f8fafc;
          font: 500 12px/1.5 "DM Sans", "Segoe UI", sans-serif;
        }
        .landing-section-fields textarea { resize: vertical; }
        .landing-section-fields select { min-height: 38px; }
        .landing-section-fields small { color: #8b9ab0; font-weight: 400; line-height: 1.45; }
        .landing-section-fields .landing-section-enabled { display: flex; align-items: center; gap: 8px; }
        .landing-carousel-editor { display: grid; gap: 14px; }
        .landing-carousel-slide-list {
          display: grid;
          gap: 10px;
          padding-top: 12px;
          border-top: 1px solid #263449;
        }
        .landing-carousel-list-heading,
        .landing-carousel-slide-heading {
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 8px;
        }
        .landing-carousel-list-heading strong { color: #fff; font-size: 12px; }
        .landing-carousel-list-heading span { color: #91a2b8; font-size: 11px; }
        .landing-carousel-empty { margin: 0; color: #aab6c7; font-size: 12px; line-height: 1.5; }
        .landing-carousel-slide {
          display: grid;
          gap: 10px;
          margin: 0 !important;
          padding: 11px !important;
          border-color: #32445b !important;
          background: #0d1726 !important;
        }
        .landing-carousel-slide-heading > strong { color: #e2e8f0; font-size: 11px; }
        .landing-carousel-slide-heading > strong span {
          margin-left: 5px;
          color: #7dd3fc;
          font-size: 9px;
          letter-spacing: .08em;
        }
        .landing-carousel-slide-heading > div { display: flex; gap: 4px; }
        .landing-carousel-slide-heading button { min-width: 29px !important; min-height: 29px !important; }
        .landing-carousel-library-button {
          min-height: 36px !important;
          color: #a5f3fc !important;
          text-align: left;
        }
        .landing-carousel-help { display: block; }
        .landing-carousel-slide-thumb {
          display: block;
          width: 100%;
          max-height: 180px;
          border: 1px solid #32445b;
          border-radius: 7px;
          background: #050a12;
          object-fit: contain;
        }
        .landing-carousel-add-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
        .landing-carousel-add-actions button {
          min-height: 38px !important;
          color: #dff8ff !important;
          font-size: 11px;
        }
        .landing-media-picker {
          position: absolute;
          z-index: 2;
          inset: 8px;
          display: flex;
          flex-direction: column;
          overflow: hidden;
          border: 1px solid #415269;
          border-radius: 12px;
          background: #0b1220;
          box-shadow: 0 12px 32px #0009;
        }
        .landing-media-picker > header {
          display: flex;
          align-items: flex-start;
          justify-content: space-between;
          gap: 10px;
          padding: 15px;
          border-bottom: 1px solid #263449;
        }
        .landing-media-picker > header span { color: #7dd3fc; font-size: 9px; font-weight: 850; letter-spacing: .14em; }
        .landing-media-picker > header h3 { margin: 3px 0 0; color: #fff; font-size: 17px; }
        .landing-media-picker > p { padding: 16px; color: #b5c1cf; font-size: 12px; }
        .landing-media-picker-empty { padding: 16px; color: #b5c1cf; font-size: 12px; }
        .landing-media-picker-empty a { color: #7dd3fc; }
        .landing-media-picker-grid {
          display: grid;
          min-height: 0;
          grid-template-columns: repeat(2, minmax(0, 1fr));
          gap: 8px;
          overflow: auto;
          padding: 12px;
        }
        .landing-media-picker-grid button {
          display: grid;
          min-width: 0;
          gap: 6px;
          overflow: hidden;
          padding: 5px;
          border: 1px solid #263449;
          border-radius: 8px;
          background: #101a2b;
          color: #e2e8f0;
          text-align: left;
        }
        .landing-media-picker-grid button:hover { border-color: #38bdf8; }
        .landing-media-picker-grid img { width: 100%; aspect-ratio: 4 / 3; border-radius: 4px; object-fit: cover; }
        .landing-media-picker-grid button > span { overflow: hidden; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
        .landing-editor-toolbar {
          position: fixed;
          z-index: 1000;
          right: 16px;
          bottom: 16px;
          display: flex;
          align-items: center;
          flex-wrap: wrap;
          gap: 8px;
          max-width: calc(100vw - 32px);
          padding: 10px;
          border: 1px solid #334155;
          border-radius: 14px;
          background: #0b1220;
          box-shadow: 0 12px 36px #0008;
          color: #e2e8f0;
          font: 500 13px/1.4 "DM Sans", "Segoe UI", sans-serif;
        }
        .landing-editor-toolbar button,
        .landing-editor-toolbar a {
          display: inline-flex;
          min-height: 36px;
          align-items: center;
          justify-content: center;
          padding: 7px 11px;
          border: 1px solid #334155;
          border-radius: 8px;
          background: #172033;
          color: #f8fafc;
          font: inherit;
          text-decoration: none;
          cursor: pointer;
        }
        .landing-editor-toolbar button:first-of-type {
          border-color: #38bdf8;
          background: #0c4a6e;
        }
        .landing-editor-toolbar button:disabled {
          opacity: .5;
          cursor: not-allowed;
        }
        .landing-editor-status { padding: 0 4px; }
        .landing-editor-access-error {
          position: fixed;
          z-index: 1000;
          right: 16px;
          bottom: 16px;
          max-width: calc(100vw - 32px);
          padding: 12px 16px;
          border: 1px solid #7f1d1d;
          border-radius: 10px;
          background: #450a0a;
          color: #fee2e2;
          font: 500 13px/1.5 "DM Sans", "Segoe UI", sans-serif;
        }
        .inline-editable-text {
          outline: 1px dashed #38bdf8;
          outline-offset: 4px;
          cursor: text;
        }
        .inline-editable-text:hover { outline-style: solid; }
        .inline-editor-input {
          width: auto;
          min-width: 8ch;
          max-width: min(90vw, 70ch);
          padding: 3px 6px;
          border: 1px solid #38bdf8;
          border-radius: 5px;
          background: #0b1220;
          color: inherit;
          font: inherit;
          letter-spacing: inherit;
        }
        .inline-editor-textarea {
          display: block;
          min-width: min(280px, 80vw);
          resize: vertical;
          white-space: pre-wrap;
        }
        .inline-editable-content {
          position: relative;
          min-height: 42px;
          padding: 12px;
          border: 1px dashed #087f83;
          border-radius: 10px;
          background: rgba(8, 127, 131, .035);
        }
        .inline-editable-content-action {
          display: inline-flex;
          min-height: 32px;
          align-items: center;
          gap: 6px;
          margin: 0 0 10px;
          padding: 5px 10px;
          border: 1px solid #b9d9d5;
          border-radius: 7px;
          background: #fff;
          color: #155e59;
          font: 700 12px/1.3 "DM Sans", "Segoe UI", sans-serif;
          cursor: pointer;
        }
        .inline-editable-content-action:hover { background: #eff8f4; }
        .inline-editable-content-action:focus-visible {
          outline: 2px solid #087f83;
          outline-offset: 2px;
        }
        .inline-editable-content-preview > :last-child { margin-bottom: 0; }
        .inline-editable-content-empty {
          margin: 0;
          color: #627386;
          font-size: 13px;
          font-style: italic;
        }
        .inline-editor-content-input {
          width: 100%;
          min-width: 0;
          max-width: 100%;
          box-sizing: border-box;
          background: #fff;
          color: #102033;
        }
        .inline-hero-highlight {
          color: transparent;
          background: linear-gradient(95deg, #38bdf8, #818cf8 54%, #a3e635);
          background-clip: text;
          -webkit-background-clip: text;
        }
        @media (max-width: 600px) {
          .landing-section-panel {
            top: auto;
            right: 8px;
            bottom: 74px;
            left: 8px;
            width: auto;
            max-height: min(74vh, 640px);
          }
          .landing-section-panel-embedded { bottom: 8px; }
          .landing-editor-toolbar {
            right: 8px;
            bottom: 8px;
            left: 8px;
            max-width: none;
          }
          .landing-editor-status { flex: 1 0 100%; }
        }
      `}</style>
    </LandingEditorContext.Provider>
  );
}
