import { Page, expect } from '@playwright/test';
import { BasePage } from './BasePage';

/**
 * Map Page Object
 * Handles all interactions on the /map route
 */
export class MapPage extends BasePage {
  // Map container
  private readonly mapContainer = () => this.page.locator('[data-testid="map-container"]').first();
  private readonly mapElement = () => this.page.locator('.leaflet-map').first();

  // Markers
  private readonly questMarkers = () => this.page.locator('[data-testid="quest-marker"]');
  private readonly locationMarkers = () => this.page.locator('.location-marker');
  private readonly sellerMarkers = () => this.page.locator('[data-testid="seller-marker"]');

  // Popups
  private readonly markerPopup = () => this.page.locator('[data-testid="marker-popup"]').first();
  private readonly popupTitle = () => this.markerPopup().locator('h4').first();
  private readonly popupDescription = () => this.markerPopup().locator('p').first();
  private readonly popupLocation = () => this.markerPopup().locator('[data-testid="popup-location"]').first();
  private readonly popupActionButton = () =>
    this.page.locator('[data-testid="popup-action-button"]').first();

  // Location info
  private readonly locationInfo = () => this.page.locator('[data-testid="location-info"]').first();

  async goto(): Promise<void> {
    await super.goto('/map');
  }

  /**
   * Check if map is loaded
   * Uses active polling to detect map readiness
   * Fallback checks for loading state clearing
   */
  async isLoaded(): Promise<boolean> {
    try {
      // First check if loading indicator is gone
      const isStillLoading = await this.page
        .getByText(/charting the map/i)
        .isVisible({ timeout: 1000 })
        .catch(() => false);

      if (isStillLoading) {
        // Page is still loading, wait for loading to finish
        await this.pollUntil(
          async () => {
            const loading = await this.page
              .getByText(/charting the map/i)
              .isVisible()
              .catch(() => false);
            return !loading; // Return true when loading finishes
          },
          20000, // 20 second timeout for loading
          1000, // Poll every 1s
        );
      }

      // Now poll for map container to be visible
      await this.pollForElementVisible(
        this.mapContainer(),
        15000, // 15 second timeout
        500, // Poll every 500ms
      );
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Get count of quest markers
   * Polls for markers to be rendered
   */
  async getQuestMarkerCount(): Promise<number> {
    try {
      // Wait for at least one quest marker to appear (polls until count > 0)
      await this.pollUntil(
        async () => {
          const count = await this.questMarkers().count();
          return count > 0;
        },
        15000, // 15s — markers render after map tiles load
        500,
      );
    } catch {
      // Timeout: markers may genuinely be 0, fall through and return actual count
    }
    return this.questMarkers().count();
  }

  /**
   * Get count of location markers
   * Polls for markers to be rendered
   */
  async getLocationMarkerCount(): Promise<number> {
    try {
      await this.pollUntil(
        async () => {
          const count = await this.locationMarkers().count();
          return count >= 0; // Always true, but waits a bit for markers to render
        },
        5000,
        500,
      );
    } catch {
      // Timeout, continue
    }
    return this.locationMarkers().count();
  }

  /**
   * Get count of seller markers
   * Polls for markers to be rendered
   */
  async getSellerMarkerCount(): Promise<number> {
    try {
      await this.pollUntil(
        async () => {
          const count = await this.sellerMarkers().count();
          return count >= 0; // Always true, but waits a bit for markers to render
        },
        5000,
        500,
      );
    } catch {
      // Timeout, continue
    }
    return this.sellerMarkers().count();
  }

  /**
   * Click on a quest marker by index
   */
  async clickQuestMarker(index: number = 0): Promise<void> {
    const markers = this.questMarkers();
    await markers.nth(index).click();
    // Wait for popup or quest-details-card to appear (either indicates a successful click)
    await Promise.race([
      this.markerPopup().waitFor({ state: 'visible', timeout: 5000 }).catch(() => {}),
      this.page.locator('.quest-details-card').waitFor({ state: 'visible', timeout: 5000 }).catch(() => {}),
    ]);
  }

  /**
   * Click on a location marker by index
   */
  async clickLocationMarker(index: number = 0): Promise<void> {
    const markers = this.locationMarkers();
    await markers.nth(index).click();
    await this.markerPopup().waitFor({ state: 'visible', timeout: 5000 });
  }

  /**
   * Click on a seller marker by index
   * Sellers are character markers (Frodo, Sam, Gandalf) who sell items
   */
  async clickSellerMarker(index: number = 0): Promise<void> {
    try {
      // Poll for seller markers to appear
      await this.pollUntil(
        async () => {
          const count = await this.sellerMarkers().count();
          return count > index; // Wait for at least index+1 markers
        },
        10000,
        500,
      );
    } catch {
      // Markers might not load, continue anyway
    }

    const markers = this.sellerMarkers();
    
    // Click the marker
    try {
      await markers.nth(index).click({ timeout: 5000 });
      // Give the map time to process the click and Leaflet to open the popup
      await this.page.waitForTimeout(1000);
    } catch (err) {
      // Marker might not be clickable directly, try with JS
      await this.page.evaluate((idx: number) => {
        const selector = '[data-testid="seller-marker"]';
        const elements = document.querySelectorAll(selector);
        if (elements[idx]) {
          (elements[idx] as HTMLElement).click();
        }
      }, index);
      // Give the map time to process the click and Leaflet to open the popup
      await this.page.waitForTimeout(1000);
    }

    // Wait for popup to appear (may not appear if user is not logged in and navigation occurs)
    try {
      await this.pollForElementVisible(
        this.markerPopup(),
        3000,
        200,
      );
    } catch {
      // Popup may not appear if clicking triggers navigation (e.g. to /login)
      // This is acceptable — the step verification handles the outcome
    }
  }

  /**
   * Check if marker popup is visible
   */
  async isPopupVisible(): Promise<boolean> {
    return this.markerPopup().isVisible({ timeout: 3000 }).catch(() => false);
  }

  /**
   * Get popup title
   */
  async getPopupTitle(): Promise<string> {
    return this.popupTitle().textContent().then(t => t || '');
  }

  /**
   * Get popup description
   */
  async getPopupDescription(): Promise<string> {
    return this.popupDescription().textContent().then(t => t || '');
  }

  /**
   * Get popup location
   */
  async getPopupLocation(): Promise<string> {
    return this.popupLocation().textContent().then(t => t || '');
  }

  /**
   * Click popup action button
   * For sellers, this opens the bargaining interface
   */
  async clickPopupActionButton(): Promise<void> {
    try {
      // Poll for button to be visible
      await this.pollForElementVisible(
        this.popupActionButton(),
        10000,
        500,
      );
    } catch {
      // Button might not be visible, try clicking anyway
    }

    try {
      await this.popupActionButton().click();
    } catch (err) {
      // Try with JavaScript if regular click fails
      await this.page.evaluate(() => {
        const btn = document.querySelector('[data-testid="popup-action-button"]') as HTMLElement;
        if (btn) {
          btn.click();
        }
      });
    }

    // Wait a moment for the bargaining interface to open
    await this.page.waitForTimeout(1000);
  }

  /**
   * Verify popup contains quest information
   */
  async expectPopupHasQuestInfo(questTitle: string): Promise<void> {
    await expect(this.popupTitle()).toContainText(questTitle);
  }

  /**
   * Close popup by clicking elsewhere
   */
  async closePopup(): Promise<void> {
    await this.mapElement().click({ position: { x: 10, y: 10 } });
  }
}
