//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//

@testable import ElementX
import Foundation
import Testing

@MainActor
struct NavigationTabCoordinatorTests {
    enum TestTab { case tab, chats, spaces }
    private var navigationTabCoordinator: NavigationTabCoordinator<TestTab>
    
    init() {
        navigationTabCoordinator = NavigationTabCoordinator()
    }
    
    @Test
    mutating func tabs() {
        #expect(navigationTabCoordinator.tabCoordinators.isEmpty)
        
        let someCoordinator = SomeTestCoordinator()
        navigationTabCoordinator.setTabs([.init(coordinator: someCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))])
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [someCoordinator])
        
        let chatsCoordinator = SomeTestCoordinator()
        let spacesCoordinator = SomeTestCoordinator()
        navigationTabCoordinator.setTabs([
            .init(coordinator: chatsCoordinator, details: .init(tag: .chats, title: "Chats", icon: \.chat, selectedIcon: \.chatSolid)),
            .init(coordinator: spacesCoordinator, details: .init(tag: .spaces, title: "Spaces", icon: \.space, selectedIcon: \.spaceSolid))
        ])
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [chatsCoordinator, spacesCoordinator])
    }
    
    @Test
    mutating func singleSheet() {
        let tabCoordinator = SomeTestCoordinator()
        navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))])
        
        let coordinator = SomeTestCoordinator()
        navigationTabCoordinator.setSheetCoordinator(coordinator)
        
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator])
        assertCoordinatorsEqual(coordinator, navigationTabCoordinator.sheetCoordinator)
        
        navigationTabCoordinator.setSheetCoordinator(nil)
        
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator])
        #expect(navigationTabCoordinator.sheetCoordinator == nil)
    }
    
    @Test
    mutating func multipleSheets() {
        let tabCoordinator = SomeTestCoordinator()
        navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))])
        
        let sheetCoordinator = SomeTestCoordinator()
        navigationTabCoordinator.setSheetCoordinator(sheetCoordinator)
        
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator])
        assertCoordinatorsEqual(sheetCoordinator, navigationTabCoordinator.sheetCoordinator)
        
        let someOtherSheetCoordinator = SomeTestCoordinator()
        navigationTabCoordinator.setSheetCoordinator(someOtherSheetCoordinator)
        
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator])
        assertCoordinatorsEqual(someOtherSheetCoordinator, navigationTabCoordinator.sheetCoordinator)
    }
    
    @Test
    mutating func fullScreenCover() {
        let tabCoordinator = SomeTestCoordinator()
        navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))])
        
        let coordinator = SomeTestCoordinator()
        navigationTabCoordinator.setFullScreenCoverCoordinator(coordinator)
        
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator])
        assertCoordinatorsEqual(coordinator, navigationTabCoordinator.fullScreenCoverCoordinator)
        
        navigationTabCoordinator.setFullScreenCoverCoordinator(nil)
        
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator])
        #expect(navigationTabCoordinator.fullScreenCoverCoordinator == nil)
    }
    
    @Test
    mutating func overlay() {
        let tabCoordinator = SomeTestCoordinator()
        navigationTabCoordinator.setTabs([.init(coordinator: tabCoordinator, details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))])
        
        let overlayCoordinator = SomeTestCoordinator()
        navigationTabCoordinator.setOverlayCoordinator(overlayCoordinator)
        
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator])
        assertCoordinatorsEqual(overlayCoordinator, navigationTabCoordinator.overlayCoordinator)
        
        // The coordinator should still be retained when changing the presentation mode.
        navigationTabCoordinator.setOverlayPresentationMode(.minimized)
        assertCoordinatorsEqual(overlayCoordinator, navigationTabCoordinator.overlayCoordinator)
        navigationTabCoordinator.setOverlayPresentationMode(.fullScreen)
        assertCoordinatorsEqual(overlayCoordinator, navigationTabCoordinator.overlayCoordinator)
        
        navigationTabCoordinator.setOverlayCoordinator(nil)
        
        assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [tabCoordinator])
        #expect(navigationTabCoordinator.overlayCoordinator == nil)
    }
    
    // MARK: - Dismissal Callbacks
    
    @Test
    mutating func tabDismissalCallbacks() async {
        let chatsCoordinator = SomeTestCoordinator()
        let spacesCoordinator = SomeTestCoordinator()
        
        await confirmation("Wait for callback", expectedCount: 2) { confirm in
            navigationTabCoordinator.setTabs([
                .init(coordinator: chatsCoordinator, details: .init(tag: .chats, title: "Chats", icon: \.chat, selectedIcon: \.chatSolid)) { confirm() },
                .init(coordinator: spacesCoordinator, details: .init(tag: .spaces, title: "Spaces", icon: \.space, selectedIcon: \.spaceSolid)) { confirm() }
            ])
            assertCoordinatorsEqual(navigationTabCoordinator.tabCoordinators, [chatsCoordinator, spacesCoordinator])
            
            navigationTabCoordinator.setTabs([.init(coordinator: SomeTestCoordinator(), details: .init(tag: .tab, title: "Tab", icon: \.help, selectedIcon: \.helpSolid))])
        }
    }
    
    @Test
    mutating func sheetDismissalCallback() async {
        let coordinator = SomeTestCoordinator()
        await confirmation("Wait for callback") { confirm in
            navigationTabCoordinator.setSheetCoordinator(coordinator) {
                confirm()
            }
            
            navigationTabCoordinator.setSheetCoordinator(nil)
        }
    }
    
    @Test
    mutating func fullScreenCoverDismissalCallback() async {
        let coordinator = SomeTestCoordinator()
        await confirmation("Wait for callback") { confirm in
            navigationTabCoordinator.setFullScreenCoverCoordinator(coordinator) {
                confirm()
            }
            
            navigationTabCoordinator.setFullScreenCoverCoordinator(nil)
        }
    }
    
    @Test
    mutating func overlayDismissalCallback() async {
        let overlayCoordinator = SomeTestCoordinator()
        
        await confirmation("Wait for callback") { confirm in
            navigationTabCoordinator.setOverlayCoordinator(overlayCoordinator) {
                confirm()
            }
            
            navigationTabCoordinator.setOverlayCoordinator(nil)
        }
    }
    
    @Test
    mutating func overlayDismissalCallbackWhenChangingMode() async {
        let overlayCoordinator = SomeTestCoordinator()
        
        await waitForConfirmation("Callback should not be called when just changing mode",
                                  expectedCount: 0,
                                  timeout: .seconds(1)) { confirmation in
            navigationTabCoordinator.setOverlayCoordinator(overlayCoordinator) {
                confirmation()
            }
            
            navigationTabCoordinator.setOverlayPresentationMode(.minimized)
        }
    }
    
    // MARK: - Private
    
    private func assertCoordinatorsEqual(_ lhs: CoordinatorProtocol?, _ rhs: CoordinatorProtocol?) {
        guard let lhs = lhs as? SomeTestCoordinator,
              let rhs = rhs as? SomeTestCoordinator else {
            Issue.record("Coordinators are not the same")
            return
        }
        
        #expect(lhs.id == rhs.id)
    }
    
    private func assertCoordinatorsEqual(_ lhs: [CoordinatorProtocol], _ rhs: [CoordinatorProtocol]) {
        guard lhs.count == rhs.count else {
            Issue.record("Coordinators are not the same")
            return
        }
        
        for (index, coordinator) in lhs.enumerated() {
            assertCoordinatorsEqual(coordinator, rhs[index])
        }
    }
}

private class SomeTestCoordinator: CoordinatorProtocol {
    let id = UUID()
}
