import Foundation
#if canImport(AppKit)
import AppKit

// MARK: - Note Renderer

/// Renders individual notes on the staff: noteheads, stems, flags, dots, accidentals, rests.
struct NoteRenderer {
    let L = AppConstants.Layout.self

    // MARK: - Staff position calculation

    /// Convert a pitch to a Y position on the staff.
    /// Staff top line = F5 in treble clef, middle line (3rd) = B4.
    /// Each staff position (line or space) is half a staffLineSpacing apart.
    func noteY(for pitch: Pitch, staffTopY: CGFloat) -> CGFloat {
        // In treble clef, the top line is F5 (staffPosition from middle C = 10)
        // Each step down is +halfSpacing in Y
        let topLinePosition = 10  // F5 staff position from middle C
        let notePosition = pitch.staffPositionFromMiddleC
        let halfSpacing = L.staffLineSpacing / 2.0
        return staffTopY + CGFloat(topLinePosition - notePosition) * halfSpacing
    }

    // MARK: - Note drawing

    /// Draw a complete note (notehead + stem + flags + dot + accidental)
    func drawNote(
        _ note: Note,
        at x: CGFloat,
        staffTopY: CGFloat,
        color: NSColor,
        in context: CGContext
    ) {
        if note.isRest {
            drawRest(note.duration, at: x, staffTopY: staffTopY, color: color, in: context)
            return
        }

        guard let pitch = note.pitch else { return }

        let y = noteY(for: pitch, staffTopY: staffTopY)

        // Draw accidental if needed
        // (For now, always draw explicit accidentals — key signature filtering happens at a higher level)
        drawAccidental(pitch.accidental, at: x - L.accidentalOffset, y: y, color: color, in: context)

        // Draw notehead
        drawNotehead(filled: note.duration.isFilledNotehead, at: x, y: y, color: color, in: context)

        // Draw stem
        if note.duration.hasStem {
            let stemUp = pitch.staffPositionFromMiddleC < 7  // Below B4 = stem up
            drawStem(at: x, y: y, stemUp: stemUp, color: color, in: context)

            // Draw flags (if not beamed — beaming handled separately)
            if note.duration.flagCount > 0 {
                drawFlags(count: note.duration.flagCount, at: x, y: y, stemUp: stemUp, color: color, in: context)
            }
        }

        // Draw dot
        if note.duration.isDotted {
            drawDot(at: x + L.noteHeadWidth + L.dotOffset, y: y, color: color, in: context)
        }
    }

    // MARK: - Component drawing

    /// Draw a notehead (filled or open)
    func drawNotehead(filled: Bool, at x: CGFloat, y: CGFloat, color: NSColor, in context: CGContext) {
        let rect = CGRect(
            x: x,
            y: y - L.noteHeadHeight / 2,
            width: L.noteHeadWidth,
            height: L.noteHeadHeight
        )

        // Slightly tilted ellipse for note heads
        context.saveGState()
        context.translateBy(x: rect.midX, y: rect.midY)
        context.rotate(by: -0.2)  // slight tilt
        let centeredRect = CGRect(
            x: -L.noteHeadWidth / 2,
            y: -L.noteHeadHeight / 2,
            width: L.noteHeadWidth,
            height: L.noteHeadHeight
        )

        if filled {
            context.setFillColor(color.cgColor)
            context.fillEllipse(in: centeredRect)
        } else {
            context.setStrokeColor(color.cgColor)
            context.setLineWidth(1.5)
            context.strokeEllipse(in: centeredRect)
        }

        context.restoreGState()
    }

    /// Draw a stem
    func drawStem(at x: CGFloat, y: CGFloat, stemUp: Bool, color: NSColor, in context: CGContext) {
        context.setStrokeColor(color.cgColor)
        context.setLineWidth(L.stemWidth)

        let stemX = stemUp ? x + L.noteHeadWidth - 1 : x + 1
        let stemEndY = stemUp ? y - L.stemLength : y + L.stemLength

        context.move(to: CGPoint(x: stemX, y: y))
        context.addLine(to: CGPoint(x: stemX, y: stemEndY))
        context.strokePath()
    }

    /// Draw flags on a stem
    func drawFlags(count: Int, at x: CGFloat, y: CGFloat, stemUp: Bool, color: NSColor, in context: CGContext) {
        context.setStrokeColor(color.cgColor)
        context.setLineWidth(1.5)

        let stemX = stemUp ? x + L.noteHeadWidth - 1 : x + 1
        let stemEndY = stemUp ? y - L.stemLength : y + L.stemLength

        for i in 0..<count {
            let flagY = stemUp ? stemEndY + CGFloat(i) * 8 : stemEndY - CGFloat(i) * 8
            let flagEndX = stemX + (stemUp ? 10 : -10)
            let flagEndY = flagY + (stemUp ? 12 : -12)

            context.move(to: CGPoint(x: stemX, y: flagY))
            context.addQuadCurve(
                to: CGPoint(x: flagEndX, y: flagEndY),
                control: CGPoint(x: stemX + (stemUp ? 12 : -12), y: flagY + (stemUp ? 4 : -4))
            )
            context.strokePath()
        }
    }

    /// Draw a dot for dotted notes
    func drawDot(at x: CGFloat, y: CGFloat, color: NSColor, in context: CGContext) {
        context.setFillColor(color.cgColor)
        context.fillEllipse(in: CGRect(x: x, y: y - 1.5, width: 3, height: 3))
    }

    /// Draw an accidental symbol
    func drawAccidental(_ accidental: Accidental, at x: CGFloat, y: CGFloat, color: NSColor, in context: CGContext) {
        guard accidental != .natural else { return }

        let font = NSFont.systemFont(ofSize: 14)
        let attrs: [NSAttributedString.Key: Any] = [
            .font: font,
            .foregroundColor: color
        ]

        let symbol: String
        switch accidental {
        case .sharp:       symbol = "♯"
        case .flat:        symbol = "♭"
        case .natural:     symbol = "♮"
        case .doubleSharp: symbol = "𝄪"
        case .doubleFlat:  symbol = "𝄫"
        }

        (symbol as NSString).draw(at: CGPoint(x: x, y: y - 8), withAttributes: attrs)
    }

    // MARK: - Rest drawing

    /// Draw a rest symbol
    func drawRest(_ duration: Duration, at x: CGFloat, staffTopY: CGFloat, color: NSColor, in context: CGContext) {
        let font = NSFont.systemFont(ofSize: 20)
        let attrs: [NSAttributedString.Key: Any] = [
            .font: font,
            .foregroundColor: color
        ]

        let centerY = staffTopY + L.staffHeight / 2

        let symbol: String
        switch duration.baseValue {
        case 1:  symbol = "𝄻"  // whole rest
        case 2:  symbol = "𝄼"  // half rest
        case 4:  symbol = "𝄽"  // quarter rest
        case 8:  symbol = "𝄾"  // eighth rest
        case 16: symbol = "𝄿"  // sixteenth rest
        default: symbol = "𝄽"
        }

        (symbol as NSString).draw(at: CGPoint(x: x, y: centerY - 12), withAttributes: attrs)
    }
}

#endif
