;; Phase 8 FFI BIF tests — one round-trip per BIF.
;; Each BIF lives in lib/erlang/runtime.sx (registered with
;; er-bif-registry) and wraps an SX-host primitive.

(define er-ffi-test-count 0)
(define er-ffi-test-pass 0)
(define er-ffi-test-fails (list))

(define
  er-ffi-test
  (fn
    (name actual expected)
    (set! er-ffi-test-count (+ er-ffi-test-count 1))
    (if
      (= actual expected)
      (set! er-ffi-test-pass (+ er-ffi-test-pass 1))
      (append! er-ffi-test-fails {:name name :expected expected :actual actual}))))

(define ffi-ev erlang-eval-ast)
(define ffi-nm (fn (v) (get v :name)))

;; ── file:read_file/1 + file:write_file/2 ────────────────────────
(er-ffi-test
  "file:write_file ok"
  (ffi-nm (ffi-ev "file:write_file(\"/tmp/er-ffi-1.txt\", \"hello\")"))
  "ok")

(er-ffi-test
  "file:read_file ok tag"
  (ffi-nm (ffi-ev "element(1, file:read_file(\"/tmp/er-ffi-1.txt\"))"))
  "ok")

(er-ffi-test
  "file:read_file payload is binary"
  (ffi-nm
    (ffi-ev
      "case file:read_file(\"/tmp/er-ffi-1.txt\") of {ok, B} -> is_binary(B) end"))
  "true")

(er-ffi-test
  "file:read_file content byte_size"
  (ffi-ev
    "case file:read_file(\"/tmp/er-ffi-1.txt\") of {ok, B} -> byte_size(B) end")
  5)

(er-ffi-test
  "file:read_file missing enoent"
  (ffi-nm (ffi-ev "element(2, file:read_file(\"/tmp/er-ffi-no-such-xyz\"))"))
  "enoent")

(er-ffi-test
  "file:write_file bad path enoent"
  (ffi-nm
    (ffi-ev "element(2, file:write_file(\"/tmp/er-ffi-no-dir-xyz/x\", \"y\"))"))
  "enoent")

(er-ffi-test
  "file:write_file binary payload"
  (ffi-ev
    "file:write_file(\"/tmp/er-ffi-2.bin\", <<1, 2, 3, 4, 5>>), case file:read_file(\"/tmp/er-ffi-2.bin\") of {ok, B} -> byte_size(B) end")
  5)

;; ── file:delete/1 ────────────────────────────────────────────────
(er-ffi-test
  "file:delete ok"
  (ffi-nm
    (ffi-ev
      "file:write_file(\"/tmp/er-ffi-del.txt\", \"x\"), file:delete(\"/tmp/er-ffi-del.txt\")"))
  "ok")

(er-ffi-test
  "file:read_file after delete enoent"
  (ffi-nm
    (ffi-ev
      "file:write_file(\"/tmp/er-ffi-del2.txt\", \"x\"), file:delete(\"/tmp/er-ffi-del2.txt\"), element(2, file:read_file(\"/tmp/er-ffi-del2.txt\"))"))
  "enoent")

(er-ffi-test
  "crypto:hash sha256 -> 32-byte binary"
  (ffi-ev "byte_size(crypto:hash(sha256, <<97,98,99>>))")
  32)

(er-ffi-test
  "crypto:hash sha512 -> 64-byte binary"
  (ffi-ev "byte_size(crypto:hash(sha512, <<97,98,99>>))")
  64)

(er-ffi-test
  "crypto:hash sha3_256 is_binary"
  (ffi-nm (ffi-ev "is_binary(crypto:hash(sha3_256, <<120>>))"))
  "true")

(er-ffi-test
  "crypto:hash deterministic"
  (ffi-nm (ffi-ev "crypto:hash(sha256, <<97>>) =:= crypto:hash(sha256, <<97>>)"))
  "true")

(er-ffi-test
  "crypto:hash distinct inputs distinct digests"
  (ffi-nm (ffi-ev "crypto:hash(sha256, <<97>>) =/= crypto:hash(sha256, <<98>>)"))
  "true")

(er-ffi-test
  "crypto:hash bad type -> error:badarg"
  (ffi-nm (ffi-ev "try crypto:hash(md5, <<120>>) catch error:badarg -> ok end"))
  "ok")

(er-ffi-test
  "cid:from_bytes is_binary"
  (ffi-nm (ffi-ev "is_binary(cid:from_bytes(<<97,98,99>>))"))
  "true")

(er-ffi-test
  "cid:from_bytes deterministic"
  (ffi-nm (ffi-ev "cid:from_bytes(<<97,98,99>>) =:= cid:from_bytes(<<97,98,99>>)"))
  "true")

(er-ffi-test
  "cid:from_bytes distinct inputs distinct CIDs"
  (ffi-nm (ffi-ev "cid:from_bytes(<<97,98,99>>) =/= cid:from_bytes(<<97,98,100>>)"))
  "true")

(er-ffi-test
  "cid:from_bytes non-binary -> error:badarg"
  (ffi-nm (ffi-ev "try cid:from_bytes(42) catch error:badarg -> ok end"))
  "ok")

(er-ffi-test
  "cid:to_string is_binary"
  (ffi-nm (ffi-ev "is_binary(cid:to_string({ok, 42}))"))
  "true")

(er-ffi-test
  "cid:to_string deterministic"
  (ffi-nm (ffi-ev "cid:to_string(foo) =:= cid:to_string(foo)"))
  "true")

(er-ffi-test
  "cid:to_string distinct terms distinct CIDs"
  (ffi-nm (ffi-ev "cid:to_string(foo) =/= cid:to_string(bar)"))
  "true")

(er-ffi-test
  "file:list_dir ok tag"
  (ffi-nm (ffi-ev "element(1, file:list_dir(\"lib/erlang\"))"))
  "ok")

(er-ffi-test
  "file:list_dir non-empty"
  (ffi-nm (ffi-ev "case file:list_dir(\"lib/erlang\") of {ok, L} -> length(L) > 3 end"))
  "true")

(er-ffi-test
  "file:list_dir entries are binaries"
  (ffi-nm (ffi-ev "case file:list_dir(\"lib/erlang\") of {ok, L} -> is_binary(hd(L)) end"))
  "true")

(er-ffi-test
  "file:list_dir missing enoent"
  (ffi-nm (ffi-ev "element(2, file:list_dir(\"/no/such/dir/xyz\"))"))
  "enoent")

(er-ffi-test
  "binary_to_list <<1,2,3>> length"
  (ffi-ev "length(binary_to_list(<<1,2,3,4,5>>))")
  5)

(er-ffi-test
  "binary_to_list hd byte"
  (ffi-ev "hd(binary_to_list(<<7,8,9>>))")
  7)

(er-ffi-test
  "binary_to_list empty -> []"
  (ffi-nm (ffi-ev "case binary_to_list(<<>>) of [] -> empty end"))
  "empty")

(er-ffi-test
  "list_to_binary flat list bytes"
  (ffi-ev "byte_size(list_to_binary([1,2,3]))")
  3)

(er-ffi-test
  "list_to_binary nested iolist"
  (ffi-ev "byte_size(list_to_binary([1, <<2,3>>, [4, [5]]]))")
  5)

(er-ffi-test
  "list_to_binary round-trip via binary_to_list"
  (ffi-nm (ffi-ev "list_to_binary(binary_to_list(<<10,20,30>>)) =:= <<10,20,30>>"))
  "true")

(er-ffi-test
  "binary_to_list non-binary -> error:badarg"
  (ffi-nm (ffi-ev "try binary_to_list(42) catch error:badarg -> ok end"))
  "ok")

(er-ffi-test
  "list_to_binary out-of-range byte -> error:badarg"
  (ffi-nm (ffi-ev "try list_to_binary([300]) catch error:badarg -> ok end"))
  "ok")

(er-ffi-test
  "list_to_binary non-iolist -> error:badarg"
  (ffi-nm (ffi-ev "try list_to_binary(42) catch error:badarg -> ok end"))
  "ok")

;; ── Still deferred (no host primitive): httpc (HTTP client, v2),
;; sqlite-* (v2 indexes). Assert NOT registered so a future iteration
;; that wires them without updating this suite fails fast.
(er-ffi-test
  "httpc:request unregistered"
  (er-lookup-bif "httpc" "request" 4)
  nil)

(er-ffi-test
  "sqlite:exec unregistered"
  (er-lookup-bif "sqlite" "exec" 2)
  nil)

(define
  er-ffi-test-summary
  (str "ffi " er-ffi-test-pass "/" er-ffi-test-count))
