///|
/// Conditions are preserved without selecting a compilation backend.
#derive(Debug, Eq, ToJson)
pub(all) enum Condition {
  Bool(Bool)
  Target(String)
  All(Array[Condition])
  Any(Array[Condition])
  Not(Condition)
}

///|
/// The original attribute is retained even if its payload is invalid.
#derive(Debug, Eq)
pub(all) struct ImportCondition {
  raw : String
  parsed : Condition?
  loc : Location
}

///|
fn Condition::is_valid(self : Self) -> Bool {
  match self {
    Bool(_) => true
    Target("wasm" | "wasm-gc" | "js" | "native" | "llvm") => true
    Target(_) => false
    All(args) | Any(args) => args.all(arg => arg.is_valid())
    Not(arg) => arg.is_valid()
  }
}

///|
fn parse_condition(state : State) -> Condition? {
  match state.consume().0 {
    TRUE => Some(Bool(true))
    FALSE => Some(Bool(false))
    LIDENT("target") => {
      guard state.consume_if(TK_EQUAL) else { return None }
      match state.peek_token() {
        STRING(value) => {
          state.skip()
          Some(Target(value))
        }
        _ => None
      }
    }
    LIDENT("all" | "any" | "not" as name) => {
      guard state.consume_if(TK_LPAREN) else { return None }
      let args : Array[Condition] = []
      if !state.consume_if(TK_RPAREN) {
        while true {
          guard state.peek_kind() != TK_EOF else { return None }
          guard parse_condition(state) is Some(arg) else { return None }
          args.push(arg)
          if state.consume_if(TK_RPAREN) {
            break
          }
          guard state.consume_if(TK_COMMA) else { return None }
        }
      }
      match name {
        "all" => Some(All(args))
        "any" => Some(Any(args))
        _ => if args is [arg] { Some(Not(arg)) } else { None }
      }
    }
    _ => None
  }
}

///|
fn parse_condition_payload(payload : String) -> Condition? {
  let lexed = @lexer.tokens_from_string(payload, comment=false)
  guard lexed.errors.is_empty() else { return None }
  let state : State = {
    tokens: lexed.tokens,
    diagnostics: [],
    next: 0,
    parsed_position: dummy_pos,
  }
  guard state.consume_if(TK_LPAREN) && state.peek_kind() != TK_EOF else {
    return None
  }
  let parsed = parse_condition(state)
  guard state.consume_if(TK_RPAREN) else { return None }
  guard state.peek_kind() == TK_EOF else { return None }
  parsed
}

///|
pub extend Condition with Debug::{to_repr}

///|
pub extend Condition with Eq::{equal, not_equal}

///|
pub extend Condition with ToJson::{to_json}

///|
pub extend ImportCondition with Debug::{to_repr}

///|
pub extend ImportCondition with Eq::{equal, not_equal}