# WebMapGeosCreator.py
# Independent script with OpenLayers map view widget and buttons to open GeoServer files
# and add layers into the web map

import sys
import os
import json
import configparser
import uuid
import requests
import xml.etree.ElementTree as ET
from PyQt5.QtWidgets import (
    QApplication, QDialog, QHBoxLayout, QVBoxLayout, QWidget, 
    QPushButton, QListWidget, QTreeWidget, QTreeWidgetItem, 
    QFileDialog, QMessageBox, QCheckBox, QSlider, QLabel, QLineEdit
)
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView

# Try to import QGIS interface, handle failure for standalone execution
try:
    from qgis.utils import iface
except ImportError:
    iface = None


class WebMapGeosCreator(QDialog):
    def apply_stylesheet(self):
        """Applies a QGIS-like stylesheet to the dialog."""
        stylesheet = """
            QDialog {
                background-color: #2c313c;
                color: #f0f0f0;
            }
            QWidget {
                background-color: #2c313c;
                color: #f0f0f0;
            }
            QTreeWidget {
                background-color: #3c424f;
                border: 1px solid #505561;
                color: #f0f0f0;
            }
            QTreeWidget::item:hover {
                background-color: #4a505e;
            }
            QTreeWidget::item:selected {
                background-color: #5a606e;
            }
            QHeaderView::section {
                background-color: #505561;
                color: #f0f0f0;
                padding: 4px;
                border: 1px solid #2c313c;
            }
            QPushButton {
                background-color: #505561;
                color: #f0f0f0;
                border: 1px solid #6a707c;
                padding: 5px;
                border-radius: 3px;
            }
            QPushButton:hover {
                background-color: #6a707c;
            }
            QPushButton:pressed {
                background-color: #5a606e;
            }
            QLineEdit, QTextEdit {
                background-color: #3c424f;
                border: 1px solid #505561;
                color: #f0f0f0;
                padding: 5px;
            }
            QLabel {
                color: #f0f0f0;
            }
            QGroupBox {
                border: 1px solid #505561;
                border-radius: 3px;
                margin-top: 10px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top left;
                padding: 0 3px;
                background-color: #2c313c;
            }
        """
        self.setStyleSheet(stylesheet)

    """Independent dialog for creating web maps with GeoServer layers."""
    
    def __init__(self, parent=None):
        super().__init__(parent)
        print("DEBUG: Starting initialization")
        try:
            self.geoserver_url = "http://localhost:8080/geoserver"
            self.username = "admin"
            self.password = "geoserver"
            self.added_layers = {}
            self.all_layers = []
            print("DEBUG: Basic attributes set")
            
            try:
                print("DEBUG: About to call init_ui")
                self.init_ui()
                print("DEBUG: init_ui completed")
                self.apply_stylesheet()
                print("DEBUG: Stylesheet applied")
            except Exception as e:
                QMessageBox.critical(self, "UI Initialization Error", f"Failed to initialize UI: {e}")
                raise
            
            try:
                print("DEBUG: About to call initialize_openlayers_map")
                self.initialize_openlayers_map()
                print("DEBUG: initialize_openlayers_map completed")
            except Exception as e:
                QMessageBox.critical(self, "Map Initialization Error", f"Failed to initialize map: {e}")
                raise
        except Exception as e:
            QMessageBox.critical(self, "Initialization Error", f"Failed to initialize the dialog: {e}")
            # Optionally re-raise or close the dialog
            # raise
        print("DEBUG: Initialization completed")
    
    def init_ui(self):
        """Initialize the user interface."""
        print("DEBUG: init_ui - start")
        self.setWindowTitle("Web Map Creator - GeoServer Layers")
        self.resize(1200, 700)
        
        # Main layout
        layout = QHBoxLayout()
        print("DEBUG: init_ui - created main layout")
        
        # Left panel
        left_widget = self.create_left_panel()
        print("DEBUG: init_ui - created left panel")
        layout.addWidget(left_widget, 1)
        
        # Right panel (map)
        right_widget = self.create_right_panel()
        print("DEBUG: init_ui - created right panel")
        layout.addWidget(right_widget, 3)
        
        self.setLayout(layout)
        print("DEBUG: init_ui - layout set")
    
    def create_left_panel(self):
        """Create the left panel with layer controls."""
        print("DEBUG: create_left_panel - start")
        left_widget = QWidget()
        left_layout = QVBoxLayout()
        
        # GeoServer connection settings
        conn_layout = QVBoxLayout()
        conn_layout.addWidget(QLabel("GeoServer Connection:"))
        
        url_layout = QHBoxLayout()
        url_layout.addWidget(QLabel("URL:"))
        self.url_input = QLineEdit(self.geoserver_url)
        url_layout.addWidget(self.url_input)
        conn_layout.addLayout(url_layout)
        
        auth_layout = QHBoxLayout()
        auth_layout.addWidget(QLabel("User:"))
        self.user_input = QLineEdit(self.username)
        auth_layout.addWidget(self.user_input)
        auth_layout.addWidget(QLabel("Pass:"))
        self.pass_input = QLineEdit(self.password)
        self.pass_input.setEchoMode(QLineEdit.Password)
        auth_layout.addWidget(self.pass_input)
        conn_layout.addLayout(auth_layout)
        
        self.connect_btn = QPushButton("Connect to GeoServer")
        self.connect_btn.clicked.connect(self.connect_to_geoserver)
        conn_layout.addWidget(self.connect_btn)
        
        left_layout.addLayout(conn_layout)
        print("DEBUG: create_left_panel - connection widgets added")
        
        # Added layers tree
        left_layout.addWidget(QLabel("Added Layers:"))
        self.added_layers_tree = QTreeWidget()
        self.added_layers_tree.setHeaderLabels(["Visible", "Layer Name", "Transparency"])
        self.added_layers_tree.setColumnWidth(0, 60)
        self.added_layers_tree.setColumnWidth(1, 180)
        self.added_layers_tree.itemDoubleClicked.connect(self.on_added_layer_double_clicked)
        left_layout.addWidget(self.added_layers_tree)
        print("DEBUG: create_left_panel - added_layers_tree added")
        
        # Extent Display Box
        self.extent_display = QLineEdit()
        self.extent_display.setReadOnly(True)
        self.extent_display.setPlaceholderText("Double-click a layer to see its extent here...")
        left_layout.addWidget(self.extent_display)
        print("DEBUG: create_left_panel - extent_display added")
        
        # Map state buttons
        state_layout = QHBoxLayout()
        self.save_btn = QPushButton("Save Map (.geos)")
        self.save_btn.clicked.connect(self.save_map_state)
        self.load_btn = QPushButton("Load Map (.geos)")
        self.load_btn.clicked.connect(self.load_map_state)
        state_layout.addWidget(self.save_btn)
        state_layout.addWidget(self.load_btn)
        left_layout.addLayout(state_layout)
        print("DEBUG: create_left_panel - state buttons added")
        
        # Save HTML button
        self.save_html_btn = QPushButton("Save as HTML (ReactJS)")
        self.save_html_btn.clicked.connect(self.save_as_html_react)
        left_layout.addWidget(self.save_html_btn)
        print("DEBUG: create_left_panel - save_html_btn added")
        
        # Folder browser with favorites (QTreeWidget)
        left_layout.addWidget(QLabel("Folders:"))
        self.folder_tree = QTreeWidget()
        self.folder_tree.setHeaderLabels(["", "Folder"])
        self.folder_tree.setColumnWidth(0, 32)
        self.folder_tree.setColumnWidth(1, 220)
        self.folder_tree.itemClicked.connect(self.on_folder_tree_item_clicked)
        self.folder_tree.itemExpanded.connect(self.on_item_expanded) # Connect expand signal
        left_layout.addWidget(self.folder_tree)
        print("DEBUG: create_left_panel - folder_tree widget created")
        self.populate_folder_tree()
        print("DEBUG: create_left_panel - folder_tree populated")
        
        left_widget.setLayout(left_layout)
        print("DEBUG: create_left_panel - layout set")
        return left_widget
    
    def create_right_panel(self):
        """Create the right panel with the map view."""
        right_widget = QWidget()
        right_layout = QVBoxLayout()
        
        self.web_view = QWebEngineView()
        right_layout.addWidget(self.web_view)
        
        right_widget.setLayout(right_layout)
        return right_widget
    
    def connect_to_geoserver(self):
        """Connect to GeoServer with provided credentials."""
        self.geoserver_url = self.url_input.text().rstrip('/')
        self.username = self.user_input.text()
        self.password = self.pass_input.text()

    def populate_folder_tree(self):
        """Populate the folder tree with the file system and favorites."""
        print("DEBUG: populate_folder_tree - start")
        import pathlib
        from PyQt5.QtGui import QIcon
        self.folder_tree.clear()
        self.favorites_set = set()
        self.favorites_config_file = os.path.join(os.path.expanduser("~"), ".geoserver_favorites.cfg")
        self.star_icon = QIcon(os.path.join(os.path.dirname(__file__), "star_yellow.png")) if os.path.exists(os.path.join(os.path.dirname(__file__), "star_yellow.png")) else QIcon()
        self.star_gray_icon = QIcon(os.path.join(os.path.dirname(__file__), "star_gray.png")) if os.path.exists(os.path.join(os.path.dirname(__file__), "star_gray.png")) else QIcon()
        
        # Load favorites from config
        if os.path.exists(self.favorites_config_file):
            import configparser
            config = configparser.ConfigParser()
            config.read(self.favorites_config_file)
            if 'Favorites' in config:
                for key in config['Favorites']:
                    self.favorites_set.add(config['Favorites'][key])

        print("DEBUG: populate_folder_tree - loaded favorites")
        # Add favorites section at the top
        self.favorites_root = QTreeWidgetItem(self.folder_tree, ["★", "Favorites"])
        self.favorites_root.setFirstColumnSpanned(True)
        self.favorites_root.setFlags(self.favorites_root.flags() & ~Qt.ItemIsSelectable)
        for fav in sorted(self.favorites_set):
            item = QTreeWidgetItem(self.favorites_root, ["", fav])
            item.setIcon(0, self.star_icon)
            item.setData(1, Qt.UserRole, fav)
        self.favorites_root.setExpanded(True)
        print("DEBUG: populate_folder_tree - favorites section created")
        # Add file system tree (root directories)
        drives = []
        if os.name == 'nt':
            import string
            from ctypes import windll
            bitmask = windll.kernel32.GetLogicalDrives()
            for letter in string.ascii_uppercase:
                if bitmask & 1:
                    drives.append(f"{letter}:/")
                bitmask >>= 1
        else:
            drives = ['/']
        print(f"DEBUG: populate_folder_tree - drives: {drives}")
        for drive in drives:
            drive_item = QTreeWidgetItem(self.folder_tree, ["", drive])
            drive_item.setIcon(0, self.star_icon if drive in self.favorites_set else self.star_gray_icon)
            drive_item.setData(1, Qt.UserRole, drive)
            # Add a dummy child to make it expandable
            drive_item.addChild(QTreeWidgetItem())
        print("DEBUG: populate_folder_tree - done")
    def add_subfolders(self, parent_item, parent_path):
        """Add subfolders and .geos files to the given tree item."""
        import pathlib
        from PyQt5.QtGui import QIcon
        try:
            entries = sorted(pathlib.Path(parent_path).iterdir())
            for entry in entries:
                if entry.is_dir():
                    item = QTreeWidgetItem(parent_item, ["", str(entry)])
                    item.setIcon(0, self.star_icon if str(entry) in self.favorites_set else self.star_gray_icon)
                    item.setData(1, Qt.UserRole, str(entry))
                    item.setFlags(item.flags() & ~Qt.ItemIsSelectable)
                    # Add a dummy child to make it expandable
                    item.addChild(QTreeWidgetItem())
                elif entry.is_file() and entry.suffix.lower() == '.geos':
                    file_item = QTreeWidgetItem(parent_item, ["", str(entry)])
                    file_item.setIcon(0, QIcon())  # No star for files
                    file_item.setData(1, Qt.UserRole, str(entry))
                    file_item.setFlags(file_item.flags() | Qt.ItemIsSelectable)
        except Exception:
            pass

    def on_item_expanded(self, item):
        """Load subfolders when a tree item is expanded."""
        # Check if the item has the dummy child, meaning it hasn't been populated yet
        if item.childCount() > 0 and not item.child(0).data(1, Qt.UserRole):
            item.takeChild(0)  # Remove the dummy item
            path = item.data(1, Qt.UserRole)
            if path:
                self.add_subfolders(item, path)

    def on_folder_tree_item_clicked(self, item, column):
        """Handle clicks in the folder tree for selection or favoriting."""
        from PyQt5.QtGui import QIcon
        path = item.data(1, Qt.UserRole)
        if not path:
            return

        # Column 0 is the star for favoriting (only for folders)
        if column == 0 and os.path.isdir(path):
            if path in self.favorites_set:
                self.favorites_set.remove(path)
            else:
                self.favorites_set.add(path)
            self.save_favorites_tree()
            self.populate_folder_tree() # Repopulate to update favorites list and icons

        # Column 1 is for selection (only for .geos files)
        elif column == 1 and path.lower().endswith('.geos'):
            print(f"Selected .geos file: {path}")
            # You can add logic here to load the selected .geos file
            self.load_map_state(file_path=path)

    def save_favorites_tree(self):
        import configparser
        config = configparser.ConfigParser()
        config['Favorites'] = {}
        for i, fav in enumerate(sorted(self.favorites_set)):
            config['Favorites'][f'folder_{i}'] = fav
        with open(self.favorites_config_file, 'w') as f:
            config.write(f)

    
    
    def initialize_openlayers_map(self):
        """Initialize the OpenLayers map in the web view."""
        html_content = '''
        <!DOCTYPE html>
        <html>
        <head>
            <title>Web Map Creator</title>
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css">
            <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol-popup@4.0.0/src/ol-popup.css" />
            <script src="https://cdn.jsdelivr.net/npm/ol-popup@4.0.0/dist/ol-popup.js"></script>
            <style>
                html, body, #map { 
                    margin: 0; 
                    padding: 0; 
                    width: 100%; 
                    height: 100%; 
                    font-family: Arial, sans-serif;
                }
                .ol-control {
                    background-color: rgba(255,255,255,0.8);
                    border-radius: 4px;
                    padding: 2px;
                }
                .ol-control:hover {
                    background-color: rgba(255,255,255,0.9);
                }
                .ol-control button {
                    background-color: #4c7ea1;
                    color: white;
                    border: none;
                    border-radius: 2px;
                    margin: 1px;
                    font-size: 1.1em;
                }
                .ol-control button:hover {
                    background-color: #3a6a8a;
                }
                .ol-scale-line {
                    background: rgba(255,255,255,0.8);
                    padding: 2px 5px;
                    border-radius: 4px;
                }
                .map-controls {
                    position: absolute;
                    top: 10px;
                    right: 10px;
                    z-index: 1000;
                    display: flex;
                    flex-direction: column;
                    gap: 5px;
                }
                .map-controls button {
                    background: rgba(255, 255, 255, 0.9);
                    border: 1px solid #ccc;
                    border-radius: 4px;
                    padding: 8px 12px;
                    cursor: pointer;
                    font-size: 14px;
                    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
                }
                .map-controls button:hover {
                    background: #f0f0f0;
                }
                .ol-popup {
                    position: absolute;
                    background-color: white;
                    box-shadow: 0 1px 4px rgba(0,0,0,0.2);
                    padding: 15px;
                    border-radius: 10px;
                    border: 1px solid #cccccc;
                    bottom: 12px;
                    left: -50px;
                    min-width: 200px;
                }
                .ol-popup:after, .ol-popup:before {
                    top: 100%;
                    border: solid transparent;
                    content: " ";
                    height: 0;
                    width: 0;
                    position: absolute;
                    pointer-events: none;
                }
                .ol-popup:after {
                    border-top-color: white;
                    border-width: 10px;
                    left: 48px;
                    margin-left: -10px;
                }
                .ol-popup:before {
                    border-top-color: #cccccc;
                    border-width: 11px;
                    left: 48px;
                    margin-left: -11px;
                }
                .ol-popup-closer {
                    text-decoration: none;
                    position: absolute;
                    top: 2px;
                    right: 8px;
                }
                .ol-popup-closer:after {
                    content: "✖";
                }
            </style>
        </head>
        <body>
            <div id="map" class="map"></div>
            <div id="popup" class="ol-popup">
                <a href="#" id="popup-closer" class="ol-popup-closer"></a>
                <div id="popup-content"></div>
            </div>
            <div class="map-controls">
                <button id="toggle-theme">Toggle Dark Mode</button>
                <button id="toggle-basemap">Hide Base Map</button>
                <button id="zoom-to-extent">Zoom to Full Extent</button>
            </div>
            <script type="text/javascript">
                // Base layers
                var lightBase = new ol.layer.Tile({
                    source: new ol.source.OSM(),
                    visible: true,
                    type: 'base',
                    title: 'OpenStreetMap (Light)'
                });
                
                var darkBase = new ol.layer.Tile({
                    source: new ol.source.XYZ({
                        url: 'https://{a-c}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
                        attributions: '© OpenStreetMap contributors, © CartoDB'
                    }),
                    visible: false,
                    type: 'base',
                    title: 'CartoDB Dark Matter'
                });
                
                var isDarkMode = false;
                var baseLayers = [lightBase, darkBase];
                
                // Map initialization
                var map = new ol.Map({
                    target: 'map',
                    layers: baseLayers,
                    view: new ol.View({
                        center: ol.proj.fromLonLat([0, 0]),
                        zoom: 2
                    }),
                    controls: ol.control.defaults({
                        attributionOptions: {
                            collapsible: false
                        }
                    }).extend([
                        new ol.control.ScaleLine(),
                        new ol.control.Zoom()
                    ])
                });
                
                // Popup overlay
                var popup = new ol.Overlay({
                    element: document.getElementById('popup'),
                    autoPan: true,
                    autoPanAnimation: {
                        duration: 250
                    }
                });
                map.addOverlay(popup);
                
                // Close popup when clicking the closer
                document.getElementById('popup-closer').onclick = function() {
                    popup.setPosition(undefined);
                    return false;
                };
                
                // Layer storage
                window.ol_map = map;
                window.ol_layers = {};
                
                // Functions for layer management
                function addWmsLayer(url, layerName, layerId) {
                    var wmsSource = new ol.source.TileWMS({
                        url: url,
                        params: {'LAYERS': layerName, 'TILED': true, 'VERSION': '1.3.0'},
                        serverType: 'geoserver',
                        transition: 0,
                        crossOrigin: 'anonymous'
                    });
                    
                    var wmsLayer = new ol.layer.Tile({
                        source: wmsSource,
                        name: layerName,
                        id: layerId,
                        visible: true,
                        type: 'overlay'
                    });
                    
                    // Add click interaction for WMS layers
                    wmsLayer.getSource().on('tileloadstart', function() {
                        map.getTargetElement().style.cursor = 'wait';
                    });
                    
                    wmsLayer.getSource().on('tileloadend', function() {
                        map.getTargetElement().style.cursor = '';
                    });
                    
                    // Add the layer to the map
                    map.addLayer(wmsLayer);
                    window.ol_layers[layerId] = wmsLayer;
                    
                    // Return the layer's extent if available
                    return wmsLayer;
                }
                
                function setLayerOpacity(layerId, opacity) {
                    if (window.ol_layers[layerId]) {
                        window.ol_layers[layerId].setOpacity(opacity / 100);
                    }
                }
                
                function toggleLayerVisibility(layerId, visible) {
                    if (window.ol_layers[layerId]) {
                        window.ol_layers[layerId].setVisible(visible);
                    }
                }
                
                function removeLayer(layerId) {
                    if (window.ol_layers[layerId]) {
                        map.removeLayer(window.ol_layers[layerId]);
                        delete window.ol_layers[layerId];
                    }
                }
                
                function clearMap() {
                    // Remove all layers except base layers
                    map.getLayers().forEach(function(layer) {
                        if (layer.get('type') !== 'base') {
                            map.removeLayer(layer);
                        }
                    });
                    window.ol_layers = {};
                }
                
                function getMapView() {
                    var view = map.getView();
                    var center = ol.proj.toLonLat(view.getCenter());
                    var zoom = view.getZoom();
                    return {
                        center: center,
                        zoom: zoom
                    };
                }
                
                function setMapView(center, zoom) {
                    var view = map.getView();
                    if (center && center.length === 2) {
                        view.setCenter(ol.proj.fromLonLat(center));
                    }
                    if (zoom !== undefined) {
                        view.setZoom(zoom);
                    }
                }

                function zoomToLayer(extent, crs) {
                    console.log('Attempting to zoom to:', extent, 'CRS:', crs);
                    if (!extent || extent.length !== 4) {
                        console.error('Invalid extent received:', extent);
                        return;
                    }
                    try {
                        map.getView().fit(extent, {
                            duration: 1200,
                            padding: [50, 50, 50, 50],
                            maxZoom: 19
                        });
                    } catch (e) {
                        console.error('Error during zoom operation:', e);
                    }
                }
                
                // Toggle between light and dark base maps
                function toggleTheme() {
                    isDarkMode = !isDarkMode;
                    lightBase.setVisible(!isDarkMode);
                    darkBase.setVisible(isDarkMode);
                    document.getElementById('toggle-theme').textContent = 
                        isDarkMode ? 'Toggle Light Mode' : 'Toggle Dark Mode';
                }
                
                // Toggle base map visibility
                function toggleBaseMap() {
                    var btn = document.getElementById('toggle-basemap');
                    var isVisible = lightBase.getVisible() || darkBase.getVisible();
                    lightBase.setVisible(!isVisible);
                    darkBase.setVisible(isDarkMode && !isVisible);
                    btn.textContent = isVisible ? 'Show Base Map' : 'Hide Base Map';
                }
                
                // Zoom to full extent of all visible layers
                function zoomToFullExtent() {
                    var extent = ol.extent.createEmpty();
                    var hasLayers = false;
                    
                    // Check if we have any WMS layers
                    var hasWmsLayers = Object.values(window.ol_layers).some(layer => 
                        layer.get('type') === 'overlay'
                    );
                    
                    if (!hasWmsLayers) {
                        // If no WMS layers, just zoom to world extent
                        map.getView().setZoom(2);
                        return;
                    }
                    
                    // For WMS layers, we need to fetch the extent from the server
                    // This is a simplified approach - in a real app, you'd want to cache this
                    Object.values(window.ol_layers).forEach(function(layer) {
                        if (layer.getVisible() && layer.get('type') === 'overlay') {
                            // In a real implementation, you would fetch the layer's extent from GeoServer
                            // For now, we'll just use a default extent
                            var defaultExtent = [-180, -85, 180, 85]; // Default world extent
                            ol.extent.extend(extent, defaultExtent);
                            hasLayers = true;
                        }
                    });
                    
                    if (hasLayers && !ol.extent.isEmpty(extent)) {
                        map.getView().fit(extent, {
                            padding: [50, 50, 50, 50],
                            duration: 1000
                        });
                    }
                }
                
                // Add event listeners for the control buttons
                document.getElementById('toggle-theme').addEventListener('click', toggleTheme);
                document.getElementById('toggle-basemap').addEventListener('click', toggleBaseMap);
                document.getElementById('zoom-to-extent').addEventListener('click', zoomToFullExtent);
                
                // Add click handler for WMS GetFeatureInfo
                map.on('singleclick', function(evt) {
                    // Clear existing popup
                    popup.setPosition(undefined);
                    
                    // Only proceed if we have WMS layers
                    var hasWmsLayers = Object.values(window.ol_layers).some(layer => 
                        layer.get('type') === 'overlay' && layer.getVisible()
                    );
                    
                    if (!hasWmsLayers) return;
                    
                    // Show loading cursor
                    map.getTargetElement().style.cursor = 'wait';
                    
                    // Get the first visible WMS layer
                    var wmsLayer = null;
                    map.getLayers().forEach(function(layer) {
                        if (!wmsLayer && layer.get('type') === 'overlay' && layer.getVisible()) {
                            wmsLayer = layer;
                        }
                    });
                    
                    if (!wmsLayer) {
                        map.getTargetElement().style.cursor = '';
                        return;
                    }
                    
                    var view = map.getView();
                    var viewResolution = view.getResolution();
                    var url = wmsLayer.getSource().getFeatureInfoUrl(
                        evt.coordinate,
                        viewResolution,
                        view.getProjection(),
                        {
                            'INFO_FORMAT': 'text/html',
                            'FEATURE_COUNT': 5
                        }
                    );
                    
                    if (url) {
                        fetch(url)
                            .then(function(response) { return response.text(); })
                            .then(function(html) {
                                if (html) {
                                    document.getElementById('popup-content').innerHTML = html;
                                    popup.setPosition(evt.coordinate);
                                }
                                map.getTargetElement().style.cursor = '';
                            })
                            .catch(function() {
                                map.getTargetElement().style.cursor = '';
                            });
                    } else {
                        map.getTargetElement().style.cursor = '';
                    }
                });
                
                // Change cursor when over clickable features
                map.on('pointermove', function(e) {
                    if (e.dragging) {
                        return;
                    }
                    
                    var hasWmsLayers = Object.values(window.ol_layers).some(layer => 
                        layer.get('type') === 'overlay' && layer.getVisible()
                    );
                    
                    if (hasWmsLayers) {
                        map.getTargetElement().style.cursor = 'pointer';
                    } else {
                        map.getTargetElement().style.cursor = '';
                    }
                });
            </script>
        </body>
        </html>
        '''
        
        self.web_view.setHtml(html_content, QUrl(self.geoserver_url))
    
    def on_added_layer_double_clicked(self, item, column):
        """Handle double-click on an added layer to show its extent."""
        layer_id = item.data(1, Qt.UserRole)
        if layer_id and layer_id in self.added_layers:
            layer_info = self.added_layers[layer_id]
            extent = layer_info.get('extent')
            if extent:
                self.extent_display.setText(f"Extent: {extent}")
            else:
                self.extent_display.setText("Extent not available.")

    def zoom_to_selected_layer(self):
        """Zoom the map to the extent of the selected layer."""
        if not hasattr(self, 'added_layers_tree') or self.added_layers_tree is None: return
        selected_items = self.added_layers_tree.selectedItems()
        if not selected_items: return

        layer_id = selected_items[0].data(0, Qt.UserRole)
        layer_info = self.added_layers.get(layer_id)
        if not layer_info: return

        actual_name = layer_info['actual_name']
        
        try:
            # Step 1: Get layer.xml to find the resource href
            layer_url = f"{self.geoserver_url}/rest/layers/{actual_name}.xml"
            layer_response = requests.get(layer_url, auth=(self.username, self.password), headers={"Accept": "application/xml"}, timeout=10)
            if layer_response.status_code != 200:
                QMessageBox.warning(self, "Error", f"Could not fetch layer details for {actual_name} (Status: {layer_response.status_code}).")
                return

            layer_root = ET.fromstring(layer_response.content)
            resource_elem = layer_root.find('.//resource')
            if resource_elem is None or 'href' not in resource_elem.attrib:
                QMessageBox.information(self, "Zoom", f"No resource link found for layer {actual_name}.")
                return
            
            # Step 2: Get the resource XML from the href
            resource_url = resource_elem.attrib['href']
            resource_response = requests.get(resource_url, auth=(self.username, self.password), headers={"Accept": "application/xml"}, timeout=10)
            if resource_response.status_code != 200:
                QMessageBox.warning(self, "Error", f"Could not fetch resource details from {resource_url} (Status: {resource_response.status_code}).")
                return
            
            resource_root = ET.fromstring(resource_response.content)
            
            # Step 3: Parse bounding box from the resource XML
            latlon_bbox = resource_root.find('.//latLonBoundingBox')
            native_bbox = resource_root.find('.//nativeBoundingBox')
            bbox_elem = latlon_bbox if latlon_bbox is not None else native_bbox
            
            if bbox_elem is None:
                QMessageBox.information(self, "Zoom", f"No bounding box found in resource for layer {actual_name}.")
                return

            minx = float(bbox_elem.find('minx').text)
            maxx = float(bbox_elem.find('maxx').text)
            miny = float(bbox_elem.find('miny').text)
            maxy = float(bbox_elem.find('maxy').text)
            extent = [minx, miny, maxx, maxy]
            
            crs = 'EPSG:4326'
            if bbox_elem.tag == 'nativeBoundingBox':
                crs_elem = bbox_elem.find('crs')
                if crs_elem is not None:
                    crs = crs_elem.text

            self.extent_display.setText(f"Extent: {extent}, CRS: {crs}")
            js_call = f"zoomToLayer({json.dumps(extent)}, '{crs}');"
            self.web_view.page().runJavaScript(js_call)

        except requests.exceptions.Timeout:
            QMessageBox.critical(self, "Error", "Request to GeoServer timed out.")
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed to zoom to layer: {str(e)}")

    def add_layer_to_map(self, layer_info):
        """Add a single layer to the map from loaded state."""
        layer_id = layer_info.get('layer_id', str(uuid.uuid4()))
        layer_name = layer_info['actual_name']
        
        # Add to tree
        tree_item = QTreeWidgetItem(self.added_layers_tree)
        tree_item.setText(1, layer_name)
        tree_item.setData(0, Qt.UserRole, layer_id)
        
        # Visibility checkbox
        visibility_checkbox = QCheckBox()
        visibility_checkbox.setChecked(layer_info.get('visible', True))
        visibility_checkbox.stateChanged.connect(
            lambda state, l_id=layer_id: self.toggle_layer_visibility(l_id, state == Qt.Checked)
        )
        self.added_layers_tree.setItemWidget(tree_item, 0, visibility_checkbox)
        
        # Transparency slider
        slider = QSlider(Qt.Horizontal)
        slider.setRange(0, 100)
        slider.setValue(int(layer_info.get('opacity', 1.0) * 100))
        slider.valueChanged.connect(
            lambda value, l_id=layer_id: self.set_layer_transparency(l_id, value)
        )
        self.added_layers_tree.setItemWidget(tree_item, 2, slider)
        
        # Store layer info
        self.added_layers[layer_id] = {
            'actual_name': layer_name,
            'item': tree_item,
            'type': layer_info.get('type', 'WMS'),
            'visibility_widget': visibility_checkbox,
            'slider_widget': slider
        }
        
        # Add to map
        wms_url = f"{self.geoserver_url}/wms"
        js_call = f"addWmsLayer('{wms_url}', '{layer_name}', '{layer_id}');"
        self.web_view.page().runJavaScript(js_call)
        
        # Set initial visibility and opacity
        self.toggle_layer_visibility(layer_id, visibility_checkbox.isChecked())
        self.set_layer_transparency(layer_id, slider.value())

    def toggle_layer_visibility(self, layer_id, visible):
        """Toggle the visibility of a layer on the map."""
        if not hasattr(self, 'web_view'): return
        js_call = f"toggleLayerVisibility('{layer_id}', {str(visible).lower()});"
        self.web_view.page().runJavaScript(js_call)

    def set_layer_transparency(self, layer_id, value):
        """Set the transparency of a layer on the map."""
        if not hasattr(self, 'web_view'): return
        opacity = value / 100.0
        js_call = f"setLayerOpacity('{layer_id}', {opacity});"
        self.web_view.page().runJavaScript(js_call)

    def save_as_html_react(self):
        """Placeholder for saving the map as a ReactJS HTML file."""
        QMessageBox.information(self, "Not Implemented", "This feature is not yet implemented.")
    
    def save_map_state(self):
        """Save the current map state to a .geos file."""
        file_path, _ = QFileDialog.getSaveFileName(
            self, "Save Map State", "", "GeoServer Map Files (*.geos)"
        )
        
        if not file_path:
            return
        
        # Ensure .geos extension
        if not file_path.endswith('.geos'):
            file_path += '.geos'
        
        try:
            # Get map view state
            def save_with_view(view_state_json):
                try:
                    view_state = json.loads(view_state_json) if view_state_json else {}
                    
                    # Create config
                    config = configparser.ConfigParser()
                    
                    # General info
                    config['General'] = {
                        'layer_count': str(len(self.added_layers))
                    }
                    
                    # View state
                    config['View'] = {
                        'center': json.dumps(view_state.get('center', [])),
                        'zoom': str(view_state.get('zoom', 2))
                    }
                    
                    # Layers
                    for i, (layer_id, layer_info) in enumerate(self.added_layers.items()):
                        section_name = f'Layer_{i}'
                        # Get current visibility and transparency
                        visibility_widget = layer_info.get('visibility_widget')
                        slider_widget = layer_info.get('slider_widget')
                        
                        visible = visibility_widget.isChecked() if visibility_widget else True
                        transparency = slider_widget.value() if slider_widget else 100
                        
                        config[section_name] = {
                            'layer_id': layer_id,
                            'display_name': layer_info['display_name'],
                            'actual_name': layer_info['actual_name'],
                            'visible': str(visible),
                            'transparency': str(transparency)
                        }
                    
                    # Save to file
                    with open(file_path, 'w') as configfile:
                        config.write(configfile)
                    
                    QMessageBox.information(self, "Success", f"Map state saved to {file_path}")
                except Exception as e:
                    QMessageBox.critical(self, "Error", f"Failed to save map state: {str(e)}")
            
            # Get view state from JavaScript
            self.web_view.page().runJavaScript("JSON.stringify(getMapView());", save_with_view)
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed to save map state: {str(e)}")
    
    def load_map_state(self, file_path=None):
        """Load a map state from a .geos file."""
        if not file_path:
            file_path, _ = QFileDialog.getOpenFileName(
                self, "Load Map State", "", "GeoServer Map Files (*.geos)"
            )
        
        if not file_path or not os.path.exists(file_path):
            return
        
        try:
            # Clear existing map state
            self.web_view.page().runJavaScript("clearMap();")
            self.added_layers_tree.clear()
            self.added_layers.clear()
            
            # Load config
            config = configparser.ConfigParser()
            config.read(file_path)
            
            # Load layers
            layer_sections = sorted([s for s in config.sections() if s.startswith('Layer_')])
            for section_name in layer_sections:
                if config.has_option(section_name, 'actual_name'): # Check for valid layer section
                    layer_info = {
                        'layer_id': config.get(section_name, 'layer_id'),
                        'type': config.get(section_name, 'type', fallback='WMS'),
                        'actual_name': config.get(section_name, 'actual_name'),
                        'visible': config.getboolean(section_name, 'visible', fallback=True),
                        'opacity': config.getfloat(section_name, 'opacity', fallback=1.0)
                    }
                    self.add_layer_to_map(layer_info)

            # Set map view after layers are loaded
            if 'View' in config:
                view_config = config['View']
                center = json.loads(view_config.get('center', '[]'))
                zoom = view_config.getfloat('zoom', 2)
                js_call = f"setMapView({json.dumps(center)}, {zoom});"
                self.web_view.page().runJavaScript(js_call)
            
            # Restore view
            if 'View' in config:
                try:
                    center = json.loads(config.get('View', 'center', fallback='[]'))
                    zoom = config.getfloat('View', 'zoom', fallback=2.0)
                    
                    if center and len(center) == 2:
                        js_call = f"setMapView([{center[0]}, {center[1]}], {zoom});"
                        self.web_view.page().runJavaScript(js_call)
                except Exception as e:
                    print(f"Error restoring view: {e}")
            
            QMessageBox.information(self, "Success", f"Map state loaded from {file_path}")
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed to load map state: {str(e)}")
    
    def save_as_html_react(self):
        """Save the current map as an HTML file with ReactJS implementation."""
        folder_path = QFileDialog.getExistingDirectory(self, "Select Folder to Save HTML")
        
        if not folder_path:
            return
        
        try:
            # Get current map view state
            def save_html_with_view(view_state_json):
                try:
                    view_state = json.loads(view_state_json) if view_state_json else {}
                    center = view_state.get('center', [0, 0])
                    zoom = view_state.get('zoom', 2)
                    
                    # Generate ReactJS HTML content
                    html_content = self.generate_react_html(center, zoom)
                    
                    # Save main HTML file
                    html_file_path = os.path.join(folder_path, "geoserver_map.html")
                    with open(html_file_path, 'w', encoding='utf-8') as f:
                        f.write(html_content)
                    
                    # Create a simple package.json for dependencies
                    package_json = {
                        "name": "geoserver-web-map",
                        "version": "1.0.0",
                        "description": "Web map with GeoServer layers",
                        "dependencies": {
                            "ol": "^6.5.0",
                            "react": "^17.0.2",
                            "react-dom": "^17.0.2"
                        },
                        "scripts": {
                            "start": "npx serve ."
                        }
                    }
                    
                    package_json_path = os.path.join(folder_path, "package.json")
                    with open(package_json_path, 'w') as f:
                        json.dump(package_json, f, indent=2)
                    
                    # Create README with instructions
                    readme_content = """# GeoServer Web Map

This is a React-based web map application that displays layers from GeoServer.

## How to run

1. Install dependencies:
   ```
   npm install
   ```

2. Start the development server:
   ```
   npm start
   ```

3. Open your browser to view the map.

## Features

- Interactive OpenLayers map
- Layer visibility controls
- Transparency adjustments
- Responsive design
"""
                    readme_path = os.path.join(folder_path, "README.md")
                    with open(readme_path, 'w') as f:
                        f.write(readme_content)
                    
                    QMessageBox.information(self, "Success", f"Web map saved to {folder_path}\n\nTo run:\n1. Navigate to the folder in terminal\n2. Run 'npm install'\n3. Run 'npm start'")
                except Exception as e:
                    QMessageBox.critical(self, "Error", f"Failed to save HTML: {str(e)}")
            
            # Get view state from JavaScript
            self.web_view.page().runJavaScript("JSON.stringify(getMapView());", save_html_with_view)
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed to save HTML: {str(e)}")
    
    def generate_react_html(self, center, zoom):
        """Generate ReactJS-based HTML content for the map."""
        # Prepare layer data for React
        layers_data = []
        for layer_id, layer_info in self.added_layers.items():
            visibility_widget = layer_info.get('visibility_widget')
            slider_widget = layer_info.get('slider_widget')
            
            visible = visibility_widget.isChecked() if visibility_widget else True
            transparency = slider_widget.value() if slider_widget else 100
            
            layers_data.append({
                'id': layer_id,
                'name': layer_info['actual_name'],
                'visible': visible,
                'transparency': transparency
            })
        
        layers_json = json.dumps(layers_data)
        
        html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GeoServer Web Map</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css">
    <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
    <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
    <style>
        html, body {{
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
            font-family: Arial, sans-serif;
        }}
        #root {{
            width: 100%;
            height: 100%;
            display: flex;
            flex-direction: column;
        }}
        .header {{
            background-color: #333;
            color: white;
            padding: 10px;
            text-align: center;
            flex-shrink: 0;
        }}
        .container {{
            display: flex;
            flex: 1;
            overflow: hidden;
        }}
        .sidebar {{
            width: 300px;
            background-color: #f5f5f5;
            padding: 15px;
            overflow-y: auto;
            flex-shrink: 0;
        }}
        .map-container {{
            flex: 1;
            position: relative;
            overflow: hidden;
        }}
        #map {{
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
        }}
        .layer-item {{
            margin-bottom: 15px;
            padding: 10px;
            background-color: white;
            border-radius: 5px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }}
        .layer-header {{
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
        }}
        .visibility-checkbox {{
            margin-right: 10px;
        }}
        .transparency-control {{
            margin-top: 8px;
        }}
        .transparency-label {{
            display: flex;
            justify-content: space-between;
            margin-bottom: 5px;
        }}
    </style>
</head>
<body>
    <div id="root"></div>
    
    <script>
        // Layer data from Python
        const initialLayers = {layers_json};
        
        // Map configuration
        const geoserverUrl = "{self.geoserver_url}";
        
        // React components
        function LayerItem({{ layer, onVisibilityChange, onTransparencyChange }}) {{
            return (
                React.createElement('div', {{ className: 'layer-item' }},
                    React.createElement('div', {{ className: 'layer-header' }},
                        React.createElement('label', {{ className: 'visibility-checkbox' }},
                            React.createElement('input', {{
                                type: 'checkbox',
                                checked: layer.visible,
                                onChange: (e) => onVisibilityChange(layer.id, e.target.checked)
                            }}),
                            ' '
                        ),
                        React.createElement('span', null, layer.name)
                    ),
                    React.createElement('div', {{ className: 'transparency-control' }},
                        React.createElement('div', {{ className: 'transparency-label' }},
                            React.createElement('span', null, 'Transparency'),
                            React.createElement('span', null, `${{100 - layer.transparency}}%`)
                        ),
                        React.createElement('input', {{
                            type: 'range',
                            min: 0,
                            max: 100,
                            value: layer.transparency,
                            onChange: (e) => onTransparencyChange(layer.id, parseInt(e.target.value))
                        }})
                    )
                )
            );
        }}
        
        function Sidebar({{ layers, onVisibilityChange, onTransparencyChange }}) {{
            return (
                React.createElement('div', {{ className: 'sidebar' }},
                    React.createElement('h3', null, 'Layers'),
                    layers.map(layer => 
                        React.createElement(LayerItem, {{
                            key: layer.id,
                            layer: layer,
                            onVisibilityChange: onVisibilityChange,
                            onTransparencyChange: onTransparencyChange
                        }})
                    )
                )
            );
        }}
        
        function MapContainer({{ center, zoom, layers }}) {{
            const mapRef = React.useRef(null);
            const mapInstanceRef = React.useRef(null);
            const layersRef = React.useRef({{}});
            
            React.useEffect(() => {{
                if (!mapRef.current) return;
                
                // Initialize base layer
                const baseLayer = new ol.layer.Tile({{
                    source: new ol.source.OSM()
                }});
                
                // Initialize map
                const map = new ol.Map({{
                    target: mapRef.current,
                    layers: [baseLayer],
                    view: new ol.View({{
                        center: ol.proj.fromLonLat(center),
                        zoom: zoom
                    }})
                }});
                
                mapInstanceRef.current = map;
                
                // Add initial layers
                layers.forEach(layer => {{
                    if (layer.visible) {{
                        addWmsLayer(layer);
                    }}
                }});
                
                // Handle map resizing
                const resizeObserver = new ResizeObserver(() => {{
                    map.updateSize();
                }});
                resizeObserver.observe(mapRef.current);
                
                return () => {{
                    map.setTarget(null);
                    resizeObserver.disconnect();
                }};
            }}, []);
            
            // Add WMS layer function
            const addWmsLayer = (layerData) => {{
                const wmsSource = new ol.source.TileWMS({{
                    url: `${{geoserverUrl}}/wms`,
                    params: {{'LAYERS': layerData.name, 'TILED': true}},
                    serverType: 'geoserver',
                    transition: 0
                }});
                
                const wmsLayer = new ol.layer.Tile({{
                    source: wmsSource,
                    opacity: layerData.transparency / 100
                }});
                
                wmsLayer.set('id', layerData.id);
                mapInstanceRef.current.addLayer(wmsLayer);
                layersRef.current[layerData.id] = wmsLayer;
            }};
            
            // Remove layer function
            const removeLayer = (layerId) => {{
                const layer = layersRef.current[layerId];
                if (layer) {{
                    mapInstanceRef.current.removeLayer(layer);
                    delete layersRef.current[layerId];
                }}
            }};
            
            // Update layers when they change
            React.useEffect(() => {{
                if (!mapInstanceRef.current) return;
                
                layers.forEach(layer => {{
                    const existingLayer = layersRef.current[layer.id];
                    
                    if (layer.visible && !existingLayer) {{
                        // Add layer if visible and not already added
                        addWmsLayer(layer);
                    }} else if (!layer.visible && existingLayer) {{
                        // Remove layer if not visible but currently added
                        removeLayer(layer.id);
                    }} else if (existingLayer) {{
                        // Update properties of existing layer
                        existingLayer.setVisible(layer.visible);
                        existingLayer.setOpacity(layer.transparency / 100);
                    }}
                }});
            }}, [layers]);
            
            return React.createElement('div', {{ className: 'map-container' }},
                React.createElement('div', {{ id: 'map', ref: mapRef }})
            );
        }}
        
        function App() {{
            const [layers, setLayers] = React.useState(initialLayers);
            
            const handleVisibilityChange = (layerId, visible) => {{
                setLayers(prevLayers => 
                    prevLayers.map(layer => 
                        layer.id === layerId ? {{ ...layer, visible }} : layer
                    )
                );
            }};
            
            const handleTransparencyChange = (layerId, transparency) => {{
                setLayers(prevLayers => 
                    prevLayers.map(layer => 
                        layer.id === layerId ? {{ ...layer, transparency }} : layer
                    )
                );
            }};
            
            return (
                React.createElement('div', {{ id: 'root' }},
                    React.createElement('div', {{ className: 'header' }},
                        React.createElement('h1', null, 'GeoServer Web Map')
                    ),
                    React.createElement('div', {{ className: 'container' }},
                        React.createElement(Sidebar, {{
                            layers: layers,
                            onVisibilityChange: handleVisibilityChange,
                            onTransparencyChange: handleTransparencyChange
                        }}),
                        React.createElement(MapContainer, {{
                            center: [{center[0]}, {center[1]}],
                            zoom: {zoom},
                            layers: layers
                        }})
                    )
                )
            );
        }}
        
        // Render the app
        ReactDOM.render(
            React.createElement(App),
            document.getElementById('root')
        );
    </script>
</body>
</html>"""
        
        return html_content


def main():
    """Main function to run the application."""
    # Ensure a QApplication instance exists
    app = QApplication.instance() 
    if not app:
        app = QApplication(sys.argv)

    try:
        print("DEBUG: Creating dialog instance.")
        dialog = WebMapGeosCreator()
        print("DEBUG: Showing dialog.")
        dialog.show()
        dialog.raise_()
        dialog.activateWindow()
        print("DEBUG: Dialog shown, starting event loop.")
        app.exec_()
        print("DEBUG: Event loop finished.")
    except Exception as e:
        print(f"FATAL: An error occurred: {e}")
        import traceback
        traceback.print_exc()

if __name__ == '__main__':
    main()
