package de.avatic.lcc.e2e.pages;

import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.WaitForSelectorState;
import de.avatic.lcc.e2e.testdata.DestinationInput;
import de.avatic.lcc.e2e.testdata.TestCaseInput;

import java.util.logging.Logger;

/**
 * Page Object for the calculation edit page.
 * Handles form filling for materials, packaging, pricing, and destinations.
 */
public class CalculationEditPage extends BasePage {

    private static final Logger logger = Logger.getLogger(CalculationEditPage.class.getName());

    // Screenshot settings
    private String screenshotPrefix = null;
    private int destinationCounter = 0;

    // Material section selectors (first master-data-item box)
    // Note: Use [1] after following-sibling::div to get only the first following sibling
    private static final String HS_CODE_INPUT = "//div[contains(@class, 'master-data-item')][1]//div[contains(@class, 'caption-column')][text()='HS code']/following-sibling::div[1]//input[@class='input-field']";
    private static final String TARIFF_RATE_INPUT = "//div[contains(@class, 'master-data-item')][1]//div[contains(@class, 'caption-column')][contains(., 'Tariff rate')]/following-sibling::div[1]//input[@class='input-field']";

    // Price section selectors (second master-data-item box)
    // Note: Labels are "MEK_A [EUR]", "Overseas share [%]" (spelling: OverSeas, not OverSea)
    private static final String PRICE_INPUT = "//div[contains(@class, 'master-data-item')][2]//div[contains(@class, 'caption-column')][contains(., 'MEK_A')]/following-sibling::div[1]//input[@class='input-field']";
    private static final String OVERSEA_SHARE_INPUT = "//div[contains(@class, 'master-data-item')][2]//div[contains(@class, 'caption-column')][contains(., 'Overseas share')]/following-sibling::div[1]//input[@class='input-field']";
    private static final String FCA_FEE_CHECKBOX = "//div[contains(@class, 'master-data-item')][2]//div[contains(@class, 'caption-column')][contains(., 'FCA')]/following-sibling::div[1]//label[contains(@class, 'checkbox-item')]";

    // Handling Unit section selectors (third master-data-item box)
    private static final String LENGTH_INPUT = "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='HU length']/following-sibling::div[1]//input[@class='input-field']";
    private static final String WIDTH_INPUT = "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='HU width']/following-sibling::div[1]//input[@class='input-field']";
    private static final String HEIGHT_INPUT = "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='HU height']/following-sibling::div[1]//input[@class='input-field']";
    private static final String WEIGHT_INPUT = "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='HU weight']/following-sibling::div[1]//input[@class='input-field']";
    private static final String PIECES_PER_UNIT_INPUT = "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='Pieces per HU']/following-sibling::div[1]//input[@class='input-field']";

    // Dropdown selectors
    private static final String DIMENSION_UNIT_DROPDOWN = "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='Dimension unit']/following-sibling::div[1]//button[contains(@class, 'dropdown-trigger')]";
    private static final String WEIGHT_UNIT_DROPDOWN = "//div[contains(@class, 'master-data-item')][3]//div[contains(@class, 'caption-column')][text()='Weight unit']/following-sibling::div[1]//button[contains(@class, 'dropdown-trigger')]";

    // Checkbox selectors
    private static final String MIXED_CHECKBOX = "//label[contains(@class, 'checkbox-item')][.//span[contains(@class, 'checkbox-label')][text()='Mixable']]";
    private static final String STACKED_CHECKBOX = "//label[contains(@class, 'checkbox-item')][.//span[contains(@class, 'checkbox-label')][text()='Stackable']]";

    // Destination selectors
    // Note: Use contains(., 'text') instead of contains(text(), 'text') when text is inside nested elements like tooltips
    private static final String DESTINATION_NAME_INPUT = "//input[@placeholder='Add new Destination ...']";
    private static final String DESTINATION_QUANTITY_INPUT = "//div[contains(@class, 'destination-edit-column-caption')][contains(., 'Annual quantity')]/following-sibling::div[1]//input[@class='input-field']";
    // Radio buttons are hidden and styled via label - click the label text instead
    private static final String ROUTING_RADIO = "//label[contains(@class, 'radio-item')]//span[contains(@class, 'radio-label')][contains(., 'standard routing')]";
    private static final String D2D_RADIO = "//label[contains(@class, 'radio-item')]//span[contains(@class, 'radio-label')][contains(., 'individual rate')]";
    // Note: D2D fields use "D2D Rate [EUR]" and "Lead time [days]" as labels in the UI
    private static final String D2D_COST_INPUT = "//div[contains(@class, 'destination-edit-column-caption')][contains(., 'D2D Rate')]/following-sibling::div[1]//input[@class='input-field']";
    private static final String D2D_DURATION_INPUT = "//div[contains(@class, 'destination-edit-column-caption')][contains(., 'Lead time')]/following-sibling::div[1]//input[@class='input-field']";
    private static final String HANDLING_TAB = "//button[contains(@class, 'tab-header')][contains(., 'Handling')]";
    private static final String CUSTOM_HANDLING_CHECKBOX = "//div[contains(@class, 'destination-edit-handling-cost')]//label[contains(@class, 'checkbox-item')]";
    private static final String HANDLING_COST_INPUT = "//div[contains(@class, 'destination-edit-column-caption')][contains(., 'Handling cost')]/following-sibling::div[1]//input[@class='input-field']";
    private static final String REPACKING_COST_INPUT = "//div[contains(@class, 'destination-edit-column-caption')][contains(., 'Repackaging cost')]/following-sibling::div[1]//input[@class='input-field']";
    private static final String DISPOSAL_COST_INPUT = "//div[contains(@class, 'destination-edit-column-caption')][contains(., 'Disposal cost')]/following-sibling::div[1]//input[@class='input-field']";

    // Buttons
    private static final String CALCULATE_AND_CLOSE_BUTTON = "//button[contains(., 'Calculate & close')]";
    private static final String CLOSE_BUTTON = "//button[contains(., 'Close') and not(contains(., 'Calculate'))]";

    public CalculationEditPage(Page page) {
        super(page);
    }

    /**
     * Enables screenshot mode with a test case prefix.
     * Screenshots will be saved at key points during form filling.
     */
    public void enableScreenshots(String testCaseId) {
        this.screenshotPrefix = testCaseId;
        this.destinationCounter = 0;
    }

    /**
     * Takes a screenshot if screenshot mode is enabled.
     */
    private void captureScreenshot(String suffix) {
        if (screenshotPrefix != null) {
            String filename = screenshotPrefix + "_" + suffix;
            java.nio.file.Path screenshotPath = java.nio.file.Paths.get("target/screenshots/" + filename + ".png");
            page.screenshot(new Page.ScreenshotOptions().setPath(screenshotPath).setFullPage(true));
            logger.info(() -> "Screenshot saved: " + screenshotPath);
        }
    }

    /**
     * Takes a screenshot of the current page state before calculation.
     */
    public void screenshotBeforeCalculate() {
        captureScreenshot("before_calculate");
    }

    /**
     * Fills the main calculation form with input data.
     */
    public void fillForm(TestCaseInput input) {
        logger.info("Filling calculation form");

        // Material section (if HS code input exists)
        fillInputByXPath(HS_CODE_INPUT, String.valueOf(input.hsCode()), true);
        fillInputByXPath(TARIFF_RATE_INPUT, String.valueOf(input.tariffRate()), true);

        // Price section
        fillInputByXPath(PRICE_INPUT, String.valueOf(input.price()), false);
        fillInputByXPath(OVERSEA_SHARE_INPUT, String.valueOf(input.overseaShare()), false);
        setCheckboxByXPath(FCA_FEE_CHECKBOX, input.fcaFee());

        // Handling Unit section
        fillInputByXPath(LENGTH_INPUT, String.valueOf(input.length()), false);
        fillInputByXPath(WIDTH_INPUT, String.valueOf(input.width()), false);
        fillInputByXPath(HEIGHT_INPUT, String.valueOf(input.height()), false);
        fillInputByXPath(WEIGHT_INPUT, String.valueOf(input.weight()), false);
        fillInputByXPath(PIECES_PER_UNIT_INPUT, String.valueOf(input.piecesPerUnit()), false);

        // Dropdowns
        selectDropdownByXPath(DIMENSION_UNIT_DROPDOWN, input.dimensionUnit());
        selectDropdownByXPath(WEIGHT_UNIT_DROPDOWN, input.weightUnit());

        // Checkboxes
        setCheckboxByXPath(STACKED_CHECKBOX, input.stacked());
        setCheckboxByXPath(MIXED_CHECKBOX, input.mixed());

        logger.info("Calculation form filled successfully");
    }

    /**
     * Adds a new destination by name.
     */
    public void addDestination(DestinationInput destination) {
        searchAndSelectAutosuggestByXPath(DESTINATION_NAME_INPUT, destination.name());
        page.waitForTimeout(500);
        logger.info(() -> "Added destination: " + destination.name());
    }

    /**
     * Fills destination-specific fields.
     */
    public void fillDestination(DestinationInput destination) {
        destinationCounter++;
        String destNum = String.valueOf(destinationCounter);

        // First, ensure no modal is currently open
        try {
            Locator existingModal = page.locator(".modal-overlay");
            if (existingModal.count() > 0 && existingModal.isVisible()) {
                logger.info("Closing existing modal before opening destination edit");
                // Press Escape to close any open modal
                page.keyboard().press("Escape");
                page.waitForTimeout(500);
            }
        } catch (Exception e) {
            // No modal open, continue
        }

        // Click on the destination item's edit button to open the modal
        // The destination item shows the name, so we find it and click the pencil icon
        String destinationName = destination.name();
        Locator destinationRow = page.locator(".destination-item-row:has-text('" + destinationName + "')");

        if (destinationRow.count() > 0) {
            logger.info(() -> "Found destination row for: " + destinationName);
            Locator editButton = destinationRow.locator("button:has([class*='pencil'])");
            if (editButton.count() == 0) {
                // Try alternative selector for icon button
                editButton = destinationRow.locator(".destination-item-action button").first();
            }
            if (editButton.count() > 0) {
                logger.info("Clicking edit button to open destination modal");
                editButton.click();
                page.waitForTimeout(1000); // Wait for modal to open
            }
        }

        // Wait for destination edit modal to be visible
        Locator quantityInput = page.locator("xpath=" + DESTINATION_QUANTITY_INPUT);
        quantityInput.waitFor(new Locator.WaitForOptions()
            .setState(WaitForSelectorState.VISIBLE)
            .setTimeout(10000));

        // Wait extra time for Vue component to fully initialize
        // This is critical for subsequent destinations
        page.waitForTimeout(1000);

        // Fill quantity
        fillInputByXPath(DESTINATION_QUANTITY_INPUT, String.valueOf(destination.quantity()), false);

        // Select transport mode
        if (destination.d2d()) {
            page.locator("xpath=" + D2D_RADIO).click();
            page.waitForTimeout(300);

            // Fill D2D specific fields if individual rate (custom cost/duration)
            if (destination.d2dCost() != null) {
                fillInputByXPath(D2D_COST_INPUT, String.valueOf(destination.d2dCost()), true);
            }
            if (destination.d2dDuration() != null) {
                fillInputByXPath(D2D_DURATION_INPUT, String.valueOf(destination.d2dDuration()), true);
            }

            // Note: D2D mode does NOT show route selection UI - routes are determined by the D2D provider
            // If using standard routing (no cost specified), the system uses database D2D rates
            if (destination.d2dCost() == null) {
                logger.info("D2D with standard routing - D2D rates will be loaded from database");
            }
        } else {
            page.locator("xpath=" + ROUTING_RADIO).click();
            page.waitForTimeout(300);

            // Select route - if not specified, select first available route
            selectRoute(destination.route());
        }

        // Take screenshot of Routes tab (with route selection or D2D fields)
        captureScreenshot("dest" + destNum + "_routes_tab");

        // Handle custom handling costs
        if (destination.customHandling()) {
            // Click handling tab
            try {
                Locator handlingTab = page.locator("xpath=" + HANDLING_TAB);
                if (handlingTab.isVisible()) {
                    handlingTab.click();
                    page.waitForTimeout(300);
                }
            } catch (Exception e) {
                // Tab might not exist or already selected
            }

            setCheckboxByXPath(CUSTOM_HANDLING_CHECKBOX, true);
            page.waitForTimeout(300);

            if (destination.handlingCost() != null) {
                fillInputByXPath(HANDLING_COST_INPUT, String.valueOf(destination.handlingCost()), true);
            }
            if (destination.repackingCost() != null) {
                fillInputByXPath(REPACKING_COST_INPUT, String.valueOf(destination.repackingCost()), true);
            }
            if (destination.disposalCost() != null) {
                fillInputByXPath(DISPOSAL_COST_INPUT, String.valueOf(destination.disposalCost()), true);
            }

            // Take screenshot of Handling tab
            captureScreenshot("dest" + destNum + "_handling_tab");
        } else {
            // For destinations without custom handling, also take a screenshot of the handling tab for verification
            try {
                Locator handlingTab = page.locator("xpath=" + HANDLING_TAB);
                if (handlingTab.isVisible()) {
                    handlingTab.click();
                    page.waitForTimeout(300);
                    captureScreenshot("dest" + destNum + "_handling_tab");
                    // Go back to routes tab
                    Locator routesTab = page.locator("//button[contains(@class, 'tab-header')][contains(., 'Routes')]");
                    if (routesTab.count() > 0 && routesTab.isVisible()) {
                        routesTab.click();
                        page.waitForTimeout(200);
                    }
                }
            } catch (Exception e) {
                // Tab might not exist
            }
        }

        // Close the destination edit modal by clicking OK
        Locator okButton = page.locator("button:has-text('OK')");
        okButton.click();
        page.waitForTimeout(500);

        // Wait for modal and overlay to fully close
        try {
            page.locator(".destination-edit-modal-container").waitFor(
                new Locator.WaitForOptions()
                    .setState(WaitForSelectorState.HIDDEN)
                    .setTimeout(5000));
        } catch (Exception e) {
            logger.warning("Destination edit modal might not have closed: " + e.getMessage());
        }

        // Also wait for any modal overlay to disappear
        try {
            page.locator(".modal-overlay").waitFor(
                new Locator.WaitForOptions()
                    .setState(WaitForSelectorState.HIDDEN)
                    .setTimeout(3000));
        } catch (Exception e) {
            // Overlay might not exist or already hidden
        }

        // Extra wait to ensure DOM is stable
        page.waitForTimeout(500);

        logger.info(() -> "Filled destination: " + destination.name());
    }

    /**
     * Selects a route from the available routes.
     * Routes are displayed as clickable elements in the destination edit modal.
     * Each route shows external_mapping_id values like "HH", "WH HH", etc.
     *
     * The Vue component (DestinationEditRoutes) uses a Pinia store for route selection.
     * When a route is clicked, selectRoute(id) sets route.is_selected = true.
     *
     * IMPORTANT: Standard DOM clicks don't reliably trigger Vue's event system.
     * We need to find the Vue component and call its methods directly.
     */
    private void selectRoute(String route) {
        // Wait for routes to fully load
        page.waitForTimeout(500);

        // Wait for routes to be visible
        try {
            page.locator(".destination-route-container").first().waitFor(
                new Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE).setTimeout(5000));
        } catch (Exception e) {
            logger.info("No routes visible yet, continuing anyway");
        }

        // Check for "no routes available" warning
        Locator routeWarning = page.locator(".destination-edit-route-warning");
        if (routeWarning.count() > 0 && routeWarning.isVisible()) {
            String warningText = routeWarning.textContent();
            logger.warning(() -> "Route warning displayed: " + warningText);
            logger.info("No routes available - route selection skipped.");
            return;
        }

        // Get routes from DOM and find the Vue component
        Locator allRoutes = page.locator(".destination-route-container");
        int routeCount = allRoutes.count();
        logger.info(() -> "Found " + routeCount + " routes in DOM");

        if (routeCount == 0) {
            logger.warning("No routes found");
            return;
        }

        // Log available routes
        for (int i = 0; i < routeCount; i++) {
            final int idx = i;
            String routeText = allRoutes.nth(i).textContent();
            logger.info(() -> "  Route " + idx + ": " + routeText.trim());
        }

        // Find best matching route index
        int routeIndexToSelect = findBestMatchingRouteIndexFromDom(allRoutes, route);
        logger.info(() -> "Will select route at index " + routeIndexToSelect);

        // Try to find and call the Vue component's selectRoute method
        // The component is mounted on the modal's routes container
        Object result = page.evaluate("(routeIndex) => { " +
            "try { " +
            // Find the route element
            "  const routeElements = document.querySelectorAll('.destination-route-container'); " +
            "  if (!routeElements || routeElements.length === 0) return 'no_routes_in_dom'; " +
            "  if (routeIndex >= routeElements.length) return 'index_out_of_bounds'; " +
            // Find the Vue component that handles routes - it's the parent of the routes container
            "  const routesCell = document.querySelector('.destination-edit-cell-routes'); " +
            "  if (!routesCell) return 'no_routes_cell'; " +
            // Walk up to find the component with selectRoute method
            "  let vueComponent = null; " +
            "  let el = routesCell; " +
            "  for (let i = 0; i < 10 && el; i++) { " +
            "    if (el.__vueParentComponent) { " +
            "      let comp = el.__vueParentComponent; " +
            "      while (comp) { " +
            "        if (comp.ctx && typeof comp.ctx.selectRoute === 'function') { " +
            "          vueComponent = comp; " +
            "          break; " +
            "        } " +
            "        comp = comp.parent; " +
            "      } " +
            "      if (vueComponent) break; " +
            "    } " +
            "    el = el.parentElement; " +
            "  } " +
            "  if (!vueComponent) { " +
            // Alternative: try to access pinia via window or through component
            "    const routeEl = routeElements[routeIndex]; " +
            "    let compEl = routeEl; " +
            "    for (let i = 0; i < 5 && compEl; i++) { " +
            "      if (compEl.__vueParentComponent?.ctx?.destination?.routes) { " +
            "        const routes = compEl.__vueParentComponent.ctx.destination.routes; " +
            "        if (Array.isArray(routes) && routes.length > routeIndex) { " +
            "          routes.forEach((r, idx) => { r.is_selected = (idx === routeIndex); }); " +
            "          return 'set_via_ctx_destination'; " +
            "        } " +
            "      } " +
            "      compEl = compEl.parentElement; " +
            "    } " +
            "    return 'no_vue_component'; " +
            "  } " +
            // Get the route id from the component's destination.routes
            "  const routes = vueComponent.ctx.destination?.routes; " +
            "  if (!routes || routes.length === 0) return 'no_routes_in_ctx'; " +
            "  if (routeIndex >= routes.length) return 'route_index_exceeds_ctx'; " +
            "  const routeId = routes[routeIndex].id; " +
            // Call the selectRoute method
            "  vueComponent.ctx.selectRoute(routeId); " +
            "  return 'called_selectRoute:' + routeId; " +
            "} catch (e) { return 'error:' + e.message; } " +
            "}", routeIndexToSelect);

        final Object vueResult = result;
        logger.info(() -> "Vue component route selection result: " + vueResult);

        // Always try click simulation as the primary method - it's most reliable
        logger.info("Using click simulation to select route");
        Locator routeToClick = allRoutes.nth(routeIndexToSelect);
        simulateRobustClick(routeToClick);

        // Wait for UI update
        page.waitForTimeout(500);

        // Verify selection worked
        boolean selected = verifyRouteSelectionVisual(allRoutes.nth(routeIndexToSelect));

        // If click didn't work, try Pinia as fallback
        if (!selected) {
            logger.info("Click simulation didn't select route, trying Pinia direct access");
            Object piniaResult = tryPiniaDirectAccess(routeIndexToSelect);
            final Object piniaResultFinal = piniaResult;
            logger.info(() -> "Pinia direct access result: " + piniaResultFinal);
            page.waitForTimeout(300);
            selected = verifyRouteSelectionVisual(allRoutes.nth(routeIndexToSelect));
        }

        if (!selected) {
            logger.warning(() -> "Route selection may have failed for index " + routeIndexToSelect);
        }
    }

    /**
     * Try direct Pinia store access through various paths.
     */
    private Object tryPiniaDirectAccess(int routeIndex) {
        return page.evaluate("(routeIndex) => { " +
            "try { " +
            // Try different ways to find Pinia
            "  let pinia = null; " +
            // Method 1: Through app provides
            "  const app = document.querySelector('#app')?.__vue_app__; " +
            "  if (app?._context?.provides?.pinia) { " +
            "    pinia = app._context.provides.pinia; " +
            "  } " +
            // Method 2: Through window (if exposed)
            "  if (!pinia && window.__pinia) { " +
            "    pinia = window.__pinia; " +
            "  } " +
            // Method 3: Walk through app's config
            "  if (!pinia && app?.config?.globalProperties?.$pinia) { " +
            "    pinia = app.config.globalProperties.$pinia; " +
            "  } " +
            "  if (!pinia) return 'pinia_not_found'; " +
            // Access the store
            "  const storeState = pinia.state?.value?.['destinationSingleEdit']; " +
            "  if (!storeState?.destination?.routes) return 'store_not_found'; " +
            "  const routes = storeState.destination.routes; " +
            "  if (routeIndex >= routes.length) return 'index_out_of_range'; " +
            // Set selection
            "  routes.forEach((r, idx) => { r.is_selected = (idx === routeIndex); }); " +
            "  return 'pinia_success'; " +
            "} catch (e) { return 'pinia_error:' + e.message; } " +
            "}", routeIndex);
    }

    /**
     * Simulate a robust click that Vue should recognize.
     */
    private void simulateRobustClick(Locator element) {
        try {
            // First, scroll into view
            element.scrollIntoViewIfNeeded();
            page.waitForTimeout(100);

            // Try to trigger via native Playwright click
            element.click(new Locator.ClickOptions().setForce(true));
            page.waitForTimeout(100);

            // Also dispatch events manually
            element.evaluate("el => { " +
                "const mousedown = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window }); " +
                "const mouseup = new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window }); " +
                "const click = new MouseEvent('click', { bubbles: true, cancelable: true, view: window }); " +
                "el.dispatchEvent(mousedown); " +
                "el.dispatchEvent(mouseup); " +
                "el.dispatchEvent(click); " +
                "}");

            logger.info("Simulated robust click on route element");
        } catch (Exception e) {
            logger.warning(() -> "Robust click simulation failed: " + e.getMessage());
        }
    }

    /**
     * Verify route selection is visible in the DOM.
     * @return true if the route appears selected, false otherwise
     */
    private boolean verifyRouteSelectionVisual(Locator routeElement) {
        try {
            Locator innerContainer = routeElement.locator(".destination-route-inner-container");
            if (innerContainer.count() > 0) {
                String classes = innerContainer.getAttribute("class");
                boolean selected = classes != null && classes.contains("selected");
                logger.info(() -> "Route visual verification - classes: " + classes + ", selected: " + selected);
                return selected;
            }
        } catch (Exception e) {
            logger.warning(() -> "Could not verify route selection: " + e.getMessage());
        }
        return false;
    }

    /**
     * Find exact matching route from DOM elements.
     * The route must contain all spec segments in order, and the route text
     * (when normalized) should match the concatenated spec segments.
     *
     * @throws IllegalStateException if no exact match is found
     */
    private int findBestMatchingRouteIndexFromDom(Locator allRoutes, String routeSpec) {
        int routeCount = allRoutes.count();
        if (routeSpec == null || routeSpec.isEmpty()) {
            return 0; // No route specified, use first available
        }
        if (routeCount == 0) {
            throw new IllegalStateException("No routes available, but route spec was: " + routeSpec);
        }

        String[] specSegments = routeSpec.split(",");

        // Build expected route text by concatenating segments (routes display without separators)
        StringBuilder expectedBuilder = new StringBuilder();
        for (String segment : specSegments) {
            expectedBuilder.append(segment.trim().toLowerCase().replace("_", " "));
        }
        String expectedRouteText = expectedBuilder.toString();

        // Find exact match
        for (int i = 0; i < routeCount; i++) {
            String routeText = allRoutes.nth(i).textContent().toLowerCase().trim();
            // Remove common whitespace/separator variations
            String normalizedRouteText = routeText.replaceAll("\\s+", "").replace(">", "");
            String normalizedExpected = expectedRouteText.replaceAll("\\s+", "");

            if (normalizedRouteText.equals(normalizedExpected)) {
                final int matchedIndex = i;
                final String matchedRoute = routeText;
                logger.info(() -> "Exact route match found at index " + matchedIndex + ": " + matchedRoute);
                return i;
            }
        }

        // No exact match found - log available routes and fail
        StringBuilder availableRoutes = new StringBuilder("Available routes:\n");
        for (int i = 0; i < routeCount; i++) {
            availableRoutes.append("  ").append(i).append(": ").append(allRoutes.nth(i).textContent().trim()).append("\n");
        }

        throw new IllegalStateException(
            "No exact route match found for spec: '" + routeSpec + "' (expected: '" + expectedRouteText + "')\n" +
            availableRoutes.toString()
        );
    }


    /**
     * Clicks the "Calculate & close" button.
     */
    public void calculateAndClose() {
        page.locator("xpath=" + CALCULATE_AND_CLOSE_BUTTON).click();
        page.waitForTimeout(2000);
        logger.info("Clicked Calculate & close");
    }

    /**
     * Clicks the "Close" button.
     */
    public void close() {
        page.locator("xpath=" + CLOSE_BUTTON).click();
        logger.info("Clicked Close");
    }

    // Helper methods for XPath-based operations

    private void fillInputByXPath(String xpath, String value, boolean optional) {
        try {
            Locator locator = page.locator("xpath=" + xpath);
            if (optional) {
                locator.waitFor(new Locator.WaitForOptions()
                    .setState(WaitForSelectorState.VISIBLE)
                    .setTimeout(2000));
            } else {
                locator.waitFor(new Locator.WaitForOptions()
                    .setState(WaitForSelectorState.VISIBLE));
            }
            locator.clear();
            locator.fill(value);
            logger.fine(() -> "Filled XPath input: " + xpath + " with value: " + value);
        } catch (Exception e) {
            if (!optional) {
                throw e;
            }
            logger.warning(() -> "Optional field not found: " + xpath);
        }
    }

    private void setCheckboxByXPath(String xpath, boolean checked) {
        try {
            Locator label = page.locator("xpath=" + xpath);
            label.waitFor(new Locator.WaitForOptions()
                .setState(WaitForSelectorState.VISIBLE)
                .setTimeout(2000));

            Locator checkbox = label.locator("input[type='checkbox']");
            boolean isChecked = checkbox.isChecked();

            if (isChecked != checked) {
                label.click();
                page.waitForTimeout(300);
            }
        } catch (Exception e) {
            logger.warning(() -> "Could not set checkbox: " + xpath);
        }
    }

    private void selectDropdownByXPath(String xpath, String optionText) {
        try {
            Locator dropdown = page.locator("xpath=" + xpath);
            dropdown.waitFor(new Locator.WaitForOptions()
                .setState(WaitForSelectorState.VISIBLE)
                .setTimeout(2000));

            // Check current value
            try {
                String currentValue = dropdown.locator("span.dropdown-trigger-text").textContent();
                if (optionText.equals(currentValue)) {
                    return;
                }
            } catch (Exception ignored) {
            }

            dropdown.click();

            Locator menu = page.locator("ul.dropdown-menu");
            menu.waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE));

            String optionXPath = String.format(
                "//li[contains(@class, 'dropdown-option')][normalize-space(text())='%s']",
                optionText
            );
            page.locator("xpath=" + optionXPath).click();
            page.waitForTimeout(200);
        } catch (Exception e) {
            logger.warning(() -> "Could not select dropdown option: " + optionText);
        }
    }

    private void searchAndSelectAutosuggestByXPath(String xpath, String searchText) {
        Locator input = page.locator("xpath=" + xpath);
        input.waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE));
        input.clear();
        input.fill(searchText);
        page.waitForTimeout(1000);

        Locator suggestion = page.locator(".suggestion-item").first();
        suggestion.waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE));
        suggestion.click();
        page.waitForTimeout(500);
    }
}
