//
//  BrushCanvasView.swift
//  CheapRetouch
//
//  Canvas overlay for brush-based manual selection.
//

import SwiftUI
import UIKit

struct BrushCanvasView: View {
    @Bindable var viewModel: EditorViewModel
    let imageSize: CGSize
    let displayedImageFrame: CGRect

    @State private var currentStroke: [CGPoint] = []
    @State private var allStrokes: [[CGPoint]] = []
    @State private var currentTouchLocation: CGPoint?
    @State private var gradientImage: EdgeRefinement.GradientImage?

    var body: some View {
        Canvas { context, size in
            // Draw all completed strokes
            for stroke in allStrokes {
                drawStroke(stroke, in: &context, color: .white)
            }

            // Draw current stroke
            if !currentStroke.isEmpty {
                drawStroke(currentStroke, in: &context, color: .white)
            }

            // Draw brush preview circle at current touch location
            if let location = currentTouchLocation {
                let previewRect = CGRect(
                    x: location.x - viewModel.brushSize / 2,
                    y: location.y - viewModel.brushSize / 2,
                    width: viewModel.brushSize,
                    height: viewModel.brushSize
                )
                context.stroke(
                    Path(ellipseIn: previewRect),
                    with: .color(.white.opacity(0.8)),
                    lineWidth: 2
                )
            }
        }
        .gesture(drawingGesture)
        .onAppear {
            // Precompute gradient for edge refinement if enabled
            if viewModel.useEdgeRefinement, let image = viewModel.displayImage {
                computeGradientAsync(from: image)
            }
        }
        .onChange(of: viewModel.useEdgeRefinement) { _, newValue in
            if newValue, gradientImage == nil, let image = viewModel.displayImage {
                computeGradientAsync(from: image)
            }
        }
        .onChange(of: viewModel.editedImage) { _, _ in
            // Recompute gradient when image changes (e.g., after undo/redo)
            // to ensure edge refinement works correctly with the current image
            if viewModel.useEdgeRefinement, let image = viewModel.displayImage {
                computeGradientAsync(from: image)
            } else {
                // Invalidate cached gradient so it will be recomputed when edge refinement is enabled
                gradientImage = nil
            }
        }
        .onChange(of: allStrokes.count) { _, newCount in
            viewModel.brushStrokesCount = newCount
        }
        .onChange(of: viewModel.triggerClearBrushStrokes) { _, shouldClear in
            if shouldClear {
                allStrokes.removeAll()
                currentStroke.removeAll()
                viewModel.triggerClearBrushStrokes = false
            }
        }
        .onChange(of: viewModel.triggerApplyBrushMask) { _, shouldApply in
            if shouldApply {
                Task {
                    await applyBrushMask()
                }
                viewModel.triggerApplyBrushMask = false
            }
        }
    }

    private func computeGradientAsync(from image: CGImage) {
        Task {
            let gradient = await Task.detached(priority: .userInitiated) {
                EdgeRefinement.computeGradient(from: image)
            }.value
            gradientImage = gradient
        }
    }

    private func drawStroke(_ points: [CGPoint], in context: inout GraphicsContext, color: Color) {
        guard points.count >= 2 else { return }

        var path = Path()
        path.move(to: points[0])

        if points.count == 2 {
            path.addLine(to: points[1])
        } else {
            for i in 1..<points.count {
                let mid = CGPoint(
                    x: (points[i-1].x + points[i].x) / 2,
                    y: (points[i-1].y + points[i].y) / 2
                )
                path.addQuadCurve(to: mid, control: points[i-1])
            }
            path.addLine(to: points[points.count - 1])
        }

        context.stroke(
            path,
            with: .color(color.opacity(0.7)),
            style: StrokeStyle(
                lineWidth: viewModel.brushSize,
                lineCap: .round,
                lineJoin: .round
            )
        )
    }

    private var drawingGesture: some Gesture {
        DragGesture(minimumDistance: 0)
            .onChanged { value in
                let point = value.location
                currentTouchLocation = point

                // Only add points within the image bounds
                if displayedImageFrame.contains(point) {
                    currentStroke.append(point)
                }
            }
            .onEnded { _ in
                currentTouchLocation = nil
                if !currentStroke.isEmpty {
                    allStrokes.append(currentStroke)
                    currentStroke = []
                }
            }
    }

    private func applyBrushMask() async {
        DebugLogger.action("BrushCanvasView.applyBrushMask called")
        
        // Check if we have strokes to apply or a pending mask to refine
        let hasStrokes = !allStrokes.isEmpty
        let hasPendingMask = viewModel.pendingRefineMask != nil
        
        guard hasStrokes || hasPendingMask else {
            DebugLogger.log("No strokes or pending mask to apply")
            return
        }
        
        // Get the actual display image dimensions - this may differ from originalImage
        // after undo/redo due to preview scaling in renderPreview
        guard let displayImage = viewModel.displayImage else {
            DebugLogger.error("No display image available")
            return
        }
        let actualImageSize = CGSize(width: displayImage.width, height: displayImage.height)

        DebugLogger.log("Stroke count: \(allStrokes.count), total points: \(allStrokes.reduce(0) { $0 + $1.count })")
        DebugLogger.log("Original image size: \(imageSize)")
        DebugLogger.log("Actual display image size: \(actualImageSize)")
        DebugLogger.log("Displayed frame: \(displayedImageFrame)")
        DebugLogger.log("Has pending mask: \(hasPendingMask)")

        // Use actual display image size for scaling, not the original image size
        let scaleX = actualImageSize.width / displayedImageFrame.width
        let scaleY = actualImageSize.height / displayedImageFrame.height
        DebugLogger.log("Scale factors: X=\(scaleX), Y=\(scaleY)")

        // Convert all strokes to image coordinates (using actual display image dimensions)
        var imageCoordStrokes: [[CGPoint]] = []
        for stroke in allStrokes {
            let imageStroke = stroke.map { point in
                CGPoint(
                    x: (point.x - displayedImageFrame.minX) * scaleX,
                    y: (point.y - displayedImageFrame.minY) * scaleY
                )
            }
            imageCoordStrokes.append(imageStroke)
        }

        // Apply edge refinement if enabled and gradient is available
        if viewModel.useEdgeRefinement, let gradient = gradientImage {
            imageCoordStrokes = imageCoordStrokes.map { stroke in
                EdgeRefinement.refineSelectionToEdges(
                    selection: stroke,
                    gradient: gradient,
                    searchRadius: Int(viewModel.brushSize / 2)
                )
            }
        }

        // Create mask image from strokes at the ACTUAL display image size
        // (not the original image size, which may differ after undo/redo)
        let format = UIGraphicsImageRendererFormat()
        format.scale = 1.0  // Don't use screen scale, use actual pixel size
        let renderer = UIGraphicsImageRenderer(size: actualImageSize, format: format)
        let maskImage = renderer.image { ctx in
            // Fill with black (not masked)
            UIColor.black.setFill()
            ctx.fill(CGRect(origin: .zero, size: actualImageSize))
            
            // If we have a pending mask from Person/Object detection, draw it first
            // Note: pendingMask might be at original size, so we draw it scaled
            if let pendingMask = viewModel.pendingRefineMask {
                // Draw the pending mask - white areas become part of the combined mask
                ctx.cgContext.draw(pendingMask, in: CGRect(origin: .zero, size: actualImageSize))
            }

            // Draw brush strokes in white (masked areas) on top
            UIColor.white.setStroke()

            for stroke in imageCoordStrokes {
                guard stroke.count >= 2 else { continue }

                let path = UIBezierPath()
                path.move(to: stroke[0])

                for i in 1..<stroke.count {
                    path.addLine(to: stroke[i])
                }

                path.lineWidth = viewModel.brushSize * scaleX
                path.lineCapStyle = .round
                path.lineJoinStyle = .round
                path.stroke()
            }
        }

        if let cgImage = maskImage.cgImage {
            DebugLogger.imageInfo("Created brush mask", image: cgImage)
            // Clear the pending mask since we've incorporated it
            viewModel.pendingRefineMask = nil
            viewModel.maskPreview = nil
            await viewModel.applyBrushMask(cgImage)
        } else {
            DebugLogger.error("Failed to create CGImage from brush mask")
        }

        // Clear strokes after applying
        allStrokes.removeAll()
        DebugLogger.log("Strokes cleared")
    }
}

#Preview {
    let viewModel = EditorViewModel()
    return BrushCanvasView(
        viewModel: viewModel,
        imageSize: CGSize(width: 1000, height: 1000),
        displayedImageFrame: CGRect(x: 0, y: 0, width: 300, height: 300)
    )
}
