import yaml from "js-yaml"

export interface OptionsData {
  ":ignore_for"?: string[]
  ":todo"?: string[]
  ":warning_todo"?: string[]
  ":precision"?: number
}

// A key for an option type represented by an array of supported implementations
export type OptionKey = ":ignore_for" | ":todo" | ":warning_todo"

/**
 * Represents the possible options of a sass-spec test case.
 */
export default class SpecOptions {
  private data: OptionsData
  constructor(data: OptionsData) {
    this.data = data
  }

  /** Create SpecOptions from yaml contents (as a string) */
  static fromYaml(content: string): SpecOptions {
    // TODO validate
    return new SpecOptions((yaml.safeLoad(content) ?? {}) as OptionsData)
  }

  /** Create new SpecOptions by merging this with other options */
  merge(other: SpecOptions): SpecOptions {
    // return the result of these options merged with other options
    const mergeOption = (option: OptionKey) => {
      return [...(this.data[option] ?? []), ...(other.data[option] ?? [])]
    }
    return new SpecOptions({
      ":ignore_for": mergeOption(":ignore_for"),
      ":todo": mergeOption(":todo"),
      ":warning_todo": mergeOption(":warning_todo"),
      ":precision": other.data[":precision"] ?? this.data[":precision"],
    })
  }

  /** Get the run mode of the given implementation */
  getMode(impl: string): "todo" | "ignore" | undefined {
    if (this.hasForImpl(impl, ":ignore_for")) {
      return "ignore"
    }
    if (this.hasForImpl(impl, ":todo")) {
      return "todo"
    }
  }

  /** Return whether this options object is :warning_todo for the given implementation */
  isWarningTodo(impl: string): boolean {
    return this.hasForImpl(impl, ":warning_todo")
  }

  private hasForImpl(impl: string, option: OptionKey): boolean {
    return !!this.data[option]?.some((item) => item.includes(impl))
  }

  /** Get the precision for this options object */
  precision(): number {
    return this.data[":precision"] ?? 10
  }

  /** Return these options modified to add the given impl to the given option key */
  addImpl(impl: string, optKey: OptionKey): SpecOptions {
    const newOption = [...(this.data[optKey] ?? []), impl]
    return new SpecOptions({ ...this.data, [optKey]: newOption })
  }

  /** Convert this options object to a Yaml string */
  toYaml(): string {
    return yaml.safeDump(this.data)
  }
}