import React, { useState } from 'react';
import { cn } from '@/lib/utils';
import { useImageDrop } from '@/hooks/useImageDrop';
import { Button } from '@/components/ui/button';
import { Plus, Minus, Grid3X3, Trash2, Settings, ArrowUp, ArrowDown, GripVertical } from 'lucide-react';
import { LayoutContainer as LayoutContainerType, WidgetInstance } from '@/modules/layout/LayoutManager';
import { widgetRegistry } from '@/lib/widgetRegistry';
import { WidgetSettingsManager } from '@/components/widgets/WidgetSettingsManager';
import { WidgetMovementControls } from '@/components/widgets/WidgetMovementControls';
import { useLayout } from '@/modules/layout/LayoutContext';
import CollapsibleSection from '@/components/CollapsibleSection';
import { ContainerSettingsManager } from '@/components/containers/ContainerSettingsManager';
import { useDraggable, useDroppable } from '@dnd-kit/core';
import { LayoutContainer } from './LayoutContainer';
// Droppable slot for empty areas (column indicators, add-widget buttons)
const DroppableSlot: React.FC<{
containerId: string;
index: number;
isEditMode: boolean;
children: React.ReactNode;
}> = ({ containerId, index, isEditMode, children }) => {
const { setNodeRef, isOver } = useDroppable({
id: `slot-${containerId}-${index}`,
data: {
type: 'container-cell',
containerId,
index,
},
disabled: !isEditMode,
});
return (
{children}
);
};
export interface LayoutContainerEditProps {
container: LayoutContainerType;
isEditMode: boolean;
pageId: string;
selectedContainerId?: string | null;
onSelect?: (containerId: string, pageId?: string) => void;
onAddWidget?: (containerId: string, targetColumn?: number) => void;
onRemoveWidget?: (widgetInstanceId: string) => void;
onMoveWidget?: (widgetInstanceId: string, direction: 'up' | 'down' | 'left' | 'right') => void;
onUpdateColumns?: (containerId: string, columns: number) => void;
onUpdateSettings?: (containerId: string, settings: Partial) => void;
onAddContainer?: (parentContainerId: string) => void;
onRemoveContainer?: (containerId: string) => void;
onMoveContainer?: (containerId: string, direction: 'up' | 'down') => void;
canMoveContainerUp?: boolean;
canMoveContainerDown?: boolean;
selectedWidgetId?: string | null;
selectedWidgetIds?: Set;
onSelectWidget?: (widgetId: string, pageId?: string) => void;
depth?: number;
isCompactMode?: boolean;
editingWidgetId?: string | null;
onEditWidget?: (widgetId: string | null) => void;
newlyAddedWidgetId?: string | null;
contextVariables?: Record;
pageContext?: Record;
onFilesDrop?: (files: File[], targetColumn?: number) => void;
}
const LayoutContainerEditComponent: React.FC = ({
container,
isEditMode,
pageId,
selectedContainerId,
onSelect,
onAddWidget,
onRemoveWidget,
onMoveWidget,
onUpdateColumns,
onUpdateSettings,
onAddContainer,
onRemoveContainer,
onMoveContainer,
canMoveContainerUp,
canMoveContainerDown,
selectedWidgetId,
selectedWidgetIds,
onSelectWidget,
depth = 0,
isCompactMode = false,
editingWidgetId,
onEditWidget,
newlyAddedWidgetId,
contextVariables,
pageContext,
onFilesDrop,
}) => {
const { isDragging, handlers } = useImageDrop({
onFilesDrop: (files) => onFilesDrop?.(files),
isEditMode: isEditMode && !!onFilesDrop,
});
const maxDepth = 3; // Limit nesting depth
const canNest = depth < maxDepth;
const isSelected = selectedContainerId === container.id;
const [showContainerSettings, setShowContainerSettings] = useState(false);
// Generate responsive grid classes based on container.columns
const getGridClasses = (columns: number) => {
const baseClass = "grid"; // Always grid with gap
// Mobile: always 1 column, Desktop: respect container.columns
switch (columns) {
case 1: return `${baseClass} grid-cols-1`;
case 2: return `${baseClass} grid-cols-1 md:grid-cols-2`;
case 3: return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-3`;
case 4: return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-4`;
case 5: return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5`;
case 6: return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6`;
default:
// For 7+ columns, use a more conservative approach
return `${baseClass} grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-${Math.min(columns, 12)}`;
}
};
const gridClasses = getGridClasses(container.columns);
// Extract container content rendering logic
const renderContainerContent = () => (
<>
{/* Grid Column Indicators (only in edit mode when selected) */}
{isEditMode && isSelected && container.widgets.length === 0 && container.children.length === 0 && (
<>
{Array.from({ length: container.columns }, (_, i) => (
{
e.stopPropagation();
onAddWidget?.(container.id, i);
}}
title={`Double-click to add widget to column ${i + 1}`}
>
Col {i + 1}
))}
>
)}
{/* Render Widgets */}
{container.widgets
.sort((a, b) => (a.order || 0) - (b.order || 0))
.map((widget, index) => (
onSelectWidget?.(widget.id, pageId)}
canMoveUp={index > 0}
canMoveDown={index < container.widgets.length - 1}
onRemove={onRemoveWidget}
onMove={onMoveWidget}
isEditing={editingWidgetId === widget.id}
onEditWidget={onEditWidget}
isNew={newlyAddedWidgetId === widget.id}
selectedWidgetId={selectedWidgetId}
onSelectWidget={onSelectWidget}
editingWidgetId={editingWidgetId}
contextVariables={contextVariables}
pageContext={pageContext}
selectedContainerId={selectedContainerId}
onSelectContainer={onSelect}
/>
))}
{/* Add Widget Buttons - always visible in edit mode at bottom of every container */}
{isEditMode && (
<>
{container.columns > 1 ? (
{Array.from({ length: container.columns }, (_, colIndex) => (
{
e.stopPropagation();
onAddWidget?.(container.id, colIndex);
}}
title={`Add widget to column ${colIndex + 1}`}
>
))}
) : (
{
e.stopPropagation();
onAddWidget?.(container.id);
}}
title="Add widget"
>
)}
>
)}
{/* Render Nested Containers */}
{container.children
.sort((a, b) => (a.order || 0) - (b.order || 0))
.map((childContainer) => (
))}
{/* Empty State - only show when not showing column indicators */}
{container.widgets.length === 0 && container.children.length === 0 && !(isEditMode && isSelected) && !isEditMode && (
)}
>
);
// Handle Enabled State
const isContainerEnabled = container.settings?.enabled !== false; // Default to true
if (!isContainerEnabled && !isEditMode) {
return null;
}
return (
{/* Edit Mode Controls */}
{isEditMode && (
{
e.stopPropagation();
if (isEditMode) {
onSelect?.(container.id, pageId);
}
}}
>
{/* Responsive layout: title and buttons wrap on small screens */}
{container.settings?.showTitle && container.settings?.title
? container.settings.title
: `Container (${container.columns} col${container.columns !== 1 ? 's' : ''})`}
{(container.settings?.collapsible || container.settings?.showTitle) && (
⚙️
)}
{/* Minimalist button row - wraps and justifies to the end */}
{/* Move controls for root containers */}
{depth === 0 && (
)}
{/* Column controls */}
{container.columns}
{/* Add widget button */}
{/* Add nested container button */}
{canNest && (
)}
{/* Container settings button */}
{/* Remove container button */}
)}
{/* Container Content */}
{
e.stopPropagation();
if (isEditMode) {
onSelect?.(container.id, pageId);
}
}}
>
{container.settings?.collapsible ? (
{renderContainerContent()}
) : (
{/* Title for non-collapsible containers */}
{container.settings?.showTitle && (
{container.settings?.title || `Container (${container.columns} col${container.columns !== 1 ? 's' : ''})`}
)}
{renderContainerContent()}
)}
{/* Drop Overlay */}
{/* Container Settings Dialog */}
{showContainerSettings && (
setShowContainerSettings(false)}
onSave={(settings) => {
onUpdateSettings?.(container.id, settings);
setShowContainerSettings(false);
}}
currentSettings={container.settings}
containerInfo={{
id: container.id,
columns: container.columns,
}}
/>
)}
);
};
interface WidgetItemProps {
widget: WidgetInstance;
index: number;
containerId: string;
isEditMode: boolean;
pageId: string;
canMoveUp: boolean;
canMoveDown: boolean;
onRemove?: (widgetInstanceId: string) => void;
onMove?: (widgetInstanceId: string, direction: 'up' | 'down' | 'left' | 'right') => void;
isSelected?: boolean;
onSelect?: () => void;
isEditing?: boolean;
onEditWidget?: (widgetId: string | null) => void;
isNew?: boolean;
selectedWidgetId?: string | null;
onSelectWidget?: (widgetId: string, pageId?: string) => void;
editingWidgetId?: string | null;
contextVariables?: Record;
pageContext?: Record;
selectedContainerId?: string | null;
onSelectContainer?: (containerId: string, pageId?: string) => void;
}
const WidgetItem: React.FC = ({
widget,
index,
containerId,
isEditMode,
pageId,
canMoveUp,
canMoveDown,
onRemove,
onMove,
isSelected,
onSelect,
isEditing,
onEditWidget,
isNew,
selectedWidgetId,
onSelectWidget,
editingWidgetId,
contextVariables,
pageContext,
selectedContainerId,
onSelectContainer,
}) => {
const widgetDefinition = widgetRegistry.get(widget.widgetId);
const { updateWidgetProps, renameWidget } = useLayout();
// DnD hooks (only active in edit mode)
const { attributes, listeners, setNodeRef: setDragRef, isDragging: isDndDragging } = useDraggable({
id: `widget-${widget.id}`,
data: {
type: 'existing-widget',
widgetInstanceId: widget.id,
widgetDefId: widget.widgetId,
sourceContainerId: containerId,
sourceIndex: index,
},
disabled: !isEditMode,
});
const { setNodeRef: setDropRef, isOver } = useDroppable({
id: `cell-${containerId}-${index}`,
data: {
type: 'container-cell',
containerId,
index,
},
disabled: !isEditMode,
});
// Combine refs
const combinedRef = (node: HTMLElement | null) => {
setDragRef(node);
setDropRef(node);
};
// Internal state removed in favor of controlled state
// const [showSettingsModal, setShowSettingsModal] = useState(false);
const handlePropsChange = React.useCallback(async (newProps: Record) => {
try {
await updateWidgetProps(pageId, widget.id, newProps);
} catch (error) {
console.error('Failed to update widget props:', error);
}
}, [pageId, widget.id, updateWidgetProps]);
// pageId is now passed as a prop from the parent component
if (!widgetDefinition) {
return (
Widget "{widget.widgetId}" not found in registry
{isEditMode && (
)}
);
}
const WidgetComponent = widgetDefinition.component;
const handleSettingsSave = async (settings: Record) => {
try {
await updateWidgetProps(pageId, widget.id, settings);
} catch (error) {
console.error('Failed to save widget settings:', error);
}
};
const handleSettingsCancel = () => {
if (isNew) {
onRemove?.(widget.id);
}
};
// Handle Enabled State
const isEnabled = widget.props?.enabled !== false; // Default to true
if (!isEnabled && !isEditMode) {
return null;
}
return (
);
};
export default LayoutContainerEditComponent;