import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import type { WindowHandle } from './types.js';

/**
 * 窗口定位与激活（通用，跨应用复用）
 *
 *  - findByAppName: 用 node-screenshots 枚举 + 按 appName 过滤
 *    （不依赖 FindWindowW，因为 PoC 实测微信主窗口 title 是中文"微信"，
 *      FindWindowW('Weixin') 返回的是子窗口，hwnd 不对）
 *  - activate: koffi 调 user32.dll 的 SetForegroundWindow + ShowWindow(SW_RESTORE)
 *  - isForeground: GetForegroundWindow + GetWindowThreadProcessId 比对 pid
 *
 * platform !== 'win32' 时，任意方法均抛 'unsupported-platform'。
 */
@Provide()
@Scope(ScopeEnum.Singleton)
export class WindowLocator {
  /** koffi lib 缓存，同进程内只 load 一次 */
  private user32: any = null;

  /**
   * 懒加载 user32.dll，非 win32 直接抛错
   */
  private ensureUser32(): any {
    if (process.platform !== 'win32') {
      throw new Error('unsupported-platform');
    }
    if (this.user32) return this.user32;
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const koffi = require('koffi');
    const lib = koffi.load('user32.dll');
    this.user32 = {
      SetForegroundWindow: lib.func('SetForegroundWindow', 'bool', ['void *']),
      ShowWindow: lib.func('ShowWindow', 'bool', ['void *', 'int']),
      GetForegroundWindow: lib.func('GetForegroundWindow', 'void *', []),
      GetWindowThreadProcessId: lib.func('GetWindowThreadProcessId', 'uint32', ['void *', 'uint32 *']),
    };
    return this.user32;
  }

  /**
   * 用 node-screenshots 枚举所有窗口，按 appName 过滤后返回句柄
   *
   * @param appName      - 目标进程的 appName，如 'WeChat'
   * @param opts.skipMinimized - 是否跳过最小化窗口（默认 true）
   * @param opts.largest       - 多个匹配时取面积最大的（默认 true）
   * @returns WindowHandle 或 null（未找到时）
   */
  findByAppName(
    appName: string,
    opts: { skipMinimized?: boolean; largest?: boolean } = {},
  ): WindowHandle | null {
    if (process.platform !== 'win32') {
      throw new Error('unsupported-platform');
    }
    const skipMinimized = opts.skipMinimized ?? true;
    const largest = opts.largest ?? true;

    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const ns = require('node-screenshots') as { Window: { all(): Array<any> } };

    // ★ v4 修复:node-screenshots 的 Window 实例属性是 getter 方法,不是直接字段
    // 统一通过 readProp(w, 'appName') 兼容两种暴露方式
    const readProp = (w: any, key: string): any => {
      const v = w?.[key];
      if (typeof v === 'function') {
        try { return v.call(w); } catch { return undefined; }
      }
      // 兼容下划线变体
      const snake = key.replace(/[A-Z]/g, ch => '_' + ch.toLowerCase());
      const v2 = w?.[snake];
      if (typeof v2 === 'function') {
        try { return v2.call(w); } catch { return undefined; }
      }
      return v ?? v2;
    };

    // 拿所有窗口,先打印诊断(MVP 调试期)
    const allWindows = ns.Window.all();
    const summarize = (w: any) => ({
      appName: readProp(w, 'appName'),
      title: readProp(w, 'title'),
      width: readProp(w, 'width'),
      height: readProp(w, 'height'),
      isMinimized: readProp(w, 'isMinimized'),
    });
    // eslint-disable-next-line no-console
    console.log('[WindowLocator DEBUG] looking for appName=%j; all windows (sample 30):',
      appName, allWindows.slice(0, 30).map(summarize));

    // ★ v4 兼容:appName 精确 OR title/appName 模糊包含(大小写不敏感)
    const needle = appName.toLowerCase();
    let candidates = allWindows.filter(w => {
      const an = String(readProp(w, 'appName') ?? '').toLowerCase();
      const ti = String(readProp(w, 'title') ?? '').toLowerCase();
      return an === needle
        || an.includes(needle)
        || ti.includes(needle)
        || (appName === 'Weixin' && (ti.includes('微信') || an.includes('微信')));
    });

    // 跳过最小化窗口
    if (skipMinimized) {
      candidates = candidates.filter(w => !readProp(w, 'isMinimized'));
    }

    // 跳过 0 面积窗口
    candidates = candidates.filter(w => {
      const wv = Number(readProp(w, 'width') ?? 0);
      const hv = Number(readProp(w, 'height') ?? 0);
      return wv > 0 && hv > 0;
    });

    // eslint-disable-next-line no-console
    console.log('[WindowLocator DEBUG] candidates after filter:', candidates.map(summarize));

    if (candidates.length === 0) return null;

    // 多个匹配时取面积最大的(通常为主窗口)
    if (largest && candidates.length > 1) {
      candidates.sort((a, b) => {
        const aw = Number(readProp(a, 'width') ?? 0) * Number(readProp(a, 'height') ?? 0);
        const bw = Number(readProp(b, 'width') ?? 0) * Number(readProp(b, 'height') ?? 0);
        return bw - aw;
      });
    }

    const w = candidates[0];
    return {
      hwnd: Number(readProp(w, 'hwnd') ?? readProp(w, 'id') ?? 0),
      pid: Number(readProp(w, 'processId') ?? readProp(w, 'pid') ?? 0),
      appName: String(readProp(w, 'appName') ?? appName),
      title: String(readProp(w, 'title') ?? ''),
      bounds: {
        x: Number(readProp(w, 'x') ?? 0),
        y: Number(readProp(w, 'y') ?? 0),
        width: Number(readProp(w, 'width') ?? 0),
        height: Number(readProp(w, 'height') ?? 0),
      },
      nsWindow: w,
    };
  }

  /**
   * 激活窗口：先 ShowWindow(SW_RESTORE) 恢复显示，再 SetForegroundWindow 置前
   *
   * 注意：SetForegroundWindow 在某些系统策略下可能需要 AttachThreadInput 兜底，
   * MVP 阶段暂不实现，Phase H E2E 若发现失败再补。
   */
  async activate(handle: WindowHandle): Promise<void> {
    const u = this.ensureUser32();
    const SW_RESTORE = 9;
    // koffi 接受数字作为 void * 参数（hwnd 本身是数字）
    u.ShowWindow(handle.hwnd, SW_RESTORE);
    u.SetForegroundWindow(handle.hwnd);
  }

  /**
   * 判断 handle 对应的进程是否处于前台
   *
   * 通过 GetForegroundWindow 获取当前前台窗口句柄，
   * 再用 GetWindowThreadProcessId 取其 pid，与 handle.pid 比对。
   */
  isForeground(handle: WindowHandle): boolean {
    const u = this.ensureUser32();
    const fg = u.GetForegroundWindow();
    if (!fg) return false;

    // koffi out 参数：用数组接收 uint32 输出
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const pidBuf = [0]; // koffi 支持 array 作为 out-int，见 koffi 文档
    u.GetWindowThreadProcessId(fg, pidBuf);
    return Number(pidBuf[0]) === handle.pid;
  }
}
