/**
 * Canvas Style Provider
 *
 * Provides canvas style context to all child components.
 * Injects CSS variables scoped to the canvas container (not global).
 */

import React, { createContext, useContext, useRef, useLayoutEffect, type RefCallback } from 'react';
import { atom, useAtom, useSetAtom } from 'jotai';
import { type CanvasStyleGuide, type RequiredCanvasStyleGuide, mergeWithDefaults, defaultLightStyles } from '../styles';

// --- Atoms for programmatic theme control ---

/**
 * Atom holding the current canvas styles
 */
export const canvasStylesAtom = atom<RequiredCanvasStyleGuide>(defaultLightStyles);

/**
 * Atom for dark mode preference
 */
export const canvasIsDarkAtom = atom<boolean>(false);

/**
 * Atom for custom style overrides
 */
export const canvasStyleOverridesAtom = atom<CanvasStyleGuide | undefined>(undefined);

// --- Context for container ref ---

interface CanvasStyleContextValue {
	styles: RequiredCanvasStyleGuide;
	containerRef: React.RefObject<HTMLDivElement | null>;
	setStyles: (styles: CanvasStyleGuide) => void;
	setDarkMode: (isDark: boolean) => void;
}

const CanvasStyleContext = createContext<CanvasStyleContextValue | null>(null);

// --- CSS Variable names ---

const CSS_VARIABLE_NAMES = [
	'--canvas-bg',
	'--node-bg',
	'--node-border',
	'--node-radius',
	'--node-selected-border',
	'--node-selected-glow',
	'--node-shadow',
	'--node-shadow-opacity',
	'--node-dragging-bg',
	'--node-dragging-border',
	'--node-resize-handle',
	'--node-resize-handle-active',
	'--edge-color',
	'--edge-weight',
	'--edge-label-color',
	'--edge-label-stroke',
] as const;

// --- Helper to apply styles to an element ---

function applyStylesToElement(element: HTMLElement, styles: RequiredCanvasStyleGuide): void {
	const { nodes, edges, background } = styles;

	// Background
	element.style.setProperty('--canvas-bg', background.color || '');

	// Node Styles
	element.style.setProperty('--node-bg', nodes.defaultBackground || '');
	element.style.setProperty('--node-border', nodes.defaultBorderColor || '');
	element.style.setProperty('--node-radius', nodes.defaultBorderRadius ? `${nodes.defaultBorderRadius}px` : '8px');
	element.style.setProperty('--node-selected-border', nodes.selectedBorderColor || '');
	element.style.setProperty('--node-selected-glow', nodes.selectedGlowColor || '');
	element.style.setProperty('--node-shadow', nodes.shadowColor || '');
	element.style.setProperty('--node-shadow-opacity', nodes.shadowOpacity?.toString() || '0.2');
	element.style.setProperty('--node-dragging-bg', nodes.draggingBackgroundColor || '');
	element.style.setProperty('--node-dragging-border', nodes.draggingBorderColor || '');
	element.style.setProperty('--node-resize-handle', nodes.resizeHandleColor || '');
	element.style.setProperty('--node-resize-handle-active', nodes.resizeHandleActiveColor || '');

	// Edge Styles
	element.style.setProperty('--edge-color', edges.defaultColor || '');
	element.style.setProperty('--edge-weight', edges.defaultWeight?.toString() || '1.5');
	element.style.setProperty('--edge-label-color', edges.labelColor || '');
	element.style.setProperty('--edge-label-stroke', edges.labelStrokeColor || '');
}

function removeStylesFromElement(element: HTMLElement): void {
	CSS_VARIABLE_NAMES.forEach((name) => {
		element.style.removeProperty(name);
	});
}

// --- Provider Props ---

export interface CanvasStyleProviderProps {
	children: React.ReactNode;
	/** Custom styles to apply */
	styles?: CanvasStyleGuide;
	/** Whether to use dark theme defaults */
	isDark?: boolean;
}

/**
 * Provides canvas style context to all child components.
 * CSS variables are scoped to the canvas container, not the document root.
 *
 * @example
 * ```tsx
 * <CanvasStyleProvider isDark={true} styles={{ nodes: { defaultBackground: '#1a1a1a' } }}>
 *   <Canvas ... />
 * </CanvasStyleProvider>
 * ```
 */
export function CanvasStyleProvider({ children, styles: propStyles, isDark: propIsDark = false }: CanvasStyleProviderProps) {
	const containerRef = useRef<HTMLDivElement | null>(null);
	const [styleOverrides, setStyleOverrides] = useAtom(canvasStyleOverridesAtom);
	const [isDarkAtom, setIsDarkAtom] = useAtom(canvasIsDarkAtom);
	const setCanvasStyles = useSetAtom(canvasStylesAtom);

	// Merge props with atom overrides (atom takes precedence for programmatic control)
	const effectiveStyles = styleOverrides ?? propStyles;
	const effectiveIsDark = isDarkAtom || propIsDark;

	const mergedStyles = mergeWithDefaults(effectiveStyles, effectiveIsDark);

	// Update the global atom so other components can access current styles
	useLayoutEffect(() => {
		setCanvasStyles(mergedStyles);
	}, [mergedStyles, setCanvasStyles]);

	// Ref callback with cleanup — React 19 calls cleanup on unmount or when
	// the callback identity changes. The compiler auto-memoizes this based on
	// mergedStyles, so style changes trigger cleanup → re-apply automatically.
	const containerRefCallback: RefCallback<HTMLDivElement> = (node) => {
		containerRef.current = node;
		if (!node) return;
		applyStylesToElement(node, mergedStyles);
		return () => removeStylesFromElement(node);
	};

	// Programmatic control functions
	const setStyles = (newStyles: CanvasStyleGuide) => {
		setStyleOverrides(newStyles);
	};

	const setDarkMode = (isDark: boolean) => {
		setIsDarkAtom(isDark);
	};

	const contextValue: CanvasStyleContextValue = {
		styles: mergedStyles,
		containerRef,
		setStyles,
		setDarkMode,
	};

	return (
		<CanvasStyleContext value={contextValue}>
			<div
				ref={containerRefCallback}
				className="canvas-style-scope"
				style={{
					display: 'contents', // Don't affect layout
				}}
			>
				{children}
			</div>
		</CanvasStyleContext>
	);
}

/**
 * Hook to access the current canvas style configuration.
 * Returns fully resolved styles (merged with defaults) and control functions.
 */
export function useCanvasStyle(): RequiredCanvasStyleGuide {
	const context = useContext(CanvasStyleContext);
	if (!context) {
		throw new Error('useCanvasStyle must be used within a CanvasStyleProvider');
	}
	return context.styles;
}

/**
 * Hook for programmatic theme control.
 * Allows changing styles and dark mode at runtime.
 *
 * @example
 * ```tsx
 * function ThemeToggle() {
 *   const { setDarkMode, setStyles, isDark, styles } = useCanvasTheme();
 *
 *   return (
 *     <button onClick={() => setDarkMode(!isDark)}>
 *       Toggle Dark Mode
 *     </button>
 *   );
 * }
 * ```
 */
export function useCanvasTheme() {
	const context = useContext(CanvasStyleContext);
	const [isDark, setIsDarkAtom] = useAtom(canvasIsDarkAtom);
	const [styleOverrides, setStyleOverrides] = useAtom(canvasStyleOverridesAtom);

	if (!context) {
		throw new Error('useCanvasTheme must be used within a CanvasStyleProvider');
	}

	return {
		/** Current resolved styles */
		styles: context.styles,
		/** Whether dark mode is active */
		isDark,
		/** Current style overrides */
		styleOverrides,
		/** Set dark mode on/off */
		setDarkMode: context.setDarkMode,
		/** Set style overrides (partial, merged with defaults) */
		setStyles: context.setStyles,
		/** Reset to default styles */
		resetStyles: () => {
			setStyleOverrides(undefined);
			setIsDarkAtom(false);
		},
	};
}
