// ModifierStateTests.swift
// Unit tests for ModifierState — the sticky-modifier observable for ModifierBar.
//
// ModifierState is pure state (no UIKit, no networking) so all tests are
// synchronous. The class is @MainActor; every test method is also marked
// @MainActor to satisfy Swift 6 strict concurrency.

import XCTest
@testable import piRemote

@MainActor
final class ModifierStateTests: XCTestCase {

    // Fresh instance per test.
    private var state: ModifierState!

    override func setUp() async throws {
        state = ModifierState()
    }

    override func tearDown() async throws {
        state = nil
    }

    // MARK: - Initial state

    /// Both published properties must be `false` immediately after init.
    func testInitialState_bothFalse() {
        XCTAssertFalse(state.ctrlActive, "ctrlActive should start as false")
        XCTAssertFalse(state.isRepeating, "isRepeating should start as false")
    }

    // MARK: - toggleCtrl

    /// First toggle arms the Ctrl modifier.
    func testToggleCtrl_armsModifier() {
        state.toggleCtrl()
        XCTAssertTrue(state.ctrlActive)
    }

    /// Second toggle disarms it.
    func testToggleCtrl_disarmsModifier() {
        state.toggleCtrl() // arm
        state.toggleCtrl() // disarm
        XCTAssertFalse(state.ctrlActive)
    }

    /// Six successive toggles cycle true/false/true/false/true/false correctly.
    func testMultipleToggles_cycleCorrectly() {
        for i in 1...6 {
            state.toggleCtrl()
            let expected = (i % 2 == 1)
            XCTAssertEqual(
                state.ctrlActive, expected,
                "After \(i) toggle(s) ctrlActive should be \(expected)"
            )
        }
    }

    /// `toggleCtrl` must not affect `isRepeating`.
    func testToggleCtrl_doesNotAffectIsRepeating() {
        state.isRepeating = true
        state.toggleCtrl()
        XCTAssertTrue(state.isRepeating, "toggleCtrl must not touch isRepeating")
    }

    // MARK: - reset

    /// `reset()` clears `ctrlActive` when it was armed.
    func testReset_clearsCtrActive() {
        state.toggleCtrl()
        XCTAssertTrue(state.ctrlActive) // precondition

        state.reset()
        XCTAssertFalse(state.ctrlActive)
    }

    /// `reset()` clears `isRepeating` when it was set.
    func testReset_clearsIsRepeating() {
        state.isRepeating = true
        state.reset()
        XCTAssertFalse(state.isRepeating)
    }

    /// `reset()` clears both properties simultaneously.
    func testReset_clearsAllState() {
        state.toggleCtrl()
        state.isRepeating = true

        state.reset()

        XCTAssertFalse(state.ctrlActive, "ctrlActive must be false after reset()")
        XCTAssertFalse(state.isRepeating, "isRepeating must be false after reset()")
    }

    /// Calling `reset()` on an already-neutral state must not crash or change values.
    func testReset_isIdempotent() {
        // state is already all-false from setUp
        state.reset()
        state.reset()
        XCTAssertFalse(state.ctrlActive)
        XCTAssertFalse(state.isRepeating)
    }

    /// `reset()` after many toggles returns to neutral.
    func testReset_afterManyToggles_returnsToNeutral() {
        for _ in 0..<10 { state.toggleCtrl() }
        state.isRepeating = true
        state.reset()
        XCTAssertFalse(state.ctrlActive)
        XCTAssertFalse(state.isRepeating)
    }
}
