module TangentialGradient
using WGLMakie, Bonito, Observables, StaticArrays, Colors
Page() # for Franklin, you still need to configure
WGLMakie.activate!()

dist2d(x, y) = sqrt(sum(((_x, _y),) -> (_y - _x)^2, zip(x, y)))

function tangential_gradient()

    app = App() do session::Session
        fig = Figure(size=(400, 400), fontsize=20)
        # fig = Figure(size=(400, 400), fontsize=20, backgroundcolor=:transparent)
        xmin = ymin = 0.0
        xmax = ymax = 2.0
        ax = Axis(
            fig[1, 1],
            limits=(xmin, xmax, ymin, ymax),
            leftspinevisible=false,
            rightspinevisible=false,
            bottomspinevisible=false,
            topspinevisible=false,
            aspect=1,
            # backgroundcolor=:transparent
        )
        hidedecorations!(ax)
        deregister_interaction!(ax, :rectanglezoom)
        deregister_interaction!(ax, :dragpan)
        deregister_interaction!(ax, :scrollzoom)
        deregister_interaction!(ax, :limitreset)


        # Settings
        C = Point2f(0, 0)
        R = 1.0
        R_control = R / 4.0

        # Control zone
        center = Point2f(0.0, 0.0)
        arc_points = [Point2f(R_control * cos(θ), R_control * sin(θ)) for θ in LinRange(0, π / 2, 50)]
        poly!(ax, [center, arc_points..., center], color=colorant"rgb(243,243,243)")

        # dbg
        dbg = Observable("dbg")
        # text!(0.3, 0.3, text=dbg, align=(:center, :bottom))

        # Redefine mouseposition
        visible_xymin = Observable(true)
        visible_xymax = Observable(true)
        a_x = b_x = 0.0
        a_y = b_y = 0.0
        x1 = x2 = 0.0
        y1 = y2 = 0.0
        ms = 30
        c = :red
        scatter!([xmin], [ymin], visible=visible_xymin, markersize=ms, color=c)
        scatter!([xmax], [ymax], visible=visible_xymax, markersize=ms, color=c)
        function mymouseposition(ax)
            x, y = mouseposition(ax)
            return a_x * x + b_x, a_y * y + b_y
        end

        # Circle arc
        c = colorant"#1154a6"
        arc!(ax, C, R, 0, π / 2, linewidth=2, color=c)
        # arc!(C, R, 0, π / 2, linewidth=2, color=:blue)

        # Legend for Gamma
        text!(0.1, 0.25, text=L"\Gamma \coloneq \{x \, | \, d(x) = 0 \}")
        text!(0.1, 0.1, text=L"d(x) = |x| - R")

        # Normal vectors
        lν = 0.5
        θ_ν = Observable(deg2rad(10.0))
        points_ν = @lift([Point2f(R * cos($θ_ν), R * sin($θ_ν))])
        directions_ν = @lift([Point2f(lν * cos($θ_ν), lν * sin($θ_ν))])
        xytext_ν = @lift($points_ν[1] + $directions_ν[1])
        color_ν = :red
        text!(xytext_ν, text=L"\nu", color=color_ν, offset=(5, 0))
        arrows2d!(points_ν, directions_ν; color=color_ν)

        # Gradient
        ∇_visible = Observable(false)
        lr = Observable(0.75)
        lθ = Observable(0.75)
        θ_∇ = θ_ν
        points_∇ = @lift([Point2f(R * cos($θ_∇), R * sin($θ_∇))])
        directions_∇ = @lift([Point2f($lr * cos($θ_∇) - $lθ * sin($θ_∇), $lr * sin($θ_∇) + $lθ * cos($θ_∇))])
        xytext_∇ = @lift($points_∇[1] + $directions_∇[1])
        c = :darkorange
        text!(xytext_∇, text=L"\nabla u", color=c, offset=(5, 0), visible=∇_visible)
        arrows2d!(points_∇, directions_∇; color=c, visible=∇_visible)

        # Tangential gradient
        ∇Γ_visible = Observable(false)
        c = :green
        directions_∇Γ = @lift([Point2f(-$lθ * sin($θ_∇), $lθ * cos($θ_∇))])
        xytext_∇Γ = @lift($points_∇[1] + $directions_∇Γ[1])
        text!(xytext_∇Γ, text=L"\nabla_\Gamma u", color=c, offset=(-5, 2), align=(:right, :bottom), visible=∇Γ_visible)
        arrows2d!(points_∇, directions_∇Γ; color=c, visible=∇Γ_visible)

        # Projection legend
        points_proj = @lift([$xytext_∇Γ, $xytext_∇])
        xytext_proj = @lift(0.5($xytext_∇Γ + $xytext_∇))
        u = @lift($points_proj[2] - $points_proj[1])
        θ_proj = @lift(acos($u.data[1] / sqrt(($u.data[1])^2 + ($u.data[2])^2)))
        text!(xytext_proj, text=L"(\nabla u \cdot \nu) \nu", color=color_ν, offset=(-10, 5), align=(:center, :bottom), rotation=θ_proj, visible=∇Γ_visible)
        lines!(points_proj, linestyle=:dash, color=color_ν, visible=∇Γ_visible)

        # Event listeners
        move_ν = false
        move_∇_head = false
        on(events(ax).mousebutton) do event
            if event.button == Mouse.left && event.action == Mouse.press

                if visible_xymin[]
                    x1, y1 = mouseposition(ax)
                    visible_xymin[] = false
                    dbg[] = string(mouseposition(ax))
                    return
                elseif visible_xymax[]
                    x2, y2 = mouseposition(ax)
                    a_x = (xmax - xmin) / (x2 - x1)
                    b_x = xmax - a_x * x2
                    a_y = (ymax - ymin) / (y2 - y1)
                    b_y = ymax - a_y * y2
                    visible_xymax[] = false
                    dbg[] = string(mouseposition(ax))
                    return
                else
                    dbg[] = string(mymouseposition(ax))
                end


                x = mymouseposition(ax)

                # Better with a keyboard listener but quarto is capturing
                # the keyboard...
                if dist2d(x, (0, 0)) < R / 2
                    if !∇_visible[]
                        ∇_visible[] = true
                    elseif !∇Γ_visible[]
                        ∇Γ_visible[] = true
                    else
                        # reset
                        ∇_visible[] = false
                        ∇Γ_visible[] = false
                    end
                    return
                end

                dist_to_ν_head = dist2d(x, xytext_ν[])
                dist_to_∇_head = dist2d(x, xytext_∇[])
                i = argmin((dist_to_ν_head, dist_to_∇_head))
                (i == 1) && (move_ν = true)
                (i == 2) && (move_∇_head = true)
            else
                move_ν = false
                move_∇_head = false
            end
        end
        on(events(ax).mouseposition) do mp
            x, y = mymouseposition(ax)
            if move_ν
                θ_ν[] = atan(y, x)
            elseif move_∇_head
                xfoot = SA[R*cos(θ_∇[]), R*sin(θ_∇[])]
                u = SA[x, y] - xfoot
                lr[] = u[1] * cos(θ_∇[]) + u[2] * sin(θ_∇[])
                lθ[] = -u[1] * sin(θ_∇[]) + u[2] * cos(θ_∇[])
            end
        end

        # keyboard listener is OK but quarto is capturing the event...
        # on(events(ax).keyboardbutton) do event
        #     @show event
        #     ((event.key == Keyboard.a) && (event.action == Keyboard.release)) || return
        #     if !∇_visible[]
        #         ∇_visible[] = true
        #     elseif !∇Γ_visible[]
        #         ∇Γ_visible[] = true
        #     end
        # end

        # Add everything to the DOM
        # return Bonito.record_states(session, DOM.div(fig))
        return DOM.div(fig)

    end
    return app
end

if haskey(ENV, "JULIA_EDITOR") && (ENV["JULIA_EDITOR"] == "code")
    @warn "VSCode display (not in slide)"
    display(tangential_gradient())
end
end