/**
 * PageOrganizer — top-level React island for /admin/layout.
 *
 * Responsibilities:
 * - Hydrate the zustand store from the API on mount.
 * - Autosave the draft with a 500 ms debounce whenever `body` changes.
 * - Wrap the layout in LocaleProvider + ErrorBoundary.
 * - Provide shared DndContext (ancestor of both Palette and Canvas).
 */

import { useCallback, useEffect, useState, useRef, lazy, Suspense } from 'react';
import { Spinner } from '@/components/ui/feedback/Spinner';
import {
  DndContext,
  PointerSensor,
  KeyboardSensor,
  useSensor,
  useSensors,
  closestCenter,
} from '@dnd-kit/core';
import { sortableKeyboardCoordinates } from '@dnd-kit/sortable';
import { useOrganizer } from './state';
import { fetchPage, saveDraft } from './api';
import { Palette } from './Palette';
import { Canvas } from './Canvas';
import { makeDragEndHandler } from './Canvas';
// Lazy-load heavy overlays — ConfigDrawer opens on selection; RichTextEditDialog
// pulls @tiptap (150-200 KB) and is only shown when editing rich-text modules.
const ConfigDrawer = lazy(() =>
  import('./ConfigDrawer').then((m) => ({ default: m.ConfigDrawer })),
);
const RichTextEditDialog = lazy(() =>
  import('./RichTextEditDialog').then((m) => ({ default: m.RichTextEditDialog })),
);
import { Toolbar } from './Toolbar';
import { PreviewPanel } from './PreviewPanel';
import { notify } from '@/lib/query/toast-bridge';
import { captureCaught } from '@/lib/observability';
import { useT } from '@/lib/i18n/react';
import { Container } from '@/components/ui/layout/Container';
import { LocaleProvider } from '@/lib/i18n/react';
import { ErrorBoundary } from '@/components/ui/feedback/ErrorBoundary';
import type { LayoutBody } from '@/server/page-layout/types';

// ─── Inner (must be inside LocaleProvider to use hooks) ───────────────────────

function Inner({ page: initialPage }: { page: string }) {
  const t = useT('page_organizer');
  const body = useOrganizer((s) => s.body);
  const hydrate = useOrganizer((s) => s.hydrate);
  const setSaving = useOrganizer((s) => s.setSaving);
  const device = useOrganizer((s) => s.device);
  const add = useOrganizer((s) => s.add);
  const remove = useOrganizer((s) => s.remove);
  const reorder = useOrganizer((s) => s.reorder);

  const [currentPage, setCurrentPage] = useState(initialPage);
  const [fetchError, setFetchError] = useState<string | null>(null);
  // Prevents autosave from firing on the body change caused by hydrate().
  // Set true before calling hydrate(), cleared in a setTimeout(0) so it
  // remains true for the entire synchronous React commit that processes the
  // hydrated body, then becomes false for subsequent user-driven mutations.
  const isHydratingRef = useRef(false);

  const items = body[device];

  const handlePageChange = useCallback((newPage: string) => {
    window.history.replaceState(null, '', `?page=${newPage}`);
    setCurrentPage(newPage);
  }, []);

  // Hydrate on mount and when currentPage changes
  useEffect(() => {
    // Raise guard and wipe stale body BEFORE the fetch so the canvas never shows
    // the previous page's modules during the async load, and the autosave effect
    // (which runs after the body-change re-render) sees isHydratingRef===true
    // and skips the PUT for the now-irrelevant previous body.
    isHydratingRef.current = true;
    useOrganizer.setState({ body: { mobile: [], desktop: [] }, updatedAt: null, saving: 'idle' });

    void fetchPage(currentPage)
      .then((r) => {
        setFetchError(null);
        const initial = (r.draft?.body ?? r.liveVersion.body) as LayoutBody;
        hydrate(initial, r.draft?.updatedAt ?? null);
        setTimeout(() => {
          isHydratingRef.current = false;
          // No saved draft exists: seed one from the live body immediately so the
          // preview iframe renders the same sections the canvas is showing.
          if (!r.draft) {
            setSaving('saving');
            void saveDraft(currentPage, initial, null)
              .then((res) => {
                useOrganizer.setState({ updatedAt: res.updatedAt, saving: 'saved' });
              })
              .catch((err: unknown) => {
                captureCaught(err, {
                  scope: 'features.admin-page-organizer.PageOrganizer.seed-draft',
                });
                setSaving('error');
              });
          }
        }, 0);
      })
      .catch((err: unknown) => {
        isHydratingRef.current = false;
        captureCaught(err, { scope: 'features.admin-page-organizer.PageOrganizer.fetch' });
        setFetchError((err as Error).message ?? t('fetch_error'));
      });
  }, [currentPage, hydrate, setSaving, t]);

  // Autosave: debounced 500 ms on body changes, skipped during hydration
  useEffect(() => {
    // Skip while hydrate() is being committed — prevents spurious PUT on load/page-switch
    if (isHydratingRef.current) return;
    // Skip if body is not yet hydrated (store initial state)
    if (!body.mobile.length && !body.desktop.length) return;

    setSaving('saving');
    const id = setTimeout(() => {
      // Read updatedAt from the store at fire time — not from the closure — so a
      // save that completes between the effect running and this callback firing
      // doesn't cause the next PUT to send a stale updatedAt → 409.
      const currentUpdatedAt = useOrganizer.getState().updatedAt;
      void (async () => {
        try {
          const res = await saveDraft(currentPage, body, currentUpdatedAt);
          useOrganizer.setState({ updatedAt: res.updatedAt, saving: 'saved' });
        } catch (err: unknown) {
          const status = (err as { status?: number }).status;
          const conflictAt = (err as { currentUpdatedAt?: string }).currentUpdatedAt;
          if (status === 409 && conflictAt) {
            // Self-race: a previous save completed while this one was in-flight,
            // advancing draftUpdatedAt before we got the new value. Sync our
            // local timestamp and retry once — transparent to the user.
            useOrganizer.setState({ updatedAt: conflictAt });
            try {
              const retryRes = await saveDraft(currentPage, body, conflictAt);
              useOrganizer.setState({ updatedAt: retryRes.updatedAt, saving: 'saved' });
            } catch (retryErr) {
              captureCaught(retryErr, {
                scope: 'features.admin-page-organizer.PageOrganizer.autosave-retry',
              });
              setSaving('conflict');
            }
          } else {
            setSaving(status === 409 ? 'conflict' : 'error');
            if (status !== 409) {
              notify.error(t('saving_error'));
            }
          }
        }
      })();
    }, 500);

    return () => clearTimeout(id);
  }, [body, currentPage, setSaving, t]);

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
  );

  const onDragEnd = makeDragEndHandler({ items, device, add, remove, reorder });

  return (
    <Container maxWidth="full" px="4" className="py-6">
      <Toolbar page={currentPage} onPageChange={handlePageChange} />
      <DndContext
        sensors={sensors}
        collisionDetection={closestCenter}
        onDragEnd={onDragEnd}
        accessibility={{ announcements: undefined }}
      >
        {/* Desktop: Palette | Preview | Canvas | ConfigDrawer (overlay) */}
        {/* Mobile: stacked full-width */}
        {fetchError && (
          <p className="text-feedback-error bg-feedback-error/10 mt-4 rounded p-3 text-sm">
            {fetchError}
          </p>
        )}
        <div className="mt-4 grid grid-cols-1 gap-4 lg:grid-cols-[256px_1fr_280px]">
          <Palette />
          {/* Live preview occupies the centre column on desktop */}
          <div className="hidden min-h-(--page-organizer-preview-height) lg:flex lg:flex-col">
            <PreviewPanel page={currentPage} />
          </div>
          <Canvas />
          {/* Lazy overlays: ConfigDrawer (selection-triggered) + RichTextEditDialog (tiptap) */}
          <Suspense fallback={<Spinner />}>
            <ConfigDrawer />
            <RichTextEditDialog />
          </Suspense>
        </div>
      </DndContext>
    </Container>
  );
}

// ─── Public export ─────────────────────────────────────────────────────────────

export function PageOrganizer({ page }: { page: string }) {
  return (
    <LocaleProvider>
      <ErrorBoundary>
        <Inner page={page} />
      </ErrorBoundary>
    </LocaleProvider>
  );
}
