src/gleeam_code/list_cmd.gleam

import gleam/int
import gleam/list
import gleam/result
import gleam/string
import gleeam_code/internal/file
import gleeam_code/internal/meta

pub type ProblemEntry {
  ProblemEntry(number: Int, slug: String, difficulty: String, status: String)
}

pub type Filter {
  Filter(difficulty: List(String), solved: Option, unsolved: Option)
}

pub type Option {
  On
  Off
}

pub fn parse_filters(args: List(String)) -> Filter {
  do_parse_filters(args, Filter(difficulty: [], solved: Off, unsolved: Off))
}

fn do_parse_filters(args: List(String), acc: Filter) -> Filter {
  case args {
    [] -> acc
    ["--easy", ..rest] ->
      do_parse_filters(
        rest,
        Filter(..acc, difficulty: ["Easy", ..acc.difficulty]),
      )
    ["--medium", ..rest] ->
      do_parse_filters(
        rest,
        Filter(..acc, difficulty: ["Medium", ..acc.difficulty]),
      )
    ["--hard", ..rest] ->
      do_parse_filters(
        rest,
        Filter(..acc, difficulty: ["Hard", ..acc.difficulty]),
      )
    ["--solved", ..rest] -> do_parse_filters(rest, Filter(..acc, solved: On))
    ["--unsolved", ..rest] ->
      do_parse_filters(rest, Filter(..acc, unsolved: On))
    [_, ..rest] -> do_parse_filters(rest, acc)
  }
}

pub fn apply_filters(
  problems: List(ProblemEntry),
  filter: Filter,
) -> List(ProblemEntry) {
  problems
  |> filter_by_difficulty(filter.difficulty)
  |> filter_by_status(filter.solved, filter.unsolved)
}

fn filter_by_difficulty(
  problems: List(ProblemEntry),
  difficulties: List(String),
) -> List(ProblemEntry) {
  case difficulties {
    [] -> problems
    _ ->
      list.filter(problems, fn(p) { list.contains(difficulties, p.difficulty) })
  }
}

fn filter_by_status(
  problems: List(ProblemEntry),
  solved: Option,
  unsolved: Option,
) -> List(ProblemEntry) {
  case solved, unsolved {
    On, Off -> list.filter(problems, fn(p) { p.status == "Accepted" })
    Off, On -> list.filter(problems, fn(p) { p.status != "Accepted" })
    _, _ -> problems
  }
}

pub fn run(
  base_dir: String,
  args: List(String),
  print: fn(String) -> Nil,
) -> Result(Nil, String) {
  let filter = parse_filters(args)
  let solutions_dir = base_dir <> "/src/solutions"
  case file.dir_exists(solutions_dir) {
    False -> {
      print("No solutions found. Run `glc fetch` first.")
      Ok(Nil)
    }
    True -> {
      use entries <- result.try(
        file.list_directory(solutions_dir)
        |> result.map_error(fn(err) {
          "Failed to read solutions directory: " <> file.describe_error(err)
        }),
      )

      let problems =
        entries
        |> list.filter_map(fn(entry) { parse_entry(solutions_dir, entry) })
        |> list.sort(fn(a, b) { int.compare(a.number, b.number) })
        |> apply_filters(filter)

      case problems {
        [] -> {
          print("No solutions found. Run `glc fetch` first.")
          Ok(Nil)
        }
        _ -> {
          print(format_header())
          list.each(problems, fn(p) { print(format_row(p)) })
          Ok(Nil)
        }
      }
    }
  }
}

fn parse_entry(
  solutions_dir: String,
  dir_name: String,
) -> Result(ProblemEntry, Nil) {
  let solution_path = solutions_dir <> "/" <> dir_name <> "/solution.gleam"
  let meta_path = solutions_dir <> "/" <> dir_name <> "/.glc_meta"
  case file.read(solution_path) {
    Ok(content) -> parse_solution_header(content, dir_name, meta_path)
    Error(_) -> Error(Nil)
  }
}

fn parse_solution_header(
  content: String,
  dir_name: String,
  meta_path: String,
) -> Result(ProblemEntry, Nil) {
  let lines = string.split(content, "\n")
  use number <- result.try(extract_number(lines))
  let slug = extract_slug(dir_name)
  let difficulty = extract_difficulty(lines)
  let status = read_status(meta_path)
  Ok(ProblemEntry(
    number: number,
    slug: slug,
    difficulty: difficulty,
    status: status,
  ))
}

fn extract_number(lines: List(String)) -> Result(Int, Nil) {
  case lines {
    [] -> Error(Nil)
    [line, ..rest] ->
      case string.starts_with(line, "//// Problem ") {
        True -> {
          let after = string.drop_start(line, string.length("//// Problem "))
          case string.split_once(after, ":") {
            Ok(#(num_str, _)) ->
              num_str |> string.trim |> int.parse |> result.replace_error(Nil)
            Error(_) -> Error(Nil)
          }
        }
        False -> extract_number(rest)
      }
  }
}

fn extract_slug(dir_name: String) -> String {
  case string.split_once(dir_name, "_") {
    Ok(#(_, rest)) -> string.replace(rest, "_", "-")
    Error(_) -> dir_name
  }
}

fn extract_difficulty(lines: List(String)) -> String {
  case lines {
    [] -> "?"
    [line, ..rest] ->
      case string.starts_with(line, "//// Difficulty: ") {
        True ->
          string.drop_start(line, string.length("//// Difficulty: "))
          |> string.trim
        False -> extract_difficulty(rest)
      }
  }
}

fn read_status(meta_path: String) -> String {
  case file.read(meta_path) {
    Ok(content) -> meta.find_value(string.split(content, "\n"), "status")
    Error(_) -> ""
  }
}

fn format_header() -> String {
  pad_right("#", 5)
  <> pad_right("Slug", 30)
  <> pad_right("Difficulty", 12)
  <> "Status"
}

fn format_row(entry: ProblemEntry) -> String {
  pad_right(int.to_string(entry.number), 5)
  <> pad_right(entry.slug, 30)
  <> pad_right(entry.difficulty, 12)
  <> format_status(entry.status)
}

fn format_status(status: String) -> String {
  case status {
    "Accepted" -> "✓ Accepted"
    "" -> ""
    other -> "✗ " <> other
  }
}

fn pad_right(s: String, width: Int) -> String {
  let len = string.length(s)
  case len >= width {
    True -> s
    False -> s <> string.repeat(" ", width - len)
  }
}