-- tools/docs/gen_stdlib_ref.rail — Walk stdlib/*.rail, extract top-level
-- function signatures with preceding line comments, emit docs/site/stdlib.md.
--
-- A "top-level function" here is any non-comment, non-indented line whose
-- text starts with an identifier and contains `=` before any `--`. Lines
-- starting with reserved heads (type, import, foreign, --) are skipped.
-- Preceding `-- ...` lines are captured as the docstring.
--
-- Usage:
--   ./rail_native run tools/docs/gen_stdlib_ref.rail
-- Output:
--   docs/site/stdlib.md

cat parts = join "" parts
trim_nl s = head (split "\n" s)

-- ── classify a line ──
-- Returns one of: "blank" "comment" "indent" "decl" "other".
-- A "decl" looks like a top-level function or value: first column is a
-- letter/underscore, the line contains "=" before any "--", and the head
-- token is not one of the reserved keywords.

is_blank_line s =
  let t = trim_left s
  length t == 0

is_comment_line s =
  let t = trim_left s
  if length t < 2 then false
  else str_sub t 0 2 == "--"

is_indented_line s =
  if length s == 0 then false
  else
    let c = str_sub s 0 1
    c == " " || c == "\t"

trim_left s =
  if length s == 0 then s
  else
    let c = str_sub s 0 1
    if c == " " then trim_left (str_sub s 1 (length s - 1))
    else if c == "\t" then trim_left (str_sub s 1 (length s - 1))
    else s

-- Find an `=` not inside a comment. Match " = " (interior) or " =" (EOL),
-- not "==", "<=", ">=", ":=" or "/=".
eq_before_comment s =
  let cut = str_find "--" s
  let raw = trim_right s
  let n = length raw
  if n == 0 then -1
  else
    let tail2 = str_sub raw (n - 2) 2
    let eq_inner = str_find " = " s
    if tail2 == " =" then
      let eol_eq = n - 1
      if cut < 0 then eol_eq
      else if eol_eq < cut then eol_eq
      else if eq_inner >= 0 && eq_inner < cut then eq_inner
      else -1
    else if eq_inner < 0 then -1
    else if cut < 0 then eq_inner
    else if eq_inner < cut then eq_inner
    else -1

first_token s =
  let n = length s
  first_token_loop s 0 n

first_token_loop s i n =
  if i >= n then str_sub s 0 n
  else
    let c = str_sub s i 1
    if c == " " then str_sub s 0 i
    else if c == "=" then str_sub s 0 i
    else if c == "(" then str_sub s 0 i
    else first_token_loop s (i + 1) n

is_reserved_head tok =
  tok == "type" || tok == "import" || tok == "foreign" || tok == "main"

classify_line s =
  if is_blank_line s then "blank"
  else if is_comment_line s then "comment"
  else if is_indented_line s then "indent"
  else
    let eq = eq_before_comment s
    if eq < 0 then "other"
    else
      let tok = first_token s
      if length tok == 0 then "other"
      else if is_reserved_head tok then "other"
      else
        -- first char must be a letter or underscore
        let c0 = str_sub tok 0 1
        if alpha_or_under c0 then "decl"
        else "other"

alpha_or_under c =
  if c == "_" then true
  else str_find c "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" >= 0

-- Pull the signature substring: everything before " = " (or " =" at EOL).
sig_of_line s =
  let eq = eq_before_comment s
  if eq < 0 then trim_right s
  else trim_right (str_sub s 0 eq)

trim_right s =
  let n = length s
  if n == 0 then s
  else
    let c = str_sub s (n - 1) 1
    if c == " " then trim_right (str_sub s 0 (n - 1))
    else if c == "\t" then trim_right (str_sub s 0 (n - 1))
    else if c == "\r" then trim_right (str_sub s 0 (n - 1))
    else s

strip_dashes s =
  let t = trim_left s
  if length t < 2 then t
  else if str_sub t 0 2 == "--" then trim_left (str_sub t 2 (length t - 2))
  else t

-- ── per-file extraction ──
-- Walk lines; maintain a "pending comments" buffer that resets on any
-- non-comment, non-blank line. On a "decl" line, emit one entry.

-- Strip CR bytes via shell (Rail's "\r" literal is backslash+r, not a real CR).
strip_cr path =
  let tmp = "/tmp/_stdlib_lf.txt"
  let _ = shell (cat ["tr -d '\\r' < ", path, " > ", tmp])
  read_file tmp

extract_file out_path stdlib_path =
  let raw = strip_cr stdlib_path
  if length raw == 0 then 0
  else
    let lines = split "\n" raw
    let _ = w out_path (cat ["\n## `", stdlib_path, "`\n\n"])
    let n_before = count_fns lines [] 0
    if n_before == 0 then
      let _ = w out_path "_(no top-level functions detected)_\n"
      0
    else
      let _ = walk_lines out_path lines []
      0

count_fns lines pending acc =
  if length lines == 0 then acc
  else
    let line = head lines
    let kind = classify_line line
    if kind == "decl" then count_fns (tail lines) [] (acc + 1)
    else if kind == "comment" then count_fns (tail lines) (cons line pending) acc
    else if kind == "blank" then count_fns (tail lines) [] acc
    else count_fns (tail lines) [] acc

walk_lines out_path lines pending =
  if length lines == 0 then 0
  else
    let line = head lines
    let kind = classify_line line
    if kind == "decl" then
      let _ = emit_entry out_path line (reverse pending)
      walk_lines out_path (tail lines) []
    else if kind == "comment" then walk_lines out_path (tail lines) (cons line pending)
    else if kind == "blank" then walk_lines out_path (tail lines) []
    else walk_lines out_path (tail lines) []

emit_entry out_path decl_line comments_fifo =
  let sig = sig_of_line decl_line
  let _ = w out_path (cat ["### `", sig, "`\n\n"])
  let _ = emit_doc out_path comments_fifo
  let _ = w out_path "\n"
  0

emit_doc out_path comments =
  if length comments == 0 then 0
  else
    let line = head comments
    let stripped = strip_dashes line
    let _ = w out_path (cat ["> ", stripped, "\n"])
    emit_doc out_path (tail comments)

-- ── output helpers ──

w path s =
  let _ = write_file "/tmp/_doc_chunk.txt" s
  shell (cat ["cat /tmp/_doc_chunk.txt >> ", path])

trunc path = shell (cat [": > ", path])

-- ── driver ──

main =
  let out = "docs/site/stdlib.md"
  let _ = shell "mkdir -p docs/site"
  let _ = trunc out
  let _ = w out "# Rail stdlib reference\n\n"
  let _ = w out "_Auto-generated by `tools/docs/gen_stdlib_ref.rail`._\n"
  let _ = w out "_Each entry shows the function signature; lines prefixed with `>` are the leading source comments._\n\n"
  let _ = w out "Import a module with:\n\n"
  let _ = w out "```rail\nimport \"stdlib/<name>.rail\"\n```\n"

  let list_out = shell "ls -1 stdlib/*.rail | LC_ALL=C sort"
  let paths = split "\n" list_out
  let n_files = walk_files out paths 0
  let _ = print (cat ["files processed: ", show n_files])
  let size = trim_nl (shell (cat ["wc -c < ", out, " | tr -d ' '"]))
  let _ = print (cat ["wrote: ", out, " (", size, " bytes)"])
  0

walk_files out paths acc =
  if length paths == 0 then acc
  else
    let p = head paths
    if length p == 0 then walk_files out (tail paths) acc
    else
      let _ = extract_file out p
      walk_files out (tail paths) (acc + 1)
