{ lib
, pkgs

# llvm 17 is not in nixpkgs
#, llvmPackages_17
#, llvmPackages_attrName ? "llvmPackages_17"
# llvm 16 is latest llvm in nixpkgs
, llvmPackages_16
, llvmPackages_attrName ? "llvmPackages_16"

, stdenv
, buildPackages
, pkgsBuildBuild
, pkgsBuildTarget

, git
, cacert
, fetchurl
, fetchgit
, fetchFromGitiles
, gn
, python3
, ninja
, pkg-config
, perl
, which
, rustc
, libuuid
, overrideCC
, pkgsBuildHost

, libpng
, bzip2
, flac
, speex
, libopus
, libevent
, expat
, libjpeg
, snappy
, libcap
, xdg-utils
, minizip
, libwebp
, libusb1
, re2
, ffmpeg
, libxslt
, libxml2
, nasm
, nspr
, nss
, util-linux
, alsa-lib
, bison
, gperf
, libkrb5
, glib
, gtk3
, dbus-glib
, libXScrnSaver
, libXcursor
, libXtst
, libxshmfence
, libGLU
, libGL
, mesa
, pciutils
, protobuf
, speechd
, libXdamage
, at-spi2-core
, pipewire
, libva
, libdrm
, wayland
, libxkbcommon # Ozone
, curl
, libffi
, libepoxy
, libevdev

, glibc # gconv + locale

, vulkan-loader
}:

let

  # fix: error: Unknown function: filter_include
  # generate-ninja
  # https://manpages.debian.org/experimental/generate-ninja/gn.1.en.html
  # GN is a meta-build system that generates build files for Ninja.
  gnPdfium = gn.overrideAttrs (oldAttrs: {
    # TODO move to sources.json
    src = fetchFromGitiles {
      url = "https://gn.googlesource.com/gn";
      rev = "cc56a0f98bb34accd5323316e0292575ff17a5d4";
      sha256 = "sha256-SwlET5h5xtDlQvlt8wbG73ZfUWJr4hlWc+uQsBH5x9M=";
    };
  });

  # sources.json is generated by gclient2nix from the DEPS file
  sources = lib.importJSON ./sources.json;

  # https://github.com/NixOS/nixpkgs/pull/207766/files#diff-f697f2318d2aae818cbe3a36d2121dd423a82baf07dfe891f4543ce441a26b8aR20
  fetchdep = dep: let
    opts = removeAttrs dep ["fetcher"];
  in pkgs.${dep.fetcher} opts;

  # based on nixpkgs/pkgs/applications/networking/browsers/chromium/common.nix
  mkGnFlags =
    let
      # Serialize Nix types into GN types according to this document:
      # https://source.chromium.org/gn/gn/+/master:docs/language.md
      mkGnString = value: "\"${lib.escape ["\"" "$" "\\"] value}\"";
      sanitize = value:
        if value == true then "true"
        else if value == false then "false"
        else if lib.isList value then "[${lib.concatMapStringsSep ", " sanitize value}]"
        else if lib.isInt value then toString value
        else if lib.isString value then mkGnString value
        else throw "Unsupported type for GN value `${value}'.";
      toFlag = key: value: "${key}=${sanitize value}";
    in
    (
      attrs:
      (
        lib.concatStringsSep " " (
          lib.attrValues (
            lib.mapAttrs toFlag (
              attrs
            )
          )
        )
      )
    )
  ;

  # https://source.chromium.org/chromium/chromium/src/+/master:build/linux/unbundle/replace_gn_files.py
  gnSystemLibraries = [
    # TODO:
    # "ffmpeg"
    # "snappy"
    "flac"
    "libjpeg"
    "libpng"
    "libwebp"
    "libxslt"
    # "opus"
  ];


  # build paths and release info
  #packageName = extraAttrs.packageName or extraAttrs.name;
  packageName = "pdfium";
  buildType = "Release";
  buildPath = "out/${buildType}";
  libExecPath = "$out/libexec/${packageName}";

  # There currently isn't a (much) more concise way to get a stdenv
  # that uses lld as its linker without bootstrapping pkgsLLVM; see
  # https://github.com/NixOS/nixpkgs/issues/142901
  buildPlatformLlvmStdenv =
    let
      llvmPackages = pkgsBuildBuild.${llvmPackages_attrName};
    in
    overrideCC llvmPackages.stdenv
    (llvmPackages.stdenv.cc.override {
      inherit (llvmPackages) bintools;
    });

  chromiumRosettaStone = {
    cpu = platform:
      let name = platform.parsed.cpu.name;
      in ({ "x86_64" = "x64";
            "i686" = "x86";
            "arm" = "arm";
            "aarch64" = "arm64";
          }.${platform.parsed.cpu.name}
        or (throw "no chromium Rosetta Stone entry for cpu: ${name}"));
    os = platform:
      if platform.isLinux
      then "linux"
      else throw "no chromium Rosetta Stone entry for os: ${platform.config}";
  };

in

llvmPackages_16.stdenv.mkDerivation rec {

  pname = "pdfium";

  # FIXME get version from sources.json
  version = "unstable-2023-08-18";

  # https://github.com/NixOS/nixpkgs/pull/207766/files#diff-f697f2318d2aae818cbe3a36d2121dd423a82baf07dfe891f4543ce441a26b8aR53
  unpackPhase =
    ''
      sourceRoot=source
    ''
    +
    (lib.concatStrings (lib.mapAttrsToList (path: dep:
      if path == "" then
      ''
        echo copying ${fetchdep dep} to $sourceRoot
        cp -r ${fetchdep dep} $sourceRoot
        chmod -R +w $sourceRoot
      ''
      else
      ''
        echo copying ${fetchdep dep} to $sourceRoot/${path}
        mkdir -p $sourceRoot/${builtins.dirOf path}
        cp -r ${fetchdep dep} $sourceRoot/${path}
        chmod -R +w $sourceRoot/${path}
      ''
    ) sources)
  );

  # https://github.com/NixOS/nixpkgs/pull/207766/files#diff-f697f2318d2aae818cbe3a36d2121dd423a82baf07dfe891f4543ce441a26b8aR65
  postPatch = ''
    # fix: ERROR at //.gn:28:20: Assignment had no effect.
    # TODO remove? this may be fixed by updating gn
    cp ${./pdfium.gn} .gn

    echo patching shebangs
    patchShebangs . >/dev/null

    # add gn to $PATH
    export PATH=$PATH:$PWD/third_party/depot_tools
  '';

  nativeBuildInputs = [
    gnPdfium
    python3
    ninja
    # fix: FileNotFoundError: [Errno 2] No such file or directory: 'git'
    git
  ];

  depsBuildBuild =
    [
      buildPlatformLlvmStdenv
      buildPlatformLlvmStdenv.cc
      pkg-config
      libuuid
      libpng # needed for "host/generate_colors_info"
    ] 
    # When cross-compiling, chromium builds a huge proportion of its
    # components for both the `buildPlatform` (which it calls
    # `host`) as well as for the `hostPlatform` -- easily more than
    # half of the dependencies are needed here.  To avoid having to
    # maintain a separate list of buildPlatform-dependencies, we
    # simply throw in the kitchen sink.
    ++ buildInputs
  ;

  # based on nixpkgs/pkgs/applications/networking/browsers/chromium/common.nix
  # TODO remove unused inputs
  buildInputs =
    [
      (libpng.override { apngSupport = false; }) # https://bugs.chromium.org/p/chromium/issues/detail?id=752403
      bzip2
      flac
      speex
      #opusWithCustomModes
      libopus
      libevent
      expat
      libjpeg
      snappy
      libcap
    ]
    ++ lib.optionals (!xdg-utils.meta.broken) [
      xdg-utils
    ]
    ++ [
      minizip
      libwebp
      libusb1
      re2
      ffmpeg
      libxslt
      libxml2
      nasm
      nspr
      nss
      util-linux
      alsa-lib
      libkrb5
      glib
      gtk3
      dbus-glib
      libXScrnSaver
      libXcursor
      libXtst
      libxshmfence
      libGLU
      libGL
      mesa # required for libgbm
      pciutils
      protobuf
      speechd
      libXdamage
      at-spi2-core
      pipewire
      libva
      libdrm
      wayland
      mesa.drivers
      libxkbcommon
      curl
      libepoxy
      libffi
      libevdev
    ]
  ;

  # based on nixpkgs/pkgs/applications/networking/browsers/chromium/common.nix
  gnFlags = mkGnFlags ({

    # Main build and toolchain settings:
    # Create an official and optimized release build (only official builds
    # should be distributed to users, as non-official builds are intended for
    # development and may not be configured appropriately for production,
    # e.g. unsafe developer builds have developer-friendly features that may
    # weaken or disable security measures like sandboxing or ASLR):
    is_official_build = true;

    # note: chromium calls buildPlatform "host" and calls hostPlatform "target"
    host_cpu      = chromiumRosettaStone.cpu stdenv.buildPlatform;
    host_os       = chromiumRosettaStone.os  stdenv.buildPlatform;
    target_cpu    = chromiumRosettaStone.cpu stdenv.hostPlatform;
    v8_target_cpu = chromiumRosettaStone.cpu stdenv.hostPlatform;
    target_os     = chromiumRosettaStone.os  stdenv.hostPlatform;

    # Build Chromium using the system toolchain (for Linux distributions):
    #
    # What you would expect to be caled "target_toolchain" is
    # actually called either "default_toolchain" or "custom_toolchain",
    # depending on which part of the codebase you are in; see:
    # https://github.com/chromium/chromium/blob/d36462cc9279464395aea5e65d0893d76444a296/build/config/BUILDCONFIG.gn#L17-L44
    custom_toolchain = "//build/toolchain/linux/unbundle:default";
    host_toolchain = "//build/toolchain/linux/unbundle:host";

    # Build argument has no effect.
    v8_snapshot_toolchain = "//build/toolchain/linux/unbundle:host";

    host_pkg_config = "${pkgsBuildBuild.pkg-config}/bin/pkg-config";
    pkg_config      = "${pkgsBuildHost.pkg-config}/bin/${stdenv.cc.targetPrefix}pkg-config";

    # Don't build against a sysroot image downloaded from Cloud Storage:
    use_sysroot = false;

    # Because we use a different toolchain / compiler version:
    treat_warnings_as_errors = false;

    # We aren't compiling with Chrome's Clang (would enable Chrome-specific
    # plugins for enforcing coding guidelines, etc.):
    clang_use_chrome_plugins = false;

    # Disable symbols (they would negatively affect the performance of the
    # build since the symbols are large and dealing with them is slow):
    symbol_level = 0;

    # Optional features:
    use_gio = true;

    # Disable PGO because the profile data requires a newer compiler version (LLVM 14 isn't sufficient):
    chrome_pgo_phase = 0;
    clang_base_path = "${pkgsBuildTarget.${llvmPackages_attrName}.stdenv.cc}";

    # Use nixpkgs Rust compiler instead of the one shipped by Chromium.
    # We do intentionally not set rustc_version as nixpkgs will never do incremental
    # rebuilds, thus leaving this empty is fine.
    rust_sysroot_absolute = "${rustc}";

    # fix: 
    # ../../base/allocator/partition_allocator/pointers/raw_ptr_backup_ref_impl.h:341:5: error: static assertion failed
    # static_assert(BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT));
    # https://chromium.googlesource.com/chromium/src/base/allocator/partition_allocator/+/refs/heads/main/pointers/raw_ptr_backup_ref_impl.h
    put_ref_count_in_previous_slot = true;

  }
  );

  configurePhase = ''
    runHook preConfigure

    # This is to ensure expansion of $out.
    libExecPath="${libExecPath}"

    # fix: /bin/sh: illegal option -M
    # set environment variables for
    # https://chromium.googlesource.com/chromium/src/build/+/refs/heads/main/toolchain/linux/unbundle/BUILD.gn
    export BUILD_CC=$CC
    export BUILD_CXX=$CXX
    export BUILD_AR=$AR
    export BUILD_NM=$NM
    export BUILD_CFLAGS=$CFLAGS
    export BUILD_CPPFLAGS=$CPPFLAGS
    export BUILD_CXXFLAGS=$CXXFLAGS
    export BUILD_LDFLAGS=$LDFLAGS

    ${gnPdfium}/bin/gn gen --args=${lib.escapeShellArg gnFlags} out/Release | tee gn-gen-outputs.txt

    # Fail if `gn gen` contains a WARNING.
    if grep -q WARNING gn-gen-outputs.txt; then
      echo "Found gn WARNING, exiting nix build"

      echo "gn args:"
      # using sed to remove ansi colors
      ${gnPdfium}/bin/gn args --list out/Release |
      sed $'s/\e\\[[0-9;:]*[a-zA-Z]//g'

      echo "gn warning:"
      grep -A10 WARNING gn-gen-outputs.txt

      exit 1
    fi

    runHook postConfigure
  '';

  /*
    # list targets
    # https://gn.googlesource.com/gn/+/main/docs/reference.md#cmd_ls
    ${gnPdfium}/bin/gn ls out/Release

    //:default
    //:freetype_common
    //:gn_check
    //:pdfium
    //:pdfium_all
    //:pdfium_diff
    //:pdfium_embeddertest_deps
    //:pdfium_embeddertests
    //:pdfium_public_headers
    //:pdfium_public_headers_impl
    //:pdfium_unittest_deps
    //:pdfium_unittests
  */

  buildPhase = ''
    runHook preBuild

    # fix: clang++ ... -fsanitize-ignorelist=../../tools/cfi/ignores.txt ...
    # fix: clang++: error: no such file or directory: '../../tools/cfi/ignores.txt'
    mkdir -p tools/cfi
    touch tools/cfi/ignores.txt

    # disable ninja line clearing
    export TERM=dumb

    set -x
    ninja -C out/Release -j$NIX_BUILD_CORES :default
    set +x

    runHook postBuild
  '';

  meta = with lib; {
    description = "library for PDF manipulation and rendering";
    homepage = "https://pdfium.googlesource.com/pdfium";
    license = licenses.asl20;
    maintainers = with maintainers; [ ];
  };
}
