(* SPDX-License-Identifier: MPL-2.0 *)
(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell *)

(** Verilog emitter (combinational MVP).

    Lowers a strict subset of AffineScript to a Verilog module: a single
    pure function on integer inputs becomes a combinational module with
    an always_comb block. No clock, no state, no buses — that's a Phase 2
    feature. *)

open Ast

exception Vlog_unsupported of string
let unsupported m = raise (Vlog_unsupported m)

let bit_width = 32  (* MVP: every Int is 32-bit signed *)

let ty_to_decl ty kind =
  (match ty with
   | TyCon id when id.name = "Int"  -> ()
   | TyCon id when id.name = "Bool" -> ()
   | _ -> unsupported "Verilog backend accepts only Int/Bool ports/wires");
  let width = match ty with
    | TyCon id when id.name = "Bool" -> 1
    | _ -> bit_width
  in
  if width = 1 then kind ^ " "
  else Printf.sprintf "%s signed [%d:0] " kind (width - 1)

(* ============================================================================
   Pure expression compiler

   Returns a Verilog expression string. No side effects — combinational
   logic only.
   ============================================================================ *)

let rec gen_expr (e : expr) : string =
  match e with
  | ExprLit (LitInt (n, _))     -> string_of_int n
  | ExprLit (LitBool (true, _)) -> "1'b1"
  | ExprLit (LitBool (false, _))-> "1'b0"
  | ExprLit _ -> unsupported "non-numeric literal"
  | ExprVar id -> id.name
  | ExprBinary (a, op, b) ->
      let s = match op with
        | OpAdd -> "+" | OpSub -> "-" | OpMul -> "*" | OpDiv -> "/" | OpMod -> "%"
        | OpEq  -> "==" | OpNe -> "!="
        | OpLt  -> "<" | OpLe -> "<=" | OpGt -> ">" | OpGe -> ">="
        | OpAnd -> "&&" | OpOr  -> "||"
        | OpBitAnd -> "&" | OpBitOr -> "|" | OpBitXor -> "^"
        | OpShl -> "<<" | OpShr -> ">>"
        | OpConcat -> unsupported "string concat not supported in Verilog"
      in
      "(" ^ gen_expr a ^ " " ^ s ^ " " ^ gen_expr b ^ ")"
  | ExprUnary (op, x) ->
      (match op with
       | OpNeg -> "(-" ^ gen_expr x ^ ")"
       | OpNot -> "(!" ^ gen_expr x ^ ")"
       | OpBitNot -> "(~" ^ gen_expr x ^ ")"
       | _ -> unsupported "unary op not supported in Verilog")
  | ExprIf { ei_cond; ei_then; ei_else } ->
      let c = gen_expr ei_cond in
      let t = gen_expr ei_then in
      let f = match ei_else with Some e -> gen_expr e | None -> "0" in
      Printf.sprintf "(%s ? %s : %s)" c t f
  | ExprSpan (inner, _) -> gen_expr inner
  | ExprReturn (Some e) -> gen_expr e
  | ExprApp _ ->
      unsupported "function calls not supported in Verilog MVP \
                   (lift body of called function inline first)"
  | _ -> unsupported "expression form not supported in Verilog MVP"

let gen_module (buf : Buffer.t) (fd : fn_decl) : unit =
  let name = fd.fd_name.name in
  let body_expr = match fd.fd_body with
    | FnExpr e -> e
    | FnBlock { blk_stmts = []; blk_expr = Some e } -> e
    | FnBlock _ ->
        unsupported "Verilog backend MVP only handles single-expression bodies"
  in
  let ports = List.map (fun (p : param) ->
    let kind = ty_to_decl p.p_ty "input" in
    Printf.sprintf "%s%s" kind p.p_name.name
  ) fd.fd_params in
  let out_kind = ty_to_decl
    (match fd.fd_ret_ty with Some t -> t | None -> TyCon { name="Int"; span=Span.dummy })
    "output" in
  let port_list = String.concat ",\n  " (ports @ [out_kind ^ "out"]) in
  Buffer.add_string buf (Printf.sprintf "module %s (\n  %s\n);\n" name port_list);
  Buffer.add_string buf
    (Printf.sprintf "  assign out = %s;\n" (gen_expr body_expr));
  Buffer.add_string buf "endmodule\n\n"

let generate (program : program) (_symbols : Symbol.t) : string =
  let buf = Buffer.create 1024 in
  Buffer.add_string buf "// Generated by AffineScript compiler\n";
  Buffer.add_string buf "// SPDX-License-Identifier: MPL-2.0\n\n";
  List.iter (function
    | TopFn fd -> gen_module buf fd
    | _ -> ()
  ) program.prog_decls;
  Buffer.contents buf

let codegen_verilog (program : program) (symbols : Symbol.t) : (string, string) result =
  try Ok (generate program symbols)
  with
  | Vlog_unsupported m -> Error ("Verilog backend: " ^ m)
  | Failure m          -> Error ("Verilog codegen error: " ^ m)
  | e                  -> Error ("Verilog codegen error: " ^ Printexc.to_string e)
