// Assets/Editor/GeoTilePrefabImporter.cs
// Creates prefab assets from geo tiles. Each prefab contains terrain + buildings + trees + furniture.
// Unlike GeoTileImporter (which creates scene objects), this importer produces reusable .prefab assets.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using UnityEditor;
using UnityEngine;

public class GeoTilePrefabImporter : EditorWindow
{
    // Input paths (same as GeoTileImporter)
    private string tilesCsvPath = "Assets/GeoData/tile_index.csv";
    private string heightmapsDir = "Assets/GeoData/height_png16";
    private string orthoDir = "Assets/GeoData/ortho_jpg";
    private string buildingsDir = "Assets/GeoData/buildings_tiles";
    private string treesDir = "Assets/GeoData/trees_tiles";
    private string furnitureDir = "Assets/GeoData/street_furniture";
    private string enhancedTreesDir = "Assets/GeoData/trees_enhanced";

    // Output settings
    private string prefabOutputDir = "Assets/GeoData/TilePrefabs";
    private bool overwriteExisting = false;

    // Terrain settings
    private float tileSizeMeters = 1000f;
    private int heightmapResolution = 1025;

    // Component toggles
    private bool applyOrthoTextures = true;
    private bool includeBuildings = true;
    private bool includeTrees = true;
    private bool includeFurniture = false;
    private bool includeEnhancedTrees = false;

    // Prefabs for furniture (optional)
    private GameObject lampPrefab;
    private GameObject benchPrefab;
    private GameObject signPrefab;
    private GameObject bollardPrefab;
    private GameObject defaultFurniturePrefab;
    private GameObject treePrefab;

    private struct TileMetadata
    {
        public string TileId;
        public double Xmin, Ymin, Xmax, Ymax;
        public double GlobalMin, GlobalMax;
        public double TileMin, TileMax;
        public bool HasTileMinMax;
        public int OutRes;
    }

    [MenuItem("Tools/Geo Tiles/Import Tiles as Prefabs")]
    public static void ShowWindow()
    {
        var win = GetWindow<GeoTilePrefabImporter>("Geo Tile Prefab Importer");
        win.minSize = new Vector2(620, 480);
    }

    private void OnGUI()
    {
        GUILayout.Label("Input Paths", EditorStyles.boldLabel);
        tilesCsvPath = EditorGUILayout.TextField("tile_index.csv", tilesCsvPath);
        heightmapsDir = EditorGUILayout.TextField("height_png16 dir", heightmapsDir);
        orthoDir = EditorGUILayout.TextField("ortho_jpg dir", orthoDir);
        buildingsDir = EditorGUILayout.TextField("buildings_tiles dir", buildingsDir);
        treesDir = EditorGUILayout.TextField("trees_tiles dir", treesDir);
        furnitureDir = EditorGUILayout.TextField("street_furniture dir", furnitureDir);
        enhancedTreesDir = EditorGUILayout.TextField("trees_enhanced dir", enhancedTreesDir);

        GUILayout.Space(10);
        GUILayout.Label("Output Settings", EditorStyles.boldLabel);
        prefabOutputDir = EditorGUILayout.TextField("Prefab output dir", prefabOutputDir);
        overwriteExisting = EditorGUILayout.ToggleLeft("Overwrite existing prefabs", overwriteExisting);

        GUILayout.Space(10);
        GUILayout.Label("Terrain Settings", EditorStyles.boldLabel);
        tileSizeMeters = EditorGUILayout.FloatField("Tile size (m)", tileSizeMeters);
        heightmapResolution = EditorGUILayout.IntField("Heightmap resolution", heightmapResolution);

        GUILayout.Space(10);
        GUILayout.Label("Include Components", EditorStyles.boldLabel);
        applyOrthoTextures = EditorGUILayout.ToggleLeft("Apply ortho textures", applyOrthoTextures);
        includeBuildings = EditorGUILayout.ToggleLeft("Include buildings (GLB)", includeBuildings);
        includeTrees = EditorGUILayout.ToggleLeft("Include trees (GLB chunks)", includeTrees);
        includeFurniture = EditorGUILayout.ToggleLeft("Include street furniture (CSV)", includeFurniture);
        includeEnhancedTrees = EditorGUILayout.ToggleLeft("Include enhanced trees (CSV)", includeEnhancedTrees);

        GUILayout.Space(10);
        GUILayout.Label("Prefabs (optional, for furniture/trees)", EditorStyles.boldLabel);
        treePrefab = (GameObject)EditorGUILayout.ObjectField("Tree Prefab", treePrefab, typeof(GameObject), false);
        lampPrefab = (GameObject)EditorGUILayout.ObjectField("Lamp Prefab", lampPrefab, typeof(GameObject), false);
        benchPrefab = (GameObject)EditorGUILayout.ObjectField("Bench Prefab", benchPrefab, typeof(GameObject), false);
        signPrefab = (GameObject)EditorGUILayout.ObjectField("Sign Prefab", signPrefab, typeof(GameObject), false);
        bollardPrefab = (GameObject)EditorGUILayout.ObjectField("Bollard Prefab", bollardPrefab, typeof(GameObject), false);
        defaultFurniturePrefab = (GameObject)EditorGUILayout.ObjectField("Default Furniture", defaultFurniturePrefab, typeof(GameObject), false);

        GUILayout.Space(15);
        if (GUILayout.Button("Generate Prefabs"))
            ImportTilesAsPrefabs();

        GUILayout.Space(10);
        if (GUILayout.Button("Place All Prefabs in Scene"))
            PlaceAllPrefabsInScene();

        EditorGUILayout.HelpBox(
            "Creates one .prefab asset per tile in the manifest.\n" +
            "Each prefab contains: Terrain (with TerrainData), Buildings, Trees, Furniture.\n" +
            "TerrainData and TerrainLayers are saved as separate .asset files.\n" +
            "IMPORTANT: Use 'Place All Prefabs in Scene' to position tiles correctly.",
            MessageType.Info);
    }

    private void PlaceAllPrefabsInScene()
    {
        if (!Directory.Exists(prefabOutputDir))
        {
            Debug.LogError($"[GeoTilePrefabImporter] Prefab directory not found: {prefabOutputDir}");
            return;
        }

        var prefabFiles = Directory.GetFiles(prefabOutputDir, "*.prefab");
        if (prefabFiles.Length == 0)
        {
            Debug.LogError("[GeoTilePrefabImporter] No prefabs found. Generate prefabs first.");
            return;
        }

        // Load all prefabs and collect metadata to compute origin
        var prefabsWithMeta = new List<(GameObject prefab, GeoTileMetadata meta)>();
        double originX = double.PositiveInfinity;
        double originY = double.PositiveInfinity;

        foreach (var path in prefabFiles)
        {
            string assetPath = path.Replace("\\", "/");
            var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
            if (prefab == null) continue;

            var meta = prefab.GetComponent<GeoTileMetadata>();
            if (meta == null)
            {
                Debug.LogWarning($"[GeoTilePrefabImporter] Prefab missing GeoTileMetadata: {assetPath}");
                continue;
            }

            prefabsWithMeta.Add((prefab, meta));
            originX = Math.Min(originX, meta.xmin);
            originY = Math.Min(originY, meta.ymin);
        }

        if (prefabsWithMeta.Count == 0)
        {
            Debug.LogError("[GeoTilePrefabImporter] No valid prefabs with metadata found.");
            return;
        }

        // Create parent container
        var parent = GameObject.Find("Geo_Tile_Prefabs");
        if (parent == null) parent = new GameObject("Geo_Tile_Prefabs");

        // Instantiate each prefab at correct position
        int placed = 0;
        foreach (var (prefab, meta) in prefabsWithMeta)
        {
            // Check if already placed
            var existing = parent.transform.Find(meta.tileId);
            if (existing != null)
            {
                Debug.Log($"[GeoTilePrefabImporter] Tile already in scene: {meta.tileId}");
                continue;
            }

            var instance = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
            if (instance == null)
            {
                instance = Instantiate(prefab);
                instance.name = meta.tileId;
            }

            instance.transform.SetParent(parent.transform, false);
            instance.transform.position = meta.GetWorldPosition(originX, originY);
            placed++;

            Debug.Log($"[GeoTilePrefabImporter] Placed {meta.tileId} at {instance.transform.position}");
        }

        Debug.Log($"[GeoTilePrefabImporter] Placed {placed} tile prefabs under 'Geo_Tile_Prefabs'. Origin: ({originX}, {originY})");
    }

    private void ImportTilesAsPrefabs()
    {
        Debug.Log($"[GeoTilePrefabImporter] START\n  csv={tilesCsvPath}\n  output={prefabOutputDir}");

        // Validate inputs
        if (!File.Exists(tilesCsvPath))
        {
            Debug.LogError($"[GeoTilePrefabImporter] CSV not found: {tilesCsvPath}");
            return;
        }
        if (!Directory.Exists(heightmapsDir))
        {
            Debug.LogError($"[GeoTilePrefabImporter] Heightmap dir not found: {heightmapsDir}");
            return;
        }

        // Create output directories
        EnsureDirectoryExists(prefabOutputDir);
        EnsureDirectoryExists($"{prefabOutputDir}/TerrainData");
        EnsureDirectoryExists($"{prefabOutputDir}/TerrainLayers");

        // Parse CSV
        var tiles = ParseTilesCsv();
        if (tiles == null || tiles.Count == 0)
        {
            Debug.LogError("[GeoTilePrefabImporter] No valid tiles found in CSV.");
            return;
        }

        Debug.Log($"[GeoTilePrefabImporter] Found {tiles.Count} tiles to process.");

        int created = 0, skipped = 0, failed = 0;

        for (int i = 0; i < tiles.Count; i++)
        {
            var tile = tiles[i];
            EditorUtility.DisplayProgressBar(
                "Creating Tile Prefabs",
                $"Processing {tile.TileId} ({i + 1}/{tiles.Count})",
                (float)i / tiles.Count);

            string prefabPath = $"{prefabOutputDir}/{tile.TileId}.prefab";
            if (File.Exists(prefabPath) && !overwriteExisting)
            {
                Debug.Log($"[GeoTilePrefabImporter] Skipping existing: {tile.TileId}");
                skipped++;
                continue;
            }

            try
            {
                if (CreateTilePrefab(tile))
                    created++;
                else
                    failed++;
            }
            catch (Exception e)
            {
                Debug.LogError($"[GeoTilePrefabImporter] Failed to create prefab for {tile.TileId}: {e.Message}\n{e.StackTrace}");
                failed++;
            }
        }

        EditorUtility.ClearProgressBar();
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();

        Debug.Log($"[GeoTilePrefabImporter] DONE. Created={created}, Skipped={skipped}, Failed={failed}");
    }

    private List<TileMetadata> ParseTilesCsv()
    {
        var tiles = new List<TileMetadata>();
        var ci = CultureInfo.InvariantCulture;
        var lines = File.ReadAllLines(tilesCsvPath);

        if (lines.Length < 2)
        {
            Debug.LogError("[GeoTilePrefabImporter] CSV has no data rows.");
            return null;
        }

        var headerMap = BuildHeaderMap(lines[0]);
        string[] required = { "tile_id", "xmin", "ymin", "xmax", "ymax", "global_min", "global_max", "out_res" };
        if (!HasAll(headerMap, required))
        {
            Debug.LogError("[GeoTilePrefabImporter] CSV missing required columns. Required: " +
                           string.Join(", ", required));
            return null;
        }

        int IDX_TILE = headerMap["tile_id"];
        int IDX_XMIN = headerMap["xmin"];
        int IDX_YMIN = headerMap["ymin"];
        int IDX_XMAX = headerMap["xmax"];
        int IDX_YMAX = headerMap["ymax"];
        int IDX_GMIN = headerMap["global_min"];
        int IDX_GMAX = headerMap["global_max"];
        int IDX_RES = headerMap["out_res"];
        bool hasTileMin = headerMap.TryGetValue("tile_min", out int IDX_TMIN);
        bool hasTileMax = headerMap.TryGetValue("tile_max", out int IDX_TMAX);
        bool useTileRange = hasTileMin && hasTileMax;

        for (int i = 1; i < lines.Length; i++)
        {
            var line = lines[i].Trim();
            if (string.IsNullOrWhiteSpace(line)) continue;

            var parts = line.Split(',');
            int maxIdx = Math.Max(Math.Max(Math.Max(IDX_TILE, IDX_XMAX), IDX_YMAX), Math.Max(IDX_GMAX, IDX_RES));
            if (useTileRange)
                maxIdx = Math.Max(maxIdx, Math.Max(IDX_TMIN, IDX_TMAX));
            if (parts.Length <= maxIdx)
            {
                Debug.LogWarning($"[GeoTilePrefabImporter] Skipping line {i + 1}: too few columns.");
                continue;
            }

            try
            {
                double gmin = double.Parse(parts[IDX_GMIN], ci);
                double gmax = double.Parse(parts[IDX_GMAX], ci);
                double tileMin = gmin;
                double tileMax = gmax;
                bool tileRangeValid = false;
                if (useTileRange)
                {
                    if (double.TryParse(parts[IDX_TMIN], NumberStyles.Float, ci, out double tmin) &&
                        double.TryParse(parts[IDX_TMAX], NumberStyles.Float, ci, out double tmax))
                    {
                        tileMin = tmin;
                        tileMax = tmax;
                        tileRangeValid = true;
                    }
                    else
                    {
                        Debug.LogWarning($"[GeoTilePrefabImporter] Tile {parts[IDX_TILE].Trim()}: invalid tile_min/tile_max; falling back to global range.");
                    }
                }

                tiles.Add(new TileMetadata
                {
                    TileId = parts[IDX_TILE].Trim(),
                    Xmin = double.Parse(parts[IDX_XMIN], ci),
                    Ymin = double.Parse(parts[IDX_YMIN], ci),
                    Xmax = double.Parse(parts[IDX_XMAX], ci),
                    Ymax = double.Parse(parts[IDX_YMAX], ci),
                    GlobalMin = gmin,
                    GlobalMax = gmax,
                    TileMin = tileMin,
                    TileMax = tileMax,
                    HasTileMinMax = tileRangeValid,
                    OutRes = int.Parse(parts[IDX_RES], ci)
                });
            }
            catch (Exception e)
            {
                Debug.LogWarning($"[GeoTilePrefabImporter] Parse error line {i + 1}: {e.Message}");
            }
        }

        return tiles;
    }

    private bool CreateTilePrefab(TileMetadata tile)
    {
        // Validate height range
        double baseMin = tile.HasTileMinMax ? tile.TileMin : tile.GlobalMin;
        double baseMax = tile.HasTileMinMax ? tile.TileMax : tile.GlobalMax;
        float heightRange = (float)(baseMax - baseMin);
        if (heightRange <= 0.0001f)
        {
            if (tile.HasTileMinMax)
            {
                Debug.LogWarning($"[GeoTilePrefabImporter] Tile {tile.TileId}: flat tile range; using epsilon height range.");
                heightRange = 0.001f;
            }
            else
            {
                Debug.LogWarning($"[GeoTilePrefabImporter] Tile {tile.TileId}: invalid height range. Skipping.");
                return false;
            }
        }

        // Load heightmap
        string pngPath = Path.Combine(heightmapsDir, $"{tile.TileId}.png").Replace("\\", "/");
        if (!File.Exists(pngPath))
        {
            Debug.LogError($"[GeoTilePrefabImporter] Missing heightmap for {tile.TileId}: {pngPath}");
            return false;
        }

        EnsureHeightmapImportSettings(pngPath);
        var heightmapTex = AssetDatabase.LoadAssetAtPath<Texture2D>(pngPath);
        if (heightmapTex == null)
        {
            Debug.LogError($"[GeoTilePrefabImporter] Could not load heightmap: {pngPath}");
            return false;
        }

        // Create TerrainData
        var terrainData = new TerrainData
        {
            heightmapResolution = heightmapResolution,
            size = new Vector3(tileSizeMeters, heightRange, tileSizeMeters)
        };

        // Read heightmap pixels
        int w = heightmapTex.width;
        int h = heightmapTex.height;
        var heights = new float[h, w];

        bool usedU16 = false;
        try
        {
            var raw = heightmapTex.GetPixelData<ushort>(0);
            if (raw.Length == w * h)
            {
                for (int y = 0; y < h; y++)
                    for (int x = 0; x < w; x++)
                        heights[y, x] = raw[y * w + x] / 65535f;
                usedU16 = true;
            }
        }
        catch { }

        if (!usedU16)
        {
            var pixels = heightmapTex.GetPixels();
            for (int y = 0; y < h; y++)
                for (int x = 0; x < w; x++)
                    heights[y, x] = pixels[y * w + x].r;
        }

        terrainData.SetHeights(0, 0, heights);

        // Save TerrainData as asset (MUST be done before creating prefab)
        string terrainDataPath = $"{prefabOutputDir}/TerrainData/{tile.TileId}_TerrainData.asset";
        if (File.Exists(terrainDataPath))
            AssetDatabase.DeleteAsset(terrainDataPath);
        AssetDatabase.CreateAsset(terrainData, terrainDataPath);

        // Apply ortho texture if enabled
        if (applyOrthoTextures)
        {
            ApplyOrthoTexture(terrainData, tile.TileId);
        }

        // Create root GameObject with Terrain (ensures prefab contains the terrain asset)
        var root = Terrain.CreateTerrainGameObject(terrainData);
        root.name = tile.TileId;
        var terrain = root.GetComponent<Terrain>();
        terrain.drawInstanced = true;

        // Store metadata as component for later use
        var metadata = root.AddComponent<GeoTileMetadata>();
        metadata.tileId = tile.TileId;
        metadata.xmin = tile.Xmin;
        metadata.ymin = tile.Ymin;
        metadata.globalMin = tile.GlobalMin;
        metadata.globalMax = tile.GlobalMax;
        metadata.tileMin = tile.TileMin;
        metadata.tileMax = tile.TileMax;
        metadata.hasTileMinMax = tile.HasTileMinMax;

        // Add child components
        if (includeBuildings)
            AddBuildings(root, tile);

        if (includeTrees)
            AddTrees(root, tile);

        if (includeFurniture)
            AddFurniture(root, tile);

        if (includeEnhancedTrees)
            AddEnhancedTrees(root, tile);

        // Save as prefab
        string prefabPath = $"{prefabOutputDir}/{tile.TileId}.prefab";
        if (File.Exists(prefabPath))
            AssetDatabase.DeleteAsset(prefabPath);

        PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
        Debug.Log($"[GeoTilePrefabImporter] Created prefab: {prefabPath}");

        // Cleanup temporary scene object
        DestroyImmediate(root);

        return true;
    }

    private void ApplyOrthoTexture(TerrainData terrainData, string tileId)
    {
        string orthoPath = Path.Combine(orthoDir, $"{tileId}.jpg").Replace("\\", "/");
        if (!File.Exists(orthoPath))
        {
            Debug.LogWarning($"[GeoTilePrefabImporter] Ortho texture missing for {tileId}: {orthoPath}");
            return;
        }

        EnsureOrthoImportSettings(orthoPath);
        var orthoTex = AssetDatabase.LoadAssetAtPath<Texture2D>(orthoPath);
        if (orthoTex == null)
        {
            Debug.LogWarning($"[GeoTilePrefabImporter] Could not load ortho texture: {orthoPath}");
            return;
        }

        // Create and save TerrainLayer as asset (required for prefab serialization)
        var layer = new TerrainLayer
        {
            diffuseTexture = orthoTex,
            tileSize = new Vector2(tileSizeMeters, tileSizeMeters),
            tileOffset = Vector2.zero
        };

        string layerPath = $"{prefabOutputDir}/TerrainLayers/{tileId}_Layer.asset";
        if (File.Exists(layerPath))
            AssetDatabase.DeleteAsset(layerPath);
        AssetDatabase.CreateAsset(layer, layerPath);

        terrainData.terrainLayers = new[] { layer };

        // Setup alphamap (single layer, full coverage)
        terrainData.alphamapResolution = 16;
        var alpha = new float[16, 16, 1];
        for (int y = 0; y < 16; y++)
            for (int x = 0; x < 16; x++)
                alpha[y, x, 0] = 1f;
        terrainData.SetAlphamaps(0, 0, alpha);
    }

    private void AddBuildings(GameObject root, TileMetadata tile)
    {
        string glbPath = Path.Combine(buildingsDir, $"{tile.TileId}.glb").Replace("\\", "/");
        if (!File.Exists(glbPath))
            return;

        var buildingPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(glbPath);
        if (buildingPrefab == null)
        {
            Debug.LogWarning($"[GeoTilePrefabImporter] Could not load building GLB: {glbPath}");
            return;
        }

        float baseMin = (float)(tile.HasTileMinMax ? tile.TileMin : tile.GlobalMin);
        // Building GLB vertices have absolute Z (elevation) from CityGML.
        // Since prefab root will be at Y=baseMin when placed, offset buildings by -baseMin
        // so building world Y = baseMin + (-baseMin) + GLB_Y = GLB_Y (correct absolute elevation)
        var instance = PrefabUtility.InstantiatePrefab(buildingPrefab) as GameObject;
        if (instance == null)
            instance = Instantiate(buildingPrefab);

        instance.name = "Buildings";
        instance.transform.SetParent(root.transform, false);
        instance.transform.localPosition = new Vector3(0f, -baseMin, 0f);
        instance.transform.localRotation = Quaternion.Euler(0f, 180f, 0f);
        instance.isStatic = true;
    }

    private void AddTrees(GameObject root, TileMetadata tile)
    {
        if (!Directory.Exists(treesDir))
            return;

        // Look for chunk files or single file
        var chunkFiles = Directory.GetFiles(treesDir, $"{tile.TileId}_*.glb");
        if (chunkFiles.Length == 0)
        {
            string singlePath = Path.Combine(treesDir, $"{tile.TileId}.glb").Replace("\\", "/");
            if (File.Exists(singlePath))
                chunkFiles = new[] { singlePath };
            else
                return;
        }

        float baseMin = (float)(tile.HasTileMinMax ? tile.TileMin : tile.GlobalMin);
        // Tree GLB vertices use absolute elevation (z_ground from DGM).
        // Since prefab root will be at Y=baseMin when placed, offset trees by -baseMin
        // so tree world Y = baseMin + (-baseMin) + GLB_Y = GLB_Y (correct absolute elevation)
        var treesContainer = new GameObject("Trees");
        treesContainer.transform.SetParent(root.transform, false);
        treesContainer.transform.localPosition = new Vector3(0f, -baseMin, 0f);
        treesContainer.transform.localRotation = Quaternion.Euler(0f, 180f, 0f);
        treesContainer.isStatic = true;

        foreach (var chunkPath in chunkFiles)
        {
            string assetPath = chunkPath.Replace("\\", "/");
            var chunkPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
            if (chunkPrefab == null)
            {
                Debug.LogWarning($"[GeoTilePrefabImporter] Could not load tree chunk: {assetPath}");
                continue;
            }

            var instance = PrefabUtility.InstantiatePrefab(chunkPrefab) as GameObject;
            if (instance == null)
                instance = Instantiate(chunkPrefab);

            instance.name = Path.GetFileNameWithoutExtension(chunkPath);
            instance.transform.SetParent(treesContainer.transform, false);
            instance.transform.localPosition = Vector3.zero;
            instance.isStatic = true;
        }
    }

    private void AddFurniture(GameObject root, TileMetadata tile)
    {
        string csvPath = Path.Combine(furnitureDir, $"{tile.TileId}.csv").Replace("\\", "/");
        if (!File.Exists(csvPath))
            return;

        var lines = File.ReadAllLines(csvPath);
        if (lines.Length < 2)
            return;

        var ci = CultureInfo.InvariantCulture;
        var header = lines[0].Split(',');
        int idxXLocal = -1, idxYLocal = -1, idxZGround = -1, idxHeight = -1, idxType = -1;

        for (int i = 0; i < header.Length; i++)
        {
            var col = header[i].Trim().ToLowerInvariant();
            if (col == "x_local") idxXLocal = i;
            else if (col == "y_local") idxYLocal = i;
            else if (col == "z_ground") idxZGround = i;
            else if (col == "height") idxHeight = i;
            else if (col == "type") idxType = i;
        }

        if (idxXLocal < 0 || idxYLocal < 0 || idxType < 0)
        {
            Debug.LogWarning($"[GeoTilePrefabImporter] Furniture CSV missing required columns: {csvPath}");
            return;
        }

        var furnitureContainer = new GameObject("Furniture");
        furnitureContainer.transform.SetParent(root.transform, false);
        furnitureContainer.transform.localPosition = Vector3.zero;
        furnitureContainer.isStatic = true;

        float baseMin = (float)(tile.HasTileMinMax ? tile.TileMin : tile.GlobalMin);

        for (int i = 1; i < lines.Length; i++)
        {
            var parts = lines[i].Split(',');
            if (parts.Length <= Math.Max(Math.Max(idxXLocal, idxYLocal), idxType))
                continue;

            try
            {
                float xLocal = float.Parse(parts[idxXLocal].Trim(), ci);
                float yLocal = float.Parse(parts[idxYLocal].Trim(), ci);
                float zGround = idxZGround >= 0 && idxZGround < parts.Length
                    ? float.Parse(parts[idxZGround].Trim(), ci) : 0f;
                float height = idxHeight >= 0 && idxHeight < parts.Length
                    ? float.Parse(parts[idxHeight].Trim(), ci) : 1f;
                string furnitureType = parts[idxType].Trim().ToLowerInvariant();

                GameObject obj = CreateFurnitureObject(furnitureType, height, i);
                obj.transform.SetParent(furnitureContainer.transform, false);
                obj.transform.localPosition = new Vector3(xLocal, zGround - baseMin, yLocal);
                obj.isStatic = true;
            }
            catch (Exception e)
            {
                Debug.LogWarning($"[GeoTilePrefabImporter] Failed to parse furniture line {i}: {e.Message}");
            }
        }
    }

    private GameObject CreateFurnitureObject(string type, float height, int index)
    {
        GameObject prefabToUse = null;
        Vector3 fallbackScale = Vector3.one;

        switch (type)
        {
            case "lamp":
                prefabToUse = lampPrefab;
                fallbackScale = new Vector3(0.3f, height, 0.3f);
                break;
            case "bench":
                prefabToUse = benchPrefab;
                fallbackScale = new Vector3(1.5f, height, 0.5f);
                break;
            case "sign":
                prefabToUse = signPrefab;
                fallbackScale = new Vector3(0.1f, height, 0.5f);
                break;
            case "bollard":
                prefabToUse = bollardPrefab;
                fallbackScale = new Vector3(0.2f, height, 0.2f);
                break;
            default:
                prefabToUse = defaultFurniturePrefab;
                fallbackScale = new Vector3(0.5f, height, 0.5f);
                break;
        }

        GameObject obj;
        if (prefabToUse != null)
        {
            obj = PrefabUtility.InstantiatePrefab(prefabToUse) as GameObject ?? Instantiate(prefabToUse);
            obj.name = $"{type}_{index}";
            obj.transform.localScale = Vector3.one * height;
        }
        else
        {
            obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
            obj.name = $"{type}_{index}";
            obj.transform.localScale = fallbackScale;
        }

        return obj;
    }

    private void AddEnhancedTrees(GameObject root, TileMetadata tile)
    {
        string csvPath = Path.Combine(enhancedTreesDir, $"{tile.TileId}.csv").Replace("\\", "/");
        if (!File.Exists(csvPath))
            return;

        var lines = File.ReadAllLines(csvPath);
        if (lines.Length < 2)
            return;

        var ci = CultureInfo.InvariantCulture;
        var header = lines[0].Split(',');
        int idxXLocal = -1, idxYLocal = -1, idxZGround = -1, idxHeight = -1, idxRadius = -1;
        int idxCanopyR = -1, idxCanopyG = -1, idxCanopyB = -1;

        for (int i = 0; i < header.Length; i++)
        {
            var col = header[i].Trim().ToLowerInvariant();
            if (col == "x_local") idxXLocal = i;
            else if (col == "y_local") idxYLocal = i;
            else if (col == "z_ground") idxZGround = i;
            else if (col == "height") idxHeight = i;
            else if (col == "radius") idxRadius = i;
            else if (col == "canopy_r") idxCanopyR = i;
            else if (col == "canopy_g") idxCanopyG = i;
            else if (col == "canopy_b") idxCanopyB = i;
        }

        if (idxXLocal < 0 || idxYLocal < 0 || idxHeight < 0)
        {
            Debug.LogWarning($"[GeoTilePrefabImporter] Enhanced trees CSV missing required columns: {csvPath}");
            return;
        }

        var treesContainer = new GameObject("EnhancedTrees");
        treesContainer.transform.SetParent(root.transform, false);
        treesContainer.transform.localPosition = Vector3.zero;
        treesContainer.isStatic = true;

        float baseMin = (float)(tile.HasTileMinMax ? tile.TileMin : tile.GlobalMin);

        for (int i = 1; i < lines.Length; i++)
        {
            var parts = lines[i].Split(',');
            if (parts.Length <= Math.Max(Math.Max(idxXLocal, idxYLocal), idxHeight))
                continue;

            try
            {
                float xLocal = float.Parse(parts[idxXLocal].Trim(), ci);
                float yLocal = float.Parse(parts[idxYLocal].Trim(), ci);
                float zGround = idxZGround >= 0 && idxZGround < parts.Length
                    ? float.Parse(parts[idxZGround].Trim(), ci) : 0f;
                float height = float.Parse(parts[idxHeight].Trim(), ci);
                float radius = idxRadius >= 0 && idxRadius < parts.Length
                    ? float.Parse(parts[idxRadius].Trim(), ci) : height * 0.25f;

                int canopyR = 128, canopyG = 160, canopyB = 80;
                if (idxCanopyR >= 0 && idxCanopyR < parts.Length)
                    canopyR = Mathf.Clamp(int.Parse(parts[idxCanopyR].Trim()), 0, 255);
                if (idxCanopyG >= 0 && idxCanopyG < parts.Length)
                    canopyG = Mathf.Clamp(int.Parse(parts[idxCanopyG].Trim()), 0, 255);
                if (idxCanopyB >= 0 && idxCanopyB < parts.Length)
                    canopyB = Mathf.Clamp(int.Parse(parts[idxCanopyB].Trim()), 0, 255);

                Color canopyColor = new Color(canopyR / 255f, canopyG / 255f, canopyB / 255f);

                GameObject treeObj = CreateEnhancedTree(height, radius, canopyColor, i);
                treeObj.transform.SetParent(treesContainer.transform, false);
                treeObj.transform.localPosition = new Vector3(xLocal, zGround - baseMin, yLocal);
                treeObj.isStatic = true;
            }
            catch (Exception e)
            {
                Debug.LogWarning($"[GeoTilePrefabImporter] Failed to parse enhanced tree line {i}: {e.Message}");
            }
        }
    }

    private GameObject CreateEnhancedTree(float height, float radius, Color canopyColor, int index)
    {
        if (treePrefab != null)
        {
            var treeObj = PrefabUtility.InstantiatePrefab(treePrefab) as GameObject ?? Instantiate(treePrefab);
            treeObj.name = $"tree_{index}";

            float scaleY = height / 10f;
            float scaleXZ = radius * 2f / 5f;
            treeObj.transform.localScale = new Vector3(scaleXZ, scaleY, scaleXZ);

            var renderers = treeObj.GetComponentsInChildren<Renderer>();
            foreach (var rend in renderers)
            {
                foreach (var mat in rend.sharedMaterials)
                {
                    if (mat != null)
                    {
                        var matInst = new Material(mat);
                        matInst.color = canopyColor;
                        rend.material = matInst;
                    }
                }
            }

            return treeObj;
        }

        // Create procedural placeholder
        var tree = new GameObject($"tree_{index}");

        var trunk = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
        trunk.name = "trunk";
        trunk.transform.SetParent(tree.transform, false);
        trunk.transform.localPosition = new Vector3(0, height * 0.25f, 0);
        trunk.transform.localScale = new Vector3(0.3f, height * 0.5f, 0.3f);
        var trunkRend = trunk.GetComponent<Renderer>();
        if (trunkRend != null)
        {
            var trunkMat = new Material(Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"));
            trunkMat.color = new Color(0.4f, 0.25f, 0.15f);
            trunkRend.material = trunkMat;
        }

        var canopy = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        canopy.name = "canopy";
        canopy.transform.SetParent(tree.transform, false);
        canopy.transform.localPosition = new Vector3(0, height * 0.7f, 0);
        canopy.transform.localScale = new Vector3(radius * 2f, height * 0.5f, radius * 2f);
        var canopyRend = canopy.GetComponent<Renderer>();
        if (canopyRend != null)
        {
            var canopyMat = new Material(Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"));
            canopyMat.color = canopyColor;
            canopyRend.material = canopyMat;
        }

        return tree;
    }

    #region Helper Methods

    private static void EnsureDirectoryExists(string path)
    {
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
            AssetDatabase.Refresh();
        }
    }

    private static void EnsureHeightmapImportSettings(string assetPath)
    {
        var ti = (TextureImporter)AssetImporter.GetAtPath(assetPath);
        if (ti == null) return;

        bool changed = false;

        if (!ti.isReadable) { ti.isReadable = true; changed = true; }
        if (ti.sRGBTexture) { ti.sRGBTexture = false; changed = true; }
        if (ti.textureCompression != TextureImporterCompression.Uncompressed)
        {
            ti.textureCompression = TextureImporterCompression.Uncompressed;
            changed = true;
        }
        if (ti.npotScale != TextureImporterNPOTScale.None)
        {
            ti.npotScale = TextureImporterNPOTScale.None;
            changed = true;
        }
        if (ti.textureType != TextureImporterType.SingleChannel)
        {
            ti.textureType = TextureImporterType.SingleChannel;
            changed = true;
        }

        var ps = ti.GetDefaultPlatformTextureSettings();
        if (ps.format != TextureImporterFormat.R16)
        {
            ps.format = TextureImporterFormat.R16;
            ti.SetPlatformTextureSettings(ps);
            changed = true;
        }

        changed |= EnsurePlatformR16(ti, "Standalone");
        changed |= EnsurePlatformR16(ti, "Android");
        changed |= EnsurePlatformR16(ti, "iPhone");

        if (changed) ti.SaveAndReimport();
    }

    private static bool EnsurePlatformR16(TextureImporter ti, string platform)
    {
        var ps = ti.GetPlatformTextureSettings(platform);

        bool changed = false;
        if (ps.name != platform)
        {
            ps = new TextureImporterPlatformSettings
            {
                name = platform,
                overridden = true,
                format = TextureImporterFormat.R16,
                textureCompression = TextureImporterCompression.Uncompressed,
            };
            ti.SetPlatformTextureSettings(ps);
            return true;
        }

        if (!ps.overridden) { ps.overridden = true; changed = true; }
        if (ps.format != TextureImporterFormat.R16) { ps.format = TextureImporterFormat.R16; changed = true; }
        if (ps.textureCompression != TextureImporterCompression.Uncompressed)
        {
            ps.textureCompression = TextureImporterCompression.Uncompressed;
            changed = true;
        }

        if (changed) ti.SetPlatformTextureSettings(ps);
        return changed;
    }

    private static void EnsureOrthoImportSettings(string assetPath)
    {
        var ti = (TextureImporter)AssetImporter.GetAtPath(assetPath);
        if (ti == null) return;

        bool changed = false;
        if (!ti.isReadable) { ti.isReadable = true; changed = true; }
        if (!ti.sRGBTexture) { ti.sRGBTexture = true; changed = true; }
        if (ti.npotScale != TextureImporterNPOTScale.None) { ti.npotScale = TextureImporterNPOTScale.None; changed = true; }
        if (ti.wrapMode != TextureWrapMode.Clamp) { ti.wrapMode = TextureWrapMode.Clamp; changed = true; }
        if (ti.textureCompression != TextureImporterCompression.Uncompressed)
        {
            ti.textureCompression = TextureImporterCompression.Uncompressed;
            changed = true;
        }

        if (changed) ti.SaveAndReimport();
    }

    private static string NormalizeHeader(string s)
        => (s ?? "").Trim().ToLowerInvariant();

    private static Dictionary<string, int> BuildHeaderMap(string headerLine)
    {
        var map = new Dictionary<string, int>();
        var cols = headerLine.Split(',');
        for (int i = 0; i < cols.Length; i++)
        {
            var key = NormalizeHeader(cols[i]);
            if (string.IsNullOrEmpty(key)) continue;
            if (!map.ContainsKey(key))
                map[key] = i;
        }
        return map;
    }

    private static bool HasAll(Dictionary<string, int> map, params string[] required)
    {
        foreach (var r in required)
            if (!map.ContainsKey(NormalizeHeader(r)))
                return false;
        return true;
    }

    #endregion
}
