import { BrowserWindow, ipcMain, screen } from 'electron';
import { join } from 'path';
import { TaskCopy } from '../../src/app/features/tasks/task.model';
import { TaskWidgetConfig } from '../../src/app/features/config/global-config.model';
import { info } from 'electron-log/main';
import { IPC } from '../shared-with-frontend/ipc-events.const';
import { loadSimpleStoreAll, saveSimpleStore } from '../simple-store';
import { IS_MAC } from '../common.const';

let taskWidgetWin: BrowserWindow | null = null;
let isTaskWidgetEnabled = false;
let isAlwaysShow = false;
// Set when the user explicitly reveals the widget via the global shortcut
// (`globalToggleTaskWidget`) while the main window is visible. Like
// `isAlwaysShow`, it suppresses the automatic "hide the widget when the main
// window is shown/focused" behavior — but only until the user hides the widget
// again (toggles off) or opens the app from the widget. This gives the shortcut
// a sticky "user-forced visible" effect instead of being immediately undone by
// the next focus event.
let isUserForcedVisible = false;
let currentTask: TaskCopy | null = null;
let isPomodoroEnabled = false;
let currentPomodoroSessionTime = 0;
let isFocusModeEnabled = false;
let currentFocusSessionTime = 0;
let initTimeoutId: NodeJS.Timeout | null = null;
let currentOpacity = 95;
let listenersRegistered = false;
let taskWidgetCreationPromise: Promise<void> | null = null;
let taskWidgetCreationGeneration = 0;
let pendingShowAfterCreate = false;
let pendingShowAfterCreateInactive = false;

const TASK_WIDGET_BOUNDS_KEY = 'taskWidgetBounds';
const LEGACY_BOUNDS_KEY = 'overlayBounds';
let boundsDebounceTimer: NodeJS.Timeout | null = null;

type ShowTaskWidgetOptions = Readonly<{
  inactive?: boolean;
}>;

export const updateTaskWidgetEnabled = (isEnabled: boolean): void => {
  isTaskWidgetEnabled = isEnabled;

  if (!isEnabled) {
    destroyTaskWidget();
    return;
  }

  if (!taskWidgetWin && !taskWidgetCreationPromise) {
    initListeners();
    createTaskWidgetWindow().then(() => {
      // Window creation is async; re-apply the cached opacity here because
      // updateTaskWidgetOpacity() is a no-op while taskWidgetWin is still null,
      // and on macOS BrowserWindow.setOpacity() defaults to 1 (no CSS fallback).
      if (taskWidgetWin && !taskWidgetWin.isDestroyed()) {
        updateTaskWidgetOpacity(currentOpacity);
      }
      // Request current task state after window is ready
      const mainWindow = BrowserWindow.getAllWindows().find(
        (win) => win !== taskWidgetWin,
      );
      if (mainWindow) {
        mainWindow.webContents.send(IPC.REQUEST_CURRENT_TASK_FOR_TASK_WIDGET);
      }
    });
  }
};

const clearPendingTaskWidgetCreation = (): void => {
  taskWidgetCreationGeneration += 1;
  taskWidgetCreationPromise = null;
  pendingShowAfterCreate = false;
  pendingShowAfterCreateInactive = false;
};

export const destroyTaskWidget = (): void => {
  // Clear any pending timeouts
  if (initTimeoutId) {
    clearTimeout(initTimeoutId);
    initTimeoutId = null;
  }

  // Clear bounds debounce timer
  if (boundsDebounceTimer) {
    clearTimeout(boundsDebounceTimer);
    boundsDebounceTimer = null;
  }

  // Disable task widget to prevent close event prevention
  isTaskWidgetEnabled = false;
  isUserForcedVisible = false;
  clearPendingTaskWidgetCreation();

  // Remove IPC listeners
  ipcMain.removeAllListeners('task-widget-show-main-window');
  listenersRegistered = false;

  if (taskWidgetWin && !taskWidgetWin.isDestroyed()) {
    try {
      // Remove ALL event listeners
      taskWidgetWin.removeAllListeners();

      // Remove webContents listeners
      if (taskWidgetWin.webContents && !taskWidgetWin.webContents.isDestroyed()) {
        taskWidgetWin.webContents.removeAllListeners();
      }

      // Hide first to prevent visual issues
      taskWidgetWin.hide();

      // Set closable to ensure we can close it
      taskWidgetWin.setClosable(true);

      // Force destroy the window
      taskWidgetWin.destroy();
    } catch (e) {
      // Window might already be destroyed
      console.error('Error destroying task widget window:', e);
    }

    taskWidgetWin = null;
  }
};

const createTaskWidgetWindow = (): Promise<void> => {
  if (taskWidgetWin) {
    return Promise.resolve();
  }

  if (taskWidgetCreationPromise) {
    return taskWidgetCreationPromise;
  }

  const creationGeneration = taskWidgetCreationGeneration;
  const nextCreationPromise = createTaskWidgetWindowForGeneration(
    creationGeneration,
  ).finally(() => {
    if (taskWidgetCreationPromise === nextCreationPromise) {
      taskWidgetCreationPromise = null;
    }
  });

  taskWidgetCreationPromise = nextCreationPromise;
  return nextCreationPromise;
};

const createTaskWidgetWindowForGeneration = async (
  creationGeneration: number,
): Promise<void> => {
  if (taskWidgetWin) {
    return;
  }

  const primaryDisplay = screen.getPrimaryDisplay();
  const { width: screenWidth } = primaryDisplay.workAreaSize;
  const defaultBounds = { width: 300, height: 80, x: screenWidth - 320, y: 20 };

  // Restore persisted bounds or use defaults
  let bounds = defaultBounds;
  try {
    const store = await loadSimpleStoreAll();
    // Try new key first, fall back to legacy key for migration
    const saved = (store[TASK_WIDGET_BOUNDS_KEY] || store[LEGACY_BOUNDS_KEY]) as
      | { width: number; height: number; x: number; y: number }
      | undefined;
    if (
      saved &&
      typeof saved.width === 'number' &&
      saved.width > 0 &&
      typeof saved.height === 'number' &&
      saved.height > 0 &&
      typeof saved.x === 'number' &&
      typeof saved.y === 'number'
    ) {
      // Validate saved bounds are visible on any connected display
      const matchingDisplay = screen.getDisplayMatching({
        x: saved.x,
        y: saved.y,
        width: saved.width,
        height: saved.height,
      });
      const isOnScreen =
        matchingDisplay &&
        saved.x + saved.width > matchingDisplay.bounds.x &&
        saved.x < matchingDisplay.bounds.x + matchingDisplay.bounds.width &&
        saved.y >= matchingDisplay.bounds.y &&
        saved.y < matchingDisplay.bounds.y + matchingDisplay.bounds.height;
      bounds = isOnScreen ? saved : defaultBounds;
    }
  } catch (_e) {
    // Use defaults (file may not exist on first run)
  }

  if (
    taskWidgetWin ||
    !isTaskWidgetEnabled ||
    creationGeneration !== taskWidgetCreationGeneration
  ) {
    return;
  }

  // On macOS, transparent + frameless windows do not support native window
  // dragging or edge resizing (see Electron's BrowserWindow docs: "Transparent
  // windows are not resizable. Setting `resizable` to `true` may make a
  // transparent window stop working on some platforms."). Use a solid window
  // instead and rely on BrowserWindow.setOpacity() for the user-set opacity so
  // the OS keeps native drag/resize behavior intact.
  taskWidgetWin = new BrowserWindow({
    width: bounds.width,
    height: bounds.height,
    x: bounds.x,
    y: bounds.y,
    title: 'Super Productivity Task Widget',
    frame: false,
    transparent: !IS_MAC,
    backgroundColor: IS_MAC ? '#00000000' : undefined,
    alwaysOnTop: true,
    skipTaskbar: true,
    resizable: true,
    minWidth: 60,
    minHeight: 24,
    maxWidth: 700,
    maxHeight: 120,
    minimizable: false,
    maximizable: false,
    closable: true, // Ensure window is closable
    hasShadow: IS_MAC, // Mac: solid window can keep native shadow
    autoHideMenuBar: true,
    roundedCorners: IS_MAC, // Mac: rely on OS-native rounded corners
    webPreferences: {
      preload: join(__dirname, 'task-widget-preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      disableDialogs: true,
      webSecurity: true,
      allowRunningInsecureContent: false,
      backgroundThrottling: false, // Prevent throttling when hidden
    },
  });

  taskWidgetWin.loadFile(join(__dirname, 'task-widget.html'));

  // Set visible on all workspaces immediately after creation
  taskWidgetWin.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });

  taskWidgetWin.on('closed', () => {
    taskWidgetWin = null;
    // Tie "user-forced visible" to the window's lifetime: once the window is
    // gone the sticky flag has no widget to keep visible, so don't let it
    // linger into a future re-create.
    isUserForcedVisible = false;
  });

  taskWidgetWin.on('ready-to-show', () => {
    if (!taskWidgetWin || taskWidgetWin.isDestroyed()) return;
    // Ensure window stays on all workspaces
    taskWidgetWin.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });

    // Request current task state from main window
    const mainWindow = BrowserWindow.getAllWindows().find((win) => win !== taskWidgetWin);
    if (mainWindow) {
      mainWindow.webContents.send(IPC.REQUEST_CURRENT_TASK_FOR_TASK_WIDGET);
    }
    // Don't show task widget here - it should only show when main window is minimized
  });

  const persistBoundsDebounced = (): void => {
    if (boundsDebounceTimer) clearTimeout(boundsDebounceTimer);
    boundsDebounceTimer = setTimeout(() => {
      if (taskWidgetWin && !taskWidgetWin.isDestroyed()) {
        saveSimpleStore(TASK_WIDGET_BOUNDS_KEY, taskWidgetWin.getBounds());
      }
    }, 300);
  };

  taskWidgetWin.on('resize', persistBoundsDebounced);
  taskWidgetWin.on('move', persistBoundsDebounced);

  // Prevent context menu on right-click to avoid crashes
  taskWidgetWin.webContents.on('context-menu', (e) => {
    e.preventDefault();
  });

  // Prevent any window system menu
  taskWidgetWin.on('system-context-menu', (e) => {
    e.preventDefault();
  });

  // Don't make window click-through initially to allow dragging
  // The renderer process will handle mouse events dynamically

  // Update initial state
  updateTaskWidgetContent();

  updateTaskWidgetOpacity(currentOpacity);

  if (pendingShowAfterCreate) {
    const showInactive = pendingShowAfterCreateInactive;
    pendingShowAfterCreate = false;
    pendingShowAfterCreateInactive = false;
    showTaskWidgetWindow({ inactive: showInactive });
  }
};

const showTaskWidgetWindow = (options: ShowTaskWidgetOptions = {}): void => {
  if (!taskWidgetWin || taskWidgetWin.isDestroyed()) {
    return;
  }

  if (options.inactive) {
    taskWidgetWin.showInactive();
  } else {
    taskWidgetWin.show();
  }
};

export const showTaskWidget = (options: ShowTaskWidgetOptions = {}): void => {
  if (!isTaskWidgetEnabled) {
    return;
  }

  // Recreate task widget if it was accidentally closed
  if (!taskWidgetWin) {
    info('Task widget window was destroyed, recreating');
    pendingShowAfterCreate = true;
    pendingShowAfterCreateInactive = pendingShowAfterCreateInactive || !!options.inactive;
    createTaskWidgetWindow();
    return;
  }

  if (taskWidgetWin.isDestroyed()) {
    return;
  }

  // Only show if not already visible
  if (!taskWidgetWin.isVisible()) {
    info('Showing task widget');
    showTaskWidgetWindow(options);
  } else {
    info('Task widget already visible');
  }
};

export const hideTaskWidget = (): void => {
  if (!taskWidgetWin || !isTaskWidgetEnabled) {
    info(
      'Task widget hide skipped: window=' +
        !!taskWidgetWin +
        ', enabled=' +
        isTaskWidgetEnabled,
    );
    return;
  }

  // Only hide if currently visible
  if (taskWidgetWin.isVisible()) {
    info('Hiding task widget');
    taskWidgetWin.hide();
  } else {
    info('Task widget already hidden');
  }
};

/**
 * Toggles the task widget's visibility. Intended for the global shortcut
 * (`globalToggleTaskWidget`): it only acts when the task widget feature is
 * enabled in settings and never changes that persisted enabled/disabled
 * preference — it just shows or hides the existing widget.
 */
export const toggleTaskWidgetVisibility = (): void => {
  if (!isTaskWidgetEnabled) {
    return;
  }

  if (taskWidgetWin && !taskWidgetWin.isDestroyed() && taskWidgetWin.isVisible()) {
    isUserForcedVisible = false;
    hideTaskWidget();
    return;
  }

  isUserForcedVisible = true;
  showTaskWidget({ inactive: true });
};

const initListeners = (): void => {
  if (listenersRegistered) {
    return;
  }
  listenersRegistered = true;

  // Listen for show main window request
  ipcMain.on('task-widget-show-main-window', () => {
    const mainWindow = BrowserWindow.getAllWindows().find((win) => win !== taskWidgetWin);
    if (mainWindow) {
      // Mirror showOrFocus() logic: restore() before show() to handle the case where
      // the window is minimized+hidden (e.g. minimize-to-tray on Linux where
      // event.preventDefault() on 'minimize' has no effect).
      mainWindow.restore();
      mainWindow.show();
      // Opening the app from the widget is an explicit "I'm going to the app"
      // gesture, so clear any sticky user-forced visibility and let the widget
      // follow the normal companion behavior again.
      isUserForcedVisible = false;
      if (!isAlwaysShow) {
        hideTaskWidget();
      }
      setTimeout(() => {
        if (!mainWindow.isDestroyed()) {
          mainWindow.focus();
          if (!mainWindow.webContents.isDestroyed()) {
            mainWindow.webContents.focus();
          }
        }
      }, 60);
    }
  });
};

export const updateTaskWidgetTask = (
  task: TaskCopy | null,
  pomodoroEnabled: boolean,
  pomodoroTime: number,
  focusModeEnabled: boolean,
  focusTime: number,
): void => {
  currentTask = task;
  isPomodoroEnabled = pomodoroEnabled;
  currentPomodoroSessionTime = pomodoroTime;
  isFocusModeEnabled = focusModeEnabled;
  currentFocusSessionTime = focusTime;

  updateTaskWidgetContent();
};

const updateTaskWidgetContent = (): void => {
  if (!taskWidgetWin || !isTaskWidgetEnabled) {
    return;
  }

  let title = '';
  let timeStr = '';
  let mode: 'pomodoro' | 'focus' | 'task' | 'idle' = 'idle';

  if (currentTask && currentTask.title) {
    title = currentTask.title;
    if (title.length > 40) {
      title = title.substring(0, 37) + '...';
    }

    if (isPomodoroEnabled) {
      mode = 'pomodoro';
      timeStr = formatTime(currentPomodoroSessionTime);
    } else if (isFocusModeEnabled) {
      mode = 'focus';
      timeStr = formatTime(currentFocusSessionTime);
    } else if (currentTask.timeEstimate) {
      mode = 'task';
      const remainingTime = Math.max(currentTask.timeEstimate - currentTask.timeSpent, 0);
      timeStr = formatTime(remainingTime);
    } else if (currentTask.timeSpent) {
      mode = 'task';
      timeStr = formatTime(currentTask.timeSpent);
    }
  }

  taskWidgetWin.webContents.send('update-content', {
    title,
    time: timeStr,
    mode,
  });
};

export const updateTaskWidgetAlwaysShow = (alwaysShow: boolean): void => {
  isAlwaysShow = alwaysShow;
};

export const getIsTaskWidgetAlwaysShow = (): boolean => isAlwaysShow;

export const getIsTaskWidgetUserForcedVisible = (): boolean => isUserForcedVisible;

export const updateTaskWidgetOpacity = (opacity: number): void => {
  currentOpacity = opacity;
  if (!taskWidgetWin || taskWidgetWin.isDestroyed()) {
    return;
  }
  const clamped = Math.max(0.1, Math.min(1, opacity / 100));
  if (IS_MAC) {
    // On Mac the window is solid (transparent: false), so opacity is applied
    // at the window level rather than via CSS background alpha.
    taskWidgetWin.setOpacity(clamped);
  } else {
    taskWidgetWin.webContents.send('update-opacity', clamped);
  }
};

// Apply the per-instance task widget settings sent by the renderer.
const applyTaskWidgetSettings = (cfg: TaskWidgetConfig | undefined): void => {
  const isEnabled = !!cfg?.isEnabled;
  updateTaskWidgetEnabled(isEnabled);
  if (isEnabled) {
    updateTaskWidgetOpacity(cfg?.opacity ?? 95);
    updateTaskWidgetAlwaysShow(!!cfg?.isAlwaysShow);
  } else {
    updateTaskWidgetAlwaysShow(false);
  }
};

let taskWidgetSettingsListenerRegistered = false;
export const initTaskWidgetSettingsListener = (): void => {
  if (taskWidgetSettingsListenerRegistered) return;
  taskWidgetSettingsListenerRegistered = true;
  ipcMain.on(IPC.UPDATE_TASK_WIDGET_SETTINGS, (_ev, cfg: TaskWidgetConfig) => {
    applyTaskWidgetSettings(cfg);
  });
};

const formatTime = (timeMs: number): string => {
  const totalSeconds = Math.floor(timeMs / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;

  if (hours > 0) {
    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds
      .toString()
      .padStart(2, '0')}`;
  }
  return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
};
