// Injected into the HWLAB control-plane renderer. It intentionally uses the
// surrounding renderer's overlay, stepEnv, patchScript, YAML, and text values.
function patchPipelineTaskRuntime(task) {
  const governance = overlay.ciResourceGovernance?.build;
  const governed = governance && task.name === governance.matrixTaskName;
  if (governed) task.timeout = governance.taskTimeout;
  for (const sidecar of task.taskSpec?.sidecars || []) {
    if (overlay.buildkitSidecarImage && typeof sidecar.image === 'string' && sidecar.image.includes('buildkit')) sidecar.image = overlay.buildkitSidecarImage;
    if (governed) sidecar.computeResources = structuredClone(governance.sidecarResources);
  }
  for (const step of task.taskSpec?.steps || []) {
    if (step.image === overlay.toolsImage && overlay.toolsImagePullPolicy) step.imagePullPolicy = overlay.toolsImagePullPolicy;
    if (governed) step.computeResources = structuredClone(governance.stepResources);
    if (Array.isArray(step.env)) {
      for (const env of step.env) {
        if (Object.prototype.hasOwnProperty.call(stepEnv, env.name) && stepEnv[env.name] !== undefined) env.value = stepEnv[env.name];
      }
    }
    step.env = Array.isArray(step.env) ? step.env : [];
    const existingEnv = new Set(step.env.map((env) => env.name));
    for (const [name, value] of Object.entries(stepEnv)) {
      if (value !== undefined && !existingEnv.has(name)) step.env.push({ name, value });
    }
    if (typeof step.script === 'string') step.script = patchScript(step.script);
  }
}

function applyPipelineBuildQueue(doc) {
  const governance = overlay.ciResourceGovernance?.build;
  if (!governance) return;
  const tasks = Array.isArray(doc.spec.tasks) ? doc.spec.tasks : [];
  const task = tasks.find((candidate) => candidate?.name === governance.matrixTaskName);
  if (!task) return;
  const matrixParams = Array.isArray(task.matrix?.params) ? task.matrix.params : [];
  const serviceParam = matrixParams.find((param) => param?.name === 'service-id');
  if (!serviceParam || !Array.isArray(serviceParam.value) || serviceParam.value.length === 0) {
    throw new Error(`CI resource governance task ${governance.matrixTaskName} must declare a static service-id matrix`);
  }
  const maxParallel = governance.maxParallelServices;
  const publish = (task.taskSpec?.steps || []).find((step) => step?.name === 'publish');
  if (!publish || typeof publish.script !== 'string') throw new Error(`CI resource governance task ${governance.matrixTaskName} must contain a publish script`);
  const serviceIds = serviceParam.value.map((value) => String(value));
  let workerScript = publish.script
    .replaceAll('$(params.service-id)', '$service_id')
    .replaceAll('/workspace/service-work', '/workspace/service-work/$service_id')
    .replaceAll('"/tekton/results/" + name', 'process.env.HWLAB_TEKTON_RESULTS_DIR + "/" + name')
    .replaceAll('--tekton-results-dir /tekton/results', '--tekton-results-dir "$HWLAB_TEKTON_RESULTS_DIR"');
  if (workerScript.includes('/tekton/results')) {
    throw new Error(`CI resource governance task ${governance.matrixTaskName} contains an unsupported Tekton result path context`);
  }
  publish.script = [
    'set -eu',
    'build_service() {',
    '  service_id="$1"',
    '  mkdir -p "/workspace/service-work/$service_id" "/workspace/service-results-tekton/$service_id"',
    '  export HWLAB_TEKTON_RESULTS_DIR="/workspace/service-results-tekton/$service_id"',
    // Keep the worker body at column zero: POSIX heredoc terminators in the
    // generated publish script must not be indented by the function wrapper.
    workerScript,
    '}',
    `max_parallel=${maxParallel}`,
    'service_csv="$(params.services)"',
    'old_ifs="$IFS"',
    "IFS=','",
    'set -- $service_csv',
    'IFS="$old_ifs"',
    'failed=0',
    'while [ "$#" -gt 0 ]; do',
    '  pids=""',
    '  launched=0',
    '  while [ "$#" -gt 0 ] && [ "$launched" -lt "$max_parallel" ]; do',
    '    service_id="$1"',
    '    shift',
    '    (build_service "$service_id") &',
    '    pids="$pids $!"',
    '    launched=$((launched + 1))',
    '  done',
    '  for pid in $pids; do if ! wait "$pid"; then failed=1; fi; done',
    '  if [ "$failed" -ne 0 ]; then exit "$failed"; fi',
    'done',
  ].join('\n');
  task.params = (task.params || []).map((param) => param?.name === 'service-id' ? { name: 'services', value: '$(params.services)' } : param);
  if (!task.params.some((param) => param?.name === 'services')) task.params.push({ name: 'services', value: '$(params.services)' });
  task.taskSpec.params = (task.taskSpec.params || []).map((param) => param?.name === 'service-id' ? { ...param, name: 'services' } : param);
  task.taskSpec.results = [];
  delete task.matrix;
  doc.metadata.annotations['unidesk.ai/ci-build-queue-mode'] = 'single-taskrun-bounded-workers';
  doc.metadata.annotations['unidesk.ai/ci-build-service-count'] = String(serviceIds.length);
  doc.metadata.annotations['unidesk.ai/ci-build-batch-count'] = String(Math.ceil(serviceIds.length / maxParallel));
  doc.metadata.annotations['unidesk.ai/ci-build-max-parallel'] = String(maxParallel);
}

function patchManifestObject(doc) {
  if (!doc || typeof doc !== 'object') return false;
  if (doc.kind !== 'Pipeline' || !doc.spec) return false;
  const defaults = {
    'git-url': overlay.gitUrl,
    'git-read-url': overlay.gitReadUrl,
    'git-write-url': overlay.gitWriteUrl,
    'catalog-path': overlay.catalogPath,
    'runtime-path': overlay.runtimePath,
    'registry-prefix': overlay.registryPrefix,
  };
  for (const param of doc.spec?.params || []) {
    if (Object.prototype.hasOwnProperty.call(defaults, param.name)) param.default = defaults[param.name];
  }
  doc.metadata = doc.metadata || {};
  if (typeof overlay.pipelineName === 'string' && overlay.pipelineName.length > 0) doc.metadata.name = overlay.pipelineName;
  doc.metadata.annotations = doc.metadata.annotations || {};
  doc.metadata.annotations['hwlab.pikastech.local/download-profile'] = overlay.downloadProfileId;
  doc.metadata.annotations['hwlab.pikastech.local/network-profile'] = overlay.networkProfileId;
  if (overlay.ciResourceGovernance) {
    doc.metadata.annotations['unidesk.ai/ci-resource-governance-config-ref'] = overlay.ciResourceGovernance.configRef;
    doc.metadata.annotations['unidesk.ai/ci-dedupe-identity'] = overlay.ciResourceGovernance.dedupe.identityFields.join('+');
  }
  for (const task of doc.spec?.tasks || []) patchPipelineTaskRuntime(task);
  applyPipelineBuildQueue(doc);
  return true;
}

function patchStructuredPipeline() {
  try {
    const doc = JSON.parse(text);
    if (!patchManifestObject(doc)) return false;
    text = JSON.stringify(doc, null, 2) + '\n';
    return true;
  } catch (error) {
    if (String(error?.message || '').includes('CI resource governance')) throw error;
  }
  if (YAML) {
    try {
      const docs = YAML.parseAllDocuments(text).map((document) => document.toJS()).filter((doc) => doc !== null);
      const changed = docs.some((doc) => patchManifestObject(doc));
      if (!changed) return false;
      text = docs.map((doc) => YAML.stringify(doc).trimEnd()).join('\n---\n') + '\n';
      return true;
    } catch (error) {
      if (String(error?.message || '').includes('CI resource governance')) throw error;
    }
  }
  return false;
}
