// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Relationship between the supported before and after computed facts.
///
/// `status` is `equivalent`, `different`, `indeterminate`, or
/// `not_applicable`; `reason_code` and Diagnostic references explain why.
pub(all) struct ComputedRelation {
  status : String
  reason_code : String
  missing_side : String?
  diagnostic_ids : Array[String]
} derive(ToJson)

///|
/// Continuous canonical raster measurements for one rendered outcome.
pub(all) struct RenderedMagnitude {
  changed_pixels : Int
  changed_pixel_fraction : Double
  rgba8_rmse : Double
  linear_premultiplied_rgba_rmse : Double
} derive(ToJson)

///|
/// Intrinsic decoded-raster comparison, independent of final SVG compositing.
pub(all) struct IntrinsicRasterMagnitude {
  before_width : Int
  before_height : Int
  after_width : Int
  after_height : Int
  compared_pixels : Int?
  changed_pixels : Int?
  changed_pixel_fraction : Double?
  rgba8_rmse : Double?
  linear_premultiplied_rgba_rmse : Double?
} derive(ToJson)

///|
/// Displayed-color magnitude for one selected rendered outcome after explicit
/// background compositing.
pub(all) struct PerceptualColorMagnitude {
  method_id : String
  sample_count : Int
  mean_delta_e_ok : Double
} derive(ToJson, Eq)

///|
/// Availability and optional event-local perceptual-color magnitude.
pub(all) struct PerceptualColorEvidence {
  status : String
  reason_code : String?
  magnitude : PerceptualColorMagnitude?
} derive(ToJson, Eq)

///|
/// Deterministic quantized LDR-FLIP map for one selected rendered outcome.
pub(all) struct PerceptualFlipMap {
  method_id : String
  pixel_x : Int
  pixel_y : Int
  pixel_width : Int
  pixel_height : Int
  encoding : String
  quantization_step : Double
  values_base64 : String
} derive(ToJson, Eq)

///|
/// Explicit-threshold spatial extent for one event-local LDR-FLIP response.
pub(all) struct PerceptualFlipAreaAboveThreshold {
  threshold : Double
  pixel_count : Int
  canvas_fraction : Double
} derive(ToJson, Eq)

///|
/// Separate pooled domains over one unquantized LDR-FLIP map.
pub(all) struct PerceptualFlipStatistics {
  method_id : String
  canvas_pixel_count : Int
  event_region_sample_count : Int
  response_sample_count : Int
  canvas_mean : Double
  event_region_mean : Double
  response_p95 : Double
  response_maximum : Double
  area_above_threshold : PerceptualFlipAreaAboveThreshold?
} derive(Eq)

///|
pub impl ToJson for PerceptualFlipStatistics with fn to_json(self) {
  let fields : Map[String, Json] = Map([])
  fields["method_id"] = self.method_id.to_json()
  fields["canvas_pixel_count"] = self.canvas_pixel_count.to_json()
  fields["event_region_sample_count"] = self.event_region_sample_count.to_json()
  fields["response_sample_count"] = self.response_sample_count.to_json()
  fields["canvas_mean"] = self.canvas_mean.to_json()
  fields["event_region_mean"] = self.event_region_mean.to_json()
  fields["response_p95"] = self.response_p95.to_json()
  fields["response_maximum"] = self.response_maximum.to_json()
  fields["area_above_threshold"] = nullable_report_value(
    self.area_above_threshold,
  )
  Json::object(fields)
}

///|
/// Availability and optional event-local LDR-FLIP map.
pub(all) struct PerceptualFlipEvidence {
  status : String
  reason_code : String?
  map : PerceptualFlipMap?
  statistics : PerceptualFlipStatistics?
} derive(ToJson, Eq)

///|
/// Availability and optional magnitude of canonical rendered evidence.
///
/// A computed zero magnitude is distinct from `not_computed`.
pub(all) struct RenderedEvidence {
  status : String
  reason_code : String?
  magnitude : RenderedMagnitude?
  perceptual_color : PerceptualColorEvidence
  perceptual_flip : PerceptualFlipEvidence
} derive(ToJson)

///|
/// Availability and comparison-wide LDR-FLIP mean.
pub(all) struct CanvasPerceptualFlipEvidence {
  status : String
  reason_code : String?
  method_id : String?
  canvas_mean : Double?
} derive(ToJson)

///|
/// Measurements computed once from the final before and after canvases.
pub(all) struct CanvasOutcome {
  status : String
  reason_code : String?
  magnitude : RenderedMagnitude?
  perceptual_flip : CanvasPerceptualFlipEvidence
} derive(ToJson)

///|
/// Conservative Changed Fact candidates for one Difference Region.
///
/// A `sound_overapproximation` contains every actual changed cause within
/// complete analysis coverage, but may contain false positives.
pub(all) struct CauseEnvelope {
  guarantee : String
  coverage : String
  fallback_scope : String
  candidate_changed_fact_ids : Array[String]
  diagnostic_ids : Array[String]
} derive(ToJson)

///|
/// Pixel-space and CSS-space localization of one visual outcome component.
pub(all) struct DifferenceRegion {
  id : String
  source : String
  pixel_x : Int
  pixel_y : Int
  pixel_width : Int
  pixel_height : Int
  css_x : Double
  css_y : Double
  css_width : Double
  css_height : Double
  changed_pixels : Int
  viewport_fraction : Double
  cause_envelope : CauseEnvelope
} derive(ToJson)

///|
/// Numeric footprint of inserted or deleted visual subjects.
pub(all) struct PresenceMagnitude {
  affected_entity_count : Int
  bounds_area_css_px2 : Double
  bounds_viewport_fraction : Double
  painted_area_css_px2 : Double
  painted_viewport_fraction : Double
} derive(ToJson)

///|
/// Viewport-space translation change and its Euclidean CSS-pixel magnitude.
pub(all) struct TransformTranslationMagnitude {
  before_x_css_px : Double
  before_y_css_px : Double
  after_x_css_px : Double
  after_y_css_px : Double
  delta_x_css_px : Double
  delta_y_css_px : Double
  norm_css_px : Double
} derive(ToJson)

///|
/// Canonical paired rotation values and shortest signed angular change.
pub(all) struct TransformRotationMagnitude {
  before_degrees : Double
  after_degrees : Double
  signed_delta_degrees : Double
  abs_delta_degrees : Double
} derive(ToJson)

///|
/// Canonical signed axis scales and their largest absolute paired delta.
pub(all) struct TransformScaleMagnitude {
  before_x : Double
  before_y : Double
  after_x : Double
  after_y : Double
  max_abs_delta : Double
} derive(ToJson)

///|
/// Canonical paired X-skew values and shortest signed angular change.
pub(all) struct TransformSkewMagnitude {
  before_degrees : Double
  after_degrees : Double
  signed_delta_degrees : Double
  abs_delta_degrees : Double
} derive(ToJson)

///|
/// Exact singular linear transforms retained without a coefficient score.
pub(all) struct TransformResidualMagnitude {
  before_matrix : Array[Double]
  after_matrix : Array[Double]
  before_determinant : Double
  after_determinant : Double
} derive(ToJson)

///|
/// Tagged raw magnitude for one canonical transform-effect domain.
pub(all) enum TransformEffectMagnitude {
  TranslationEffect(TransformTranslationMagnitude)
  RotationEffect(TransformRotationMagnitude)
  ScaleEffect(TransformScaleMagnitude)
  SkewEffect(TransformSkewMagnitude)
  ResidualMatrixEffect(TransformResidualMagnitude)
}

///|
pub impl ToJson for TransformEffectMagnitude with fn to_json(self) {
  match self {
    TranslationEffect(value) =>
      {
        "kind": "translation",
        "before_x_css_px": value.before_x_css_px,
        "before_y_css_px": value.before_y_css_px,
        "after_x_css_px": value.after_x_css_px,
        "after_y_css_px": value.after_y_css_px,
        "delta_x_css_px": value.delta_x_css_px,
        "delta_y_css_px": value.delta_y_css_px,
        "norm_css_px": value.norm_css_px,
      }
    RotationEffect(value) =>
      {
        "kind": "rotation",
        "before_degrees": value.before_degrees,
        "after_degrees": value.after_degrees,
        "signed_delta_degrees": value.signed_delta_degrees,
        "abs_delta_degrees": value.abs_delta_degrees,
      }
    ScaleEffect(value) =>
      {
        "kind": "scale",
        "before_x": value.before_x,
        "before_y": value.before_y,
        "after_x": value.after_x,
        "after_y": value.after_y,
        "max_abs_delta": value.max_abs_delta,
      }
    SkewEffect(value) =>
      {
        "kind": "skew",
        "before_degrees": value.before_degrees,
        "after_degrees": value.after_degrees,
        "signed_delta_degrees": value.signed_delta_degrees,
        "abs_delta_degrees": value.abs_delta_degrees,
      }
    ResidualMatrixEffect(value) =>
      {
        "kind": "residual_matrix",
        "before_matrix": value.before_matrix,
        "after_matrix": value.after_matrix,
        "before_determinant": value.before_determinant,
        "after_determinant": value.after_determinant,
      }
  }
}

///|
/// Symmetric nearest-boundary statistics from the pinned renderer's alpha support.
pub(all) struct PaintedBoundaryDisplacementMagnitude {
  method_id : String
  before_sample_count : Int
  after_sample_count : Int
  mean_css_px : Double
  p95_css_px : Double
  max_css_px : Double
} derive(ToJson, Eq)

///|
/// Symmetric alpha-only isolated painted-coverage comparison.
pub(all) struct PaintedCoverageDifferenceMagnitude {
  method_id : String
  before_coverage_css_px2 : Double
  after_coverage_css_px2 : Double
  absolute_difference_css_px2 : Double
  union_coverage_css_px2 : Double
  fraction : Double
} derive(ToJson, Eq)

///|
/// Domain-appropriate magnitude vector for one Atomic Difference.
///
/// Optional components that were not computed serialize as null rather than zero.
pub(all) struct DifferenceMagnitude {
  parameter_abs_user_units : Double?
  parameter_signed_user_units : Double?
  symmetric_relative : Double?
  parameter_abs_css_px : Double?
  parameter_viewport_fraction : Double?
  parameter_entity_fraction : Double?
  geometry_displacement_css_px : Double?
  painted_boundary_displacement : PaintedBoundaryDisplacementMagnitude?
  painted_coverage_difference : PaintedCoverageDifferenceMagnitude?
  geometry_viewport_fraction : Double?
  presence_painted_viewport_fraction : Double?
  raster_changed_pixel_fraction : Double?
  raster_rgba8_rmse : Double?
  raster_linear_premultiplied_rgba_rmse : Double?
  intrinsic_raster : IntrinsicRasterMagnitude?
  transform_effect : TransformEffectMagnitude?
}

///|
fn[T : ToJson] nullable_report_value(value : T?) -> Json {
  match value {
    Some(value) => value.to_json()
    None => Json::null()
  }
}

///|
pub impl ToJson for DifferenceMagnitude with fn to_json(self) {
  let fields : Map[String, Json] = Map([])
  fields["parameter_abs_user_units"] = nullable_report_value(
    self.parameter_abs_user_units,
  )
  fields["parameter_signed_user_units"] = nullable_report_value(
    self.parameter_signed_user_units,
  )
  fields["symmetric_relative"] = nullable_report_value(self.symmetric_relative)
  fields["parameter_abs_css_px"] = nullable_report_value(
    self.parameter_abs_css_px,
  )
  fields["parameter_viewport_fraction"] = nullable_report_value(
    self.parameter_viewport_fraction,
  )
  fields["parameter_entity_fraction"] = nullable_report_value(
    self.parameter_entity_fraction,
  )
  fields["geometry_displacement_css_px"] = nullable_report_value(
    self.geometry_displacement_css_px,
  )
  fields["painted_boundary_displacement"] = nullable_report_value(
    self.painted_boundary_displacement,
  )
  fields["painted_coverage_difference"] = nullable_report_value(
    self.painted_coverage_difference,
  )
  fields["geometry_viewport_fraction"] = nullable_report_value(
    self.geometry_viewport_fraction,
  )
  fields["presence_painted_viewport_fraction"] = nullable_report_value(
    self.presence_painted_viewport_fraction,
  )
  fields["raster_changed_pixel_fraction"] = nullable_report_value(
    self.raster_changed_pixel_fraction,
  )
  fields["raster_rgba8_rmse"] = nullable_report_value(self.raster_rgba8_rmse)
  fields["raster_linear_premultiplied_rgba_rmse"] = nullable_report_value(
    self.raster_linear_premultiplied_rgba_rmse,
  )
  fields["intrinsic_raster"] = nullable_report_value(self.intrinsic_raster)
  match self.transform_effect {
    Some(value) => fields["transform_effect"] = value.to_json()
    None => ()
  }
  Json::object(fields)
}

///|
/// Versioned lexicographic components for ordering differences in one domain.
pub(all) struct DomainOrdering {
  policy_id : String
  components : Array[Double]
} derive(ToJson)

///|
/// Half-open UTF-16 offsets into the original SVG source.
pub(all) struct ReportSourceSpan {
  start_offset : Int
  end_offset : Int
} derive(ToJson)

///|
/// One source-qualified location establishing a Diagnostic.
pub(all) struct DiagnosticSourceLocation {
  source_role : String
  source_span : ReportSourceSpan
} derive(ToJson)

///|
/// One supported authored visual declaration with exact source provenance.
pub(all) struct DeclaredVisualFact {
  property : String
  authored_value : String
  declared_value : String
  origin : String
  source_span : ReportSourceSpan
} derive(ToJson)

///|
/// One supported visual fact after declaration precedence and inheritance.
pub(all) struct ResolvedVisualFact {
  subject_id : String
  property : String
  resolved_value : String
  resolution : String
  declaration_owner_id : String?
  declaration : DeclaredVisualFact?
  inheritance_depth : Int
} derive(ToJson)

///|
/// Before and after resolved facts for one subject property.
pub(all) struct SourceResolutionPair {
  subject_id : String
  property : String
  before : ResolvedVisualFact?
  after : ResolvedVisualFact?
} derive(ToJson)

///|
/// Placement identity for a subject rendered through one or more `use` hosts.
///
/// The corresponding authored definition keeps its own identity and Source
/// Spans; `instance_id` identifies only this rendered outcome.
pub(all) struct SubjectInstanceContext {
  instance_id : String
  definition_subject_id : String
  use_path : Array[String]
} derive(ToJson)

///|
/// Report-local reference to one visual subject in an input SVG.
pub(all) struct SubjectReference {
  source_index : Int
  kind : String
  authored_id : String?
  instance_context : SubjectInstanceContext?
}

///|
pub impl ToJson for SubjectReference with fn to_json(self) {
  {
    "source_index": self.source_index,
    "kind": self.kind,
    "authored_id": nullable_report_value(self.authored_id),
    "instance_context": nullable_report_value(self.instance_context),
  }
}

///|
/// Reviewable selection evidence for one Subject Alignment.
///
/// `confidence` remains null while `confidence_status` is `not_calibrated`;
/// deterministic selection does not imply a calibrated probability.
pub(all) struct AlignmentEvidence {
  score_kind : String
  selected_score : Double?
  candidate_count : Int
  equal_score_candidate_count : Int
  ambiguity : String
  confidence : Double?
  confidence_status : String
}

///|
pub impl ToJson for AlignmentEvidence with fn to_json(self) {
  {
    "score_kind": self.score_kind,
    "selected_score": nullable_report_value(self.selected_score),
    "candidate_count": self.candidate_count,
    "equal_score_candidate_count": self.equal_score_candidate_count,
    "ambiguity": self.ambiguity,
    "confidence": nullable_report_value(self.confidence),
    "confidence_status": self.confidence_status,
  }
}

///|
/// Set-to-set relationship between before and after visual subjects.
pub(all) struct SubjectAlignment {
  id : String
  subject_role : String
  relation : String
  before : Array[SubjectReference]
  after : Array[SubjectReference]
  basis : String
  evidence : AlignmentEvidence
} derive(ToJson)

///|
/// One changed supported authored fact and the subjects it may affect.
pub(all) struct ChangedFact {
  id : String
  subject_id : String
  property : String
  before : DeclaredVisualFact?
  after : DeclaredVisualFact?
  affected_subject_ids : Array[String]
}

///|
pub impl ToJson for ChangedFact with fn to_json(self) {
  {
    "id": self.id,
    "subject_id": self.subject_id,
    "property": self.property,
    "before": nullable_report_value(self.before),
    "after": nullable_report_value(self.after),
    "affected_subject_ids": self.affected_subject_ids,
  }
}

///|
/// Smallest independently reportable visual-semantic change.
///
/// Domain, evidence layers, computed relation, magnitude, and ordering remain
/// independent so representation and measured visual impact are not conflated.
pub(all) struct AtomicDifference {
  id : String
  subject_role : String
  subject_alignment_id : String?
  domain : String
  source_before : String
  source_after : String
  source_fact_before : DeclaredVisualFact?
  source_fact_after : DeclaredVisualFact?
  changed_fact_ids : Array[String]
  evidence_layers : Array[String]
  computed_relation : ComputedRelation
  parameter_delta_css_px : Double?
  presence_magnitude : PresenceMagnitude?
  magnitude : DifferenceMagnitude
  domain_ordering : DomainOrdering
}

///|
pub impl ToJson for AtomicDifference with fn to_json(self) {
  let fields : Map[String, Json] = Map([])
  fields["id"] = self.id.to_json()
  fields["subject_role"] = self.subject_role.to_json()
  fields["subject_alignment_id"] = nullable_report_value(
    self.subject_alignment_id,
  )
  fields["domain"] = self.domain.to_json()
  fields["source_before"] = self.source_before.to_json()
  fields["source_after"] = self.source_after.to_json()
  match self.source_fact_before {
    Some(value) => fields["source_fact_before"] = value.to_json()
    None => ()
  }
  match self.source_fact_after {
    Some(value) => fields["source_fact_after"] = value.to_json()
    None => ()
  }
  fields["changed_fact_ids"] = self.changed_fact_ids.to_json()
  fields["evidence_layers"] = self.evidence_layers.to_json()
  fields["computed_relation"] = self.computed_relation.to_json()
  match self.parameter_delta_css_px {
    Some(value) => fields["parameter_delta_css_px"] = value.to_json()
    None => ()
  }
  match self.presence_magnitude {
    Some(value) => fields["presence_magnitude"] = value.to_json()
    None => ()
  }
  fields["magnitude"] = self.magnitude.to_json()
  fields["domain_ordering"] = self.domain_ordering.to_json()
  Json::object(fields)
}

///|
/// Agent-facing grouping of Atomic Differences around one subject outcome.
pub(all) struct VisualEvent {
  id : String
  primary_subject_id : String
  atomic_difference_ids : Array[String]
  rendered_outcome : RenderedEvidence
  difference_regions : Array[DifferenceRegion]
} derive(ToJson)

///|
/// Common whole-canvas event measurements used by one Impact Assessment.
pub(all) struct ImpactMeasurement {
  changed_pixel_fraction : Double
  linear_premultiplied_rgba_rmse : Double
} derive(ToJson, Eq)

///|
/// One singleton or exact measured-tie group on the main-event frontier.
pub(all) struct ImpactFrontierGroup {
  event_ids : Array[String]
  atomic_difference_ids : Array[String]
  measurements : ImpactMeasurement?
} derive(Eq)

///|
pub impl ToJson for ImpactFrontierGroup with fn to_json(self) {
  let fields : Map[String, Json] = Map([])
  fields["event_ids"] = self.event_ids.to_json()
  fields["atomic_difference_ids"] = self.atomic_difference_ids.to_json()
  fields["measurements"] = nullable_report_value(self.measurements)
  Json::object(fields)
}

///|
/// One deterministic explanation for excluding a dominated event.
pub(all) struct ImpactDominationWitness {
  dominant_event_id : String
  dominated_event_id : String
  rule_id : String
} derive(ToJson, Eq)

///|
/// Additive versioned main-event assessment that preserves partial ordering.
pub(all) struct ImpactAssessment {
  policy_id : String
  status : String
  calibration_status : String
  input_fields : Array[String]
  candidate_event_count : Int
  frontier_relation : String
  frontier_groups : Array[ImpactFrontierGroup]
  domination_witnesses : Array[ImpactDominationWitness]
} derive(ToJson, Eq)

///|
/// Structured coverage limitation or analysis failure.
pub(all) struct Diagnostic {
  id : String
  code : String
  subject_id : String
  affected_evidence_layers : Array[String]
  source_locations : Array[DiagnosticSourceLocation]
} derive(ToJson)

///|
/// Coverage of one encountered feature at each canonical evidence layer.
///
/// Layer status is `covered`, `limited`, `not_applicable`, or `failed`.
/// Diagnostics explain every limited or failed row.
pub(all) struct FeatureCoverage {
  feature_id : String
  subject_id : String
  source_semantics : String
  computed_appearance : String
  rendered_evidence : String
  diagnostic_ids : Array[String]
} derive(ToJson)

///|
/// One renderer capability gap encountered by the current comparison.
///
/// `support_status` is `guarded` or `unavailable`. Diagnostic references remain
/// the authority for affected subjects and evidence layers.
pub(all) struct RendererCapabilityGap {
  capability_id : String
  support_status : String
  diagnostic_ids : Array[String]
} derive(ToJson)

///|
/// Complete schema `2.0` result of one SVG comparison.
///
/// Read `analysis_status` and `diagnostics` before interpreting an empty
/// `atomic_differences` array as profile-scoped equality.
pub(all) struct StructuredReport {
  schema_version : String
  analysis_status : String
  canvas_outcome : CanvasOutcome
  impact_assessment : ImpactAssessment
  coverage_matrix : Array[FeatureCoverage]
  renderer_capability_gaps : Array[RendererCapabilityGap]
  profile : ComparisonProfile
  subject_alignments : Array[SubjectAlignment]
  changed_facts : Array[ChangedFact]
  source_resolutions : Array[SourceResolutionPair]
  atomic_differences : Array[AtomicDifference]
  events : Array[VisualEvent]
  diagnostics : Array[Diagnostic]
}

///|
/// Serialize this report as indented schema `2.0` JSON.
pub fn StructuredReport::to_json_string(self : StructuredReport) -> String {
  ToJson::to_json(self).stringify(indent=2)
}

///|
/// Serialize this report as whitespace-free schema `2.0` JSON.
///
/// This serialization contains the same fields and evidence as
/// `to_json_string`; it only removes presentation whitespace.
pub fn StructuredReport::to_compact_json_string(
  self : StructuredReport,
) -> String {
  ToJson::to_json(self).stringify()
}