using System.Runtime.InteropServices;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;

static class ModelImageExporter
{
    private const int PartDoc = 1;
    private const int AssemblyDocType = 2;
    private const int Silent = 1;
    private const int ReadOnly = 2;
    private const int HighlightSettleDelayMs = 650;

    public static List<string> DefaultViewNames { get; } = ["front", "back", "top", "bottom", "left", "right", "isometric"];
    public static List<string> DefaultSectionViewNames { get; } = ["front", "top", "right"];
    public static List<string> DefaultHighlightViewNames { get; } = ["isometric"];
    public static List<string> DefaultContextViewNames { get; } = ["isometric"];

    private static readonly Dictionary<string, ModelImageViewSpec> ViewSpecs = new(StringComparer.OrdinalIgnoreCase)
    {
        ["front"] = new("front", "*Front", (int)swStandardViews_e.swFrontView),
        ["back"] = new("back", "*Back", (int)swStandardViews_e.swBackView),
        ["left"] = new("left", "*Left", (int)swStandardViews_e.swLeftView),
        ["right"] = new("right", "*Right", (int)swStandardViews_e.swRightView),
        ["top"] = new("top", "*Top", (int)swStandardViews_e.swTopView),
        ["bottom"] = new("bottom", "*Bottom", (int)swStandardViews_e.swBottomView),
        ["isometric"] = new("isometric", "*Isometric", (int)swStandardViews_e.swIsometricView),
        ["trimetric"] = new("trimetric", "*Trimetric", (int)swStandardViews_e.swTrimetricView),
        ["dimetric"] = new("dimetric", "*Dimetric", (int)swStandardViews_e.swDimetricView)
    };

    private static readonly Dictionary<string, string[]> RefPlaneNameCandidates = new(StringComparer.OrdinalIgnoreCase)
    {
        ["front"] = ["前视基准面", "Front Plane"],
        ["top"] = ["上视基准面", "Top Plane"],
        ["right"] = ["右视基准面", "Right Plane"]
    };

    public static ModelImageExportReport Export(
        SldWorks sw,
        string modelPath,
        string outputDir,
        IReadOnlyCollection<string> requestedViews,
        IReadOnlyCollection<string> requestedSectionViews,
        double sectionOffsetMm,
        IReadOnlyCollection<ComponentHighlightImageRequest>? componentHighlightRequests,
        IReadOnlyCollection<InterfaceHighlightImageRequest>? interfaceHighlightRequests,
        IReadOnlyCollection<FeatureHighlightImageRequest>? featureHighlightRequests,
        IReadOnlyCollection<ComponentContextImageRequest>? componentContextRequests,
        Action<string, string> log)
    {
        Directory.CreateDirectory(outputDir);
        ClearGeneratedImageFamilyDirectory(outputDir, "component_highlights", log);
        ClearGeneratedImageFamilyDirectory(outputDir, "physical_interfaces", log);
        ClearGeneratedImageFamilyDirectory(outputDir, "feature_highlights", log);
        ClearGeneratedImageFamilyDirectory(outputDir, "component_contexts", log);

        var report = new ModelImageExportReport
        {
            Schema = "solidworks_model_image_export_v1",
            ModelPath = modelPath,
            OutputDir = outputDir,
            StartedAt = DateTimeOffset.Now,
            RequestedViews = requestedViews.ToList(),
            RequestedSectionViews = requestedSectionViews.ToList(),
            SectionOffsetMm = sectionOffsetMm
        };

        ModelDoc2? doc = null;
        var shadedFaceHighlightPreference = default(TemporaryUserPreferenceToggle);
        try
        {
            shadedFaceHighlightPreference = EnableTemporaryUserPreferenceToggle(
                sw,
                (int)swUserPreferenceToggle_e.swUseShadedFaceHighlight,
                "swUseShadedFaceHighlight",
                log);

            doc = OpenOrActivateModel(sw, modelPath, log, out var openErrors, out var openWarnings);
            report.OpenErrors = openErrors;
            report.OpenWarnings = openWarnings;

            if (doc == null)
            {
                report.Message = $"OpenDoc6 failed. errors={openErrors}, warnings={openWarnings}";
                report.CompletedAt = DateTimeOffset.Now;
                return report;
            }
            dynamic mathUtility = sw.GetMathUtility();
            var partFaceRuntimeMap = doc is SolidWorks.Interop.sldworks.PartDoc partDoc
                ? BuildPartFaceRuntimeMap(partDoc, log)
                : new Dictionary<int, Face2>();
            ClearSectionViewBeforeStandardViews(doc, requestedViews, log);

            foreach (var viewName in requestedViews)
            {
                report.Views.Add(ExportView(doc, viewName, outputDir, log));
            }

            foreach (var request in componentHighlightRequests ?? [])
            {
                foreach (var viewName in request.Views.Count > 0 ? request.Views : DefaultHighlightViewNames)
                {
                    report.Views.Add(ExportHighlightedComponentView(doc, mathUtility, request, viewName, outputDir, log));
                }
            }

            foreach (var request in interfaceHighlightRequests ?? [])
            {
                var views = request.Views.Count > 0 ? request.Views : ["best_oblique"];
                foreach (var viewName in views.Take(3))
                {
                    ClearNativeSelectionHighlight(doc, log, $"before_interface:{request.InterfaceId}");
                    report.Views.Add(ExportHighlightedInterfaceView(sw, doc, mathUtility, request, viewName, outputDir, log));
                }
            }

            foreach (var request in featureHighlightRequests ?? [])
            {
                var views = request.Views.Count > 0 ? request.Views : ["isometric"];
                foreach (var viewName in views.Take(2))
                {
                    ClearNativeSelectionHighlight(doc, log, $"before_feature:{request.FeatureId}");
                    report.Views.Add(ExportHighlightedFeatureView(sw, doc, mathUtility, partFaceRuntimeMap, request, viewName, outputDir, log));
                }
            }

            foreach (var group in (componentContextRequests ?? []).GroupBy(ContextVisibilityGroupKey, StringComparer.OrdinalIgnoreCase))
            {
                report.Views.AddRange(ExportComponentContextGroupViews(doc, mathUtility, group.ToList(), outputDir, log));
            }

            foreach (var sectionViewName in requestedSectionViews)
            {
                ClearNativeSelectionHighlight(doc, log, $"before_section:{sectionViewName}");
                report.Views.Add(ExportSectionView(doc, sectionViewName, sectionOffsetMm, outputDir, log));
            }

            report.Message = $"Exported {report.SuccessCount}/{report.Views.Count} model images.";
        }
        catch (Exception ex)
        {
            report.Message = $"Image export failed: {ex.Message}";
            report.Error = ex.ToString();
        }
        finally
        {
            RestoreTemporaryUserPreferenceToggle(sw, shadedFaceHighlightPreference, log);
            report.CompletedAt = DateTimeOffset.Now;
        }

        return report;
    }

    private static void ClearGeneratedImageFamilyDirectory(string outputDir, string familyDirectoryName, Action<string, string> log)
    {
        var path = Path.Combine(outputDir, familyDirectoryName);
        if (!Directory.Exists(path))
            return;

        try
        {
            Directory.Delete(path, recursive: true);
            log("image_clear_generated_family_dir", path);
        }
        catch (Exception ex)
        {
            log("image_clear_generated_family_dir_failed", $"{path}; {ex.Message}");
        }
    }

    private static ModelDoc2? OpenOrActivateModel(
        SldWorks sw,
        string modelPath,
        Action<string, string> log,
        out int errors,
        out int warnings)
    {
        errors = 0;
        warnings = 0;

        string title = Path.GetFileName(modelPath);
        int docType = Path.GetExtension(modelPath).Equals(".SLDASM", StringComparison.OrdinalIgnoreCase)
            ? AssemblyDocType
            : PartDoc;

        if (docType == PartDoc)
        {
            TryCloseOpenPartDocument(sw, modelPath, title, log);
            log("image_open_doc", $"{modelPath}; force_reopen_part=true");
            return sw.OpenDoc6(modelPath, docType, Silent | ReadOnly, "", ref errors, ref warnings) as ModelDoc2;
        }

        try
        {
            log("image_activate_doc", title);
            int activateErrors = 0;
            var active = sw.ActivateDoc3(title, true, 0, ref activateErrors) as ModelDoc2;
            if (active != null)
                return active;
        }
        catch (Exception ex)
        {
            log("image_activate_doc_failed", ex.Message);
        }

        log("image_open_doc", modelPath);
        return sw.OpenDoc6(modelPath, docType, Silent | ReadOnly, "", ref errors, ref warnings) as ModelDoc2;
    }

    private static void TryCloseOpenPartDocument(
        SldWorks sw,
        string modelPath,
        string title,
        Action<string, string> log)
    {
        try
        {
            log("image_force_reopen_part_activate_existing", title);
            int activateErrors = 0;
            var active = sw.ActivateDoc3(title, true, 0, ref activateErrors) as ModelDoc2;
            if (active == null)
            {
                log("image_force_reopen_part_no_existing_doc", $"title={title}; activate_errors={activateErrors}");
                return;
            }

            var activePath = Safe(() => active.GetPathName(), "");
            if (!string.Equals(Path.GetFullPath(activePath), Path.GetFullPath(modelPath), StringComparison.OrdinalIgnoreCase))
            {
                log("image_force_reopen_part_skip_close_path_mismatch", $"title={title}; active_path={activePath}");
                return;
            }

            var closeTitle = Safe(() => active.GetTitle(), title);
            sw.CloseDoc(closeTitle);
            log("image_force_reopen_part_closed_existing_doc", $"title={closeTitle}; path={activePath}");
        }
        catch (Exception ex)
        {
            log("image_force_reopen_part_close_existing_failed", $"{title}; {ex.Message}");
        }
    }

    private static ModelImageViewExport ExportView(
        ModelDoc2 doc,
        string requestedView,
        string outputDir,
        Action<string, string> log)
    {
        var view = NormalizeViewSpec(requestedView);
        var result = new ModelImageViewExport
        {
            ImageKind = "standard_model_view",
            EvidenceFamily = "model_overview",
            EvidencePurpose = "global_shape_overview",
            RequestedView = requestedView,
            ViewName = view.Name,
            SolidWorksNamedView = view.NamedView,
            SolidWorksViewId = view.ViewId,
            StartedAt = DateTimeOffset.Now
        };

        try
        {
            log("image_show_named_view", view.Name);
            doc.ShowNamedView2(view.NamedView, view.ViewId);
            doc.ViewZoomtofit2();
            doc.GraphicsRedraw2();

            string fileName = $"{SanitizeFileName(view.Name)}.jpg";
            string outputPath = Path.Combine(outputDir, fileName);
            int errors = 0;
            int warnings = 0;

            log("image_save_as_jpg", outputPath);
            bool saved = doc.SaveAs4(
                outputPath,
                (int)swSaveAsVersion_e.swSaveAsCurrentVersion,
                (int)swSaveAsOptions_e.swSaveAsOptions_Silent,
                ref errors,
                ref warnings);

            result.Ok = saved && File.Exists(outputPath);
            result.OutputPath = outputPath;
            result.SaveErrors = errors;
            result.SaveWarnings = warnings;
            result.Message = result.Ok
                ? "saved"
                : $"SaveAs4 returned {saved}, file_exists={File.Exists(outputPath)}";
        }
        catch (Exception ex)
        {
            result.Ok = false;
            result.Message = ex.Message;
            result.Error = ex.ToString();
        }
        finally
        {
            result.CompletedAt = DateTimeOffset.Now;
        }

        return result;
    }

    private static ModelImageViewExport ExportSectionView(
        ModelDoc2 doc,
        string requestedSectionView,
        double offsetMm,
        string outputDir,
        Action<string, string> log)
    {
        var spec = ParseSectionViewRequest(requestedSectionView, globalOffsetMm: offsetMm);
        var planeKey = spec.PlaneKey;
        var result = new ModelImageViewExport
        {
            ImageKind = "section_model_view",
            EvidenceFamily = "model_section",
            EvidencePurpose = "internal_structure_overview",
            RequestedView = requestedSectionView,
            ViewName = string.IsNullOrWhiteSpace(spec.Label) ? $"section_{planeKey}" : $"section_{spec.Label}",
            SectionPlaneKey = planeKey,
            SectionOffsetMm = spec.OffsetMm,
            SectionTarget = spec.Target,
            SectionReason = spec.Reason,
            StartedAt = DateTimeOffset.Now
        };

        ModelViewManager? manager = null;
        try
        {
            ClearNativeSelectionHighlight(doc, log, $"section_start:{planeKey}");
            manager = doc.ModelViewManager;
            if (manager == null)
                throw new InvalidOperationException("ModelViewManager is null.");

            log("image_remove_existing_section_view", planeKey);
            SafeRemoveSectionView(manager);
            ClearNativeSelectionHighlight(doc, log, $"section_after_remove_existing:{planeKey}");

            var plane = FindReferencePlane(doc, planeKey, out var planeName, out var planeSource);
            result.SectionPlaneName = planeName;
            result.Message = planeSource;
            if (plane == null)
                throw new InvalidOperationException($"Reference plane not found for section view `{planeKey}`.");

            log("image_create_section_view_data", $"{planeKey}; plane={planeName}; offset_mm={spec.OffsetMm}; target={spec.Target}");
            var sectionData = manager.CreateSectionViewData();
            if (sectionData == null)
                throw new InvalidOperationException("CreateSectionViewData returned null.");

            sectionData.FirstPlane = plane;
            sectionData.FirstOffset = spec.OffsetMm / 1000.0;
            sectionData.ShowSectionCap = false;
            sectionData.GraphicsOnlySection = true;
            sectionData.Redraw = true;

            bool created = manager.CreateSectionView(sectionData);
            if (!created)
                throw new InvalidOperationException("CreateSectionView returned false.");

            var display = ViewSpecs["isometric"];
            doc.ShowNamedView2(display.NamedView, display.ViewId);
            doc.ViewZoomtofit2();
            ClearNativeSelectionHighlight(doc, log, $"section_before_save:{planeKey}");

            string outputPath = Path.Combine(outputDir, $"{SanitizeFileName(result.ViewName)}.jpg");
            int errors = 0;
            int warnings = 0;
            log("image_save_section_jpg", outputPath);
            bool saved = doc.SaveAs4(
                outputPath,
                (int)swSaveAsVersion_e.swSaveAsCurrentVersion,
                (int)swSaveAsOptions_e.swSaveAsOptions_Silent,
                ref errors,
                ref warnings);

            result.SolidWorksNamedView = display.NamedView;
            result.SolidWorksViewId = display.ViewId;
            result.OutputPath = outputPath;
            result.SaveErrors = errors;
            result.SaveWarnings = warnings;
            result.Ok = saved && File.Exists(outputPath);
            result.Message = result.Ok
                ? $"saved with section plane {planeName}"
                : $"SaveAs4 returned {saved}, file_exists={File.Exists(outputPath)}";
        }
        catch (Exception ex)
        {
            result.Ok = false;
            result.Message = ex.Message;
            result.Error = ex.ToString();
        }
        finally
        {
            if (manager != null)
            {
                log("image_remove_section_view", planeKey);
                SafeRemoveSectionView(manager);
            }

            try { doc.GraphicsRedraw2(); } catch { }
            result.CompletedAt = DateTimeOffset.Now;
        }

        return result;
    }

    private static ModelImageViewExport ExportHighlightedComponentView(
        ModelDoc2 doc,
        dynamic mathUtility,
        ComponentHighlightImageRequest request,
        string requestedView,
        string outputDir,
        Action<string, string> log)
    {
        var view = TryNormalizeViewSpec(requestedView);
        var viewName = view?.Name ?? SanitizeFileName(requestedView);
        var result = new ModelImageViewExport
        {
            ImageKind = "assembly_component_highlight_view",
            EvidenceFamily = "assembly_component_position",
            EvidencePurpose = "component_position_and_whole_component_function",
            PositionVisibility = "unobstructed",
            RequestedView = requestedView,
            ViewName = $"highlight_{request.ComponentId}_{viewName}",
            SolidWorksNamedView = view?.NamedView ?? "custom_oblique",
            SolidWorksViewId = view?.ViewId ?? 0,
            HighlightPlanId = request.PlanId,
            HighlightComponentId = request.ComponentId,
            HighlightComponentInstanceName = request.InstanceName,
            HighlightComponentDisplayName = request.DisplayName,
            HighlightViewDirectionMm = request.PreferredDirectionMm.ToList(),
            ContextShownComponentCount = 0,
            StartedAt = DateTimeOffset.Now
        };

        try
        {
            var component = FindComponent(doc, request);
            if (component == null)
                throw new InvalidOperationException($"Component instance not found: {request.InstanceName}");

            if (doc is AssemblyDoc assembly)
            {
                result.ContextShownComponentCount = CountVisibleAssemblyComponents(assembly);
                log(
                    "image_component_highlight_visibility_preserved",
                    $"{request.ComponentId}; mode=whole_assembly_preserved; visible_components={result.ContextShownComponentCount}; hidden_by_exporter=0");
            }

            var customViewApplied = ApplyPreferredObliqueView(doc, mathUtility, request.PreferredDirectionMm, log, request.ComponentId);
            if (!customViewApplied)
            {
                var fallback = view ?? NormalizeViewSpec("isometric");
                doc.ShowNamedView2(fallback.NamedView, fallback.ViewId);
            }
            doc.ViewZoomtofit2();
            doc.GraphicsRedraw2();

            doc.ClearSelection2(true);
            var selectionManager = doc.SelectionManager as SelectionMgr
                ?? throw new InvalidOperationException("SelectionManager is null.");
            var selectData = selectionManager.CreateSelectData() as SelectData
                ?? throw new InvalidOperationException("CreateSelectData returned null.");

                log("image_select_component_for_highlight", $"{request.InstanceName}; view={viewName}; custom_oblique_view_applied={customViewApplied}");
            var selected = component.Select4(false, selectData, false);
            if (!selected)
                throw new InvalidOperationException($"Select4 returned false for component `{request.InstanceName}`.");
            LogComponentSelectionDiagnostics(doc, component, request.InstanceName, request.ComponentPath, log, $"highlight:{request.ComponentId}:after_select4");

            FlushGraphics(doc, log, $"highlight:{request.ComponentId}:before_final_highlight", HighlightSettleDelayMs);
            var nativeHighlightDrawn = DrawNativeSelectionHighlight(doc, log, $"highlight:{request.ComponentId}");
            LogComponentSelectionDiagnostics(doc, component, request.InstanceName, request.ComponentPath, log, $"highlight:{request.ComponentId}:before_save");

            var highlightDir = Path.Combine(outputDir, "component_highlights", SanitizeFileName(request.ComponentId));
            Directory.CreateDirectory(highlightDir);
            string outputPath = Path.Combine(highlightDir, $"{SanitizeFileName(viewName)}.jpg");
            int errors = 0;
            int warnings = 0;

            log("image_save_component_highlight_jpg", outputPath);
            bool saved = doc.SaveAs4(
                outputPath,
                (int)swSaveAsVersion_e.swSaveAsCurrentVersion,
                (int)swSaveAsOptions_e.swSaveAsOptions_Silent,
                ref errors,
                ref warnings);
            FlushGraphics(doc, log, $"highlight:{request.ComponentId}:after_save_settle", HighlightSettleDelayMs);

            result.Ok = saved && File.Exists(outputPath);
            result.OutputPath = outputPath;
            result.SaveErrors = errors;
            result.SaveWarnings = warnings;
            result.Message = result.Ok
                ? $"saved in the unchanged assembly visibility state with whole component selected using SolidWorks native selection highlight immediately before JPG export; custom_oblique_view_applied={customViewApplied}; native_highlight_drawn={nativeHighlightDrawn}; hidden_by_exporter=0; model not saved"
                : $"SaveAs4 returned {saved}, file_exists={File.Exists(outputPath)}";
        }
        catch (Exception ex)
        {
            result.Ok = false;
            result.Message = ex.Message;
            result.Error = ex.ToString();
        }
        finally
        {
            ClearNativeSelectionHighlight(doc, log, $"after_component:{request.ComponentId}");
            try { doc.GraphicsRedraw2(); } catch { }
            result.CompletedAt = DateTimeOffset.Now;
        }

        return result;
    }

    private static Component2? FindComponent(ModelDoc2 doc, ComponentHighlightImageRequest request)
    {
        return FindComponent(doc, request.InstanceName, request.ComponentPath);
    }

    private static int CountVisibleAssemblyComponents(AssemblyDoc assembly)
    {
        var visibleCount = 0;
        foreach (var component in (assembly.GetComponents(false) as object[] ?? []).OfType<Component2>())
        {
            try
            {
                if (component.Visible != (int)swComponentVisibilityState_e.swComponentHidden)
                    visibleCount++;
            }
            catch
            {
            }
        }

        return visibleCount;
    }

    private static ComponentVisibilityIsolationStats ApplyComponentVisibilityIsolation(
        ModelDoc2 doc,
        AssemblyDoc assembly,
        IReadOnlyCollection<ComponentImageRef> showComponents,
        IReadOnlyCollection<ComponentImageRef> preserveComponents,
        IDictionary<Component2, int> originalVisibility)
    {
        var allComponents = (assembly.GetComponents(false) as object[] ?? [])
            .OfType<Component2>()
            .ToList();
        var showKeys = showComponents
            .Select(component => (component.InstanceName, component.ComponentPath))
            .ToList();
        var preserveKeys = preserveComponents
            .Select(component => (component.InstanceName, component.ComponentPath))
            .DistinctBy(key => $"{key.InstanceName}::{key.ComponentPath}", StringComparer.OrdinalIgnoreCase)
            .ToList();
        var shownCount = 0;
        var preservedCount = 0;
        var hiddenCount = 0;

        foreach (var component in allComponents)
        {
            int original = swComponentVisibilityState_e.swComponentUnknown.GetHashCode();
            try { original = component.Visible; } catch { }
            originalVisibility[component] = original;

            var show = showKeys.Any(key => SameComponentHierarchyRef(component, key.InstanceName, key.ComponentPath));
            if (show)
            {
                component.Visible = (int)swComponentVisibilityState_e.swComponentVisible;
                shownCount++;
                continue;
            }

            var preserve = preserveKeys.Any(key => SameComponentHierarchyRef(component, key.InstanceName, key.ComponentPath));
            if (preserve)
            {
                preservedCount++;
                continue;
            }

            component.Visible = (int)swComponentVisibilityState_e.swComponentHidden;
            hiddenCount++;
        }

        return new ComponentVisibilityIsolationStats(shownCount, preservedCount, hiddenCount);
    }

    private static ComponentVisibilityIsolationStats ApplySelectiveComponentVisibility(
        AssemblyDoc assembly,
        IReadOnlyCollection<ComponentImageRef> showComponents,
        IReadOnlyCollection<ComponentImageRef> preserveComponents,
        IReadOnlyCollection<ComponentImageRef> hideComponents,
        IDictionary<Component2, int> originalVisibility)
    {
        var allComponents = (assembly.GetComponents(false) as object[] ?? [])
            .OfType<Component2>()
            .ToList();
        var showKeys = showComponents
            .Select(component => (component.InstanceName, component.ComponentPath))
            .ToList();
        var preserveKeys = preserveComponents
            .Select(component => (component.InstanceName, component.ComponentPath))
            .DistinctBy(key => $"{key.InstanceName}::{key.ComponentPath}", StringComparer.OrdinalIgnoreCase)
            .ToList();
        var hideKeys = hideComponents
            .Select(component => (component.InstanceName, component.ComponentPath))
            .DistinctBy(key => $"{key.InstanceName}::{key.ComponentPath}", StringComparer.OrdinalIgnoreCase)
            .ToList();
        var shownCount = 0;
        var preservedCount = 0;
        var hiddenCount = 0;

        foreach (var component in allComponents)
        {
            int original = swComponentVisibilityState_e.swComponentUnknown.GetHashCode();
            try { original = component.Visible; } catch { }
            originalVisibility[component] = original;

            var show = showKeys.Any(key => SameComponentHierarchyRef(component, key.InstanceName, key.ComponentPath));
            if (show)
            {
                component.Visible = (int)swComponentVisibilityState_e.swComponentVisible;
                shownCount++;
                continue;
            }

            var preserve = preserveKeys.Any(key => SameComponentHierarchyRef(component, key.InstanceName, key.ComponentPath));
            if (preserve)
            {
                preservedCount++;
                continue;
            }

            var hide = hideKeys.Any(key => SameComponentHierarchyRef(component, key.InstanceName, key.ComponentPath));
            if (!hide)
                continue;

            component.Visible = (int)swComponentVisibilityState_e.swComponentHidden;
            hiddenCount++;
        }

        return new ComponentVisibilityIsolationStats(shownCount, preservedCount, hiddenCount);
    }

    private static void RestoreComponentVisibility(IDictionary<Component2, int> originalVisibility)
    {
        foreach (var pair in originalVisibility)
        {
            try
            {
                if (pair.Value != (int)swComponentVisibilityState_e.swComponentUnknown)
                    pair.Key.Visible = pair.Value;
            }
            catch
            {
            }
        }
    }

    private static ModelImageViewExport ExportHighlightedFeatureView(
        SldWorks sw,
        ModelDoc2 doc,
        dynamic mathUtility,
        IReadOnlyDictionary<int, Face2> partFaceRuntimeMap,
        FeatureHighlightImageRequest request,
        string requestedView,
        string outputDir,
        Action<string, string> log)
    {
        var view = TryNormalizeViewSpec(requestedView);
        var viewName = view?.Name ?? SanitizeFileName(requestedView);
        var result = new ModelImageViewExport
        {
            ImageKind = string.IsNullOrWhiteSpace(request.ImageKind) ? "part_feature_highlight_view" : request.ImageKind,
            EvidenceFamily = string.IsNullOrWhiteSpace(request.EvidenceFamily) ? "part_feature_surface" : request.EvidenceFamily,
            EvidencePurpose = string.IsNullOrWhiteSpace(request.EvidencePurpose) ? "part_feature_and_surface_analysis" : request.EvidencePurpose,
            RequestedView = requestedView,
            ViewName = $"feature_highlight_{request.FeatureId}_{viewName}",
            SolidWorksNamedView = view?.NamedView ?? "custom_oblique",
            SolidWorksViewId = view?.ViewId ?? 0,
            HighlightPlanId = request.PlanId,
            HighlightComponentId = request.ComponentId,
            HighlightComponentInstanceName = request.InstanceName,
            HighlightComponentDisplayName = request.DisplayName,
            HighlightFeatureId = request.FeatureId,
            HighlightFeatureType = request.FeatureType,
            HighlightFaceRefs = request.FaceRefs.ToList(),
            HighlightViewDirectionMm = request.PreferredDirectionMm.ToList(),
            HighlightBlockingFaceRefs = request.BlockingFaceRefs.ToList(),
            StartedAt = DateTimeOffset.Now
        };

        var targetFaces = new List<Face2>();
        try
        {
            if (doc is not SolidWorks.Interop.sldworks.PartDoc)
                throw new InvalidOperationException("Feature highlight images currently require a direct part document.");

            var customViewApplied = ApplyPreferredObliqueView(doc, mathUtility, request.PreferredDirectionMm, log, request.FeatureId);
            if (!customViewApplied)
            {
                var fallback = view ?? NormalizeViewSpec("isometric");
                doc.ShowNamedView2(fallback.NamedView, fallback.ViewId);
            }
            doc.ViewZoomtofit2();
            doc.GraphicsRedraw2();

            doc.ClearSelection2(true);
            targetFaces = ResolvePartFacesByRefs(partFaceRuntimeMap, request.FaceRefs, log, request.FeatureId);
            if (targetFaces.Count == 0)
                throw new InvalidOperationException($"No faces found for feature `{request.FeatureId}`.");

            var selected = SelectFeatureFaces(doc, targetFaces, log, request.FeatureId);
            if (selected == 0)
                throw new InvalidOperationException($"No faces selected for feature `{request.FeatureId}`.");

            LogFeatureHighlightDiagnostics(sw, doc, selected, log, $"{request.FeatureId}; after_select");
            var nativeHighlightDrawn = DrawNativeSelectionHighlight(doc, log, $"feature:{request.FeatureId}");
            LogFeatureHighlightDiagnostics(sw, doc, selected, log, $"{request.FeatureId}; after_draw_highlight");

            var highlightDir = string.IsNullOrWhiteSpace(request.OutputRelativeDirectory)
                ? Path.Combine(outputDir, "feature_highlights", SanitizeFileName(request.FeatureId))
                : Path.Combine(outputDir, request.OutputRelativeDirectory.Replace('/', Path.DirectorySeparatorChar));
            Directory.CreateDirectory(highlightDir);
            var fileStem = string.IsNullOrWhiteSpace(request.OutputFileStem)
                ? SanitizeFileName(viewName)
                : SanitizeFileName(request.OutputFileStem);
            string outputPath = Path.Combine(highlightDir, $"{fileStem}.jpg");
            int errors = 0;
            int warnings = 0;

            log("image_save_feature_highlight_jpg", $"{request.FeatureId}; faces={selected}; path={outputPath}");
            bool saved = doc.SaveAs4(
                outputPath,
                (int)swSaveAsVersion_e.swSaveAsCurrentVersion,
                (int)swSaveAsOptions_e.swSaveAsOptions_Silent,
                ref errors,
                ref warnings);
            LogFeatureHighlightDiagnostics(sw, doc, selected, log, $"{request.FeatureId}; after_save");

            result.Ok = saved && File.Exists(outputPath);
            result.OutputPath = outputPath;
            result.SaveErrors = errors;
            result.SaveWarnings = warnings;
            result.Message = result.Ok
                ? $"saved with runtime B-rep Face2 objects selected immediately before SaveAs4; selected_faces={selected}; custom_oblique_view_applied={customViewApplied}; native_highlight_drawn={nativeHighlightDrawn}"
                : $"SaveAs4 returned {saved}, file_exists={File.Exists(outputPath)}";
        }
        catch (Exception ex)
        {
            result.Ok = false;
            result.Message = ex.Message;
            result.Error = ex.ToString();
        }
        finally
        {
            ClearNativeSelectionHighlight(doc, log, $"after_feature:{request.FeatureId}");
            result.CompletedAt = DateTimeOffset.Now;
        }

        return result;
    }

    private static ModelImageViewExport ExportHighlightedInterfaceView(
        SldWorks sw,
        ModelDoc2 doc,
        dynamic mathUtility,
        InterfaceHighlightImageRequest request,
        string requestedView,
        string outputDir,
        Action<string, string> log)
    {
        var view = TryNormalizeViewSpec(requestedView);
        var viewName = view?.Name ?? SanitizeFileName(requestedView);
        var result = new ModelImageViewExport
        {
            ImageKind = "assembly_physical_interface_view",
            EvidenceFamily = "assembly_physical_interface",
            EvidencePurpose = "contact_and_fit_function_analysis",
            PositionVisibility = "interface_isolated",
            RequestedView = requestedView,
            ViewName = $"interface_highlight_{request.InterfaceId}_{viewName}",
            SolidWorksNamedView = view?.NamedView ?? "custom_oblique",
            SolidWorksViewId = view?.ViewId ?? 0,
            HighlightPlanId = request.PlanId,
            HighlightInterfaceId = request.InterfaceId,
            HighlightInterfaceContactKind = request.ContactKind,
            HighlightInterfaceMateRole = request.MateRole,
            HighlightComponentId = request.ComponentA.ComponentId,
            HighlightComponentInstanceName = request.ComponentA.InstanceName,
            HighlightComponentDisplayName = request.ComponentA.DisplayName,
            HighlightInterfaceComponentAId = request.ComponentA.ComponentId,
            HighlightInterfaceComponentAInstanceName = request.ComponentA.InstanceName,
            HighlightInterfaceComponentADisplayName = request.ComponentA.DisplayName,
            HighlightInterfaceComponentBId = request.ComponentB.ComponentId,
            HighlightInterfaceComponentBInstanceName = request.ComponentB.InstanceName,
            HighlightInterfaceComponentBDisplayName = request.ComponentB.DisplayName,
            HighlightFaceRefs = request.FaceARefs.Concat(request.FaceBRefs).Distinct(StringComparer.OrdinalIgnoreCase).ToList(),
            HighlightViewDirectionMm = request.PreferredDirectionMm.ToList(),
            HighlightInterfaceSourceContactIds = request.SourceContactIds.ToList(),
            HighlightInterfaceAxisMm = request.InterfaceAxisMm.ToList(),
            HighlightInterfaceCenterMm = request.InterfaceCenterMm.ToList(),
            HighlightInterfaceBBoxMm = request.InterfaceBBoxMm.ToList(),
            InterfaceAnalysisPriority = request.AnalysisPriority,
            InterfaceConfidenceTier = request.ConfidenceTier,
            InterfaceEvidencePurpose = request.EvidencePurpose,
            InterfaceTrimmedPatchVerified = request.TrimmedPatchVerified,
            InterfaceVerificationStatus = request.VerificationStatus,
            InterfaceVerificationMethods = request.VerificationMethods.ToList(),
            InterfaceVerificationSampleHits = request.VerificationSampleHits,
            InterfaceVerificationMinDistanceMm = request.VerificationMinDistanceMm,
            InterfaceDisplayMode = request.DisplayMode,
            InterfaceActualAssemblyPosition = request.ActualAssemblyPosition,
            InterfaceNotActualClearance = request.NotActualClearance,
            ContextShownComponentCount = request.ShowComponents.Count,
            StartedAt = DateTimeOffset.Now
        };

        var originalVisibility = new Dictionary<Component2, int>();
        var visibilityContextApplied = false;
        var visibilityHiddenCount = 0;
        var visibilityPreservedCount = 0;

        try
        {
            if (doc is not AssemblyDoc assembly)
                throw new InvalidOperationException("Interface highlight images require an assembly document.");

            var componentA = FindComponent(doc, request.ComponentA.InstanceName, request.ComponentA.ComponentPath);
            var componentB = FindComponent(doc, request.ComponentB.InstanceName, request.ComponentB.ComponentPath);
            if (componentA == null)
                throw new InvalidOperationException($"Component A not found: {request.ComponentA.InstanceName}");
            if (componentB == null)
                throw new InvalidOperationException($"Component B not found: {request.ComponentB.InstanceName}");

            if (request.ShowComponents.Count > 0)
            {
                var stats = ApplyComponentVisibilityIsolation(
                    doc,
                    assembly,
                    request.ShowComponents,
                    request.PreserveComponents,
                    originalVisibility);
                visibilityContextApplied = true;
                visibilityHiddenCount = stats.Hidden;
                visibilityPreservedCount = stats.Preserved;
                log("image_interface_highlight_visibility_applied", $"{request.InterfaceId}; shown={stats.Shown}; preserved_context={stats.Preserved}; hidden={stats.Hidden}");
                doc.ClearSelection2(true);
                doc.GraphicsRedraw2();
            }

            var effectiveDirection = ResolveInterfaceViewDirection(request, requestedView);
            result.HighlightViewDirectionMm = effectiveDirection.ToList();
            var customViewApplied = ApplyPreferredObliqueView(doc, mathUtility, effectiveDirection, log, request.InterfaceId);
            if (!customViewApplied)
            {
                var fallback = view ?? NormalizeViewSpec("isometric");
                doc.ShowNamedView2(fallback.NamedView, fallback.ViewId);
            }
            doc.GraphicsRedraw2();

            var faces = request.RuntimeFaces.Count > 0
                ? request.RuntimeFaces.Where(face => face != null).Distinct().ToList()
                : [];
            if (faces.Count == 0)
            {
                faces.AddRange(ResolveAssemblyComponentFaces(componentA, request.FaceAIndices, log, $"{request.InterfaceId}:A"));
                faces.AddRange(ResolveAssemblyComponentFaces(componentB, request.FaceBIndices, log, $"{request.InterfaceId}:B"));
            }
            else
            {
                log("image_resolve_interface_runtime_faces", $"{request.InterfaceId}; faces={faces.Count}");
            }
            if (faces.Count == 0)
                throw new InvalidOperationException($"No runtime faces resolved for interface `{request.InterfaceId}`.");

            var selected = SelectFeatureFaces(doc, faces, log, $"interface:{request.InterfaceId}");
            if (selected == 0)
                throw new InvalidOperationException($"No faces selected for interface `{request.InterfaceId}`.");

            var localZoomApplied = TryZoomToSelectedInterface(doc, log, request.InterfaceId);
            if (!localZoomApplied)
                doc.ViewZoomtofit2();
            doc.GraphicsRedraw2();

            LogFeatureHighlightDiagnostics(sw, doc, selected, log, $"interface:{request.InterfaceId}; after_select");
            FlushGraphics(doc, log, $"interface:{request.InterfaceId}:before_final_highlight", HighlightSettleDelayMs);
            var nativeHighlightDrawn = DrawNativeSelectionHighlight(doc, log, $"interface:{request.InterfaceId}");
            LogFeatureHighlightDiagnostics(sw, doc, selected, log, $"interface:{request.InterfaceId}; after_draw_highlight");

            var highlightDir = Path.Combine(outputDir, "physical_interfaces", SanitizeFileName(request.InterfaceId));
            Directory.CreateDirectory(highlightDir);
            var fileStem = SanitizeFileName(viewName);
            string outputPath = Path.Combine(highlightDir, $"{fileStem}.jpg");
            int errors = 0;
            int warnings = 0;

            log("image_save_interface_highlight_jpg", $"{request.InterfaceId}; faces={selected}; path={outputPath}");
            bool saved = doc.SaveAs4(
                outputPath,
                (int)swSaveAsVersion_e.swSaveAsCurrentVersion,
                (int)swSaveAsOptions_e.swSaveAsOptions_Silent,
                ref errors,
                ref warnings);

            result.Ok = saved && File.Exists(outputPath);
            result.OutputPath = outputPath;
            result.SaveErrors = errors;
            result.SaveWarnings = warnings;
            result.Message = result.Ok
                ? $"saved physical interface evidence; selected_faces={selected}; source_contacts={request.SourceContactIds.Count}; view_purpose={requestedView}; custom_view={customViewApplied}; local_zoom={localZoomApplied}; native_highlight={nativeHighlightDrawn}; actual_assembly_position={request.ActualAssemblyPosition}; not_actual_clearance={request.NotActualClearance}; visibility_context_applied={visibilityContextApplied}; hidden={visibilityHiddenCount}; preserved_context={visibilityPreservedCount}; model not saved"
                : $"SaveAs4 returned {saved}, file_exists={File.Exists(outputPath)}";
        }
        catch (Exception ex)
        {
            result.Ok = false;
            result.Message = ex.Message;
            result.Error = ex.ToString();
        }
        finally
        {
            ClearNativeSelectionHighlight(doc, log, $"after_interface:{request.InterfaceId}");
            RestoreComponentVisibility(originalVisibility);
            try { doc.GraphicsRedraw2(); } catch { }
            result.CompletedAt = DateTimeOffset.Now;
        }

        return result;
    }

    private static List<Face2> ResolveAssemblyComponentFaces(
        Component2 component,
        IReadOnlyCollection<int> faceIndices,
        Action<string, string> log,
        string scope)
    {
        var required = faceIndices.Where(index => index > 0).Distinct().ToList();
        if (required.Count == 0)
            return [];

        var map = BuildComponentFaceRuntimeMap(component, log, scope);
        var result = required.Where(map.ContainsKey).Select(index => map[index]).ToList();
        if (result.Count != required.Count)
            log("image_resolve_interface_faces_partial", $"{scope}; requested={required.Count}; found={result.Count}");
        return result;
    }

    private static double[] ResolveInterfaceViewDirection(InterfaceHighlightImageRequest request, string requestedView)
    {
        var axis = request.InterfaceAxisMm.Length >= 3 ? NormalizeVector(request.InterfaceAxisMm) : [];
        var normal = request.InterfaceNormalMm.Length >= 3 ? NormalizeVector(request.InterfaceNormalMm) : [];
        if (requestedView.Equals("axis_end", StringComparison.OrdinalIgnoreCase) && axis.Length >= 3)
            return axis;
        if (requestedView.Equals("axis_side", StringComparison.OrdinalIgnoreCase) && axis.Length >= 3)
        {
            var reference = Math.Abs(Dot(axis, [0.0, 1.0, 0.0])) > 0.88
                ? new[] { 0.0, 0.0, 1.0 }
                : new[] { 0.0, 1.0, 0.0 };
            return NormalizeVector(Cross(axis, reference));
        }
        if (requestedView.Equals("normal_view", StringComparison.OrdinalIgnoreCase) && normal.Length >= 3)
            return normal;
        return request.PreferredDirectionMm.Length >= 3
            ? NormalizeVector(request.PreferredDirectionMm)
            : [1.0, 1.0, 1.0];
    }

    private static bool TryZoomToSelectedInterface(ModelDoc2 doc, Action<string, string> log, string interfaceId)
    {
        try
        {
            dynamic extension = doc.Extension;
            extension.ViewZoomToSelection();
            log("image_interface_zoom_to_selection", interfaceId);
            return true;
        }
        catch (Exception ex)
        {
            log("image_interface_zoom_to_selection_failed", $"{interfaceId}; {ex.Message}");
            return false;
        }
    }

    private static Face2? ResolveAssemblyComponentFace(
        Component2 component,
        int faceIndex,
        Action<string, string> log,
        string scope)
    {
        if (faceIndex <= 0)
            return null;

        var map = BuildComponentFaceRuntimeMap(component, log, scope);
        if (map.TryGetValue(faceIndex, out var face))
            return face;

        log("image_resolve_interface_face_missing", $"{scope}; face_index={faceIndex}; available={map.Count}");
        return null;
    }

    private static Dictionary<int, Face2> BuildComponentFaceRuntimeMap(
        Component2 component,
        Action<string, string> log,
        string scope)
    {
        var result = new Dictionary<int, Face2>();
        foreach (var bodyObj in EnumerateComponentBodies(component))
        {
            if (bodyObj is not Body2 body)
                continue;

            var faceIndex = 0;
            for (object? faceObj = Safe<object?>(() => body.GetFirstFace(), null); faceObj != null;)
            {
                faceIndex++;
                if (faceObj is not Face2 face)
                    break;

                result[faceIndex] = face;
                faceObj = Safe<object?>(() => face.GetNextFace(), null);
            }
        }

        log("image_component_face_runtime_map_built", $"{scope}; faces={result.Count}");
        return result;
    }

    private static object[] EnumerateComponentBodies(Component2 component)
    {
        try
        {
            dynamic dyn = component;
            object info;
            return ToObjectArray(dyn.GetBodies3((int)swBodyType_e.swSolidBody, out info));
        }
        catch
        {
            return [];
        }
    }

    private static Dictionary<int, Face2> BuildPartFaceRuntimeMap(
        PartDoc part,
        Action<string, string> log)
    {
        var result = new Dictionary<int, Face2>();
        var faceIndex = 0;
        foreach (var bodyObj in ToObjectArray(part.GetBodies2((int)swBodyType_e.swSolidBody, true)))
        {
            if (bodyObj is not Body2 body)
                continue;

            for (object? faceObj = Safe<object?>(() => body.GetFirstFace(), null); faceObj != null;)
            {
                faceIndex++;
                if (faceObj is not Face2 face)
                    break;

                result[faceIndex] = face;

                faceObj = Safe<object?>(() => face.GetNextFace(), null);
            }
        }

        log("image_part_face_runtime_map_built", $"faces={result.Count}");
        return result;
    }

    private static List<Face2> ResolvePartFacesByRefs(
        IReadOnlyDictionary<int, Face2> partFaceRuntimeMap,
        IReadOnlyCollection<string> faceRefs,
        Action<string, string> log,
        string featureId)
    {
        var required = faceRefs
            .Select(ParseFaceIndex)
            .Where(index => index > 0)
            .Distinct()
            .ToList();
        if (required.Count == 0)
            return [];

        var result = new List<Face2>();
        foreach (var index in required)
        {
            if (partFaceRuntimeMap.TryGetValue(index, out var face))
                result.Add(face);
        }

        var missing = required.Count - result.Count;
        if (missing > 0)
            log("image_resolve_runtime_feature_faces_partial", $"{featureId}; found={result.Count}; missing={missing}");
        return result;
    }

    private static int SelectFeatureFaces(
        ModelDoc2 doc,
        IReadOnlyList<Face2> faces,
        Action<string, string> log,
        string featureId)
    {
        if (faces.Count == 0)
            return 0;

        doc.ClearSelection2(true);
        var selectData = (doc.SelectionManager as SelectionMgr)?.CreateSelectData() as SelectData;
        var faceObjects = BuildFaceDispatchWrappers(faces);
        log("image_feature_face_dispatch_wrappers", $"{featureId}; faces={faces.Count}; wrappers={faceObjects.Length}");

        var selected = 0;
        try
        {
            selected = doc.Extension.MultiSelect2(faceObjects, false, selectData);
            log("image_multiselect_feature_faces", $"{featureId}; requested={faces.Count}; selected={selected}");
        }
        catch (Exception ex)
        {
            log("image_multiselect_feature_faces_failed", $"{featureId}; {ex.Message}");
        }

        if (selected <= 0 && doc.SelectionManager is SelectionMgr selectionManager)
        {
            try
            {
                selectionManager.SuspendSelectionList();
                selected = selectionManager.AddSelectionListObjects(faceObjects, selectData);
                selectionManager.ResumeSelectionList2(true);
                log("image_selection_list_feature_faces", $"{featureId}; requested={faces.Count}; selected={selected}");
            }
            catch (Exception ex)
            {
                try { selectionManager.ResumeSelectionList2(false); } catch { }
                log("image_selection_list_feature_faces_failed", $"{featureId}; {ex.Message}");
            }
        }

        if (selected <= 0)
        {
            foreach (var face in faces)
            {
                var append = selected > 0;
                var ok = false;
                try
                {
                    if (face is Entity entity)
                        ok = entity.Select4(append, selectData);
                }
                catch
                {
                }

                if (ok)
                    selected++;
            }

            log("image_select4_feature_faces_fallback", $"{featureId}; requested={faces.Count}; selected={selected}");
        }

        doc.WindowRedraw();
        doc.GraphicsRedraw2();
        return selected;
    }

    private static void LogFeatureHighlightDiagnostics(
        SldWorks sw,
        ModelDoc2 doc,
        int selected,
        Action<string, string> log,
        string scope)
    {
        var parts = new List<string> { scope, $"selected={selected}" };

        try
        {
            var activeView = doc.IActiveView as ModelView ?? doc.ActiveView as ModelView;
            if (activeView != null)
                parts.Add($"display_mode={activeView.DisplayMode}");
            else
                parts.Add("display_mode=<no_active_view>");
        }
        catch (Exception ex)
        {
            parts.Add($"display_mode_error={ex.Message}");
        }

        try
        {
            if (doc.SelectionManager is SelectionMgr selectionManager)
            {
                var count = Safe(() => selectionManager.GetSelectedObjectCount2(-1), selected);
                parts.Add($"selection_count={count}");

                var types = new List<string>();
                for (var i = 1; i <= Math.Min(count, 12); i++)
                {
                    var type = Safe(() => selectionManager.GetSelectedObjectType3(i, -1), 0);
                    var name = Enum.GetName(typeof(swSelectType_e), type) ?? type.ToString();
                    types.Add($"{i}:{name}({type})");
                }
                parts.Add($"selection_types=[{string.Join(",", types)}]");
            }
            else
            {
                parts.Add("selection_manager=<null>");
            }
        }
        catch (Exception ex)
        {
            parts.Add($"selection_diagnostics_error={ex.Message}");
        }

        try
        {
            parts.Add($"swUseShadedFaceHighlight={sw.GetUserPreferenceToggle((int)swUserPreferenceToggle_e.swUseShadedFaceHighlight)}");
            parts.Add($"selected_face_shaded_color={sw.GetUserPreferenceIntegerValue((int)swUserPreferenceIntegerValue_e.swSystemColorsSelectedFaceShaded)}");
            parts.Add($"highlight_color={sw.GetUserPreferenceIntegerValue((int)swUserPreferenceIntegerValue_e.swSystemColorsHighlight)}");
        }
        catch (Exception ex)
        {
            parts.Add($"preference_diagnostics_error={ex.Message}");
        }

        log("image_feature_highlight_diagnostics", string.Join("; ", parts));
    }

    private static DispatchWrapper[] BuildFaceDispatchWrappers(IReadOnlyList<Face2> faces)
    {
        return faces
            .Select(face => face is Entity entity ? new DispatchWrapper(entity) : new DispatchWrapper(face))
            .ToArray();
    }

    private static int ParseFaceIndex(string faceRef)
    {
        var marker = faceRef.LastIndexOf("face#", StringComparison.OrdinalIgnoreCase);
        if (marker < 0)
            return 0;
        var text = faceRef[(marker + "face#".Length)..].Trim();
        return int.TryParse(text, out var index) ? index : 0;
    }

    private static string ContextVisibilityGroupKey(ComponentContextImageRequest request)
    {
        if (!string.IsNullOrWhiteSpace(request.GroupId))
            return request.GroupId;

        var showKey = string.Join("|", request.ShowComponents
            .Select(c => $"{c.InstanceName}::{c.ComponentPath}")
            .OrderBy(v => v, StringComparer.OrdinalIgnoreCase));
        return string.IsNullOrWhiteSpace(showKey) ? request.PlanId : showKey;
    }

    private static List<ModelImageViewExport> ExportComponentContextGroupViews(
        ModelDoc2 doc,
        dynamic mathUtility,
        List<ComponentContextImageRequest> requests,
        string outputDir,
        Action<string, string> log)
    {
        var results = new List<ModelImageViewExport>();
        var first = requests.FirstOrDefault();
        var startedAt = DateTimeOffset.Now;
        if (first == null)
            return results;

        if (doc is not AssemblyDoc assembly)
        {
            foreach (var request in requests)
                results.Add(BuildFailedContextResult(request, "isometric", startedAt, "Context isolate images require an assembly document."));
            return results;
        }

        var originalVisibility = new Dictionary<Component2, int>();

        try
        {
            var preserveComponents = requests
                .SelectMany(request => request.PreserveComponents)
                .DistinctBy(component => $"{component.InstanceName}::{component.ComponentPath}", StringComparer.OrdinalIgnoreCase)
                .ToList();
            var hideComponents = requests
                .SelectMany(request => request.HideComponents)
                .DistinctBy(component => $"{component.InstanceName}::{component.ComponentPath}", StringComparer.OrdinalIgnoreCase)
                .ToList();
            var stats = ApplySelectiveComponentVisibility(
                assembly,
                first.ShowComponents,
                preserveComponents,
                hideComponents,
                originalVisibility);

            log("image_component_context_visibility_applied", $"{first.GroupId}; mode=hide_blockers_only; targets={requests.Count}; shown={stats.Shown}; preserved_connected={stats.Preserved}; requested_blockers={hideComponents.Count}; hidden={stats.Hidden}");
            doc.ClearSelection2(true);
            doc.GraphicsRedraw2();

            foreach (var request in requests)
            {
                var views = request.Views.Count > 0 ? request.Views : DefaultContextViewNames;
                var target = FindComponent(doc, request.TargetInstanceName, request.TargetComponentPath);
                if (target == null)
                {
                    results.Add(BuildFailedContextResult(request, views.FirstOrDefault() ?? "isometric", startedAt, $"Target component instance not found: {request.TargetInstanceName}"));
                    continue;
                }

                foreach (var viewName in views)
                {
                    results.Add(SaveComponentContextView(doc, mathUtility, target, request, viewName, outputDir, log));
                }
            }
        }
        catch (Exception ex)
        {
            foreach (var request in requests)
            {
                var views = request.Views.Count > 0 ? request.Views : DefaultContextViewNames;
                results.Add(BuildFailedContextResult(request, views.FirstOrDefault() ?? "isometric", startedAt, ex.Message, ex.ToString()));
            }
        }
        finally
        {
            RestoreComponentVisibility(originalVisibility);

            try
            {
                doc.ClearSelection2(true);
                doc.GraphicsRedraw2();
            }
            catch
            {
            }
        }

        return results;
    }

    private static ModelImageViewExport SaveComponentContextView(
        ModelDoc2 doc,
        dynamic mathUtility,
        Component2 target,
        ComponentContextImageRequest request,
        string requestedView,
        string outputDir,
        Action<string, string> log)
    {
        var view = TryNormalizeViewSpec(requestedView);
        var viewName = view?.Name ?? SanitizeFileName(requestedView);
        var result = new ModelImageViewExport
        {
            ImageKind = "assembly_component_context_isolate_view",
            EvidenceFamily = "assembly_component_position",
            EvidencePurpose = "component_position_and_whole_component_function",
            PositionVisibility = "occluded_context",
            RequestedView = requestedView,
            ViewName = $"context_{request.TargetComponentId}_{viewName}",
            SolidWorksNamedView = view?.NamedView ?? "custom_oblique",
            SolidWorksViewId = view?.ViewId ?? 0,
            HighlightPlanId = request.PlanId,
            HighlightComponentId = request.TargetComponentId,
            HighlightComponentInstanceName = request.TargetInstanceName,
            HighlightComponentDisplayName = request.TargetDisplayName,
            HighlightViewDirectionMm = request.PreferredDirectionMm.ToList(),
            ContextShownComponentCount = doc is AssemblyDoc assembly
                ? CountVisibleAssemblyComponents(assembly)
                : request.ShowComponents.Count,
            StartedAt = DateTimeOffset.Now
        };

        try
        {
            var effectiveDirection = ResolveComponentContextViewDirection(doc, target, request, log);
            result.HighlightViewDirectionMm = effectiveDirection.ToList();

            var customViewApplied = ApplyPreferredObliqueView(doc, mathUtility, effectiveDirection, log, request.TargetComponentId);
            if (!customViewApplied)
            {
                var fallback = view ?? NormalizeViewSpec("isometric");
                doc.ShowNamedView2(fallback.NamedView, fallback.ViewId);
            }
            doc.ViewZoomtofit2();
            doc.GraphicsRedraw2();

            doc.ClearSelection2(true);
            var selectionManager = doc.SelectionManager as SelectionMgr
                ?? throw new InvalidOperationException("SelectionManager is null.");
            var selectData = selectionManager.CreateSelectData() as SelectData
                ?? throw new InvalidOperationException("CreateSelectData returned null.");

            var selected = target.Select4(false, selectData, false);
            if (!selected)
                throw new InvalidOperationException($"Select4 returned false for target component `{request.TargetInstanceName}`.");
            LogComponentSelectionDiagnostics(doc, target, request.TargetInstanceName, request.TargetComponentPath, log, $"context:{request.TargetComponentId}:after_select4");

            FlushGraphics(doc, log, $"context:{request.TargetComponentId}:before_final_highlight", HighlightSettleDelayMs);
            var nativeHighlightDrawn = DrawNativeSelectionHighlight(doc, log, $"context:{request.TargetComponentId}");
            LogComponentSelectionDiagnostics(doc, target, request.TargetInstanceName, request.TargetComponentPath, log, $"context:{request.TargetComponentId}:before_save");

            var contextDir = Path.Combine(outputDir, "component_contexts", SanitizeFileName(request.TargetComponentId));
            Directory.CreateDirectory(contextDir);
            string outputPath = Path.Combine(contextDir, $"{SanitizeFileName(viewName)}.jpg");
            int errors = 0;
            int warnings = 0;

            log("image_save_component_context_jpg", outputPath);
            bool saved = doc.SaveAs4(
                outputPath,
                (int)swSaveAsVersion_e.swSaveAsCurrentVersion,
                (int)swSaveAsOptions_e.swSaveAsOptions_Silent,
                ref errors,
                ref warnings);
            FlushGraphics(doc, log, $"context:{request.TargetComponentId}:after_save_settle", HighlightSettleDelayMs);

            result.Ok = saved && File.Exists(outputPath);
            result.OutputPath = outputPath;
            result.SaveErrors = errors;
            result.SaveWarnings = warnings;
            result.Message = result.Ok
                ? $"saved after selectively hiding detected blockers while preserving connected context using SolidWorks native selection highlight; custom_oblique_view_applied={customViewApplied}; native_highlight_drawn={nativeHighlightDrawn}; effective_direction=[{FormatVector(effectiveDirection)}]; model not saved"
                : $"SaveAs4 returned {saved}, file_exists={File.Exists(outputPath)}";
        }
        catch (Exception ex)
        {
            result.Ok = false;
            result.Message = ex.Message;
            result.Error = ex.ToString();
        }
        finally
        {
            ClearNativeSelectionHighlight(doc, log, $"after_context:{request.TargetComponentId}");
            result.CompletedAt = DateTimeOffset.Now;
        }

        return result;
    }

    private static double[] ResolveComponentContextViewDirection(
        ModelDoc2 doc,
        Component2 target,
        ComponentContextImageRequest request,
        Action<string, string> log)
    {
        var fallback = request.PreferredDirectionMm.Length >= 3
            ? NormalizeVector([request.PreferredDirectionMm[0], request.PreferredDirectionMm[1], request.PreferredDirectionMm[2]])
            : NormalizeVector([1.0, 1.0, 1.0]);

        var targetRef = request.ShowComponents.FirstOrDefault(component =>
                component.ComponentId.Equals(request.TargetComponentId, StringComparison.OrdinalIgnoreCase)) ??
            request.ShowComponents.FirstOrDefault(component =>
                component.InstanceName.Equals(request.TargetInstanceName, StringComparison.OrdinalIgnoreCase));

        if (targetRef == null || targetRef.BBoxMm.Length < 6)
            return fallback;

        var targetBox = NormalizeBox(targetRef.BBoxMm);
        if (targetBox.Length < 6)
            return fallback;

        var visibleBlockers = request.ShowComponents
            .Concat(request.PreserveComponents)
            .Where(component => !component.ComponentId.Equals(targetRef.ComponentId, StringComparison.OrdinalIgnoreCase))
            .Where(component => component.BBoxMm.Length >= 6)
            .ToList();

        var candidates = BuildContextObliqueCandidateDirections();
        var bboxScores = candidates
            .Select(direction => ScoreContextDirectionByBBox(targetBox, visibleBlockers, direction))
            .OrderByDescending(score => score.Score)
            .ToList();

        if (bboxScores.Count == 0)
            return fallback;

        var best = bboxScores[0];
        var worst = bboxScores[^1];
        var spread = best.Score - worst.Score;
        var scale = Math.Max(1.0, Math.Abs(best.Score));
        var ambiguous = spread <= scale * 0.08 || bboxScores.All(score => score.OcclusionRatio >= 0.65);

        if (!ambiguous)
        {
            log("image_component_context_bbox_view_selected",
                $"{request.TargetComponentId}; direction=[{FormatVector(best.Direction)}]; score={Math.Round(best.Score, 3)}; occlusion={Math.Round(best.OcclusionRatio, 3)}");
            return best.Direction;
        }

        var rayDirection = TryChooseComponentContextDirectionByRay(
            doc,
            target,
            request,
            targetBox,
            bboxScores,
            log);

        if (rayDirection.Length >= 3)
            return rayDirection;

        log("image_component_context_bbox_ambiguous_ray_fallback",
            $"{request.TargetComponentId}; using_bbox_best=[{FormatVector(best.Direction)}]; spread={Math.Round(spread, 3)}");
        return best.Direction;
    }

    private static double[] TryChooseComponentContextDirectionByRay(
        ModelDoc2 doc,
        Component2 target,
        ComponentContextImageRequest request,
        double[] targetBoxMm,
        IReadOnlyList<ContextBboxDirectionScore> bboxScores,
        Action<string, string> log)
    {
        if (doc.Extension == null || doc.SelectionManager is not SelectionMgr selectionManager)
            return [];

        var sceneBox = NormalizeBox(request.ShowComponents
            .Concat(request.PreserveComponents)
            .Where(component => component.BBoxMm.Length >= 6)
            .Select(component => component.BBoxMm)
            .Aggregate(targetBoxMm, UnionTwoBoxes));
        var sceneSpanMm = BoxDiagonalMm(sceneBox.Length >= 6 ? sceneBox : targetBoxMm);
        var originOffsetMm = Math.Max(1000.0, sceneSpanMm * 4.0);
        var rayRadiusM = Math.Max(0.0002, Math.Min(0.003, sceneSpanMm / 1000.0 * 0.002));

        var scored = new List<(double[] Direction, int Hits, int Samples, double BboxScore)>();
        foreach (var candidate in bboxScores.Select(score => score.Direction))
        {
            var samples = BuildRaySamplePointsOnTargetBox(targetBoxMm, candidate);
            var hits = 0;
            foreach (var sampleMm in samples)
            {
                if (RayFirstHitMatchesTarget(doc, selectionManager, target, request, sampleMm, candidate, originOffsetMm, rayRadiusM))
                    hits++;
            }

            var bboxScore = bboxScores.First(score => SameDirection(score.Direction, candidate)).Score;
            scored.Add((candidate, hits, samples.Count, bboxScore));
        }

        try { doc.ClearSelection2(true); } catch { }

        if (scored.Count == 0)
            return [];

        var best = scored
            .OrderByDescending(item => item.Hits)
            .ThenByDescending(item => item.BboxScore)
            .First();

        if (best.Hits <= 0)
        {
            log("image_component_context_ray_view_no_target_hits",
                $"{request.TargetComponentId}; candidates={scored.Count}; samples_per_candidate={best.Samples}");
            return [];
        }

        log("image_component_context_ray_view_selected",
            $"{request.TargetComponentId}; direction=[{FormatVector(best.Direction)}]; target_hits={best.Hits}/{best.Samples}; bbox_score={Math.Round(best.BboxScore, 3)}");
        return best.Direction;
    }

    private static bool RayFirstHitMatchesTarget(
        ModelDoc2 doc,
        SelectionMgr selectionManager,
        Component2 target,
        ComponentContextImageRequest request,
        double[] sampleMm,
        double[] cameraDirection,
        double originOffsetMm,
        double rayRadiusM)
    {
        try
        {
            var d = NormalizeVector(cameraDirection);
            var originMm = Add(sampleMm, Mul(d, originOffsetMm));
            doc.ClearSelection2(true);

            var selected = doc.Extension.SelectByRay(
                originMm[0] / 1000.0,
                originMm[1] / 1000.0,
                originMm[2] / 1000.0,
                -d[0],
                -d[1],
                -d[2],
                rayRadiusM,
                (int)swSelectType_e.swSelFACES,
                false,
                0,
                0);

            if (!selected || selectionManager.GetSelectedObjectCount2(-1) <= 0)
                return false;

            var selectedComponentObj = selectionManager.GetSelectedObjectsComponent4(1, -1);
            if (selectedComponentObj is Component2 selectedComponent)
                return SameOrChildComponentRef(selectedComponent, target, request.TargetInstanceName, request.TargetComponentPath);

            return false;
        }
        catch
        {
            return false;
        }
        finally
        {
            try { doc.ClearSelection2(true); } catch { }
        }
    }

    private static List<double[]> BuildRaySamplePointsOnTargetBox(double[] box, double[] viewDirection)
    {
        var d = NormalizeVector(viewDirection);
        var up = Math.Abs(Dot(d, [0.0, 1.0, 0.0])) > 0.92 ? new[] { 0.0, 0.0, 1.0 } : new[] { 0.0, 1.0, 0.0 };
        var u = NormalizeVector(Cross(up, d));
        var v = NormalizeVector(Cross(d, u));
        var center = BoxCenter(box);
        var projected = ProjectBoxForContext(box, d);
        var centerDepth = Dot(center, d);
        var uMid = (projected.MinU + projected.MaxU) / 2.0;
        var vMid = (projected.MinV + projected.MaxV) / 2.0;
        var halfU = (projected.MaxU - projected.MinU) * 0.3;
        var halfV = (projected.MaxV - projected.MinV) * 0.3;

        double[] Point(double pu, double pv) => Add(Add(Mul(u, pu), Mul(v, pv)), Mul(d, centerDepth));

        return
        [
            Point(uMid, vMid),
            Point(uMid - halfU, vMid - halfV),
            Point(uMid - halfU, vMid + halfV),
            Point(uMid + halfU, vMid - halfV),
            Point(uMid + halfU, vMid + halfV)
        ];
    }

    private static ContextBboxDirectionScore ScoreContextDirectionByBBox(
        double[] targetBox,
        IReadOnlyList<ComponentImageRef> blockers,
        double[] direction)
    {
        var targetProjection = ProjectBoxForContext(targetBox, direction);
        var targetArea = Math.Max(1e-9, targetProjection.Area);
        var occlusionRatio = 0.0;

        foreach (var blocker in blockers)
        {
            var blockerBox = NormalizeBox(blocker.BBoxMm);
            if (blockerBox.Length < 6)
                continue;

            var blockerProjection = ProjectBoxForContext(blockerBox, direction);
            var blockerCenterDepth = (blockerProjection.MinDepth + blockerProjection.MaxDepth) / 2.0;
            var targetCenterDepth = (targetProjection.MinDepth + targetProjection.MaxDepth) / 2.0;
            if (blockerCenterDepth >= targetCenterDepth)
                continue;

            var overlapRatio = OverlapAreaForContext(targetProjection, blockerProjection) / targetArea;
            occlusionRatio += Math.Min(1.0, overlapRatio);
        }

        occlusionRatio = Math.Min(1.0, occlusionRatio);
        var score = targetArea * (1.0 - occlusionRatio * 1.8);
        return new ContextBboxDirectionScore(direction, score, occlusionRatio);
    }

    private static List<double[]> BuildContextObliqueCandidateDirections()
    {
        var signs = new[] { -1.0, 1.0 };
        var result = new List<double[]>();
        foreach (var sx in signs)
        foreach (var sy in signs)
        foreach (var sz in signs)
            result.Add(NormalizeVector([sx, sy, sz]));
        return result;
    }

    private static ContextProjectedBox ProjectBoxForContext(double[] box, double[] viewDirection)
    {
        var d = NormalizeVector(viewDirection);
        var up = Math.Abs(Dot(d, [0.0, 1.0, 0.0])) > 0.92 ? new[] { 0.0, 0.0, 1.0 } : new[] { 0.0, 1.0, 0.0 };
        var u = NormalizeVector(Cross(up, d));
        var v = NormalizeVector(Cross(d, u));

        var corners = BoxCornersForContext(box);
        var uValues = corners.Select(p => Dot(p, u)).ToList();
        var vValues = corners.Select(p => Dot(p, v)).ToList();
        var dValues = corners.Select(p => Dot(p, d)).ToList();

        return new ContextProjectedBox(
            uValues.Min(),
            uValues.Max(),
            vValues.Min(),
            vValues.Max(),
            dValues.Min(),
            dValues.Max());
    }

    private static List<double[]> BoxCornersForContext(double[] box) =>
    [
        [box[0], box[1], box[2]],
        [box[0], box[1], box[5]],
        [box[0], box[4], box[2]],
        [box[0], box[4], box[5]],
        [box[3], box[1], box[2]],
        [box[3], box[1], box[5]],
        [box[3], box[4], box[2]],
        [box[3], box[4], box[5]]
    ];

    private static double OverlapAreaForContext(ContextProjectedBox a, ContextProjectedBox b)
    {
        var width = Math.Max(0.0, Math.Min(a.MaxU, b.MaxU) - Math.Max(a.MinU, b.MinU));
        var height = Math.Max(0.0, Math.Min(a.MaxV, b.MaxV) - Math.Max(a.MinV, b.MinV));
        return width * height;
    }

    private static double[] NormalizeBox(double[] box)
    {
        if (box.Length < 6)
            return [];

        return
        [
            Math.Min(box[0], box[3]),
            Math.Min(box[1], box[4]),
            Math.Min(box[2], box[5]),
            Math.Max(box[0], box[3]),
            Math.Max(box[1], box[4]),
            Math.Max(box[2], box[5])
        ];
    }

    private static double[] UnionTwoBoxes(double[] a, double[] b)
    {
        var boxA = NormalizeBox(a);
        var boxB = NormalizeBox(b);
        if (boxA.Length < 6)
            return boxB;
        if (boxB.Length < 6)
            return boxA;

        return
        [
            Math.Min(boxA[0], boxB[0]),
            Math.Min(boxA[1], boxB[1]),
            Math.Min(boxA[2], boxB[2]),
            Math.Max(boxA[3], boxB[3]),
            Math.Max(boxA[4], boxB[4]),
            Math.Max(boxA[5], boxB[5])
        ];
    }

    private static double[] BoxCenter(double[] box) =>
        box.Length >= 6
            ? [(box[0] + box[3]) / 2.0, (box[1] + box[4]) / 2.0, (box[2] + box[5]) / 2.0]
            : [];

    private static double BoxDiagonalMm(double[] box)
    {
        if (box.Length < 6)
            return 1000.0;

        var dx = box[3] - box[0];
        var dy = box[4] - box[1];
        var dz = box[5] - box[2];
        return Math.Sqrt(dx * dx + dy * dy + dz * dz);
    }

    private static bool SameDirection(double[] a, double[] b) =>
        a.Length >= 3 && b.Length >= 3 &&
        Math.Abs(a[0] - b[0]) < 1e-6 &&
        Math.Abs(a[1] - b[1]) < 1e-6 &&
        Math.Abs(a[2] - b[2]) < 1e-6;

    private static bool SameOrChildComponentRef(
        Component2 selectedComponent,
        Component2 target,
        string targetInstanceName,
        string targetComponentPath)
    {
        if (ReferenceEquals(selectedComponent, target))
            return true;

        string selectedName = "";
        string selectedPath = "";
        string targetName = "";
        string targetPath = "";
        try { selectedName = selectedComponent.Name2 ?? ""; } catch { }
        try { selectedPath = selectedComponent.GetPathName() ?? ""; } catch { }
        try { targetName = target.Name2 ?? ""; } catch { }
        try { targetPath = target.GetPathName() ?? ""; } catch { }

        if (SameComponentRef(selectedComponent, targetInstanceName, targetComponentPath))
            return true;

        if (!string.IsNullOrWhiteSpace(targetName) &&
            (selectedName.Equals(targetName, StringComparison.OrdinalIgnoreCase) ||
             selectedName.StartsWith(targetName + "/", StringComparison.OrdinalIgnoreCase)))
        {
            return true;
        }

        return !string.IsNullOrWhiteSpace(targetPath) &&
               !string.IsNullOrWhiteSpace(selectedPath) &&
               selectedPath.Equals(targetPath, StringComparison.OrdinalIgnoreCase);
    }

    private static ModelImageViewExport BuildFailedContextResult(
        ComponentContextImageRequest request,
        string requestedView,
        DateTimeOffset startedAt,
        string message,
        string error = "")
    {
        return new ModelImageViewExport
        {
            ImageKind = "assembly_component_context_isolate_view",
            EvidenceFamily = "assembly_component_position",
            EvidencePurpose = "component_position_and_whole_component_function",
            PositionVisibility = "occluded_context",
            RequestedView = requestedView,
            ViewName = $"context_{request.TargetComponentId}_{requestedView}",
            HighlightPlanId = request.PlanId,
            HighlightComponentId = request.TargetComponentId,
            HighlightComponentInstanceName = request.TargetInstanceName,
            HighlightComponentDisplayName = request.TargetDisplayName,
            ContextShownComponentCount = request.ShowComponents.Count,
            StartedAt = startedAt,
            CompletedAt = DateTimeOffset.Now,
            Ok = false,
            Message = message,
            Error = error
        };
    }

    private static Component2? FindComponent(ModelDoc2 doc, string instanceName, string componentPath)
    {
        if (doc is not AssemblyDoc assembly)
            return null;

        var components = assembly.GetComponents(false) as object[];
        if (components == null)
            return null;

        Component2? fallback = null;
        foreach (var item in components)
        {
            if (item is not Component2 component)
                continue;

            string name = "";
            string path = "";
            try { name = component.Name2 ?? ""; } catch { }
            try { path = component.GetPathName() ?? ""; } catch { }

            if (!string.IsNullOrWhiteSpace(instanceName) &&
                name.Equals(instanceName, StringComparison.OrdinalIgnoreCase) &&
                (string.IsNullOrWhiteSpace(componentPath) ||
                 string.IsNullOrWhiteSpace(path) ||
                 path.Equals(componentPath, StringComparison.OrdinalIgnoreCase)))
            {
                return component;
            }

            if (fallback == null &&
                !string.IsNullOrWhiteSpace(componentPath) &&
                !string.IsNullOrWhiteSpace(path) &&
                path.Equals(componentPath, StringComparison.OrdinalIgnoreCase))
            {
                fallback = component;
            }
        }

        return fallback;
    }

    private static void LogComponentSelectionDiagnostics(
        ModelDoc2 doc,
        Component2 expectedComponent,
        string expectedInstanceName,
        string expectedComponentPath,
        Action<string, string> log,
        string scope)
    {
        try
        {
            if (doc.SelectionManager is not SelectionMgr selectionManager)
            {
                log("image_component_selection_diagnostics", $"{scope}; selection_manager=null");
                return;
            }

            var count = selectionManager.GetSelectedObjectCount2(-1);
            var parts = new List<string> { $"{scope}; selected_count={count}" };
            for (var index = 1; index <= Math.Min(count, 5); index++)
            {
                var type = 0;
                try { type = selectionManager.GetSelectedObjectType3(index, -1); } catch { }

                Component2? selectedComponent = null;
                try { selectedComponent = selectionManager.GetSelectedObjectsComponent4(index, -1) as Component2; } catch { }
                if (selectedComponent == null)
                {
                    try
                    {
                        var selectedObject = selectionManager.GetSelectedObject6(index, -1);
                        if (selectedObject is Component2 component)
                            selectedComponent = component;
                        else if (selectedObject is Entity entity)
                            selectedComponent = entity.GetComponent() as Component2;
                    }
                    catch
                    {
                    }
                }

                var selectedName = "";
                var selectedPath = "";
                try { selectedName = selectedComponent?.Name2 ?? ""; } catch { }
                try { selectedPath = selectedComponent?.GetPathName() ?? ""; } catch { }

                var same = selectedComponent != null &&
                           SameComponentRef(selectedComponent, expectedInstanceName, expectedComponentPath);
                var sameObject = false;
                try { sameObject = selectedComponent != null && ReferenceEquals(selectedComponent, expectedComponent); } catch { }

                parts.Add($"item{index}=type:{SelectionTypeName(type)}, component:{selectedName}, same_ref:{same}, same_object:{sameObject}, path:{selectedPath}");
            }

            log("image_component_selection_diagnostics", string.Join("; ", parts));
        }
        catch (Exception ex)
        {
            log("image_component_selection_diagnostics_failed", $"{scope}; {ex.Message}");
        }
    }

    private static string SelectionTypeName(int selectionType)
    {
        try
        {
            return $"{Enum.GetName(typeof(swSelectType_e), selectionType) ?? selectionType.ToString()}({selectionType})";
        }
        catch
        {
            return selectionType.ToString();
        }
    }

    private static bool SameComponentRef(Component2 component, string instanceName, string componentPath)
    {
        string name = "";
        string path = "";
        try { name = component.Name2 ?? ""; } catch { }
        try { path = component.GetPathName() ?? ""; } catch { }

        if (!string.IsNullOrWhiteSpace(instanceName))
        {
            return name.Equals(instanceName, StringComparison.OrdinalIgnoreCase) &&
                   (string.IsNullOrWhiteSpace(componentPath) ||
                    string.IsNullOrWhiteSpace(path) ||
                    path.Equals(componentPath, StringComparison.OrdinalIgnoreCase));
        }

        return !string.IsNullOrWhiteSpace(componentPath) &&
               !string.IsNullOrWhiteSpace(path) &&
               path.Equals(componentPath, StringComparison.OrdinalIgnoreCase);
    }

    private static bool SameOrChildComponentNameRef(Component2 component, string instanceName, string componentPath)
    {
        if (SameComponentRef(component, instanceName, componentPath))
            return true;

        if (string.IsNullOrWhiteSpace(instanceName))
            return false;

        string name = "";
        try { name = component.Name2 ?? ""; } catch { }
        return name.StartsWith(instanceName + "/", StringComparison.OrdinalIgnoreCase);
    }

    private static bool SameComponentHierarchyRef(Component2 component, string instanceName, string componentPath)
    {
        if (SameOrChildComponentNameRef(component, instanceName, componentPath))
            return true;

        if (string.IsNullOrWhiteSpace(instanceName))
            return false;

        string name = "";
        try { name = component.Name2 ?? ""; } catch { }
        return !string.IsNullOrWhiteSpace(name) &&
               instanceName.StartsWith(name + "/", StringComparison.OrdinalIgnoreCase);
    }

    private static object? FindReferencePlane(ModelDoc2 doc, string planeKey, out string planeName, out string source)
    {
        planeName = "";
        source = "";

        if (!RefPlaneNameCandidates.TryGetValue(planeKey, out var candidates))
            throw new ArgumentException($"Unsupported section view `{planeKey}`. Supported: {string.Join(", ", RefPlaneNameCandidates.Keys)}");

        var allRefPlanes = new List<(Feature Feature, string Name)>();
        Feature? feature = null;
        try { feature = doc.FirstFeature() as Feature; } catch { feature = null; }

        while (feature != null)
        {
            string typeName = "";
            string name = "";
            try { typeName = feature.GetTypeName2(); } catch { }
            try { name = feature.Name; } catch { }

            if (typeName.Equals("RefPlane", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(name))
                allRefPlanes.Add((feature, name));

            try { feature = feature.GetNextFeature() as Feature; }
            catch { break; }
        }

        foreach (var candidate in candidates)
        {
            var match = allRefPlanes.FirstOrDefault(p => p.Name.Equals(candidate, StringComparison.OrdinalIgnoreCase));
            if (match.Feature != null)
                return BuildPlaneReturn(match.Feature, match.Name, "matched_by_name", out planeName, out source);
        }

        int fallbackIndex = planeKey.ToLowerInvariant() switch
        {
            "front" => 0,
            "top" => 1,
            "right" => 2,
            _ => -1
        };

        if (fallbackIndex >= 0 && allRefPlanes.Count > fallbackIndex)
        {
            var fallback = allRefPlanes[fallbackIndex];
            return BuildPlaneReturn(fallback.Feature, fallback.Name, "matched_by_refplane_order", out planeName, out source);
        }

        source = $"available_ref_planes={string.Join(", ", allRefPlanes.Select(p => p.Name))}";
        return null;
    }

    private static object BuildPlaneReturn(Feature feature, string name, string source, out string planeName, out string planeSource)
    {
        planeName = name;
        planeSource = source;
        try
        {
            var specific = feature.GetSpecificFeature2();
            if (specific != null)
                return specific;
        }
        catch
        {
        }

        return feature;
    }

    private static bool DrawNativeSelectionHighlight(ModelDoc2 doc, Action<string, string> log, string scope)
    {
        try
        {
            var activeView = doc.IActiveView as ModelView ?? doc.ActiveView as ModelView;
            if (activeView == null)
            {
                log("image_native_selection_highlight_unavailable", $"{scope}; active view is null");
                doc.GraphicsRedraw2();
                return false;
            }

            doc.GraphicsRedraw2();
            activeView.GraphicsRedraw(null);
            activeView.DrawHighlightedItems();
            log("image_native_selection_highlight_drawn", scope);
            return true;
        }
        catch (Exception ex)
        {
            log("image_native_selection_highlight_failed", $"{scope}; {ex.Message}");
            try { doc.GraphicsRedraw2(); } catch { }
            return false;
        }
    }

    private static void FlushGraphics(ModelDoc2 doc, Action<string, string> log, string scope, int delayMs)
    {
        try
        {
            doc.GraphicsRedraw2();
            var activeView = doc.IActiveView as ModelView ?? doc.ActiveView as ModelView;
            activeView?.GraphicsRedraw(null);
            if (delayMs > 0)
                System.Threading.Thread.Sleep(delayMs);
            doc.GraphicsRedraw2();
            log("image_graphics_settled", $"{scope}; delay_ms={delayMs}");
        }
        catch (Exception ex)
        {
            log("image_graphics_settle_failed", $"{scope}; {ex.Message}");
            try
            {
                if (delayMs > 0)
                    System.Threading.Thread.Sleep(delayMs);
            }
            catch
            {
            }
        }
    }

    private static void ClearNativeSelectionHighlight(ModelDoc2 doc, Action<string, string> log, string scope)
    {
        try
        {
            doc.ClearSelection2(true);
            doc.GraphicsRedraw2();
            var activeView = doc.IActiveView as ModelView ?? doc.ActiveView as ModelView;
            activeView?.DrawHighlightedItems();
            doc.GraphicsRedraw2();
            log("image_native_selection_highlight_cleared", scope);
        }
        catch (Exception ex)
        {
            log("image_native_selection_highlight_clear_failed", $"{scope}; {ex.Message}");
            try { doc.ClearSelection2(true); } catch { }
            try { doc.GraphicsRedraw2(); } catch { }
        }
    }

    private static TemporaryUserPreferenceToggle EnableTemporaryUserPreferenceToggle(
        SldWorks sw,
        int preference,
        string name,
        Action<string, string> log)
    {
        try
        {
            var original = sw.GetUserPreferenceToggle(preference);
            if (!original)
                sw.SetUserPreferenceToggle(preference, true);
            log("image_user_preference_toggle_enabled", $"{name}; original={original}; current=true");
            return new TemporaryUserPreferenceToggle(name, preference, original, true);
        }
        catch (Exception ex)
        {
            log("image_user_preference_toggle_enable_failed", $"{name}; {ex.Message}");
            return new TemporaryUserPreferenceToggle(name, preference, false, false);
        }
    }

    private static void RestoreTemporaryUserPreferenceToggle(
        SldWorks sw,
        TemporaryUserPreferenceToggle preference,
        Action<string, string> log)
    {
        if (!preference.Valid)
            return;

        try
        {
            sw.SetUserPreferenceToggle(preference.Preference, preference.OriginalValue);
            log("image_user_preference_toggle_restored", $"{preference.Name}; restored={preference.OriginalValue}");
        }
        catch (Exception ex)
        {
            log("image_user_preference_toggle_restore_failed", $"{preference.Name}; {ex.Message}");
        }
    }

    private static bool ApplyPreferredObliqueView(
        ModelDoc2 doc,
        dynamic mathUtility,
        IReadOnlyList<double> preferredDirection,
        Action<string, string> log,
        string featureId)
    {
        if (preferredDirection.Count < 3)
            return false;

        try
        {
            var activeView = doc.IActiveView as ModelView ?? doc.ActiveView as ModelView;
            if (activeView == null)
            {
                log("image_custom_oblique_view_unavailable", $"{featureId}; active view is null");
                return false;
            }

            var cameraDirection = NormalizeVector([preferredDirection[0], preferredDirection[1], preferredDirection[2]]);
            if (VectorNorm(cameraDirection) < 1e-9)
                return false;

            var d = cameraDirection;
            var up = Math.Abs(Dot(d, [0.0, 1.0, 0.0])) > 0.92
                ? new[] { 0.0, 0.0, 1.0 }
                : new[] { 0.0, 1.0, 0.0 };
            var u = NormalizeVector(Cross(up, d));
            var v = NormalizeVector(Cross(d, u));

            // SolidWorks MathTransform uses a 3x3 rotation, translation, scale, and unused slots.
            // The third basis vector is the model-space view direction used by Orientation3.
            double[] transformData =
            [
                u[0], u[1], u[2],
                v[0], v[1], v[2],
                d[0], d[1], d[2],
                0.0, 0.0, 0.0,
                1.0,
                0.0, 0.0, 0.0
            ];

            var transform = mathUtility.CreateTransform(transformData);
            activeView.Orientation3 = transform;
            doc.GraphicsRedraw2();
            log("image_custom_oblique_view_applied", $"{featureId}; camera_direction=[{FormatVector(cameraDirection)}]; solidworks_view_direction=[{FormatVector(d)}]");
            return true;
        }
        catch (Exception ex)
        {
            log("image_custom_oblique_view_failed", $"{featureId}; {ex.Message}");
            return false;
        }
    }

    private static void ClearSectionViewBeforeStandardViews(
        ModelDoc2 doc,
        IReadOnlyCollection<string> requestedViews,
        Action<string, string> log)
    {
        if (requestedViews.Count == 0)
            return;

        try
        {
            var manager = doc.ModelViewManager;
            if (manager == null)
            {
                log("image_clear_section_before_standard_views_skipped", "ModelViewManager is null");
                return;
            }

            log("image_clear_section_before_standard_views", $"standard_views={requestedViews.Count}");
            SafeRemoveSectionView(manager);
            doc.GraphicsRedraw2();
        }
        catch (Exception ex)
        {
            log("image_clear_section_before_standard_views_failed", ex.Message);
        }
    }

    private static void SafeRemoveSectionView(ModelViewManager manager)
    {
        try { manager.RemoveSectionView(); } catch { }
    }

    private static T Safe<T>(Func<T> action, T fallback)
    {
        try { return action(); }
        catch { return fallback; }
    }

    private static object? InvokeCom(object obj, string name, params object?[] args)
    {
        try
        {
            return obj.GetType().InvokeMember(
                name,
                System.Reflection.BindingFlags.InvokeMethod,
                null,
                obj,
                args);
        }
        catch
        {
            return null;
        }
    }

    private static object[] ToObjectArray(object? value) => value as object[] ?? [];

    private static ModelImageViewSpec NormalizeViewSpec(string requestedView)
    {
        var key = (requestedView ?? "").Trim();
        if (ViewSpecs.TryGetValue(key, out var view))
            return view;

        throw new ArgumentException($"Unsupported image view `{requestedView}`. Supported: {string.Join(", ", ViewSpecs.Keys)}");
    }

    private static ModelImageViewSpec? TryNormalizeViewSpec(string requestedView)
    {
        var key = (requestedView ?? "").Trim();
        return ViewSpecs.TryGetValue(key, out var view) ? view : null;
    }

    private static double[] NormalizeVector(double[] v)
    {
        var norm = VectorNorm(v);
        return norm < 1e-9 ? [0.0, 0.0, 0.0] : v.Select(x => x / norm).ToArray();
    }

    private static string FormatVector(IReadOnlyList<double> values) =>
        string.Join(",", values.Take(3).Select(v => Math.Round(v, 3).ToString("0.###")));

    private static double VectorNorm(double[] v) => Math.Sqrt(v.Sum(x => x * x));

    private static double Dot(double[] a, double[] b) => a.Zip(b, (x, y) => x * y).Sum();

    private static double[] Add(double[] a, double[] b) => a.Zip(b, (x, y) => x + y).ToArray();

    private static double[] Mul(double[] a, double s) => a.Select(x => x * s).ToArray();

    private static double[] Cross(double[] a, double[] b) =>
    [
        a[1] * b[2] - a[2] * b[1],
        a[2] * b[0] - a[0] * b[2],
        a[0] * b[1] - a[1] * b[0]
    ];

    private static string SanitizeFileName(string value)
    {
        var invalid = Path.GetInvalidFileNameChars();
        return string.Concat(value.Select(ch => invalid.Contains(ch) ? '_' : ch));
    }

    private static SectionViewRequestSpec ParseSectionViewRequest(string requestedSectionView, double globalOffsetMm)
    {
        var raw = (requestedSectionView ?? "").Trim();
        if (string.IsNullOrWhiteSpace(raw))
            throw new ArgumentException("Section view request is empty.");

        string metadata = "";
        int metadataIndex = raw.IndexOf('|');
        if (metadataIndex >= 0)
        {
            metadata = raw[(metadataIndex + 1)..];
            raw = raw[..metadataIndex];
        }

        string label = "";
        int labelIndex = raw.IndexOf(':');
        if (labelIndex >= 0)
        {
            label = raw[(labelIndex + 1)..].Trim();
            raw = raw[..labelIndex];
        }

        double offsetMm = globalOffsetMm;
        string planeKey = raw;
        int offsetIndex = raw.IndexOf('@');
        if (offsetIndex >= 0)
        {
            planeKey = raw[..offsetIndex];
            var offsetText = raw[(offsetIndex + 1)..];
            if (!double.TryParse(offsetText, out offsetMm))
                throw new ArgumentException($"Invalid section offset in `{requestedSectionView}`.");
        }

        var target = "";
        var reason = "";
        foreach (var item in metadata.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
        {
            var parts = item.Split('=', 2, StringSplitOptions.TrimEntries);
            if (parts.Length != 2)
                continue;
            if (parts[0].Equals("target", StringComparison.OrdinalIgnoreCase))
                target = parts[1];
            else if (parts[0].Equals("reason", StringComparison.OrdinalIgnoreCase))
                reason = parts[1];
        }

        if (string.IsNullOrWhiteSpace(label))
            label = $"{planeKey}_{Math.Round(offsetMm, 3).ToString(System.Globalization.CultureInfo.InvariantCulture).Replace('-', 'm').Replace('.', 'p')}";

        return new SectionViewRequestSpec(planeKey.Trim(), offsetMm, label, target, reason);
    }
}

sealed record ModelImageViewSpec(string Name, string NamedView, int ViewId);
sealed record SectionViewRequestSpec(string PlaneKey, double OffsetMm, string Label, string Target, string Reason);
readonly record struct TemporaryUserPreferenceToggle(string Name, int Preference, bool OriginalValue, bool Valid);
readonly record struct ComponentVisibilityIsolationStats(int Shown, int Preserved, int Hidden);
sealed record ContextBboxDirectionScore(double[] Direction, double Score, double OcclusionRatio);
sealed record ContextProjectedBox(double MinU, double MaxU, double MinV, double MaxV, double MinDepth, double MaxDepth)
{
    public double Area => Math.Max(0.0, MaxU - MinU) * Math.Max(0.0, MaxV - MinV);
}

sealed class ComponentHighlightImageRequest
{
    public string PlanId { get; set; } = "";
    public string ComponentId { get; set; } = "";
    public string InstanceName { get; set; } = "";
    public string DisplayName { get; set; } = "";
    public string ComponentPath { get; set; } = "";
    public List<string> Views { get; set; } = [];
    public double[] PreferredDirectionMm { get; set; } = [];
    public List<ComponentImageRef> ShowComponents { get; set; } = [];
    public List<ComponentImageRef> PreserveComponents { get; set; } = [];
}

sealed class FeatureHighlightImageRequest
{
    public string PlanId { get; set; } = "";
    public string ComponentId { get; set; } = "";
    public string InstanceName { get; set; } = "";
    public string DisplayName { get; set; } = "";
    public string ComponentPath { get; set; } = "";
    public string FeatureId { get; set; } = "";
    public string FeatureType { get; set; } = "";
    public List<string> FaceRefs { get; set; } = [];
    public List<string> Views { get; set; } = [];
    public double[] PreferredDirectionMm { get; set; } = [];
    public List<string> BlockingFaceRefs { get; set; } = [];
    public string SelectionBasis { get; set; } = "";
    public string OutputRelativeDirectory { get; set; } = "";
    public string OutputFileStem { get; set; } = "";
    public string ImageKind { get; set; } = "";
    public string EvidenceFamily { get; set; } = "";
    public string EvidencePurpose { get; set; } = "";
}

sealed class InterfaceHighlightImageRequest
{
    public string PlanId { get; set; } = "";
    public string InterfaceId { get; set; } = "";
    public string ContactKind { get; set; } = "";
    public string MateRole { get; set; } = "";
    public ComponentImageRef ComponentA { get; set; } = new();
    public ComponentImageRef ComponentB { get; set; } = new();
    public string FaceARef { get; set; } = "";
    public string FaceBRef { get; set; } = "";
    public int FaceAIndex { get; set; }
    public int FaceBIndex { get; set; }
    public List<string> FaceARefs { get; set; } = [];
    public List<string> FaceBRefs { get; set; } = [];
    public List<int> FaceAIndices { get; set; } = [];
    public List<int> FaceBIndices { get; set; } = [];
    [System.Text.Json.Serialization.JsonIgnore]
    public List<Face2> RuntimeFaces { get; set; } = [];
    public List<string> SourceContactIds { get; set; } = [];
    public List<string> Views { get; set; } = [];
    public double[] PreferredDirectionMm { get; set; } = [];
    public double[] InterfaceAxisMm { get; set; } = [];
    public double[] InterfaceNormalMm { get; set; } = [];
    public double[] InterfaceCenterMm { get; set; } = [];
    public double[] InterfaceBBoxMm { get; set; } = [];
    public double AnalysisPriority { get; set; }
    public string ConfidenceTier { get; set; } = "";
    public string EvidencePurpose { get; set; } = "";
    public bool TrimmedPatchVerified { get; set; }
    public string VerificationStatus { get; set; } = "";
    public List<string> VerificationMethods { get; set; } = [];
    public int VerificationSampleHits { get; set; }
    public double VerificationMinDistanceMm { get; set; }
    public string DisplayMode { get; set; } = "";
    public bool ActualAssemblyPosition { get; set; } = true;
    public bool NotActualClearance { get; set; } = true;
    public List<ComponentImageRef> ShowComponents { get; set; } = [];
    public List<ComponentImageRef> PreserveComponents { get; set; } = [];
}

sealed class ComponentContextImageRequest
{
    public string PlanId { get; set; } = "";
    public string GroupId { get; set; } = "";
    public string TargetComponentId { get; set; } = "";
    public string TargetInstanceName { get; set; } = "";
    public string TargetDisplayName { get; set; } = "";
    public string TargetComponentPath { get; set; } = "";
    public List<string> Views { get; set; } = [];
    public double[] PreferredDirectionMm { get; set; } = [];
    public List<ComponentImageRef> ShowComponents { get; set; } = [];
    public List<ComponentImageRef> PreserveComponents { get; set; } = [];
    public List<ComponentImageRef> HideComponents { get; set; } = [];
}

sealed class ComponentImageRef
{
    public string ComponentId { get; set; } = "";
    public string InstanceName { get; set; } = "";
    public string DisplayName { get; set; } = "";
    public string ComponentPath { get; set; } = "";
    public double[] BBoxMm { get; set; } = [];
}

sealed class ModelImageExportReport
{
    public string Schema { get; set; } = "";
    public string ModelPath { get; set; } = "";
    public string OutputDir { get; set; } = "";
    public List<string> RequestedViews { get; set; } = [];
    public List<string> RequestedSectionViews { get; set; } = [];
    public double SectionOffsetMm { get; set; }
    public int OpenErrors { get; set; }
    public int OpenWarnings { get; set; }
    public DateTimeOffset StartedAt { get; set; }
    public DateTimeOffset? CompletedAt { get; set; }
    public string Message { get; set; } = "";
    public string Error { get; set; } = "";
    public List<ModelImageViewExport> Views { get; set; } = [];

    public int SuccessCount => Views.Count(v => v.Ok);
}

sealed class ModelImageViewExport
{
    public string ImageKind { get; set; } = "";
    public string EvidenceFamily { get; set; } = "";
    public string EvidencePurpose { get; set; } = "";
    public string PositionVisibility { get; set; } = "";
    public string RequestedView { get; set; } = "";
    public string ViewName { get; set; } = "";
    public string SolidWorksNamedView { get; set; } = "";
    public int SolidWorksViewId { get; set; }
    public string SectionPlaneKey { get; set; } = "";
    public string SectionPlaneName { get; set; } = "";
    public double SectionOffsetMm { get; set; }
    public string SectionTarget { get; set; } = "";
    public string SectionReason { get; set; } = "";
    public string HighlightPlanId { get; set; } = "";
    public string HighlightComponentId { get; set; } = "";
    public string HighlightComponentInstanceName { get; set; } = "";
    public string HighlightComponentDisplayName { get; set; } = "";
    public string HighlightFeatureId { get; set; } = "";
    public string HighlightFeatureType { get; set; } = "";
    public string HighlightInterfaceId { get; set; } = "";
    public string HighlightInterfaceContactKind { get; set; } = "";
    public string HighlightInterfaceMateRole { get; set; } = "";
    public string HighlightInterfaceComponentAId { get; set; } = "";
    public string HighlightInterfaceComponentAInstanceName { get; set; } = "";
    public string HighlightInterfaceComponentADisplayName { get; set; } = "";
    public string HighlightInterfaceComponentBId { get; set; } = "";
    public string HighlightInterfaceComponentBInstanceName { get; set; } = "";
    public string HighlightInterfaceComponentBDisplayName { get; set; } = "";
    public List<string> HighlightFaceRefs { get; set; } = [];
    public List<string> HighlightInterfaceSourceContactIds { get; set; } = [];
    public List<double> HighlightInterfaceAxisMm { get; set; } = [];
    public List<double> HighlightInterfaceCenterMm { get; set; } = [];
    public List<double> HighlightInterfaceBBoxMm { get; set; } = [];
    public double InterfaceAnalysisPriority { get; set; }
    public string InterfaceConfidenceTier { get; set; } = "";
    public string InterfaceEvidencePurpose { get; set; } = "";
    public bool InterfaceTrimmedPatchVerified { get; set; }
    public string InterfaceVerificationStatus { get; set; } = "";
    public List<string> InterfaceVerificationMethods { get; set; } = [];
    public int InterfaceVerificationSampleHits { get; set; }
    public double InterfaceVerificationMinDistanceMm { get; set; }
    public string InterfaceDisplayMode { get; set; } = "";
    public bool InterfaceActualAssemblyPosition { get; set; }
    public bool InterfaceNotActualClearance { get; set; }
    public List<double> HighlightViewDirectionMm { get; set; } = [];
    public List<string> HighlightBlockingFaceRefs { get; set; } = [];
    public int ContextShownComponentCount { get; set; }
    public bool Ok { get; set; }
    public string OutputPath { get; set; } = "";
    public int SaveErrors { get; set; }
    public int SaveWarnings { get; set; }
    public string Message { get; set; } = "";
    public string Error { get; set; } = "";
    public DateTimeOffset StartedAt { get; set; }
    public DateTimeOffset? CompletedAt { get; set; }
}
