///|
/// A declared interpretation target, never automatic version detection.
pub(all) enum StandardEdition {
  Liberty2017_06
} derive(Eq, Debug)

///|
pub fn StandardEdition::label(self : StandardEdition) -> String {
  match self {
    Liberty2017_06 => "Liberty 2017.06"
  }
}

///|
pub fn parse_standard(text : String) -> StandardEdition raise LibertyError {
  match text {
    "2017.06" | "liberty-2017.06" => Liberty2017_06
    _ =>
      raise failure(
        "UnsupportedStandard",
        "supported target: Liberty 2017.06",
        no_position(),
      )
  }
}

///|
pub(all) struct FormatFeature {
  family : String
  occurrences : Int32
  evaluation : String
} derive(Eq, Debug)

///|
pub(all) struct CompatibilityReport {
  standard : StandardEdition
  library : String
  findings : Array[Finding]
  features : Array[FormatFeature]
  roundtrip_verified : Bool
  complete_conformance : Bool
} derive(Debug)

///|
fn declaration_argument(
  arguments : Array[Array[Atom]],
  index : Int32,
) -> String? {
  if index >= arguments.length() {
    return None
  }
  match arguments[index] {
    [atom] => Some(atom_text(atom))
    _ => None
  }
}

///|
fn collect_declarations(
  root : Group,
  attrs : Map[String, Map[String, String]],
  groups : Map[String, String],
  findings : Array[Finding],
) -> Unit {
  for item in root.items {
    match item {
      Complex("define", args, pos) => {
        let name = declaration_argument(args, 0).unwrap_or("")
        let parent = declaration_argument(args, 1).unwrap_or("")
        let value_type = declaration_argument(args, 2).unwrap_or("")
        if args.length() != 3 ||
          name.is_empty() ||
          parent.is_empty() ||
          !["boolean", "string", "integer", "float"].contains(value_type) {
          add_finding(
            findings, "InvalidDefine", "/library/define", "define needs name, group and boolean/string/integer/float type",
            pos,
          )
        } else {
          let parents = attrs.get(name).unwrap_or(Map([]))
          if parents.contains(parent) {
            add_finding(
              findings,
              "DuplicateDefine",
              "/library/define/" + name,
              "attribute definition repeated",
              pos,
            )
          } else {
            parents[parent] = value_type
            attrs[name] = parents
          }
        }
      }
      Complex("define_group", args, pos) => {
        let name = declaration_argument(args, 0).unwrap_or("")
        let parent = declaration_argument(args, 1).unwrap_or("")
        if args.length() != 2 || name.is_empty() || parent.is_empty() {
          add_finding(
            findings, "InvalidDefineGroup", "/library/define_group", "define_group needs group and parent",
            pos,
          )
        } else if groups.contains(name) {
          add_finding(
            findings,
            "DuplicateDefineGroup",
            "/library/define_group/" + name,
            "group definition repeated",
            pos,
          )
        } else {
          groups[name] = parent
        }
      }
      _ => ()
    }
  }
}

///|
fn attribute_value_valid(value_type : String, atoms : Array[Atom]) -> Bool {
  guard atoms is [atom] else { return false }
  let text = atom_text(atom)
  match value_type {
    "string" => atom is Text(_)
    "boolean" => text == "true" || text == "false"
    "integer" => {
      let start = if text.has_prefix("+") || text.has_prefix("-") {
        1
      } else {
        0
      }
      if text.length() == start {
        return false
      }
      for i = start; i < text.length(); i = i + 1 {
        if text[i] < 48 || text[i] > 57 {
          return false
        }
      }
      true
    }
    "float" => {
      ignore(finite_number(text)) catch {
        _ => return false
      }
      true
    }
    _ => false
  }
}

///|
/// Recognition here is for coverage reporting, not a complete 2017.06 schema.
fn feature_family(kind : String) -> (String, String) {
  if ["bus", "bundle", "type"].contains(kind) {
    ("bus_bundle", "preserved_only")
  } else if ["ff", "ff_bank", "latch", "latch_bank", "statetable"].contains(
      kind,
    ) {
    ("sequential", "preserved_only")
  } else if kind.has_prefix("ocv_") {
    ("variation_lvf", "preserved_only")
  } else if kind.has_prefix("output_current") ||
    kind.has_prefix("ccs_") ||
    [
      "vector", "receiver_capacitance", "receiver_capacitance1_rise", "receiver_capacitance1_fall",
    ].contains(kind) {
    ("current_waveform", "preserved_only")
  } else if [
      "internal_power", "leakage_power", "rise_power", "fall_power", "power_lut_template",
    ].contains(kind) {
    ("power", "preserved_only")
  } else if ["cell_rise", "cell_fall", "rise_transition", "fall_transition"].contains(
      kind,
    ) {
    ("nldm_combinational", "eligible_after_query_checks")
  } else if ["rise_constraint", "fall_constraint"].contains(kind) {
    ("nldm_constraint", "table_lookup_only")
  } else if [
      "library", "cell", "pin", "pg_pin", "timing", "lu_table_template", "operating_conditions",
      "wire_load", "wire_load_selection", "scaled_cell", "input_voltage", "output_voltage",
    ].contains(kind) {
    ("library_structure", "model_specific")
  } else {
    ("unclassified_extension", "preserved_only")
  }
}

///|
fn inspect_format(
  group : Group,
  parent : String,
  path : String,
  attrs : Map[String, Map[String, String]],
  groups : Map[String, String],
  counts : Map[String, Int32],
  modes : Map[String, String],
  findings : Array[Finding],
) -> Unit {
  let (family, mode) = feature_family(group.kind)
  counts[family] = counts.get(family).unwrap_or(0) + 1
  modes[family] = mode
  if family == "unclassified_extension" {
    let defined = groups.contains(group.kind)
    if !defined {
      add_finding(
        findings,
        "UnclassifiedGroup",
        path,
        "group retained; no complete standard/schema classification is claimed",
        group.position,
        severity=Warning,
      )
    }
  }
  if groups.get(group.kind) is Some(declared_parent) &&
    declared_parent != parent {
    add_finding(
      findings,
      "DefinedGroupParent",
      path,
      "declared parent is " + declared_parent,
      group.position,
    )
  }
  for item in group.items {
    match item {
      Section(child) =>
        inspect_format(
          child,
          group.kind,
          path + "/" + child.kind + "(" + child.name() + ")",
          attrs,
          groups,
          counts,
          modes,
          findings,
        )
      Attribute(name, atoms, pos) =>
        if attrs.get(name) is Some(parents) {
          match parents.get(group.kind) {
            Some(value_type) =>
              if !attribute_value_valid(value_type, atoms) {
                add_finding(
                  findings,
                  "DefinedAttributeType",
                  path + "/" + name,
                  "expected " + value_type,
                  pos,
                )
              }
            None =>
              add_finding(
                findings,
                "DefinedAttributeParent",
                path + "/" + name,
                "user attribute used outside declared group",
                pos,
              )
          }
        }
      Complex("include_file", args, pos) => {
        ignore(include_name(args, pos)) catch {
          error => {
            findings.push(finding(error, path + "/include_file"))
            continue
          }
        }
        add_finding(
          findings,
          "UnresolvedInclude",
          path + "/include_file",
          "supply an explicit source map before checking expanded structure",
          pos,
        )
      }
      Complex(name, _, pos) =>
        if attrs.contains(name) {
          add_finding(
            findings,
            "DefinedAttributeForm",
            path + "/" + name,
            "declared simple attributes require name : value;",
            pos,
          )
        }
    }
  }
}

///|
/// Check implemented format rules and report separate format/evaluation coverage.
/// complete_conformance remains false until the full edition checklist is closed.
pub fn compatibility_report(
  root : Group,
  standard~ : StandardEdition = Liberty2017_06,
) -> CompatibilityReport raise LibertyError {
  ignore(write_liberty(root))
  let attrs : Map[String, Map[String, String]] = Map([])
  let groups : Map[String, String] = Map([])
  let findings = []
  collect_declarations(root, attrs, groups, findings)
  findings.append(collect_standard_findings(root))
  let counts : Map[String, Int32] = Map([])
  let modes : Map[String, String] = Map([])
  inspect_format(
    root,
    "",
    "/library(" + root.name() + ")",
    attrs,
    groups,
    counts,
    modes,
    findings,
  )
  let keys = counts.keys().to_array()
  keys.sort_by(lexical_compare)
  let features = keys.map(key => {
    (
      { family: key, occurrences: counts[key], evaluation: modes[key], } :
      FormatFeature)
  })
  {
    standard,
    library: root.name(),
    findings,
    features,
    roundtrip_verified: true,
    complete_conformance: false,
  }
}

///|
pub fn compatibility_json(report : CompatibilityReport) -> Json {
  let errors = report.findings.filter(f => f.severity == Error).length()
  {
    "schema": "moonliberty.compatibility.v1",
    "target_standard": Json(report.standard.label()),
    "version_detection": "caller_declared",
    "library": Json(report.library),
    "implemented_checks_passed": Json(errors == 0),
    "complete_standard_conformance": Json(report.complete_conformance),
    "roundtrip_verified": Json(report.roundtrip_verified),
    "implemented_rule_ids": Json(standard_rule_ids()),
    "rule_inventory_exhaustive": false,
    "errors": Json(errors),
    "warnings": Json(report.findings.length() - errors),
    "features": Json(
      report.features.map(feature => {
        (
          {
            "family": Json(feature.family),
            "occurrences": Json(feature.occurrences),
            "evaluation": Json(feature.evaluation),
          } : Json)
      }),
    ),
    "findings": findings_json(report.findings),
    "conformance_contract": "docs/STANDARD_CONFORMANCE.md",
  }
}