///|
pub struct VariantChoice {
  value : String
  rules : Array[Rule]
}

///|
pub fn variant_choice(value : String, rules : Array[Rule]) -> VariantChoice {
  { value, rules: rules.copy(), }
}

///|
pub struct VariantSelection {
  axis : String
  value : String
}

///|
pub fn variant_is(axis : String, value : String) -> VariantSelection {
  { axis, value, }
}

///|
pub fn boolean_variant_is(axis : String, enabled : Bool) -> VariantSelection {
  { axis, value: if enabled { "true" } else { "false" }, }
}

///|
struct RecipeAxis {
  name : String
  choices : Array[VariantChoice]
  default_value : String?
  boolean : Bool
}

///|
struct RecipeCompound {
  matches : Array[VariantSelection]
  rules : Array[Rule]
}

///|
pub struct Recipe {
  name : String
  base : Array[Rule]
  variants : Array[RecipeAxis]
  compounds : Array[RecipeCompound]
  contract : String?
}

///|
pub fn recipe_rule() -> Rule {
  rule("recipe")
}

///|
fn Rule::recipe_named(self : Rule, name : String) -> Rule {
  { name, declarations: self.declarations, variables: self.variables, }
}

///|
fn recipe_rules_named(rules : Array[Rule], prefix : String) -> Array[Rule] {
  let output : Array[Rule] = []
  for index, authored in rules {
    output.push(authored.recipe_named(prefix + "_" + index.to_string()))
  }
  output
}

///|
pub fn recipe(name : String, base : Array[Rule]) -> Recipe {
  {
    name,
    base: recipe_rules_named(base, name + "__base"),
    variants: [],
    compounds: [],
    contract: None,
  }
}

///|
pub fn Recipe::variant(
  self : Recipe,
  axis : String,
  choices : Array[VariantChoice],
  default~ : String? = None,
) -> Recipe {
  let variants = self.variants.copy()
  let normalized = choices.map(choice => {
    value: choice.value,
    rules: recipe_rules_named(
      choice.rules,
      self.name + "__" + axis + "__" + choice.value,
    ),
  })
  variants.push({
    name: axis,
    choices: normalized,
    default_value: default,
    boolean: false,
  })
  { ..self, variants, }
}

///|
pub fn Recipe::boolean_variant(
  self : Recipe,
  axis : String,
  enabled : Array[Rule],
  disabled~ : Array[Rule] = [],
  default~ : Bool? = None,
) -> Recipe {
  let variants = self.variants.copy()
  variants.push({
    name: axis,
    choices: [
      {
        value: "true",
        rules: recipe_rules_named(enabled, self.name + "__" + axis + "__true"),
      },
      {
        value: "false",
        rules: recipe_rules_named(disabled, self.name + "__" + axis + "__false"),
      },
    ],
    default_value: default.map(value => if value { "true" } else { "false" }),
    boolean: true,
  })
  { ..self, variants, }
}

///|
pub fn Recipe::compound(
  self : Recipe,
  matches : Array[VariantSelection],
  rules : Array[Rule],
) -> Recipe {
  let compounds = self.compounds.copy()
  compounds.push({
    matches: matches.copy(),
    rules: recipe_rules_named(
      rules,
      self.name + "__compound__" + compounds.length().to_string(),
    ),
  })
  { ..self, compounds, }
}

///|
pub fn Recipe::for_contract(self : Recipe, contract : StyleContract) -> Recipe {
  { ..self, contract: Some(contract.name), }
}

///|
pub fn Recipe::rules(self : Recipe) -> Array[Rule] {
  let output = self.base.copy()
  for axis in self.variants {
    for choice in axis.choices {
      for authored in choice.rules {
        output.push(authored)
      }
    }
  }
  for compound in self.compounds {
    for authored in compound.rules {
      output.push(authored)
    }
  }
  output
}

///|
pub fn Recipe::to_ir(self : Recipe) -> @ir.Recipe {
  {
    name: self.name,
    base: self.base.map(authored => authored.name),
    variants: self.variants.map(axis => {
      name: axis.name,
      choices: axis.choices.map(choice => {
        value: choice.value,
        rules: choice.rules.map(authored => authored.name),
      }),
      default_value: axis.default_value,
      boolean: axis.boolean,
    }),
    compounds: self.compounds.map(compound => {
      matches: compound.matches.map(selection => {
        axis: selection.axis,
        value: selection.value,
      }),
      rules: compound.rules.map(authored => authored.name),
    }),
    contract: self.contract,
  }
}