package rosa

import (
	"regexp"
	"slices"
	"strings"

	"hakurei.app/internal/pkg"
)

var (
	_llvmSource    = H("llvm-project")
	_muslHeaders   = H("musl-headers")
	_earlyRuntimes = H("early-runtimes")
	_musl          = H("musl")

	_bash        = H("bash")
	_awk         = H("awk")
	_pythonEarly = H("python-early")
	_coreutils   = H("coreutils")
	_diffutils   = H("diffutils")
	_findutils   = H("findutils")

	_zlib          = H("zlib")
	_zstd          = H("zstd")
	_kernelHeaders = H("kernel-headers")
)

// litArgs returns LIT arguments for optional verbosity and check skipping.
func litArgs(verbose bool, skipChecks ...string) string {
	args := []string{"-sv"}
	if verbose {
		args[0] = "--verbose"
	}

	if len(skipChecks) > 0 {
		skipChecks = slices.Clone(skipChecks)
		for i, s := range skipChecks {
			s = regexp.QuoteMeta(s)
			s = strings.ReplaceAll(s, "/", "\\/")
			skipChecks[i] = s
		}
		args = append(args,
			"--filter-out='\\''"+strings.Join(skipChecks, "|")+"'\\''")
	}

	return "'" + strings.Join(args, " ") + "'"
}

func init() {
	builtin.MustRegister("llvm", func(t Toolchain) (*Metadata, pkg.Artifact) {
		meta := Metadata{
			Name:        "llvm",
			Description: "a collection of modular and reusable compiler and toolchain technologies",
			Website:     "https://llvm.org",

			Dependencies: P{
				_zlib,
				_zstd,
				_musl,
			},
		}
		_meta, source := t.MustLoad(_llvmSource)
		meta.Version = _meta.Version

		early := _muslHeaders
		if t.stage.isStage0() && !t.stage.isRosa() {
			// The LLVM build system uses the system installation when building with
			// LLVM_LINK_LLVM_DYLIB, since it builds runtimes after the fact, using
			// the just-built toolchain. This is unacceptable in stage0 due to the
			// potential version difference. Later stages bootstrap off of runtimes
			// of its previous stage via 3-stage determinism.
			early = _earlyRuntimes
		}

		cache := []KV{
			{"ENABLE_LINKER_BUILD_ID", "ON"},
			{"COMPILER_RT_USE_BUILTINS_LIBRARY", "ON"},
			{"COMPILER_RT_DEFAULT_TARGET_ONLY", "ON"},
			{"COMPILER_RT_BUILD_GWP_ASAN", "OFF"},
			{"LIBCXX_CXX_ABI", "libcxxabi"},
			{"LIBCXX_USE_COMPILER_RT", "ON"},
			{"LIBCXX_ENABLE_STATIC_ABI_LIBRARY", "OFF"},
			{"LIBCXX_HAS_MUSL_LIBC", "ON"},
			{"LIBCXX_HARDENING_MODE", "fast"},
			{"LIBCXX_STATICALLY_LINK_ABI_IN_STATIC_LIBRARY", "ON"},
			{"LIBCXXABI_USE_LLVM_UNWINDER", "ON"},
			{"LIBCXXABI_ENABLE_STATIC_UNWINDER", "ON"},
			{"LIBCXXABI_USE_COMPILER_RT", "ON"},
			{"LIBCXXABI_STATICALLY_LINK_UNWINDER_IN_STATIC_LIBRARY", "ON"},
			{"LLVM_INSTALL_BINUTILS_SYMLINKS", "ON"},
			{"LLVM_INSTALL_UTILS", "ON"},
			{"LLVM_BUILD_LLVM_DYLIB", "ON"},
			{"LLVM_LINK_LLVM_DYLIB", "ON"},
			{"LLVM_APPEND_VC_REV", "OFF"},
			{"LLVM_ENABLE_RTTI", "ON"},
			{"LLVM_ENABLE_ZLIB", "FORCE_ON"},
			{"LLVM_ENABLE_ZSTD", "FORCE_ON"},
			{"LLVM_ENABLE_PER_TARGET_RUNTIME_DIR", "ON"},
			{"LLVM_INCLUDE_BENCHMARKS", "OFF"},
			{"CLANG_DEFAULT_RTLIB", "compiler-rt"},
			{"CLANG_DEFAULT_UNWINDLIB", "libunwind"},
			{"CLANG_DEFAULT_CXX_STDLIB", "libc++"},
			{"CLANG_CONFIG_FILE_SYSTEM_DIR", "/system/etc/clang"},
			{"LLVM_ENABLE_FFI", "OFF"},
			{"LLVM_ENABLE_LIBXML2", "OFF"},
			{"LLVM_ENABLE_LIBCXX", "ON"},
			{"LLVM_ENABLE_LLD", "ON"},
			{"LIBUNWIND_ENABLE_ASSERTIONS", "OFF"},
			{"LIBUNWIND_USE_COMPILER_RT", "ON"},

			{"LLVM_HOST_TRIPLE", `"${ROSA_TRIPLE}"`},
			{"LLVM_DEFAULT_TARGET_TRIPLE", `"${ROSA_TRIPLE}"`},
			{"LLVM_ENABLE_PROJECTS", "'" + strings.Join([]string{
				"clang",
				"lld",
			}, ";") + "'"},
			{"LLVM_ENABLE_RUNTIMES", "'" + strings.Join([]string{
				"compiler-rt",
				"libcxx",
				"libcxxabi",
				"libunwind",
				"libclc",
			}, ";") + "'"},
		}

		if !t.stage.isStage0() {
			skipChecks := []string{
				// expensive, pointless to run here
				"benchmarks",
				// LLVM ERROR: Tried to execute an unknown external function: roundevenf
				"ExecutionEngine/Interpreter/intrinsics.ll",
				// clang: deadlocks with LLVM_BUILD_LLVM_DYLIB
				"crash-recovery-modules",
				// clang: fatal error: '__config_site' file not found
				"CodeGen/PowerPC/ppc-xmmintrin.c",
				"CodeGen/PowerPC/ppc-mmintrin.c",
				"CodeGen/PowerPC/ppc-emmintrin.c",
				"CodeGen/PowerPC/ppc-pmmintrin.c",
				"CodeGen/PowerPC/ppc-tmmintrin.c",
				"CodeGen/PowerPC/ppc-smmintrin.c",
				"CodeGenCUDA/amdgpu-alias-undef-symbols.cu",
				// clang: deadlocks with LIBCXXABI_ENABLE_STATIC_UNWINDER
				"global-value-regex",
				"global-hex-value-regex",
				"check-globals",
				// cxx: fails on musl
				"close.dont-get-rid-of-buffer",
				"re/re.traits",
				"std/time",
				"localization/locales",
				"localization/locale.categories",
				"selftest/dsl/dsl.sh.py",
				"input.output/iostream.format",
				"locale-specific_form",
				// cxx: deadlocks
				"std/thread/thread.jthread",
				// unwind: fails on musl
				"eh_frame_fde_pc_range",
			}
			switch t.arch {
			case "arm64":
				skipChecks = append(skipChecks,
					// LLVM: intermittently crashes
					"ExecutionEngine/OrcLazy/multiple-compile-threads-basic.ll",
					// unwind: unexpectedly passes
					"unwind_leaffunction",
				)
			}

			if t.opts&OptToolchainLTO != 0 {
				cache = append(cache, []KV{
					// very expensive
					{"LLVM_ENABLE_LTO", "Thin"},
				}...)
			}

			cache = append(cache, []KV{
				// symbols: clock_gettime, mallopt
				{"COMPILER_RT_INCLUDE_TESTS", "OFF"},

				{"LLVM_BUILD_TESTS", "ON"},
				{"LLVM_LIT_ARGS", litArgs(true, skipChecks...)},
			}...)
		}

		return &meta, t.New("llvm", meta.Version, source, &PackageAttr{
			Exclusive: true,
			Bin: []string{
				"chmod",
				"mkdir",
				"rm",
				"tr",
				"awk",
			},

			ScriptEarly: `
rm -f /system/bin/clang++.cfg
`,
		}, &CMakeHelper{
			Append: []string{"llvm"},

			Cache: cache,
			Script: `
ln -s ld.lld /work/system/bin/ld
ln -s clang /work/system/bin/cc
ln -s clang /work/system/bin/cpp
ln -s clang++ /work/system/bin/c++
cat << EOF > /work/system/bin/clang++.cfg
-static-libstdc++
-Wl,--exclude-libs,libunwind.a:libc++.a:libc++experimental.a:libc++abi.a
EOF
`,

			// LLVM_LINK_LLVM_DYLIB causes llvm test suite to leak system
			// installation into test environment, and the tests end up testing the
			// system installation instead. Tests are disabled on stage0 and relies
			// on 3-stage determinism to test later stages.
			SkipTest: t.stage.isStage0(),

			Test: "ninja " + jobsFlagE + " " + loadFlagE + " check-all",
		},
			_pythonEarly,
			_perl,
			_diffutils,
			_bash,
			_awk,
			_coreutils,
			_findutils,

			_zlib,
			_zstd,
			early,
			_kernelHeaders,
		)

	})
}
