//
//  WaitHelpers.swift
//  Tests iOS
//
//  Centralized, explicit wait helpers. No sleep() allowed.
//

import XCTest

enum UITestID {
    enum Tab {
        static let day = "tab_day"
        static let month = "tab_month"
        static let year = "tab_year"
        static let insights = "tab_insights"
        static let settings = "tab_settings"
    }

    enum Day {
        static let moodHeader = "mood_header"
        static let entryRowPrefix = "entry_row_"
        static let sectionPrefix = "day_section_"
        static let emptyStateNoData = "empty_state_no_data"
    }

    enum Settings {
        static let header = "settings_header"
        static let customizeTab = "settings_tab_customize"
        static let settingsTab = "settings_tab_settings"
        static let upgradeBanner = "upgrade_banner"
        static let subscribeButton = "subscribe_button"
        static let whyUpgradeButton = "why_upgrade_button"
        static let browseThemesButton = "browse_themes_button"
        static let clearDataButton = "settings_clear_data"
        static let analyticsToggle = "settings_analytics_toggle"
        static let bypassSubscriptionToggle = "settings_bypass_subscription"
        static let eulaButton = "settings_eula"
        static let privacyPolicyButton = "settings_privacy_policy"
    }

    enum Customize {
        static func themeButton(_ name: String) -> String { "customize_theme_\(name.lowercased())" }
        static func votingLayoutButton(_ name: String) -> String { "customize_voting_\(name.lowercased())" }
        static func dayStyleButton(_ name: String) -> String { "customize_daystyle_\(name.lowercased())" }
        static func iconPackButton(_ name: String) -> String { "customize_iconpack_\(name.lowercased())" }
        static func personalityPackButton(_ name: String) -> String { "customize_personality_\(name.lowercased())" }
        static func appThemeCard(_ name: String) -> String { "apptheme_card_\(name.lowercased())" }
        static let pickerDoneButton = "apptheme_picker_done"
        static let previewCancelButton = "apptheme_preview_cancel"
        static let previewApplyButton = "apptheme_preview_apply"
    }

    enum EntryDetail {
        static let sheet = "entry_detail_sheet"
        static let doneButton = "entry_detail_done"
        static let deleteButton = "entry_detail_delete"
        static let noteButton = "entry_detail_note_button"
        static let noteArea = "entry_detail_note_area"
    }

    enum NoteEditor {
        static let text = "note_editor_text"
        static let save = "note_editor_save"
        static let cancel = "note_editor_cancel"
    }

    enum Onboarding {
        static let welcome = "onboarding_welcome"
        static let time = "onboarding_time"
        static let day = "onboarding_day"
        static let dayToday = "onboarding_day_today"
        static let dayYesterday = "onboarding_day_yesterday"
        static let style = "onboarding_style"
        static let subscription = "onboarding_subscription"
        static let subscribe = "onboarding_subscribe_button"
        static let skip = "onboarding_skip_button"
    }

    enum Paywall {
        static let monthOverlay = "paywall_month_overlay"
        static let yearOverlay = "paywall_year_overlay"
        static let insightsOverlay = "paywall_insights_overlay"
    }

    enum Insights {
        static let header = "insights_header"
        static let monthSection = "insights_month_section"
        static let yearSection = "insights_year_section"
        static let allTimeSection = "insights_all_time_section"
    }

    enum Year {
        static let heatmap = "year_heatmap"
        static let donutChart = "year_donut_chart"
        static let barChart = "year_bar_chart"
        static let statsSection = "year_stats_section"
        static func cardHeader(year: Int) -> String { "year_card_header_\(year)" }
        static let shareButton = "year_share_button"
    }

    enum Month {
        static let grid = "month_grid"
        static let shareButton = "month_share_button"
    }
}

extension XCUIElement {

    /// Wait for the element to exist in the hierarchy.
    /// - Parameters:
    ///   - timeout: Maximum seconds to wait.
    ///   - message: Custom failure message.
    /// - Returns: `true` if the element exists within the timeout.
    @discardableResult
    func waitForExistence(timeout: TimeInterval = 5, message: String? = nil) -> Bool {
        let result = waitForExistence(timeout: timeout)
        if !result, let message = message {
            XCTFail(message)
        }
        return result
    }

    /// Wait until the element is hittable (exists and is enabled/visible).
    /// - Parameter timeout: Maximum seconds to wait.
    @discardableResult
    func waitUntilHittable(timeout: TimeInterval = 5) -> Bool {
        let predicate = NSPredicate(format: "isHittable == true")
        let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
        let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
        return result == .completed
    }

    /// Tap the element after waiting for it to become hittable.
    /// - Parameter timeout: Maximum seconds to wait before tapping.
    func tapWhenReady(timeout: TimeInterval = 5, file: StaticString = #file, line: UInt = #line) {
        guard waitForExistence(timeout: timeout) else {
            XCTFail("Element \(identifier) not found after \(timeout)s", file: file, line: line)
            return
        }
        if isHittable {
            tap()
            return
        }

        // Coordinate tap fallback for iOS 26 overlays where XCUI reports false-negative hittability.
        coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
    }

    /// Wait for the element to disappear from the hierarchy.
    /// - Parameter timeout: Maximum seconds to wait.
    @discardableResult
    func waitForDisappearance(timeout: TimeInterval = 5) -> Bool {
        let predicate = NSPredicate(format: "exists == false")
        let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
        let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
        return result == .completed
    }
}

extension XCUIApplication {

    /// Find any element matching an accessibility identifier.
    func element(_ identifier: String) -> XCUIElement {
        let element = descendants(matching: .any).matching(identifier: identifier).firstMatch
        return element
    }

    /// Wait for any element matching the identifier to exist.
    func waitForElement(identifier: String, timeout: TimeInterval = 5) -> XCUIElement {
        let element = element(identifier)
        _ = element.waitForExistence(timeout: timeout)
        return element
    }

    var entryRows: XCUIElementQuery {
        descendants(matching: .any).matching(NSPredicate(format: "identifier BEGINSWITH %@", UITestID.Day.entryRowPrefix))
    }

    var firstEntryRow: XCUIElement {
        entryRows.firstMatch
    }

    func tapTab(identifier: String, labels: [String], timeout: TimeInterval = 5, file: StaticString = #file, line: UInt = #line) {
        let idMatch = tabBars.buttons[identifier]
        if idMatch.waitForExistence(timeout: 1) {
            idMatch.tapWhenReady(timeout: timeout, file: file, line: line)
            return
        }

        for label in labels {
            let labelMatch = tabBars.buttons[label]
            if labelMatch.waitForExistence(timeout: 1) {
                labelMatch.tapWhenReady(timeout: timeout, file: file, line: line)
                return
            }
        }

        XCTFail("Unable to find tab by id \(identifier) or labels \(labels)", file: file, line: line)
    }

    @discardableResult
    func swipeUntilExists(
        _ element: XCUIElement,
        direction: SwipeDirection = .up,
        maxSwipes: Int = 6,
        timeoutPerTry: TimeInterval = 0.6
    ) -> Bool {
        if element.waitForExistence(timeout: timeoutPerTry) {
            return true
        }

        for _ in 0..<maxSwipes {
            switch direction {
            case .up:
                swipeUp()
            case .down:
                swipeDown()
            case .left:
                swipeLeft()
            case .right:
                swipeRight()
            @unknown default:
                swipeUp()
            }

            if element.waitForExistence(timeout: timeoutPerTry) {
                return true
            }
        }

        return false
    }
}

enum SwipeDirection {
    case up
    case down
    case left
    case right
}
