import React, { useRef, useState } from 'react';
import { useDesignMode } from './DesignModeContext';
import { SourceInfo, AddToChatMessage, CopyElementMessage } from '@xagi/design-mode-shared/messages';
import { extractSourceInfo, hasSourceMapping } from '@xagi/design-mode-shared/sourceInfo';
import { isPureStaticText } from '@xagi/design-mode-shared/elementUtils';
import { AttributeNames } from '@xagi/design-mode-shared/attributeNames';
import { showContextMenu, MenuItem } from './ui/ContextMenu';
import { UpdateOperation, UpdateState, UpdateResult, BatchUpdateItem, UpdateManagerConfig } from './types/UpdateTypes';
import { HistoryManager } from '@xagi/design-mode-shared/HistoryManager';
import { ObserverManager } from './managers/ObserverManager';
import { EditManager } from './managers/EditManager';
import { UpdateService } from '@xagi/design-mode-shared/UpdateService';
import { bridge } from './bridge';
import { Toast } from './ui/Toast';





/**
 * Coordinates direct edit, context menu, batching, and persistence hooks.
 */
export class UpdateManager {
  private updateQueue: UpdateState[] = [];
  private historyManager: HistoryManager;
  private observerManager: ObserverManager;
  private editManager: EditManager;
  private updateService: UpdateService;
  private callbacks: Map<UpdateOperation, Set<(update: UpdateState) => void>> =
    new Map();
  private batchTimer: NodeJS.Timeout | null = null;
  private saveTimer: NodeJS.Timeout | null = null;

  // Design mode state
  private isDesignMode: boolean = false;
  private selectedElement: HTMLElement | null = null;
  private selectElementCallback: ((element: HTMLElement | null) => void) | null = null;

  constructor(
    private config: UpdateManagerConfig = {
      enableDirectEdit: true,
      enableBatching: true,
      batchDebounceMs: 300,
      maxRetries: 3,
      autoSave: true,
      saveDelay: 1000,
      validation: {
        validateSource: true,
        validateValue: true,
        maxLength: 10000,
      },
    }
  ) {
    this.historyManager = new HistoryManager();
    this.observerManager = new ObserverManager(
      (target, type) => this.editManager.handleDirectEdit(target, type),
      (node) => this.setupElementEditHandlers(node)
    );
    this.editManager = new EditManager(
      (update) => this.updateService.processUpdate(update),
      this.config
    );
    this.updateService = new UpdateService(
      this.config,
      (update) => {
        this.historyManager.add(update);
        this.notifyCallbacks(update.operation, update);
        if (this.config.autoSave) {
          this.triggerAutoSave();
        }
      },
      (update) => {
        // onFail callback
        if (update.error) {
          Toast.error(`Update failed: ${update.error}`);
        }
      }
    );

    if (this.config.enableDirectEdit) {
      this.observerManager.enable();
    }

    this.initializeEventListeners();
  }

  /**
   * (Observer wired in ObserverManager)
   */


  /**
   * register dblclick / contextmenu
   */
  private initializeEventListeners() {
    if (!this.config.enableDirectEdit) return;

    // Double-click → edit
    document.addEventListener('dblclick', this.handleDblClick.bind(this));

    // Context menu
    document.addEventListener('contextmenu', this.handleContextMenu.bind(this));

    // Shortcuts (optional)
    // document.addEventListener('keydown', this.handleKeyDown.bind(this));
  }



  /**
   * Double-click: only when design mode on
   */
  private handleDblClick(event: MouseEvent) {
    // Require design mode
    if (!this.isDesignMode) return;

    const target = event.target as HTMLElement;

    // Need source mapping
    if (!hasSourceMapping(target)) return;

    // Skip plugin UI
    if (target.closest('#__vite_plugin_design_mode__')) return;

    // Skip context menu DOM
    if (target.closest(`[${AttributeNames.contextMenu}="true"]`)) return;

    // static-content="true" only
    // Respects configurable attribute prefix
    const staticContentAttr = target.getAttribute(AttributeNames.staticContent);
    if (staticContentAttr !== 'true') {
      // Not editable without static text marker
      return;
    }

    // preventDefault for dblclick
    event.preventDefault();
    event.stopPropagation();

    // Check if it's pure static text - REMOVED to let EditManager handle validation
    // if (!isPureStaticText(target)) {
    //   console.log('[UpdateManager] Ignored dblclick on non-static text element');
    //   return;
    // }

    // Delegate to EditManager
    this.editManager.handleDirectEdit(target, 'content');
  }

  /**
   * Context menu: add to chat / copy
   */
  private handleContextMenu(event: MouseEvent) {
    const target = event.target as HTMLElement;

    // Only show context menu if design mode is enabled
    if (!this.isDesignMode) return;

    // Exclude context menu
    if (target.closest(`[${AttributeNames.contextMenu}="true"]`)) return;

    // Must be mapped
    if (!hasSourceMapping(target)) return;

    // isSelected flag
    const isSelected = !!(this.selectedElement && target === this.selectedElement);

    // Preserve hover while menu opens
    const hadHoverState = target.hasAttribute('data-design-hover');
    if (hadHoverState) {
      // context-menu-hover marker
      target.setAttribute(AttributeNames.contextMenuHover, 'true');
      if (target.hasAttribute(AttributeNames.staticClass) || target.hasAttribute(AttributeNames.staticContent)) {
        // keep data-design-hover
        target.setAttribute('data-design-hover', 'true');

      }
    }

    event.preventDefault();

    // Custom menu for mapped nodes
    this.showContextMenu(target, event.clientX, event.clientY, isSelected);
  }

  /**
   * Update design mode state
   */
  public setDesignModeState(
    isDesignMode: boolean,
    selectedElement: HTMLElement | null = null,
    selectElementCallback?: (element: HTMLElement | null) => void
  ) {
    this.isDesignMode = isDesignMode;
    this.selectedElement = selectedElement;
    if (selectElementCallback) {
      this.selectElementCallback = selectElementCallback;
    }
  }

  /**
   * Whether design mode is active
   */
  public getDesignModeState(): boolean {
    return this.isDesignMode;
  }


  /**
   * Keyboard shortcuts (currently unused hook)
   */
  private handleKeyDown(event: KeyboardEvent) {
    // F2 → edit
    if (event.key === 'F2') {
      const selectedElement = document.activeElement as HTMLElement;
      if (selectedElement && hasSourceMapping(selectedElement)) {
        event.preventDefault();
        this.editManager.handleDirectEdit(selectedElement, 'content');
        // if (isPureStaticText(selectedElement)) {
        //   this.editManager.handleDirectEdit(selectedElement, 'content');
        // } else {
        //   console.log('[UpdateManager] Cannot edit non-static text element');
        // }
      }
    }

    // Save all
    if ((event.ctrlKey || event.metaKey) && event.key === 's') {
      event.preventDefault();
      this.saveAllChanges();
    }

    // Undo
    if (
      (event.ctrlKey || event.metaKey) &&
      event.key === 'z' &&
      !event.shiftKey
    ) {
      event.preventDefault();
      this.undoLastUpdate();
    }

    // Redo
    if (
      (event.ctrlKey || event.metaKey) &&
      (event.key === 'y' || (event.key === 'z' && event.shiftKey))
    ) {
      event.preventDefault();
      this.redoLastUpdate();
    }
  }

  /**
   * Observer callback: mark editable nodes
   */
  private setupElementEditHandlers(element: HTMLElement) {
    if (!hasSourceMapping(element)) return;

    // data-edit-enabled
    element.setAttribute('data-edit-enabled', 'true');

    // dashed outline on hover
    element.addEventListener('mouseenter', () => {
      if (this.config.enableDirectEdit && this.isDesignMode) {
        element.style.outline = '1px dashed #007acc';
      }
    });

    element.addEventListener('mouseleave', () => {
      if (!element.hasAttribute('data-selected')) {
        element.style.outline = '';
      }
    });
  }



  /**
   * Internal direct-edit path
   */
  private handleDirectEdit(
    element: HTMLElement,
    type: 'style' | 'content' | 'attribute'
  ) {
    if (!this.config.enableDirectEdit) return;

    const sourceInfo = extractSourceInfo(element);
    if (!sourceInfo) return;

    switch (type) {
      case 'content':
        this.updateContent(element, element.innerText, sourceInfo);
        break;
      case 'style':
        this.updateStyle(element, element.className, sourceInfo);
        break;
    }
  }

  /**
   * enterEditMode
   */
  public enterEditMode(
    element: HTMLElement,
    type: 'style' | 'content' | 'attribute'
  ) {
    const sourceInfo = extractSourceInfo(element);
    if (!sourceInfo) {
      console.warn('[UpdateManager] Element has no source mapping');
      return;
    }

    switch (type) {
      case 'content':
        this.editManager.handleDirectEdit(element, 'content');
        break;
      case 'style':
        this.editManager.editStyle(element);
        break;
      case 'attribute':
        this.editManager.editAttributes(element);
        break;
    }
  }



  /**
   * (reserved)
   */


  /**
   * showContextMenu
   */
  private showContextMenu(element: HTMLElement, x: number, y: number, isSelected: boolean) {
    const menuItems: MenuItem[] = [];

    // Built-in actions
    menuItems.push(
      {
        label: 'Add to chat',
        action: () => this.addToChat(element),
      },
      {
        label: 'Copy element',
        action: () => this.copyElement(element)
      }
    );

    showContextMenu(element, x, y, menuItems);
  }

  /**
   * (handled in ContextMenu)
   */


  /**
   * copyElement
   */
  private copyElement(element: HTMLElement) {
    const sourceInfo = extractSourceInfo(element);
    const content = element.innerText || element.textContent || '';

    // Copy element to clipboard (if possible)
    const elementInfo = {
      tagName: element.tagName.toLowerCase(),
      className: element.className,
      content: content,
      sourceInfo: sourceInfo ?? undefined
    };

    const textToCopy = JSON.stringify(elementInfo, null, 2);

    // sendCopyMessage helper
    const sendCopyMessage = (success: boolean, error?: string) => {
      const message: CopyElementMessage = {
        type: 'COPY_ELEMENT',
        payload: {
          elementInfo: {
            tagName: elementInfo.tagName,
            className: elementInfo.className,
            content: elementInfo.content,
            sourceInfo: elementInfo.sourceInfo
          },
          textContent: textToCopy,
          success,
          error
        },
        timestamp: Date.now()
      };

      // iframe: bridge; top: postMessage
      if (typeof window !== 'undefined' && window.self !== window.top) {

        bridge.send(message).catch(error => {
          console.error('[UpdateManager] Failed to send COPY_ELEMENT via bridge:', error);
          // fallback postMessage
          window.parent.postMessage(message, '*');
        });
      } else {

        window.postMessage(message, '*');
      }
    };

    // Clipboard API when available
    if (navigator.clipboard) {
      navigator.clipboard.writeText(textToCopy).then(() => {
        let alertMessage = 'Copied element info to clipboard:\n\n';
        if (sourceInfo) {
          alertMessage += `File: ${sourceInfo.fileName}\n`;
          alertMessage += `Line: L${sourceInfo.lineNumber}\n`;
          alertMessage += `\n`;
        }
        alertMessage += `Tag: <${elementInfo.tagName}>\n`;
        alertMessage += `className: ${elementInfo.className}\n`;
        alertMessage += `Text: ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`;


        sendCopyMessage(true);
      }).catch(err => {
        const errorMessage = err instanceof Error ? err.message : String(err);
        console.error('[UpdateManager] Failed to copy to clipboard:', err);


        sendCopyMessage(false, errorMessage);
      });
    } else {

      sendCopyMessage(false, 'Clipboard API not available');
    }
  }

  /**
   * Add element content to chat
   */
  private addToChat(element: HTMLElement) {
    const sourceInfo = extractSourceInfo(element);
    const content = element.innerText || element.textContent || '';

    // console.log('[UpdateManager] Adding to chat:', { content, sourceInfo });



    const contextSourceInfo = sourceInfo ?? undefined;


    const elementInfo = sourceInfo ? {
      tagName: element.tagName.toLowerCase(),
      className: element.className,
      textContent: content,
      sourceInfo,
      isStaticText: isPureStaticText(element)
    } : undefined;

    const message: AddToChatMessage = {
      type: 'ADD_TO_CHAT',
      payload: {
        content,
        context: {
          sourceInfo: contextSourceInfo,
          elementInfo
        }
      },
      timestamp: Date.now()
    };

    // iframe vs top-level postMessage
    if (typeof window !== 'undefined' && window.self !== window.top) {

      bridge.send(message).catch(error => {
        console.error('[UpdateManager] Failed to send ADD_TO_CHAT via bridge:', error);

        window.parent.postMessage(message, '*');
      });
    } else {

      window.postMessage(message, '*');
    }

    // Format alert message with source info
    let alertMessage = 'Added to chat:\n\n';
    if (sourceInfo) {
      alertMessage += `File: ${sourceInfo.fileName}\n`;
      alertMessage += `Line: L${sourceInfo.lineNumber}\n`;
      alertMessage += `\n`;
    }
    alertMessage += `Text:\n${content.substring(0, 100)}${content.length > 100 ? '...' : ''}`;
  }

  /**
   * deleteElement (stub)
   */
  private deleteElement(element: HTMLElement) {
    // Destructive edit not implemented
  }

  /**
   * updateStyle
   */
  public updateStyle(
    element: HTMLElement,
    newClass: string,
    sourceInfo: SourceInfo
  ): Promise<UpdateResult> {
    return this.editManager.updateStyle(element, newClass, sourceInfo);
  }

  /**
   * updateContent
   */
  public updateContent(
    element: HTMLElement,
    newContent: string,
    sourceInfo?: SourceInfo,
    oldValue?: string
  ): Promise<UpdateResult> {
    return this.editManager.updateContent(element, newContent, sourceInfo, oldValue);
  }

  /**
   * updateAttribute
   */
  public updateAttribute(
    element: HTMLElement,
    attributeName: string,
    newValue: string,
    sourceInfo: SourceInfo
  ): Promise<UpdateResult> {
    return this.editManager.updateAttribute(element, attributeName, newValue, sourceInfo);
  }

  /**
   * batchUpdate
   */
  public batchUpdate(updates: BatchUpdateItem[]): Promise<UpdateResult[]> {
    if (!this.config.enableBatching) {
      // Sequential when batching off
      return Promise.all(
        updates.map(item => {
          if (item.type === 'style') {
            return this.updateStyle(
              item.element,
              item.newValue,
              item.sourceInfo
            );
          } else if (item.type === 'content') {
            return this.updateContent(
              item.element,
              item.newValue,
              item.sourceInfo
            );
          } else {
            return this.updateAttribute(
              item.element,
              'data-test',
              item.newValue,
              item.sourceInfo
            );
          }
        })
      );
    }

    // Debounced batch
    return new Promise(resolve => {

      const updateStates: UpdateState[] = updates.map(item => ({
        id: this.generateUpdateId(),
        operation: item.type === 'style' ? 'style_update' : 'content_update',
        sourceInfo: item.sourceInfo,
        element: item.element,
        oldValue: item.originalValue || '',
        newValue: item.newValue,
        status: 'pending' as const,
        timestamp: Date.now(),
        retryCount: 0,
      }));


      this.updateQueue.push(...updateStates);


      if (this.batchTimer) {
        clearTimeout(this.batchTimer);
      }

      this.batchTimer = setTimeout(async () => {
        const results = await this.processBatchUpdate(updateStates);
        resolve(results);
      }, this.config.batchDebounceMs);
    });
  }

  /**
   * processUpdate
   */
  private async processUpdate(update: UpdateState): Promise<UpdateResult> {
    return this.updateService.processUpdate(update);
  }

  /**
   * processBatchUpdate
   */
  private async processBatchUpdate(
    updates: UpdateState[]
  ): Promise<UpdateResult[]> {
    return this.updateService.processBatchUpdate(updates);
  }

  /**
   * (extract in service)
   */


  /**
   * generateUpdateId
   */
  private generateUpdateId(): string {
    return `update_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  /**
   * triggerAutoSave
   */
  private triggerAutoSave() {
    if (this.saveTimer) {
      clearTimeout(this.saveTimer);
    }

    this.saveTimer = setTimeout(() => {
      this.saveAllChanges();
    }, this.config.saveDelay);
  }

  /**
   * saveAllChanges
   */
  public async saveAllChanges(): Promise<void> {
    const pendingUpdates = this.updateQueue.filter(u => u.status === 'pending');
    if (pendingUpdates.length === 0) return;

    try {
      const results = await this.processBatchUpdate(pendingUpdates);
    } catch (error) {
      console.error('[UpdateManager] Failed to save changes:', error);
    }
  }

  /**
   * undoLastUpdate
   */
  public undoLastUpdate(): boolean {
    const lastUpdate = this.historyManager.undo();
    if (!lastUpdate) return false;

    // Restore DOM from history
    lastUpdate.element.innerText = lastUpdate.oldValue;
    lastUpdate.element.className = lastUpdate.oldValue;

    return true;
  }

  /**
   * redoLastUpdate
   */
  public redoLastUpdate(): boolean {
    const lastReverted = this.historyManager.redo();
    if (!lastReverted) return false;

    // Re-apply from history
    lastReverted.element.innerText = lastReverted.newValue;
    lastReverted.element.className = lastReverted.newValue;

    return true;
  }

  /**
   * addUpdateCallback
   */
  public addUpdateCallback(
    operation: UpdateOperation,
    callback: (update: UpdateState) => void
  ) {
    if (!this.callbacks.has(operation)) {
      this.callbacks.set(operation, new Set());
    }
    this.callbacks.get(operation)!.add(callback);


    return () => {
      this.callbacks.get(operation)?.delete(callback);
    };
  }

  /**
   * notifyCallbacks
   */
  private notifyCallbacks(operation: UpdateOperation, update: UpdateState) {
    const callbacks = this.callbacks.get(operation);
    if (callbacks) {
      callbacks.forEach(callback => callback(update));
    }
  }

  /**
   * getUpdateStates
   */
  public getUpdateStates(): UpdateState[] {
    return [...this.updateQueue];
  }

  /**
   * getUpdateHistory
   */
  public getUpdateHistory(): UpdateState[] {
    return this.historyManager.getHistory();
  }

  /**
   * destroy
   */
  public destroy() {

    this.observerManager.disable();


    if (this.batchTimer) {
      clearTimeout(this.batchTimer);
    }

    if (this.saveTimer) {
      clearTimeout(this.saveTimer);
    }


    this.updateQueue = [];
    this.historyManager.clear();
    this.callbacks.clear();
  }
}

/**
 * UpdateManager React Hook
 */
export const useUpdateManager = (config?: Partial<UpdateManagerConfig>) => {
  const updateManagerRef = useRef<UpdateManager | null>(null);
  const [updateStates, setUpdateStates] = useState<UpdateState[]>([]);
  const { config: designModeConfig, isDesignMode, selectedElement } = useDesignMode();

  React.useEffect(() => {
    // Direct edit can run outside strict design mode when enabled
    updateManagerRef.current = new UpdateManager({
      enableDirectEdit: designModeConfig.iframeMode?.enableDirectEdit ?? true,
      enableBatching: designModeConfig.batchUpdate?.enabled ?? true,
      batchDebounceMs: designModeConfig.batchUpdate?.debounceMs ?? 300,
      maxRetries: 3,
      autoSave: true,
      saveDelay: 1000,
      validation: {
        validateSource: true,
        validateValue: true,
        maxLength: 10000,
      },
      ...config,
    });

    // Track content_update events
    const unsubscribe = updateManagerRef.current.addUpdateCallback(
      'content_update',
      update => {
        setUpdateStates(prev => [...prev, update]);
      }
    );

    return () => {
      unsubscribe();
      updateManagerRef.current?.destroy();
      updateManagerRef.current = null;
    };
  }, [designModeConfig]);

  // Sync design mode state and selected element with UpdateManager
  const { selectElement } = useDesignMode();
  React.useEffect(() => {
    if (updateManagerRef.current) {
      updateManagerRef.current.setDesignModeState(isDesignMode, selectedElement, selectElement);
    }
  }, [isDesignMode, selectedElement, selectElement]);


  return {
    updateManager: updateManagerRef.current,
    updateStates,
    updateStyle: (
      element: HTMLElement,
      newClass: string,
      sourceInfo: SourceInfo
    ) => updateManagerRef.current?.updateStyle(element, newClass, sourceInfo),
    updateContent: (
      element: HTMLElement,
      newContent: string,
      sourceInfo: SourceInfo
    ) =>
      updateManagerRef.current?.updateContent(element, newContent, sourceInfo),
    batchUpdate: (updates: BatchUpdateItem[]) =>
      updateManagerRef.current?.batchUpdate(updates),
    saveAllChanges: () => updateManagerRef.current?.saveAllChanges(),
    undoLastUpdate: () => updateManagerRef.current?.undoLastUpdate(),
    redoLastUpdate: () => updateManagerRef.current?.redoLastUpdate(),
  };
};

export default UpdateManager;
