import Foundation
#if canImport(os)
import os
#endif
#if canImport(XCTest) && os(iOS)
    import XCTest
#endif
#if os(iOS)
    import UIKit
#endif

/// Text-input command trace logger.
/// See `Logging.swift` for the log-level contract shared across CtrlProxy.
private let textInputLog = Logger(subsystem: ctrlProxyLogSubsystem, category: "CommandHandler.text")

/// Handles WebSocket commands matching Android AccessibilityService protocol
public class CommandHandler: CommandHandling {
    private let elementLocator: ElementLocating
    private let gesturePerformer: GesturePerforming
    private let perfProvider: PerfProvider
    private let storageInspector: StorageInspecting?
    private let sdkHierarchyClient: (any SdkHierarchyFetching)?
    private let sdkHierarchyCache: (any SdkHierarchyCaching)?

    public init(
        elementLocator: ElementLocating,
        gesturePerformer: GesturePerforming,
        perfProvider: PerfProvider = PerfProvider.instance,
        storageInspector: StorageInspecting? = nil,
        sdkHierarchyClient: (any SdkHierarchyFetching)? = nil,
        sdkHierarchyCache: (any SdkHierarchyCaching)? = nil
    ) {
        self.elementLocator = elementLocator
        self.gesturePerformer = gesturePerformer
        self.perfProvider = perfProvider
        self.storageInspector = storageInspector
        self.sdkHierarchyClient = sdkHierarchyClient
        self.sdkHierarchyCache = sdkHierarchyCache
    }

    /// Factory for testing - allows injecting fakes
    public static func createForTesting(
        elementLocator: ElementLocating,
        gesturePerformer: GesturePerforming,
        perfProvider: PerfProvider,
        storageInspector: StorageInspecting? = nil,
        sdkHierarchyClient: (any SdkHierarchyFetching)? = nil,
        sdkHierarchyCache: (any SdkHierarchyCaching)? = nil
    )
        -> CommandHandler
    {
        return CommandHandler(
            elementLocator: elementLocator,
            gesturePerformer: gesturePerformer,
            perfProvider: perfProvider,
            storageInspector: storageInspector,
            sdkHierarchyClient: sdkHierarchyClient,
            sdkHierarchyCache: sdkHierarchyCache
        )
    }

    /// Handle an incoming request and return a response
    public func handle(_ request: WebSocketRequest) -> Any {
        let startTime = Date()

        do {
            switch request.type {
            // View hierarchy commands
            case RequestType.requestHierarchy.rawValue,
                 RequestType.requestHierarchyIfStale.rawValue:
                return try handleRequestHierarchy(request, startTime: startTime)

            case RequestType.requestScreenshot.rawValue:
                return try handleRequestScreenshot(request, startTime: startTime)

            // Gesture commands
            case RequestType.requestTapCoordinates.rawValue:
                return try handleTapCoordinates(request, startTime: startTime)

            case RequestType.requestSwipe.rawValue:
                return try handleSwipe(request, startTime: startTime)

            case RequestType.requestTwoFingerSwipe.rawValue:
                return try handleTwoFingerSwipe(request, startTime: startTime)

            case RequestType.requestDrag.rawValue:
                return try handleDrag(request, startTime: startTime)

            case RequestType.requestPinch.rawValue:
                return try handlePinch(request, startTime: startTime)

            // Text input commands
            case RequestType.requestSetText.rawValue:
                return try handleSetText(request, startTime: startTime)

            case RequestType.requestClearText.rawValue:
                return try handleClearText(request, startTime: startTime)

            case RequestType.requestImeAction.rawValue:
                return try handleImeAction(request, startTime: startTime)

            case RequestType.requestSelectAll.rawValue:
                return try handleSelectAll(request, startTime: startTime)

            case RequestType.requestPressHome.rawValue:
                return try handlePressHome(request, startTime: startTime)

            case RequestType.requestRecentApps.rawValue:
                return try handleRecentApps(request, startTime: startTime)

            // Action commands
            case RequestType.requestAction.rawValue:
                return try handleAction(request, startTime: startTime)

            case RequestType.requestLaunchApp.rawValue:
                return try handleLaunchApp(request, startTime: startTime)

            // Device control
            case RequestType.requestRotate.rawValue:
                return try handleRotate(request, startTime: startTime)

            // Clipboard commands
            case RequestType.requestClipboard.rawValue:
                return try handleClipboard(request, startTime: startTime)

            // Accessibility features
            case RequestType.getCurrentFocus.rawValue:
                return try handleGetCurrentFocus(request, startTime: startTime)

            case RequestType.getTraversalOrder.rawValue:
                return try handleGetTraversalOrder(request, startTime: startTime)

            case RequestType.addHighlight.rawValue:
                return try handleAddHighlight(request, startTime: startTime)

            case RequestType.getVoiceOverState.rawValue:
                return try handleGetVoiceOverState(request, startTime: startTime)

            // Storage commands
            case RequestType.listPreferenceFiles.rawValue:
                return handleListPreferenceFiles(request, startTime: startTime)

            case RequestType.getPreferences.rawValue:
                return handleGetPreferences(request, startTime: startTime)

            case RequestType.getPreference.rawValue:
                return handleGetPreference(request, startTime: startTime)

            case RequestType.setPreference.rawValue:
                return try handleSetPreference(request, startTime: startTime)

            case RequestType.removePreference.rawValue:
                return try handleRemovePreference(request, startTime: startTime)

            case RequestType.clearPreferences.rawValue:
                return try handleClearPreferences(request, startTime: startTime)

            default:
                return WebSocketResponse.error(
                    type: "error",
                    requestId: request.requestId,
                    error: "Unknown command type: \(request.type)",
                    totalTimeMs: totalTimeMs(from: startTime)
                )
            }
        } catch {
            return WebSocketResponse.error(
                type: responseType(for: request.type),
                requestId: request.requestId,
                error: error.localizedDescription,
                totalTimeMs: totalTimeMs(from: startTime)
            )
        }
    }

    // MARK: - View Hierarchy

    private func handleRequestHierarchy(
        _ request: WebSocketRequest,
        startTime _: Date
    )
        throws -> HierarchyUpdateResponse
    {
        perfProvider.serial("handleRequestHierarchy")
        defer { perfProvider.end() }

        let disableAllFiltering = request.disableAllFiltering ?? false
        let hierarchy: ViewHierarchy
        do {
            hierarchy = try perfProvider.track("extraction") {
                try elementLocator.getViewHierarchy(disableAllFiltering: disableAllFiltering)
            }
        } catch {
            print("[CommandHandler] Hierarchy extraction failed: \(error)")
            throw CommandError.executionFailed("Failed to get view hierarchy: \(error.localizedDescription)")
        }

        // Merge with SDK hierarchy: prefer cached (fast), fall back to fresh pull if no cache
        let sdkHierarchy: SdkViewHierarchy? = sdkHierarchyCache?.latest
            ?? sdkHierarchyClient?.fetchFreshHierarchy()
        let enriched = HierarchyMerger.merge(xcuitest: hierarchy, sdk: sdkHierarchy)

        // Get accumulated timing for this operation
        let perfTimings = perfProvider.flush()

        return HierarchyUpdateResponse(
            requestId: request.requestId,
            data: enriched,
            perfTiming: perfTimings?.first
        )
    }

    private func handleRequestScreenshot(_ request: WebSocketRequest, startTime _: Date) throws -> ScreenshotResponse {
        let data = try gesturePerformer.getScreenshot()
        let base64 = data.base64EncodedString()

        return ScreenshotResponse(
            requestId: request.requestId,
            data: base64,
            format: "png"
        )
    }

    // MARK: - Gestures

    private func handleTapCoordinates(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let x = request.x, let y = request.y else {
            throw CommandError.missingParameter("x and y coordinates")
        }

        let duration = request.duration ?? 0
        try gesturePerformer.tap(x: Double(x), y: Double(y), duration: TimeInterval(duration) / 1000.0)

        return WebSocketResponse.success(
            type: ResponseType.tapCoordinatesResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleSwipe(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let x1 = request.x1, let y1 = request.y1,
              let x2 = request.x2, let y2 = request.y2
        else {
            throw CommandError.missingParameter("x1, y1, x2, y2")
        }

        let duration = request.duration ?? 300
        try gesturePerformer.swipe(
            startX: Double(x1), startY: Double(y1),
            endX: Double(x2), endY: Double(y2),
            duration: TimeInterval(duration) / 1000.0
        )

        return WebSocketResponse.success(
            type: ResponseType.swipeResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleTwoFingerSwipe(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        // Stub: Two-finger swipe not yet implemented on iOS
        return WebSocketResponse.error(
            type: ResponseType.swipeResult.rawValue,
            requestId: request.requestId,
            error: "Two-finger swipe not yet implemented on iOS",
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleDrag(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let x1 = request.x1, let y1 = request.y1,
              let x2 = request.x2, let y2 = request.y2
        else {
            throw CommandError.missingParameter("x1, y1, x2, y2")
        }

        let pressDuration = request.pressDurationMs ?? request.holdTime ?? 600
        let dragDuration = request.dragDurationMs ?? 300
        let holdDuration = request.holdDurationMs ?? 100

        try gesturePerformer.drag(
            startX: Double(x1), startY: Double(y1),
            endX: Double(x2), endY: Double(y2),
            pressDuration: TimeInterval(pressDuration) / 1000.0,
            dragDuration: TimeInterval(dragDuration) / 1000.0,
            holdDuration: TimeInterval(holdDuration) / 1000.0
        )

        return WebSocketResponse.success(
            type: ResponseType.dragResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handlePinch(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let centerX = request.centerX, let centerY = request.centerY,
              let distanceStart = request.distanceStart, let distanceEnd = request.distanceEnd
        else {
            throw CommandError.missingParameter("centerX, centerY, distanceStart, distanceEnd")
        }

        let duration = request.duration ?? 300
        let scale = Double(distanceEnd) / Double(distanceStart)

        try gesturePerformer.pinch(
            centerX: Double(centerX),
            centerY: Double(centerY),
            scale: scale,
            duration: TimeInterval(duration) / 1000.0
        )

        return WebSocketResponse.success(
            type: ResponseType.pinchResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    // MARK: - Text Input

    private func handleSetText(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let text = request.text else {
            throw CommandError.missingParameter("text")
        }

        perfProvider.serial("handleSetText")
        defer { perfProvider.end() }

        let resourceId = request.resourceId
        textInputLog.debug("handleSetText begin resourceId=\(resourceId ?? "nil", privacy: .public) textLength=\(text.count, privacy: .public) requestId=\(request.requestId ?? "nil", privacy: .public)")

        do {
            if let resourceId = resourceId {
                try perfProvider.track("setText.byResourceId") {
                    try gesturePerformer.setText(resourceId: resourceId, text: text)
                }
            } else {
                try perfProvider.track("typeText") {
                    try gesturePerformer.typeText(text: text)
                }
            }
        } catch {
            textInputLog.error("handleSetText FAILED resourceId=\(resourceId ?? "nil", privacy: .public) error=\(String(describing: error), privacy: .public) elapsedMs=\(self.totalTimeMs(from: startTime), privacy: .public)")
            throw error
        }

        textInputLog.debug("handleSetText OK resourceId=\(resourceId ?? "nil", privacy: .public) elapsedMs=\(self.totalTimeMs(from: startTime), privacy: .public)")
        return WebSocketResponse.success(
            type: ResponseType.setTextResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleClearText(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        perfProvider.serial("handleClearText")
        defer { perfProvider.end() }

        let resourceId = request.resourceId
        textInputLog.debug("handleClearText begin resourceId=\(resourceId ?? "nil", privacy: .public) requestId=\(request.requestId ?? "nil", privacy: .public)")

        do {
            try perfProvider.track("clearText") {
                try gesturePerformer.clearText(resourceId: resourceId)
            }
        } catch {
            textInputLog.error("handleClearText FAILED resourceId=\(resourceId ?? "nil", privacy: .public) error=\(String(describing: error), privacy: .public) elapsedMs=\(self.totalTimeMs(from: startTime), privacy: .public)")
            throw error
        }

        textInputLog.debug("handleClearText OK resourceId=\(resourceId ?? "nil", privacy: .public) elapsedMs=\(self.totalTimeMs(from: startTime), privacy: .public)")
        return WebSocketResponse.success(
            type: ResponseType.clearTextResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleImeAction(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let action = request.action else {
            throw CommandError.missingParameter("action")
        }

        perfProvider.serial("handleImeAction")
        defer { perfProvider.end() }

        textInputLog.debug("handleImeAction begin action=\(action, privacy: .public) requestId=\(request.requestId ?? "nil", privacy: .public)")

        do {
            try perfProvider.track("imeAction") {
                try gesturePerformer.performImeAction(action)
            }
        } catch {
            textInputLog.error("handleImeAction FAILED action=\(action, privacy: .public) error=\(String(describing: error), privacy: .public) elapsedMs=\(self.totalTimeMs(from: startTime), privacy: .public)")
            throw error
        }

        textInputLog.debug("handleImeAction OK action=\(action, privacy: .public) elapsedMs=\(self.totalTimeMs(from: startTime), privacy: .public)")
        return WebSocketResponse.success(
            type: ResponseType.imeActionResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleSelectAll(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        perfProvider.serial("handleSelectAll")
        defer { perfProvider.end() }

        textInputLog.debug("handleSelectAll begin requestId=\(request.requestId ?? "nil", privacy: .public)")

        do {
            try perfProvider.track("selectAll") {
                try gesturePerformer.selectAll()
            }
        } catch {
            textInputLog.error("handleSelectAll FAILED error=\(String(describing: error), privacy: .public) elapsedMs=\(self.totalTimeMs(from: startTime), privacy: .public)")
            throw error
        }

        textInputLog.debug("handleSelectAll OK elapsedMs=\(self.totalTimeMs(from: startTime), privacy: .public)")
        return WebSocketResponse.success(
            type: ResponseType.selectAllResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handlePressHome(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        perfProvider.serial("handlePressHome")
        defer { perfProvider.end() }

        try perfProvider.track("pressHome") {
            try gesturePerformer.pressHome()
        }

        // Explicit state transition: home screen means springboard is now foreground
        perfProvider.track("switchForegroundApp") {
            elementLocator.switchForegroundApp(bundleId: "com.apple.springboard")
        }
        perfProvider.track("updateApplication") {
            gesturePerformer.updateApplication(bundleId: "com.apple.springboard")
        }

        return WebSocketResponse.success(
            type: ResponseType.pressHomeResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleRecentApps(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        perfProvider.serial("handleRecentApps")
        defer { perfProvider.end() }

        try perfProvider.track("openRecentApps") {
            try gesturePerformer.openRecentApps()
        }

        // Explicit state transition: app switcher is SpringBoard UI
        perfProvider.track("switchForegroundApp") {
            elementLocator.switchForegroundApp(bundleId: "com.apple.springboard")
        }
        perfProvider.track("updateApplication") {
            gesturePerformer.updateApplication(bundleId: "com.apple.springboard")
        }

        return WebSocketResponse.success(
            type: ResponseType.recentAppsResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    // MARK: - Actions

    private func handleAction(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let action = request.action else {
            throw CommandError.missingParameter("action")
        }

        try gesturePerformer.performAction(action, resourceId: request.resourceId, label: request.label)

        return WebSocketResponse.success(
            type: ResponseType.actionResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleLaunchApp(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        perfProvider.serial("handleLaunchApp")
        defer { perfProvider.end() }

        guard let bundleId = request.bundleId else {
            throw CommandError.missingParameter("bundleId")
        }

        let coldBoot = request.coldBoot ?? false

        // Check current app state to decide launch strategy
        let appState = perfProvider.track("checkAppState") {
            elementLocator.getAppState(bundleId: bundleId)
        }

        let strategy: String
        let alreadyForeground = appState == .runningForeground
        if coldBoot {
            strategy = appState == .notRunning || appState == .unknown ? "coldBoot:launch" : "coldBoot:terminate+launch"
        } else {
            switch appState {
            case .runningForeground: strategy = "activate(foreground)"
            case .runningBackground, .runningBackgroundSuspended: strategy = "activate(background)"
            default: strategy = "launch(notRunning)"
            }
        }
        print("[CtrlProxy] handleLaunchApp bundleId=\(bundleId) appState=\(appState) strategy=\(strategy)")

        if coldBoot {
            // Cold boot: always terminate then launch fresh
            if appState == .runningForeground || appState == .runningBackground || appState == .runningBackgroundSuspended {
                try perfProvider.track("terminateApp") {
                    try gesturePerformer.terminateApp(bundleId: bundleId)
                }
            }
            try perfProvider.track("launchApp") {
                try gesturePerformer.launchApp(bundleId: bundleId)
            }
        } else if appState == .runningForeground {
            // Already in foreground — activate is a no-op but ensures XCTest sync
            try perfProvider.track("activateApp") {
                try gesturePerformer.activateApp(bundleId: bundleId)
            }
        } else if appState == .runningBackground || appState == .runningBackgroundSuspended {
            // App running but not visible — activate brings to foreground (fast path)
            try perfProvider.track("activateApp") {
                try gesturePerformer.activateApp(bundleId: bundleId)
            }
        } else {
            // App not running — must do full launch
            try perfProvider.track("launchApp") {
                try gesturePerformer.launchApp(bundleId: bundleId)
            }
        }

        // Explicit state transition: switch tracking to launched app
        perfProvider.track("switchForegroundApp") {
            elementLocator.switchForegroundApp(bundleId: bundleId)
        }
        perfProvider.track("updateApplication") {
            gesturePerformer.updateApplication(bundleId: bundleId)
        }

        // Skip foreground poll when activate() was called on an already-foreground app —
        // activate() is synchronous and the app is guaranteed to remain foreground.
        if !alreadyForeground || coldBoot {
            perfProvider.track("awaitForeground") {
                let _ = elementLocator.awaitAppState(bundleId: bundleId, expectedState: .foreground)
            }
        }

        return WebSocketResponse.success(
            type: ResponseType.launchAppResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    // MARK: - Device Control

    private func handleRotate(_ request: WebSocketRequest, startTime: Date) throws -> RotateResponse {
        guard let orientation = request.orientation else {
            throw CommandError.missingParameter("orientation")
        }

        let previousOrientation = gesturePerformer.getOrientation()

        // Map "landscape" to "landscape_left" (standard rotation direction)
        let iosOrientation: String
        switch orientation.lowercased() {
        case "portrait":
            iosOrientation = "portrait"
        case "landscape":
            iosOrientation = "landscape_left"
        case "portrait_upside_down", "portraitupsidedown":
            iosOrientation = "portrait_upside_down"
        case "landscape_left", "landscapeleft":
            iosOrientation = "landscape_left"
        case "landscape_right", "landscaperight":
            iosOrientation = "landscape_right"
        default:
            throw CommandError.invalidParameter("orientation", orientation)
        }

        // Normalize to "portrait" or "landscape" for the result
        let normalizedPrevious = previousOrientation.hasPrefix("landscape") ? "landscape" : "portrait"
        let normalizedTarget = iosOrientation.hasPrefix("landscape") ? "landscape" : "portrait"
        let value = normalizedTarget == "portrait" ? 0 : 1

        // Check if already in the desired orientation
        if normalizedPrevious == normalizedTarget {
            return RotateResponse(
                requestId: request.requestId,
                success: true,
                totalTimeMs: totalTimeMs(from: startTime),
                previousOrientation: normalizedPrevious,
                currentOrientation: normalizedTarget,
                value: value,
                rotationPerformed: false
            )
        }

        try gesturePerformer.setOrientation(iosOrientation)

        return RotateResponse(
            requestId: request.requestId,
            success: true,
            totalTimeMs: totalTimeMs(from: startTime),
            previousOrientation: normalizedPrevious,
            currentOrientation: normalizedTarget,
            value: value,
            rotationPerformed: true
        )
    }

    // MARK: - Clipboard

    private func handleClipboard(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let action = request.action else {
            throw CommandError.missingParameter("action")
        }

        let resultText = try gesturePerformer.clipboard(action: action, text: request.text)

        return WebSocketResponse.success(
            type: ResponseType.clipboardResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime),
            text: resultText
        )
    }

    // MARK: - Accessibility Features

    private func handleGetCurrentFocus(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        // Stub: Focus tracking not yet implemented
        return WebSocketResponse.error(
            type: ResponseType.currentFocusResult.rawValue,
            requestId: request.requestId,
            error: "Current focus not yet implemented on iOS",
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleGetTraversalOrder(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        // Stub: Traversal order not yet implemented
        return WebSocketResponse.error(
            type: ResponseType.traversalOrderResult.rawValue,
            requestId: request.requestId,
            error: "Traversal order not yet implemented on iOS",
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleAddHighlight(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        // Stub: Highlights not yet implemented
        return WebSocketResponse.error(
            type: ResponseType.highlightResponse.rawValue,
            requestId: request.requestId,
            error: "Highlights not yet implemented on iOS",
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleGetVoiceOverState(
        _ request: WebSocketRequest,
        startTime: Date
    )
        throws -> VoiceOverStateResponse
    {
        #if os(iOS)
            let enabled = UIAccessibility.isVoiceOverRunning
        #else
            let enabled = false
        #endif

        return VoiceOverStateResponse(
            requestId: request.requestId,
            enabled: enabled,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    // MARK: - Storage

    /// Resolve suite name from request fileName: nil/empty/"Standard" -> nil (UserDefaults.standard)
    private func resolveSuiteName(_ fileName: String?) -> String? {
        guard let name = fileName, !name.isEmpty, name != "Standard" else { return nil }
        return name
    }

    private func handleListPreferenceFiles(_ request: WebSocketRequest, startTime: Date) -> StorageFilesResponse {
        guard let inspector = storageInspector else {
            return StorageFilesResponse(
                requestId: request.requestId,
                success: false,
                error: "Storage inspection not available",
                totalTimeMs: totalTimeMs(from: startTime)
            )
        }

        let suites = inspector.listSuites()
        return StorageFilesResponse(
            requestId: request.requestId,
            success: true,
            files: suites,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleGetPreferences(_ request: WebSocketRequest, startTime: Date) -> StorageEntriesResponse {
        guard let inspector = storageInspector else {
            return StorageEntriesResponse(
                requestId: request.requestId,
                success: false,
                error: "Storage inspection not available",
                totalTimeMs: totalTimeMs(from: startTime)
            )
        }

        let suiteName = resolveSuiteName(request.fileName)
        let entries = inspector.getEntries(suiteName: suiteName)
        return StorageEntriesResponse(
            requestId: request.requestId,
            success: true,
            entries: entries,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleGetPreference(_ request: WebSocketRequest, startTime: Date) -> StorageEntryResponse {
        guard let inspector = storageInspector else {
            return StorageEntryResponse(
                requestId: request.requestId,
                success: false,
                found: false,
                error: "Storage inspection not available",
                totalTimeMs: totalTimeMs(from: startTime)
            )
        }

        guard let key = request.key else {
            return StorageEntryResponse(
                requestId: request.requestId,
                success: false,
                found: false,
                error: "Missing required parameter: key",
                totalTimeMs: totalTimeMs(from: startTime)
            )
        }

        let suiteName = resolveSuiteName(request.fileName)
        if let entry = inspector.getEntry(suiteName: suiteName, key: key) {
            return StorageEntryResponse(
                requestId: request.requestId,
                success: true,
                found: true,
                key: entry.key,
                value: entry.value,
                valueType: entry.type,
                totalTimeMs: totalTimeMs(from: startTime)
            )
        } else {
            return StorageEntryResponse(
                requestId: request.requestId,
                success: true,
                found: false,
                totalTimeMs: totalTimeMs(from: startTime)
            )
        }
    }

    private func handleSetPreference(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let inspector = storageInspector else {
            return WebSocketResponse.error(
                type: ResponseType.setPreferenceResult.rawValue,
                requestId: request.requestId,
                error: "Storage inspection not available",
                totalTimeMs: totalTimeMs(from: startTime)
            )
        }

        guard let key = request.key else {
            throw CommandError.missingParameter("key")
        }
        guard let valueType = request.valueType else {
            throw CommandError.missingParameter("valueType")
        }

        let suiteName = resolveSuiteName(request.fileName)
        try inspector.setEntry(suiteName: suiteName, key: key, value: request.value, type: valueType)

        return WebSocketResponse.success(
            type: ResponseType.setPreferenceResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleRemovePreference(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let inspector = storageInspector else {
            return WebSocketResponse.error(
                type: ResponseType.removePreferenceResult.rawValue,
                requestId: request.requestId,
                error: "Storage inspection not available",
                totalTimeMs: totalTimeMs(from: startTime)
            )
        }

        guard let key = request.key else {
            throw CommandError.missingParameter("key")
        }

        let suiteName = resolveSuiteName(request.fileName)
        try inspector.removeEntry(suiteName: suiteName, key: key)

        return WebSocketResponse.success(
            type: ResponseType.removePreferenceResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    private func handleClearPreferences(_ request: WebSocketRequest, startTime: Date) throws -> WebSocketResponse {
        guard let inspector = storageInspector else {
            return WebSocketResponse.error(
                type: ResponseType.clearPreferencesResult.rawValue,
                requestId: request.requestId,
                error: "Storage inspection not available",
                totalTimeMs: totalTimeMs(from: startTime)
            )
        }

        let suiteName = resolveSuiteName(request.fileName)
        try inspector.clearEntries(suiteName: suiteName)

        return WebSocketResponse.success(
            type: ResponseType.clearPreferencesResult.rawValue,
            requestId: request.requestId,
            totalTimeMs: totalTimeMs(from: startTime)
        )
    }

    // MARK: - Helpers

    private func totalTimeMs(from startTime: Date) -> Int64 {
        return Int64(Date().timeIntervalSince(startTime) * 1000)
    }

    private func responseType(for requestType: String) -> String {
        switch requestType {
        case RequestType.requestHierarchy.rawValue,
             RequestType.requestHierarchyIfStale.rawValue:
            return ResponseType.hierarchyUpdate.rawValue
        case RequestType.requestScreenshot.rawValue:
            return ResponseType.screenshot.rawValue
        case RequestType.requestTapCoordinates.rawValue:
            return ResponseType.tapCoordinatesResult.rawValue
        case RequestType.requestSwipe.rawValue,
             RequestType.requestTwoFingerSwipe.rawValue:
            return ResponseType.swipeResult.rawValue
        case RequestType.requestDrag.rawValue:
            return ResponseType.dragResult.rawValue
        case RequestType.requestPinch.rawValue:
            return ResponseType.pinchResult.rawValue
        case RequestType.requestSetText.rawValue:
            return ResponseType.setTextResult.rawValue
        case RequestType.requestClearText.rawValue:
            return ResponseType.clearTextResult.rawValue
        case RequestType.requestImeAction.rawValue:
            return ResponseType.imeActionResult.rawValue
        case RequestType.requestSelectAll.rawValue:
            return ResponseType.selectAllResult.rawValue
        case RequestType.requestPressHome.rawValue:
            return ResponseType.pressHomeResult.rawValue
        case RequestType.requestAction.rawValue:
            return ResponseType.actionResult.rawValue
        case RequestType.requestLaunchApp.rawValue:
            return ResponseType.launchAppResult.rawValue
        case RequestType.requestRotate.rawValue:
            return ResponseType.rotateResult.rawValue
        case RequestType.requestClipboard.rawValue:
            return ResponseType.clipboardResult.rawValue
        case RequestType.getVoiceOverState.rawValue:
            return ResponseType.voiceOverStateResult.rawValue
        case RequestType.listPreferenceFiles.rawValue:
            return ResponseType.preferenceFiles.rawValue
        case RequestType.getPreferences.rawValue:
            return ResponseType.preferences.rawValue
        case RequestType.getPreference.rawValue:
            return ResponseType.getPreferenceResult.rawValue
        case RequestType.setPreference.rawValue:
            return ResponseType.setPreferenceResult.rawValue
        case RequestType.removePreference.rawValue:
            return ResponseType.removePreferenceResult.rawValue
        case RequestType.clearPreferences.rawValue:
            return ResponseType.clearPreferencesResult.rawValue
        default:
            return "error"
        }
    }
}

// MARK: - Errors

public enum CommandError: LocalizedError {
    case unknownCommand(String)
    case missingParameter(String)
    case invalidParameter(String, String)
    case elementNotFound(String)
    case executionFailed(String)
    case notSupported(String)

    public var errorDescription: String? {
        switch self {
        case let .unknownCommand(cmd):
            return "Unknown command: \(cmd)"
        case let .missingParameter(param):
            return "Missing required parameter: \(param)"
        case let .invalidParameter(param, value):
            return "Invalid value '\(value)' for parameter '\(param)'"
        case let .elementNotFound(id):
            return "Element not found: \(id)"
        case let .executionFailed(reason):
            return "Command execution failed: \(reason)"
        case let .notSupported(feature):
            return "Feature not supported: \(feature)"
        }
    }
}
