(* Copyright © 2026 Stéphane Glondu. Licensed under GPL-2+. *)

open Support
open Common

let is_deb_dev x = extract ~prefix:"lib" ~suffix:"-ocaml-dev" x
let is_deb_run x = extract ~prefix:"lib" ~suffix:"-ocaml" x
let is_stub x = extract ~prefix:"lib" ~suffix:".a" (Filename.basename x)

let is_run_file x =
  let suffixes = [ "/META"; ".cma"; ".cmxs" ] in
  List.exists (fun suffix -> String.ends_with ~suffix x) suffixes

module Make (A : sig
  val args : string list
end) : sig end = struct
  let () =
    match Lazy.force config with
    | Some (`Assoc o) -> (
        match List.assoc_opt "dh_ocamllibinstall" o with
        | Some (`Bool true) -> ()
        | _ -> exit 0)
    | _ -> exit 0

  let deb_packages =
    Printf.sprintf "dh_listpackages %s" (A.args |> String.concat " ")
    |> pread |> split_lines_opt

  let deb_dev_packages = List.filter_map is_deb_dev deb_packages
  let deb_run_packages = List.filter_map is_deb_run deb_packages

  let main_lib =
    match deb_dev_packages with
    | [] -> ( match deb_packages with [] -> exit 0 | x :: _ -> `Absolute x)
    | x :: _ -> `OCaml x

  let findlib_packages =
    let list =
      match loadFile "debian/debian-dune" with
      | Some _ as x -> x
      | None -> loadFile "debian/debian-findlib"
    in
    split_lines_opt list
    |> List.map (fun x ->
        match String.split_on_char ' ' x with
        | [ x ] -> (x, main_lib)
        | [ x; y ] ->
            let y =
              if String.starts_with ~prefix:"!" y then
                `Absolute (String.sub y 1 (String.length y - 1))
              else `OCaml y
            in
            (x, y)
        | _ -> exit 11)

  let get_deb_dev = function
    | `OCaml x -> Printf.sprintf "lib%s-ocaml-dev" x
    | `Absolute x -> x

  let get_deb_run = function
    | `OCaml x ->
        let x' = Printf.sprintf "lib%s-ocaml" x in
        if List.mem x' deb_packages then x'
        else Printf.sprintf "lib%s-ocaml-dev" x
    | `Absolute x ->
        if String.ends_with ~suffix:"-dev" x then
          let x' = String.sub x 0 (String.length x - 4) in
          if List.mem x' deb_packages then x' else x
        else x

  let all_files = "find debian/tmp -type f" |> pread |> split_lines_opt

  let lib_files =
    let prefix = Printf.sprintf "debian/tmp%s" (Lazy.force ocaml_stdlib_dir) in
    let n = String.length prefix + 1 in
    all_files
    |> List.filter_map (fun x ->
        if String.starts_with ~prefix x then
          Some (String.sub x n (String.length x - n))
        else None)

  let share_files =
    let prefix = "debian/tmp/usr/share/ocaml-dune" in
    let n = String.length prefix + 1 in
    all_files
    |> List.filter_map (fun x ->
        if String.starts_with ~prefix x then
          match
            String.split_on_char '/' (String.sub x n (String.length x - n))
          with
          | section :: package :: path ->
              Some (package, section, String.concat "/" path)
          | _ -> None
        else None)

  let lib_process_findlib_package (files, accu) (findlib, deb) =
    let my, files =
      List.partition (String.starts_with ~prefix:(findlib ^ "/")) files
    in
    let stubs = List.filter_map is_stub my in
    let dlls, files =
      List.fold_left
        (fun (dlls, files) stub ->
          let dll = Printf.sprintf "stublibs/dll%s.so" stub in
          match mem_remove dll files with
          | None -> (dlls, files)
          | Some files -> (dll :: dlls, files))
        ([], files) stubs
    in
    let run_files, dev_files = List.partition is_run_file my in
    let accu =
      let pkg = get_deb_run deb in
      let current = SMap.find_opt pkg accu |> Option.value ~default:[] in
      let current = List.rev_append run_files current in
      let current = List.rev_append dlls current in
      SMap.add pkg current accu
    in
    let accu =
      let pkg = get_deb_dev deb in
      let current = SMap.find_opt pkg accu |> Option.value ~default:[] in
      let current = List.rev_append dev_files current in
      SMap.add pkg current accu
    in
    (files, accu)

  let _, lib_map =
    List.fold_left lib_process_findlib_package (lib_files, SMap.empty)
      findlib_packages

  let share_process_findlib_package (files, accu) (findlib, deb) =
    let my, files =
      List.partition (fun (package, _, _) -> package = findlib) files
    in
    let pkg = get_deb_dev deb in
    let current = SMap.find_opt pkg accu |> Option.value ~default:[] in
    let current = List.rev_append my current in
    (files, SMap.add pkg current accu)

  let _, share_map =
    List.fold_left share_process_findlib_package (share_files, SMap.empty)
      findlib_packages

  let map =
    SMap.merge
      (fun pkg lib_files share_files ->
        let lib_files =
          Option.value ~default:[] lib_files
          |> List.map (Printf.sprintf "%s/%s" (Lazy.force ocaml_stdlib_dir))
        in
        let share_files =
          Option.value ~default:[] share_files
          |> List.map (fun (package, section, path) ->
              Printf.sprintf "/usr/share/ocaml-dune/%s/%s/%s" section package
                path)
        in
        let merged = share_files @ lib_files in
        if merged = [] then None else Some merged)
      lib_map share_map

  let () =
    SMap.iter
      (fun pkg files ->
        let aux_file =
          Printf.sprintf "debian/%s.ocamllibinstall.debhelper" pkg
        in
        let () = files |> merge_lines |> saveFile aux_file in
        let inner_cmd = [ "dh_install"; "-p" ^ pkg; "--autodest" ] in
        let cmd = "xargs" :: "-a" :: aux_file :: inner_cmd in
        printf "I: running %S with %s...\n"
          (String.concat " " inner_cmd)
          aux_file;
        let r = exec cmd in
        if r <> 0 then (
          printf "E: dh_install exited with code %d\n" r;
          exit 12))
      map
end

let main args =
  let module _ = Make (struct
    let args = args
  end) in
  ()
