// SPEC: PJ2026-01060703 CI/CD branch follower reuse config.
// Responsibility: native target-side parser for runtime/env reuse declarations.

import { createHash } from "node:crypto";

export const RUNTIME_REUSE_CONFIG_PATH = "gitops/reuse.ymal";

export function parseRuntimeReuseConfig(text, source) {
  const errors = [];
  let parsed = null;
  try {
    parsed = Bun.YAML.parse(text);
  } catch (error) {
    errors.push(`${RUNTIME_REUSE_CONFIG_PATH} YAML parse failed: ${error instanceof Error ? error.message : String(error)}`);
  }
  const root = asOptionalRecord(parsed);
  if (root === null) errors.push(`${RUNTIME_REUSE_CONFIG_PATH} must be a YAML object`);
  const spec = asOptionalRecord(root?.spec) ?? root ?? {};
  const services = parseServices(spec, errors);
  if (services.length === 0) errors.push(`${RUNTIME_REUSE_CONFIG_PATH}.spec.services must declare at least one service`);
  const kind = stringOrNull(root?.kind);
  if (kind !== null && kind !== "RuntimeReuseConfig") errors.push(`${RUNTIME_REUSE_CONFIG_PATH}.kind must be RuntimeReuseConfig`);
  return {
    ok: errors.length === 0,
    present: true,
    path: RUNTIME_REUSE_CONFIG_PATH,
    sourceCommit: source.sourceCommit,
    stageRef: source.stageRef,
    sha256: createHash("sha256").update(text).digest("hex"),
    apiVersion: stringOrNull(root?.apiVersion),
    kind,
    serviceCount: services.length,
    services,
    errors,
    valuesRedacted: true,
  };
}

export function summarizeRuntimeReuseConfig(config) {
  return {
    ok: config.ok,
    present: config.present,
    path: config.path,
    sourceCommit: config.sourceCommit,
    stageRef: config.stageRef,
    sha256: config.sha256,
    serviceCount: config.serviceCount,
    serviceIds: config.services.map((service) => service.id).sort(),
    errors: config.errors.slice(0, 5),
    valuesRedacted: true,
  };
}

function parseServices(spec, errors) {
  const raw = spec.services;
  const items = Array.isArray(raw)
    ? raw.map((value, index) => ({ id: stringOrNull(asOptionalRecord(value)?.id) ?? String(index), value, path: `services[${index}]` }))
    : Object.entries(asOptionalRecord(raw) ?? {}).map(([id, value]) => ({ id, value, path: `services.${id}` }));
  return items.flatMap(({ id, value, path }) => {
    const record = asOptionalRecord(value);
    if (record === null) {
      errors.push(`${RUNTIME_REUSE_CONFIG_PATH}.spec.${path} must be an object`);
      return [];
    }
    const serviceId = stringOrNull(record.id) ?? id;
    if (serviceId.length === 0) errors.push(`${RUNTIME_REUSE_CONFIG_PATH}.spec.${path}.id is required`);
    return [{
      id: serviceId,
      runtimeReuse: parseRuntimeReuse(asOptionalRecord(record.runtimeReuse), `${path}.runtimeReuse`, errors),
      envReuse: parseEnvReuse(asOptionalRecord(record.envReuse), `${path}.envReuse`, errors),
    }];
  });
}

function parseRuntimeReuse(record, path, errors) {
  if (record === null) return null;
  return {
    enabled: booleanOrNull(record.enabled),
    codeIdentityPaths: safePathArray(asOptionalRecord(record.codeIdentity)?.paths ?? record.codeIdentityPaths, `${path}.codeIdentity.paths`, errors),
    envIdentityPaths: safePathArray(asOptionalRecord(record.envIdentity)?.paths ?? record.envIdentityPaths, `${path}.envIdentity.paths`, errors),
  };
}

function parseEnvReuse(record, path, errors) {
  if (record === null) return null;
  return {
    enabled: booleanOrNull(record.enabled),
    mode: stringOrNull(record.mode),
    nodeDepsPath: stringOrNull(record.nodeDepsPath),
    envIdentityFiles: safePathArray(record.envIdentityFiles ?? asOptionalRecord(record.envIdentity)?.paths, `${path}.envIdentityFiles`, errors),
    buildArgs: stringRecord(asOptionalRecord(record.buildArgs), `${path}.buildArgs`, errors),
  };
}

function safePathArray(value, path, errors) {
  if (value === undefined || value === null) return [];
  if (!Array.isArray(value)) {
    errors.push(`${RUNTIME_REUSE_CONFIG_PATH}.spec.${path} must be a string array`);
    return [];
  }
  return value.flatMap((item, index) => {
    if (typeof item !== "string" || item.length === 0) {
      errors.push(`${RUNTIME_REUSE_CONFIG_PATH}.spec.${path}[${index}] must be a non-empty string`);
      return [];
    }
    if (item.startsWith("/") || item.includes("..")) {
      errors.push(`${RUNTIME_REUSE_CONFIG_PATH}.spec.${path}[${index}] must be a relative path without ..`);
      return [];
    }
    return [item];
  });
}

function stringRecord(record, path, errors) {
  if (record === null) return {};
  const out = {};
  for (const [key, value] of Object.entries(record)) {
    if (typeof value !== "string") errors.push(`${RUNTIME_REUSE_CONFIG_PATH}.spec.${path}.${key} must be a string`);
    else out[key] = value;
  }
  return out;
}

function asOptionalRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
}

function stringOrNull(value) {
  return typeof value === "string" && value.length > 0 ? value : null;
}

function booleanOrNull(value) {
  return typeof value === "boolean" ? value : null;
}
