//
//  EditorViewModel.swift
//  CheapRetouch
//
//  View model coordinating edit operations between services and UI.
//

import SwiftUI
import UIKit
import CoreGraphics
import Observation

// MARK: - Accessibility Helpers

private func announceForVoiceOver(_ message: String) {
    UIAccessibility.post(notification: .announcement, argument: message)
}

@Observable
@MainActor
final class EditorViewModel {

    // MARK: - State

    var originalImage: CGImage?
    var editedImage: CGImage?
    var maskPreview: CGImage?
    var selectedTool: EditTool = .person
    var brushSize: CGFloat = 20
    var featherAmount: CGFloat = 4

    // Brush tool state
    var brushStrokesCount = 0
    var triggerClearBrushStrokes = false
    var triggerApplyBrushMask = false

    var isProcessing = false
    var processingMessage = ""
    var errorMessage: String?
    var showingMaskConfirmation = false
    var detectedPeopleCount = 0
    var showSelectAllPeople = false
    var useHighContrastMask = false
    var useEdgeRefinement = true
    var pendingRefineMask: CGImage?
    var isLowConfidenceMask = false
    var triggerResetZoom = false

    private(set) var project: Project?

    // MARK: - Services

    private let maskingService = MaskingService()
    private let inpaintEngine = InpaintEngine()
    private let imagePipeline = ImagePipeline()

    // MARK: - Computed Properties

    var displayImage: CGImage? {
        editedImage ?? originalImage
    }

    var canUndo: Bool {
        project?.canUndo ?? false
    }

    var canRedo: Bool {
        project?.canRedo ?? false
    }

    var imageSize: CGSize {
        guard let image = originalImage else { return .zero }
        return CGSize(width: image.width, height: image.height)
    }

    // MARK: - Image Loading

    func loadImage(_ uiImage: UIImage, localIdentifier: String? = nil) {
        DebugLogger.action("loadImage called")
        
        // Normalize image orientation - fixes photos that appear rotated
        let normalizedImage = normalizeImageOrientation(uiImage)
        
        guard let cgImage = normalizedImage.cgImage else {
            DebugLogger.error("Failed to get CGImage from UIImage")
            return
        }
        DebugLogger.imageInfo("Loading image", image: cgImage)

        originalImage = cgImage
        editedImage = nil
        maskPreview = nil
        errorMessage = nil
        pendingRefineMask = nil
        showSelectAllPeople = false
        detectedPeopleCount = 0
        triggerResetZoom.toggle()

        // Check image size and warn if large
        let pixelCount = cgImage.width * cgImage.height
        if pixelCount > 48_000_000 {
            errorMessage = "Very large image (48MP+). May cause memory issues."
        } else if pixelCount > 12_000_000 {
            // Just a note, not an error - processing continues
        }

        // Create new project with appropriate image source
        let imageSource: Project.ImageSource
        if let identifier = localIdentifier {
            imageSource = .photoLibrary(localIdentifier: identifier)
        } else {
            let imageData = normalizedImage.jpegData(compressionQuality: 0.9) ?? Data()
            imageSource = .embedded(data: imageData)
        }
        project = Project(imageSource: imageSource)

        announceForVoiceOver("Photo loaded")
    }
    
    /// Normalizes image orientation by redrawing with correct transform
    private func normalizeImageOrientation(_ image: UIImage) -> UIImage {
        // If orientation is already up, no need to redraw
        guard image.imageOrientation != .up else { return image }
        
        // Redraw image with correct orientation applied
        UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
        image.draw(in: CGRect(origin: .zero, size: image.size))
        let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return normalizedImage ?? image
    }

    // MARK: - Tap Handling

    func handleTap(at point: CGPoint) async {
        DebugLogger.action("handleTap at point: \(point), tool: \(selectedTool)")
        guard let image = displayImage else {
            DebugLogger.error("handleTap: No display image available")
            return
        }
        DebugLogger.imageInfo("Current display image", image: image)

        isProcessing = true
        errorMessage = nil

        do {
            switch selectedTool {
            case .person:
                processingMessage = "Detecting person..."
                DebugLogger.processing("Starting person detection")
                try await handlePersonTap(at: point, in: image)

            case .object:
                processingMessage = "Detecting object..."
                DebugLogger.processing("Starting object detection")
                try await handleObjectTap(at: point, in: image)

            case .brush:
                DebugLogger.log("Brush tool - tap ignored (handled by canvas)")
                break

            case .move:
                DebugLogger.log("Move tool - tap ignored")
                break
            }
        } catch {
            DebugLogger.error("handleTap failed", error: error)
            errorMessage = error.localizedDescription
        }

        isProcessing = false
        processingMessage = ""
    }

    private func handlePersonTap(at point: CGPoint, in image: CGImage) async throws {
        DebugLogger.log("handlePersonTap at \(point)")
        let result = try await maskingService.generatePersonMaskWithCount(at: point, in: image)

        detectedPeopleCount = result.instanceCount
        DebugLogger.state("Detected \(result.instanceCount) people, confidence: \(result.confidence)")

        guard let mask = result.mask else {
            DebugLogger.log("No person found at tap location")
            errorMessage = "No person found at tap location"
            return
        }

        DebugLogger.imageInfo("Person mask before dilation", image: mask)

        // Dilate mask by 3px to capture edge pixels
        let dilatedMask = dilateMask(mask, by: 3)
        DebugLogger.imageInfo("Person mask after dilation", image: dilatedMask)

        maskPreview = dilatedMask ?? mask
        showingMaskConfirmation = true
        showSelectAllPeople = result.instanceCount > 1

        // Check for low confidence and flag for user warning
        isLowConfidenceMask = result.confidence < 0.7
        DebugLogger.state("Mask preview set, lowConfidence: \(isLowConfidenceMask)")
    }

    func selectAllPeople() async {
        guard let image = displayImage else { return }

        isProcessing = true
        processingMessage = "Selecting all people..."

        do {
            let mask = try await maskingService.generateAllForegroundMasks(in: image)

            guard let mask = mask else {
                errorMessage = "No people found in image"
                isProcessing = false
                processingMessage = ""
                return
            }

            // Dilate mask
            let dilatedMask = dilateMask(mask, by: 3)
            maskPreview = dilatedMask ?? mask
            showingMaskConfirmation = true
            showSelectAllPeople = false
        } catch {
            errorMessage = error.localizedDescription
        }

        isProcessing = false
        processingMessage = ""
    }

    private func dilateMask(_ mask: CGImage, by pixels: Int) -> CGImage? {
        let width = mask.width
        let height = mask.height

        guard let context = CGContext(
            data: nil,
            width: width,
            height: height,
            bitsPerComponent: 8,
            bytesPerRow: width,
            space: CGColorSpaceCreateDeviceGray(),
            bitmapInfo: CGImageAlphaInfo.none.rawValue
        ) else { return nil }

        // Draw original mask
        context.draw(mask, in: CGRect(x: 0, y: 0, width: width, height: height))

        // Apply dilation by drawing the mask multiple times with offsets
        context.setBlendMode(.lighten)
        for dx in -pixels...pixels {
            for dy in -pixels...pixels {
                if dx * dx + dy * dy <= pixels * pixels {
                    context.draw(mask, in: CGRect(x: dx, y: dy, width: width, height: height))
                }
            }
        }

        return context.makeImage()
    }

    private func handleObjectTap(at point: CGPoint, in image: CGImage) async throws {
        DebugLogger.log("handleObjectTap at \(point)")
        DebugLogger.log("Calling maskingService.generateForegroundMask...")

        let mask = try await maskingService.generateForegroundMask(at: point, in: image)

        DebugLogger.log("generateForegroundMask returned")
        DebugLogger.imageInfo("Object mask", image: mask)

        guard let mask = mask else {
            DebugLogger.log("No mask returned - prompting for brush tool")
            errorMessage = "Couldn't detect object. Try the brush tool to select manually."
            return
        }

        maskPreview = mask
        showingMaskConfirmation = true
        DebugLogger.state("Object mask preview set, showing confirmation")
    }

    // MARK: - Mask Confirmation

    func confirmMask() async {
        DebugLogger.action("confirmMask called")
        guard let image = displayImage, let mask = maskPreview else {
            DebugLogger.error("confirmMask: Missing image or mask")
            return
        }

        DebugLogger.imageInfo("Source image for inpaint", image: image)
        DebugLogger.imageInfo("Mask for inpaint", image: mask)

        isProcessing = true
        processingMessage = "Removing..."

        do {
            DebugLogger.processing("Starting inpaint...")
            let result = try await inpaintEngine.inpaint(image: image, mask: mask)
            DebugLogger.imageInfo("Inpaint result", image: result)
            editedImage = result

            // Add operation to project
            if var project = project {
                if let maskDataObj = MaskData(from: mask) {
                    let maskOp = MaskOperation(
                        toolType: toolTypeForCurrentTool(),
                        maskData: maskDataObj.data,
                        maskWidth: maskDataObj.width,
                        maskHeight: maskDataObj.height
                    )
                    let inpaintOp = InpaintOperation(maskOperationId: maskOp.id, featherAmount: Float(featherAmount))

                    project.addOperation(.mask(maskOp))
                    project.addOperation(.inpaint(inpaintOp))
                    self.project = project
                }
            }

            announceForVoiceOver("Removal complete")
        } catch {
            errorMessage = error.localizedDescription
        }

        maskPreview = nil
        showingMaskConfirmation = false
        showSelectAllPeople = false
        isLowConfidenceMask = false
        isProcessing = false
        processingMessage = ""
    }

    func cancelMask() {
        maskPreview = nil
        showingMaskConfirmation = false
        showSelectAllPeople = false
        pendingRefineMask = nil
        isLowConfidenceMask = false
    }

    func refineWithBrush() {
        // Save current mask for refinement and switch to brush tool
        // Keep maskPreview visible so user can see what they're refining
        pendingRefineMask = maskPreview
        showingMaskConfirmation = false
        showSelectAllPeople = false
        selectedTool = .brush
        announceForVoiceOver("Switched to brush tool for mask refinement")
    }

    // MARK: - Undo/Redo

    func undo() async {
        guard var project = project, project.undo() else { return }
        self.project = project
        await rebuildEditedImage()
        announceForVoiceOver("Undo complete")
    }

    func redo() async {
        guard var project = project, project.redo() else { return }
        self.project = project
        await rebuildEditedImage()
        announceForVoiceOver("Redo complete")
    }

    private func rebuildEditedImage() async {
        guard let original = originalImage, let project = project else { return }

        isProcessing = true
        processingMessage = "Rebuilding..."

        do {
            let result = try await imagePipeline.renderPreview(
                originalImage: original,
                operations: project.activeOperations
            )
            editedImage = result
        } catch {
            errorMessage = error.localizedDescription
        }

        isProcessing = false
        processingMessage = ""
    }

    // MARK: - Brush Tool

    func applyBrushMask(_ mask: CGImage) async {
        DebugLogger.action("applyBrushMask called")
        DebugLogger.imageInfo("Brush mask", image: mask)
        maskPreview = mask
        await confirmMask()
    }

    // MARK: - Helpers

    private func toolTypeForCurrentTool() -> ToolType {
        switch selectedTool {
        case .person: return .person
        case .object: return .object
        case .brush: return .brush
        case .move: return .move
        }
    }
}
