import type { Page } from 'playwright';
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
import type { FormFillResultDTO } from '../../../../application/dto/FormFillResultDTO';
import type { ClickResultDTO } from '../../../../application/dto/ClickResultDTO';
import type { ModalResultDTO } from '../../../../application/dto/ModalResultDTO';
import { StepId } from '../../../../domain/value-objects/StepId';
import type { PlaywrightConfig } from '../core/PlaywrightAutomationAdapter';
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
import { IRACING_SELECTORS, IRACING_TIMEOUTS } from './IRacingSelectors';
import { SafeClickService } from './SafeClickService';


export class IRacingDomInteractor {
  constructor(
    private readonly config: Required<PlaywrightConfig>,
    private readonly browserSession: PlaywrightBrowserSession,
    private readonly safeClickService: SafeClickService,
    private readonly logger?: LoggerPort,
  ) {}

  private log(level: 'debug' | 'info' | 'warn' | 'error', message: string, context?: Record<string, unknown>): void {
    if (!this.logger) {
      return;
    }
    const logger = this.logger as Record<
      'debug' | 'info' | 'warn' | 'error',
      (msg: string, ctx?: Record<string, unknown>) => void
    >;
    logger[level](message, context);
  }

  private isRealMode(): boolean {
    return this.config.mode === 'real';
  }

  private getPage(): Page {
    const page = this.browserSession.getPage();
    if (!page) {
      throw new Error('Browser not connected');
    }
    return page;
  }

  // ===== Public port-facing operations =====

  async fillFormField(fieldName: string, value: string): Promise<FormFillResultDTO> {
    const page = this.browserSession.getPage();
    if (!page) {
      return { success: false, fieldName, valueSet: value, error: 'Browser not connected' };
    }

    const fieldMap: Record<string, string> = {
      sessionName: `${IRACING_SELECTORS.steps.sessionName}, ${IRACING_SELECTORS.steps.sessionNameAlt}`,
      password: `${IRACING_SELECTORS.steps.password}, ${IRACING_SELECTORS.steps.passwordAlt}`,
      description: `${IRACING_SELECTORS.steps.description}, ${IRACING_SELECTORS.steps.descriptionAlt}`,
      adminSearch: IRACING_SELECTORS.steps.adminSearch,
      carSearch: IRACING_SELECTORS.steps.carSearch,
      trackSearch: IRACING_SELECTORS.steps.trackSearch,
      maxDrivers: IRACING_SELECTORS.steps.maxDrivers,
    };

    if (!Object.prototype.hasOwnProperty.call(fieldMap, fieldName)) {
      return { success: false, fieldName, valueSet: value, error: `Unknown form field: ${fieldName}` };
    }

    const selector = fieldMap[fieldName as keyof typeof fieldMap] ?? IRACING_SELECTORS.fields.textInput;
    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    this.log('debug', 'Filling form field', { fieldName, _selector, mode: this.config.mode });

    try {
      await page.waitForSelector(_selector, { state: 'attached', timeout });

      try {
        await page.fill(selector, value);
        return { success: true, fieldName, valueSet: value };
      } catch (fillErr) {
        if (this.isRealMode()) {
          throw fillErr;
        }

        try {
          await page.evaluate(({ sel, val }) => {
            document
              .querySelectorAll<HTMLElement>('.wizard-step.hidden, .modal.hidden, .wizard-step[hidden]')
              .forEach((el) => {
                el.classList.remove('hidden');
                el.removeAttribute('hidden');
              });
            const el = document.querySelector(sel) as HTMLInputElement | HTMLTextAreaElement | null;
            if (!el) return;
            el.value = val;
            el.dispatchEvent(new Event('input', { bubbles: true }));
            el.dispatchEvent(new Event('change', { bubbles: true }));
          }, { sel: selector, val: value });
          return { success: true, fieldName, valueSet: value };
        } catch (evalErr) {
          const message = evalErr instanceof Error ? evalErr.message : String(evalErr);
          return { success: false, fieldName, valueSet: value, error: message };
        }
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return { success: false, fieldName, valueSet: value, error: message };
    }
  }

  async clickElement(target: string): Promise<ClickResultDTO> {
    const page = this.browserSession.getPage();
    if (!page) {
      return { success: false, target, error: 'Browser not connected' };
    }

    try {
      const selector = this.getActionSelector(target);
      const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

      this.log('debug', 'Clicking element', { target, _selector, mode: this.config.mode });
      await page.waitForSelector(_selector, { state: 'attached', timeout });
      await page.click(selector);
      return { success: true, target };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return { success: false, target, error: message };
    }
  }

  async handleModal(stepId: StepId, action: string): Promise<ModalResultDTO> {
    const page = this.browserSession.getPage();
    if (!page) {
      return { success: false, stepId: stepId.value, action, error: 'Browser not connected' };
    }

    try {
      const modalSelector = IRACING_SELECTORS.wizard.modal;
      const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

      this.log('debug', 'Handling modal', { action, mode: this.config.mode });
      await page.waitForSelector(modalSelector, { state: 'attached', timeout });

      let buttonSelector: string;
      if (action === 'confirm') {
        buttonSelector = IRACING_SELECTORS.wizard.confirmButton;
      } else if (action === 'cancel') {
        buttonSelector = IRACING_SELECTORS.wizard.cancelButton;
      } else {
        return { success: false, stepId: stepId.value, action, error: `Unknown modal action: ${action}` };
      }

      await page.click(buttonSelector);
      return { success: true, stepId: stepId.value, action };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return { success: false, stepId: stepId.value, action, error: message };
    }
  }

  // ===== Public interaction helpers used by adapter steps =====

  async fillField(fieldName: string, value: string): Promise<FormFillResultDTO> {
    const page = this.browserSession.getPage();
    if (!page) {
      return { success: false, fieldName, valueSet: value, error: 'Browser not connected' };
    }
    const selector = this.getFieldSelector(fieldName);
    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    this.log('debug', 'fillField', { fieldName, _selector, mode: this.config.mode });

    if (!this.isRealMode()) {
      try {
        await page.evaluate(() => {
          document
            .querySelectorAll<HTMLElement>('.wizard-step.hidden, .modal.hidden, .wizard-step[hidden]')
            .forEach((el) => {
              el.classList.remove('hidden');
              el.removeAttribute('hidden');
            });
        });
      } catch {
        // ignore
      }
    }

    await page.waitForSelector(_selector, { state: 'attached', timeout });

    try {
      await page.fill(selector, value);
      return { success: true, fieldName, valueSet: value };
    } catch (fillErr) {
      if (this.isRealMode()) {
        const message = fillErr instanceof Error ? fillErr.message : String(fillErr);
        return { success: false, fieldName, valueSet: value, error: message };
      }

      try {
        await page.evaluate(({ sel, val }) => {
          const el = document.querySelector(sel) as HTMLInputElement | HTMLTextAreaElement | null;
          if (!el) return;
          el.value = val;
          el.dispatchEvent(new Event('input', { bubbles: true }));
          el.dispatchEvent(new Event('change', { bubbles: true }));
        }, { sel: selector, val: value });
        return { success: true, fieldName, valueSet: value };
      } catch (evalErr) {
        const message = evalErr instanceof Error ? evalErr.message : String(evalErr);
        return { success: false, fieldName, valueSet: value, error: message };
      }
    }
  }

  async fillFieldWithFallback(fieldName: string, value: string): Promise<FormFillResultDTO> {
    const page = this.browserSession.getPage();
    if (!page) {
      return { success: false, fieldName, valueSet: value, error: 'Browser not connected' };
    }

    const selector = this.getFieldSelector(fieldName);
    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    const selectors = _selector.split(', ').map((s) => s.trim());

    for (const sel of selectors) {
      try {
        this.log('debug', `Trying _selector for ${fieldName}`, { _selector: sel });

        const element = page.locator(sel).first();
        const isVisible = await element.isVisible().catch(() => false);

        if (isVisible) {
          await element.waitFor({ state: 'attached', timeout });
          await element.fill(value);
          this.log('info', `Successfully filled ${fieldName}`, { _selector: sel, value });
          return { success: true, fieldName, valueSet: value };
        }
      } catch (error) {
        this.log('debug', `Selector failed for ${fieldName}`, { _selector: sel, error: String(error) });
      }
    }

    try {
      this.log('debug', `Trying combined selector for ${fieldName}`, { selector });
      await page.waitForSelector(_selector, { state: 'attached', timeout });
      await page.fill(selector, value);
      return { success: true, fieldName, valueSet: value };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', `Failed to fill ${fieldName}`, { _selector, error: message });
      return { success: false, fieldName, valueSet: value, error: message };
    }
  }

  async clickAction(action: string): Promise<ClickResultDTO> {
    const page = this.browserSession.getPage();
    if (!page) {
      return { success: false, target: action, error: 'Browser not connected' };
    }

    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;
    let selector: string;

    if (!this.isRealMode()) {
      const mockMap: Record<string, string> = {
        create: '#create-race-btn, [data-action="create"], button:has-text("Create a Race")',
        next: '.wizard-footer a.btn.btn-primary, .wizard-footer a:has(.icon-caret-right), [data-action="next"], button:has-text("Next")',
        back: '.wizard-footer a.btn.btn-secondary, .wizard-footer a:has(.icon-caret-left):has-text("Back"), [data-action="back"], button:has-text("Back")',
        confirm: '.modal-footer a.btn-success, button:has-text("Confirm"), [data-action="confirm"]',
        cancel: '.modal-footer a.btn-secondary, button:has-text("Cancel"), [data-action="cancel"]',
        close: '[aria-label="Close"], #gridpilot-close-btn',
      };
      selector = mockMap[action] || `[data-action="${action}"], button:has-text("${action}")`;
    } else {
      selector = this.getActionSelector(action);
    }

    await page.waitForSelector(_selector, { state: 'attached', timeout });
    await this.safeClickService.safeClick(selector, { timeout });
    return { success: true, target: selector };
  }

  async clickNextButton(nextStepName: string): Promise<void> {
    const page = this.getPage();

    if (!this.isRealMode()) {
      const timeout = this.config.timeout;

      try {
        const footerButtons = page.locator('.wizard-footer a.btn, .wizard-footer button');
        const count = await footerButtons.count().catch(() => 0);

        if (count > 0) {
          const targetText = nextStepName.toLowerCase();
          for (let i = 0; i < count; i++) {
            const button = footerButtons.nth(i);
            const text = (await button.innerText().catch(() => '')).trim().toLowerCase();
            if (text && text.includes(targetText)) {
              await button.click({ timeout, force: true });
              this.log('info', 'Clicked mock next button via footer text match', {
                nextStepName,
                text,
              });
              return;
            }
          }
        }
      } catch {
      }

      await this.clickAction('next');
      return;
    }

    const timeout = IRACING_TIMEOUTS.elementWait;

    const nextButtonSelector = IRACING_SELECTORS.wizard.nextButton;
    const fallbackSelector = `.wizard-footer a.btn:has-text("${nextStepName}")`;

    try {
      this.log('debug', 'Attempting next button (primary) with forced click', { _selector: nextButtonSelector });
      try {
        await this.safeClickService.safeClick(nextButtonSelector, { timeout, force: true });
        this.log('info', `Clicked next button to ${nextStepName} (primary forced)`);
        return;
      } catch (e) {
        this.log('debug', 'Primary forced click failed, falling back', { error: String(e) });
      }

      this.log('debug', 'Trying fallback next button (forced)', { _selector: fallbackSelector });
      try {
        await this.safeClickService.safeClick(fallbackSelector, { timeout, force: true });
        this.log('info', `Clicked next button (fallback) to ${nextStepName}`);
        return;
      } catch (e) {
        this.log('debug', 'Fallback forced click failed, trying last resort', { error: String(e) });
      }

      const lastResort = '.wizard-footer a.btn:not(.disabled):last-child';
      await this.safeClickService.safeClick(lastResort, { timeout, force: true });
      this.log('info', `Clicked next button (last resort) to ${nextStepName}`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', `Failed to click next button to ${nextStepName}`, { error: message });
      throw new Error(`Failed to navigate to ${nextStepName}: ${message}`);
    }
  }

  async selectDropdown(name: string, value: string): Promise<void> {
    const page = this.getPage();
    const selector = this.getDropdownSelector(name);
    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    try {
      await page.waitForSelector(_selector, { state: 'attached', timeout });
      await page.selectOption(selector, value);
      return;
    } catch {
      // fallthrough
    }

    const heuristics = [
      `select[id*="${name}"]`,
      `select[name*="${name}"]`,
      `select[data-dropdown*="${name}"]`,
      `select`,
      `[data-dropdown="${name}"]`,
      `[data-dropdown*="${name}"]`,
      `[role="listbox"] select`,
      `[role="listbox"]`,
    ];

    for (const h of heuristics) {
      try {
        const count = await page.locator(h).first().count().catch(() => 0);
        if (count > 0) {
          const tag = await page
            .locator(h)
            .first()
            .evaluate((el: Element) =>
              String(el.tagName || '').toLowerCase(),
            )
            .catch(() => '');
          if (tag === 'select') {
            try {
              await page.selectOption(h, value);
              return;
            } catch {
              await page.evaluate(
                ({ sel, val }) => {
                  const els = Array.from(document.querySelectorAll(sel)) as HTMLSelectElement[];
                  for (const el of els) {
                    try {
                      el.value = String(val);
                      el.dispatchEvent(new Event('input', { bubbles: true }));
                      el.dispatchEvent(new Event('change', { bubbles: true }));
                    } catch {
                      // ignore
                    }
                  }
                },
                { sel: h, val: value },
              );
              return;
            }
          } else {
            await page.evaluate(
              ({ sel, val }) => {
                try {
                  const container = document.querySelector(sel) as HTMLElement | null;
                  if (!container) return;
                  const byText = Array.from(container.querySelectorAll('button, a, li')).find((el) => {
                    try {
                      return (el.textContent || '').trim().toLowerCase() === String(val).trim().toLowerCase();
                    } catch {
                      return false;
                    }
                  });
                  if (byText) {
                    (byText as HTMLElement).click();
                    return;
                  }
                  const selInside = container.querySelector('select') as HTMLSelectElement | null;
                  if (selInside) {
                    selInside.value = String(val);
                    selInside.dispatchEvent(new Event('input', { bubbles: true }));
                    selInside.dispatchEvent(new Event('change', { bubbles: true }));
                    return;
                  }
                } catch {
                  // ignore
                }
              },
              { sel: h, val: value },
            );
            return;
          }
        }
      } catch {
        // ignore
      }
    }

    await page.evaluate(
      ({ n, v }) => {
        try {
          const selectors = [
            `select[id*="${n}"]`,
            `select[name*="${n}"]`,
            `input[id*="${n}"]`,
            `input[name*="${n}"]`,
            `[data-dropdown*="${n}"]`,
            '[role="listbox"] select',
          ];
          for (const s of selectors) {
            const els = Array.from(document.querySelectorAll(s));
            if (els.length === 0) continue;
            for (const el of els) {
              try {
                if (el instanceof HTMLSelectElement) {
                  el.value = String(v);
                  el.dispatchEvent(new Event('input', { bubbles: true }));
                  el.dispatchEvent(new Event('change', { bubbles: true }));
                } else if (el instanceof HTMLInputElement) {
                  el.value = String(v);
                  el.dispatchEvent(new Event('input', { bubbles: true }));
                  el.dispatchEvent(new Event('change', { bubbles: true }));
                }
              } catch {
                // ignore
              }
            }
            if (els.length > 0) break;
          }
        } catch {
          // ignore
        }
      },
      { n: name, v: value },
    );
  }

  async setToggle(name: string, checked: boolean): Promise<void> {
    const page = this.getPage();
    const primarySelector = this.getToggleSelector(name);
    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    const candidates = [
      primarySelector,
      IRACING_SELECTORS.fields.toggle,
      IRACING_SELECTORS.fields.checkbox,
      'input[type="checkbox"]',
      '.switch-checkbox',
      '.toggle-switch input',
    ].filter(Boolean);

    const combined = candidates.join(', ');

    await page.waitForSelector(combined, { state: 'attached', timeout }).catch(() => {});

    if (!this.isRealMode()) {
      try {
        await page.evaluate(({ cands, should }) => {
          document
            .querySelectorAll<HTMLElement>('.wizard-step.hidden, .modal.hidden, .wizard-step[hidden]')
            .forEach((el) => {
              el.classList.remove('hidden');
              el.removeAttribute('hidden');
            });

          for (const sel of cands) {
            try {
              const els = Array.from(document.querySelectorAll(sel)) as HTMLElement[];
              if (els.length === 0) continue;
              for (const el of els) {
                try {
                  if ('checked' in el) {
                    (el as HTMLInputElement).checked = Boolean(should);
                    el.dispatchEvent(new Event('change', { bubbles: true }));
                  } else {
                    el.setAttribute('aria-checked', String(Boolean(should)));
                    el.dispatchEvent(new Event('change', { bubbles: true }));
                    try {
                      el.click();
                    } catch {
                      // ignore
                    }
                  }
                } catch {
                  // ignore
                }
              }
              if (els.length > 0) break;
            } catch {
              // ignore
            }
          }
        }, { cands: candidates, should: checked });
        return;
      } catch {
        // ignore
      }
    }

    for (const cand of candidates) {
      try {
        const locator = page.locator(cand).first();
        const count = await locator.count().catch(() => 0);
        if (count === 0) continue;

        const tagName = await locator
          .evaluate((el: Element) =>
            String(el.tagName || '').toLowerCase(),
          )
          .catch(() => '');
        const type = await locator.getAttribute('type').catch(() => '');

        if (tagName === 'input' && (type === 'checkbox' || type === 'radio')) {
          const isChecked = await locator.isChecked().catch(() => false);
          if (isChecked !== checked) {
            await this.safeClickService.safeClick(cand, { timeout });
          }
          return;
        }

        const ariaChecked = await locator.getAttribute('aria-checked').catch(() => '');
        if (ariaChecked !== '') {
          const desired = String(Boolean(checked));
          if (ariaChecked !== desired) {
            await this.safeClickService.safeClick(cand, { timeout });
          }
          return;
        }

        await this.safeClickService.safeClick(cand, { timeout });
        return;
      } catch {
        // try next
      }
    }

    this.log('warn', `Could not locate toggle for "${name}" to set to ${checked}`, { candidates });
  }

  async setSlider(name: string, value: number): Promise<void> {
    const page = this.getPage();
    const selector = this.getSliderSelector(name);
    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    const candidates = [
      selector,
      IRACING_SELECTORS.fields.slider,
      'input[id*="slider"]',
      'input[id*="track-state"]',
      'input[type="range"]',
      'input[type="text"]',
      '[data-slider]',
      'input[data-value]',
    ].filter(Boolean);

    if (!this.isRealMode()) {
      try {
        await page.evaluate(() => {
          document
            .querySelectorAll<HTMLElement>('.wizard-step.hidden, .modal.hidden, .wizard-step[hidden]')
            .forEach((el) => {
              el.classList.remove('hidden');
              el.removeAttribute('hidden');
            });
        });
      } catch {
        // ignore
      }

      for (const cand of candidates) {
        try {
          const applied = await page.evaluate(
            ({ sel, val }) => {
              try {
                const els = Array.from(document.querySelectorAll(sel)) as HTMLInputElement[];
                if (els.length === 0) return false;
                for (const el of els) {
                  try {
                    el.value = String(val);
                    el.setAttribute('data-value', String(val));
                    const inputEvent = new Event('input', { bubbles: true });
                    const changeEvent = new Event('change', { bubbles: true });
                    el.dispatchEvent(inputEvent);
                    el.dispatchEvent(changeEvent);
                  } catch {
                    // ignore
                  }
                }
                return true;
              } catch {
                return false;
              }
            },
            { sel: cand, val: value },
          );

          if (applied) return;
        } catch {
          // continue
        }
      }
    }

    const combined = candidates.join(', ');
    try {
      await page.waitForSelector(combined, { state: 'attached', timeout });
    } catch {
      await page.evaluate((val) => {
        const heuristics = [
          'input[id*="slider"]',
          'input[id*="track-state"]',
          '[data-slider]',
          'input[data-value]',
          'input[type="range"]',
          'input[type="text"]',
        ];
        for (const sel of heuristics) {
          try {
            const els = Array.from(document.querySelectorAll(sel)) as HTMLInputElement[];
            if (els.length === 0) continue;
            for (const el of els) {
              try {
                (el as HTMLInputElement).value = String(val);
                el.setAttribute('data-value', String(val));
                el.dispatchEvent(new Event('input', { bubbles: true }));
                el.dispatchEvent(new Event('change', { bubbles: true }));
              } catch {
                // ignore
              }
            }
            if (els.length > 0) break;
          } catch {
            // ignore
          }
        }
      }, value);
      return;
    }

    for (const cand of candidates) {
      try {
        const locator = page.locator(cand).first();
        const count = await locator.count().catch(() => 0);
        if (count === 0) continue;

        const tagName = await locator
          .evaluate((el: Element) =>
            String(el.tagName || '').toLowerCase(),
          )
          .catch(() => '');
        if (tagName === 'input') {
          const type = await locator.getAttribute('type').catch(() => '');
          if (type === 'range' || type === 'text' || type === 'number') {
            try {
              await locator.fill(String(value));
              return;
            } catch {
              await locator.evaluate((el, val) => {
                try {
                  (el as HTMLInputElement).value = String(val);
                  el.setAttribute('data-value', String(val));
                  el.dispatchEvent(new Event('input', { bubbles: true }));
                  el.dispatchEvent(new Event('change', { bubbles: true }));
                } catch {
                  // ignore
                }
              }, value);
              return;
            }
          }
        }

        try {
          await locator.fill(String(value));
          return;
        } catch {
          await locator.evaluate((el, val) => {
            try {
              (el as HTMLInputElement).value = String(val);
              el.setAttribute('data-value', String(val));
              el.dispatchEvent(new Event('input', { bubbles: true }));
              el.dispatchEvent(new Event('change', { bubbles: true }));
            } catch {
              // ignore
            }
          }, value);
          return;
        }
      } catch {
        // try next
      }
    }
  }

  async selectListItem(itemId: string): Promise<void> {
    const page = this.getPage();
    const selector = `[data-item="${itemId}"], button:has-text("${itemId}")`;
    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    if (!this.isRealMode()) {
      try {
        await page.evaluate(() => {
          document
            .querySelectorAll<HTMLElement>('.wizard-step.hidden, .modal.hidden, .wizard-step[hidden]')
            .forEach((el) => {
              el.classList.remove('hidden');
              el.removeAttribute('hidden');
            });
        });
      } catch {
        // ignore
      }
    }
    await page.waitForSelector(_selector, { state: 'attached', timeout });
    await this.safeClickService.safeClick(selector, { timeout });
  }

  async openModalTrigger(type: string): Promise<void> {
    const page = this.getPage();
    const escaped = type.replace(/"/g, '\\"');
    const _selector = `button:has-text("${escaped}"), a:has-text("${escaped}"), [aria-label*="${escaped}" i], [data-action="${escaped}"], [data-modal-trigger="${escaped}"]`;
    const timeout = this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout;

    if (!this.isRealMode()) {
      try {
        await page.evaluate(() => {
          document
            .querySelectorAll<HTMLElement>('.wizard-step.hidden, .modal.hidden, .wizard-step[hidden]')
            .forEach((el) => {
              el.classList.remove('hidden');
              el.removeAttribute('hidden');
            });
        });
      } catch {
        // ignore
      }
    }

    await page.waitForSelector(_selector, { state: 'attached', timeout });
    await this.safeClickService.safeClick(selector, { timeout });
  }

  async clickAddCarButton(): Promise<void> {
    const page = this.getPage();

    const addCarButtonSelector = this.isRealMode()
      ? IRACING_SELECTORS.steps.addCarButton
      : `${IRACING_SELECTORS.steps.addCarButton}, [data-action="add-car"]`;

    try {
      this.log('info', 'Clicking Add Car button to open modal');
      await page.waitForSelector(addCarButtonSelector, {
        state: 'attached',
        timeout: this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout,
      });
      await this.safeClickService.safeClick(addCarButtonSelector);
      this.log('info', 'Clicked Add Car button');
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Could not click Add Car button', { error: message });
      throw new Error(`Failed to click Add Car button: ${message}`);
    }
  }

  async waitForAddCarModal(): Promise<void> {
    const page = this.getPage();

    try {
      this.log('debug', 'Waiting for Add Car modal to appear (primary selector)');
      const modalSelector = IRACING_SELECTORS.steps.addCarModal;
      await page.waitForSelector(modalSelector, {
        state: 'attached',
        timeout: this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout,
      });
      await page.waitForTimeout(150);
      this.log('info', 'Add Car modal is visible', { _selector: modalSelector });
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('warn', 'Add Car modal not found with primary selector, dumping #create-race-wizard innerHTML and retrying', {
        error: message,
      });
      const html = await page.innerHTML('#create-race-wizard').catch(() => '');
      this.log('debug', 'create-race-wizard innerHTML (truncated)', { html: html ? html.slice(0, 2000) : '' });
      this.log('info', 'Retrying wait for Add Car modal with extended timeout');
      try {
        const modalSelectorRetry = IRACING_SELECTORS.steps.addCarModal;
        await page.waitForSelector(modalSelectorRetry, {
          state: 'attached',
          timeout: 10000,
        });
        await page.waitForTimeout(150);
        this.log('info', 'Add Car modal found after retry', { _selector: modalSelectorRetry });
      } catch {
        this.log('warn', 'Add Car modal still not found after retry');
      }
    }
  }

  async clickAddTrackButton(): Promise<void> {
    const page = this.getPage();

    const addTrackButtonSelector = IRACING_SELECTORS.steps.addTrackButton;

    try {
      this.log('info', 'Clicking Add Track button to open modal');
      await page.waitForSelector(addTrackButtonSelector, {
        state: 'attached',
        timeout: this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout,
      });
      await this.safeClickService.safeClick(addTrackButtonSelector);
      this.log('info', 'Clicked Add Track button');
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Could not click Add Track button', { error: message });
      throw new Error(`Failed to click Add Track button: ${message}`);
    }
  }

  async waitForAddTrackModal(): Promise<void> {
    const page = this.getPage();

    try {
      this.log('debug', 'Waiting for Add Track modal to appear');
      const modalSelector = IRACING_SELECTORS.steps.addTrackModal;
      await page.waitForSelector(modalSelector, {
        state: 'attached',
        timeout: this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout,
      });
      await page.waitForTimeout(150);
      this.log('info', 'Add Track modal is visible');
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('warn', 'Add Track modal did not appear', { error: message });
    }
  }

  async selectFirstSearchResult(): Promise<void> {
    const page = this.getPage();

    const directSelectors = [IRACING_SELECTORS.steps.trackSelectButton, IRACING_SELECTORS.steps.carSelectButton];

    for (const selector of directSelectors) {
      const button = page.locator(selector).first();
      if ((await button.count()) > 0 && (await button.isVisible())) {
        await this.safeClickService.safeClick(_selector, { timeout: IRACING_TIMEOUTS.elementWait });
        this.log('info', 'Clicked direct Select button for first search result', { selector });
        return;
      }
    }

    const dropdownSelector = IRACING_SELECTORS.steps.trackSelectDropdown;
    const dropdownButton = page.locator(dropdownSelector).first();

    if ((await dropdownButton.count()) > 0 && (await dropdownButton.isVisible())) {
      await this.safeClickService.safeClick(dropdownSelector, { timeout: IRACING_TIMEOUTS.elementWait });
      this.log('debug', 'Clicked dropdown toggle, waiting for menu', { _selector: dropdownSelector });

      await page.waitForSelector('.dropdown-menu.show', { timeout: 3000 }).catch(() => {});

      const itemSelector = IRACING_SELECTORS.steps.trackSelectDropdownItem;
      await page.waitForTimeout(200);
      await this.safeClickService.safeClick(itemSelector, { timeout: IRACING_TIMEOUTS.elementWait });
      this.log('info', 'Clicked first dropdown item to select track config', { _selector: itemSelector });
      return;
    }

    const carRowSelector =
      '.car-row, .car-item, [data-testid*="car"], [id*="favorite_cars"], [id*="select-car"]';
    const carRow = page.locator(carRowSelector).first();
    if ((await carRow.count()) > 0) {
      this.log('info', 'Fallback: clicking car row/item to select', { _selector: carRowSelector });
      try {
        await this.safeClickService.safeClick(carRowSelector, { timeout: IRACING_TIMEOUTS.elementWait });
        this.log('info', 'Clicked car row fallback selector');
        return;
      } catch (e) {
        this.log('debug', 'Car row fallback click failed, attempting to click first link inside row', {
          error: String(e),
        });
        const linkInside = page.locator(`${carRowSelector} a, ${carRowSelector} button`).first();
        if ((await linkInside.count()) > 0 && (await linkInside.isVisible())) {
          await this.safeClickService.safeClick(
            `${carRowSelector} a, ${carRowSelector} button`,
            { timeout: IRACING_TIMEOUTS.elementWait },
          );
          this.log('info', 'Clicked link/button inside car row fallback');
          return;
        }
      }
    }

    throw new Error('No Select button found in modal table and no fallback car row found');
  }

  async clickAdminModalConfirm(): Promise<void> {
    const page = this.getPage();

    const adminConfirmSelector =
      '#set-admins .modal .btn-primary, #set-admins .modal button:has-text("Add"), #set-admins .modal button:has-text("Select")';

    try {
      await page.waitForSelector(adminConfirmSelector, {
        state: 'attached',
        timeout: IRACING_TIMEOUTS.elementWait,
      });
      await this.safeClickService.safeClick(adminConfirmSelector, { timeout: IRACING_TIMEOUTS.elementWait });
      this.log('info', 'Clicked admin modal confirm button');
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('warn', 'Could not click admin modal confirm button', { error: message });
      throw new Error(`Failed to confirm admin selection: ${message}`);
    }
  }

  async clickNewRaceInModal(): Promise<void> {
    const page = this.getPage();
 
    const modalSelector = IRACING_SELECTORS.hostedRacing.createRaceModal;
    const newRaceSelector = IRACING_SELECTORS.hostedRacing.newRaceButton;
 
    try {
      this.log('info', 'Waiting for Create Race modal to appear');
 
      const isFixtureHost =
        this.isRealMode() &&
        this.config.baseUrl &&
        !this.config.baseUrl.includes('members.iracing.com');
 
      if (isFixtureHost) {
        try {
          await page.waitForSelector(modalSelector, {
            state: 'attached',
            timeout: 3000,
          });
        } catch {
          const fixture = getFixtureForStep(2);
          if (fixture) {
            const base = this.config.baseUrl.replace(/\/$/, '');
            const url = `${base}/${fixture}`;
            this.log('info', 'Fixture host detected, navigating directly to Step 2 fixture before New Race click', {
              url,
            });
            await page.goto(url, {
              waitUntil: 'domcontentloaded',
              timeout: IRACING_TIMEOUTS.navigation,
            });
          }
        }
      }
 
      await page.waitForSelector(modalSelector, {
        state: 'attached',
        timeout: IRACING_TIMEOUTS.elementWait,
      });
 
      this.log('info', 'Create Race modal attached, resolving New Race control', {
        modalSelector,
        newRaceSelector,
      });
 
      await page.waitForSelector(newRaceSelector, {
        state: 'attached',
        timeout: IRACING_TIMEOUTS.elementWait,
      });
      await this.safeClickService.safeClick(newRaceSelector, {
        timeout: IRACING_TIMEOUTS.elementWait,
      });
 
      this.log('info', 'Clicked New Race button, waiting for Race Information form to load');
 
      await page.waitForTimeout(500);
 
      if (isFixtureHost) {
        const raceInfoFixture = getFixtureForStep(3);
        if (raceInfoFixture) {
          const base = this.config.baseUrl.replace(/\/$/, '');
          const url = `${base}/${raceInfoFixture}`;
          this.log(
            'info',
            'Fixture host detected, navigating directly to Step 3 Race Information fixture after New Race click',
            { url },
          );
          await page.goto(url, {
            waitUntil: 'domcontentloaded',
            timeout: IRACING_TIMEOUTS.navigation,
          });
          const raceInfoSelector =
            IRACING_SELECTORS.wizard.stepContainers.raceInformation;
          await page.waitForSelector(raceInfoSelector, {
            state: 'attached',
            timeout: IRACING_TIMEOUTS.elementWait,
          });
        }
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Failed to click New Race in modal', { error: message });
      throw new Error(`Failed to click New Race button: ${message}`);
    }
  }

  async selectWeatherType(weatherType: string): Promise<void> {
    const page = this.browserSession.getPage();
    if (!page) {
      throw new Error('Browser not connected');
    }

    try {
      this.log('info', 'Selecting weather type via radio button', { weatherType });

      const weatherTypeLower = weatherType.toLowerCase();
      let labelSelector: string;

      if (weatherTypeLower.includes('static') || weatherType === '2') {
        labelSelector = 'label.chakra-radio:has-text("Static Weather")';
      } else if (weatherTypeLower.includes('forecast') || weatherType === '1') {
        labelSelector = 'label.chakra-radio:has-text("Forecasted weather")';
      } else if (weatherTypeLower.includes('timeline') || weatherTypeLower.includes('custom') || weatherType === '3') {
        labelSelector = 'label.chakra-radio:has-text("Timeline editor")';
      } else {
        labelSelector = 'label.chakra-radio:has-text("Static Weather")';
        this.log('warn', `Unknown weather type "${weatherType}", defaulting to Static Weather`);
      }

      const radioGroup = page.locator('[role="radiogroup"]').first();
      const exists = (await radioGroup.count()) > 0;

      if (!exists) {
        this.log('debug', 'Weather radio group not found, step may be optional');
        return;
      }

      const radioLabel = page.locator(labelSelector).first();
      const isVisible = await radioLabel.isVisible().catch(() => false);

      if (isVisible) {
        await radioLabel.click({ timeout: IRACING_TIMEOUTS.elementWait });
        this.log('info', 'Selected weather type', { weatherType, _selector: labelSelector });
      } else {
        this.log('debug', 'Weather type radio not visible, may already be selected or step is different');
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('warn', 'Could not select weather type (non-critical)', { error: message, weatherType });
    }
  }

  // ===== Private helper mappings =====

  private getFieldSelector(fieldName: string): string {
    const fieldMap: Record<string, string> = {
      sessionName: `${IRACING_SELECTORS.steps.sessionName}, ${IRACING_SELECTORS.steps.sessionNameAlt}`,
      password: `${IRACING_SELECTORS.steps.password}, ${IRACING_SELECTORS.steps.passwordAlt}`,
      description: `${IRACING_SELECTORS.steps.description}, ${IRACING_SELECTORS.steps.descriptionAlt}`,
      adminSearch: IRACING_SELECTORS.steps.adminSearch,
      carSearch: IRACING_SELECTORS.steps.carSearch,
      trackSearch: IRACING_SELECTORS.steps.trackSearch,
      maxDrivers: IRACING_SELECTORS.steps.maxDrivers,
    };
    return fieldMap[fieldName] || IRACING_SELECTORS.fields.textInput;
  }

  private getActionSelector(action: string): string {
    if (action.startsWith('[') || action.startsWith('button') || action.startsWith('#')) {
      return action;
    }

    const actionMap: Record<string, string> = {
      next: IRACING_SELECTORS.wizard.nextButton,
      back: IRACING_SELECTORS.wizard.backButton,
      confirm: IRACING_SELECTORS.wizard.confirmButton,
      cancel: IRACING_SELECTORS.wizard.cancelButton,
      create: IRACING_SELECTORS.hostedRacing.createRaceButton,
      close: IRACING_SELECTORS.wizard.closeButton,
    };

    return actionMap[action] || `button:has-text("${action}")`;
  }

  private getDropdownSelector(name: string): string {
    const dropdownMap: Record<string, string> = {
      region: IRACING_SELECTORS.steps.region,
      trackConfig: IRACING_SELECTORS.steps.trackConfig,
      weatherType: IRACING_SELECTORS.steps.weatherType,
      trackState: IRACING_SELECTORS.steps.trackState,
    };

    return dropdownMap[name] || IRACING_SELECTORS.fields.select;
  }

  private getToggleSelector(name: string): string {
    const toggleMap: Record<string, string> = {
      startNow: IRACING_SELECTORS.steps.startNow,
      rollingStart: IRACING_SELECTORS.steps.rollingStart,
    };

    return toggleMap[name] || IRACING_SELECTORS.fields.checkbox;
  }

  private getSliderSelector(name: string): string {
    const sliderMap: Record<string, string> = {
      practice: IRACING_SELECTORS.steps.practice,
      qualify: IRACING_SELECTORS.steps.qualify,
      race: IRACING_SELECTORS.steps.race,
      timeOfDay: IRACING_SELECTORS.steps.timeOfDay,
      temperature: IRACING_SELECTORS.steps.temperature,
    };

    return sliderMap[name] || IRACING_SELECTORS.fields.slider;
  }
}