import Foundation

/// Parses iCalendar VTODO components into property dictionaries.
public enum VTODOParser {

	/// Parse an iCalendar string and extract the first VTODO block as a property dictionary.
	/// Returns nil if no valid VTODO block is found.
	public static func parse(_ icsContent: String) -> [String: String]? {
		// Unfold lines: lines starting with space or tab are continuations
		let unfolded = unfoldLines(icsContent)
		let lines = unfolded.components(separatedBy: "\n")

		// Find BEGIN:VTODO / END:VTODO block
		var inVTODO = false
		var properties: [String: String] = [:]

		for line in lines {
			let trimmed = line.trimmingCharacters(in: .carriageReturn)
			if trimmed == "BEGIN:VTODO" {
				inVTODO = true
				continue
			}
			if trimmed == "END:VTODO" {
				break
			}
			guard inVTODO else { continue }

			// Split on first colon
			guard let colonIndex = trimmed.firstIndex(of: ":") else { continue }
			let rawName = String(trimmed[trimmed.startIndex..<colonIndex])
			let value = String(trimmed[trimmed.index(after: colonIndex)...])

			// Strip parameters from property name (e.g., "DTSTART;VALUE=DATE" → "DTSTART")
			let propertyName: String
			if let semiIndex = rawName.firstIndex(of: ";") {
				propertyName = String(rawName[rawName.startIndex..<semiIndex])
			} else {
				propertyName = rawName
			}

			properties[propertyName] = unescapeText(value)
		}

		guard !properties.isEmpty else { return nil }
		return properties
	}

	/// Unfold lines per RFC 5545: lines starting with a space or tab are continuations.
	private static func unfoldLines(_ text: String) -> String {
		var result = ""
		result.reserveCapacity(text.count)
		var firstLine = true

		for line in text.components(separatedBy: "\n") {
			let l = line.trimmingCharacters(in: .carriageReturn)
			if l.hasPrefix(" ") || l.hasPrefix("\t") {
				// Continuation: append without the leading whitespace
				result.append(String(l.dropFirst()))
			} else {
				if !firstLine {
					result.append("\n")
				}
				result.append(l)
				firstLine = false
			}
		}
		return result
	}

	// MARK: - Date parsing

	/// Parse an iCalendar date or date-time value.
	/// Supports: `20260315T090000Z`, `20260315T090000`, `20260315`
	public static func parseDate(_ value: String) -> Date? {
		let stripped = value.trimmingCharacters(in: .whitespaces)

		if stripped.count == 16 && stripped.hasSuffix("Z") {
			// DATE-TIME with Z: yyyyMMdd'T'HHmmss'Z'
			return dateTimeFormatterUTC.date(from: stripped)
		} else if stripped.count == 15 && stripped.contains("T") {
			// DATE-TIME without Z: yyyyMMdd'T'HHmmss (treat as UTC)
			return dateTimeFormatterLocal.date(from: stripped)
		} else if stripped.count == 8 {
			// DATE only: yyyyMMdd
			return dateOnlyFormatter.date(from: stripped)
		}
		return nil
	}

	/// Format a date as UTC date-time: `yyyyMMdd'T'HHmmss'Z'`
	public static func formatDate(_ date: Date) -> String {
		dateTimeFormatterUTC.string(from: date)
	}

	/// Format a date as date-only: `yyyyMMdd`
	public static func formatDateOnly(_ date: Date) -> String {
		dateOnlyFormatter.string(from: date)
	}

	// MARK: - Text unescaping

	/// Unescape iCalendar text values per RFC 5545.
	public static func unescapeText(_ text: String) -> String {
		var result = ""
		result.reserveCapacity(text.count)
		var iterator = text.makeIterator()

		while let char = iterator.next() {
			if char == "\\" {
				guard let next = iterator.next() else {
					result.append(char)
					break
				}
				switch next {
				case "n", "N":
					result.append("\n")
				case ",":
					result.append(",")
				case ";":
					result.append(";")
				case "\\":
					result.append("\\")
				default:
					result.append(char)
					result.append(next)
				}
			} else {
				result.append(char)
			}
		}
		return result
	}

	// MARK: - Formatters

	private static let dateTimeFormatterUTC: DateFormatter = {
		let f = DateFormatter()
		f.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
		f.timeZone = TimeZone(identifier: "UTC")
		f.locale = Locale(identifier: "en_US_POSIX")
		return f
	}()

	private static let dateTimeFormatterLocal: DateFormatter = {
		let f = DateFormatter()
		f.dateFormat = "yyyyMMdd'T'HHmmss"
		f.timeZone = TimeZone(identifier: "UTC")
		f.locale = Locale(identifier: "en_US_POSIX")
		return f
	}()

	private static let dateOnlyFormatter: DateFormatter = {
		let f = DateFormatter()
		f.dateFormat = "yyyyMMdd"
		f.timeZone = TimeZone(identifier: "UTC")
		f.locale = Locale(identifier: "en_US_POSIX")
		return f
	}()
}

// Make character sets available
private extension CharacterSet {
	static let carriageReturn = CharacterSet(charactersIn: "\r")
}
