import XPCShared
import XcodeInspector
import AppKit

public enum AXHelperError: LocalizedError {
    case failedToSetValue(AXError)
    
    public var errorDescription: String? {
        switch self {
        case .failedToSetValue(let axError):
            return "Failed to set focus element value by AccessibilityAPI: \(axError.rawValue)"
        }
    }
}

public struct AXHelper {
    public init() {}
    
    /// When Xcode commands are not available, we can fallback to directly
    /// set the value of the editor with Accessibility API.
    public func injectUpdatedCodeWithAccessibilityAPI(
        _ result: UpdatedContent,
        focusElement: AXUIElement,
        onSuccess: (() -> Void)? = nil,
        onError: (() -> Void)? = nil
    ) throws {
        let oldPosition = focusElement.selectedTextRange
        let oldScrollPosition = focusElement.parent?.verticalScrollBar?.doubleValue

        let error = AXUIElementSetAttributeValue(
            focusElement,
            kAXValueAttribute as CFString,
            result.content as CFTypeRef
        )

        if error != AXError.success {
            if let onError = onError {
                onError()
            }
            throw AXHelperError.failedToSetValue(error)
        }

        // recover selection range
        if let selection = result.newSelection {
            var range = SourceEditor.convertCursorRangeToRange(selection, in: result.content)
            if let value = AXValueCreate(.cfRange, &range) {
                AXUIElementSetAttributeValue(
                    focusElement,
                    kAXSelectedTextRangeAttribute as CFString,
                    value
                )
            }
        } else if let oldPosition {
            var range = CFRange(
                location: oldPosition.lowerBound,
                length: 0
            )
            if let value = AXValueCreate(.cfRange, &range) {
                AXUIElementSetAttributeValue(
                    focusElement,
                    kAXSelectedTextRangeAttribute as CFString,
                    value
                )
            }
        }

        // recover scroll position
        if let oldScrollPosition,
           let scrollBar = focusElement.parent?.verticalScrollBar
        {
            Self.setScrollBarValue(scrollBar, value: oldScrollPosition)
        }
        
        if let onSuccess = onSuccess {
            onSuccess()
        }
    }
    
    /// Helper method to set scroll bar value using Accessibility API
    private static func setScrollBarValue(_ scrollBar: AXUIElement, value: Double) {
        AXUIElementSetAttributeValue(
            scrollBar,
            kAXValueAttribute as CFString,
            value as CFTypeRef
        )
    }
    
    private static func getScrollPositionForLine(_ lineNumber: Int, content: String) -> Double? {
        let lines = content.components(separatedBy: .newlines)
        let linesCount = lines.count
        
        guard lineNumber > 0 && lineNumber <= linesCount 
        else { return nil }
        
        // Calculate relative position (0.0 to 1.0)
        let relativePosition = Double(lineNumber - 1) / Double(linesCount - 1)
        
        // Ensure valid range
        return (0.0 <= relativePosition && relativePosition <= 1.0) ? relativePosition : nil
    }
    
    public static func scrollSourceEditorToLine(_ lineNumber: Int, content: String, focusedElement: AXUIElement) {
        guard focusedElement.isNonNavigatorSourceEditor,
              let scrollBar = focusedElement.parent?.verticalScrollBar,
              let linePosition = Self.getScrollPositionForLine(lineNumber, content: content)
        else { return }
        
        Self.setScrollBarValue(scrollBar, value: linePosition)
    }
}
