import "https://unpkg.com/deck.gl@latest/dist.min.js"
import "https://unpkg.com/@deck.gl/json@latest/dist.min.js"
import "https://unpkg.com/@deck.gl/extensions@latest/dist.min.js"
import "https://unpkg.com/@deck.gl/geo-layers@latest/dist.min.js"
import "https://unpkg.com/chroma-js@3.2.0/dist/chroma.min.cjs"


export function render({ model, el }) {

    const { DeckGL, JSONConverter, FlyToInterpolator } = deck;
    const json = model.object
    const json_obj = JSON.parse(json)

    // Determine which layers should be loaded in the configuration
    const layer_types = new Object();
    const layer_id_type = new Object();
    for (const i in json_obj.layers) {
        var layer_type = json_obj.layers[i]["@@type"]
        var layer_id = json_obj.layers[i]["id"]
        var layer_obj = deck[layer_type]
        layer_types[layer_type] = layer_obj
        layer_id_type[layer_id] = layer_type
    }

    /////
    // Tooltip
    /////
    function set_tooltip( {customTemplate} ) {
        return function tooltip(info) {
            const object = info.object ?? {};
            const properties = object.properties ?? {};

            if (!properties || Object.keys(properties).length === 0) {
                return null; // No tooltip if no properties
            }

            if (typeof customTemplate === 'string' && customTemplate != "") {
                // Replace placeholders in the template with property values
                let formattedHtml = customTemplate;
                for (const [key, value] of Object.entries(properties)) {
                    const placeholder = `{{${key}}}`;
                    formattedHtml = formattedHtml.replaceAll(placeholder, value);
                }
                return { html: formattedHtml };
            } else {
                // Default behavior: JSON string of properties
                return { text: JSON.stringify(properties) };
            }
        }
    }

    var click_state = new Object();
    var hover_state = new Object();

    var is_auto_zooming_simple = false;
    var is_auto_zooming_contains =false;
    var has_click_color = false;
    var has_hover_color = false;

    // Cache for fetched GeoJSON data (keyed by URL)
    var geojson_data_cache = {};

    async function ensureDataLoaded(url) {
        if (!geojson_data_cache[url]) {
            const response = await fetch(url);
            const data = await response.json();
            geojson_data_cache[url] = data.features || data;
        }
        return geojson_data_cache[url];
    }

    async function computeSelectionBboxes(clickProps) {
        console.log("RUNNING computeSelectionBboxes")
        const json_obj = JSON.parse(model.object);
        for (const layer of json_obj.layers) {
            for (const [key, value] of Object.entries(layer)) {
                if (typeof value !== 'object' || !value) continue;
                if (value["@@function"] !== "set_selection") continue;
                if (!value["auto_zoom"]) continue;

                if (clickProps.layer_id == -1) {
                    continue;
                }
                
                let features;
                if (layer["@@type"] === "GeoJsonLayer" && typeof layer.data === 'string') {
                    // GeoJsonLayer: fetch source data for exact bbox
                    features = await ensureDataLoaded(layer.data);
                } else {
                    // MVTLayer or other: pick visible features from viewport
                    const picked = deckgl.pickObjects({
                        x: 0,
                        y: 0,
                        width: container.clientWidth,
                        height: container.clientHeight,
                    });
                    features = picked.map(p => p.object).filter(Boolean);
                }

                let bbox = [Infinity, Infinity, -Infinity, -Infinity];
                let found = false;

                for (const f of features) {
                    let isSelected = false;
                    if (value.selection_type === "simple") {
                        isSelected = clickProps.properties?.[value.selection_prop] !== undefined &&
                            f.properties[value.selection_prop] === clickProps.properties[value.selection_prop];
                    } else if (value.selection_type === "contains") {
                        isSelected = clickProps.properties?.[value.containing_prop]?.includes(f.properties[value.selection_prop]) ||
                            f.properties[value.selection_prop] === clickProps.properties[value.selection_prop];
                    }

                    if (isSelected && f.geometry?.coordinates) {
                        found = true;
                        var coords = f.geometry.coordinates;
                        var flat = coords.flat(Infinity);
                        for (var i = 0; i < flat.length; i += 2) {
                            if (flat[i] < bbox[0]) bbox[0] = flat[i];
                            if (flat[i + 1] < bbox[1]) bbox[1] = flat[i + 1];
                            if (flat[i] > bbox[2]) bbox[2] = flat[i];
                            if (flat[i + 1] > bbox[3]) bbox[3] = flat[i + 1];
                        }
                    }
                }

                if (found) {
                    model.send_msg(JSON.stringify({"zoom_box": bbox}));
                }
            }
        }
    }

    /////
    // Colorbar
    /////
    var update_color = 0
    function colorscale_trigger() {
        return update_color
    }

    function set_colorscale({ cmap_var, cmap_range, cmap_palette, cmap_qvar, cmap_qthreshold }) {

        var nodata_color = chroma("gray")
        var chr_scale = chroma.scale(cmap_palette).domain(cmap_range).nodata(nodata_color)
        update_color++

        return function colorscale(f) {
            var indicator = f.properties[cmap_var]
            var qindicator = f.properties[cmap_qvar]
            if (qindicator < cmap_qthreshold) {
                var color = chr_scale(null).rgb()
            } else {
                var color = chr_scale(indicator).rgb()
            }

            return color
        }
    }

    function set_selection({ name, selection_type, selection_prop, containing_prop, trigger_type, color_true, color_false, auto_zoom}) {

        has_click_color = true;

        if (selection_type == "simple") {

            is_auto_zooming_simple = auto_zoom;
            is_auto_zooming_contains = false;

            if (trigger_type == "click") {
                has_click_color = true;
                return function selection(f) {
                    if (click_state.properties && f.properties[selection_prop] == click_state.properties[selection_prop]) {
                        var color = chroma(color_true).rgb()
                    } else {
                        var color = chroma(color_false).rgb()
                    }
                    return color
                }
            } else if (trigger_type == "hover") {
                has_hover_color = true;
                return function selection(f) {
                    if (hover_state.properties && f.properties[selection_prop] == hover_state.properties[selection_prop]) {
                        var color = chroma(color_true).rgb()
                    } else {
                        var color = chroma(color_false).rgb()
                    }
                    return color
                }
            }

        } else if (selection_type == "contains") {

            is_auto_zooming_contains = auto_zoom;
            is_auto_zooming_simple = false;

            if (trigger_type == "click") {
                has_click_color = true;
                return function selection(f) {

                    if (click_state.properties && (click_state.properties[containing_prop]?.includes(f.properties[selection_prop]) || click_state.properties[selection_prop] == f.properties[selection_prop])) {
                        var color = chroma(color_true).rgb()
                    } else {
                        var color = chroma(color_false).rgb()
                    }
                    return color
                }

            } else if (trigger_type == "hover") {
                has_hover_color = true;
                return function selection(f) {

                    if (hover_state.properties && (hover_state.properties[containing_prop]?.includes(f.properties[selection_prop]) || hover_state.properties[selection_prop] == f.properties[selection_prop])) {
                        var color = chroma(color_true).rgb()
                    } else {
                        var color = chroma(color_false).rgb()
                    }
                    return color
                }
            }



        }
    }

    /////
    // Click events
    /////
    function set_clicker({parseGeometry}) {
        return function clicker(info, event) {

            let foo = info.layer ?? "null";
            let id = foo.id ?? -1;

            click_state = {
                "coordinate": info.coordinate,
                "layer_id": id,
                "idx": info.index,
            }

            var object_props = info.object ?? {};

            // Compute bounding box from geometry coordinates
            var coords = object_props.geometry?.coordinates;
            if (coords) {
                var flat = coords.flat(Infinity);
                var lngs = flat.filter((_, i) => i % 2 === 0);
                var lats = flat.filter((_, i) => i % 2 === 1);
                var bounding_box = [
                    Math.min(...lngs), Math.min(...lats),
                    Math.max(...lngs), Math.max(...lats),
                ];
            } else {
                var bounding_box = null;
            }

            // Ensure geometry.coordinates survives JSON.stringify
            // (MVTLayer exposes coordinates as a non-enumerable getter)
            if (object_props.geometry?.coordinates && parseGeometry) {
                object_props = {
                    ...object_props,
                    geometry: { ...object_props.geometry, coordinates: JSON.parse(JSON.stringify(Array.from(coords))) }
                };
            }

            if (!parseGeometry) {
                delete object_props.geometry;
            }
                
            click_state = { ...click_state, ...object_props, bounding_box }
            var to_send = { "click_state": click_state }
            model.send_msg(JSON.stringify(to_send))

            // Force deck.gl to re-evaluate layer accessors (e.g. getFillColor)
            if (has_click_color) {
                update_color++
                update({ "object": model.object })
                if (is_auto_zooming_contains){
                    computeSelectionBboxes(click_state);
                } else if (is_auto_zooming_simple) {
                    model.send_msg(JSON.stringify({"zoom_box": bounding_box}));
                }
            }

        }
    }

    function set_hover() {
        return function hover(info, event) {

            let foo = info.layer ?? "null";
            let id = foo.id ?? -1;

            hover_state = {
                "coordinate": info.coordinate,
                "layer_id": id,
                "idx": info.index,
                "properties": info.object?.properties ?? {},
            }

            var to_send = {"hover_state": hover_state }
            model.send_msg(JSON.stringify(to_send))

            // Force deck.gl to re-evaluate layer accessors (e.g. getFillColor)
            if (has_hover_color) {
                update_color++
                update({ "object": model.object })
            }

        }
    }

    function create_FlyToInterpolator() {
        return new FlyToInterpolator();
    }

    // Update the map when model.object changes.
    function update(event) {
        var x = jsonConverter.convert(JSON.parse(event.object))
        deckgl.setProps(x);
    }

    // Create container for the map.
    const container = document.createElement('div')
    container.setAttribute('id', 'container');
    container.style.width = "100%"
    container.style.height = "100%"
    container.style.padding = "0px"
    container.style.margin = "0px"
    el.style.backgroundColor = "rgb(212, 215, 215)"
    el.appendChild(container)

    function reportContainerSize() {
        let to_send = {"width": container.offsetWidth, "height": container.offsetHeight}
        model.send_msg(JSON.stringify({"container_size": to_send}))
    };

    window.onresize = reportContainerSize;
    reportContainerSize();

    // Add required functions and classes to the JSON Convertor.
    const configuration = {
        classes: { ...layer_types, GlobeView: deck._GlobeView, MapView: deck.MapView },
        constants: {  },
        functions: { set_clicker, set_hover, set_tooltip, set_colorscale, set_selection, colorscale_trigger, create_FlyToInterpolator },
    };

    // Create the convertor.
    const jsonConverter = new JSONConverter({ configuration });

    // Initiate the deck.
    const deckgl = new DeckGL({
        container: container,
        json
    });

    // Update the deck and add Py->JS update trigger.
    update({ "object": model.object })
    model.on("msg:custom", update)

}