///|
fn rule_ref(
  code : String,
  severity : Severity,
  format : String,
  description : String,
  repair_hint : String,
) -> ValidationRuleReference {
  { code, severity, format, description, repair_hint }
}

///|
pub fn validation_rule_references() -> Array[ValidationRuleReference] {
  [
    rule_ref(
      "duplicate_id",
      Error,
      "GFF3",
      "Two features share the same ID",
      "Make ID values unique within the document",
    ),
    rule_ref(
      "orphan_parent",
      Error,
      "GFF3",
      "A Parent attribute points to a missing feature",
      "Add the parent feature or correct the Parent value",
    ),
    rule_ref(
      "invalid_phase",
      Warning,
      "both",
      "A CDS phase is not 0, 1, or 2",
      "Normalize CDS phase to one of 0, 1, or 2",
    ),
    rule_ref(
      "missing_id",
      Warning,
      "GFF3",
      "A container feature lacks ID",
      "Add ID for gene and transcript-like features",
    ),
    rule_ref(
      "missing_gene_id",
      Error,
      "GTF",
      "A GTF feature lacks gene_id",
      "Add gene_id to every GTF row",
    ),
    rule_ref(
      "missing_transcript_id",
      Error,
      "GTF",
      "A non-gene GTF feature lacks transcript_id",
      "Add transcript_id to transcript child rows",
    ),
    rule_ref(
      "invalid_coordinate_order",
      Error,
      "both",
      "Feature start is greater than end",
      "Swap or correct the feature coordinates",
    ),
    rule_ref(
      "non_positive_start",
      Error,
      "both",
      "Feature start is below 1",
      "Use 1-based closed coordinates",
    ),
    rule_ref(
      "empty_seqid",
      Error,
      "both",
      "Feature seqid is empty",
      "Provide chromosome, contig, or scaffold name",
    ),
    rule_ref(
      "empty_feature_type",
      Error,
      "both",
      "Feature type is empty",
      "Provide a Sequence Ontology style feature type",
    ),
    rule_ref(
      "unknown_strand",
      Warning,
      "both",
      "Strand is not one of +, -, ., or ?",
      "Normalize strand field",
    ),
    rule_ref(
      "score_not_numeric",
      Warning,
      "both",
      "Score is present but not numeric",
      "Use a numeric score or '.'",
    ),
    rule_ref(
      "reserved_attribute_key",
      Warning,
      "GFF3",
      "Attribute key uses a reserved name unexpectedly",
      "Check provider-specific conventions",
    ),
    rule_ref(
      "empty_attribute_key",
      Error,
      "both",
      "Attribute key is empty",
      "Remove malformed attribute entry",
    ),
    rule_ref(
      "empty_required_attribute",
      Error,
      "both",
      "Required attribute has empty value",
      "Fill required attribute value",
    ),
    rule_ref(
      "duplicate_attribute",
      Warning,
      "both",
      "Same attribute key appears multiple times on one feature",
      "Merge values or remove duplicate keys",
    ),
    rule_ref(
      "gene_without_transcript",
      Warning,
      "both",
      "Gene has no transcript child",
      "Add transcript feature when available",
    ),
    rule_ref(
      "transcript_without_exon",
      Warning,
      "both",
      "Transcript has no exon child",
      "Add exon intervals",
    ),
    rule_ref(
      "cds_without_transcript",
      Error,
      "both",
      "CDS cannot be assigned to a transcript",
      "Fix Parent or transcript_id",
    ),
    rule_ref(
      "exon_outside_transcript",
      Error,
      "both",
      "Exon lies outside transcript span",
      "Correct exon or transcript coordinates",
    ),
    rule_ref(
      "cds_outside_exon",
      Warning,
      "both",
      "CDS is not covered by an exon",
      "Check coding structure",
    ),
    rule_ref(
      "overlapping_exons",
      Warning,
      "both",
      "Transcript exons overlap each other",
      "Merge or correct exon intervals",
    ),
    rule_ref(
      "unsorted_exons",
      Info,
      "both",
      "Transcript exons are not in genomic order",
      "Sort children for deterministic output",
    ),
    rule_ref(
      "mixed_seqid_children",
      Error,
      "both",
      "Child feature seqid differs from parent",
      "Move child to the correct parent",
    ),
    rule_ref(
      "mixed_strand_children",
      Warning,
      "both",
      "Child strand differs from parent",
      "Confirm strand conventions",
    ),
    rule_ref(
      "unknown_biotype",
      Info,
      "both",
      "Biotype is not in the reference catalog",
      "Document project-specific biotype",
    ),
    rule_ref(
      "unknown_feature_type",
      Info,
      "both",
      "Feature type is not in the reference catalog",
      "Document project-specific feature type",
    ),
    rule_ref(
      "missing_name",
      Info,
      "both",
      "Feature lacks display name",
      "Add Name or gene_name for user-facing reports",
    ),
    rule_ref(
      "large_feature_span",
      Info,
      "both",
      "Feature span is unusually large",
      "Check assembly coordinates",
    ),
    rule_ref(
      "zero_length_bed_block",
      Error,
      "both",
      "BED block would have zero length",
      "Check exon coordinates",
    ),
    rule_ref(
      "negative_bed_start",
      Error,
      "both",
      "BED conversion produced negative start",
      "Check 1-based start coordinate",
    ),
    rule_ref(
      "invalid_target_attribute",
      Warning,
      "GFF3",
      "Target attribute cannot be parsed",
      "Use target_id start end strand format",
    ),
    rule_ref(
      "invalid_gap_attribute",
      Warning,
      "GFF3",
      "Gap attribute cannot be parsed",
      "Use valid GFF3 gap operations",
    ),
    rule_ref(
      "invalid_derives_from",
      Warning,
      "GFF3",
      "Derives_from target is missing",
      "Add referenced feature or correct value",
    ),
    rule_ref(
      "multi_parent_without_split",
      Info,
      "GFF3",
      "Feature has multiple parents",
      "Split or document shared feature semantics",
    ),
    rule_ref(
      "attribute_needs_escape",
      Info,
      "GFF3",
      "Attribute value contains characters needing escape",
      "Use percent encoding in raw GFF3",
    ),
    rule_ref(
      "gtf_quote_escape",
      Info,
      "GTF",
      "GTF value contains quotes or backslashes",
      "Escape value when writing GTF",
    ),
    rule_ref(
      "missing_cds_phase",
      Warning,
      "both",
      "CDS has no phase field",
      "Add phase when coding frame is known",
    ),
    rule_ref(
      "partial_cds",
      Info,
      "both",
      "CDS appears partial",
      "Mark partial status when known",
    ),
    rule_ref(
      "missing_protein_id",
      Info,
      "GTF",
      "Coding feature lacks protein_id",
      "Add protein_id when available",
    ),
    rule_ref(
      "missing_exon_number",
      Info,
      "GTF",
      "Exon lacks exon_number",
      "Add exon_number for stable ordering",
    ),
    rule_ref(
      "duplicate_exon_number",
      Warning,
      "GTF",
      "Transcript has duplicate exon_number",
      "Correct exon numbering",
    ),
    rule_ref(
      "inconsistent_gene_name",
      Info,
      "GTF",
      "Rows for a gene use multiple names",
      "Normalize gene_name",
    ),
    rule_ref(
      "inconsistent_biotype",
      Info,
      "GTF",
      "Rows for a gene use multiple biotypes",
      "Normalize gene_biotype",
    ),
  ]
}

///|
pub fn find_validation_rule_reference(code : String) -> ValidationRuleReference {
  for rule in validation_rule_references() {
    if rule.code == code {
      return rule
    }
  }
  rule_ref(
    code,
    Info,
    "both",
    "Project-specific validation rule",
    "Document the rule in project notes",
  )
}

///|
pub fn ValidationIssue::rule_reference(
  self : ValidationIssue,
) -> ValidationRuleReference {
  find_validation_rule_reference(self.code)
}

///|
pub fn validation_rule_references_by_format(
  format : String,
) -> Array[ValidationRuleReference] {
  let result : Array[ValidationRuleReference] = []
  for rule in validation_rule_references() {
    if rule.format == format || rule.format == "both" {
      result.push(rule)
    }
  }
  result
}

///|
pub fn ValidationRuleReference::to_tsv_row(
  self : ValidationRuleReference,
) -> String {
  [
    self.code,
    self.severity.to_string(),
    self.format,
    self.description,
    self.repair_hint,
  ].join("\t")
}