import React, {
  createContext,
  useContext,
  useState,
  useCallback,
  useEffect,
} from 'react';
import { twMerge } from 'tailwind-merge';
import {
  DesignModeMessage,
  ParentToIframeMessage,
  IframeToParentMessage,
  BridgeConfig,
  ElementInfo,
  SourceInfo,
  ToggleDesignModeMessage,
  UpdateStyleMessage,
  UpdateContentMessage,
  BatchUpdateMessage,
  HeartbeatMessage,
  HealthCheckMessage,
  HealthCheckResponseMessage,
} from '@xagi/design-mode-shared/messages';
import { bridge, messageValidator } from './bridge';
import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
import { extractSourceInfo as extractSourceInfoFromAttributes } from '@xagi/design-mode-shared/sourceInfo';
import { resolveSourceInfo } from '@xagi/design-mode-shared/sourceInfoResolver';

export interface Modification {
  id: string;
  element: string;
  type: 'class' | 'style';
  oldValue: string;
  newValue: string;
  timestamp: number;
}

export interface DesignModeConfig {
  enabled?: boolean;
  iframeMode?: {
    enabled: boolean;
    hideUI: boolean;
    enableSelection: boolean;
    enableDirectEdit: boolean;
  };
  batchUpdate?: {
    enabled: boolean;
    debounceMs: number;
  };
  bridge?: Partial<BridgeConfig>;
}

interface DesignModeContextType {
  // State
  isDesignMode: boolean;
  selectedElement: HTMLElement | null;
  modifications: Modification[];
  isConnected: boolean;
  bridgeStatus: 'connected' | 'disconnected' | 'connecting' | 'error';

  // Config
  config: DesignModeConfig;

  // Actions
  toggleDesignMode: () => void;
  selectElement: (element: HTMLElement | null) => void;
  modifyElementClass: (element: HTMLElement, newClass: string) => Promise<void>;
  updateElementContent: (
    element: HTMLElement,
    newContent: string
  ) => Promise<void>;
  batchUpdateElements: (
    updates: Array<{
      element: HTMLElement;
      type: 'style' | 'content';
      newValue: string;
      originalValue?: string;
    }>
  ) => Promise<void>;
  resetModifications: () => void;

  // Bridge helpers
  sendMessage: <T extends DesignModeMessage>(message: T) => Promise<void>;
  sendMessageWithResponse: <
    T extends DesignModeMessage,
    R extends DesignModeMessage,
  >(
    message: T,
    responseType: R['type']
  ) => Promise<R>;
  healthCheck: () => Promise<any>;
}

const DesignModeContext = createContext<DesignModeContextType | undefined>(
  undefined
);

export const DesignModeProvider: React.FC<{
  children: React.ReactNode;
  config?: DesignModeConfig;
}> = ({ children, config: userConfig = {} }) => {
  // Defaults
  const defaultConfig: DesignModeConfig = {
    enabled: true,
    iframeMode: {
      enabled: true,
      hideUI: false,
      enableSelection: true,
      enableDirectEdit: true,
    },
    batchUpdate: {
      enabled: true,
      debounceMs: 300,
    },
    bridge: {
      timeout: 10000,
      retryAttempts: 3,
      heartbeatInterval: 30000,
      debug: process.env.NODE_ENV === 'development',
    },
    ...userConfig,
  };

  // State
  const [isDesignMode, setIsDesignMode] = useState(false);
  const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(
    null
  );
  const [modifications, setModifications] = useState<Modification[]>([]);
  const [isConnected, setIsConnected] = useState(false);
  const [bridgeStatus, setBridgeStatus] = useState<
    'connected' | 'disconnected' | 'connecting' | 'error'
  >('connecting');
  const [config] = useState<DesignModeConfig>(defaultConfig);

  // Batch debounce state
  const [batchUpdateTimer, setBatchUpdateTimer] =
    useState<NodeJS.Timeout | null>(null);
  const [pendingBatchUpdates, setPendingBatchUpdates] = useState<
    Array<{
      element: HTMLElement;
      type: 'style' | 'content';
      newValue: string;
      originalValue?: string;
    }>
  >([]);

  /**
   * Wire iframe bridge listeners
   */
  useEffect(() => {
    // Bridge setup
    if (config.iframeMode?.enabled) {
      setBridgeStatus('connecting');

      // Poll isConnected
      const connectionCheck = setInterval(() => {
        const connected = bridge.isConnected();
        setIsConnected(connected);
        setBridgeStatus(connected ? 'connected' : 'disconnected');
      }, 1000);

      // Subscriptions
      const unsubscribeHandlers: (() => void)[] = [];

      // TOGGLE_DESIGN_MODE
      unsubscribeHandlers.push(
        bridge.on<ToggleDesignModeMessage>('TOGGLE_DESIGN_MODE', message => {
          const newState = message.enabled;
          setIsDesignMode(newState);

          // Echo DESIGN_MODE_CHANGED
          if (window.self !== window.top) {
            sendToParent({
              type: 'DESIGN_MODE_CHANGED',
              enabled: newState,
              requestId: message.requestId,
              timestamp: Date.now(),
            });
          }
        })
      );

      // UPDATE_STYLE
      unsubscribeHandlers.push(
        bridge.on<UpdateStyleMessage>('UPDATE_STYLE', async message => {
          await handleExternalStyleUpdate(message);
        })
      );

      // UPDATE_CONTENT
      unsubscribeHandlers.push(
        bridge.on<UpdateContentMessage>('UPDATE_CONTENT', async message => {
          await handleExternalContentUpdate(message);
        })
      );

      // BATCH_UPDATE
      unsubscribeHandlers.push(
        bridge.on<BatchUpdateMessage>('BATCH_UPDATE', async message => {
          await handleExternalBatchUpdate(message);
        })
      );

      // HEARTBEAT
      unsubscribeHandlers.push(
        bridge.on<HeartbeatMessage>('HEARTBEAT', _ => {
          // Echo HEARTBEAT
          bridge.send({
            type: 'HEARTBEAT',
            payload: { timestamp: Date.now() },
            timestamp: Date.now(),
          });
        })
      );

      // HEALTH_CHECK
      unsubscribeHandlers.push(
        bridge.on<HealthCheckMessage>('HEALTH_CHECK', async message => {
          const healthStatus = await bridge.healthCheck();
          // Use a type assertion or construct the object carefully to match HealthCheckResponseMessage
          const response: HealthCheckResponseMessage = {
            type: 'HEALTH_CHECK_RESPONSE',
            payload: {
              status: healthStatus.status === 'healthy' ? 'healthy' : 'unhealthy',
              version: '2.0.0',
              uptime: Date.now() - ((window as any).__startTime || 0),
            },
            requestId: message.requestId || '', // Provide requestId
            timestamp: Date.now(),
          };
          bridge.send(response);
        })
      );

      // Initial health probe
      const initialHealthCheck = setTimeout(async () => {
        try {
          const health = await bridge.healthCheck();
          setBridgeStatus(health.status === 'healthy' ? 'connected' : 'error');
        } catch (error) {
          setBridgeStatus('error');
        }
      }, 1000);

      return () => {
        clearInterval(connectionCheck);
        clearTimeout(initialHealthCheck);
        unsubscribeHandlers.forEach(unsubscribe => unsubscribe());
      };
    }
  }, [config]);

  /**
   * postMessage when iframe + connected
   */
  const sendToParent = useCallback(
    (message: IframeToParentMessage) => {
      if (!config.iframeMode?.enabled) {
        return;
      }

      // 优先走 bridge；bridge 未连通或发送失败时兜底直发 parent，避免关键回执丢失。
      if (bridge.isConnected()) {
        bridge.send(message).catch(error => {
          if (window.self !== window.top) {
            window.parent.postMessage(message, '*');
          }
        });
        return;
      }

      if (window.self !== window.top) {
        window.parent.postMessage(message, '*');
      }
    },
    [config.iframeMode?.enabled]
  );

  /**
   * findElementBySourceInfo
   */
  const findElementBySourceInfo = useCallback(
    (sourceInfo: SourceInfo): HTMLElement | null => {
      // 1) element-id
      if (sourceInfo.elementId) {
        const element = document.querySelector(`[${AttributeNames.elementId}="${sourceInfo.elementId}"]`);
        if (element) return element as HTMLElement;
      }

      // 2) legacy file/line/column attrs
      const selector = `[${AttributeNames.file}="${sourceInfo.fileName}"][${AttributeNames.line}="${sourceInfo.lineNumber}"][${AttributeNames.column}="${sourceInfo.columnNumber}"]`;
      const element = document.querySelector(selector);
      if (element) return element as HTMLElement;

      // 3) children-source
      const childrenSourceValue = `${sourceInfo.fileName}:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`;
      const elementByChildrenSource = document.querySelector(`[${AttributeNames.childrenSource}="${childrenSourceValue}"]`);
      if (elementByChildrenSource) return elementByChildrenSource as HTMLElement;

      // 4) scan all -info
      const allElements = document.querySelectorAll(`[${AttributeNames.info}]`);
      for (let i = 0; i < allElements.length; i++) {
        const el = allElements[i] as HTMLElement;
        try {
          const infoStr = el.getAttribute(AttributeNames.info);
          if (infoStr) {
            const info = JSON.parse(infoStr);
            if (
              info.fileName === sourceInfo.fileName &&
              info.lineNumber === sourceInfo.lineNumber &&
              info.columnNumber === sourceInfo.columnNumber
            ) {
              return el;
            }
          }
        } catch (e) {
          // ignore parse error
        }
      }

      return null;
    },
    []
  );

  /**
   * Parent-driven style patch
   */
  const handleExternalStyleUpdate = useCallback(
    async (message: UpdateStyleMessage) => {
      if (!config.iframeMode?.enabled) return;

      const updateMessage = message;
      const { sourceInfo, newClass } = updateMessage.payload;

      try {
        // validate
        const validation = messageValidator.validate(updateMessage);
        if (!validation.isValid) {
          console.error(
            '[DesignMode] Invalid style update message:',
            validation.errors
          );
          return;
        }

        // resolve element
        const element = findElementBySourceInfo(sourceInfo);
        if (!element) {
          console.error(
            '[DesignMode] Element not found for sourceInfo:',
            sourceInfo
          );

          sendToParent({
            type: 'ERROR',
            payload: {
              code: 'ELEMENT_NOT_FOUND',
              message: `Element not found: ${sourceInfo.fileName}:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`,
              details: { sourceInfo },
            },
            timestamp: Date.now(),
          });
          return;
        }

        const oldClass = element.className;

        // Same element-id → list sync
        const elementId = element.getAttribute(AttributeNames.elementId);
        let relatedElements: HTMLElement[] = [element];

        if (elementId) {
          // query all [element-id]
          const allElementsWithId = Array.from(
            document.querySelectorAll(`[${AttributeNames.elementId}]`)
          ) as HTMLElement[];

          relatedElements = allElementsWithId.filter(el => {
            const elId = el.getAttribute(AttributeNames.elementId);
            return elId === elementId;
          });
        } else {
          console.warn('[DesignMode] Element missing element-id attribute, only updating current element');
        }

        // Apply class to all peers
        relatedElements.forEach(el => {
          el.setAttribute('data-ignore-mutation', 'true');
          el.className = newClass;
          // Use setTimeout to ensure MutationObserver sees the attribute
          setTimeout(() => {
            el.removeAttribute('data-ignore-mutation');
          }, 0);
        });


        // Refresh selection ref
        if (selectedElement === element) {
          setSelectedElement(element);
        }

        // (persist via API — commented)
        // try {
        //   const response = await fetch('/__appdev_design_mode/update', {
        //     method: 'POST',
        //     headers: {
        //       'Content-Type': 'application/json',
        //     },
        //     body: JSON.stringify({
        //       filePath: sourceInfo.fileName,
        //       line: sourceInfo.lineNumber,
        //       column: sourceInfo.columnNumber,
        //       newValue: newClass,
        //       type: 'style',
        //       originalValue: oldClass,
        //     }),
        //   });

        //   if (!response.ok) {
        //     throw new Error('Failed to update source');
        //   }
        // } catch (error) {
        //   console.error('[DesignMode] Error updating source:', error);
        //   throw error;
        // }

        // STYLE_UPDATED
        sendToParent({
          type: 'STYLE_UPDATED',
          payload: {
            sourceInfo,
            oldClass,
            newClass,
          },
          timestamp: Date.now(),
        });

      } catch (error) {
        console.error(
          '[DesignMode] Error handling external style update:',
          error
        );

        // ERROR
        sendToParent({
          type: 'ERROR',
          payload: {
            code: 'STYLE_UPDATE_FAILED',
            message: error instanceof Error ? error.message : 'Unknown error',
            details: { sourceInfo: updateMessage.payload.sourceInfo },
          },
          timestamp: Date.now(),
        });
      }
    },
    [
      selectedElement,
      config.iframeMode?.enabled,
      sendToParent,
      setSelectedElement,
      findElementBySourceInfo
    ]
  );

  /**
   * Parent-driven content patch
   */
  const handleExternalContentUpdate = useCallback(
    async (message: UpdateContentMessage) => {
      if (!config.iframeMode?.enabled) return;

      const updateMessage = message;
      const { sourceInfo, newContent } = updateMessage.payload;

      try {
        // validate
        const validation = messageValidator.validate(updateMessage);
        if (!validation.isValid) {
          console.error(
            '[DesignMode] Invalid content update message:',
            validation.errors
          );
          return;
        }

        // resolve element
        const element = findElementBySourceInfo(sourceInfo);
        if (!element) {
          console.error(
            '[DesignMode] Element not found for sourceInfo:',
            sourceInfo
          );

          sendToParent({
            type: 'ERROR',
            payload: {
              code: 'ELEMENT_NOT_FOUND',
              message: `Element not found: ${sourceInfo.fileName}:${sourceInfo.lineNumber}:${sourceInfo.columnNumber}`,
              details: { sourceInfo },
            },
            timestamp: Date.now(),
          });
          return;
        }

        const originalContent = element.innerText || element.textContent || '';

        // Same element-id → list sync
        const elementId = element.getAttribute(AttributeNames.elementId);
        let relatedElements: HTMLElement[] = [element];

        if (elementId) {
          // query all [element-id]
          const allElementsWithId = Array.from(
            document.querySelectorAll(`[${AttributeNames.elementId}]`)
          ) as HTMLElement[];

          relatedElements = allElementsWithId.filter(el => {
            const elId = el.getAttribute(AttributeNames.elementId);
            return elId === elementId;
          });
        } else {
          console.warn('[DesignMode] Element missing element-id attribute, only updating current element');
        }

        // Apply text to all peers (list sync)
        relatedElements.forEach(el => {
          el.setAttribute('data-ignore-mutation', 'true');
          el.innerText = newContent;
          // Use setTimeout to ensure MutationObserver sees the attribute
          setTimeout(() => {
            el.removeAttribute('data-ignore-mutation');
          }, 0);
        });


        // Refresh selection ref
        if (selectedElement === element) {
          setSelectedElement(element);
        }

        // persist flag (commented)
        // if (updateMessage.payload.persist !== false) {
        //   // Persist via API using extractSourceInfo (commented)
        //   const elementSourceInfo = extractSourceInfo(element);
        //   if (elementSourceInfo) {
        //     try {
        //       const response = await fetch('/__appdev_design_mode/update', {
        //         method: 'POST',
        //         headers: {
        //           'Content-Type': 'application/json',
        //         },
        //         body: JSON.stringify({
        //           filePath: elementSourceInfo.fileName,
        //           line: elementSourceInfo.lineNumber,
        //           column: elementSourceInfo.columnNumber,
        //           newValue: newContent,
        //           type: 'content',
        //           originalValue: originalContent,
        //         }),
        //       });

        //       if (!response.ok) {
        //         throw new Error('Failed to update source');
        //       }
        //     } catch (error) {
        //       console.error('[DesignMode] Error updating source:', error);
        //       throw error;
        //     }
        //   }
        // }

        // STYLE_UPDATED
        sendToParent({
          type: 'CONTENT_UPDATED_CALLBACK',
          payload: {
            sourceInfo,
            oldValue: originalContent,
            newValue: newContent,
          },
          timestamp: Date.now(),
        });

      } catch (error) {
        console.error(
          '[DesignMode] Error handling external content update:',
          error
        );

        // ERROR
        sendToParent({
          type: 'ERROR',
          payload: {
            code: 'CONTENT_UPDATE_FAILED',
            message: error instanceof Error ? error.message : 'Unknown error',
            details: { sourceInfo: updateMessage.payload.sourceInfo },
          },
          timestamp: Date.now(),
        });
      }
    },
    [
      selectedElement,
      config.iframeMode?.enabled,
      sendToParent,
      setSelectedElement,
      findElementBySourceInfo
    ]
  );

  /**
   * updateSource (HTTP)
   */
  const updateSource = useCallback(
    async (
      element: HTMLElement,
      newValue: string,
      type: 'style' | 'content',
      originalValue?: string
    ) => {
      const sourceInfo = extractSourceInfo(element);
      if (!sourceInfo) {
        throw new Error('Element does not have source mapping data');
      }

      try {
        const response = await fetch('/__appdev_design_mode/update', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            filePath: sourceInfo.fileName,
            line: sourceInfo.lineNumber,
            column: sourceInfo.columnNumber,
            newValue,
            type,
            originalValue,
          }),
        });

        if (!response.ok) {
          throw new Error('Failed to update source');
        }

      } catch (error) {
        console.error('[DesignMode] Error updating source:', error);
        throw error;
      }
    },
    []
  );

  /**
   * Parent BATCH_UPDATE
   */
  const handleExternalBatchUpdate = useCallback(
    async (message: BatchUpdateMessage) => {
      const updateMessage = message;
      const { updates } = updateMessage.payload;

      try {
        // validate
        const validation = messageValidator.validate(updateMessage);
        if (!validation.isValid) {
          console.error(
            '[DesignMode] Invalid batch update message:',
            validation.errors
          );
          return;
        }

        // Promise.allSettled items
        const results = await Promise.allSettled(
          updates.map(async (update: any) => {
            const element = findElementBySourceInfo(update.sourceInfo);
            if (!element) {
              throw new Error(
                `Element not found: ${update.sourceInfo.fileName}:${update.sourceInfo.lineNumber}`
              );
            }

            if (update.type === 'style') {
              element.setAttribute('data-ignore-mutation', 'true');
              element.className = update.newValue;
              setTimeout(() => element.removeAttribute('data-ignore-mutation'), 0);
            } else if (update.type === 'content') {
              element.setAttribute('data-ignore-mutation', 'true');
              element.innerText = update.newValue;
              setTimeout(() => element.removeAttribute('data-ignore-mutation'), 0);
            }

            await updateSource(
              element,
              update.newValue,
              update.type,
              update.originalValue
            );

            return { success: true, sourceInfo: update.sourceInfo };
          })
        );

      } catch (error) {
        console.error('[DesignMode] Error handling batch update:', error);

        // ERROR
        sendToParent({
          type: 'ERROR',
          payload: {
            code: 'BATCH_UPDATE_FAILED',
            message: error instanceof Error ? error.message : 'Unknown error',
            details: { updatesCount: updates?.length || 0 },
          },
          timestamp: Date.now(),
        });
      }
    },
    [findElementBySourceInfo, sendToParent, updateSource]
  );

  /**
   * selectElement
   */
  const selectElement = useCallback(
    async (element: HTMLElement | null) => {
      if (element && (element.hasAttribute(AttributeNames.staticContent)
        || element.hasAttribute(AttributeNames.staticClass))) {
        setSelectedElement(element);
      } else {
        setSelectedElement(null);
      }

      // iframe: ELEMENT_SELECTED
      if (element && config.iframeMode?.enabled) {
        // resolveSourceInfo for usage site

        const sourceInfo = resolveSourceInfo(element);
        // console.log('[DesignModeContext] selectElement - resolveSourceInfo:', sourceInfo);

        if (sourceInfo) {
          // Static text if attr + pure text


          const hasStaticContentAttr = element.hasAttribute(AttributeNames.staticContent);
          const isActuallyPureText = isPureStaticText(element);
          const isStaticText = hasStaticContentAttr && isActuallyPureText;

          // static-class attr

          const isStaticClass = element.hasAttribute(AttributeNames.staticClass);


          let textContent = '';
          if (isStaticText) {

            textContent = element.textContent || element.innerText || '';
          } else {

            textContent = element.innerText || element.textContent || '';
          }

          const elementInfo: ElementInfo = {
            tagName: element.tagName.toLowerCase(),
            className: element.className,
            textContent: textContent,
            sourceInfo,
            isStaticText: isStaticText || false,
            isStaticClass: isStaticClass,
          };

          sendToParent({
            type: 'ELEMENT_SELECTED',
            payload: { elementInfo },
            timestamp: Date.now(),
          });

        } else {
          console.warn(
            `[DesignMode] Element selected but could not resolve source info:`,
            element
          );
        }
      } else if (!element && config.iframeMode?.enabled) {
        sendToParent({
          type: 'ELEMENT_DESELECTED',
          timestamp: Date.now(),
        });
      }
    },
    [config.iframeMode?.enabled]
  );

  /**
   * toggleDesignMode
   */
  const toggleDesignMode = useCallback(() => {
    setIsDesignMode(prev => {
      const next = !prev;
      if (!next) {
        setSelectedElement(null);
      }
      return next;
    });
  }, []);

  /**
   * Local extractSourceInfo helper
   */
  const extractSourceInfo = useCallback(
    (element: HTMLElement): SourceInfo | null => {
      return extractSourceInfoFromAttributes(element);
    },
    []
  );

  /**
   * modifyElementClass
   */
  const modifyElementClass = useCallback(
    async (element: HTMLElement, newClass: string) => {
      const oldClasses = element.className;
      const mergedClasses = twMerge(oldClasses, newClass);

      // twMerge + DOM
      element.className = mergedClasses;

      // PATCH /update
      await updateSource(element, mergedClasses, 'style', oldClasses);

      // modifications list
      const modification: Modification = {
        id: Date.now().toString(),
        element: element.id || 'unknown',
        type: 'class',
        oldValue: oldClasses,
        newValue: mergedClasses,
        timestamp: Date.now(),
      };

      setModifications(prev => [modification, ...prev]);

      // STYLE_UPDATED to parent
      if (config.iframeMode?.enabled) {
        const sourceInfo = extractSourceInfo(element);
        if (sourceInfo) {
          sendToParent({
            type: 'STYLE_UPDATED',
            payload: {
              sourceInfo,
              oldClass: oldClasses,
              newClass: mergedClasses,
            },
            timestamp: Date.now(),
          });
        }
      }
    },
    [updateSource, config.iframeMode?.enabled]
  );

  /**
   * updateElementContent
   */
  const updateElementContent = useCallback(
    async (element: HTMLElement, newContent: string) => {
      const sourceInfo = extractSourceInfo(element);
      const originalContent = element.innerText;


      // twMerge + DOM
      element.innerText = newContent;

      // PATCH /update
      await updateSource(element, newContent, 'content', originalContent);

      // STYLE_UPDATED to parent
      if (config.iframeMode?.enabled) {
        const sourceInfo = extractSourceInfo(element);
        if (sourceInfo) {
          sendToParent({
            type: 'CONTENT_UPDATED',
            payload: {
              sourceInfo,
              oldValue: originalContent,
              newValue: newContent,
            },
            timestamp: Date.now(),
          });
        }
      }
    },
    [updateSource, config.iframeMode?.enabled]
  );

  /**
   * batchUpdateElements
   */
  const batchUpdateElements = useCallback(
    async (
      updates: Array<{
        element: HTMLElement;
        type: 'style' | 'content';
        newValue: string;
        originalValue?: string;
      }>
    ) => {
      if (!config.batchUpdate?.enabled) {
        // Sequential fallback
        await Promise.all(
          updates.map(update => {
            if (update.type === 'style') {
              return modifyElementClass(update.element, update.newValue);
            } else {
              return updateElementContent(update.element, update.newValue);
            }
          })
        );
        return;
      }

      // Debounced queue
      const newUpdates = [...pendingBatchUpdates, ...updates];
      setPendingBatchUpdates(newUpdates);

      // Reset debounce timer
      if (batchUpdateTimer) {
        clearTimeout(batchUpdateTimer);
      }

      // Schedule flush
      const timer = setTimeout(async () => {
        try {
          // Build payload
          const batchUpdateItems = newUpdates.map(update => {
            const sourceInfo = extractSourceInfo(update.element);
            if (!sourceInfo) {
              throw new Error('Element missing source mapping');
            }

            return {
              type: update.type,
              sourceInfo,
              newValue: update.newValue,
              originalValue: update.originalValue,
            };
          });

          // iframe: bridge BATCH_UPDATE
          if (config.iframeMode?.enabled) {
            await bridge.send({
              type: 'BATCH_UPDATE',
              payload: { updates: batchUpdateItems },
              timestamp: Date.now(),
            });
          } else {
            // top: fetch batch endpoint
            await fetch('/__appdev_design_mode/batch-update', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                updates: batchUpdateItems,
              }),
            });
          }

          // Clear queue
          setPendingBatchUpdates([]);
        } catch (error) {
          console.error('[DesignMode] Batch update failed:', error);
          setPendingBatchUpdates([]);
          throw error;
        }
      }, config.batchUpdate.debounceMs);

      setBatchUpdateTimer(timer);
    },
    [
      config.batchUpdate,
      pendingBatchUpdates,
      batchUpdateTimer,
      config.iframeMode?.enabled,
      modifyElementClass,
      updateElementContent,
    ]
  );

  /**
   * resetModifications
   */
  const resetModifications = useCallback(() => {
    window.location.reload();
  }, []);

  /**
   * postMessage when iframe + connected
   */
  const sendMessage = useCallback(
    async <T extends DesignModeMessage>(message: T) => {
      if (config.iframeMode?.enabled) {
        await bridge.send(message);
      }
    },
    [config.iframeMode?.enabled]
  );

  /**
   * sendMessageWithResponse
   */
  const sendMessageWithResponse = useCallback(
    async <T extends DesignModeMessage, R extends DesignModeMessage>(
      message: T,
      responseType: R['type']
    ): Promise<R> => {
      if (config.iframeMode?.enabled) {
        return await bridge.sendWithResponse(message, responseType);
      }
      throw new Error('Iframe mode is not enabled');
    },
    [config.iframeMode?.enabled]
  );

  /**
   * healthCheck
   */
  const healthCheck = useCallback(async () => {
    if (config.iframeMode?.enabled) {
      return await bridge.healthCheck();
    }
    return { status: 'healthy', details: { mode: 'standalone' } };
  }, [config.iframeMode?.enabled]);

  return (
    <DesignModeContext.Provider
      value={{
        // State
        isDesignMode,
        selectedElement,
        modifications,
        isConnected,
        bridgeStatus,

        // Config
        config,

        // Actions
        toggleDesignMode,
        selectElement,
        modifyElementClass,
        updateElementContent,
        batchUpdateElements,
        resetModifications,

        // Bridge helpers
        sendMessage,
        sendMessageWithResponse,
        healthCheck,
      }}
    >
      {children}
    </DesignModeContext.Provider>
  );
};

export const useDesignMode = () => {
  const context = useContext(DesignModeContext);
  if (context === undefined) {
    throw new Error('useDesignMode must be used within a DesignModeProvider');
  }
  return context;
};
