import Foundation
import UIKit

// MARK: - Sheet Music View (iOS)

/// Custom UIView that renders sheet music notation and handles measure selection via touch.
/// UIKit port of the macOS NSView version.
/// Coordinate system: UIView uses top-left origin by default (same as macOS with isFlipped=true).
class SheetMusicView_iOS: UIView {

    // MARK: - Properties

    var song: Song? {
        didSet {
            recalculateLayout()
            setNeedsDisplay()
        }
    }

    var selection: Selection = .none {
        didSet { setNeedsDisplay() }
    }

    var playbackMeasure: Int = -1 {
        didSet { setNeedsDisplay() }
    }

    var playbackBeatFraction: Double = 0.0 {
        didSet { setNeedsDisplay() }
    }

    /// Callback when selection changes
    var onSelectionChanged: ((Selection) -> Void)?

    // MARK: - Layout

    private(set) var layout: SheetMusicLayout = SheetMusicLayout(
        systems: [], measureFrames: [], totalSize: .zero, measuresPerSystem: 0
    )

    // MARK: - Renderers

    private let staffRenderer = StaffRenderer_iOS()
    private let noteRenderer = NoteRenderer_iOS()
    private let selectionRenderer = SelectionRenderer_iOS()

    // MARK: - Touch tracking

    private var isDragging = false
    private var dragAnchorMeasure: Int?

    // MARK: - Initialization

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }

    private func setupView() {
        backgroundColor = .white
        isMultipleTouchEnabled = false
        contentMode = .redraw
    }

    // MARK: - Layout

    func recalculateLayout() {
        guard let song = song else {
            layout = SheetMusicLayout(systems: [], measureFrames: [], totalSize: .zero, measuresPerSystem: 0)
            return
        }

        let viewWidth = max(bounds.width, 320)

        layout = LayoutCalculator.calculateLayout(
            song: song,
            viewWidth: viewWidth
        )

        // Update intrinsic content size for the scroll view
        let newSize = CGSize(width: viewWidth, height: layout.totalSize.height)
        if frame.size != newSize {
            frame.size = newSize
        }
        invalidateIntrinsicContentSize()
    }

    override var intrinsicContentSize: CGSize {
        return layout.totalSize
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        // Recalculate if width changed (device rotation, etc.)
        if let song = song {
            let currentWidth = max(bounds.width, 320)
            if abs(currentWidth - layout.totalSize.width) > 1 {
                recalculateLayout()
            }
        }
    }

    // MARK: - Drawing

    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        guard let song = song else {
            drawEmptyState(in: context, rect: bounds)
            return
        }

        // Background
        context.setFillColor(UIColor.white.cgColor)
        context.fill(bounds)

        // Draw selection highlight (behind everything)
        selectionRenderer.drawSelection(selection, layout: layout, in: context)

        // Draw each system
        for system in layout.systems {
            drawSystem(system, song: song, in: context)
        }

        // Draw playback position
        if playbackMeasure >= 0 {
            selectionRenderer.drawPlaybackPosition(
                measure: playbackMeasure,
                beatFraction: playbackBeatFraction,
                layout: layout,
                in: context
            )
        }
    }

    private func drawSystem(_ system: StaffSystem, song: Song, in context: CGContext) {
        let L = AppConstants.Layout.self

        for (partIndex, part) in song.parts.enumerated() {
            let staffY = system.staffY(forPartIndex: partIndex)

            let systemStartX = system.measures.first?.rect.minX ?? L.leftMargin
            let systemEndX = system.measures.last?.rect.maxX ?? bounds.width

            staffRenderer.drawStaffLines(
                in: context,
                x: systemStartX - L.leftMargin,
                y: staffY,
                width: systemEndX - systemStartX + L.leftMargin
            )

            if partIndex < song.parts.count {
                let useBassClef = partIndex >= song.parts.count - 1 && song.parts.count >= 4
                if useBassClef {
                    staffRenderer.drawBassClef(in: context, x: systemStartX - L.leftMargin + 4, y: staffY)
                } else {
                    staffRenderer.drawTrebleClef(in: context, x: systemStartX - L.leftMargin + 4, y: staffY)
                }
            }

            if system.systemIndex == 0 {
                var sigX = systemStartX - L.leftMargin + 36
                sigX = staffRenderer.drawKeySignature(
                    in: context,
                    keySignature: song.keySignature,
                    x: sigX,
                    y: staffY
                )
                staffRenderer.drawTimeSignature(
                    in: context,
                    timeSignature: song.timeSignature,
                    x: sigX + 4,
                    y: staffY
                )
            }

            let partColor = PartColors.color(forIndex: part.colorIndex)

            for measureFrame in system.measures {
                let measureIdx = measureFrame.measureIndex
                guard measureIdx < part.measures.count else { continue }

                let measure = part.measures[measureIdx]
                drawMeasureNotes(
                    measure,
                    in: measureFrame.rect,
                    staffY: staffY,
                    color: partColor,
                    context: context
                )

                staffRenderer.drawBarLine(
                    in: context,
                    x: measureFrame.rect.maxX,
                    y: staffY
                )
            }

            if let lastFrame = system.measures.last,
               lastFrame.measureIndex == song.totalMeasures - 1 {
                staffRenderer.drawDoubleBarLine(
                    in: context,
                    x: lastFrame.rect.maxX,
                    y: staffY
                )
            }
        }
    }

    private func drawMeasureNotes(
        _ measure: Measure,
        in rect: CGRect,
        staffY: CGFloat,
        color: UIColor,
        context: CGContext
    ) {
        let notes = measure.notes
        guard !notes.isEmpty else { return }

        let totalBeats = notes.reduce(0.0) { $0 + $1.beats }
        guard totalBeats > 0 else { return }

        let padding: CGFloat = 8.0
        let availableWidth = rect.width - padding * 2
        var currentX = rect.minX + padding

        for note in notes {
            let noteWidth = CGFloat(note.beats / totalBeats) * availableWidth
            let noteX = currentX + noteWidth * 0.3

            noteRenderer.drawNote(
                note,
                at: noteX,
                staffTopY: staffY,
                color: color,
                in: context
            )

            if let pitch = note.pitch {
                let noteY = noteRenderer.noteY(for: pitch, staffTopY: staffY)
                staffRenderer.drawLedgerLines(
                    in: context,
                    noteY: noteY,
                    staffTopY: staffY,
                    noteX: noteX
                )
            }

            currentX += noteWidth
        }
    }

    private func drawEmptyState(in context: CGContext, rect: CGRect) {
        context.setFillColor(UIColor.systemGroupedBackground.cgColor)
        context.fill(rect)

        let text = "Import a song file to begin" as NSString
        let attrs: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 18),
            .foregroundColor: UIColor.secondaryLabel
        ]
        let size = text.size(withAttributes: attrs)
        let point = CGPoint(
            x: (rect.width - size.width) / 2,
            y: (rect.height - size.height) / 2
        )
        text.draw(at: point, withAttributes: attrs)
    }

    // MARK: - Touch handling for selection

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        let point = touch.location(in: self)

        guard let measureIndex = layout.measureIndex(at: point) else {
            selection.clear()
            onSelectionChanged?(selection)
            setNeedsDisplay()
            return
        }

        selection.click(measure: measureIndex)
        isDragging = true
        dragAnchorMeasure = measureIndex

        onSelectionChanged?(selection)
        setNeedsDisplay()
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard isDragging, let anchor = dragAnchorMeasure, let touch = touches.first else { return }
        let point = touch.location(in: self)

        if let measureIndex = layout.measureIndex(at: point) {
            if measureIndex != anchor {
                selection.anchor = anchor
                selection.cursor = measureIndex
            } else {
                selection.click(measure: anchor)
            }
            onSelectionChanged?(selection)
            setNeedsDisplay()
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        isDragging = false
        dragAnchorMeasure = nil
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        isDragging = false
        dragAnchorMeasure = nil
    }
}
