///|
/// Runtime/export targets that commonly consume Aseprite animation metadata.
pub(all) enum RuntimeTarget {
  TargetRuntimeManifest
  TargetCanvas2D
  TargetPhaser3
  TargetGenericEngine
} derive(Eq, @debug.Debug)

///|
pub fn RuntimeTarget::to_string(self : RuntimeTarget) -> String {
  match self {
    TargetRuntimeManifest => "runtime-manifest"
    TargetCanvas2D => "canvas2d"
    TargetPhaser3 => "phaser3"
    TargetGenericEngine => "generic-engine"
  }
}

///|
pub fn RuntimeTarget::expects_external_renderer(self : RuntimeTarget) -> Bool {
  match self {
    TargetRuntimeManifest | TargetCanvas2D | TargetPhaser3 => true
    TargetGenericEngine => false
  }
}

///|
pub fn RuntimeTarget::requires_box_metadata_by_default(
  self : RuntimeTarget,
) -> Bool {
  match self {
    TargetPhaser3 | TargetGenericEngine => true
    TargetRuntimeManifest | TargetCanvas2D => false
  }
}

///|
pub(all) struct ExportCompatibilityOptions {
  target : RuntimeTarget
  require_frame_tags : Bool
  require_layer_metadata : Bool
  require_slice_boxes : Bool
  require_hitbox : Bool
  require_hurtbox : Bool
  require_collision_box : Bool
  require_image_name : Bool
  allow_rotated_frames : Bool
  allow_trimmed_frames : Bool
  max_atlas_width : Int
  max_atlas_height : Int
  min_frame_duration_ms : Int
  max_frame_duration_ms : Int
} derive(Eq, @debug.Debug)

///|
pub fn ExportCompatibilityOptions::runtime_manifest() -> ExportCompatibilityOptions {
  {
    target: TargetRuntimeManifest,
    require_frame_tags: false,
    require_layer_metadata: false,
    require_slice_boxes: false,
    require_hitbox: false,
    require_hurtbox: false,
    require_collision_box: false,
    require_image_name: true,
    allow_rotated_frames: true,
    allow_trimmed_frames: true,
    max_atlas_width: 0,
    max_atlas_height: 0,
    min_frame_duration_ms: 1,
    max_frame_duration_ms: 5000,
  }
}

///|
pub fn ExportCompatibilityOptions::canvas2d() -> ExportCompatibilityOptions {
  {
    target: TargetCanvas2D,
    require_frame_tags: false,
    require_layer_metadata: false,
    require_slice_boxes: false,
    require_hitbox: false,
    require_hurtbox: false,
    require_collision_box: false,
    require_image_name: true,
    allow_rotated_frames: false,
    allow_trimmed_frames: true,
    max_atlas_width: 4096,
    max_atlas_height: 4096,
    min_frame_duration_ms: 16,
    max_frame_duration_ms: 2000,
  }
}

///|
pub fn ExportCompatibilityOptions::phaser3() -> ExportCompatibilityOptions {
  {
    target: TargetPhaser3,
    require_frame_tags: true,
    require_layer_metadata: false,
    require_slice_boxes: true,
    require_hitbox: true,
    require_hurtbox: true,
    require_collision_box: true,
    require_image_name: true,
    allow_rotated_frames: false,
    allow_trimmed_frames: true,
    max_atlas_width: 4096,
    max_atlas_height: 4096,
    min_frame_duration_ms: 16,
    max_frame_duration_ms: 2000,
  }
}

///|
pub fn ExportCompatibilityOptions::strict_game_asset() -> ExportCompatibilityOptions {
  {
    target: TargetGenericEngine,
    require_frame_tags: true,
    require_layer_metadata: true,
    require_slice_boxes: true,
    require_hitbox: true,
    require_hurtbox: true,
    require_collision_box: true,
    require_image_name: true,
    allow_rotated_frames: false,
    allow_trimmed_frames: false,
    max_atlas_width: 2048,
    max_atlas_height: 2048,
    min_frame_duration_ms: 16,
    max_frame_duration_ms: 1000,
  }
}

///|
pub fn check_export_compatibility(
  sheet : SpriteSheet,
  options? : ExportCompatibilityOptions = ExportCompatibilityOptions::phaser3(),
) -> ValidationReport {
  let issues : Array[ValidationIssue] = []
  check_export_meta(sheet, options, issues)
  check_export_frames(sheet, options, issues)
  check_export_tags(sheet, options, issues)
  check_export_boxes(sheet, options, issues)
  ValidationReport::new(issues)
}

///|
pub fn validate_sprite_sheet_json_for_export(
  input : String,
  options? : ExportCompatibilityOptions = ExportCompatibilityOptions::phaser3(),
) -> ValidationReport {
  let parsed = parse_sprite_sheet_json(input)
  if !parsed.ok {
    ValidationReport::new([
      ValidationIssue::error(
        parsed.error.message,
        path=parsed.error.path,
        code=parsed.error.code(),
      ),
    ])
  } else {
    merge_validation_reports(
      validate_sprite_sheet(parsed.sheet),
      check_export_compatibility(parsed.sheet, options~),
    )
  }
}

///|
pub fn merge_validation_reports(
  first : ValidationReport,
  second : ValidationReport,
) -> ValidationReport {
  let issues : Array[ValidationIssue] = []
  for issue in first.issues {
    issues.push(issue)
  }
  for issue in second.issues {
    issues.push(issue)
  }
  ValidationReport::new(issues)
}

///|
pub fn validation_report_has_code(
  report : ValidationReport,
  code : String,
) -> Bool {
  for issue in report.issues {
    if issue.code == code {
      return true
    }
  }
  false
}

///|
pub fn recommended_aseprite_export_flags(
  options? : ExportCompatibilityOptions = ExportCompatibilityOptions::phaser3(),
) -> String {
  if options.require_frame_tags && options.require_slice_boxes {
    "--batch --sheet  --data  --format json-array --list-tags --list-slices"
  } else if options.require_frame_tags {
    "--batch --sheet  --data  --format json-array --list-tags"
  } else if options.require_slice_boxes {
    "--batch --sheet  --data  --format json-array --list-slices"
  } else {
    "--batch --sheet  --data  --format json-array"
  }
}

///|
fn check_export_meta(
  sheet : SpriteSheet,
  options : ExportCompatibilityOptions,
  issues : Array[ValidationIssue],
) -> Unit {
  let target = options.target.to_string()
  if options.require_image_name && sheet.meta.image == "" {
    issues.push(
      ValidationIssue::error(
        "\{target} export needs meta.image to bind the texture atlas",
        path="$.meta.image",
        code="export-missing-image",
      ),
    )
  }
  if options.require_layer_metadata && sheet.meta.layers.length() == 0 {
    issues.push(
      ValidationIssue::warning(
        "\{target} policy expects layer metadata for authoring diagnostics",
        path="$.meta.layers",
        code="export-missing-layers",
      ),
    )
  }
  if options.max_atlas_width > 0 &&
    sheet.meta.size.width > options.max_atlas_width {
    issues.push(
      ValidationIssue::warning(
        "atlas width \{sheet.meta.size.width} exceeds \{target} policy limit \{options.max_atlas_width}",
        path="$.meta.size.w",
        code="export-atlas-too-wide",
      ),
    )
  }
  if options.max_atlas_height > 0 &&
    sheet.meta.size.height > options.max_atlas_height {
    issues.push(
      ValidationIssue::warning(
        "atlas height \{sheet.meta.size.height} exceeds \{target} policy limit \{options.max_atlas_height}",
        path="$.meta.size.h",
        code="export-atlas-too-tall",
      ),
    )
  }
}

///|
fn check_export_frames(
  sheet : SpriteSheet,
  options : ExportCompatibilityOptions,
  issues : Array[ValidationIssue],
) -> Unit {
  for i, frame in sheet.frames {
    let path = json_index_path("$.frames", i)
    if frame.rotated && !options.allow_rotated_frames {
      issues.push(
        ValidationIssue::warning(
          "\{options.target.to_string()} profile disables rotated frame export",
          path=json_field_path(path, "rotated"),
          code="export-rotated-frame",
        ),
      )
    }
    if frame.trimmed && !options.allow_trimmed_frames {
      issues.push(
        ValidationIssue::warning(
          "\{options.target.to_string()} profile expects untrimmed frame rectangles",
          path=json_field_path(path, "trimmed"),
          code="export-trimmed-frame",
        ),
      )
    }
    if options.min_frame_duration_ms > 0 &&
      frame.duration_ms < options.min_frame_duration_ms {
      issues.push(
        ValidationIssue::warning(
          "frame duration \{frame.duration_ms}ms is below export policy minimum \{options.min_frame_duration_ms}ms",
          path=json_field_path(path, "duration"),
          code="export-frame-too-fast",
        ),
      )
    }
    if options.max_frame_duration_ms > 0 &&
      frame.duration_ms > options.max_frame_duration_ms {
      issues.push(
        ValidationIssue::warning(
          "frame duration \{frame.duration_ms}ms exceeds export policy maximum \{options.max_frame_duration_ms}ms",
          path=json_field_path(path, "duration"),
          code="export-frame-too-slow",
        ),
      )
    }
  }
}

///|
fn check_export_tags(
  sheet : SpriteSheet,
  options : ExportCompatibilityOptions,
  issues : Array[ValidationIssue],
) -> Unit {
  if options.require_frame_tags && sheet.meta.frame_tags.length() == 0 {
    issues.push(
      ValidationIssue::error(
        "\{options.target.to_string()} export needs frameTags to create named animation clips",
        path="$.meta.frameTags",
        code="export-missing-frame-tags",
      ),
    )
  }
  for i, tag in sheet.meta.frame_tags {
    match tag.direction {
      DirectionUnknown(value) =>
        issues.push(
          ValidationIssue::warning(
            "unknown tag direction '\{value}' may need renderer-side mapping",
            path=json_field_path(
              json_index_path("$.meta.frameTags", i),
              "direction",
            ),
            code="export-unknown-direction",
          ),
        )
      DirectionPingPongReverse =>
        if options.target == TargetPhaser3 {
          issues.push(
            ValidationIssue::warning(
              "pingpong_reverse should be normalized before Phaser animation creation",
              path=json_field_path(
                json_index_path("$.meta.frameTags", i),
                "direction",
              ),
              code="export-phaser-pingpong-reverse",
            ),
          )
        }
      _ => ()
    }
  }
}

///|
fn check_export_boxes(
  sheet : SpriteSheet,
  options : ExportCompatibilityOptions,
  issues : Array[ValidationIssue],
) -> Unit {
  if options.require_slice_boxes && sheet.meta.slices.length() == 0 {
    issues.push(
      ValidationIssue::error(
        "\{options.target.to_string()} export needs slices for frame-level boxes",
        path="$.meta.slices",
        code="export-missing-slices",
      ),
    )
  }
  if options.require_hitbox ||
    options.require_hurtbox ||
    options.require_collision_box {
    let table = build_frame_box_table(sheet)
    if !has_required_box_kinds(
        table,
        require_hit=options.require_hitbox,
        require_hurt=options.require_hurtbox,
        require_collision=options.require_collision_box,
      ) {
      issues.push(
        ValidationIssue::error(
          "required hitbox/hurtbox/collision box kinds are not all present",
          path="$.meta.slices",
          code="export-missing-required-box-kind",
        ),
      )
    }
  }
}