
module Display
import ..AbstractPlutoDingetjes
export published_to_js, ReactDOMElement


struct _PublishToJS
    x
end
function Base.show(io::IO, ::MIME"text/javascript", ptj::_PublishToJS)
    core_published_to_js = get(io, :pluto_published_to_js, nothing)
    @assert core_published_to_js !== nothing """
    `AbstractPlutoDingetjes.Display.published_to_js` is not supported by this `IO` display.

    If you are not using `published_to_js` (or you do not know what it is), or you are not using Pluto, then please report this error to the package that you are using.

    If you are trying to use `published_to_js` but it is not working, please make sure that:
    - Pluto is up to date.
    - The original IO context is used to render the widget.
    - If you want to support non-Pluto environments, you use `AbstractPlutoDingetjes.is_supported_by_display` for a fallback.
    
    See the documentation for `published_to_js` to learn more about these points.
    """

    core_published_to_js(io, ptj.x)
end
Base.show(io::IO, ::MIME"text/plain", ptj::_PublishToJS) = show(io, MIME"text/javascript"(), ptj)
Base.show(io::IO, ptj::_PublishToJS) = show(io, MIME"text/javascript"(), ptj)

"""
```julia
AbstractPlutoDingetjes.Display.published_to_js(x)
```

Make the object `x` available to the JS runtime of this cell, to be rendered
inside a `<script>` element. This system uses Pluto's optimized data transfer (probably with MsgPack and WebSocket),
which is much more efficient for large amounts of data, including lossless
transfer for `Vector{UInt8}` and `Vector{Float64}` (see the table below),
and a global cache to avoid transmitting the same object twice.

The function `published_to_js` returns a special object that behaves like a
piece of JavaScript code. We recommend using HypertextLiteral.jl to 
interpolate the result into a  `<script>` element.

# Example
```julia
import HypertextLiteral: @htl
import AbstractPlutoDingetjes.Display: published_to_js

let
    x = Dict(
        "data" => rand(Float64, 20),
        "name" => "juliette",
    )

    @htl("\""
    <script>
    // we interpolate into JavaScript:
    const x = \$(published_to_js(x))

    console.log(x.name, x.data)
    </script>
    "\"")
end
```

# Types

| Julia | JavaScript |
|:---------- |:---------- |
| `String`, `Symbol` | `string` |
| `Boolean` | `boolean` |
| `Int64`, `Int32`, `Int16`, `Int8`, `UInt64`, `UInt32`, `UInt16`, `UInt8`, `Float32`, `Float64` | `Number` |
| `Nothing`, `Missing` | `null` |
| `DateTime` | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) |
| `UUID`, `MIME` | `string` |
| --- | --- |
| `Dict` | `object` |
| `NamedTuple` | `object` |
| `Vector` | `Array` |
| `Tuple` | `Array` |
| `Vector{Int8}` | [`Int8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array) |
| `Vector{UInt8}` | [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) |
| `Vector{Int16}` | [`Int16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array) |
| `Vector{UInt16}` | [`Uint16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array) |
| `Vector{Int32}` | [`Int32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array) |
| `Vector{UInt32}` | [`Uint32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array) |
| `Vector{Float32}` | [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) |
| `Vector{Float64}` | [`Float64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array) |


# Note about IO context

The object that `published_to_js` returns needs to be rendered using the IO
context that Pluto uses to render cell output. If you are using
HypertextLiteral.jl, then this is easy to achieve. 

The example above is using `HypertextLiteral.@htl`, and the cell returns a
`HypertextLiteral` object, which will be rendered by Pluto. This means that
Pluto will render it using its magical IO context, and all is good!

## Custom show method

Below is a second example, to use when your are writing a **custom HTML show
method for your own type**:

```julia
struct MyType
    data
end

function Base.show(io::IO, m::MIME"text/html", x::MyType)

    # ✅ This works
    show(io, m, @htl("\""
    <script>
    let data = \$(published_to_js(x.data))
    console.log(data)
    </script>
    "\""))
end
```

Test it out with:

```julia
MyType([1,2,3])
```

The trick that makes it work is: `show(io, m, @htl(...))`. This will take your
`HypertextLiteral` object, and **render it using the `io` object that was passed
in**.

## Without HypertextLiteral.jl

The following would not work:

```julia
function Base.show(io::IO, m::MIME"text/html", x::MyType)

    # 🛑 This does not work
    println(io, "\""
    <script>
    let data = \$(published_to_js(x.data))
    console.log(data)
    </script>
    "\"")
end
```

This does not work, because the **string interpolation (i.e. `"\"" ...
\$(published_to_js(x.data)) ... "\""`) happens on its own**, without the `io`
context used to render it.

The solution is to use HypertextLiteral.jl, passing through the `io` in your
show method. If you can't use HypertextLiteral.jl, you could use `repr` to
manually render published object to a string, using `io` as the context:

```julia
function Base.show(io::IO, m::MIME"text/html", x::MyType)
    
    # 🟡 This works, but we recommend the HypertextLiteral.jl example instead.
    rendered = repr(published_to_js(x.data); context=io)

    println(io, "\""
    <script>
    let data = \$(rendered)
    console.log(data)
    </script>
    "\"")
end
```

# Note on published object caching

Whenever a Julia object is sent to Pluto using `published_to_js`, its value is
cached so that subsequent requests for the same object are served faster. This
means that Pluto already optimizes the performance of sending data from Julia to
Javascript and it is especially useful when the same object is rendered multiple
times within the notebook.

Also: If you use `published_to_js` twice on the same object within the
same cell, or in two different cells, the data is only transmitted once. The
second `published_to_js` just contains a reference to the same data.

It is important to note that mutating an object that has already been sent to
JavaScript with `published_to_js` will not change the value of this object on
the JavaScript side, even if the cells with the `published_to_js` calls are
re-run.


# Compatibility

## Old Pluto versions

!!! compat "Pluto 0.19.28"
    This feature only works in Pluto version 0.19.28 (July 2023) or above.

    Older versions of Pluto used `PlutoRunner.publish_to_js` for this (to avoid the need for the AbstractPlutoDingetjes package, but we changed our minds 💕).

    Use [`AbstractPlutoDingetjes.is_supported_by_display`](@ref) if you want to check support inside your widget:
    
    ```julia
    AbstractPlutoDingetjes.is_supported_by_display(io, published_to_js) ? 
        AbstractPlutoDingetjes.Display.published_to_js(x) : 
        PlutoRunner.publish_to_js(x)
    ```
    
    (You need a reference to `io` for this, so this is useful inside a custom `Base.show` method for your own struct type.)

## Outside of Pluto

This feature only works in Pluto-compatible environments (i.e. Pluto). Outside of Pluto, you might be happy with the default HypertextLiteral [script interpolation](https://juliapluto.github.io/HypertextLiteral.jl/stable/script/). This is less performant for large objects, and some Julia types are mapped to a different JavaScript type (e.g. `Vector{Int32}` is mapped to a simple `Array` instead of `Int32Array`), but it might be good enough for your use case.

In your interpolation, check for support, and otherwise, just interpolate the object directly:

```julia
AbstractPlutoDingetjes.is_supported_by_display(io, published_to_js) ? 
    AbstractPlutoDingetjes.Display.published_to_js(x) : 
    x
```

## Both

To support old versions of Pluto, and also support non-Pluto displays, you can combine the two:

```julia
AbstractPlutoDingetjes.is_supported_by_display(io, published_to_js) ? 
    # modern Pluto
    AbstractPlutoDingetjes.Display.published_to_js(x) : 
    isdefined(Main, :PlutoRunner) && isdefined(Main.PlutoRunner, :publish_to_js) ?
    # old Pluto
    PlutoRunner.publish_to_js(x) :
    # not Pluto
    x
```


"""
published_to_js(x) = _PublishToJS(x)






struct _JSLink
    callback
    on_cancellation
end
function Base.show(io::IO, ::MIME"text/javascript", wjl::_JSLink)
    core_with_js_link = get(io, :pluto_with_js_link, nothing)
    @assert core_with_js_link !== nothing """
    `AbstractPlutoDingetjes.Display.with_js_link` is not supported by this `IO` display.

    If you are not using `with_js_link` (or you do not know what it is), or you are not using Pluto, then please report this error to the package that you are using.

    If you are trying to use `with_js_link` but it is not working, please make sure that:
    - Pluto is up to date.
    - The original IO context is used to render the widget.
    - If you want to support non-Pluto environments, you use `AbstractPlutoDingetjes.is_supported_by_display` for a fallback.
    
    See the documentation for `with_js_link` to learn more about these points.
    """

    core_with_js_link(io, wjl.callback, wjl.on_cancellation)
end
Base.show(io::IO, ::MIME"text/plain", wjl::_JSLink) = show(io, MIME"text/javascript"(), wjl)
Base.show(io::IO, wjl::_JSLink) = show(io, MIME"text/javascript"(), wjl)

"""
```julia
with_js_link(f::Function[, on_cancellation::Function])
```

Make a Julia function available to the JS runtime of this cell, to be called from JavaScript. This API allows for more advanced use cases than `published_to_js`, but is also more difficult to use.

# Example
The easiest way to use this API is with HypertextLiteral. Here is a simple example:

```julia
@htl("\""
<script>
const sqrt_from_julia = \$(AbstractPlutoDingetjes.Display.with_js_link(sqrt))

// I can now call sqrt_from_julia like a JavaScript function. It returns a Promise:
const result = await sqrt_from_julia(9.0)
console.log(result)

</script>
"\"")
```

# API
The use is very similar to `Display.published_to_js`. `with_js_link` returns a "piece of JavaScript code" that you interpolate directly into a `<script>` tag. 

In JavaScript, the "piece of JavaScript code" returns a function. You can call this function with an argument (which will be passed to your Julia function), and it returns a [`Promise`](https://javascript.info/promise-basics) that resolves to the answer from your Julia function.

# Serialization and inner workings
The request and response use the same communication protocol as `published_to_js`, so in particular, `Vector{Float64}` or `Vector{UInt8}` are really fast. 

# When not to use it
This API is only meant to support **use cases that can not be covered with `Display.published_to_js`** (or `@bind`). If possible, the use of these APIs is preferred over `with_js_link`: they will work with the Static HTML export and PlutoSliderServer.

## `published_to_js` vs `with_js_link`
If the set of possible inputs is quite small, consider precomputing all possible outputs, and using `published_to_js` to publish everything at once. 

For example, in the `sqrt` example above, if you know that the input will be an integer between 1 and 1 million, then we recommend using `publish_to_js(sqrt.(1:1_000_000))` instead.

## `@bind` vs `with_js_link`
We recommend using `@bind` instead of `with_js_link` when your widget also makes sense split into two: an input widget (with `@bind`) and an output widget (possibly with `published_to_js`). This will be easier for you to develop, and easier for others to understand. If you are still considering how to design your widget, try to start with `@bind`. But if `with_js_link` is exactly what you are looking for, go for it!

For example, you are showing a map of the world, and you want to show a weather forecast for the location where the user clicks. With `with_js_link`, you could make an awesome GUI where the forecast is shown as an overlay on the map. But a simpler option would be to have one widget where you pick a location on the map, which gets bound to a Julia variable `location`. Then other cells in the notebook can compute the forecast, which you show in the cell directly above or below the map.

# Background task
JS link calculations are executed as a background task (not a thread). They can run in parallel with other computations in the notebook.


!!! warning "Don't make too many requests!"
	If you make too many requests from JS, then the notebook can become almost unusable. As a developer using this API, you need to take care to keep your users' notebooks responsive.

	The JS link request returns a `Promise` that resolves to the response. Consider keeping track of whether you are currently requesting something from Julia, and avoid making more requests in the meantime.

	It can also help to use [**throttling**](https://lodash.com/docs#throttle) or [**debouncing**](https://lodash.com/docs#debounce) to reduce the number of requests that you make.
    
    For example, starting requests at a regular interval can lead to big trouble. Instead, wait for the last request to finish, set a delay, and then make the next request.

# Advanced topics

## Multiple clients
Since this API is designed for one-off requests, this communication does not go through Pluto's state management (the request and response are not stored in the state). If multiple clients are connected in parallel, then the messages are not shared between clients. The client that made the request will receive the response.

## Cancellation
For advanced use cases, you can also provide a second argument to `with_js_link` – a function that will be called when the link is cancelled. This can be useful to clean up resources or to cancel a long-running process.

Cancellation happens **not** when the browser disconnects, but right before the cell or one of its dependency cells re-evaluates. This is done to prevent using data defined in the notebook that is no longer well-defined.

If your function is long-running, does async I/O work or runs threaded, then it is possible that your link gets cancelled during an execution. Use the `on_cancellation` callback wisely!

## Bidirectional communication
The primary purpose of this API is for JavaScript to ask a question to Julia, and receive an answer. So there is communication is both directions, but it needs to be initiated from JavaScript.

If you need to send *unrequested* updates from Julia to JavaScript, then you could use polling. [Long polling](https://javascript.info/long-polling) works well with this API, but you need to use the `on_cancellation` callback to clean up resources. Remember that Pluto notebooks can be viewed by multiple clients connected in parallel. [Here](https://github.com/fonsp/disorganised-mess/blob/main/APD%20with_js_link%20bidirectional%203.jl) is an example.

!!! compat "Pluto 0.19.41"
    This feature only works in Pluto version 0.19.41 or above.

    Use [`AbstractPlutoDingetjes.is_supported_by_display`](@ref) if you want to check support inside your widget.

"""
with_js_link(f::Function, on_cancellation=nothing) = _JSLink(f, on_cancellation)

# note: if we want to add a kwarg to this function later, we need a way for users to check support. The solution:

# const with_js_link_kwarg_blablabla_support = Ref(nothing)

# then PlutoRunner will "add support" for `with_js_link_kwarg_blablabla_support`, and users can then use it as a proxy to check support for the kwarg:

# APD.is_supported_by_display(io, with_js_link_kwarg_blablabla_support)




struct _AutoIDGiver
    source_hash::UInt64
end
function Base.show(io::IO, g::_AutoIDGiver)
    auto_id! = get(io, :pluto_auto_id!, _fallback_auto_id!)

    name = "id_$(
        string(g.source_hash, base=62)
    )_$(
        auto_id!(io)
    )"

    write(io, name)
end
_fallback_auto_id!(::IO) = string("PlutoRunner-auto-id-fallback", rand(Int64))

"""
```julia
@auto_id()::String
```

Generate an automatic unique ID that is stable across reactive re-evaluations, but different for different source code locations, repeated/nested use, and different non-reactive runs. This is useful for generating HTML element IDs in a `Base.show` method, without the risk of ID collisions.

# Example
```julia
struct MyWidget
    # ...
end

function Base.show(io::IO, m::MIME"text/html", w::MyWidget)
    h = @htl("\""
    <script id=\$(@auto_id())>
        // Some code
    </script>
    "\"")
    show(io, m, h)
end
```

Assinging an `id` to the `<script>` element enables a couple of cool JS API features. Most important is [`this` for stateful output](https://plutojl.org/en/docs/javascript-api/#this), and for re-rendering the value previously [`return`ed from JS](https://plutojl.org/en/docs/javascript-api/#return).

!!! compat "Pluto 0.20.26"
    This feature only works in Pluto version 0.20.26 (May 2026) or above. When unsupported, it falls back to a random string generated at render time.

    Use [`AbstractPlutoDingetjes.is_supported_by_display`](@ref) if you want to check support inside your widget.
"""
macro auto_id()
    _AutoIDGiver(hash(__source__))
end



struct _EmbedDisplay
    x
    source_hash::UInt64
end
function Base.show(io::IO, m::MIME"text/html", e::_EmbedDisplay)
    core = get(io, :pluto_embed_display, nothing)
    if core === nothing
        show(io, m, e.x)
    else
        show(io, m, core(io, e.x, _AutoIDGiver(e.source_hash)))
    end
end


"""
```julia
@embed(x)
```

Wrap `x` so it renders using Pluto's interactive multimedia viewer (images, arrays, tables, etc.) when interpolated into HTML output. You can interpolate the result into HTML content (we recommend [HypertextLiteral.jl](https://github.com/MechanicalRabbit/HypertextLiteral.jl) or [HyperScript.jl](https://github.com/yurivish/Hyperscript.jl)), Markdown, PlutoUI layout elements, and more.

# Example

Markdown can interpolate HTML-showable objects, including the embedded display:

```julia
md"\""
# Cool data

\$(embed_display(rand(10)))

Wow!
"\""
```

You can use HTML templating packages to create cool layouts, like two arrays side-by-side:

```julia
using HypertextLiteral
```

```julia
@htl("\""

<div style="display: flex;">
\$(embed_display(rand(4)))
\$(embed_display(rand(4)))
</div>

"\"")
```

!!! compat "Pluto 0.20.26"
    This feature only works in Pluto version 0.20.26 or above. When unsupported, it falls back to rendering `x` directly, using the `text/html` MIME type.

    Use [`AbstractPlutoDingetjes.is_supported_by_display`](@ref) if you want to check support inside your widget.
"""
macro embed(x)
    :( $(_EmbedDisplay)($(esc(x)), $(hash(__source__))) )
end



"""

```julia
ReactDOMElement(; tag="div", attributes=Dict(), children=[])
```

A lightweight Preact/React-like virtual DOM element that Pluto's frontend renders directly as a DOM node, without going through HTML strings.

# Fields
- `tag::String` — HTML/SVG tag name (`"div"`, `"span"`, `"svg"`, ...).
- `attributes::AbstractDict` — applied to the element. Use `"class"`, `"style"`, `"id"`, `"data-*"`, etc. See the notes on [`style`](@ref ReactDOMElement) and [`key`](@ref ReactDOMElement) below.
- `children::AbstractVector` — child displayables. Each child is rendered through Pluto's normal display pipeline, so other `ReactDOMElement`s, HTML strings, images, tables, etc. all work, and you can freely nest.

# Two ways to use it

## 1. Return a `ReactDOMElement` directly from a cell

```julia
using AbstractPlutoDingetjes.Display: ReactDOMElement

ReactDOMElement(;
    tag = "ul",
    children = [
        ReactDOMElement(; tag = "li", children = ["Milk"]),
        ReactDOMElement(; tag = "li", children = ["Eggs"]),
        ReactDOMElement(; tag = "li", children = ["Bread"]),
    ],
)
```

## 2. Write a show method for your own type

Implement `Base.show` for the MIME type `"application/vnd.pluto.reactdomelement+object"` and **return** a `ReactDOMElement`:

```julia
using AbstractPlutoDingetjes.Display: ReactDOMElement

struct ShoppingList
    items::Vector{String}
end

function Base.show(io::IO, ::MIME"application/vnd.pluto.reactdomelement+object", s::ShoppingList)
    # ℹ️ Note: We return a ReactDOMElement, we do not write to `io`.
    return ReactDOMElement(;
        tag = "ul",
        attributes = Dict("class" => "shopping-list"),
        children = [
            ReactDOMElement(;
                tag = "li",
                attributes = Dict("key" => item),
                children = [HTML(item)],
            )
            for item in s.items
        ],
    )
end

ShoppingList(["Milk", "Eggs", "Bread"])
```

!!! warning "The show method returns — it does **not** write to `io`!"
    Unlike a normal `Base.show` method, the show method for the
    `application/vnd.pluto.reactdomelement+object` MIME type does **not** write
    anything to `io`. Instead, it **returns** a `ReactDOMElement` (or any object
    that has a show method for this MIME). Pluto's renderer takes the returned
    value and serializes it to the frontend. Thank you [Shashi Gowda](github.com/shashi) for the idea!

    ```julia
    # ✅ Correct: return the element
    function Base.show(io::IO, ::MIME"application/vnd.pluto.reactdomelement+object", x::MyType)
        return ReactDOMElement(; tag = "div", children = ["hi"])
    end

    # 🛑 Wrong: do not write to io
    function Base.show(io::IO, ::MIME"application/vnd.pluto.reactdomelement+object", x::MyType)
        print(io, "...")  # don't do this
    end
    ```

# Styling: the `style` attribute

Set inline CSS via the `"style"` attribute, as a single CSS string:

```julia
ReactDOMElement(;
    tag = "div",
    attributes = Dict(
        "style" => "display: flex; gap: .5em; padding: 1em; background: #fee;",
    ),
    children = ["Hello!"],
)
```

# The `key` attribute

The special `"key"` attribute is forwarded to Preact as the [reconciliation key](https://preactjs.com/tutorial/08-keys/). When you render a list of children that may be reordered, inserted, or removed across re-renders, giving each child a stable, unique `"key"` lets Preact match up the old and new children correctly — preserving DOM state (focus, input values, animations) instead of recreating nodes.

```julia
ReactDOMElement(;
    tag = "ul",
    children = [
        ReactDOMElement(;
            tag = "li",
            attributes = Dict("key" => item.id),
            children = [item.name],
        )
        for item in items
    ],
)
```

!!! compat "Pluto 0.20.26"
    This feature only works in Pluto versions that support it.

    Use [`AbstractPlutoDingetjes.is_supported_by_display`](@ref) if you want to check support inside your widget. Support for `ReactDOMElement` means that the MIME type is also supported.
"""
Base.@kwdef struct ReactDOMElement
    tag::String = "div"
    attributes::AbstractDict = Dict{String,Any}()
    children::AbstractVector = Any[]
end

# Inside Pluto, the cell-output show method returns the element itself — PlutoRunner picks it up
# via the MIME type and serializes the struct to the frontend.
function Base.show(io::IO, ::MIME"application/vnd.pluto.reactdomelement+object", e::ReactDOMElement)
    return e
end

# text/html fallback: relay through `_EmbedDisplay`, which inside Pluto dispatches to the
# multimedia viewer (and therefore the reactdomelement+object MIME above), and outside
# Pluto falls back to `show(io, MIME"text/html"(), e.x)` — handled below.
function Base.show(io::IO, m::MIME"text/html", e::ReactDOMElement)
    if get(io, :pluto_embed_display, nothing) !== nothing
        Base.show(io, m, _EmbedDisplay(e, rand(UInt64)))
    else
        @warn "AbstractPlutoDingetjes.Display.ReactDOMElement cannot be displayed here. This object needs to be displayed directly by Pluto, and not rendered to a String."
        println(io, "<em>ReactDOMElement cannot be displayed here.</em>")
    end
end

end
