import Parser.*

enum ConfigValue:
  case CNumber(value: Int)
  case CBool(value: Boolean)
  case CString(value: String)

case class Entry(key: String, value: ConfigValue):
  override def toString: String = value match
    case ConfigValue.CNumber(n) => s"$key = $n (number)"
    case ConfigValue.CBool(b)   => s"$key = $b (bool)"
    case ConfigValue.CString(s) => s"$key = $s (string)"

case class Config(entries: List[Entry])

object Config:

  // A key is one or more letters, digits, or underscores: "host", "port_num"
  val key: Parser[String] =
    oneOrMore(charWhere(c => c.isLetterOrDigit || c == '_', "key character"))
      .map(_.mkString)

  // Optional horizontal whitespace (spaces and tabs, NOT newlines)
  val spaces: Parser[Unit] =
    zeroOrMore(charWhere(c => c == ' ' || c == '\t', "whitespace"))
      .map(_ => ())

  // The separator between key and value: optional spaces, '=', optional spaces
  val separator: Parser[Unit] =
    for
      _ <- spaces
      _ <- char('=')
      _ <- spaces
    yield ()

  // A number: one or more digits → CNumber
  val numberValue: Parser[ConfigValue] =
    oneOrMore(digit)
      .map(chars => ConfigValue.CNumber(chars.mkString.toInt))

  // A boolean: literal "true" or "false" → CBool
  val boolValue: Parser[ConfigValue] =
    string("true")
      .map(_ => ConfigValue.CBool(true))
      .orElse(string("false").map(_ => ConfigValue.CBool(false)))

  // A string: everything until newline or end of input → CString
  val stringValue: Parser[ConfigValue] =
    oneOrMore(charWhere(c => c != '\n' && c != '\r', "value character"))
      .map(chars => ConfigValue.CString(chars.mkString.trim))

  // A value is: try number first, then bool, then fall back to string
  val value: Parser[ConfigValue] =
    numberValue orElse boolValue orElse stringValue

  // A single entry: key = value (with optional trailing whitespace)
  val entry: Parser[Entry] =
    for
      k <- key
      _ <- separator
      v <- value
      _ <- spaces // consume any trailing spaces before newline
    yield Entry(k, v)

  // A newline character
  val newline: Parser[Char] = char('\n')

  // Blank lines or trailing whitespace between entries
  val lineBreaks: Parser[Unit] =
    zeroOrMore(newline).map(_ => ())

  // A full config: multiple entries separated by newlines
  val parser: Parser[Config] =
    for
      _     <- lineBreaks                   // skip leading blank lines
      first <- entry                        // at least one entry
      rest  <- zeroOrMore(newline ~> entry) // more entries after newlines
      _     <- lineBreaks                   // skip trailing blank lines
      _     <- eof
    yield Config(first :: rest)

@main def runConfigParser(): Unit =
  val input =
    """|host = localhost
       |port = 8080
       |debug = true
       |max_retries = 3
       |app_name = my_cool_app""".stripMargin

  println(s"Input:\n$input\n")

  Config.parser.parse(input) match
    case ParseResult.Success(config, _) =>
      println(s"Parsed ${config.entries.size} entries:\n${config.entries.mkString("\n")}")
      for entry <- config.entries do println(s"  $entry")
    case ParseResult.Failure(msg) =>
      println(s"Parse error: $msg")
