import { JupyterFrontEnd } from '@jupyterlab/application';

import { Widget } from '@lumino/widgets';
import { Message } from '@lumino/messaging';
import { ISettingRegistry } from '@jupyterlab/settingregistry';

import { VoittaToolRouter } from "./voitta/voittaServer";
import { initPythonBridge, callPython, registerFunction, get_ws } from './voitta/pythonBridge_browser'

import { get_tools } from "./integrations/jupyter_integrations"

import { createEscobarSplitButton, createTopButtons } from "./js/ui_elements"
import { SettingsPage } from "./js/setting_page"
import { MessageHandler, ResponseMessage, IChatSettings } from './messageHandler';
import { jsonrepair } from 'jsonrepair';

import { INotebookTracker } from '@jupyterlab/notebook';

import { functions } from "./integrations/jupyter_integrations"

import { IDebugger } from '@jupyterlab/debugger';


/**
 * Default settings
 */
const DEFAULT_SETTINGS: IChatSettings = {
  defaultGreeting: 'Hello! How can I assist you today?',
  maxMessages: 100,
  serverUrl: process.env.SERVER_URL || 'ws://localhost:8777/ws',
  apiKey: 'The Future Of Computing',
  username: 'User'
};

/**
 * A simple chat widget for Jupyter.
 */
export class ChatWidget extends Widget {
  private chatContainer: HTMLDivElement;
  private buttonContainer: HTMLDivElement;
  private divider: HTMLDivElement;
  private inputContainer: HTMLDivElement;
  private chatInput: HTMLTextAreaElement;
  private sendButton: HTMLButtonElement;
  private settings: IChatSettings = DEFAULT_SETTINGS;

  private voittaToolRouter: VoittaToolRouter | undefined;
  private messageHandler: MessageHandler;

  // Counter for generating unique IDs
  private static idCounter = 0;
  private app: JupyterFrontEnd;
  private notebookTracker: INotebookTracker;
  private settingsRegistry: ISettingRegistry | null;
  private debuggerService: IDebugger | null;

  private call_id_log = {};

  constructor(app: JupyterFrontEnd,
    settingsRegistry: ISettingRegistry | null,
    notebookTracker: INotebookTracker | null,
    debuggerService: IDebugger | null
  ) {
    // Generate a unique ID for this widget instance
    const id = `escobar-chat-${ChatWidget.idCounter++}`;
    super();

    this.app = app;
    this.notebookTracker = notebookTracker;
    this.settingsRegistry = settingsRegistry;
    this.debuggerService = debuggerService;
    this.id = id;
    this.addClass('escobar-chat');
    this.title.label = 'Chat';
    this.title.caption = 'Escobar Chat';
    this.title.iconClass = 'jp-MessageIcon'; // Add an icon for the sidebar
    this.title.closable = true;

    // Try to load settings from localStorage first
    try {
      const savedSettings = localStorage.getItem('escobar-settings');
      if (savedSettings) {
        const parsedSettings = JSON.parse(savedSettings);
        this.settings = { ...DEFAULT_SETTINGS, ...parsedSettings };
        console.log('Loaded settings from localStorage:', this.settings);

        // Set the username as a global variable for the Python bridge
        (window as any).escobarUsername = this.settings.username;
      }
    } catch (error) {
      console.error('Failed to load settings from localStorage:', error);
    }

    // Create the main layout
    this.node.style.display = 'flex';
    this.node.style.flexDirection = 'column';
    this.node.style.height = '100%';
    this.node.style.padding = '5px';

    // Load settings if provided
    if (settingsRegistry) {
      this.loadSettings(settingsRegistry);
    }

    // Make sure the parent container has position relative for proper absolute positioning
    this.node.style.position = 'relative';

    // Create top buttons using the function from ui_elements.ts
    this.buttonContainer = createTopButtons(
      this.app,
      this.settingsRegistry,
      () => this.settings, // Function to get the current settings
      this.createNewChat.bind(this),
      this.init.bind(this),
      this.updateSettings.bind(this)
    );

    // Add the button container to the DOM
    this.node.appendChild(this.buttonContainer);

    // Add a console log to verify button container creation
    console.log('Button container created and appended to DOM');

    // Create chat container
    this.chatContainer = document.createElement('div');
    this.chatContainer.className = 'escobar-chat-container';
    // Set initial height to 80% of the container
    this.chatContainer.style.height = '80%';
    this.chatContainer.style.flex = 'none';
    this.node.appendChild(this.chatContainer);

    // Create divider
    this.divider = document.createElement('div');
    this.divider.className = 'escobar-divider';
    this.node.appendChild(this.divider);

    // Add drag functionality to divider
    this.setupDividerDrag();

    // Create input container
    this.inputContainer = document.createElement('div');
    this.inputContainer.className = 'escobar-input-container';
    this.node.appendChild(this.inputContainer);

    // Create chat input
    this.chatInput = document.createElement('textarea');
    this.chatInput.className = 'escobar-chat-input';
    this.chatInput.placeholder = 'Type your message here...';
    this.chatInput.rows = 2;
    this.chatInput.addEventListener('keydown', (event: KeyboardEvent) => {
      if (event.key === 'Enter' && !event.shiftKey) {
        event.preventDefault();
        this.sendMessage(this.sendButton.textContent);
      }
    });
    this.inputContainer.appendChild(this.chatInput);

    const splitButton: HTMLDivElement = createEscobarSplitButton(["Talk", "Plan", "Act"]);
    this.sendButton = splitButton["mainButton"];
    this.inputContainer.appendChild(splitButton);

    this.sendButton.addEventListener('click', () => {
      this.sendMessage(this.sendButton.textContent);
    });

    // Initialize the message handler
    this.messageHandler = new MessageHandler(
      this.settings.apiKey,
      this.settings.username,
      this.chatContainer,
      this.settings.maxMessages
    );

    setTimeout(async () => {
      await this.init();
    }, 1000);
  }

  private handlePythonResponse(response: any, responseMsg?: ResponseMessage): void {
    this.messageHandler.handlePythonResponse(response, responseMsg);
  }

  async say(args: any) {
    const msg = this.messageHandler.findMessageById(args["msg_call_id"]);
    if (msg.isNew) {
      msg.setContent(args["text"]);
      msg.isNew = false;
    } else {
      msg.setContent(msg.getContent() + args["text"]);
    }
  }

  async tool_say(args: any) {
    if ((typeof args["name"] != "string") || (typeof args["name"] == undefined)) {
      console.log(args);
      return;
    }




    if (args["name"].includes("editExecuteCell_editExecuteCell") ||
      args["name"].includes("insertExecuteCell_insertExecuteCell") ||
      args["name"].includes("writeToFile_writeToFile")
    ) {

      try {
        if (this.call_id_log[args.id] == undefined) {
          this.call_id_log[args.id] = "";
        }
        this.call_id_log[args.id] += args.text;


        /*
        const fixed = jsonrepair( this.call_id_log[args.id] );
        console.log(1)
        const parsed = JSON.parse( fixed );
        console.log(2)
        */

        var parsed = {};

        try {
          parsed = JSON.parse(this.call_id_log[args.id]);
        } catch {
          parsed = JSON.parse(this.call_id_log[args.id] + '"}');
        }

        if (args["name"].includes("writeToFile_writeToFile")) {
          const content = parsed["content"];
          const filePath = parsed["filePath"];
          if (content) {
            const funcion_name = args["name"].split("_").reverse()[1];
            const callResult = await functions[funcion_name].func(
              {
                "filePath": filePath, "content": content
              }, true, args.id
            )
          }
        } else {
          const content = parsed["content"];
          const cellType = parsed["cellType"];
          const index = parseInt(parsed["index"], 10);
          if (content) {
            const funcion_name = args["name"].split("_").reverse()[1]; // this is super voitta-specific....
            const callResult = await functions[funcion_name].func({
              "index": index,
              "cellType": cellType,
              "content": content
            }, true, args.id);
          }
        }


      } catch (error) {
        console.error('Failed to repair/parse JSON:', error);

      }
    }
  }

  async init() {
    await this.messageHandler.clearMessages();
    this.voittaToolRouter = new VoittaToolRouter();
    const tools = await get_tools(this.app, this.notebookTracker, this.debuggerService);

    registerFunction('handleResponse', false, this.handlePythonResponse.bind(this));
    registerFunction('say', false, this.say.bind(this));
    registerFunction('tool_say', false, this.tool_say.bind(this));

    this.voittaToolRouter.tools = tools;

    try {
      // Use serverUrl from settings
      await initPythonBridge(this.settings.serverUrl);
      console.log("WEBSOCKET CONNECTED!!!");
    } catch (e) {
      console.error(e);
    }

    await this.messageHandler.loadMessages();

    // this is a very stupid approach....
    let start_call_id = this.messageHandler.generateMessageId();
    const payload = JSON.stringify({
      method: "start",
      api_key: this.settings.apiKey,
      call_id: start_call_id,
      intraspection: this.voittaToolRouter.intraspect(),
      rootPath: "rootPath"
    });

    const response = await callPython(payload);
  }

  /**
   * Load settings from the settings registry
   */
  private loadSettings(settingsRegistry: ISettingRegistry): void {
    settingsRegistry
      .load('escobar:plugin')
      .then(settings => {
        // Get the settings from the registry
        const loadedSettings = settings.composite as any as IChatSettings;

        // Update settings
        this.updateSettings(loadedSettings);

        // Add a greeting message
        if (this.settings.defaultGreeting) {
          this.messageHandler.addMessage('assistant', this.settings.defaultGreeting);
        }

        // Listen for setting changes
        settings.changed.connect(() => {
          console.log('Settings changed in registry');
          const updatedSettings = settings.composite as any as IChatSettings;
          console.log('Updated settings from registry:', updatedSettings);
          this.updateSettings(updatedSettings);
        });
      })
      .catch(reason => {
        console.error('Failed to load settings for escobar.', reason);
      });
  }

  /**
   * Update settings
   */
  private updateSettings(settings: IChatSettings): void {
    // Store the previous server URL to check if it changed
    const previousServerUrl = this.settings.serverUrl;

    this.settings = { ...DEFAULT_SETTINGS, ...settings };

    // Update message handler settings
    this.messageHandler.updateSettings(
      this.settings.apiKey,
      this.settings.username,
      this.settings.maxMessages
    );

    // Set the username as a global variable for the Python bridge
    (window as any).escobarUsername = this.settings.username;
    console.log(`Updated global username to: ${this.settings.username}`);

    // Save settings to localStorage for persistence across page reloads
    try {
      localStorage.setItem('escobar-settings', JSON.stringify(this.settings));
      console.log('Settings saved to localStorage');
    } catch (error) {
      console.error('Failed to save settings to localStorage:', error);
    }

    // Apply settings to UI components if needed
    this.applySettingsToUI();

    // Check if the server URL has changed
    if (previousServerUrl !== this.settings.serverUrl) {
      console.log(`Server URL changed from ${previousServerUrl} to ${this.settings.serverUrl}`);

      // Close the existing WebSocket connection
      const ws = get_ws();
      if (ws) {
        console.log('Closing existing WebSocket connection');
        ws.close();
      }

      // Reinitialize with the new URL
      console.log('Reinitializing with new server URL');
      setTimeout(() => {
        this.init();
      }, 100); // Small delay to ensure the previous connection is fully closed
    }
  }

  /**
   * Apply settings to UI components
   */
  private applySettingsToUI(): void {
    console.log('Applying settings to UI components');

    // If there are any UI components that need to be updated based on settings,
    // update them here. For example, if there's a display of the current username,
    // update it with this.settings.username.

    // This method can be extended as more UI components are added that depend on settings.
  }

  private async createNewChat(): Promise<void> {
    await this.messageHandler.createNewChat();
    this.init();
  }

  /**
   * Send a message from the input field.
   */
  private async sendMessage(mode: string): Promise<void> {
    const content = this.chatInput.value.trim();

    if (!content) {
      return;
    }

    // Clear input
    this.chatInput.value = '';

    // Send message using the message handler
    await this.messageHandler.sendMessage(content, mode);
  }

  /**
   * Handle activation requests for the widget
   */
  protected onActivateRequest(msg: Message): void {
    super.onActivateRequest(msg);
    this.chatInput.focus();
  }

  /**
   * Setup drag functionality for the divider
   */
  private setupDividerDrag(): void {
    let isDragging = false;
    let startY = 0;
    let startHeight = 0;

    const isScrolledToBottom = () => {
      // Get the scroll position
      const scrollTop = this.chatContainer.scrollTop;
      // Get the visible height
      const clientHeight = this.chatContainer.clientHeight;
      // Get the total scrollable height
      const scrollHeight = this.chatContainer.scrollHeight;

      // If scrollTop + clientHeight is approximately equal to scrollHeight,
      // then the container is scrolled to the bottom
      // (using a small threshold to account for rounding errors)

      return Math.abs(scrollTop + clientHeight - scrollHeight) < 100;
    };

    // Mouse move event handler
    const onMouseMove = (e: MouseEvent) => {
      if (!isDragging) return;

      // Calculate exact delta from start position
      const delta = e.pageY - startY - 18;

      // Get container height to calculate minimum and maximum allowed height
      const containerHeight = this.node.offsetHeight;
      const minChatHeight = Math.max(100, containerHeight * 0.3); // At least 30% of container or 100px
      const maxChatHeight = containerHeight * 0.85; // At most 85% of container

      // Apply delta directly to the starting height with min/max constraints
      const newHeight = Math.min(maxChatHeight, Math.max(minChatHeight, startHeight + delta));

      // Update chat container height
      this.chatContainer.style.height = `${newHeight}px`;
      this.chatContainer.style.flex = 'none';

      if (isScrolledToBottom()) {
        this.chatContainer.scrollTop = this.chatContainer.scrollHeight;
      }
    };

    // Mouse up event handler
    const onMouseUp = () => {
      if (!isDragging) return;

      isDragging = false;

      // Remove temporary event listeners
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('mouseup', onMouseUp);

      // Restore text selection
      document.body.style.userSelect = '';
    };

    // Attach mousedown event to divider
    this.divider.addEventListener('mousedown', (e: MouseEvent) => {
      // Prevent default to avoid text selection
      e.preventDefault();
      e.stopPropagation();

      // Store initial values
      isDragging = true;
      startY = e.pageY;
      startHeight = this.chatContainer.offsetHeight;

      // Add temporary event listeners
      window.addEventListener('mousemove', onMouseMove);
      window.addEventListener('mouseup', onMouseUp);

      // Prevent text selection during drag
      document.body.style.userSelect = 'none';
    });
  }

  /**
   * Dispose of the widget and clean up resources
   */
  dispose(): void {
    // Close WebSocket connection when widget is disposed
    const ws = get_ws();
    if (ws) {
      ws.close();
    }
    super.dispose();
  }
}
