import moment from 'moment';
import 'leaflet/dist/leaflet.js';
const L = window.L;
import 'leaflet.markercluster/dist/leaflet.markercluster.js';
import 'leaflet-groupedlayercontrol/dist/leaflet.groupedlayercontrol.min.js';
import { 
    escapeString,
    findNodeById,
    getRegionFrequencyRange,
    hasNodeUplinkedToMqttRecently,
    formatPositionPrecision,
    getTerrainProfileImage,
} from './utils.js';
import { state } from './store.js';
import { markRaw } from 'vue';

// state/config
let instance = null;
export function setMap(map) {
    instance = markRaw(map);
}
export function getMap() {
    return instance;
}

export const layerGroups = {
    nodes: new L.LayerGroup(),
    neighbors: new L.LayerGroup(),
    waypoints: new L.LayerGroup(),
    nodePositionHistory: new L.LayerGroup(),
    nodeNeighbors: new L.LayerGroup(),
    nodesRouter: L.markerClusterGroup({
        showCoverageOnHover: false,
        disableClusteringAtZoom: 10, // zoom level where node clustering is disabled
    }),
    nodesClustered: L.markerClusterGroup({
        showCoverageOnHover: false,
        disableClusteringAtZoom: 10, // zoom level where node clustering is disabled
    }),
    legend: new L.LayerGroup(),
    none: new L.LayerGroup(),
};

export const tileLayers = {
    "OpenStreetMap": L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        maxZoom: 22, // increase from 18 to 22
        attribution: 'Tiles &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>',
    }),
    "OpenTopoMap": L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
        maxZoom: 17, // open topo map doesn't have tiles closer than this
        attribution: 'Tiles &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
    }),
    "Esri Satellite": L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
        maxZoom: 21, // esri doesn't have tiles closer than this
        attribution: 'Tiles &copy; <a href="https://developers.arcgis.com/documentation/mapping-apis-and-services/deployment/basemap-attribution/">Esri</a> | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>'
    }),
    "Google Satellite": L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', {
        maxZoom: 21,
        subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
        attribution: 'Tiles &copy; Google | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>'
    }),
    "Google Hybrid": L.tileLayer('https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
        maxZoom: 21,
        subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
        attribution: 'Tiles &copy; Google | Data from <a target="_blank" href="https://meshtastic.org/docs/software/integrations/mqtt/">Meshtastic</a>'
    }),
};

export const icons = {
    mqttConnected: L.divIcon({
        className: 'icon-mqtt-connected',
        iconSize: [16, 16], // increase from 12px to 16px to make hover easier
    }),
    mqttDisconnected: L.divIcon({
        className: 'icon-mqtt-disconnected',
        iconSize: [16, 16], // increase from 12px to 16px to make hover easier
    }),
    offline: L.divIcon({
        className: 'icon-offline',
        iconSize: [16, 16], // increase from 12px to 16px to make hover easier
    }),
    positionHistory: L.divIcon({
        className: 'icon-position-history',
        iconSize: [16, 16], // increase from 12px to 16px to make hover easier
    }),
};

// tooltips

export function getTooltipContentForWaypoint(waypoint) {
    // get from node name
    var fromNode = findNodeById(waypoint.from);

    var tooltip = `<b>${escapeString(waypoint.name)}</b>` +
        (waypoint.description ? `<br/>${escapeString(waypoint.description)}` : '') +
        `<br/><br/>Expires: ${moment(new Date(waypoint.expire * 1000)).fromNow()}` +
        `<br/>Lat/Lng: ${waypoint.latitude}, ${waypoint.longitude}` +
        `<br/><br/>From ID: ${waypoint.from}` +
        `<br/>From Hex ID: !${Number(waypoint.from).toString(16)}`;

    // show node name this waypoint is from, if possible
    if(fromNode != null){
        tooltip += `<br/>From Node: <a href="#" onclick="goToNode(${waypoint.from})">${escapeString(fromNode.long_name) || 'Unnamed Node'}</a>`;
    } else {
        tooltip += `<br/>From Node: ???`;
    }

    // bottom info
    tooltip += `<br/><br/>ID: ${waypoint.waypoint_id}`;
    tooltip += `<br/>Updated: ${moment(new Date(waypoint.updated_at)).fromNow()}`;

    return tooltip;
};

export function getTooltipContentForNode(node) {
    // determine if node was recently heard uplinking packets to mqtt
    const nodeHasUplinkedToMqttRecently = hasNodeUplinkedToMqttRecently(node);
    var mqttStatus = `<span class="text-blue-700">Disconnected</span>`;
    if(node.mqtt_connection_state_updated_at){
        var mqttStatusUpdatedAt = moment(new Date(node.mqtt_connection_state_updated_at)).fromNow();
        if(nodeHasUplinkedToMqttRecently){
            mqttStatus = `<span><span class="text-green-700">Connected</span> (${mqttStatusUpdatedAt})</span>`;
        } else {
            mqttStatus = `<span><span class="text-blue-700">Disconnected</span> (${mqttStatusUpdatedAt})</span>`;
        }
    }

    var loraFrequencyRange = getRegionFrequencyRange(node.region_name);

    var tooltip = `<img class="mb-4 w-40 mx-auto" src="/images/devices/${node.hardware_model_name}.png" onerror="this.classList.add('hidden')"/>` +
        `<b>${escapeString(node.long_name)}</b>` +
        `<br/>Short Name: ${escapeString(node.short_name)}` +
        `<br/>MQTT: ${mqttStatus}` +
        (node.num_online_local_nodes != null ? `<br/>Local Nodes Online: ${node.num_online_local_nodes}` : '') +
        (node.position_precision != null && node.position_precision !== 32 ? `<br/>Position Precision: ${formatPositionPrecision(node.position_precision)}` : '') +
        `<br/><br/>Role: ${node.role_name}` +
        `<br/>Hardware: ${node.hardware_model_name}` +
        (node.firmware_version != null ? `<br/>Firmware: ${node.firmware_version}` : '') +
        (node.region_name != null ? `<br/>LoRa Region: ${node.region_name} (${loraFrequencyRange})` : '') +
        (node.modem_preset_name != null ? `<br/>Modem Preset: ${node.modem_preset_name}` : '') +
        (node.has_default_channel != null ? `<br/>Has Default Channel: ${node.has_default_channel ? "Yes" : "No"}` : '');

    if(node.battery_level){
        if(node.battery_level > 100){
            tooltip += `<br/>Battery: ${node.battery_level > 100 ? 'Plugged In' : node.battery_level}`;
        } else {
            tooltip += `<br/>Battery: ${node.battery_level}%`;
        }
    }

    if(node.voltage){
        tooltip += `<br/>Voltage: ${Number(node.voltage).toFixed(2)}V`;
    }

    if(node.channel_utilization){
        tooltip += `<br/>Ch Util: ${Number(node.channel_utilization).toFixed(2)}%`;
    }

    if(node.air_util_tx){
        tooltip += `<br/>Air Util: ${Number(node.air_util_tx).toFixed(2)}%`;
    }

    // ignore alt above 42949000 due to https://github.com/meshtastic/firmware/issues/3109
    if(node.altitude && node.altitude < 42949000){
        tooltip += `<br/>Altitude: ${node.altitude}m`;
    }

    // bottom info
    tooltip += `<br/><br/>ID: ${node.node_id}`;
    tooltip += `<br/>Hex ID: ${node.node_id_hex}`;
    tooltip += `<br/>Updated: ${moment(new Date(node.updated_at)).fromNow()}`;
    tooltip += (node.neighbours_updated_at ? `<br/>Neighbours Updated: ${moment(new Date(node.neighbours_updated_at)).fromNow()}` : '');
    tooltip += (node.position_updated_at ? `<br/>Position Updated: ${moment(new Date(node.position_updated_at)).fromNow()}` : '');

    // show details button
    tooltip += `<br/><br/><button onclick="showNodeDetails(${node.node_id});" class="border border-gray-300 bg-gray-100 p-1 w-full rounded hover:bg-gray-200 mb-1">Show Full Details</button>`;
    tooltip += `<br/><button onclick="window.showNodeNeighboursThatHeardUs(${node.node_id});" class="border border-gray-300 bg-gray-100 p-1 w-full rounded hover:bg-gray-200 mb-1">Show Neighbours (Heard Us)</button>`;
    tooltip += `<br/><button onclick="window.showNodeNeighboursThatWeHeard(${node.node_id});" class="border border-gray-300 bg-gray-100 p-1 w-full rounded hover:bg-gray-200">Show Neighbours (We Heard)</button>`;
    tooltip += `</div>`;

    return tooltip;
}

export function getNeighbourTooltipContent(type, node, neighbourNode, distanceInMeters, snr) {
    // default to showing distance in meters
    let distance = `${distanceInMeters} meters`;

    // scale to distance in kms
    if (distanceInMeters >= 1000) {
        const distanceInKilometers = (distanceInMeters / 1000).toFixed(2);
        distance = `${distanceInKilometers} kilometers`;
    }

    const terrainImageUrl = type === 'weHeard' ? getTerrainProfileImage(node, neighbourNode) : getTerrainProfileImage(neighbourNode, node);

    const templates = {
        'weHeard': `<b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b> heard <b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b>`
            + `<br/>SNR: ${snr}dB`
            + `<br/>Distance: ${distance}`
            + `<br/><br/>ID: ${node.node_id} heard ${neighbourNode.node_id}`
            + `<br/>Hex ID: ${node.node_id_hex} heard ${neighbourNode.node_id_hex}`
            + (node.neighbours_updated_at ? `<br/>Updated: ${moment(new Date(node.neighbours_updated_at)).fromNow()}` : '')
            + `<br/><br/>Terrain images from <a href="http://www.heywhatsthat.com" target="_blank">HeyWhatsThat.com</a>`
            + `<br/><a href="${terrainImageUrl}" target="_blank"><img src="${terrainImageUrl}" width="100%"></a>`,
        'theyHeard': `<b>[${escapeString(neighbourNode.short_name)}] ${escapeString(neighbourNode.long_name)}</b> heard <b>[${escapeString(node.short_name)}] ${escapeString(node.long_name)}</b>`
            + `<br/>SNR: ${snr}dB`
            + `<br/>Distance: ${distance}`
            + `<br/><br/>ID: ${neighbourNode.node_id} heard ${node.node_id}`
            + `<br/>Hex ID: ${neighbourNode.node_id_hex} heard ${node.node_id_hex}`
            + (neighbourNode.neighbours_updated_at ? `<br/>Updated: ${moment(new Date(neighbourNode.neighbours_updated_at)).fromNow()}` : '')
            + `<br/><br/>Terrain images from <a href="http://www.heywhatsthat.com" target="_blank">HeyWhatsThat.com</a>`
            + `<br/><a href="${terrainImageUrl}" target="_blank"><img src="${terrainImageUrl}" width="100%"></a>`,
    }

    return templates[type];
}

// cleanup

export function clearAllNodes() {
    layerGroups.nodes.clearLayers();
    layerGroups.nodesClustered.clearLayers();
    layerGroups.nodesRouter.clearLayers();
};

export function clearAllNeighbors() {
    layerGroups.neighbors.clearLayers();
};

export function clearAllWaypoints() {
    layerGroups.waypoints.clearLayers();
};

export function clearAllPositionHistory() {
    layerGroups.nodePositionHistory.clearLayers();
};

export function cleanUpPositionHistory() {
    // close tooltips and popups
    closeAllPopups();
    closeAllTooltips();

    // setup node neighbours layer
    layerGroups.nodePositionHistory.clearLayers();
    layerGroups.nodePositionHistory.removeFrom(getMap());
    layerGroups.nodePositionHistory.addTo(getMap());
};

export function closeAllTooltips() {
    getMap().eachLayer(function(layer) {
        if (layer.options.pane === 'tooltipPane') {
            layer.removeFrom(getMap());
        }
    });
};

export function closeAllPopups() {
    getMap().eachLayer(function(layer) {
        if (layer.options.pane === 'popupPane') {
            layer.removeFrom(getMap());
        }
    });
};

export function cleanUpNodeNeighbors() {
    // close tooltips and popups
    closeAllPopups();
    closeAllTooltips();
    // setup node neighbours layer
    layerGroups.nodeNeighbors.clearLayers();
    layerGroups.nodeNeighbors.removeFrom(getMap());
    layerGroups.nodeNeighbors.addTo(getMap());
};

export function clearNodeOutline() {
    if (state.selectedNodeOutlineCircle) {
        state.selectedNodeOutlineCircle.removeFrom(getMap());
        state.selectedNodeOutlineCircle = null;
    }
};

export function clearMap() {
    closeAllPopups();
    closeAllTooltips();
    clearAllNodes();
    clearAllNeighbors();
    clearAllWaypoints();
    clearNodeOutline();
    cleanUpNodeNeighbors();
};