// Levels, groups, and the -W policy.

///|
/// What to do with a warning when it is reported.
pub(all) enum Level {
  /// Suppress it entirely.
  Hidden
  /// Report it as a warning. The usual default.
  Displayed
  /// Promote it to an error, failing the run.
  ErrorLevel
} derive(Eq, Debug)

///|
/// Group name to members.
///
/// The special group `all` is handled by `set` and so is not listed. Note that
/// `correctness` deliberately overlaps `unused`: a field or label nobody uses
/// is both an unused binding and a correctness signal.
let group_table : Array[(String, Array[Warning])] = [
  ("unused", [UnusedLocal, UnusedField, UnusedImport, UnusedLabel]),
  (
    "correctness",
    [
      ShiftOverflow,
      ConstantTrap,
      TautologicalComparison,
      ConstantCondition,
      UnusedResult,
      DeadCode,
      CastAlwaysFails,
      EagerSelect,
      Precedence,
      UnusedField,
      UnusedImport,
      UnusedLabel,
      ConfusableUnicode,
    ],
  ),
  ("redundant", [RedundantOperation, UnnecessaryMut]),
  ("naming", [NamingConflict, ReservedWordRename, GeneratedName]),
  ("suggestion", [CompoundAssignment, FieldPunning, RedundantAnnotation]),
]

///|
/// The group names, for help text. Excludes the special `all`.
pub fn groups() -> Array[String] {
  group_table.map(g => g.0)
}

///|
/// A mapping from each warning to its level.
///
/// A function rather than a table, so `set` returns an updated closure and
/// later assignments naturally override earlier ones.
pub(all) struct Policy {
  level : (Warning) -> Level
}

///|
/// The level each warning has when nothing has configured it.
///
/// The from-wasm renaming warnings are noisy round-trip notices, and
/// redundant-operation and the suggestion group are optimisation and style
/// hints common in generated code -- all hidden unless `-W` asks for them.
/// Everything else is shown.
pub let default_policy : Policy = {
  level: w => {
    match w {
      NamingConflict
      | ReservedWordRename
      | GeneratedName
      | RedundantOperation
      | CompoundAssignment
      | FieldPunning
      | RedundantAnnotation => Hidden
      _ => Displayed
    }
  },
}

///|
pub fn Policy::resolve(self : Policy, w : Warning) -> Level {
  (self.level)(w)
}

///|
/// The warnings a name refers to: the special group `all`, one warning by
/// name, or a named group.
fn targets(target : String) -> Array[Warning]? {
  if target == "all" {
    return Some(all)
  }
  for w in all {
    if w.name() == target {
      return Some([w])
    }
  }
  for g in group_table {
    if g.0 == target {
      return Some(g.1)
    }
  }
  None
}

///|
/// `policy` updated so `target` -- a warning name, a group, or `all` -- has
/// `level`. Later calls override earlier ones.
pub fn Policy::set(
  self : Policy,
  target : String,
  level : Level,
) -> Result[Policy, String] {
  match targets(target) {
    Some(ws) =>
      Ok({
        level: w => {
          if ws.iter().any(x => x == w) {
            level
          } else {
            self.resolve(w)
          }
        },
      })
    None => {
      let known = all.map(w => w.name())
      known.append(groups())
      known.push("all")
      Err(
        "Unknown warning or group '\{target}'. Known names: \{known.join(", ")}.",
      )
    }
  }
}

///|
fn level_of_string(s : String) -> Level? {
  match s {
    "hidden" => Some(Hidden)
    "warning" => Some(Displayed)
    "error" => Some(ErrorLevel)
    _ => None
  }
}

///|
/// Parse a `-W` argument, `NAME=LEVEL`.
pub fn parse_spec(s : String) -> Result[(String, Level), String] {
  match s.find("=") {
    None =>
      Err(
        "Malformed warning spec '\{s}'; expected NAME=LEVEL (LEVEL is hidden, warning, or error).",
      )
    Some(i) => {
      let name = s.view(end_offset=i).to_owned()
      let level = s.view(start_offset=i + 1).to_owned()
      match level_of_string(level) {
        Some(l) => Ok((name, l))
        None =>
          Err(
            "Unknown warning level '\{level}'; expected hidden, warning, or error.",
          )
      }
    }
  }
}