import Foundation

enum RenderMode: String, CaseIterable, Identifiable, Codable {
    case videoGrowthLapse = "Video-Zeitraffer"
    case photoMorphing = "Foto-Morphing"

    var id: String { rawValue }
}

enum OutputFormat: String, CaseIterable, Identifiable, Codable {
    case landscape1920x1080 = "Landscape 1920x1080 SDR"
    case portrait1080x1920 = "Portrait 1080x1920"
    case keepOriginal = "Originalformat beibehalten"

    var id: String { rawValue }
}

enum SortMode: String, CaseIterable, Identifiable, Codable {
    case filenameAscending = "Dateiname aufsteigend"
    case ageWeeksAscending = "Alter in Wochen aufsteigend"
    case modificationDateAscending = "Änderungsdatum aufsteigend"

    var id: String { rawValue }
}

enum DuplicateClipSelection: String, CaseIterable, Identifiable, Codable {
    case first = "Variante 1"
    case second = "Variante 2"
    case thirdIfAvailable = "Variante 3 wenn verfügbar"
    case longest = "Längste Variante"

    var id: String { rawValue }
}

enum VideoEncoder: String, CaseIterable, Identifiable, Codable {
    case x264 = "CPU: libx264"
    case videoToolbox = "Hardware: VideoToolbox H.264"

    var id: String { rawValue }
}

enum StabilizationMethod: String, CaseIterable, Identifiable, Codable {
    case vidstab = "VidStab (empfohlen)"
    case deshake = "Deshake (Fallback)"

    var id: String { rawValue }
}

enum StabilizationStrength: String, CaseIterable, Identifiable, Codable {
    case light = "Leicht"
    case medium = "Mittel"
    case strong = "Stark"
    case veryStrong = "Sehr stark"

    var id: String { rawValue }

    var shakiness: Int {
        switch self {
        case .light: return 4
        case .medium: return 5
        case .strong: return 8
        case .veryStrong: return 10
        }
    }

    var accuracy: Int {
        switch self {
        case .light: return 10
        case .medium, .strong, .veryStrong: return 15
        }
    }

    var smoothing: Int {
        switch self {
        case .light: return 6
        case .medium: return 10
        case .strong: return 16
        case .veryStrong: return 28
        }
    }

    var maxShift: Int {
        switch self {
        case .light: return 24
        case .medium: return 48
        case .strong: return 96
        case .veryStrong: return 160
        }
    }

    var maxAngle: Double {
        switch self {
        case .light: return 0.03
        case .medium: return 0.06
        case .strong: return 0.12
        case .veryStrong: return 0.20
        }
    }

    var deshakeSearch: Int {
        switch self {
        case .light: return 48
        case .medium: return 64
        case .strong: return 96
        case .veryStrong: return 128
        }
    }
}

struct FFmpegFilterAvailability: Equatable {
    let hasVidstabDetect: Bool
    let hasVidstabTransform: Bool
    let hasDeshake: Bool
    let hasDrawtext: Bool

    var hasVidstab: Bool {
        hasVidstabDetect && hasVidstabTransform
    }
}

struct RenderSettings: Equatable {
    var inputFolder: URL?
    var outputFile: URL?
    var ffmpegExecutable: URL?
    var ffprobeExecutable: URL?
    var renderMode: RenderMode = .videoGrowthLapse
    var segmentLength: Double = 20
    var targetClipLength: Double = 3
    var transitionLength: Double = 0.5
    var fps: Int = 30
    var outputFormat: OutputFormat = .landscape1920x1080
    var sortMode: SortMode = .filenameAscending
    var duplicateClipSelection: DuplicateClipSelection = .first
    var removeAudio: Bool = true
    var takeMiddleSegment: Bool = true
    var startOffset: Double = 0
    var keepIntermediateClips: Bool = false
    var keepRenderCacheForReview: Bool = true
    var burnInClipNumbers: Bool = false
    var videoEncoder: VideoEncoder = .videoToolbox
    var crf: Int = 20
    var hardwareBitrateMbps: Int = 12
    var stabilizationEnabled: Bool = false
    var stabilizationMethod: StabilizationMethod = .vidstab
    var stabilizationStrength: StabilizationStrength = .medium
    var stabilizationAnchorEnabled: Bool = false
    var stabilizationAnchorX: Double = 0.5
    var stabilizationAnchorY: Double = 0.5
    var stabilizationAnchorSize: Double = 0.35
    var bestSegmentDetectionEnabled: Bool = false
    var faceNormalizationEnabled: Bool = false
    var targetFaceHeightRatio: Double = 0.28
}

struct PersistedRenderSettings: Codable {
    var inputFolderPath: String?
    var outputFilePath: String?
    var ffmpegExecutablePath: String?
    var ffprobeExecutablePath: String?
    var renderMode: RenderMode?
    var segmentLength: Double
    var targetClipLength: Double
    var transitionLength: Double
    var fps: Int
    var outputFormat: OutputFormat
    var sortMode: SortMode
    var duplicateClipSelection: DuplicateClipSelection?
    var removeAudio: Bool
    var takeMiddleSegment: Bool
    var startOffset: Double
    var keepIntermediateClips: Bool
    var keepRenderCacheForReview: Bool?
    var burnInClipNumbers: Bool?
    var videoEncoder: VideoEncoder
    var crf: Int
    var hardwareBitrateMbps: Int
    var stabilizationEnabled: Bool
    var stabilizationMethod: StabilizationMethod
    var stabilizationStrength: StabilizationStrength
    var stabilizationAnchorEnabled: Bool
    var stabilizationAnchorX: Double
    var stabilizationAnchorY: Double
    var stabilizationAnchorSize: Double
    var bestSegmentDetectionEnabled: Bool?
    var faceNormalizationEnabled: Bool
    var targetFaceHeightRatio: Double

    init(settings: RenderSettings) {
        inputFolderPath = settings.inputFolder?.path
        outputFilePath = settings.outputFile?.path
        ffmpegExecutablePath = settings.ffmpegExecutable?.path
        ffprobeExecutablePath = settings.ffprobeExecutable?.path
        renderMode = settings.renderMode
        segmentLength = settings.segmentLength
        targetClipLength = settings.targetClipLength
        transitionLength = settings.transitionLength
        fps = settings.fps
        outputFormat = settings.outputFormat
        sortMode = settings.sortMode
        duplicateClipSelection = settings.duplicateClipSelection
        removeAudio = settings.removeAudio
        takeMiddleSegment = settings.takeMiddleSegment
        startOffset = settings.startOffset
        keepIntermediateClips = settings.keepIntermediateClips
        keepRenderCacheForReview = settings.keepRenderCacheForReview
        burnInClipNumbers = settings.burnInClipNumbers
        videoEncoder = settings.videoEncoder
        crf = settings.crf
        hardwareBitrateMbps = settings.hardwareBitrateMbps
        stabilizationEnabled = settings.stabilizationEnabled
        stabilizationMethod = settings.stabilizationMethod
        stabilizationStrength = settings.stabilizationStrength
        stabilizationAnchorEnabled = settings.stabilizationAnchorEnabled
        stabilizationAnchorX = settings.stabilizationAnchorX
        stabilizationAnchorY = settings.stabilizationAnchorY
        stabilizationAnchorSize = settings.stabilizationAnchorSize
        bestSegmentDetectionEnabled = settings.bestSegmentDetectionEnabled
        faceNormalizationEnabled = settings.faceNormalizationEnabled
        targetFaceHeightRatio = settings.targetFaceHeightRatio
    }

    var renderSettings: RenderSettings {
        RenderSettings(
            inputFolder: inputFolderPath.map { URL(fileURLWithPath: $0) },
            outputFile: outputFilePath.map { URL(fileURLWithPath: $0) },
            ffmpegExecutable: ffmpegExecutablePath.map { URL(fileURLWithPath: $0) },
            ffprobeExecutable: ffprobeExecutablePath.map { URL(fileURLWithPath: $0) },
            renderMode: renderMode ?? .videoGrowthLapse,
            segmentLength: segmentLength,
            targetClipLength: targetClipLength,
            transitionLength: transitionLength,
            fps: fps,
            outputFormat: outputFormat,
            sortMode: sortMode,
            duplicateClipSelection: duplicateClipSelection ?? .first,
            removeAudio: removeAudio,
            takeMiddleSegment: takeMiddleSegment,
            startOffset: startOffset,
            keepIntermediateClips: keepIntermediateClips,
            keepRenderCacheForReview: keepRenderCacheForReview ?? true,
            burnInClipNumbers: burnInClipNumbers ?? false,
            videoEncoder: videoEncoder,
            crf: crf,
            hardwareBitrateMbps: hardwareBitrateMbps,
            stabilizationEnabled: stabilizationEnabled,
            stabilizationMethod: stabilizationMethod,
            stabilizationStrength: stabilizationStrength,
            stabilizationAnchorEnabled: stabilizationAnchorEnabled,
            stabilizationAnchorX: stabilizationAnchorX,
            stabilizationAnchorY: stabilizationAnchorY,
            stabilizationAnchorSize: stabilizationAnchorSize,
            bestSegmentDetectionEnabled: bestSegmentDetectionEnabled ?? false,
            faceNormalizationEnabled: faceNormalizationEnabled,
            targetFaceHeightRatio: targetFaceHeightRatio
        )
    }
}

enum SettingsStore {
    static func load() -> RenderSettings {
        let url = settingsURL()
        guard let data = try? Data(contentsOf: url),
              let persisted = try? JSONDecoder().decode(PersistedRenderSettings.self, from: data) else {
            return RenderSettings()
        }
        return persisted.renderSettings
    }

    static func save(_ settings: RenderSettings) {
        let url = settingsURL()
        do {
            try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
            let data = try JSONEncoder.prettyPrinted.encode(PersistedRenderSettings(settings: settings))
            try data.write(to: url, options: [.atomic])
        } catch {
            // Settings persistence must not block rendering or UI changes.
        }
    }

    static func reset() -> RenderSettings {
        let defaults = RenderSettings()
        save(defaults)
        return defaults
    }

    private static func settingsURL() -> URL {
        let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
            ?? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/Application Support")
        return base.appendingPathComponent("GrowthLapse", isDirectory: true).appendingPathComponent("settings.json")
    }
}

private extension JSONEncoder {
    static var prettyPrinted: JSONEncoder {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        return encoder
    }
}

struct VideoFile: Identifiable {
    let id = UUID()
    let url: URL
    let duration: Double
    let width: Int
    let height: Int
    let variants: [GrowthLapseClipVariant]

    init(url: URL, duration: Double, width: Int, height: Int, variants: [GrowthLapseClipVariant] = []) {
        self.url = url
        self.duration = duration
        self.width = width
        self.height = height
        self.variants = variants
    }

    var displayName: String {
        url.lastPathComponent
    }

    func withVariants(_ variants: [GrowthLapseClipVariant]) -> VideoFile {
        VideoFile(url: url, duration: duration, width: width, height: height, variants: variants)
    }
}

struct GrowthLapseProject: Codable, Equatable {
    var outputFilePath: String
    var cacheDirectoryPath: String
    var targetWidth: Int
    var targetHeight: Int
    var clips: [GrowthLapseProjectClip]

    var outputFileURL: URL {
        URL(fileURLWithPath: outputFilePath)
    }

    var cacheDirectoryURL: URL {
        URL(fileURLWithPath: cacheDirectoryPath, isDirectory: true)
    }

    static func url(for outputFile: URL) -> URL {
        outputFile.deletingPathExtension().appendingPathExtension("growthlapse-project.json")
    }

    static func cacheURL(for outputFile: URL) -> URL {
        outputFile.deletingPathExtension().appendingPathExtension("growthlapse-cache")
    }
}

struct GrowthLapseProjectClip: Identifiable, Codable, Equatable {
    var id: UUID = UUID()
    var index: Int
    var sourcePath: String
    var displayName: String
    var duration: Double
    var width: Int
    var height: Int
    var ageWeeks: Int?
    var segmentStart: Double
    var segmentLength: Double
    var normalizedClipPath: String
    var needsRender: Bool = false
    var variants: [GrowthLapseClipVariant]?

    var sourceURL: URL {
        URL(fileURLWithPath: sourcePath)
    }

    var normalizedClipURL: URL {
        URL(fileURLWithPath: normalizedClipPath)
    }

    var availableVariants: [GrowthLapseClipVariant] {
        variants ?? []
    }
}

struct GrowthLapseClipVariant: Identifiable, Codable, Equatable {
    var id: UUID = UUID()
    var sourcePath: String
    var displayName: String
    var duration: Double
    var width: Int
    var height: Int
    var ageWeeks: Int?

    var sourceURL: URL {
        URL(fileURLWithPath: sourcePath)
    }
}

struct VideoDimensions: Equatable {
    let width: Int
    let height: Int
}

struct FaceCrop: Equatable {
    let x: Int
    let y: Int
    let width: Int
    let height: Int
    let faceHeightRatio: Double
}

enum RenderState: Equatable {
    case idle
    case running
    case finished(URL)
    case failed(String)
    case cancelled

    var isRunning: Bool {
        if case .running = self {
            return true
        }
        return false
    }
}
