//go:build rust
// +build rust

package lockfile

import (
	"encoding/json"
	"io"

	mapset "github.com/deckarep/golang-set"
	"github.com/vercel/turbo/cli/internal/ffi"
	"github.com/vercel/turbo/cli/internal/turbopath"
)

// NpmLockfile representation of package-lock.json
type NpmLockfile struct {
	// We just story the entire lockfile in memory and pass it for every call
	contents []byte
}

// ResolvePackage Given a workspace, a package it imports and version returns the key, resolved version, and if it was found
func (l *NpmLockfile) ResolvePackage(workspacePath turbopath.AnchoredUnixPath, name string, version string) (Package, error) {
	// This is only used when doing calculating the transitive deps, but Rust
	// implementations do this calculation on the Rust side.
	panic("Unreachable")
}

// AllDependencies Given a lockfile key return all (dev/optional/peer) dependencies of that package
func (l *NpmLockfile) AllDependencies(key string) (map[string]string, bool) {
	// This is only used when doing calculating the transitive deps, but Rust
	// implementations do this calculation on the Rust side.
	panic("Unreachable")
}

// Subgraph Given a list of lockfile keys returns a Lockfile based off the original one that only contains the packages given
func (l *NpmLockfile) Subgraph(workspacePackages []turbopath.AnchoredSystemPath, packages []string) (Lockfile, error) {
	workspaces := make([]string, len(workspacePackages))
	for i, workspace := range workspacePackages {
		workspaces[i] = workspace.ToUnixPath().ToString()
	}
	contents, err := ffi.NpmSubgraph(l.contents, workspaces, packages)
	if err != nil {
		return nil, err
	}
	return &NpmLockfile{contents: contents}, nil
}

// Encode the lockfile representation and write it to the given writer
func (l *NpmLockfile) Encode(w io.Writer) error {
	_, err := w.Write(l.contents)
	return err
}

// Patches return a list of patches used in the lockfile
func (l *NpmLockfile) Patches() []turbopath.AnchoredUnixPath {
	return nil
}

// GlobalChange checks if there are any differences between lockfiles that would completely invalidate
// the cache.
func (l *NpmLockfile) GlobalChange(other Lockfile) bool {
	o, ok := other.(*NpmLockfile)
	if !ok {
		return true
	}

	// We just grab the few global fields and check if they've changed
	type minimalJSON struct {
		LockfileVersion string `json:"version"`
		Requires        bool   `json:"requires"`
	}
	var self minimalJSON
	var otherJSON minimalJSON
	if err := json.Unmarshal(o.contents, &otherJSON); err != nil {
		return true
	}
	if err := json.Unmarshal(l.contents, &self); err != nil {
		return true
	}

	return self.LockfileVersion != otherJSON.LockfileVersion ||
		self.Requires != otherJSON.Requires
}

var _ (Lockfile) = (*NpmLockfile)(nil)

// DecodeNpmLockfile Parse contents of package-lock.json into NpmLockfile
func DecodeNpmLockfile(contents []byte) (Lockfile, error) {
	return &NpmLockfile{contents: contents}, nil
}

func npmTransitiveDeps(lockfile *NpmLockfile, workspacePath turbopath.AnchoredUnixPath, unresolvedDeps map[string]string) (mapset.Set, error) {
	pkgDir := workspacePath.ToString()

	packages, err := ffi.NpmTransitiveDeps(lockfile.contents, pkgDir, unresolvedDeps)
	if err != nil {
		return nil, err
	}

	deps := make([]interface{}, len(packages))
	for i, pkg := range packages {
		deps[i] = Package{
			Found:   pkg.Found,
			Key:     pkg.Key,
			Version: pkg.Version,
		}
	}

	return mapset.NewSetFromSlice(deps), nil
}
