// @ts-nocheck
import { generateOpId } from '../../utils/uuid.js';
import { FreeformCameraWindow } from './FreeformCameraWindow.js';

/**
 * Orchestrates the freeform layout: creates/destroys FreeformCameraWindow instances
 * based on participant visibility, saves/restores positions, and manages spotlight.
 */
export class FreeformLayoutManager {
  /**
   * @param {object} adapter - FoundryAdapter instance
   * @param {object} controller - ScryingPoolController instance
   * @param {object} stateStore - StateStore instance
   * @param {object} [options] - Extra options
   * @param {Function} [options.onOpenSettings] - Callback to open the settings panel
   */
  constructor(adapter, controller, stateStore, options = {}) {
    this._adapter = adapter;
    this._controller = controller;
    this._stateStore = stateStore;
    this._onOpenSettings = options.onOpenSettings;

    /** @type {Map<string, FreeformCameraWindow>} */

    /** @type {Map<string, FreeformCameraWindow>} */
    this._windows = new Map();
    this._spotlightUserId = null;
    this._cascadeIndex = 0;
    this._saveTimer = null;
    this._initialized = false;
    /** @type {Array<{event: string, handler: Function}>} */
    this._hookHandlers = [];
  }

  /**
   * Registers hooks for sync on state/user changes.
   * Safe to call multiple times — guards against double init.
   */
  init() {
    if (this._initialized) return;
    this._initialized = true;

    const add = (event, handler) => {
      this._adapter.hooks.on(event, handler);
      this._hookHandlers.push({ event, handler });
    };

    add('scrying-pool:stateChanged', () => this.sync());
    add('updateUser', () => this.sync());
    add('userConnected', () => {
      setTimeout(() => this.sync(), 1500);
    });
  }

  /**
   * Reconciles visible participants with open windows.
   * Creates windows for newly visible users, closes for hidden/disconnected.
   */
  sync() {
    if (!this._initialized) return;
    if (!this._adapter.users.isGM()) return;

    const users = this._adapter.users.all();
    const showGMSelfFeed = this._adapter.settings?.get?.('showGMSelfFeed') ?? true;
    const currentUserId = this._adapter.users.current?.()?.id;

    const visibleUserIds = users
      .filter(u => {
        if (u.id === currentUserId && !showGMSelfFeed) return false;
        const state = this._stateStore.getState(u.id);
        return state !== 'hidden' && state !== 'ghost' && state !== 'offline';
      })
      .map(u => u.id);

    const visibleSet = new Set(visibleUserIds);
    const currentSet = new Set(this._windows.keys());

    // Close windows for users no longer visible
    for (const userId of currentSet) {
      if (!visibleSet.has(userId)) {
        this._destroyWindow(userId);
      }
    }

    // Create windows for newly visible users
    for (const userId of visibleUserIds) {
      if (!currentSet.has(userId)) {
        this._createWindow(userId);
      }
    }

    // Re-apply spotlight if the spotlighted user is still visible
    if (this._spotlightUserId && !visibleSet.has(this._spotlightUserId)) {
      this._spotlightUserId = null;
    }
  }

  /**
   * Creates a FreeformCameraWindow for a user.
   * @param {string} userId
   */
  _createWindow(userId) {
    const saved = this._loadSavedPosition(userId);
    const position = saved ?? this._cascadePosition();

    const win = new FreeformCameraWindow({
      userId,
      adapter: this._adapter,
      manager: this,
      position,
    });

    this._windows.set(userId, win);

    if (this._spotlightUserId === userId) {
      win.spotlight = true;
    }

    win.render(true);

    this._cascadeIndex++;
  }

  /**
   * Closes and removes a FreeformCameraWindow for a user.
   * @param {string} userId
   */
  _destroyWindow(userId) {
    const win = this._windows.get(userId);
    if (!win) return;
    win.removeDOM();
    this._windows.delete(userId);
  }

  /**
   * Returns a cascading position for new windows without saved positions.
   * @returns {{left: number, top: number, width: number, height: number}}
   */
  _cascadePosition() {
    const base = 50;
    const step = 30;
    const maxOffset = 300;
    const offset = (this._cascadeIndex * step) % (maxOffset + step);
    return {
      left: base + offset,
      top: base + offset,
      width: 320,
      height: 300,
    };
  }

  /**
   * Loads a saved window position for a user from the freeformLayout setting.
   * @param {string} userId
   * @returns {{left:number, top:number, width:number, height:number}|null}
   */
  _loadSavedPosition(userId) {
    try {
      const data = this._adapter.settings?.get?.('freeformLayout');
      if (data?.windows?.[userId]) {
        const { left, top, width, height } = data.windows[userId];
        if (left != null && top != null) {
          return { left, top, width: width ?? 320, height: height ?? 300 };
        }
      }
    } catch (_) {}
    return null;
  }

  /**
   * Toggles spotlight on a user.
   * If already spotlighted, removes spotlight. Otherwise sets it.
   * @param {string} userId
   */
  setSpotlight(userId) {
    if (this._spotlightUserId === userId) {
      // Un-spotlight
      const win = this._windows.get(userId);
      if (win) win.spotlight = false;
      this._spotlightUserId = null;
    } else {
      // Remove current spotlight
      if (this._spotlightUserId) {
        const old = this._windows.get(this._spotlightUserId);
        if (old) old.spotlight = false;
      }
      // Set new spotlight
      const win = this._windows.get(userId);
      if (win) win.spotlight = true;
      this._spotlightUserId = userId;
    }
  }

  /**
   * Hides a participant from the table via the controller.
   * Called when a freeform window's hide button or close button is triggered.
   * @param {string} userId
   */
  hideParticipant(userId) {
    const opId = generateOpId();
    const baseRevision = this._controller?.getRevision?.(userId) ?? 0;
    this._controller?.action('freeform', userId, 'hidden', opId, baseRevision);
  }

  /**
   * Opens the settings/config panel.
   * Called when the GM double-clicks a freeform camera window.
   */
  openSettings() {
    this._onOpenSettings?.();
  }

  /**
   * Schedules a debounced save of all window positions.
   */
  _scheduleSave() {
    if (this._saveTimer) {
      clearTimeout(this._saveTimer);
    }
    this._saveTimer = setTimeout(() => this._savePositions(), 500);
  }

  /**
   * Persists all window positions to the freeformLayout world setting.
   */
  _savePositions() {
    const windows = {};
    for (const [userId, win] of this._windows) {
      const pos = win.position;
      windows[userId] = {
        left: pos.left,
        top: pos.top,
        width: pos.width,
        height: pos.height,
      };
    }
    this._adapter.settings?.set?.('freeformLayout', { windows }).catch(err => {
      console.warn('[ScryingPool] Failed to save freeform layout positions:', err);
    });
  }

  /**
   * Closes all windows and cleans up hooks. Safe to call multiple times.
   */
  destroy() {
    for (const [userId] of this._windows) {
      this._destroyWindow(userId);
    }
    this._windows.clear();
    this._spotlightUserId = null;
    this._cascadeIndex = 0;

    for (const { event, handler } of this._hookHandlers) {
      try { this._adapter.hooks.off(event, handler); } catch (_) {}
    }
    this._hookHandlers = [];
    this._initialized = false;

    if (this._saveTimer) {
      clearTimeout(this._saveTimer);
      this._saveTimer = null;
    }
  }
}
