import { readFileSync } from "node:fs";

import { rootPath } from "./config";

export const selfMediaConfigPath = rootPath("config", "selfmedia.yaml");
export const selfMediaConfigRef = "config/selfmedia.yaml";

export interface SelfMediaDeliveryTarget {
  readonly id: string;
  readonly node: string;
  readonly lane: string;
  readonly route: string;
  readonly ci: {
    readonly namespace: string;
    readonly pipeline: string;
    readonly pipelineRunPrefix: string;
    readonly serviceAccountName: string;
    readonly serviceAccountAutomount: false;
    readonly roleBindingName: string;
    readonly parameterBinding: "repository" | "literal";
    readonly workspaceSize: string;
    readonly pipelineTimeout: string;
    readonly toolImage: string;
    readonly buildkitImage: string;
  };
  readonly source: {
    readonly repository: string;
    readonly worktreeRemote: string;
    readonly branch: string;
    readonly readUrl: string;
    readonly snapshotPrefix: string;
  };
  readonly build: Record<string, unknown> & {
    readonly dockerfile: string;
    readonly imageRepository: string;
    readonly networkMode: string;
    readonly envReuse: {
      readonly enabled: true;
      readonly mode: "buildkit-state-host-path";
      readonly statePath: string;
      readonly identityFiles: readonly string[];
      readonly semanticPackageManifests: {
        readonly policyFile: string;
        readonly renderer: string;
      };
    };
    readonly proxy: Record<string, unknown>;
  };
  readonly gitops: Record<string, unknown> & {
    readonly readUrl: string;
    readonly writeUrl: string;
    readonly branch: string;
    readonly manifestPath: string;
    readonly releaseStatePath: string;
    readonly credentialSecretName: string;
    readonly credentialTokenKey: string;
    readonly credentialUsername: string;
  };
  readonly deployment: Record<string, unknown>;
  readonly webProbe: SelfMediaWebProbeSpec;
}

export interface SelfMediaWebProbeSpec {
  readonly enabled: boolean;
  readonly productId: string;
  readonly runner: {
    readonly node: string;
    readonly lane: string;
  };
  readonly origin: {
    readonly name: "public";
    readonly mode: "public";
    readonly baseUrl: string;
    readonly browserProxyMode: "auto" | "direct";
  };
  readonly authentication: {
    readonly mode: "json-post-session-cookie";
    readonly path: string;
    readonly credentials: {
      readonly configRef: string;
      readonly targetId: string;
      readonly secretName: string;
      readonly usernameTargetKey: string;
      readonly passwordTargetKey: string;
    };
    readonly requestFields: {
      readonly username: string;
      readonly password: string;
    };
    readonly cookieNames: readonly string[];
  };
  readonly defaultSmokeProfile: string;
  readonly smokeProfiles: Readonly<Record<string, SelfMediaWebProbeSmokeProfileSpec>>;
}

export interface SelfMediaWebProbeSmokeProfileSpec {
  readonly kind: "select-and-save";
  readonly routeTemplate: string;
  readonly readySelector: string;
  readonly identitySelector: string;
  readonly selectSelector: string;
  readonly saveButtonName: string;
  readonly statusSelector: string;
  readonly successTexts: readonly string[];
  readonly saveRequest: {
    readonly method: "PUT" | "POST" | "PATCH";
    readonly pathTemplate: string;
  };
  readonly screenshotName: string;
  readonly viewport: { readonly width: number; readonly height: number };
  readonly navigationTimeoutMs: number;
  readonly settleMs: number;
  readonly commandTimeoutSeconds: number;
  readonly outputLimits: {
    readonly options: number;
    readonly network: number;
    readonly failures: number;
  };
}

export interface SelfMediaCutoverTarget {
  readonly id: string;
  readonly mode: "host-process-to-kubernetes";
  readonly source: {
    readonly workspace: string;
    readonly publicAddress: string;
    readonly healthUrl: string;
    readonly stopCommand: readonly string[];
    readonly startCommand: readonly string[];
    readonly dataPaths: readonly string[];
    readonly excludes: readonly string[];
  };
  readonly destination: {
    readonly route: string;
    readonly namespace: string;
    readonly application: string;
    readonly publicAddress: string;
    readonly healthUrl: string;
    readonly dataClaim: string;
    readonly codexClaim: string;
  };
  readonly prepare: {
    readonly requiresFinalConfirmation: true;
    readonly phases: readonly string[];
  };
  readonly rollback: {
    readonly phases: readonly string[];
    readonly dataPolicy: string;
  };
}

interface SelfMediaConfig {
  readonly defaultTargetId: string;
  readonly deliveryTargets: Readonly<Record<string, SelfMediaDeliveryTarget>>;
  readonly cutoverTargets: Readonly<Record<string, SelfMediaCutoverTarget>>;
}

export function readSelfMediaConfig(): SelfMediaConfig {
  const root = record(Bun.YAML.parse(readFileSync(selfMediaConfigPath, "utf8")), selfMediaConfigRef);
  if (integer(root.version, "version") !== 1) throw new Error(`${selfMediaConfigRef}.version must be 1`);
  if (text(root.kind, "kind") !== "selfmedia-platform-delivery") throw new Error(`${selfMediaConfigRef}.kind must be selfmedia-platform-delivery`);
  const defaults = record(root.defaults, "defaults");
  const delivery = record(root.delivery, "delivery");
  const cutover = record(root.cutover, "cutover");
  const deliveryRecords = record(delivery.targets, "delivery.targets");
  const cutoverRecords = record(cutover.targets, "cutover.targets");
  const resolvedDeliveryRecords = resolveDeliveryRecords(deliveryRecords);
  const deliveryTargets = Object.fromEntries(Object.entries(resolvedDeliveryRecords).map(([id, value]) => [id, parseDeliveryTarget(id, value)]));
  const cutoverTargets = Object.fromEntries(Object.entries(cutoverRecords).map(([id, value]) => [id, parseCutoverTarget(id, record(value, `cutover.targets.${id}`))]));
  const defaultTargetId = text(defaults.targetId, "defaults.targetId");
  if (deliveryTargets[defaultTargetId] === undefined || cutoverTargets[defaultTargetId] === undefined) {
    throw new Error(`${selfMediaConfigRef}.defaults.targetId must resolve in delivery.targets and cutover.targets`);
  }
  return { defaultTargetId, deliveryTargets, cutoverTargets };
}

export function resolveSelfMediaDeliveryTarget(targetId?: string | null): SelfMediaDeliveryTarget {
  const config = readSelfMediaConfig();
  const id = targetId ?? config.defaultTargetId;
  const target = config.deliveryTargets[id];
  if (target === undefined) throw new Error(`unknown selfmedia delivery target ${id}; known targets: ${Object.keys(config.deliveryTargets).join(", ")}`);
  return target;
}

export function resolveSelfMediaDeliveryTargetByConfigRef(configRef: string): SelfMediaDeliveryTarget {
  const prefix = `${selfMediaConfigRef}#delivery.targets.`;
  if (!configRef.startsWith(prefix)) throw new Error(`selfmedia delivery configRef must start with ${prefix}`);
  return resolveSelfMediaDeliveryTarget(configRef.slice(prefix.length));
}

export function resolveSelfMediaCutoverTarget(targetId?: string | null): SelfMediaCutoverTarget {
  const config = readSelfMediaConfig();
  const id = targetId ?? config.defaultTargetId;
  const target = config.cutoverTargets[id];
  if (target === undefined) throw new Error(`unknown selfmedia cutover target ${id}; known targets: ${Object.keys(config.cutoverTargets).join(", ")}`);
  return target;
}

export function selfMediaDeliveryConfigRef(targetId: string): string {
  return `${selfMediaConfigRef}#delivery.targets.${targetId}`;
}

export function selfMediaEffectiveDeployment(target: SelfMediaDeliveryTarget): Record<string, unknown> {
  return {
    ...structuredClone(target.deployment),
    delivery: {
      enabled: true,
      source: {
        branch: target.source.branch,
        snapshotPrefix: target.source.snapshotPrefix,
        readUrl: target.source.readUrl,
      },
      build: structuredClone(target.build),
      gitops: structuredClone(target.gitops),
    },
  };
}

function parseDeliveryTarget(id: string, value: Record<string, unknown>): SelfMediaDeliveryTarget {
  const path = `delivery.targets.${id}`;
  const ci = record(value.ci, `${path}.ci`);
  const source = record(value.source, `${path}.source`);
  const build = record(value.build, `${path}.build`);
  const envReuse = record(build.envReuse, `${path}.build.envReuse`);
  const semanticPackageManifests = record(envReuse.semanticPackageManifests, `${path}.build.envReuse.semanticPackageManifests`);
  const proxy = record(build.proxy, `${path}.build.proxy`);
  const gitops = record(value.gitops, `${path}.gitops`);
  const deployment = record(value.deployment, `${path}.deployment`);
  const webProbe = parseSelfMediaWebProbe(value.webProbe, `${path}.webProbe`);
  const target = record(deployment.target, `${path}.deployment.target`);
  const publicExposure = record(deployment.publicExposure, `${path}.deployment.publicExposure`);
  const noProxy = stringArray(proxy.noProxy, `${path}.build.proxy.noProxy`);
  if (!noProxy.includes("hyueapi.com") || !noProxy.includes(".hyueapi.com")) throw new Error(`${path}.build.proxy.noProxy must preserve hyueapi.com and .hyueapi.com`);
  const node = text(value.node, `${path}.node`);
  if (text(target.id, `${path}.deployment.target.id`) !== id) throw new Error(`${path}.deployment.target.id must match ${id}`);
  if (text(source.repository, `${path}.source.repository`) !== "pikainc/selfmedia") throw new Error(`${path}.source.repository must be pikainc/selfmedia`);
  if (!text(source.snapshotPrefix, `${path}.source.snapshotPrefix`).startsWith("refs/unidesk/snapshots/")) throw new Error(`${path}.source.snapshotPrefix must be immutable`);
  if (boolean(ci.serviceAccountAutomount, `${path}.ci.serviceAccountAutomount`) !== false) throw new Error(`${path}.ci.serviceAccountAutomount must be false`);
  if (boolean(publicExposure.enabled, `${path}.deployment.publicExposure.enabled`) !== true) throw new Error(`${path}.deployment.publicExposure.enabled must be true`);
  if (integer(publicExposure.hostPort, `${path}.deployment.publicExposure.hostPort`) <= 0) throw new Error(`${path}.deployment.publicExposure.hostPort must be positive`);
  validateAccessSecret(deployment, `${path}.deployment`);
  const gitopsWriteUrl = text(gitops.writeUrl, `${path}.gitops.writeUrl`);
  if (!gitopsWriteUrl.startsWith("http://gitea-http.")) throw new Error(`${path}.gitops.writeUrl must use internal Gitea authority`);
  if (boolean(envReuse.enabled, `${path}.build.envReuse.enabled`) !== true) throw new Error(`${path}.build.envReuse.enabled must be true`);
  const envReuseMode = text(envReuse.mode, `${path}.build.envReuse.mode`);
  if (envReuseMode !== "buildkit-state-host-path") throw new Error(`${path}.build.envReuse.mode must be buildkit-state-host-path`);
  const identityFiles = stringArray(envReuse.identityFiles, `${path}.build.envReuse.identityFiles`)
    .map((item, index) => relativePath(item, `${path}.build.envReuse.identityFiles[${index}]`));
  if (identityFiles.length === 0 || new Set(identityFiles).size !== identityFiles.length) throw new Error(`${path}.build.envReuse.identityFiles must be non-empty and unique`);
  const packageManifestPolicyFile = relativePath(
    semanticPackageManifests.policyFile,
    `${path}.build.envReuse.semanticPackageManifests.policyFile`,
  );
  const packageManifestRenderer = relativePath(
    semanticPackageManifests.renderer,
    `${path}.build.envReuse.semanticPackageManifests.renderer`,
  );
  if (packageManifestPolicyFile === packageManifestRenderer) {
    throw new Error(`${path}.build.envReuse.semanticPackageManifests paths must be distinct`);
  }
  return {
    id,
    node,
    lane: text(value.lane, `${path}.lane`),
    route: text(value.route, `${path}.route`),
    ci: {
      namespace: kubernetesName(ci.namespace, `${path}.ci.namespace`),
      pipeline: kubernetesName(ci.pipeline, `${path}.ci.pipeline`),
      pipelineRunPrefix: kubernetesName(ci.pipelineRunPrefix, `${path}.ci.pipelineRunPrefix`),
      serviceAccountName: kubernetesName(ci.serviceAccountName, `${path}.ci.serviceAccountName`),
      serviceAccountAutomount: false,
      roleBindingName: kubernetesName(ci.roleBindingName, `${path}.ci.roleBindingName`),
      parameterBinding: enumValue(ci.parameterBinding, `${path}.ci.parameterBinding`, ["repository", "literal"] as const),
      workspaceSize: text(ci.workspaceSize, `${path}.ci.workspaceSize`),
      pipelineTimeout: text(ci.pipelineTimeout, `${path}.ci.pipelineTimeout`),
      toolImage: text(ci.toolImage, `${path}.ci.toolImage`),
      buildkitImage: text(ci.buildkitImage, `${path}.ci.buildkitImage`),
    },
    source: {
      repository: "pikainc/selfmedia",
      worktreeRemote: url(source.worktreeRemote, `${path}.source.worktreeRemote`),
      branch: text(source.branch, `${path}.source.branch`),
      readUrl: url(source.readUrl, `${path}.source.readUrl`),
      snapshotPrefix: text(source.snapshotPrefix, `${path}.source.snapshotPrefix`),
    },
    build: {
      ...structuredClone(build),
      dockerfile: relativePath(build.dockerfile, `${path}.build.dockerfile`),
      imageRepository: text(build.imageRepository, `${path}.build.imageRepository`),
      networkMode: text(build.networkMode, `${path}.build.networkMode`),
      envReuse: {
        enabled: true,
        mode: "buildkit-state-host-path",
        statePath: absolutePath(envReuse.statePath, `${path}.build.envReuse.statePath`),
        identityFiles,
        semanticPackageManifests: {
          policyFile: packageManifestPolicyFile,
          renderer: packageManifestRenderer,
        },
      },
      proxy: structuredClone(proxy),
    },
    gitops: {
      ...structuredClone(gitops),
      readUrl: url(gitops.readUrl, `${path}.gitops.readUrl`),
      writeUrl: url(gitops.writeUrl, `${path}.gitops.writeUrl`),
      branch: text(gitops.branch, `${path}.gitops.branch`),
      manifestPath: relativePath(gitops.manifestPath, `${path}.gitops.manifestPath`),
      releaseStatePath: relativePath(gitops.releaseStatePath, `${path}.gitops.releaseStatePath`),
      credentialSecretName: kubernetesName(gitops.credentialSecretName, `${path}.gitops.credentialSecretName`),
      credentialTokenKey: text(gitops.credentialTokenKey, `${path}.gitops.credentialTokenKey`),
      credentialUsername: text(gitops.credentialUsername, `${path}.gitops.credentialUsername`),
    },
    deployment: structuredClone(deployment),
    webProbe,
  };
}

function parseSelfMediaWebProbe(value: unknown, path: string): SelfMediaWebProbeSpec {
  const raw = record(value, path);
  const runner = record(raw.runner, `${path}.runner`);
  const origin = record(raw.origin, `${path}.origin`);
  const authentication = record(raw.authentication, `${path}.authentication`);
  const credentials = record(authentication.credentials, `${path}.authentication.credentials`);
  const requestFields = record(authentication.requestFields, `${path}.authentication.requestFields`);
  const profiles = record(raw.smokeProfiles, `${path}.smokeProfiles`);
  const smokeProfiles = Object.fromEntries(Object.entries(profiles).map(([id, profile]) => [
    simpleId(id, `${path}.smokeProfiles.${id}`),
    parseSelfMediaWebProbeProfile(profile, `${path}.smokeProfiles.${id}`),
  ]));
  const defaultSmokeProfile = simpleId(text(raw.defaultSmokeProfile, `${path}.defaultSmokeProfile`), `${path}.defaultSmokeProfile`);
  if (smokeProfiles[defaultSmokeProfile] === undefined) throw new Error(`${selfMediaConfigRef}.${path}.defaultSmokeProfile must reference smokeProfiles`);
  return {
    enabled: boolean(raw.enabled, `${path}.enabled`),
    productId: simpleId(text(raw.productId, `${path}.productId`), `${path}.productId`),
    runner: {
      node: simpleId(text(runner.node, `${path}.runner.node`), `${path}.runner.node`),
      lane: simpleId(text(runner.lane, `${path}.runner.lane`), `${path}.runner.lane`),
    },
    origin: {
      name: enumValue(origin.name, `${path}.origin.name`, ["public"] as const),
      mode: enumValue(origin.mode, `${path}.origin.mode`, ["public"] as const),
      baseUrl: url(origin.baseUrl, `${path}.origin.baseUrl`),
      browserProxyMode: enumValue(origin.browserProxyMode, `${path}.origin.browserProxyMode`, ["auto", "direct"] as const),
    },
    authentication: {
      mode: enumValue(authentication.mode, `${path}.authentication.mode`, ["json-post-session-cookie"] as const),
      path: httpPath(authentication.path, `${path}.authentication.path`),
      credentials: {
        configRef: relativePath(credentials.configRef, `${path}.authentication.credentials.configRef`),
        targetId: simpleId(text(credentials.targetId, `${path}.authentication.credentials.targetId`), `${path}.authentication.credentials.targetId`),
        secretName: simpleId(text(credentials.secretName, `${path}.authentication.credentials.secretName`), `${path}.authentication.credentials.secretName`),
        usernameTargetKey: secretKey(credentials.usernameTargetKey, `${path}.authentication.credentials.usernameTargetKey`),
        passwordTargetKey: secretKey(credentials.passwordTargetKey, `${path}.authentication.credentials.passwordTargetKey`),
      },
      requestFields: {
        username: jsonField(requestFields.username, `${path}.authentication.requestFields.username`),
        password: jsonField(requestFields.password, `${path}.authentication.requestFields.password`),
      },
      cookieNames: stringArray(authentication.cookieNames, `${path}.authentication.cookieNames`).map((item, index) => secretKey(item, `${path}.authentication.cookieNames[${index}]`)),
    },
    defaultSmokeProfile,
    smokeProfiles,
  };
}

function parseSelfMediaWebProbeProfile(value: unknown, path: string): SelfMediaWebProbeSmokeProfileSpec {
  const raw = record(value, path);
  const saveRequest = record(raw.saveRequest, `${path}.saveRequest`);
  const viewport = record(raw.viewport, `${path}.viewport`);
  const limits = record(raw.outputLimits, `${path}.outputLimits`);
  const routeTemplate = httpPathTemplate(raw.routeTemplate, `${path}.routeTemplate`);
  const requestTemplate = httpPathTemplate(saveRequest.pathTemplate, `${path}.saveRequest.pathTemplate`);
  if (!routeTemplate.includes("{editionId}") || !requestTemplate.includes("{editionId}")) {
    throw new Error(`${selfMediaConfigRef}.${path} route and request templates must contain {editionId}`);
  }
  const screenshotName = text(raw.screenshotName, `${path}.screenshotName`);
  if (!/^[A-Za-z0-9._-]+\.png$/u.test(screenshotName)) throw new Error(`${selfMediaConfigRef}.${path}.screenshotName must be a safe PNG filename`);
  return {
    kind: enumValue(raw.kind, `${path}.kind`, ["select-and-save"] as const),
    routeTemplate,
    readySelector: boundedText(raw.readySelector, `${path}.readySelector`, 500),
    identitySelector: boundedText(raw.identitySelector, `${path}.identitySelector`, 500),
    selectSelector: boundedText(raw.selectSelector, `${path}.selectSelector`, 500),
    saveButtonName: boundedText(raw.saveButtonName, `${path}.saveButtonName`, 100),
    statusSelector: boundedText(raw.statusSelector, `${path}.statusSelector`, 500),
    successTexts: stringArray(raw.successTexts, `${path}.successTexts`).map((item, index) => boundedText(item, `${path}.successTexts[${index}]`, 200)),
    saveRequest: {
      method: enumValue(saveRequest.method, `${path}.saveRequest.method`, ["PUT", "POST", "PATCH"] as const),
      pathTemplate: requestTemplate,
    },
    screenshotName,
    viewport: {
      width: positiveInteger(viewport.width, `${path}.viewport.width`),
      height: positiveInteger(viewport.height, `${path}.viewport.height`),
    },
    navigationTimeoutMs: positiveInteger(raw.navigationTimeoutMs, `${path}.navigationTimeoutMs`),
    settleMs: positiveInteger(raw.settleMs, `${path}.settleMs`),
    commandTimeoutSeconds: positiveInteger(raw.commandTimeoutSeconds, `${path}.commandTimeoutSeconds`),
    outputLimits: {
      options: positiveInteger(limits.options, `${path}.outputLimits.options`),
      network: positiveInteger(limits.network, `${path}.outputLimits.network`),
      failures: positiveInteger(limits.failures, `${path}.outputLimits.failures`),
    },
  };
}

function resolveDeliveryRecords(records: Record<string, unknown>): Record<string, Record<string, unknown>> {
  const resolved: Record<string, Record<string, unknown>> = {};
  const resolving = new Set<string>();
  const resolveOne = (id: string): Record<string, unknown> => {
    if (resolved[id] !== undefined) return resolved[id];
    if (resolving.has(id)) throw new Error(`${selfMediaConfigRef}.delivery.targets.${id}.extends contains a cycle`);
    const raw = record(records[id], `delivery.targets.${id}`);
    resolving.add(id);
    const parentId = raw.extends === undefined ? null : text(raw.extends, `delivery.targets.${id}.extends`);
    const own = Object.fromEntries(Object.entries(raw).filter(([key]) => key !== "extends"));
    const effective = parentId === null ? structuredClone(own) : deepMerge(resolveOne(parentId), own);
    resolving.delete(id);
    resolved[id] = effective;
    return effective;
  };
  for (const id of Object.keys(records)) resolveOne(id);
  return resolved;
}

function deepMerge(base: Record<string, unknown>, override: Record<string, unknown>): Record<string, unknown> {
  const result = structuredClone(base);
  for (const [key, value] of Object.entries(override)) {
    const current = result[key];
    result[key] = isPlainRecord(current) && isPlainRecord(value) ? deepMerge(current, value) : structuredClone(value);
  }
  return result;
}

function isPlainRecord(value: unknown): value is Record<string, unknown> {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}

function validateAccessSecret(deployment: Record<string, unknown>, path: string): void {
  const secrets = record(deployment.secrets, `${path}.secrets`);
  const access = record(secrets.access, `${path}.secrets.access`);
  const target = record(access.target, `${path}.secrets.access.target`);
  absolutePath(access.sourceRef, `${path}.secrets.access.sourceRef`);
  kubernetesName(target.secretName, `${path}.secrets.access.target.secretName`);
  if (boolean(target.readOnly, `${path}.secrets.access.target.readOnly`) !== true) {
    throw new Error(`${selfMediaConfigRef}.${path}.secrets.access.target.readOnly must be true`);
  }
  if (!Array.isArray(target.files) || target.files.length === 0) throw new Error(`${selfMediaConfigRef}.${path}.secrets.access.target.files must be a non-empty array`);
  const targetKeys = new Set<string>();
  const mountPaths = new Set<string>();
  target.files.forEach((value, index) => {
    const file = record(value, `${path}.secrets.access.target.files[${index}]`);
    const targetKey = text(file.targetKey, `${path}.secrets.access.target.files[${index}].targetKey`);
    const mountPath = absolutePath(file.mountPath, `${path}.secrets.access.target.files[${index}].mountPath`);
    if (targetKeys.has(targetKey)) throw new Error(`${selfMediaConfigRef}.${path}.secrets.access.target.files contains duplicate targetKey ${targetKey}`);
    if (mountPaths.has(mountPath)) throw new Error(`${selfMediaConfigRef}.${path}.secrets.access.target.files contains duplicate mountPath ${mountPath}`);
    targetKeys.add(targetKey);
    mountPaths.add(mountPath);
  });
}

function parseCutoverTarget(id: string, value: Record<string, unknown>): SelfMediaCutoverTarget {
  const path = `cutover.targets.${id}`;
  const source = record(value.source, `${path}.source`);
  const destination = record(value.destination, `${path}.destination`);
  const prepare = record(value.prepare, `${path}.prepare`);
  const rollback = record(value.rollback, `${path}.rollback`);
  const mode = text(value.mode, `${path}.mode`);
  if (mode !== "host-process-to-kubernetes") throw new Error(`${path}.mode must be host-process-to-kubernetes`);
  if (boolean(prepare.requiresFinalConfirmation, `${path}.prepare.requiresFinalConfirmation`) !== true) throw new Error(`${path}.prepare.requiresFinalConfirmation must be true`);
  return {
    id,
    mode,
    source: {
      workspace: absolutePath(source.workspace, `${path}.source.workspace`),
      publicAddress: url(source.publicAddress, `${path}.source.publicAddress`),
      healthUrl: url(source.healthUrl, `${path}.source.healthUrl`),
      stopCommand: stringArray(source.stopCommand, `${path}.source.stopCommand`),
      startCommand: stringArray(source.startCommand, `${path}.source.startCommand`),
      dataPaths: stringArray(source.dataPaths, `${path}.source.dataPaths`).map((item, index) => relativePath(item, `${path}.source.dataPaths[${index}]`)),
      excludes: stringArray(source.excludes, `${path}.source.excludes`).map((item, index) => relativePath(item, `${path}.source.excludes[${index}]`)),
    },
    destination: {
      route: text(destination.route, `${path}.destination.route`),
      namespace: kubernetesName(destination.namespace, `${path}.destination.namespace`),
      application: kubernetesName(destination.application, `${path}.destination.application`),
      publicAddress: url(destination.publicAddress, `${path}.destination.publicAddress`),
      healthUrl: url(destination.healthUrl, `${path}.destination.healthUrl`),
      dataClaim: kubernetesName(destination.dataClaim, `${path}.destination.dataClaim`),
      codexClaim: kubernetesName(destination.codexClaim, `${path}.destination.codexClaim`),
    },
    prepare: {
      requiresFinalConfirmation: true,
      phases: stringArray(prepare.phases, `${path}.prepare.phases`),
    },
    rollback: {
      phases: stringArray(rollback.phases, `${path}.rollback.phases`),
      dataPolicy: text(rollback.dataPolicy, `${path}.rollback.dataPolicy`),
    },
  };
}

function record(value: unknown, path: string): Record<string, unknown> {
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${selfMediaConfigRef}.${path} must be an object`);
  return value as Record<string, unknown>;
}

function text(value: unknown, path: string): string {
  if (typeof value !== "string" || value.length === 0 || value.includes("\n")) throw new Error(`${selfMediaConfigRef}.${path} must be a non-empty single-line string`);
  return value;
}

function boundedText(value: unknown, path: string, max: number): string {
  const result = text(value, path);
  if (result.length > max) throw new Error(`${selfMediaConfigRef}.${path} must be at most ${max} chars`);
  return result;
}

function simpleId(value: string, path: string): string {
  if (!/^[A-Za-z0-9._-]+$/u.test(value) || value.length > 100) throw new Error(`${selfMediaConfigRef}.${path} must be a simple id up to 100 chars`);
  return value;
}

function jsonField(value: unknown, path: string): string {
  const result = text(value, path);
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(result)) throw new Error(`${selfMediaConfigRef}.${path} must be a JSON field name`);
  return result;
}

function secretKey(value: unknown, path: string): string {
  const result = text(value, path);
  if (!/^[A-Za-z0-9._-]+$/u.test(result)) throw new Error(`${selfMediaConfigRef}.${path} must be a Secret key`);
  return result;
}

function httpPath(value: unknown, path: string): string {
  const result = text(value, path);
  if (!result.startsWith("/") || result.includes("\0") || result.includes("?") || result.includes("#")) {
    throw new Error(`${selfMediaConfigRef}.${path} must be an absolute HTTP path without query or fragment`);
  }
  return result;
}

function httpPathTemplate(value: unknown, path: string): string {
  return httpPath(value, path);
}

function integer(value: unknown, path: string): number {
  if (!Number.isInteger(value)) throw new Error(`${selfMediaConfigRef}.${path} must be an integer`);
  return value as number;
}

function positiveInteger(value: unknown, path: string): number {
  const result = integer(value, path);
  if (result <= 0) throw new Error(`${selfMediaConfigRef}.${path} must be positive`);
  return result;
}

function boolean(value: unknown, path: string): boolean {
  if (typeof value !== "boolean") throw new Error(`${selfMediaConfigRef}.${path} must be a boolean`);
  return value;
}

function enumValue<const T extends string>(value: unknown, path: string, allowed: readonly T[]): T {
  const result = text(value, path);
  if (!allowed.includes(result as T)) throw new Error(`${selfMediaConfigRef}.${path} must be one of ${allowed.join(", ")}`);
  return result as T;
}

function stringArray(value: unknown, path: string): string[] {
  if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.length === 0 || item.includes("\n"))) throw new Error(`${selfMediaConfigRef}.${path} must be an array of non-empty single-line strings`);
  return [...value] as string[];
}

function kubernetesName(value: unknown, path: string): string {
  const result = text(value, path);
  if (!/^[a-z0-9](?:[-a-z0-9]*[a-z0-9])?$/u.test(result) || result.length > 63) throw new Error(`${selfMediaConfigRef}.${path} must be a Kubernetes name`);
  return result;
}

function absolutePath(value: unknown, path: string): string {
  const result = text(value, path);
  if (!result.startsWith("/") || result.split("/").includes("..")) throw new Error(`${selfMediaConfigRef}.${path} must be an absolute path without ..`);
  return result;
}

function relativePath(value: unknown, path: string): string {
  const result = text(value, path);
  if (result.startsWith("/") || result.split("/").includes("..")) throw new Error(`${selfMediaConfigRef}.${path} must be a relative path without ..`);
  return result;
}

function url(value: unknown, path: string): string {
  const result = text(value, path);
  const parsed = new URL(result);
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`${selfMediaConfigRef}.${path} must use http or https`);
  if (parsed.username.length > 0 || parsed.password.length > 0) throw new Error(`${selfMediaConfigRef}.${path} must not contain credentials`);
  return result;
}
