import Foundation

/// Shared parser/composer for attachment preview fields.
///
/// Cross-platform canonical formats:
/// - image/avatar: `tag::blurhash` or `::blurhash` (placeholder before upload)
/// - file: `tag::size::name` or `size::name`
/// - legacy/local-only: raw preview payload without `tag::` prefix
enum AttachmentPreviewCodec {

    // Legacy iOS upload IDs were generated from [a-z0-9] with fixed length 8.
    // We intentionally keep this strict to avoid stripping arbitrary prefixes
    // from valid blurhash payloads that may contain "::".
    private static let legacyTransportIdRegex = try! NSRegularExpression(
        pattern: "^[a-z0-9]{8}$",
        options: []
    )

    struct ParsedFilePreview: Equatable {
        let downloadTag: String
        let fileSize: Int
        let fileName: String
        let payload: String
    }

    static func isDownloadTag(_ value: String) -> Bool {
        UUID(uuidString: value.trimmingCharacters(in: .whitespacesAndNewlines)) != nil
    }

    static func downloadTag(from preview: String) -> String {
        let firstPart = preview.components(separatedBy: "::").first ?? ""
        return isDownloadTag(firstPart) ? firstPart : ""
    }

    static func payload(from preview: String) -> String {
        let parts = preview.components(separatedBy: "::")
        guard parts.isEmpty == false else { return "" }

        if isDownloadTag(parts[0]) {
            return normalizePayload(parts.dropFirst().joined(separator: "::"))
        }

        // Placeholder preview before upload (`::blurhash` / `::size::name`).
        if parts[0].isEmpty {
            return normalizePayload(parts.dropFirst().joined(separator: "::"))
        }

        return normalizePayload(preview)
    }

    static func blurHash(from preview: String) -> String {
        let raw = payload(from: preview)

        // Legacy compatibility: older iOS builds could embed an 8-char upload
        // id in preview, e.g. "jbov1nac::blurhash". We strip only this known
        // prefix shape to avoid corrupting valid blurhash values that may
        // legitimately contain "::".
        var candidate = raw
        if let sep = raw.range(of: "::") {
            let prefix = String(raw[..<sep.lowerBound])
            let suffix = String(raw[sep.upperBound...])
            if isLegacyTransportId(prefix), !suffix.isEmpty {
                candidate = suffix
            }
        }

        // Strip trailing "|WxH" dimension suffix if present.
        // IMPORTANT: "|" is a valid base83 character in blurhash!
        // Only strip if the suffix actually matches a dimension pattern (digits "x" digits).
        if let pipeIdx = candidate.lastIndex(of: "|") {
            let suffix = candidate[candidate.index(after: pipeIdx)...]
            if let xIdx = suffix.firstIndex(of: "x"),
               suffix[suffix.startIndex..<xIdx].allSatisfy(\.isNumber),
               suffix[suffix.index(after: xIdx)...].allSatisfy(\.isNumber),
               suffix[suffix.startIndex..<xIdx].count > 0,
               suffix[suffix.index(after: xIdx)...].count > 0 {
                return String(candidate[candidate.startIndex..<pipeIdx])
            }
        }
        return candidate
    }

    /// Extract pixel dimensions encoded as `|WxH` suffix in image preview.
    /// Format: "tag::blurhash|WxH" — "|" separator avoids breaking desktop's `::` parsing.
    /// Returns nil if no dimensions found (legacy messages).
    static func imageDimensions(from preview: String) -> CGSize? {
        let raw = payload(from: preview)
        guard let pipeIdx = raw.lastIndex(of: "|") else { return nil }
        let dimStr = raw[raw.index(after: pipeIdx)...]
        guard let xIdx = dimStr.firstIndex(of: "x"),
              let w = Int(dimStr[dimStr.startIndex..<xIdx]),
              let h = Int(dimStr[dimStr.index(after: xIdx)...]),
              w >= 10, h >= 10 else { return nil }
        return CGSize(width: CGFloat(w), height: CGFloat(h))
    }

    static func parseFilePreview(
        _ preview: String,
        fallbackFileName: String = "file",
        fallbackFileSize: Int = 0
    ) -> ParsedFilePreview {
        let tag = downloadTag(from: preview)
        let normalizedPayload = payload(from: preview)
        let components = normalizedPayload.components(separatedBy: "::")

        var fileSize = max(fallbackFileSize, 0)
        var fileName = fallbackFileName

        if components.count >= 2, let parsedSize = Int(components[0]) {
            fileSize = max(parsedSize, 0)
            let joinedName = components.dropFirst().joined(separator: "::")
            if joinedName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
                fileName = joinedName
            }
        } else if components.count >= 2 {
            // Legacy payload without explicit size.
            let joinedName = components.joined(separator: "::")
            if joinedName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
                fileName = joinedName
            }
        } else if let onlyComponent = components.first,
                  onlyComponent.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
            fileName = onlyComponent
        }

        return ParsedFilePreview(
            downloadTag: tag,
            fileSize: fileSize,
            fileName: fileName,
            payload: normalizedPayload
        )
    }

    static func compose(downloadTag: String, payload: String) -> String {
        let tag = downloadTag.trimmingCharacters(in: .whitespacesAndNewlines)
        let normalizedPayload = normalizePayload(payload)
        if tag.isEmpty { return normalizedPayload }
        return "\(tag)::\(normalizedPayload)"
    }

    static func parseCallDurationSeconds(_ preview: String) -> Int {
        let normalized = payload(from: preview)
            .trimmingCharacters(in: .whitespacesAndNewlines)

        if let direct = Int(normalized) {
            return max(direct, 0)
        }

        let patterns = [
            #"duration(?:Sec|Seconds)?\s*[:=]\s*(\d+)"#,
            #"duration\s+(\d+)"#,
        ]

        for pattern in patterns {
            guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else {
                continue
            }
            let nsRange = NSRange(normalized.startIndex..<normalized.endIndex, in: normalized)
            guard let match = regex.firstMatch(in: normalized, options: [], range: nsRange),
                  match.numberOfRanges > 1,
                  let valueRange = Range(match.range(at: 1), in: normalized),
                  let value = Int(normalized[valueRange])
            else {
                continue
            }
            return max(value, 0)
        }

        return 0
    }

    private static func normalizePayload(_ payload: String) -> String {
        var value = payload.trimmingCharacters(in: .whitespacesAndNewlines)
        while value.hasPrefix("::") {
            value.removeFirst(2)
        }
        return value
    }

    private static func isLegacyTransportId(_ value: String) -> Bool {
        let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
        let range = NSRange(trimmed.startIndex..<trimmed.endIndex, in: trimmed)
        return legacyTransportIdRegex.firstMatch(in: trimmed, options: [], range: range) != nil
    }
}
