"""
QGIS Project Handler

Manages creation and manipulation of QGIS projects and layers.
Handles creating new maps, creating group layers, and copying layers.
"""

from pathlib import Path
from typing import List, Optional, Dict, Any, Tuple
from datetime import datetime
import os
from .spatialite_manager import SpatialiteManager
from . import debug_logger
from .app_paths import ensure_workspace
from .layer_kind import LayerKindResolver
from .models import LayerKind


class ProjectHandler:
    """Handles QGIS project file operations and layer management"""

    # Default project directory (Documents/EMDSProjects)
    PROJECTS_DIR = ensure_workspace().projects_dir

    def __init__(self, projects_dir: str = None):
        """
        Initialize project handler

        Args:
            projects_dir: Directory to store QGIS projects (default: ~/.emds/projects)
        """
        if projects_dir:
            self.projects_dir = Path(projects_dir)
        else:
            self.projects_dir = ensure_workspace().projects_dir

        # Create projects directory if it doesn't exist
        self.projects_dir.mkdir(parents=True, exist_ok=True)

        # Raster-support Phase 2: names of layers skipped by the last
        # copy_layers_to_geopackage call because they were rasters
        # (GeoPackage export here is vector-only). Callers (the dock
        # widget) read this after the call to surface a clear UX
        # warning instead of letting the user wonder why a layer
        # silently disappeared from the GeoPackage. Reset at the
        # start of every copy_layers_to_geopackage call.
        self.last_skipped_raster_layers: List[str] = []

    def _check_vector_layer_geometry(self, layer) -> Optional[Dict[str, int]]:
        """Run QGIS geometry validity checks for a vector layer."""
        try:
            import processing
        except Exception as exc:
            debug_logger.warning("QGIS processing is not available for geometry validation", {
                "layer_name": getattr(layer, "name", lambda: "")(),
                "exception": str(exc),
            })
            return None

        algorithms = [
            (
                "native:checkvalidity",
                {
                    "INPUT_LAYER": layer,
                    "METHOD": 2,
                    "IGNORE_RING_SELF_INTERSECTION": False,
                    "VALID_OUTPUT": "memory:",
                    "INVALID_OUTPUT": "memory:",
                    "ERROR_OUTPUT": "memory:",
                },
            ),
            (
                "qgis:checkvalidity",
                {
                    "INPUT_LAYER": layer,
                    "METHOD": 2,
                    "VALID_OUTPUT": "memory:",
                    "INVALID_OUTPUT": "memory:",
                    "ERROR_OUTPUT": "memory:",
                },
            ),
        ]

        for algorithm_id, parameters in algorithms:
            try:
                result = processing.run(algorithm_id, parameters)
                invalid_layer = result.get("INVALID_OUTPUT")
                error_layer = result.get("ERROR_OUTPUT")
                invalid_count = invalid_layer.featureCount() if invalid_layer is not None else 0
                error_count = error_layer.featureCount() if error_layer is not None else 0
                return {
                    "invalid_count": invalid_count,
                    "error_count": error_count,
                }
            except Exception as exc:
                debug_logger.warning("Geometry check algorithm failed", {
                    "layer_name": layer.name(),
                    "algorithm": algorithm_id,
                    "exception": str(exc),
                })

        return None

    def _fix_vector_layer_geometry(self, layer, fixed_layer_name: str):
        """Run QGIS fix geometries on a vector layer and return a corrected layer."""
        try:
            import processing
        except Exception as exc:
            debug_logger.warning("QGIS processing is not available for geometry fixing", {
                "layer_name": layer.name(),
                "exception": str(exc),
            })
            return None

        for algorithm_id in ("native:fixgeometries", "qgis:fixgeometries"):
            try:
                result = processing.run(
                    algorithm_id,
                    {
                        "INPUT": layer,
                        "OUTPUT": "memory:",
                    },
                )
                fixed_layer = result.get("OUTPUT")
                if fixed_layer is not None and fixed_layer.isValid():
                    fixed_layer.setName(fixed_layer_name)
                    return fixed_layer
            except Exception as exc:
                debug_logger.warning("Geometry fix algorithm failed", {
                    "layer_name": layer.name(),
                    "algorithm": algorithm_id,
                    "exception": str(exc),
                })

        return None

    def _validate_and_fix_vector_layer(
        self,
        layer,
        *,
        layer_name: str,
        database_path: Optional[Path] = None,
        table_name: str = "",
    ):
        """
        Validate vector layer geometries and fix/persist them when needed.

        Returns the original layer when already valid, a corrected layer when a fix
        succeeds, or the original layer when validation could not be run.
        """
        try:
            from qgis.core import QgsVectorLayer  # noqa: F401  (kept for explicit QGIS dependency check)
        except ImportError:
            raise RuntimeError("QGIS is not available in this environment")

        # Use the single-source-of-truth resolver instead of ad-hoc isinstance
        # (raster-support Phase 1). Non-vector layers skip geometry validation.
        if not LayerKindResolver.is_vector(layer):
            return layer, False

        geometry_status = self._check_vector_layer_geometry(layer)
        if geometry_status is None:
            return layer, False

        invalid_count = geometry_status["invalid_count"] + geometry_status["error_count"]
        if invalid_count == 0:
            debug_logger.debug("Vector layer geometry validated", {
                "layer_name": layer_name,
                "status": "valid",
            })
            return layer, False

        debug_logger.warning("Invalid geometry detected; attempting fix", {
            "layer_name": layer_name,
            "invalid_feature_count": invalid_count,
        })

        fixed_layer = self._fix_vector_layer_geometry(layer, layer_name)
        if fixed_layer is None:
            debug_logger.error("Geometry fix failed; keeping original layer", {
                "layer_name": layer_name,
            })
            return layer, False

        if database_path and table_name:
            spatialite_manager = SpatialiteManager()
            success, persisted_table_name = spatialite_manager.copy_vector_layer_to_database(
                table_name,
                fixed_layer,
                str(database_path),
            )
            if success and persisted_table_name:
                reloaded_layer = QgsVectorLayer(
                    f"{str(database_path)}|layername={persisted_table_name}",
                    layer_name,
                    "ogr",
                )
                if reloaded_layer.isValid():
                    debug_logger.success("Persisted fixed geometry layer back to GeoPackage", {
                        "layer_name": layer_name,
                        "table_name": persisted_table_name,
                    })
                    return reloaded_layer, True

        debug_logger.success("Using in-memory fixed geometry layer", {
            "layer_name": layer_name,
        })
        return fixed_layer, True

    @staticmethod
    def _layer_display_name(layer) -> str:
        """Return a QGIS layer name across method/property test doubles."""
        name = getattr(layer, "name", "")
        if callable(name):
            return str(name())
        return str(name or "")

    @classmethod
    def _layer_names_in_subtree(cls, node) -> List[str]:
        """Return layer names under a layer-tree node, in display order."""
        names: List[str] = []

        def visit(current) -> None:
            layer_fn = getattr(current, "layer", None)
            layer = layer_fn() if callable(layer_fn) else None
            if layer is not None:
                layer_name = cls._layer_display_name(layer)
                if layer_name:
                    names.append(layer_name)
                return

            children = getattr(current, "children", None)
            if callable(children):
                for child in children():
                    visit(child)

        if node is not None:
            visit(node)
        return names

    @classmethod
    def _project_layer_tree_order(cls, project) -> List[str]:
        """Return layer names in the project's layer-tree display order."""
        try:
            root = project.layerTreeRoot()
        except Exception:
            root = None

        names = cls._layer_names_in_subtree(root)
        if names:
            return names

        try:
            return [
                cls._layer_display_name(layer)
                for layer in project.mapLayers().values()
                if cls._layer_display_name(layer)
            ]
        except Exception:
            return []

    @staticmethod
    def order_layer_names(layer_names: List[str], project_layer_order: List[str]) -> List[str]:
        """Sort selected layer names by project order while preserving unknown order."""
        first_position: Dict[str, int] = {}
        for index, name in enumerate(project_layer_order):
            first_position.setdefault(name, index)

        unknown_start = len(first_position)
        return [
            name
            for _, name in sorted(
                enumerate(layer_names),
                key=lambda item: (first_position.get(item[1], unknown_start + item[0]), item[0]),
            )
        ]

    def get_current_project_layer_names(self) -> List[str]:
        """Return current QGIS project layer names in layer-tree order."""
        try:
            from qgis.core import QgsProject
        except ImportError:
            return []
        return self._project_layer_tree_order(QgsProject.instance())

    def get_base_layer_names(self) -> List[str]:
        """Return layer names under the "Base Layers" group, in tree order.

        The Create Study Area wizard must offer only the original project
        inputs -- the layers placed under "Base Layers" when the workspace was
        created -- and NOT the output layers produced by prior analyses, which
        live in their own top-level analysis groups.

        Falls back to all project layers when no "Base Layers" group exists yet
        (e.g. while first creating a workspace from the layers currently loaded
        in QGIS, before the group has been created).
        """
        try:
            from qgis.core import QgsProject
        except ImportError:
            return []
        project = QgsProject.instance()
        try:
            root = project.layerTreeRoot()
            base_group = root.findGroup("Base Layers") if root is not None else None
        except Exception:
            base_group = None
        if base_group is None:
            return self._project_layer_tree_order(project)
        return self._layer_names_in_subtree(base_group)

    def order_layer_names_by_current_project(self, layer_names: List[str]) -> List[str]:
        """Return selected layer names sorted by the current QGIS project tree."""
        return self.order_layer_names(layer_names, self.get_current_project_layer_names())

    def create_project_directory(self, project_name: str) -> Path:
        """
        Create a directory for a new project

        Args:
            project_name: Name of the project

        Returns:
            Path to the created project directory
        """
        # Sanitize project name for filesystem
        safe_name = self._sanitize_name(project_name)
        project_dir = self.projects_dir / safe_name

        project_dir.mkdir(parents=True, exist_ok=True)
        return project_dir

    def create_project_file(self, project_dir: Path, project_name: str) -> Path:
        """
        Create a new QGIS project file

        Args:
            project_dir: Directory where project will be stored
            project_name: Name of the project

        Returns:
            Path to the created .qgz file
        """
        try:
            from qgis.core import QgsProject, QgsCoordinateReferenceSystem
        except ImportError:
            raise RuntimeError("QGIS is not available in this environment")

        # Create project file path
        safe_name = self._sanitize_name(project_name)
        project_file = project_dir / f"{safe_name}.qgz"

        # Create new QGIS project
        project = QgsProject()

        # Set default CRS (WGS84)
        project.setCrs(QgsCoordinateReferenceSystem("EPSG:4326"))

        # Save project
        project.write(str(project_file))

        return project_file

    def load_project(self, project_file: str) -> bool:
        """
        Load a QGIS project file in QGIS

        Args:
            project_file: Path to .qgz file

        Returns:
            True if successful, False otherwise
        """
        try:
            from qgis.core import QgsProject
        except ImportError:
            raise RuntimeError("QGIS is not available in this environment")

        try:
            project = QgsProject.instance()
            success = project.read(str(project_file))
            return success
        except Exception as e:
            print(f"Error loading project: {e}")
            return False

    def create_group_layer(self, project_file: str, group_name: str) -> bool:
        """
        Create a group layer in a QGIS project

        Args:
            project_file: Path to .qgz file
            group_name: Name of the group layer

        Returns:
            True if successful, False otherwise
        """
        try:
            from qgis.core import QgsProject, QgsLayerTreeGroup
        except ImportError:
            raise RuntimeError("QGIS is not available in this environment")

        try:
            # Operate on the target project file directly instead of the currently
            # loaded QGIS project instance.
            target_project = QgsProject()
            if not target_project.read(str(project_file)):
                return False

            root = target_project.layerTreeRoot()
            group = root.findGroup(group_name)
            if group is None:
                root.insertGroup(0, group_name)

            # Persist changes to the intended file path.
            target_project.write(str(project_file))

            return True
        except Exception as e:
            print(f"Error creating group layer: {e}")
            return False

    def copy_layers_to_project(
        self,
        source_layer_names: List[str],
        project_file: str,
        group_name: Optional[str] = None
    ) -> Dict[str, bool]:
        """
        Copy layers from current project to a target project

        Args:
            source_layer_names: List of layer names to copy
            project_file: Path to target .qgz file
            group_name: Optional group layer to add layers to

        Returns:
            Dict mapping layer name to success status
        """
        try:
            from qgis.core import (
                QgsProject, QgsVectorFileWriter, QgsRasterFileWriter,
                QgsCoordinateTransformContext, QgsVectorLayer
            )
        except ImportError:
            raise RuntimeError("QGIS is not available in this environment")

        results = {}
        current_project = QgsProject.instance()
        target_project = QgsProject()

        try:
            # Load target project
            if not target_project.read(str(project_file)):
                return {name: False for name in source_layer_names}

            # Get source layers
            for layer_name in source_layer_names:
                source_layer = None
                for layer in current_project.mapLayers().values():
                    if layer.name() == layer_name:
                        source_layer = layer
                        break

                if not source_layer:
                    results[layer_name] = False
                    continue

                try:
                    # Clone the layer
                    cloned_layer = self._clone_layer(source_layer, target_project)

                    if cloned_layer:
                        # Add to project
                        target_project.addMapLayer(cloned_layer)

                        # Add to group if specified
                        if group_name:
                            root = target_project.layerTreeRoot()
                            group = root.findGroup(group_name)
                            if group:
                                group.addLayer(cloned_layer)

                        results[layer_name] = True
                    else:
                        results[layer_name] = False

                except Exception as e:
                    print(f"Error cloning layer {layer_name}: {e}")
                    results[layer_name] = False

            # Save target project
            target_project.write()

        except Exception as e:
            print(f"Error in copy_layers_to_project: {e}")
            return {name: False for name in source_layer_names}

        return results

    def create_geopackage_database(self, project_dir: Path) -> Tuple[bool, Optional[Path]]:
        """
        Create a GeoPackage database for a project

        GeoPackage is a standardized OGC format for storing vector layers
        in a SQLite database. Works with QGIS, ArcGIS Pro, and other tools.

        Note: We return the database path but don't create the file yet.
        QGIS will create it when the first layer is exported.

        Args:
            project_dir: Directory where the database will be stored

        Returns:
            Tuple of (success: bool, database_path: Path or None)
        """
        try:
            database_path = project_dir / "data.gpkg"
            # Don't create empty file - QGIS will create it when exporting
            # Just ensure the directory exists
            project_dir.mkdir(parents=True, exist_ok=True)
            return True, database_path

        except Exception as e:
            print(f"Error creating GeoPackage database path: {e}")
            return False, None

    def copy_layers_to_geopackage(
        self,
        source_layer_names: List[str],
        database_path: Path
    ) -> Dict[str, str]:
        """
        Copy vector layers from QGIS to GeoPackage database

        Args:
            source_layer_names: List of layer names to copy
            database_path: Path to GeoPackage database

        Returns:
            Dict mapping layer name to table name in database
        """
        try:
            from qgis.core import QgsProject, QgsVectorLayer

            debug_logger.section("copy_layers_to_geopackage called")
            debug_logger.debug("Layer copy parameters", {
                "database_path": str(database_path),
                "layers_to_copy": source_layer_names,
                "number_of_layers": len(source_layer_names)
            })

            results = {}
            # Reset the skipped-raster list for this run; the dock
            # widget reads it after the call to surface a warning.
            self.last_skipped_raster_layers = []
            current_project = QgsProject.instance()
            spatialite_manager = SpatialiteManager()

            debug_logger.debug("Getting current QGIS project", {
                "project_instance": str(current_project)
            })

            debug_logger.log("Iterating through requested layers")
            for idx, layer_name in enumerate(source_layer_names, 1):
                debug_logger.debug(f"Processing layer {idx}/{len(source_layer_names)}", {
                    "layer_name": layer_name
                })
                source_layer = None

                # Find layer in current project
                debug_logger.log("  Searching for layer in current project...")
                available_layers = list(current_project.mapLayers().values())
                debug_logger.log(f"  Available layers in project: {len(available_layers)}")
                for available_layer in available_layers:
                    layer_type = type(available_layer).__name__
                    debug_logger.log(f"    - {available_layer.name()} (type: {layer_type})")
                    if available_layer.name() == layer_name:
                        source_layer = available_layer
                        debug_logger.log(f"      ✓ FOUND matching layer!")
                        break

                if not source_layer:
                    debug_logger.error("Layer not found in project!")
                    results[layer_name] = ""
                    continue

                # Check if it's a vector layer (raster-support Phase 1:
                # routed through LayerKindResolver). GeoPackage export only
                # accepts vectors; rasters are skipped here and will be
                # surfaced to the UI in Phase 2.
                debug_logger.debug("Layer type check", {
                    "type": type(source_layer).__name__,
                    "kind": (LayerKindResolver.resolve(source_layer).value
                             if LayerKindResolver.resolve(source_layer) else "unknown"),
                })
                if not LayerKindResolver.is_vector(source_layer):
                    # Track raster skips so the caller can surface a
                    # clear UX warning (raster-support Phase 2). Other
                    # non-vector layers (mesh, point cloud) currently
                    # land in the same skip bucket — fine for now since
                    # they're rare in EMDS workspaces.
                    if LayerKindResolver.is_raster(source_layer):
                        self.last_skipped_raster_layers.append(layer_name)
                        debug_logger.error("Raster layer skipped (GeoPackage export is vector-only)", {
                            "layer_name": layer_name,
                        })
                    else:
                        debug_logger.error("Not a vector layer (skipping)")
                    results[layer_name] = ""
                    continue

                debug_logger.log("  Layer is QgsVectorLayer: ✓")
                layer_for_export, was_fixed = self._validate_and_fix_vector_layer(
                    source_layer,
                    layer_name=layer_name,
                )
                if was_fixed:
                    debug_logger.success("Using geometry-corrected layer for export", {
                        "layer_name": layer_name,
                    })

                # Copy layer to GeoPackage database
                debug_logger.log("  Calling copy_vector_layer_to_database()...")
                success, table_name = spatialite_manager.copy_vector_layer_to_database(
                    layer_name,
                    layer_for_export,
                    str(database_path)
                )

                if success:
                    debug_logger.success("Layer copy result", {
                        "layer_name": layer_name,
                        "table_name": table_name
                    })
                    results[layer_name] = table_name
                else:
                    debug_logger.error("Layer copy failed", {
                        "layer_name": layer_name
                    })
                    results[layer_name] = ""

            # Summary
            successful = sum(1 for v in results.values() if v)
            failed = sum(1 for v in results.values() if not v)
            debug_logger.section("copy_layers_to_geopackage completed")
            debug_logger.debug("Copy summary", {
                "total_layers_processed": len(source_layer_names),
                "successful": successful,
                "failed": failed,
                "results": str(results)
            })

            return results

        except Exception as e:
            debug_logger.error("Exception in copy_layers_to_geopackage", {
                "exception": str(e)
            })
            import traceback
            debug_logger.log(traceback.format_exc())
            return {name: "" for name in source_layer_names}

    def remap_layers_to_geopackage(
        self,
        layer_names: List[str],
        database_path: Path,
        table_mapping: Dict[str, str]
    ) -> Dict[str, bool]:
        """
        Remap QGIS layers to point to tables in GeoPackage database

        Args:
            layer_names: List of layer names to remap
            database_path: Path to GeoPackage database
            table_mapping: Dict mapping layer name to table name in database

        Returns:
            Dict mapping layer name to success status
        """
        try:
            from qgis.core import QgsProject

            results = {}
            current_project = QgsProject.instance()
            spatialite_manager = SpatialiteManager()

            for layer_name in layer_names:
                table_name = table_mapping.get(layer_name, "")
                if not table_name:
                    results[layer_name] = False
                    continue

                # Find layer in current project
                layer = None
                for l in current_project.mapLayers().values():
                    if l.name() == layer_name:
                        layer = l
                        break

                if not layer:
                    results[layer_name] = False
                    continue

                # Remap the layer source
                success = spatialite_manager.remap_layer_source(
                    layer,
                    str(database_path),
                    table_name
                )

                results[layer_name] = success

            return results

        except Exception as e:
            print(f"Error remapping layers to GeoPackage: {e}")
            return {name: False for name in layer_names}

    def load_geopackage_layers_to_project(
        self,
        layer_names: List[str],
        database_path: Path,
        project_file: str,
        group_name: Optional[str] = None,
        table_mapping: Optional[Dict[str, str]] = None
    ) -> Dict[str, bool]:
        """
        Load layers directly from GeoPackage database into a project

        Args:
            layer_names: List of layer names to load
            database_path: Path to GeoPackage database
            project_file: Path to target .qgz project file
            group_name: Optional group layer name to add layers to

        Returns:
            Dict mapping layer name to success status
        """
        try:
            from qgis.core import QgsProject, QgsVectorLayer

            results = {}
            target_project = QgsProject()

            # Load target project
            if not target_project.read(str(project_file)):
                return {name: False for name in layer_names}

            root = target_project.layerTreeRoot()
            group = None
            if group_name:
                group = root.findGroup(group_name)
                if group is None:
                    group = root.insertGroup(0, group_name)

            table_mapping = table_mapping or {}

            # Create URI for each layer in the GeoPackage
            for layer_name in layer_names:
                try:
                    table_name = table_mapping.get(layer_name, layer_name)

                    # OGR provider expects GeoPackage URI in file|layername format.
                    # Example: /path/data.gpkg|layername=streams
                    gpkg_uri = f"{str(database_path)}|layername={table_name}"

                    # Create vector layer from GeoPackage
                    layer = QgsVectorLayer(gpkg_uri, layer_name, "ogr")

                    if not layer.isValid():
                        debug_logger.warning(f"Invalid layer loaded from GeoPackage", {
                            "layer_name": layer_name,
                            "uri": gpkg_uri
                        })
                        results[layer_name] = False
                        continue

                    layer_to_add, was_fixed = self._validate_and_fix_vector_layer(
                        layer,
                        layer_name=layer_name,
                        database_path=database_path,
                        table_name=table_name,
                    )
                    if not layer_to_add.isValid():
                        debug_logger.warning("Layer remained invalid after geometry validation", {
                            "layer_name": layer_name,
                            "uri": gpkg_uri,
                        })
                        results[layer_name] = False
                        continue

                    # Add to project without adding to root node, then add explicitly
                    # to the requested group.
                    target_project.addMapLayer(layer_to_add, False)
                    if group is not None:
                        group.addLayer(layer_to_add)
                        debug_logger.log(f"  Added {layer_name} to group {group_name}")
                    else:
                        root.addLayer(layer_to_add)

                    # Best-effort style carryover from same-name source layer in the
                    # currently open project.
                    self._apply_source_layer_style(layer_name, layer_to_add)

                    results[layer_name] = True
                    debug_logger.success(f"Loaded layer from GeoPackage", {
                        "layer_name": layer_name,
                        "geometry_fixed": was_fixed,
                    })

                except Exception as e:
                    debug_logger.error(f"Failed to load layer from GeoPackage", {
                        "layer_name": layer_name,
                        "error": str(e)
                    })
                    results[layer_name] = False

            # Save the project with new layers
            target_project.write()
            debug_logger.success("GeoPackage layers added to project")

            return results

        except Exception as e:
            debug_logger.error("Exception in load_geopackage_layers_to_project", {
                "exception": str(e)
            })
            return {name: False for name in layer_names}

    def _apply_source_layer_style(self, source_layer_name: str, target_layer) -> None:
        """Copy renderer/label style from the currently loaded source layer."""
        try:
            from qgis.core import QgsProject

            source_layer = None
            for layer in QgsProject.instance().mapLayers().values():
                if layer.name() == source_layer_name:
                    source_layer = layer
                    break

            if source_layer is None:
                return

            if source_layer.renderer() is not None:
                target_layer.setRenderer(source_layer.renderer().clone())

            if source_layer.labelsEnabled():
                target_layer.setLabelsEnabled(True)
                labeling = source_layer.labeling()
                if labeling is not None:
                    try:
                        target_layer.setLabeling(labeling.clone())
                    except Exception:
                        # Not all labeling implementations support clone().
                        pass

            target_layer.triggerRepaint()
        except Exception:
            # Style transfer is best effort and must not block project creation.
            pass

    def _clone_layer(self, source_layer, target_project):
        """
        Create a deep copy of a layer

        Args:
            source_layer: Source QgsMapLayer
            target_project: Target QgsProject

        Returns:
            Cloned layer or None if cloning failed
        """
        try:
            from qgis.core import QgsVectorLayer, QgsRasterLayer

            # Branch via LayerKindResolver (raster-support Phase 1) so
            # all kind detection lives in one module.
            kind = LayerKindResolver.resolve(source_layer)
            if kind == LayerKind.VECTOR:
                # For vector layers, create a new layer from the same source
                cloned = QgsVectorLayer(
                    source_layer.source(),
                    source_layer.name(),
                    source_layer.providerType()
                )
                # Copy style
                if source_layer.renderer():
                    cloned.setRenderer(source_layer.renderer().clone())
                return cloned

            elif kind == LayerKind.RASTER:
                # For raster layers, reference the same file
                cloned = QgsRasterLayer(
                    source_layer.source(),
                    source_layer.name()
                )
                return cloned

            else:
                # For other layer types (mesh, point cloud, etc.), try generic clone
                return source_layer.clone()

        except Exception as e:
            print(f"Error cloning layer: {e}")
            return None

    @staticmethod
    def _sanitize_name(name: str) -> str:
        """
        Sanitize a name for use as filesystem path

        Args:
            name: Original name

        Returns:
            Sanitized name safe for filesystem
        """
        # Replace problematic characters
        import re
        safe_name = re.sub(r'[^a-zA-Z0-9_\-]', '_', name)
        # Remove leading/trailing underscores
        safe_name = safe_name.strip('_')
        # Ensure not empty
        if not safe_name:
            safe_name = "project"
        return safe_name

    def get_project_path(self, project_name: str) -> Path:
        """
        Get the expected path for a project by name

        Args:
            project_name: Name of the project

        Returns:
            Path to project directory
        """
        safe_name = self._sanitize_name(project_name)
        return self.projects_dir / safe_name

    def project_exists(self, project_name: str) -> bool:
        """
        Check if a project exists

        Args:
            project_name: Name of the project

        Returns:
            True if project directory exists
        """
        return self.get_project_path(project_name).exists()

    def get_projects(self) -> List[Dict[str, Any]]:
        """
        Get list of all projects

        Returns:
            List of project info dicts with 'name' and 'path' keys
        """
        projects = []
        if self.projects_dir.exists():
            for item in self.projects_dir.iterdir():
                if item.is_dir():
                    qgz_file = item / f"{item.name}.qgz"
                    projects.append({
                        'name': item.name,
                        'path': str(item),
                        'has_project_file': qgz_file.exists(),
                        'created': datetime.fromtimestamp(item.stat().st_ctime),
                    })
        return projects
