



json := import("json")

ll := import("@platforma-sdk/workflow-tengo:ll")
validation := import("@platforma-sdk/workflow-tengo:validation")
limits := import("@platforma-sdk/workflow-tengo:exec.limits")
execConstants := import("@platforma-sdk/workflow-tengo:exec.constants")

assets := import("@platforma-sdk/workflow-tengo:assets")
constants := import("@platforma-sdk/workflow-tengo:constants")
smart := import("@platforma-sdk/workflow-tengo:smart")
oop := import("@platforma-sdk/workflow-tengo:oop")
sets := import("@platforma-sdk/workflow-tengo:sets")
slices := import("@platforma-sdk/workflow-tengo:slices")
path := import("@platforma-sdk/workflow-tengo:path")
render := import("@platforma-sdk/workflow-tengo:render")
maps := import("@platforma-sdk/workflow-tengo:maps")

_WD_CREATE_FIELD_ALLOCATION := "allocation"
_WD_CREATE_FIELD_WORKDIR    := "workdir"

_WD_FILL_FIELD_WORKDIR_IN   := "workdirIn"
_WD_FILL_FIELD_WORKDIR_OUT  := "workdirOut"









createV2 := func(allocationRef) {
	wdCreate := smart.ephemeralBuilder(constants.RTYPE_WORKDIR_CREATE_2).lockAndBuild()
	wdCreate.getField(_WD_CREATE_FIELD_ALLOCATION).set(allocationRef)

	return wdCreate.getField(_WD_CREATE_FIELD_WORKDIR)
}
















_getFileFillRule := func(filePath, extension, writable) {
	return {
		type: "file",
		path: filePath,
		permissions: writable ? 0o600 : 0o400,
		pathKey: filePath,
		blobKey: filePath,
		copyOptional: false,
		fileExtension: extension
	}
}

_getArchiveFillRule := func(dstDir, whatToExtract, archiveBlobKey) {
	extractRules := []
	for f in whatToExtract {
		extractRules = append(extractRules, {
			srcPath: f,
			dstPath: f,
			filePerms: 0o400,
			dirPerms: 0o700
		})
	}

	return {
		type: "archive",
		path: dstDir,
		blobKey: archiveBlobKey,
		archiveType: "zip",
		extractRules: extractRules
	}
}







_getValueFillRule := func(filePath, writable) {
	return {
		type: "value",
		path: filePath,
		permissions: writable ? 0o600 : 0o400,
		pathKey: filePath,
		valueKey: filePath
	}
}







_getDirFillRule := func(dir) {
	return {
		type: "dir",
		path: dir,
		permissions: 0o700,
		pathKey: ""
	}
}





_fill := func() {
	wd := smart.ephemeralBuilder(constants.RTYPE_WORKDIR_FILL)


	_allocationRef := undefined
	blobs := {}
	files := {}
	zipExtractions := {}
	values := {}
	dirs := {}



	writableFiles := {}

	queue := undefined
	cpu := undefined
	ram := undefined

	self := undefined
	self = ll.toStrict(oop.inherit(wd, {
		_getAllDirs: func(fileName) {
			dirsWithFileName := path.getBasenameDirs(fileName)
			return slices.slice(dirsWithFileName, 0, -1)
		},

		_addDirs: func(fileName) {
			newDirs := self._getAllDirs(fileName)
			sets.add(dirs, newDirs...)
		},






		allocation: func(ref) {
			validation.assertType(ref, validation.reference, "workdir.builder: allocation should be a valid reference")
			_allocationRef = ref
			return self
		},






		inQueue: func(queueName) {
			queue = queueName
			return self
		},






		mem: func(value) {
			if is_undefined(value) {
				return self
			}
			validation.assertType(value, ["or", "number", "string"], "workdir.builder.mem: RAM amount should be a number or string")
			ll.assert(is_string(value) || value > 0, "workdir.builder.mem: amount in bytes should be greater than 0")
			ram = value
			return self
		},






		cpu: func(value) {
			if is_undefined(value) {
				return self
			}
			validation.assertType(value, "number", "workdir.builder.cpu: value must be a number")
			cpu = value
			return self
		},













		addFromZip: func(zipResource, destinationDir, pathsInArchive) {
			validation.assertType(zipResource, validation.reference, "workdir.builder.addFromZip: <zipResource> is not smart.reference")
			validation.assertType(destinationDir, "string", "workdir.builder.addFromZip: <destinationDir> is not a string. It must be a path inside working directory")

			if (is_undefined(pathsInArchive)) {
				pathsInArchive = []
			}
			validation.assertType(pathsInArchive, ["string"], "workdir.builder.addFromZip: <pathsInArchive> must contain list of strings with paths inside archive")

			blobKey := ll.idToString(zipResource.id)

			if is_undefined(blobs[blobKey]) {
				blobs[blobKey] = zipResource
			}

			if is_undefined(zipExtractions[blobKey]) {
				zipExtractions[blobKey] = {}
			}

			if is_undefined(zipExtractions[blobKey][destinationDir]) || len(pathsInArchive) == 0 {
				zipExtractions[blobKey][destinationDir] = pathsInArchive
				return self
			}

			if (len(pathsInArchive) > 0) {
				zipExtractions[blobKey][destinationDir] = append(zipExtractions[blobKey][destinationDir], pathsInArchive...)
			}

			return self
		},















		addFile: func(fileName, fileResource, ...opts) {
			validation.assertType(fileName, "string")
			validation.assertType(fileResource, validation.reference)

			files[fileName] = fileResource
			blobs[fileName] = fileResource
			self._addDirs(fileName)
			if len(opts) > 0 && ll.isMap(opts[0]) && opts[0].writable == true {
				sets.add(writableFiles, fileName)
			}

			return self
		},







		addFiles: func(fileMap, ...opts) {
			maps.forEach(fileMap, func(fileName, fileResource) {
				self.addFile(fileName, fileResource, opts...)
			})
			return self
		},











		writeFile: func(fileName, contentRef, ...opts) {
			validation.assertType(fileName, "string")
			values[fileName] = contentRef
			self._addDirs(fileName)
			if len(opts) > 0 && ll.isMap(opts[0]) && opts[0].writable == true {
				sets.add(writableFiles, fileName)
			}

			return self
		},







		writeFiles: func(contentRefMap, ...opts) {
			maps.forEach(contentRefMap, func(fileName, contentRef) {
				self.writeFile(fileName, contentRef, opts...)
			})
			return self
		},






		mkDir: func(dir) {
			sets.add(dirs, path.getBasenameDirs(dir))
			return self
		},


		mkDirs: func(dirs) {
			for _, dir in dirs {
				self.mkDir(dir)
			}

			return self
		},






		build: func() {
			ll.assert(!is_undefined(_allocationRef) || !is_undefined(queue),
				"workdir.builder: either allocation or queue must be provided")
			ll.assert(is_undefined(_allocationRef) || is_undefined(queue),
				"workdir.builder: allocation and queue must not be set at the same time")

			if !is_undefined(_allocationRef) {
				ll.assert(is_undefined(cpu) && is_undefined(ram),
					"workdir.builder: cpu and ram cannot be set when allocation is provided")
			}

			allocationRef := _allocationRef
			if is_undefined(allocationRef) {
				quotaBuilder := limits.quotaBuilder()
				if !is_undefined(cpu) {
					quotaBuilder.cpu(cpu)
				}
				if !is_undefined(ram) {
					quotaBuilder.ram(ram)
				}

				if limits.getResourceTypes(queue).runCommand.Name == "RunCommand/" + execConstants.RUNNER_BATCH {
					quotaBuilder.queue(execConstants.BATCH_QUEUE)
				} else {
					quotaBuilder.queue(queue)
				}

				storageSpaceRef := limits.storageSpaceRequestBuilder().
					quota(quotaBuilder.build()).
					build()

				allocationRef = storageSpaceRef.allocation
			}

			emptyWorkdir := createV2(allocationRef)

			wd.getField(_WD_FILL_FIELD_WORKDIR_IN).set(emptyWorkdir)

			rules := []
			for dir, _ in dirs {
				rules = append(rules, _getDirFillRule(dir))
			}
			for fileName, _ in files {
				rules = append(rules, _getFileFillRule(fileName, path.getExtension(fileName), sets.hasElement(writableFiles, fileName)))
			}
			for fileName, _ in values {
				rules = append(rules, _getValueFillRule(fileName, sets.hasElement(writableFiles, fileName)))
			}

			for blobKey, directories in zipExtractions {
				for dstDir, whatToExtract in directories {
					rules = append(rules, _getArchiveFillRule(dstDir, whatToExtract, blobKey))
				}
			}

			rulesRes := smart.createValueResource(
				constants.RTYPE_WORKDIR_FILL_RULES,
				json.encode(rules)
			)
			self.getField("rules").set(rulesRes)

			self.getField("dataIn").set(smart.createBinaryMapResource(values))
			self.getField("blobsIn").set(smart.createBlobMapResource(blobs))

			return self.lockAndBuild().outputs()[_WD_FILL_FIELD_WORKDIR_OUT]
		}
	}))

	return self
}






builder := func() {
	return _fill()
}







save := func(workdir) {
	ll.assert(smart.isReference(workdir), "workdir must be a reference to a resource or field")


	files := {}

	filesContent := {}

	fileSets := {}

	fileSetsContent := {}

	self := undefined
	self = ll.toStrict({





		saveFile: func(fileName) {
			validation.assertType(fileName, "string")
			sets.add(files, fileName)
			return self
		},






		saveFileContent: func(fileName) {
			validation.assertType(fileName, "string")
			sets.add(filesContent, fileName)
			return self
		},







		saveFileSet: func(name, regex) {
			validation.assertType(regex, "string")
			ll.assert(is_undefined(fileSets[name]), "file set with name '", name, "' is already in added")
			fileSets[name] = regex
			return self
		},







		saveFileSetContent: func(name, regex) {
			validation.assertType(regex, "string")
			ll.assert(is_undefined(fileSetsContent[name]), "file set content with name '", name, "' is already in added")
			sets.add(fileSetsContent, regex)
			return self
		},






		build: func() {

			tpl := assets.importTemplate("@platforma-sdk/workflow-tengo:workdir.save")
			inputs := {
				workdir: workdir,
				files: files,
				filesContent: filesContent,
				fileSets: fileSets,
				fileSetsContent: fileSetsContent
			}

			wds := render.createEphemeral(tpl, inputs)

			return ll.toStrict({
				files: wds.output("files"),
				filesContent: wds.output("filesContent"),
				fileSets : wds.output("fileSets"),
				fileSetsContent : wds.output("fileSetsContent"),
				workdir: wds.output("workdir"),
				progress: wds.output("progress")
			})
		}
	})

	return self
}

export ll.toStrict({
	builder: builder,
	createV2: createV2,
	save: save
})
