import type { Page } from "@playwright/test";
import type { AsyncSelectOptions } from "./types";
import { throwError } from "./errors";
import { DEFAULT_TIMEOUTS } from "./constants";

/**
 * 选择 Radix UI 下拉框的静态选项
 *
 * @description
 * 自动处理 Radix UI Select 的 DOM 结构和交互流程，无需手动理解组件结构。
 *
 * 支持的选择器策略（按优先级）：
 * 1. `data-testid="${label}-trigger"` - 推荐，最稳定
 * 2. `aria-label="${label}"` + `role="combobox"` - 无障碍属性
 * 3. `text="${label}"` - 文本匹配（兜底）
 *
 * @param page - Playwright Page 对象
 * @param label - 下拉框的标签文本（用于定位触发器）
 * @param value - 要选择的选项值
 * @throws {E2ETestError} 当触发器或选项未找到时
 *
 * @example
 * ```ts
 * // 选择残疾类型
 * await selectRadixOption(page, "残疾类型", "视力残疾");
 *
 * // 选择性别
 * await selectRadixOption(page, "性别", "男");
 * ```
 */
export async function selectRadixOption(page: Page, label: string, value: string): Promise<void> {
  console.debug(`[selectRadixOption] 开始选择: label="${label}", value="${value}"`);
  const trigger = await findTrigger(page, label, value);
  console.debug(`[selectRadixOption] 找到触发器，准备检查元素类型`);

  // 检测是否是原生 select 元素
  const element = "elementHandle" in trigger ? await trigger.elementHandle() : trigger;
  const tagName = element ? await element.evaluate(el => el.tagName.toLowerCase()) : "";
  const isNativeSelect = tagName === "select";

  console.debug(`[selectRadixOption] 元素类型: ${tagName}, 是否原生 select: ${isNativeSelect}`);

  if (isNativeSelect) {
    // 原生 select 元素，使用 selectOption API
    console.debug(`[selectRadixOption] 使用原生 select 方法`);
    await trigger.selectOption(value);
    console.debug(`[selectRadixOption] 选择完成`);
    return;
  }

  // Radix UI Select，点击展开选项列表
  console.debug(`[selectRadixOption] 使用 Radix UI Select 方法`);
  await trigger.click();
  console.debug(`[selectRadixOption] 已点击触发器，等待选项出现`);

  // 等待选项出现（使用 getByRole 查询 accessibility tree）
  await page.getByRole("option").first().waitFor({ state: "visible", timeout: 2000 });
  console.debug(`[selectRadixOption] 选项已出现`);

  const availableOptions = await page.getByRole("option").allTextContents();
  console.debug(`[selectRadixOption] 可用选项:`, availableOptions);
  await findAndClickOption(page, value, availableOptions);
  console.debug(`[selectRadixOption] 选择完成`);
}

/**
 * 查找 Radix UI Select 触发器
 *
 * @description
 * 按优先级尝试四种选择器策略查找触发器元素。
 *
 * @internal
 *
 * @param page - Playwright Page 对象
 * @param label - 下拉框标签
 * @param expectedValue - 期望选择的选项值（用于错误提示）
 * @returns 触发器元素
 * @throws {E2ETestError} 当触发器未找到时
 */
async function findTrigger(page: Page, label: string, expectedValue: string) {
  const timeout = 2000; // 使用较短超时快速尝试多个策略
  const options = { timeout, state: "visible" as const };

  // 策略 1: data-testid (标准格式: ${label}-trigger)
  const testIdSelector = `[data-testid="${label}-trigger"]`;
  try {
    return await page.waitForSelector(testIdSelector, options);
  } catch (err) {
    console.debug(`选择器策略1失败: ${testIdSelector}`, err);
  }

  // 策略 1.5: data-testid 部分匹配 (支持自定义格式如 platform-selector-create)
  // 查找包含标签名的 data-testid，且是按钮角色的元素
  try {
    const partialTestIdSelector = `[data-testid*="${label}"][role="button"]`;
    const element = await page.waitForSelector(partialTestIdSelector, { timeout: timeout / 2 });
    console.debug(`选择器策略1.5成功: 找到 ${partialTestIdSelector}`);
    return element;
  } catch (err) {
    const selectorStr = `[data-testid*="${label}"][role="button"]`;
    console.debug(`选择器策略1.5失败: ${selectorStr}`, err);
  }

  // 策略 2: aria-label + role
  const ariaSelector = `[aria-label="${label}"][role='"combobox"']`;
  try {
    return await page.waitForSelector(ariaSelector, options);
  } catch (err) {
    console.debug(`选择器策略2失败: ${ariaSelector}`, err);
  }

  // 策略 3: role=combobox with accessible name (使用 getByRole 更可靠)
  console.debug(`选择器策略3: 尝试 getByRole(combobox, { name: "${label}" })`);
  try {
    const locator = page.getByRole("combobox", { name: label, exact: true });
    await locator.waitFor({ state: "visible", timeout });
    console.debug(`选择器策略3成功: 找到 combobox "${label}"`);
    return locator;
  } catch (err) {
    console.debug(`选择器策略3失败: getByRole(combobox, { name: "${label}" })`, err);
  }

  // 策略 3.5: role=button with data-testid 包含标签名 (处理 shadcn/ui SelectTrigger)
  try {
    const locator = page.getByRole("button").filter({ hasText: label }).first();
    await locator.waitFor({ state: "visible", timeout });
    // 验证这个按钮确实是选择器触发器（检查它是否在表单中）
    const isInForm = await page.locator('form').locator(`role="button"`).filter({ hasText: label }).count() > 0;
    if (isInForm) {
      console.debug(`选择器策略3.5成功: 找到包含"${label}"的按钮`);
      return locator;
    }
  } catch (err) {
    console.debug(`选择器策略3.5失败: 查找包含"${label}"的按钮`, err);
  }

  // 策略 4: 查找包含标签文本的元素，然后找到相邻的 combobox
  // 这种情况处理: <generic>标签文本</generic><combobox role="combobox">
  console.debug(`选择器策略4: 尝试相邻 combobox 查找`);
  try {
    // 使用 getByText 而不是 text= 选择器，更可靠地处理特殊字符
    const labelElement = page.getByText(label, { exact: true }).first();
    const labelCount = await labelElement.count();
    console.debug(`选择器策略4: 找到 ${labelCount} 个包含文本 "${label}" 的元素`);
    if (labelCount > 0) {
      // 尝试找同级的 combobox（Radix UI 结构）
      const parentLocator = labelElement.locator("..");
      const combobox = parentLocator.locator('[role="combobox"]').first();
      const comboboxCount = await combobox.count();
      console.debug(`选择器策略4: 找到 ${comboboxCount} 个相邻的 combobox`);
      if (comboboxCount > 0) {
        // 确保元素可见后再返回
        await combobox.waitFor({ state: "visible", timeout: 2000 });
        console.debug(`选择器策略4成功: 找到相邻 combobox "${label}"`);
        return combobox;
      }
    }
  } catch (err) {
    console.debug(`选择器策略4失败: 相邻 combobox 查找`, err);
  }

  // 策略 5: 处理标签和 * 分离的情况（如：城市 * 是两个元素）
  // 查找包含标签文本的元素，然后在同一容器中查找相邻的 combobox
  console.debug("选择器策略5: 尝试处理标签和 * 分离的情况");
  try {
    const labelElementLocator = page.getByText(label).first();
    const labelCount = await labelElementLocator.count();
    if (labelCount > 0) {
      const parent = labelElementLocator.locator("..");
      const allComboboxes = parent.locator("..").locator("..").locator("[role=\"combobox\"]");
      const allCount = await allComboboxes.count();
      console.debug("选择器策略5: 找到 " + allCount + " 个 combobox");
      for (let i = 0; i < allCount; i++) {
        const box = allComboboxes.nth(i);
        const isDisabled = await box.getAttribute("data-disabled");
        if (!isDisabled) {
          await box.waitFor({ state: "visible", timeout: 2000 });
          console.debug("选择器策略5成功: 找到启用的 combobox");
          return box;
        }
      }
      console.debug("选择器策略5: 所有 combobox 都被禁用");
    }
  } catch (err) {
    console.debug("选择器策略5失败:", err);
  }

  // 所有策略都失败
  throwError({
    operation: "selectRadixOption",
    target: label,
    expected: expectedValue,
    suggestion: "检查下拉框标签是否正确，或添加 data-testid 属性"
  });
}

/**
 * 查找并点击 Radix UI Select 选项
 *
 * @description
 * 使用 Playwright 的 getByRole 方法定位选项，比 waitForSelector 更可靠。
 * 按优先级尝试 data-value 和无障碍名称两种策略。
 *
 * @internal
 *
 * @param page - Playwright Page 对象
 * @param value - 选项值
 * @param availableOptions - 可用选项列表（用于错误提示）
 * @throws {E2ETestError} 当选项未找到时
 */
async function findAndClickOption(
  page: Page,
  value: string,
  availableOptions: string[]
) {
  const timeout = 2000; // 使用较短超时快速尝试多个策略

  // 策略 1: 使用 getByRole 查找 option（推荐 - 使用 accessibility tree）
  try {
    console.debug(`选项选择器策略1: getByRole("option", { name: "${value}" })`);
    const option = page.getByRole("option", { name: value, exact: true });
    // 先等待元素附加到 DOM（不要求可见）
    await option.waitFor({ state: "attached", timeout });
    // 然后等待可见
    await option.waitFor({ state: "visible", timeout: 2000 });
    await option.click();
    // 等待下拉框关闭（选项消失）
    await page.waitForTimeout(500);

    // 等待所有选项消失（不仅仅是第一个）
    try {
      await page.waitForFunction(() => {
        const options = document.querySelectorAll('[role="option"]') as NodeListOf<HTMLElement>;
        return options.length === 0 || Array.from(options).every(opt => !opt.offsetParent);
      }, { timeout: 5000 });
      await page.waitForTimeout(500);
    } catch {
      // 选项可能已经消失，继续执行
    }
    console.debug(`选项选择器策略1成功`);
    return;
  } catch (err) {
    console.debug(`选项选择器策略1失败:`, err);
  }

  // 策略 2: data-value 属性（精确匹配）
  try {
    console.debug(`选项选择器策略2: [role="option"][data-value="${value}"]`);
    const option = await page.waitForSelector(`[role="option"][data-value="${value}"]`, {
      timeout,
      state: "visible"
    });
    await option.click();
    // 等待下拉框关闭
    await page.waitForTimeout(500);

    // 等待所有选项消失
    try {
      await page.waitForFunction(() => {
        const options = document.querySelectorAll('[role="option"]') as NodeListOf<HTMLElement>;
        return options.length === 0 || Array.from(options).every(opt => !opt.offsetParent);
      }, { timeout: 5000 });
      await page.waitForTimeout(500);
    } catch {
      // 选项可能已经消失，继续执行
    }
    console.debug(`选项选择器策略2成功`);
    return;
  } catch (err) {
    console.debug(`选项选择器策略2失败:`, err);
  }

  // 未找到选项
  throwError({
    operation: "selectRadixOption",
    target: `选项 "${value}"`,
    available: availableOptions,
    suggestion: "检查选项值是否正确，或确认选项已加载到 DOM 中"
  });
}

/**
 * 选择 Radix UI 下拉框的异步加载选项
 *
 * @description
 * 用于选择通过 API 异步加载的 Radix UI Select 选项。
 * 默认自动等待网络请求完成和选项出现在 DOM 中。
 *
 * 支持的选择器策略（按优先级）：
 * 1. `data-testid="${label}-trigger"` - 推荐，最稳定
 * 2. `aria-label="${label}"` + `role="combobox"` - 无障碍属性
 * 3. `text="${label}"` - 文本匹配（兜底）
 *
 * @param page - Playwright Page 对象
 * @param label - 下拉框的标签文本（用于定位触发器）
 * @param value - 要选择的选项值
 * @param options - 可选配置
 * @param options.timeout - 超时时间（毫秒），默认 5000ms
 * @param options.waitForOption - 是否等待选项加载完成（默认：true）
 * @param options.waitForNetworkIdle - 是否等待网络空闲后再操作（默认：true）
 * @throws {E2ETestError} 当触发器未找到或等待超时时
 *
 * @example
 * ```ts
 * // 选择省份（异步加载，默认等待网络空闲）
 * await selectRadixOptionAsync(page, '省份', '广东省');
 *
 * // 选择城市（自定义超时，禁用网络空闲等待）
 * await selectRadixOptionAsync(page, '城市', '深圳市', {
 *   timeout: 30000,
 *   waitForNetworkIdle: false
 * });
 * ```
 */
export async function selectRadixOptionAsync(
  page: Page,
  label: string,
  value: string,
  options?: AsyncSelectOptions
): Promise<void> {
  console.debug(`[selectRadixOptionAsync] 开始选择: label="${label}", value="${value}"`);

  // 1. 合并默认配置
  const config = {
    timeout: options?.timeout ?? DEFAULT_TIMEOUTS.async,
    waitForOption: options?.waitForOption ?? true,
    waitForNetworkIdle: options?.waitForNetworkIdle ?? true
  };

  // 2. 查找触发器（复用静态 Select 的逻辑）
  const trigger = await findTrigger(page, label, value);
  console.debug(`[selectRadixOptionAsync] 找到触发器，准备检查元素类型`);

  // 3. 检测是否是原生 select 元素
  const element = "elementHandle" in trigger ? await trigger.elementHandle() : trigger;
  const tagName = element ? await element.evaluate(el => el.tagName.toLowerCase()) : "";
  const isNativeSelect = tagName === "select";

  console.debug(`[selectRadixOptionAsync] 元素类型: ${tagName}, 是否原生 select: ${isNativeSelect}`);

  if (isNativeSelect) {
    // 原生 select 元素，使用 selectOption API
    console.debug(`[selectRadixOptionAsync] 使用原生 select 方法`);
    await trigger.selectOption(value);
    console.debug(`[selectRadixOptionAsync] 选择完成`);
    return;
  }

  // 4. 确保之前的下拉框已完全关闭（级联选择场景）
  console.debug(`[selectRadixOptionAsync] 检查并等待之前的下拉框关闭`);
  try {
    await page.waitForFunction(() => {
      const options = document.querySelectorAll('[role="option"]') as NodeListOf<HTMLElement>;
      return options.length === 0 || Array.from(options).every(opt => !opt.offsetParent);
    }, { timeout: 2000 });
    console.debug(`[selectRadixOptionAsync] 之前的下拉框已关闭`);
  } catch {
    // 没有之前的下拉框或已关闭，继续执行
    console.debug(`[selectRadixOptionAsync] 没有需要关闭的下拉框`);
  }

  // 5. 点击 Radix UI Select 触发器展开选项列表
  console.debug(`[selectRadixOptionAsync] 使用 Radix UI Select 方法`);
  await trigger.click();
  console.debug(`[selectRadixOptionAsync] 已点击触发器，等待选项出现`);

  // 5. 等待选项出现（Radix UI Select v2 没有 listbox，直接等待 option）
  // 选项在 Portal 中渲染，需要短暂等待
  await page.waitForTimeout(100);
  await page.getByRole("option").first().waitFor({
    state: "visible",
    timeout: 2000
  });
  console.debug(`[selectRadixOptionAsync] 选项已出现`);

  // 6. 等待网络空闲（处理大量数据加载）
  // 注意：网络空闲等待失败不会中断流程，因为某些场景下网络可能始终不空闲
  if (config.waitForNetworkIdle) {
    console.debug(`[selectRadixOptionAsync] 等待网络空闲 (timeout: ${config.timeout}ms)`);
    try {
      await page.waitForLoadState('networkidle', { timeout: config.timeout });
      console.debug(`[selectRadixOptionAsync] 网络空闲`);
    } catch (err) {
      console.debug('[selectRadixOptionAsync] 网络空闲等待超时，继续尝试选择选项', err);
    }
  }

  // 7. 等待选项出现并选择
  if (config.waitForOption) {
    console.debug(`[selectRadixOptionAsync] 等待选项加载 (timeout: ${config.timeout}ms)`);
    await waitForOptionAndSelect(page, value, config.timeout);
  } else {
    // 不等待选项，直接尝试选择（向后兼容）
    const availableOptions = await page.locator('[role="option"]').allTextContents();
    console.debug(`[selectRadixOptionAsync] 可用选项:`, availableOptions);
    await findAndClickOption(page, value, availableOptions);
  }
  console.debug(`[selectRadixOptionAsync] 选择完成`);
}

/**
 * 等待异步选项加载并完成选择
 *
 * @description
 * 使用重试机制等待异步加载的选项出现在 DOM 中，
 * 然后完成选择操作。
 *
 * 按优先级尝试两种选择器策略：
 * 1. getByRole("option", { name: value }) - 使用 accessibility tree
 * 2. data-value 属性（精确匹配）
 *
 * @internal
 *
 * @param page - Playwright Page 对象
 * @param value - 选项值
 * @param timeout - 超时时间（毫秒）
 * @throws {E2ETestError} 当等待超时时
 */
async function waitForOptionAndSelect(
  page: Page,
  value: string,
  timeout: number
): Promise<void> {
  const startTime = Date.now();
  const retryInterval = 100; // 重试间隔（毫秒）

  // 级联选择场景：等待之前的选项完全消失，新选项有时间加载
  // 这是一个关键等待，确保网络请求有足够时间返回新选项
  console.debug(`[waitForOptionAndSelect] 等待新选项加载（初始等待 500ms）`);
  await page.waitForTimeout(500);

  // 等待选项出现（使用重试机制）
  while (Date.now() - startTime < timeout) {
    try {
      // 策略 1: getByRole("option", { name: value }) - 更可靠
      console.debug(`异步选项选择策略1: getByRole("option", { name: "${value}" })`);
      const option = page.getByRole("option", { name: value, exact: true });

      // 等待元素附加到 DOM
      await option.waitFor({ state: "attached", timeout: retryInterval });
      // 等待元素可见
      await option.waitFor({ state: "visible", timeout: 500 });

      await option.click();
      // 等待下拉框关闭
      await page.waitForTimeout(500);

      // 等待所有选项消失
      try {
        await page.waitForFunction(() => {
          const options = document.querySelectorAll('[role="option"]') as NodeListOf<HTMLElement>;
          return options.length === 0 || Array.from(options).every(opt => !opt.offsetParent);
        }, { timeout: 5000 });
        await page.waitForTimeout(500);
      } catch {
        // 选项可能已经消失，继续执行
      }
      console.debug(`异步选项选择策略1成功`);
      return; // 成功选择
    } catch (err) {
      console.debug(`异步选项选择策略1失败:`, err);
    }

    // 等待一小段时间后重试
    try {
      await page.waitForTimeout(retryInterval);
    } catch {
      // waitForTimeout 可能被中断，忽略错误继续重试
    }
  }

  // 策略 2: 尝试 data-value 属性匹配（一次性尝试，不重试）
  try {
    console.debug(`异步选项选择策略2: [role="option"][data-value="${value}"]`);
    const option = await page.waitForSelector(`[role="option"][data-value="${value}"]`, {
      timeout: 2000,
      state: 'visible'
    });
    await option.click();
    // 等待下拉框关闭
    await page.waitForTimeout(500);

    // 等待所有选项消失
    try {
      await page.waitForFunction(() => {
        const options = document.querySelectorAll('[role="option"]') as NodeListOf<HTMLElement>;
        return options.length === 0 || Array.from(options).every(opt => !opt.offsetParent);
      }, { timeout: 5000 });
      await page.waitForTimeout(500);
    } catch {
      // 选项可能已经消失，继续执行
    }
    console.debug(`异步选项选择策略2成功`);
    return; // 成功选择
  } catch {
    // data-value 策略也失败，继续抛出错误
  }

  // 超时：获取当前可用的选项用于错误提示
  const availableOptions = await page.locator('[role="option"]').allTextContents();

  throwError({
    operation: 'selectRadixOptionAsync',
    target: `选项 "${value}"`,
    expected: `在 ${timeout}ms 内加载`,
    available: availableOptions,
    suggestion: '检查网络请求是否正常，或增加超时时间'
  });
}
