import NIO
import NIOIMAPCore
import NIOIMAP

// NIO handlers are confined to their event loop; @unchecked Sendable is the
// standard pattern for crossing the actor/event-loop boundary.
final class IMAPResponseHandler: ChannelInboundHandler, @unchecked Sendable {
	typealias InboundIn = Response

	private var buffer: [Response] = []
	private var expectedTag: String?
	private var continuation: CheckedContinuation<[Response], Error>?
	private var greetingContinuation: CheckedContinuation<Void, Error>?

	func channelRead(context: ChannelHandlerContext, data: NIOAny) {
		let response = unwrapInboundIn(data)
		buffer.append(response)

		switch response {
		case .untagged(let payload):
			if case .conditionalState(let status) = payload, greetingContinuation != nil {
				switch status {
				case .ok:
					greetingContinuation?.resume()
					greetingContinuation = nil
				case .preauth:
					greetingContinuation?.resume()
					greetingContinuation = nil
				case .bye(let text):
					let error = IMAPError.serverError("BYE: \(text)")
					greetingContinuation?.resume(throwing: error)
					greetingContinuation = nil
				default:
					break
				}
			}
		case .tagged(let tagged):
			if tagged.tag == expectedTag {
				let collected = buffer
				buffer = []
				expectedTag = nil
				continuation?.resume(returning: collected)
				continuation = nil
			}
		case .fatal(let text):
			let error = IMAPError.serverError("FATAL: \(text)")
			continuation?.resume(throwing: error)
			continuation = nil
			greetingContinuation?.resume(throwing: error)
			greetingContinuation = nil
		case .fetch, .authenticationChallenge, .idleStarted:
			break
		}
	}

	func errorCaught(context: ChannelHandlerContext, error: Error) {
		continuation?.resume(throwing: error)
		continuation = nil
		greetingContinuation?.resume(throwing: error)
		greetingContinuation = nil
		context.close(promise: nil)
	}

	func waitForGreeting() async throws {
		try await withCheckedThrowingContinuation { cont in
			greetingContinuation = cont
		}
	}

	func sendCommand(tag: String, continuation cont: CheckedContinuation<[Response], Error>) {
		expectedTag = tag
		continuation = cont
		buffer = []
	}
}

public enum IMAPError: Error, Sendable {
	case notConnected
	case serverError(String)
	case authenticationFailed
	case unexpectedResponse(String)
}
