import SwiftUI

/// Animated OpenClaw mascot. Redraws the canonical 120x120 vector from
/// `ui/public/favicon.svg` so individual parts (claws, antennae, eyes) can
/// animate like the openclaw.ai hero mark; the bundled PNG asset cannot.
/// Styling (palette, glow colors, float depth) follows the openclaw.ai hero
/// (`src/pages/index.astro` + `Layout.astro` theme variables).
public struct OpenClawMascotView: View {
    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    @Environment(\.colorScheme) private var colorScheme

    public init() {}

    public var body: some View {
        let palette = OpenClawMascotPalette.forScheme(self.colorScheme)
        if self.reduceMotion {
            OpenClawMascotCanvas(pose: .still, palette: palette)
        } else {
            TimelineView(.animation(minimumInterval: 1.0 / 30.0)) { timeline in
                let pose = OpenClawMascotPose.at(time: timeline.date.timeIntervalSinceReferenceDate)
                // Float translates the whole canvas like the site floats the hero
                // container; drawing the offset inside the canvas would clip the
                // antennae (art starts at y~5 of 120) at the -9.6 float peak.
                GeometryReader { proxy in
                    OpenClawMascotCanvas(pose: pose, palette: palette)
                        .offset(y: pose.floatOffset * min(proxy.size.width, proxy.size.height) / 120)
                }
            }
        }
    }

    /// openclaw.ai hero drop-shadow color (`--logo-glow` / `--logo-glow-hover`).
    /// Pair with a shadow radius of ~10% of the mascot size (15% while hovering)
    /// to match the site's `drop-shadow(0 0 20px)` on a 100px mark.
    public static func heroGlowColor(for colorScheme: ColorScheme, hovering: Bool = false) -> Color {
        switch (colorScheme, hovering) {
        case (.light, false): Color(red: 239 / 255, green: 75 / 255, blue: 88 / 255).opacity(0.2)
        case (.light, true): Color(red: 0, green: 143 / 255, blue: 135 / 255).opacity(0.35)
        case (_, false): Color(red: 1, green: 77 / 255, blue: 77 / 255).opacity(0.4)
        case (_, true): Color(red: 0, green: 229 / 255, blue: 204 / 255).opacity(0.6)
        }
    }
}

/// Body/antenna colors from the openclaw.ai theme variables: `:root` (dark)
/// and `html[data-theme='light']` in `Layout.astro`. Eye colors are fixed in
/// the site markup and shared by both themes.
struct OpenClawMascotPalette: Equatable {
    let gradientTop: Color
    let gradientBottom: Color
    let antenna: Color

    static let dark = OpenClawMascotPalette(
        gradientTop: Color(red: 1, green: 77 / 255, blue: 77 / 255),
        gradientBottom: Color(red: 153 / 255, green: 27 / 255, blue: 27 / 255),
        antenna: Color(red: 1, green: 77 / 255, blue: 77 / 255))

    static let light = OpenClawMascotPalette(
        gradientTop: Color(red: 255 / 255, green: 112 / 255, blue: 121 / 255),
        gradientBottom: Color(red: 234 / 255, green: 76 / 255, blue: 89 / 255),
        antenna: Color(red: 239 / 255, green: 75 / 255, blue: 88 / 255))

    static func forScheme(_ colorScheme: ColorScheme) -> OpenClawMascotPalette {
        colorScheme == .light ? .light : .dark
    }
}

/// Part transforms for one animation frame. Mirrors the openclaw.ai CSS
/// keyframes: float 4s, antenna wiggle 2s, eye blink 3s, claw snap 4s with
/// the right claw trailing by 0.2s.
struct OpenClawMascotPose: Equatable {
    var floatOffset: CGFloat = 0
    var antennaDegrees: CGFloat = 0
    var leftClawDegrees: CGFloat = 0
    var rightClawDegrees: CGFloat = 0
    var eyeGlowOpacity: CGFloat = 1

    static let still = OpenClawMascotPose()

    static func at(time: TimeInterval) -> OpenClawMascotPose {
        // Float depth matches the hero: -8px on a 100px mark = 8% of the 120 box.
        OpenClawMascotPose(
            floatOffset: -4.8 * (1 - cos(2 * .pi * self.cyclePhase(time, period: 4))),
            antennaDegrees: -3 * sin(2 * .pi * self.cyclePhase(time, period: 2)),
            leftClawDegrees: self.clawSnapDegrees(phase: self.cyclePhase(time, period: 4)),
            rightClawDegrees: self.clawSnapDegrees(phase: self.cyclePhase(time - 0.2, period: 4)),
            eyeGlowOpacity: self.blinkOpacity(phase: self.cyclePhase(time, period: 3)))
    }

    private static func cyclePhase(_ time: TimeInterval, period: TimeInterval) -> CGFloat {
        let normalized = (time / period).truncatingRemainder(dividingBy: 1)
        return CGFloat(normalized < 0 ? normalized + 1 : normalized)
    }

    private static func clawSnapDegrees(phase: CGFloat) -> CGFloat {
        // 0deg until 85%, snap to -8deg at 90%, back to 0deg at 95%, hold.
        if phase < 0.85 || phase >= 0.95 {
            return 0
        }
        if phase < 0.9 {
            return -8 * self.easeInOut((phase - 0.85) / 0.05)
        }
        return -8 * (1 - self.easeInOut((phase - 0.9) / 0.05))
    }

    private static func blinkOpacity(phase: CGFloat) -> CGFloat {
        // Full glow until 90%, dip to 0.3 at 95%, recover by 100%.
        if phase < 0.9 {
            return 1
        }
        let dip = phase < 0.95 ? self.easeInOut((phase - 0.9) / 0.05) : 1 - self.easeInOut((phase - 0.95) / 0.05)
        return 1 - 0.7 * dip
    }

    private static func easeInOut(_ t: CGFloat) -> CGFloat {
        let clamped = min(max(t, 0), 1)
        return clamped * clamped * (3 - 2 * clamped)
    }
}

private struct OpenClawMascotCanvas: View {
    let pose: OpenClawMascotPose
    let palette: OpenClawMascotPalette

    var body: some View {
        Canvas { context, size in
            Self.draw(context: &context, size: size, pose: self.pose, palette: self.palette)
        }
        .accessibilityHidden(true)
    }

    // Geometry below is the favicon.svg path data in its native 120x120 space.
    private static let eyeColor = Color(red: 5 / 255, green: 8 / 255, blue: 16 / 255)
    private static let eyeGlowColor = Color(red: 0, green: 229 / 255, blue: 204 / 255)
    // Rotation pivots: claws hinge on their body-facing edge, antennae on their own center.
    private static let leftClawPivot = CGPoint(x: 26, y: 53)
    private static let rightClawPivot = CGPoint(x: 94, y: 53)
    private static let leftAntennaPivot = CGPoint(x: 37.5, y: 11)
    private static let rightAntennaPivot = CGPoint(x: 82.5, y: 11)

    private static let bodyPath: Path = {
        var path = Path()
        path.move(to: CGPoint(x: 60, y: 10))
        path.addCurve(to: CGPoint(x: 15, y: 55), control1: CGPoint(x: 30, y: 10), control2: CGPoint(x: 15, y: 35))
        path.addCurve(to: CGPoint(x: 45, y: 100), control1: CGPoint(x: 15, y: 75), control2: CGPoint(x: 30, y: 95))
        path.addLine(to: CGPoint(x: 45, y: 110))
        path.addLine(to: CGPoint(x: 55, y: 110))
        path.addLine(to: CGPoint(x: 55, y: 100))
        path.addCurve(to: CGPoint(x: 65, y: 100), control1: CGPoint(x: 55, y: 100), control2: CGPoint(x: 60, y: 102))
        path.addLine(to: CGPoint(x: 65, y: 110))
        path.addLine(to: CGPoint(x: 75, y: 110))
        path.addLine(to: CGPoint(x: 75, y: 100))
        path.addCurve(to: CGPoint(x: 105, y: 55), control1: CGPoint(x: 90, y: 95), control2: CGPoint(x: 105, y: 75))
        path.addCurve(to: CGPoint(x: 60, y: 10), control1: CGPoint(x: 105, y: 35), control2: CGPoint(x: 90, y: 10))
        path.closeSubpath()
        return path
    }()

    private static let leftClawPath: Path = {
        var path = Path()
        path.move(to: CGPoint(x: 20, y: 45))
        path.addCurve(to: CGPoint(x: 5, y: 60), control1: CGPoint(x: 5, y: 40), control2: CGPoint(x: 0, y: 50))
        path.addCurve(to: CGPoint(x: 25, y: 55), control1: CGPoint(x: 10, y: 70), control2: CGPoint(x: 20, y: 65))
        path.addCurve(to: CGPoint(x: 20, y: 45), control1: CGPoint(x: 28, y: 48), control2: CGPoint(x: 25, y: 45))
        path.closeSubpath()
        return path
    }()

    private static let rightClawPath: Path = {
        var path = Path()
        path.move(to: CGPoint(x: 100, y: 45))
        path.addCurve(to: CGPoint(x: 115, y: 60), control1: CGPoint(x: 115, y: 40), control2: CGPoint(x: 120, y: 50))
        path.addCurve(to: CGPoint(x: 95, y: 55), control1: CGPoint(x: 110, y: 70), control2: CGPoint(x: 100, y: 65))
        path.addCurve(to: CGPoint(x: 100, y: 45), control1: CGPoint(x: 92, y: 48), control2: CGPoint(x: 95, y: 45))
        path.closeSubpath()
        return path
    }()

    private static let leftAntennaPath: Path = {
        var path = Path()
        path.move(to: CGPoint(x: 45, y: 15))
        path.addQuadCurve(to: CGPoint(x: 30, y: 8), control: CGPoint(x: 35, y: 5))
        return path
    }()

    private static let rightAntennaPath: Path = {
        var path = Path()
        path.move(to: CGPoint(x: 75, y: 15))
        path.addQuadCurve(to: CGPoint(x: 90, y: 8), control: CGPoint(x: 85, y: 5))
        return path
    }()

    private static func draw(
        context: inout GraphicsContext,
        size: CGSize,
        pose: OpenClawMascotPose,
        palette: OpenClawMascotPalette)
    {
        let scale = min(size.width, size.height) / 120
        context.scaleBy(x: scale, y: scale)

        // Site antennae: stroke-width 2, `--coral-bright`.
        let antennaStroke = StrokeStyle(lineWidth: 2, lineCap: .round)

        // Same paint order as favicon.svg: body, claws, antennae, eyes.
        context.fill(self.bodyPath, with: self.gradient(for: self.bodyPath, palette: palette))
        self.drawRotated(context: context, degrees: pose.leftClawDegrees, pivot: self.leftClawPivot) {
            $0.fill(self.leftClawPath, with: self.gradient(for: self.leftClawPath, palette: palette))
        }
        self.drawRotated(context: context, degrees: pose.rightClawDegrees, pivot: self.rightClawPivot) {
            $0.fill(self.rightClawPath, with: self.gradient(for: self.rightClawPath, palette: palette))
        }
        self.drawRotated(context: context, degrees: pose.antennaDegrees, pivot: self.leftAntennaPivot) {
            $0.stroke(self.leftAntennaPath, with: .color(palette.antenna), style: antennaStroke)
        }
        self.drawRotated(context: context, degrees: pose.antennaDegrees, pivot: self.rightAntennaPivot) {
            $0.stroke(self.rightAntennaPath, with: .color(palette.antenna), style: antennaStroke)
        }

        context.fill(Path(ellipseIn: CGRect(x: 39, y: 29, width: 12, height: 12)), with: .color(self.eyeColor))
        context.fill(Path(ellipseIn: CGRect(x: 69, y: 29, width: 12, height: 12)), with: .color(self.eyeColor))
        var glowContext = context
        glowContext.opacity = pose.eyeGlowOpacity
        glowContext.fill(
            Path(ellipseIn: CGRect(x: 44, y: 32, width: 4, height: 4)),
            with: .color(self.eyeGlowColor))
        glowContext.fill(
            Path(ellipseIn: CGRect(x: 74, y: 32, width: 4, height: 4)),
            with: .color(self.eyeGlowColor))
    }

    /// SVG gradients default to objectBoundingBox units, so the body and each
    /// claw span the full top-left -> bottom-right ramp across their own bounds;
    /// one canvas-wide gradient would leave the claws nearly flat-colored.
    private static func gradient(
        for path: Path,
        palette: OpenClawMascotPalette) -> GraphicsContext.Shading
    {
        let box = path.boundingRect
        return .linearGradient(
            Gradient(colors: [palette.gradientTop, palette.gradientBottom]),
            startPoint: box.origin,
            endPoint: CGPoint(x: box.maxX, y: box.maxY))
    }

    private static func drawRotated(
        context: GraphicsContext,
        degrees: CGFloat,
        pivot: CGPoint,
        draw: (inout GraphicsContext) -> Void)
    {
        var rotated = context
        rotated.translateBy(x: pivot.x, y: pivot.y)
        rotated.rotate(by: .degrees(degrees))
        rotated.translateBy(x: -pivot.x, y: -pivot.y)
        draw(&rotated)
    }
}
