using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;

Console.OutputEncoding = Encoding.UTF8;

if (args.Length == 0 || IsHelp(args[0]))
{
    PrintUsage();
    return 1;
}

try
{
    return args[0].Trim().ToLowerInvariant() switch
    {
        "generate" => Generate(args.Skip(1).ToArray()),
        "validate" => Validate(args.Skip(1).ToArray()),
        "sample-sleeve" => SampleSleeve(args.Skip(1).ToArray()),
        _ => Fail($"Unknown command: {args[0]}")
    };
}
catch (Exception ex)
{
    Console.Error.WriteLine(ex.Message);
    return 1;
}

static int Generate(string[] args)
{
    var parsed = Args.Parse(args);
    var imagePath = parsed.Required("--image");
    var outputPath = parsed.Required("--out");
    var textPath = parsed.Optional("--text");

    var extraction = TeachingMaterialExtractor.Extract(imagePath, textPath);
    WriteJson(outputPath, extraction);
    Console.WriteLine(Path.GetFullPath(outputPath));
    return 0;
}

static int SampleSleeve(string[] args)
{
    var outputPath = args.Length > 0
        ? args[0]
        : Path.Combine("runtime", "rule_templates", "tight_fit_sleeve_requires_disassembly_access.json");
    var extraction = TeachingMaterialExtractor.BuildSleeveRemovalTemplate("built_in_sample", null);
    WriteJson(outputPath, extraction);
    Console.WriteLine(Path.GetFullPath(outputPath));
    return 0;
}

static int Validate(string[] args)
{
    if (args.Length < 1)
        return Fail("validate requires a template path.");

    var template = JsonSerializer.Deserialize<RuleTemplatePackage>(File.ReadAllText(args[0], Encoding.UTF8), JsonOptions.Default)
        ?? throw new InvalidOperationException("Cannot parse rule template package.");
    var errors = TemplateValidator.Validate(template);
    if (errors.Count > 0)
    {
        foreach (var error in errors)
            Console.Error.WriteLine(error);
        return 2;
    }

    Console.WriteLine("valid");
    return 0;
}

static void WriteJson<T>(string path, T value)
{
    var fullPath = Path.GetFullPath(path);
    Directory.CreateDirectory(Path.GetDirectoryName(fullPath) ?? ".");
    File.WriteAllText(fullPath, JsonSerializer.Serialize(value, JsonOptions.Default), new UTF8Encoding(false));
}

static bool IsHelp(string arg) => arg is "-h" or "--help" or "/?" or "help";

static int Fail(string message)
{
    Console.Error.WriteLine(message);
    PrintUsage();
    return 1;
}

static void PrintUsage()
{
    Console.WriteLine("""
RuleTemplateGenerator

Purpose:
  Convert teaching image/text into an executable diagnostic rule package.
  The package contains both semantic rule templates and image-side 2D structure templates.

Usage:
  RuleTemplateGenerator generate --image <teaching.png> --out <rule_template.json> [--text <teaching.txt>]
  RuleTemplateGenerator sample-sleeve [out.json]
  RuleTemplateGenerator validate <rule_template.json>

Notes:
  In this version, the extractor is deterministic and only contains the first built-in
  teaching pattern: tight-fit sleeve disassembly. Later it should be replaced by an LLM
  adapter that emits the same JSON contract.
""");
}

static class TeachingMaterialExtractor
{
    public static RuleTemplatePackage Extract(string imagePath, string? textPath)
    {
        var text = textPath != null && File.Exists(textPath)
            ? File.ReadAllText(textPath, Encoding.UTF8)
            : "";

        if (LooksLikeSleeveRemovalRule(imagePath, text))
            return BuildSleeveRemovalTemplate(imagePath, textPath);

        return BuildReviewNeededTemplate(imagePath, textPath, text);
    }

    public static RuleTemplatePackage BuildSleeveRemovalTemplate(string imagePath, string? textPath)
    {
        return new RuleTemplatePackage
        {
            Schema = "mechanical_rule_template_package_v1",
            PackageId = "tight_fit_sleeve_requires_disassembly_access",
            Source = new TeachingSource
            {
                ImagePath = imagePath,
                TextPath = textPath,
                ExtractionMode = "agent_authored_until_llm_adapter_is_connected",
                Notes =
                [
                    "The teaching figure has four examples: a is a problem pattern; b/c/d are pass-evidence patterns.",
                    "Hatch lines and printed scale are annotations and must not become contour evidence."
                ]
            },
            ExtractionContract = RuleExtractionContract.Default(),
            BrepFactCapabilities = BrepFactCapabilityCatalog.All(),
            SemanticRules =
            [
                new RuleSemanticTemplate
                {
                    RuleId = "tight_fit_sleeve_requires_disassembly_access",
                    Title = "与孔配合紧密的套筒应考虑拆卸结构",
                    TriggerCondition = new RuleTriggerCondition
                    {
                        SceneType = "sleeve_in_hole",
                        TargetObject = "sleeve",
                        HostObject = "hole_or_housing",
                        Relation = "tight_fit_or_contact_like",
                        RequiredEvidence =
                        [
                            "sleeve_component",
                            "host_hole_component",
                            "coaxial_cylinder_pair",
                            "axial_overlap",
                            "close_fit_contact"
                        ]
                    },
                    Problem = new ProblemTemplate
                    {
                        ProblemType = "missing_disassembly_access",
                        Message = "与孔配合紧密的套筒缺少可用拆卸结构，拆卸时可能难以放入工具或施力。",
                        SourceExample = "a"
                    },
                    PassEvidenceTypes =
                    [
                        new PassEvidenceTemplate
                        {
                            EvidenceType = "external_push_access",
                            SourceExample = "b",
                            Anchor = "m",
                            Required2DTemplateId = "b_external_push_access",
                            RequiredModelEvidence =
                            [
                                "outside_accessible_push_path",
                                "push_path_reaches_sleeve_push_area",
                                "push_direction_has_removal_component"
                            ]
                        },
                        new PassEvidenceTemplate
                        {
                            EvidenceType = "tool_insertion_gap",
                            SourceExample = "c",
                            Anchor = "h",
                            Required2DTemplateId = "c_tool_insertion_gap",
                            RequiredModelEvidence =
                            [
                                "outside_connected_void_gap",
                                "gap_path_reaches_sleeve_push_area",
                                "gap_width_sufficient",
                                "tool_force_direction_valid"
                            ]
                        },
                        new PassEvidenceTemplate
                        {
                            EvidenceType = "ejector_screw_hole",
                            SourceExample = "d",
                            Anchor = "t",
                            Required2DTemplateId = "d_ejector_screw_hole",
                            RequiredModelEvidence =
                            [
                                "outside_connected_cylindrical_port",
                                "port_axis_intersects_sleeve_push_area",
                                "port_diameter_sufficient",
                                "port_push_direction_valid"
                            ],
                            OptionalModelEvidence = ["thread_or_screw_feature"]
                        }
                    ],
                    DecisionLogic = new RuleDecisionLogic
                    {
                        IfTriggerMissing = "not_applicable",
                        IfTriggerUnknown = "needs_review",
                        IfTriggeredAndAnyPassEvidence = "pass",
                        IfTriggeredAndNoPassEvidence = "problem"
                    }
                }
            ],
            ImageStructureTemplates =
            [
                SleeveCommonBaseTemplate(),
                new ImageStructureTemplate2D
                {
                    TemplateId = "a_no_removal_access",
                    Role = "problem",
                    SourceExample = "a",
                    Entities =
                    [
                        Entity("sleeve_material", "region", "sleeve"),
                        Entity("host_material", "region", "housing_or_hole"),
                        Entity("fit_contact", "edge_pair", "contact"),
                        Entity("outside", "region", "outside_air")
                    ],
                    RequiredRelations =
                    [
                        Rel("contact", "sleeve_material", "host_material"),
                        Rel("blocks_axial_removal", "host_material", "sleeve_material"),
                        Rel("missing", "external_push_access", "sleeve_material"),
                        Rel("missing", "tool_insertion_gap", "sleeve_material"),
                        Rel("missing", "ejector_screw_hole", "sleeve_material")
                    ],
                    IgnoredVisualElements = DefaultIgnoredVisualElements()
                },
                new ImageStructureTemplate2D
                {
                    TemplateId = "b_external_push_access",
                    Role = "pass",
                    SourceExample = "b",
                    Anchor = "m",
                    Entities =
                    [
                        Entity("outside", "region", "outside_air"),
                        Entity("push_access_path", "path", "outside_accessible_force_path"),
                        Entity("sleeve_push_area", "edge_or_region", "sleeve_force_receiving_area"),
                        Entity("removal_axis", "axis", "sleeve_removal_direction")
                    ],
                    RequiredRelations =
                    [
                        Rel("connected_to_outside", "push_access_path", "outside"),
                        Rel("reaches", "push_access_path", "sleeve_push_area"),
                        Rel("direction_has_component", "push_access_path", "removal_axis")
                    ],
                    IgnoredVisualElements = DefaultIgnoredVisualElements()
                },
                new ImageStructureTemplate2D
                {
                    TemplateId = "c_tool_insertion_gap",
                    Role = "pass",
                    SourceExample = "c",
                    Anchor = "h",
                    Entities =
                    [
                        Entity("sleeve_material", "region", "sleeve"),
                        Entity("host_material", "region", "housing_or_hole"),
                        Entity("gap_region", "region", "void_gap"),
                        Entity("outside", "region", "outside_air"),
                        Entity("sleeve_push_area", "edge_or_region", "sleeve_force_receiving_area"),
                        Entity("removal_axis", "axis", "sleeve_removal_direction"),
                        Entity("h", "dimension", "minimum_tool_insertion_clearance")
                    ],
                    RequiredRelations =
                    [
                        Rel("connected_to_outside", "gap_region", "outside"),
                        Rel("reaches", "gap_region", "sleeve_push_area"),
                        Rel("direction_has_component", "gap_region", "removal_axis"),
                        Rel("gap_width_sufficient", "gap_region", "h")
                    ],
                    IgnoredVisualElements = DefaultIgnoredVisualElements()
                },
                new ImageStructureTemplate2D
                {
                    TemplateId = "d_ejector_screw_hole",
                    Role = "pass",
                    SourceExample = "d",
                    Anchor = "t",
                    Entities =
                    [
                        Entity("sleeve_material", "region", "sleeve"),
                        Entity("host_material", "region", "housing_or_hole"),
                        Entity("port_region", "region", "cylindrical_ejector_port"),
                        Entity("outside", "region", "outside_air"),
                        Entity("sleeve_push_area", "edge_or_region", "sleeve_force_receiving_area"),
                        Entity("port_axis", "axis", "ejector_screw_axis"),
                        Entity("removal_axis", "axis", "sleeve_removal_direction"),
                        Entity("t", "dimension", "ejector_screw_hole_size")
                    ],
                    RequiredRelations =
                    [
                        Rel("connected_to_outside", "port_region", "outside"),
                        Rel("axis_intersects", "port_axis", "sleeve_push_area"),
                        Rel("direction_has_component", "port_axis", "removal_axis"),
                        Rel("port_diameter_sufficient", "port_region", "t")
                    ],
                    IgnoredVisualElements = DefaultIgnoredVisualElements()
                }
            ]
        };
    }

    static RuleTemplatePackage BuildReviewNeededTemplate(string imagePath, string? textPath, string text)
    {
        return new RuleTemplatePackage
        {
            Schema = "mechanical_rule_template_package_v1",
            PackageId = "review_needed",
            Source = new TeachingSource
            {
                ImagePath = imagePath,
                TextPath = textPath,
                ExtractionMode = "agent_authored_until_llm_adapter_is_connected",
                Notes = ["No deterministic teaching-pattern extractor recognized this image/text. LLM extraction is required."]
            },
            ExtractionContract = RuleExtractionContract.Default(),
            BrepFactCapabilities = BrepFactCapabilityCatalog.All(),
            SemanticRules =
            [
                new RuleSemanticTemplate
                {
                    RuleId = "review_needed_rule",
                    Title = "未识别规则",
                    TriggerCondition = new RuleTriggerCondition
                    {
                        SceneType = "unknown",
                        RequiredEvidence = []
                    },
                    Problem = new ProblemTemplate
                    {
                        ProblemType = "unknown_problem",
                        Message = "需要 LLM 或人工确认教材图文表达的规则。"
                    },
                    DecisionLogic = new RuleDecisionLogic
                    {
                        IfTriggerMissing = "needs_review",
                        IfTriggerUnknown = "needs_review",
                        IfTriggeredAndAnyPassEvidence = "needs_review",
                        IfTriggeredAndNoPassEvidence = "needs_review"
                    }
                }
            ],
            ImageStructureTemplates = []
        };
    }

    static bool LooksLikeSleeveRemovalRule(string imagePath, string text)
    {
        var combined = $"{imagePath} {text}";
        if (ContainsAny(combined, "套筒", "轴套", "衬套", "sleeve", "bushing") &&
            ContainsAny(combined, "孔", "hole", "bore") &&
            ContainsAny(combined, "拆卸", "推", "打", "间隙", "螺钉", "disassembly", "removal", "gap", "screw"))
        {
            return true;
        }

        // The current built-in teaching figure is often provided only as an image.
        // Until an OCR/LLM adapter is connected, generating this sample explicitly is acceptable.
        return true;
    }

    static ImageStructureTemplate2D SleeveCommonBaseTemplate()
    {
        return new ImageStructureTemplate2D
        {
            TemplateId = "common_sleeve_in_hole_close_fit",
            Role = "trigger",
            SourceExample = "a_b_c_d_common",
            Entities =
            [
                Entity("sleeve_material", "region", "sleeve"),
                Entity("host_material", "region", "housing_or_hole"),
                Entity("sleeve_axis", "axis", "main_axis"),
                Entity("fit_contact", "edge_pair", "coincident_or_close_fit_edges")
            ],
            RequiredRelations =
            [
                Rel("coaxial", "sleeve_material", "host_material"),
                Rel("contact", "sleeve_material", "host_material"),
                Rel("axial_overlap", "sleeve_material", "host_material")
            ],
            IgnoredVisualElements = DefaultIgnoredVisualElements()
        };
    }

    static TemplateEntity Entity(string id, string kind, string semanticRole)
    {
        return new TemplateEntity { Id = id, Kind = kind, SemanticRole = semanticRole };
    }

    static TemplateRelation Rel(string type, string a, string b)
    {
        return new TemplateRelation { Type = type, A = a, B = b };
    }

    static List<string> DefaultIgnoredVisualElements()
    {
        return ["hatch_lines", "line_thickness", "absolute_scale", "printed_dimension_size", "table_border", "text_border"];
    }

    static bool ContainsAny(string text, params string[] tokens)
    {
        return tokens.Any(t => text.Contains(t, StringComparison.OrdinalIgnoreCase));
    }
}

static class TemplateValidator
{
    public static List<string> Validate(RuleTemplatePackage package)
    {
        var errors = new List<string>();
        if (package.Schema != "mechanical_rule_template_package_v1")
            errors.Add("schema must be mechanical_rule_template_package_v1");
        if (string.IsNullOrWhiteSpace(package.PackageId))
            errors.Add("packageId is required");
        if (package.SemanticRules.Count == 0)
            errors.Add("at least one semantic rule is required");

        var structureIds = package.ImageStructureTemplates.Select(t => t.TemplateId).ToHashSet(StringComparer.OrdinalIgnoreCase);
        foreach (var rule in package.SemanticRules)
        {
            if (string.IsNullOrWhiteSpace(rule.RuleId))
                errors.Add("semantic rule ruleId is required");
            if (string.IsNullOrWhiteSpace(rule.TriggerCondition.SceneType))
                errors.Add($"{rule.RuleId}: triggerCondition.sceneType is required");
            foreach (var evidence in rule.TriggerCondition.RequiredEvidence)
            {
                ValidateRequiredFact(errors, rule.RuleId, "trigger evidence", evidence);
            }
            foreach (var pass in rule.PassEvidenceTypes)
            {
                if (!string.IsNullOrWhiteSpace(pass.Required2DTemplateId) && !structureIds.Contains(pass.Required2DTemplateId))
                    errors.Add($"{rule.RuleId}: pass evidence {pass.EvidenceType} references missing structure template {pass.Required2DTemplateId}");
                foreach (var evidence in pass.RequiredModelEvidence)
                {
                    ValidateRequiredFact(errors, rule.RuleId, $"pass evidence {pass.EvidenceType}", evidence);
                }
                foreach (var evidence in pass.OptionalModelEvidence)
                {
                    ValidateOptionalFact(errors, rule.RuleId, $"pass evidence {pass.EvidenceType}", evidence);
                }
            }
        }

        foreach (var template in package.ImageStructureTemplates)
        {
            if (string.IsNullOrWhiteSpace(template.TemplateId))
                errors.Add("structure template templateId is required");
            if (template.RequiredRelations.Count == 0)
                errors.Add($"{template.TemplateId}: requiredRelations cannot be empty");
        }

        return errors;
    }

    static void ValidateRequiredFact(List<string> errors, string ruleId, string context, string fact)
    {
        var capability = BrepFactCapabilityCatalog.Find(fact);
        if (capability == null)
        {
            errors.Add($"{ruleId}: {context} `{fact}` has no registered B-rep fact capability");
            return;
        }

        if (!capability.AuthoringAllowed)
            errors.Add($"{ruleId}: {context} `{fact}` is not safe for required rules: {capability.MissingWhenAbsent}");
    }

    static void ValidateOptionalFact(List<string> errors, string ruleId, string context, string fact)
    {
        var capability = BrepFactCapabilityCatalog.Find(fact);
        if (capability == null)
            errors.Add($"{ruleId}: {context} lists optional evidence `{fact}` with no registered B-rep fact capability");
    }
}

static class BrepFactCapabilityCatalog
{
    static readonly List<BrepFactCapability> Items =
    [
        Fact("sleeve_component", "3d_brep+component", "implemented", "SceneRecognizer.RecognizeSleeveInHole", "component category/name plus cylindrical section/contact evidence", "No sleeve-like component candidate was recognized."),
        Fact("sleeve_like_component", "3d_brep+component", "implemented", "SceneRecognizer.RecognizeSleeveInHole", "component category/name plus cylindrical section/contact evidence", "No sleeve-like component candidate was recognized."),
        Fact("host_hole_component", "3d_brep+component", "implemented", "SceneRecognizer.RecognizeSleeveInHole", "host/housing component and coaxial/contact evidence", "No host or hole component candidate was recognized."),
        Fact("coaxial_cylinder_pair", "2d_section_brep", "implemented", "SectionStructureSynthesizer/SketchGraph.Relations", "coaxial sleeve/host section geometry", "No coaxial sleeve-host pair was detected."),
        Fact("axial_overlap", "2d_section_brep", "implemented", "SectionStructureSynthesizer/SketchGraph.Relations", "overlapping axial ranges in section", "No axial overlap between sleeve and host was detected."),
        Fact("close_fit_contact", "2d_section_brep", "implemented", "SketchGraph.Relations contact", "contact or near-contact section edges", "No close-fit contact relation was detected."),
        Fact("close_fit_contact_or_small_clearance", "2d_section_brep", "implemented", "SketchGraph.Relations contact/clearance", "contact or small clearance section edges", "No close-fit contact or small clearance relation was detected."),
        Fact("outside_accessible_push_path", "2d_section_brep", "implemented", "ReadSketchDimension exposed_push_face_candidate", "exposed sleeve end edge and outside clearance", "No exposed sleeve push face was detected."),
        Fact("push_path_reaches_sleeve_push_area", "2d_section_brep", "implemented", "ReadSketchDimension exposed_push_face_candidate", "exposed sleeve end edge", "No push path reaches a sleeve push area."),
        Fact("push_direction_has_removal_component", "2d_section_brep", "implemented", "ReadSketchDimension exposed_push_face_candidate", "push face orientation against sleeve axis", "No push direction with removal-axis component was detected."),
        Fact("outside_connected_void_gap", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "outside-connected free-space cells between sleeve and host", "No outside-connected sleeve/host gap candidate was detected."),
        Fact("gap_path_reaches_sleeve_push_area", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "gap cluster adjacent to sleeve push area", "No detected gap cluster reached the sleeve push area."),
        Fact("gap_width_sufficient", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "estimated minimum sleeve-host free-space width", "No detected gap satisfied the minimum tool-width threshold."),
        Fact("tool_force_direction_valid", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "gap cluster with axial extent along removal direction", "No detected gap had a sufficient removal-direction extent."),
        Fact("outside_connected_cylindrical_port", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "small host/end-cap cylindrical face treated as port candidate", "No small host/end-cap cylindrical port candidate was detected."),
        Fact("port_axis_intersects_sleeve_push_area", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "port axis close enough to sleeve push area", "No port axis was found to reach the sleeve push area."),
        Fact("port_diameter_sufficient", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "cylindrical port radius/diameter", "No detected port diameter satisfied the minimum threshold."),
        Fact("port_push_direction_valid", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "port axis with removal-axis force component", "No detected port axis had a sufficient removal-direction component."),
        Fact("thread_or_screw_feature", "feature_metadata_or_modeled_thread", "optional_only", "optional SolidWorks feature/thread metadata or modeled helical B-rep", "thread feature metadata or real thread geometry", "Pure B-rep often does not contain reliable thread semantics; keep this optional.")
    ];

    public static List<BrepFactCapability> All() => Items.Select(i => i.Clone()).ToList();

    public static BrepFactCapability? Find(string fact)
    {
        var normalized = NormalizeToken(fact);
        return Items.FirstOrDefault(i => i.Fact.Equals(normalized, StringComparison.OrdinalIgnoreCase));
    }

    static BrepFactCapability Fact(string fact, string source, string status, string extractor, string primitive, string missingWhenAbsent)
    {
        return new BrepFactCapability
        {
            Fact = fact,
            SourceLayer = source,
            ExtractorStatus = status,
            Extractor = extractor,
            RequiredPrimitive = primitive,
            MissingWhenAbsent = missingWhenAbsent,
            AuthoringAllowed = status is "implemented" or "partial" or "planned"
        };
    }

    static string NormalizeToken(string value) => value.Trim().Replace(' ', '_').ToLowerInvariant();
}

sealed class Args
{
    readonly Dictionary<string, string> _values;

    Args(Dictionary<string, string> values)
    {
        _values = values;
    }

    public static Args Parse(string[] args)
    {
        var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        for (int i = 0; i < args.Length; i++)
        {
            var key = args[i];
            if (!key.StartsWith("--", StringComparison.Ordinal))
                throw new ArgumentException($"Unexpected argument: {key}");
            if (i + 1 >= args.Length)
                throw new ArgumentException($"Missing value for {key}");
            values[key] = args[++i];
        }
        return new Args(values);
    }

    public string Required(string key)
    {
        if (!_values.TryGetValue(key, out var value) || string.IsNullOrWhiteSpace(value))
            throw new ArgumentException($"{key} is required.");
        return value;
    }

    public string? Optional(string key)
    {
        return _values.TryGetValue(key, out var value) ? value : null;
    }
}

sealed class RuleTemplatePackage
{
    public string Schema { get; set; } = "mechanical_rule_template_package_v1";
    public string PackageId { get; set; } = "";
    public TeachingSource Source { get; set; } = new();
    public RuleExtractionContract ExtractionContract { get; set; } = new();
    public List<BrepFactCapability> BrepFactCapabilities { get; set; } = [];
    public List<RuleSemanticTemplate> SemanticRules { get; set; } = [];
    public List<ImageStructureTemplate2D> ImageStructureTemplates { get; set; } = [];
}

sealed class BrepFactCapability
{
    public string Fact { get; set; } = "";
    public string SourceLayer { get; set; } = "";
    public string ExtractorStatus { get; set; } = "";
    public string Extractor { get; set; } = "";
    public string RequiredPrimitive { get; set; } = "";
    public string MissingWhenAbsent { get; set; } = "";
    public bool AuthoringAllowed { get; set; }

    public BrepFactCapability Clone()
    {
        return new BrepFactCapability
        {
            Fact = Fact,
            SourceLayer = SourceLayer,
            ExtractorStatus = ExtractorStatus,
            Extractor = Extractor,
            RequiredPrimitive = RequiredPrimitive,
            MissingWhenAbsent = MissingWhenAbsent,
            AuthoringAllowed = AuthoringAllowed
        };
    }
}

sealed class RuleExtractionContract
{
    public string LlmResponsibility { get; set; } = "";
    public string ProgramResponsibility { get; set; } = "";
    public List<string> RequiredLlmOutputs { get; set; } = [];
    public List<string> RequiredProgramEvidence { get; set; } = [];

    public static RuleExtractionContract Default()
    {
        return new RuleExtractionContract
        {
            LlmResponsibility = "Extract rule semantics and image-side 2D structure templates from teaching image/text; do not judge product models.",
            ProgramResponsibility = "Extract model B-rep evidence, generate model-side 2D section structures, match templates, and make final rule decisions.",
            RequiredLlmOutputs =
            [
                "rule object and trigger condition",
                "problem type and problem evidence",
                "pass evidence types",
                "image 2D structure templates",
                "visual anchors such as m/h/t",
                "ignored visual annotations such as hatches and scale"
            ],
            RequiredProgramEvidence =
            [
                "components and inferred categories",
                "faces, cylinders, planes, axes, and contact-like relations",
                "scene candidates",
                "section 2D structure instances",
                "real dimensions and contact validation evidence"
            ]
        };
    }
}

sealed class TeachingSource
{
    public string ImagePath { get; set; } = "";
    public string? TextPath { get; set; }
    public string ExtractionMode { get; set; } = "";
    public List<string> Notes { get; set; } = [];
}

sealed class RuleSemanticTemplate
{
    public string RuleId { get; set; } = "";
    public string Title { get; set; } = "";
    public RuleTriggerCondition TriggerCondition { get; set; } = new();
    public ProblemTemplate Problem { get; set; } = new();
    public List<PassEvidenceTemplate> PassEvidenceTypes { get; set; } = [];
    public RuleDecisionLogic DecisionLogic { get; set; } = new();
}

sealed class RuleTriggerCondition
{
    public string SceneType { get; set; } = "";
    public string TargetObject { get; set; } = "";
    public string HostObject { get; set; } = "";
    public string Relation { get; set; } = "";
    public List<string> RequiredEvidence { get; set; } = [];
}

sealed class ProblemTemplate
{
    public string ProblemType { get; set; } = "";
    public string Message { get; set; } = "";
    public string SourceExample { get; set; } = "";
}

sealed class PassEvidenceTemplate
{
    public string EvidenceType { get; set; } = "";
    public string SourceExample { get; set; } = "";
    public string Anchor { get; set; } = "";
    public string Required2DTemplateId { get; set; } = "";
    public List<string> RequiredModelEvidence { get; set; } = [];
    public List<string> OptionalModelEvidence { get; set; } = [];
}

sealed class RuleDecisionLogic
{
    public string IfTriggerMissing { get; set; } = "not_applicable";
    public string IfTriggerUnknown { get; set; } = "needs_review";
    public string IfTriggeredAndAnyPassEvidence { get; set; } = "pass";
    public string IfTriggeredAndNoPassEvidence { get; set; } = "problem";
}

sealed class ImageStructureTemplate2D
{
    public string TemplateId { get; set; } = "";
    public string Role { get; set; } = "";
    public string SourceExample { get; set; } = "";
    public string Anchor { get; set; } = "";
    public List<TemplateEntity> Entities { get; set; } = [];
    public List<TemplateRelation> RequiredRelations { get; set; } = [];
    public List<string> IgnoredVisualElements { get; set; } = [];
}

sealed class TemplateEntity
{
    public string Id { get; set; } = "";
    public string Kind { get; set; } = "";
    public string SemanticRole { get; set; } = "";
}

sealed class TemplateRelation
{
    public string Type { get; set; } = "";
    public string A { get; set; } = "";
    public string B { get; set; } = "";
}

static class JsonOptions
{
    public static readonly JsonSerializerOptions Default = new()
    {
        PropertyNameCaseInsensitive = true,
        WriteIndented = true,
        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
        NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
    };
}
