import Foundation
import CoreImage

// MARK: - LUT Parser for .cube files
// Supports both 1D and 3D LUT formats

/// LUT parsing errors
public enum LUTParserError: Error {
    case fileNotFound
    case invalidFormat
    case unsupportedSize
    case malformedData
    case invalidColorValues

    var localizedDescription: String {
        switch self {
        case .fileNotFound: return "LUT file not found"
        case .invalidFormat: return "Invalid .cube file format"
        case .unsupportedSize: return "Unsupported LUT size"
        case .malformedData: return "Malformed LUT data"
        case .invalidColorValues: return "Invalid color values in LUT"
        }
    }
}

/// LUT type
public enum LUTType {
    case lut1D(size: Int)
    case lut3D(size: Int)
}

/// Parsed LUT data structure
public struct LUTData {
    public let title: String?
    public let type: LUTType
    public let domainMin: SIMD3<Float>
    public let domainMax: SIMD3<Float>
    public let data: [Float]

    /// LUT size (number of entries per dimension)
    public var size: Int {
        switch type {
        case .lut1D(let size), .lut3D(let size):
            return size
        }
    }

    /// Total number of data points
    public var dataCount: Int {
        switch type {
        case .lut1D(let size):
            return size * 3  // R, G, B per entry
        case .lut3D(let size):
            return size * size * size * 3  // R, G, B per cube entry
        }
    }
}

/// Parser for .cube LUT files
public class LUTParser {

    // MARK: - Public Methods

    /// Parse a .cube file from file path
    /// - Parameter path: File path to .cube file
    /// - Returns: Parsed LUT data
    /// - Throws: LUTParserError if parsing fails
    public static func parse(fileURL: URL) throws -> LUTData {
        guard FileManager.default.fileExists(atPath: fileURL.path) else {
            throw LUTParserError.fileNotFound
        }

        let content = try String(contentsOf: fileURL, encoding: .utf8)
        return try parse(content: content)
    }

    /// Parse a .cube file from string content
    /// - Parameter content: String content of .cube file
    /// - Returns: Parsed LUT data
    /// - Throws: LUTParserError if parsing fails
    public static func parse(content: String) throws -> LUTData {
        let lines = content.components(separatedBy: .newlines)
            .map { $0.trimmingCharacters(in: .whitespaces) }
            .filter { !$0.isEmpty && !$0.hasPrefix("#") }  // Remove comments and empty lines

        var title: String?
        var domainMin = SIMD3<Float>(0, 0, 0)
        var domainMax = SIMD3<Float>(1, 1, 1)
        var lut1DSize: Int?
        var lut3DSize: Int?
        var colorData: [Float] = []

        // Parse header and data
        for line in lines {
            if line.hasPrefix("TITLE") {
                title = parseTitle(line)
            } else if line.hasPrefix("DOMAIN_MIN") {
                domainMin = try parseDomain(line)
            } else if line.hasPrefix("DOMAIN_MAX") {
                domainMax = try parseDomain(line)
            } else if line.hasPrefix("LUT_1D_SIZE") {
                lut1DSize = try parseSize(line)
            } else if line.hasPrefix("LUT_3D_SIZE") {
                lut3DSize = try parseSize(line)
            } else {
                // Try to parse as color data
                if let rgbValues = try? parseColorLine(line) {
                    colorData.append(contentsOf: rgbValues)
                }
            }
        }

        // Validate and determine LUT type
        let lutType: LUTType
        if let size3D = lut3DSize {
            lutType = .lut3D(size: size3D)
        } else if let size1D = lut1DSize {
            lutType = .lut1D(size: size1D)
        } else {
            // Try to infer from data size
            let dataPoints = colorData.count / 3
            if let inferredSize = inferLUTSize(dataPoints: dataPoints) {
                lutType = inferredSize
            } else {
                throw LUTParserError.invalidFormat
            }
        }

        // Validate data size
        let expectedDataCount: Int
        switch lutType {
        case .lut1D(let size):
            expectedDataCount = size * 3
        case .lut3D(let size):
            expectedDataCount = size * size * size * 3
        }

        guard colorData.count == expectedDataCount else {
            throw LUTParserError.malformedData
        }

        // Validate color values are in valid range
        guard colorData.allSatisfy({ $0 >= 0.0 && $0 <= 1.0 }) else {
            throw LUTParserError.invalidColorValues
        }

        return LUTData(
            title: title,
            type: lutType,
            domainMin: domainMin,
            domainMax: domainMax,
            data: colorData
        )
    }

    // MARK: - Private Parsing Methods

    private static func parseTitle(_ line: String) -> String? {
        let components = line.components(separatedBy: "\"")
        return components.count >= 2 ? components[1] : nil
    }

    private static func parseDomain(_ line: String) throws -> SIMD3<Float> {
        let components = line.components(separatedBy: .whitespaces)
            .filter { !$0.isEmpty }

        guard components.count == 4,
              let r = Float(components[1]),
              let g = Float(components[2]),
              let b = Float(components[3]) else {
            throw LUTParserError.invalidFormat
        }

        return SIMD3<Float>(r, g, b)
    }

    private static func parseSize(_ line: String) throws -> Int {
        let components = line.components(separatedBy: .whitespaces)
            .filter { !$0.isEmpty }

        guard components.count == 2,
              let size = Int(components[1]),
              size > 0 && size <= 256 else {  // Reasonable size limits
            throw LUTParserError.unsupportedSize
        }

        return size
    }

    private static func parseColorLine(_ line: String) throws -> [Float] {
        let components = line.components(separatedBy: .whitespaces)
            .filter { !$0.isEmpty }

        guard components.count == 3,
              let r = Float(components[0]),
              let g = Float(components[1]),
              let b = Float(components[2]) else {
            throw LUTParserError.invalidFormat
        }

        return [r, g, b]
    }

    private static func inferLUTSize(dataPoints: Int) -> LUTType? {
        // Check if it's a valid 3D LUT (cube root is integer)
        let cubeRoot = Double(dataPoints).rounded(.toNearestOrAwayFromZero).cbrt
        let size3D = Int(cubeRoot)
        if size3D * size3D * size3D == dataPoints {
            return .lut3D(size: size3D)
        }

        // Otherwise assume 1D LUT
        return .lut1D(size: dataPoints)
    }
}

// MARK: - Helper Extensions

private extension Double {
    var cbrt: Double {
        return pow(self, 1.0/3.0)
    }
}
