using System; using System.Collections.Generic; using System.IO; using System.Linq; using GitConverter.Lib.Factories; using GitConverter.Lib.Logging; using GitConverter.Lib.Models; namespace GitConverter.Lib.Converters {     public static class ConversionService     {         // Existing dictionaries unchanged except: add .geojsonseq mapping below         private static readonly Dictionary sarchiveRequirements =             new(StringComparer.OrdinalIgnoreCase)         {             { "EsriJson",     new[] { ".json", ".esrijson" } },             { "GeoJson",      new[] { ".geojson", ".json" } },             { "GeoJsonSeq",   new[] { ".json" } },             { "Kml",          new[] { ".kml" } },             { "Kmz",          new[] { ".kml" } },             { "Shapefile",    new[] { ".shp", ".shx", ".dbf" } },             { "Osm",          new[] { ".osm" } },             { "Gdb",          new[] { ".gdb" } },             { "Gpx",          new[] { ".gpx" } },             { "TopoJson",     new[] { ".json" } },             { "MapInfoInterchange", new[] { ".mif" } },             { "MapInfoTab",         new[] { ".tab", ".dat", ".map", ".id" } },             { "Csv",          new[] { ".csv" } },             { "GeoPackage",   new[] { ".gpkg" } },         };         private static readonly Dictionary sextensionToConverter =             new(StringComparer.OrdinalIgnoreCase)         {             { ".geojson", "GeoJson" },             { ".esrijson", "EsriJson" },             { ".kml", "Kml" },             { ".kmz", "Kmz" },             { ".shp", "Shapefile" },             { ".osm", "Osm" },             { ".gpx", "Gpx" },             { ".gml", "Gml" },             { ".gdb", "Gdb" },             { ".mif", "MapInfoInterchange" },             { ".tab", "MapInfoTab" },             { ".map", "MapInfoTab" },             { ".dat", "MapInfoTab" },             { ".id", "MapInfoTab" },             { ".csv", "Csv" },             { ".gpkg", "GeoPackage" },             // NEW: direct mapping for GeoJSON sequence             { ".geojsonseq", "GeoJsonSeq" },             { ".topojson", "TopoJson" },             { ".json", "GeoJson" } // default; will be refined by detector         };         // NEW: Unified JSON-family extension set         private static readonly HashSet sjsonFamilyExts =             new(StringComparer.OrdinalIgnoreCase)             { ".json", ".geojson", ".esrijson", ".topojson", ".geojsonseq" };         public static ConversionResult Run(             string gisInputFilePath,             string outFolderPath,             string tempFolderPath,             IConverterFactory factory = null)         {             try             {                 Log.Info("ConversionService: Run invoked.");                 var prep = ConverterUtils.ValidateAndPreparePaths(gisInputFilePath, outFolderPath, tempFolderPath);                 if (prep != null) return prep; // validation failure                 if (factory == null) factory = new ConverterFactory();                 // Optional: support directory .gdb inputs                 if (Directory.Exists(gisInputFilePath))                 {                     var nameExt = Path.GetExtension(gisInputFilePath);                     if (string.Equals(nameExt, ".gdb", StringComparison.OrdinalIgnoreCase))                     {                         if (!factory.TryCreate("Gdb", out var convGdb))                             return ConversionResult.Failure("Converter for 'Gdb' is not available.");                         return convGdb.Convert(gisInputFilePath, "Gdb", outFolderPath, tempFolderPath);                     }                     return ConversionResult.Failure("Unsupported folder input; provide a .gdb folder or a supported archive.");                 }                 if (ConverterUtils.IsArchiveFile(gisInputFilePath))                 {                     Log.Info($"Input '{gisInputFilePath}' detected as archive. Inspecting contents.");                     var entries = ConverterUtils.TryListArchiveEntries(gisInputFilePath);                     if (entries == null)                     {                         Log.Error("Failed to list archive entries.");                         return ConversionResult.Failure("Failed to inspect archive contents.");                     }                     // NEW: deterministic detection using source path                     var matchedConverter = DetectConverterFromArchiveEntries(entries, gisInputFilePath);                     if (string.IsNullOrEmpty(matchedConverter))                     {                         Log.Warn("No converter matched archive contents.");                         return ConversionResult.Failure("No converter matched archive contents or required files are missing.");                     }                     Log.Info($"Archive matched converter '{matchedConverter}'. Attempting to resolve converter instance.");                     if (!factory.TryCreate(matchedConverter, out var conv))                     {                         Log.Error($"ConverterFactory failed to resolve converter '{matchedConverter}'.");                         return ConversionResult.Failure($"Converter for '{matchedConverter}' is not available.");                     }                     Log.Info($"Converter '{matchedConverter}' resolved. Invoking Convert(…).");                     return conv.Convert(gisInputFilePath, matchedConverter, outFolderPath, tempFolderPath);                 }                 else                 {                     var ext = Path.GetExtension(gisInputFilePath);                     Log.Info($"Input '{gisInputFilePath}' detected as single file with extension '{ext}'.");                     if (string.IsNullOrWhiteSpace(ext))                     {                         Log.Warn("Input file has no extension.");                         return ConversionResult.Failure("Unknown input file type: missing extension.");                     }                     // NEW: JSON family handling + sniffing                     if (sjsonFamilyExts.Contains(ext))                     {                         if (!JsonFormatDetector.TryDetectFromFile(gisInputFilePath, out var jsonFmt))                         {                             Log.Error("Unable to parse JSON input to determine specific JSON GIS format.");                             return ConversionResult.Failure("Unable to determine JSON format (GeoJSON / EsriJSON / GeoJSONSeq / TopoJSON).");                         }                         var converterKeyForJson = jsonFmt switch                         {                             JsonFormatDetector.Format.GeoJson    => "GeoJson",                             JsonFormatDetector.Format.EsriJson   => "EsriJson",                             JsonFormatDetector.Format.GeoJsonSeq => "GeoJsonSeq",                             JsonFormatDetector.Format.TopoJson   => "TopoJson",                             => null                         };                         if (string.IsNullOrWhiteSpace(converterKeyForJson))                         {                             Log.Error("Failed to map detected JSON format to a converter key.");                             return ConversionResult.Failure("Failed to map JSON format to converter.");                         }                         Log.Info($"Detected JSON format '{jsonFmt}'. Resolving converter '{converterKeyForJson}'.");                         if (!factory.TryCreate(converterKeyForJson, out var convJson))                         {                             Log.Error($"ConverterFactory failed to resolve converter '{converterKeyForJson}'.");                             return ConversionResult.Failure($"Converter for '{converterKeyForJson}' is not available.");                         }                         return convJson.Convert(gisInputFilePath, converterKeyForJson, outFolderPath, tempFolderPath);                     }                     // Non-JSON mapping                     if (!sextensionToConverter.TryGetValue(ext, out var converterKeyNonJson))                     {                         Log.Warn($"No converter mapping for extension '{ext}'.");                         return ConversionResult.Failure($"Unknown input file type '{ext}'.");                     }                     Log.Info($"Mapped extension '{ext}' to converter '{converterKeyNonJson}'. Attempting to resolve.");                     if (!factory.TryCreate(converterKeyNonJson, out var convNonJson))                     {                         Log.Error($"ConverterFactory failed to resolve converter '{converterKeyNonJson}'.");                         return ConversionResult.Failure($"Converter for '{converterKeyNonJson}' is not available.");                     }                     Log.Info($"Converter '{converterKeyNonJson}' resolved. Invoking Convert(…).");                     return convNonJson.Convert(gisInputFilePath, converterKeyNonJson, outFolderPath, tempFolderPath);                 }             }             catch (Exception ex)             {                 Log.Error($"Unexpected error in ConversionService.Run: {ex.Message}", ex);                 return ConversionResult.Failure($"Unexpected error: {ex.Message}");             }         }         // NEW: deterministic archive detection         private static string DetectConverterFromArchiveEntries(IEnumerable entries, string sourcePath)         {             var exts = new HashSet(StringComparer.OrdinalIgnoreCase);             var paths = new List();             foreach (var e in entries)             {                 paths.Add(e);                 try                 {                     var ext = Path.GetExtension(e);                     if (!string.IsNullOrEmpty(ext))                         exts.Add(ext.ToLowerInvariant());                 }                 catch { /* ignore malformed names */ }             }             Log.Debug($"Archive contains {exts.Count} distinct extensions: {string.Join(", ", exts)}");             // 1) Outer extension bias for KMZ             var sourceExt = Path.GetExtension(sourcePath);             if (string.Equals(sourceExt, ".kmz", StringComparison.OrdinalIgnoreCase))             {                 Log.Debug("Outer extension is .kmz; selecting 'Kmz' converter.");                 return "Kmz";             }             // 2) FGDB detection by folder segments & markers             bool containsGdbFolder = paths.Any(p =>                 p.IndexOf(".gdb/", StringComparison.OrdinalIgnoreCase) >= 0 ||                 p.EndsWith(".gdb", StringComparison.OrdinalIgnoreCase));             bool containsFgdbMarkers = exts.Contains(".gdbtable") || exts.Contains(".gdbindexes") ||                                        exts.Contains(".gdbtablx") || exts.Contains(".atx");             if (containsGdbFolder || containsFgdbMarkers)             {                 Log.Debug("Archive contains File Geodatabase indicators; selecting 'Gdb'.");                 return "Gdb";             }             // 3) Deterministic priority for non-JSON types             var orderedCandidates = new[]             {                 "Shapefile",                 "MapInfoTab",                 "MapInfoInterchange",                 "GeoPackage",                 "Osm",                 "Gpx",                 "Kml",                 "Csv"             };             foreach (var key in orderedCandidates)             {                 if (sarchiveRequirements.TryGetValue(key, out var required))                 {                     var allPresent = required.All(r => exts.Contains(r));                     if (allPresent)                     {                         Log.Debug($"Archive satisfies requirements for '{key}'.");                         return key;                     }                 }             }             // 4) JSON family in archives: sniff first JSON-like entry             if (exts.Contains(".json") || exts.Contains(".geojson"))             {                 var jsonEntry = paths.FirstOrDefault(p =>                     p.EndsWith(".geojson", StringComparison.OrdinalIgnoreCase) ||                     p.EndsWith(".json", StringComparison.OrdinalIgnoreCase));                 if (jsonEntry != null)                 {                     Log.Debug($"Attempting JSON sniffing for '{jsonEntry}'.");                     if (ConverterUtils.TryOpenArchiveEntryStream(sourcePath, jsonEntry, out var stream))                     {                         using (stream)                         {                             if (JsonFormatDetector.TryDetectFromStream(stream, out var jsonFmt))                             {                                 var converterKeyForJson = jsonFmt switch                                 {                                     JsonFormatDetector.Format.GeoJson    => "GeoJson",                                     JsonFormatDetector.Format.EsriJson   => "EsriJson",                                     JsonFormatDetector.Format.GeoJsonSeq => "GeoJsonSeq",                                     JsonFormatDetector.Format.TopoJson   => "TopoJson",                                     => null                                 };                                 if (!string.IsNullOrWhiteSpace(converterKeyForJson))                                 {                                     Log.Debug($"JSON sniffing selected '{converterKeyForJson}'.");                                     return converterKeyForJson;                                 }                             }                         }                     }                     Log.Warn("JSON sniffing failed; cannot disambiguate JSON family inside archive.");                 }             }             Log.Debug("No archive-based converter match found.");             return null;         }     } }