"""
Dune build system configuration generator.
"""

from typing import List, Optional

from polyglot_ffi.utils.naming import sanitize_module_name


class DuneGenerator:
    """Generate Dune build configuration files."""

    def _format_packages(self, ocaml_libraries: Optional[List[str]] = None) -> str:
        """Format additional packages for ocamlfind -package flag."""
        if not ocaml_libraries:
            return ""
        return "," + ",".join(ocaml_libraries)

    def _get_library_link_flags(self, ocaml_libraries: Optional[List[str]] = None) -> str:
        """Get linker flags for OCaml libraries with C dependencies."""
        if not ocaml_libraries:
            return ""

        # Map OCaml library names to their C library flags
        # Note: The C library names often differ from OCaml package names
        lib_flags_map = {
            "str": "-lcamlstr",  # OCaml Str module -> libcamlstr
            "unix": "-lunix",
            "threads": "-lthreadsnat",
        }

        flags = []
        for lib in ocaml_libraries:
            if lib in lib_flags_map:
                flags.append(lib_flags_map[lib])

        return " " + " ".join(flags) if flags else ""

    def generate_dune(self, module_name: str, ocaml_libraries: Optional[List[str]] = None) -> str:
        """Generate dune file for the bindings library.

        Args:
            module_name: Name of the module
            ocaml_libraries: Additional OCaml libraries to link (e.g., ['str', 'unix'])
        """
        safe_name = sanitize_module_name(module_name)

        # Build libraries list
        # Note: We only include 'ctypes' here. ctypes.foreign is used via -package in ocamlfind commands
        # but doesn't need to be in the library list (it shares the same directory as ctypes)
        libs = ["ctypes"]
        if ocaml_libraries:
            libs.extend(ocaml_libraries)
        libs_str = " ".join(libs)

        return f"""; Generated by polyglot-ffi
(library
 (name {safe_name}_bindings)
 (public_name {safe_name}_bindings)
 (libraries {libs_str})
 (ctypes
  (external_library_name {safe_name})
  (build_flags_resolver
   (vendored (c_flags :standard) (c_library_flags :standard)))
  (headers (preamble "#include \\"{safe_name}_stubs.h\\""))
  (type_description
   (instance Type)
   (functor Type_description))
  (function_description
   (concurrency sequential)
   (instance Function)
   (functor Function_description))
  (generated_types Types_generated)
  (generated_entry_point C)))

; Compile C stubs to object file
(rule
 (targets {safe_name}_stubs.o)
 (deps {safe_name}_stubs.c {safe_name}_stubs.h)
 (action
  (run gcc -c -I%{{ocaml_where}} -fPIC {safe_name}_stubs.c -o {safe_name}_stubs.o)))

; Build OCaml object file for standalone shared library
(rule
 (targets lib{safe_name}_ocaml.o)
 (deps {safe_name}.ml {safe_name}.mli)
 (action
  (progn
   (run ocamlfind ocamlopt -c -thread -package ctypes.foreign{self._format_packages(ocaml_libraries)} {safe_name}.mli)
   (run ocamlfind ocamlopt -thread -output-obj -o lib{safe_name}_ocaml.o
    -package ctypes.foreign{self._format_packages(ocaml_libraries)} -linkpkg
    {safe_name}.ml))))

; Create .dylib from object files on macOS
(rule
 (targets lib{safe_name}.dylib)
 (deps lib{safe_name}_ocaml.o {safe_name}_stubs.o)
 (enabled_if (= %{{system}} macosx))
 (mode promote)
 (action
  (run gcc -dynamiclib -o lib{safe_name}.dylib
   lib{safe_name}_ocaml.o {safe_name}_stubs.o
   -L%{{ocaml_where}} -lasmrun -lunix -lthreadsnat{self._get_library_link_flags(ocaml_libraries)})))

; Create .so from object files on Linux
(rule
 (targets lib{safe_name}.so)
 (deps lib{safe_name}_ocaml.o {safe_name}_stubs.o)
 (enabled_if (= %{{system}} linux))
 (mode promote)
 (action
  (run gcc -shared -o lib{safe_name}.so
   lib{safe_name}_ocaml.o {safe_name}_stubs.o
   -L%{{ocaml_where}} -lasmrun -lunix -lthreadsnat{self._get_library_link_flags(ocaml_libraries)})))
"""

    def generate_dune_project(self, module_name: str) -> str:
        """Generate dune-project file."""
        safe_name = sanitize_module_name(module_name)
        return f"""(lang dune 3.16)
(using ctypes 0.3)

; Generated by polyglot-ffi

(name {safe_name}_bindings)

(generate_opam_files true)

(package
 (name {safe_name}_bindings)
 (synopsis "OCaml-Python bindings for {module_name}")
 (description "Auto-generated FFI bindings by polyglot-ffi")
 (depends
  (ocaml (>= 4.14))
  (dune (>= 3.16))
  (ctypes (>= 0.20.0))
  (ctypes-foreign (>= 0.20.0))))
"""
