import { z } from "zod";

export const creationSourceSchema = z
  .object({
    runId: z.string(),
    component: z.enum([
      "orchestrator",
      "detector",
      "classifier",
      "store",
      "migration",
      "import",
      "manual",
    ]),
    componentId: z.string().optional(),
  })
  .strict();

export type CreationSource = z.infer<typeof creationSourceSchema>;

export const recordEnvelopeSchema = z
  .object({
    schemaVersion: z.string(),
    id: z.string(),
    creationSource: creationSourceSchema,
  })
  .strict();

export type RecordEnvelope = z.infer<typeof recordEnvelopeSchema>;

export type SchemaErrorCode =
  | "validation"
  | "unsupported-version"
  | "unknown-field";

export class SchemaError extends Error {
  readonly path: string;
  readonly reason: string;
  readonly code: SchemaErrorCode;

  constructor(path: string, reason: string, code: SchemaErrorCode) {
    super(`Schema validation failed at ${path}: ${reason}`);
    this.name = "SchemaError";
    this.path = path;
    this.reason = reason;
    this.code = code;
  }
}

export interface RecordCodec<T> {
  readonly schema: z.ZodType<T>;
  readonly currentVersion: string;
  readonly supportedVersions: readonly string[];
}

export function defineRecordCodec<T>(config: {
  schema: z.ZodType<T>;
  currentVersion: string;
  supportedVersions?: readonly string[];
}): RecordCodec<T> {
  return {
    schema: config.schema,
    currentVersion: config.currentVersion,
    supportedVersions: config.supportedVersions ?? [config.currentVersion],
  };
}

function formatZodIssuePath(path: PropertyKey[]): string {
  if (path.length === 0) {
    return "$";
  }

  return path.reduce<string>((current, segment) => {
    if (typeof segment === "number") {
      return `${current}[${String(segment)}]`;
    }
    return current === "$" ? String(segment) : `${current}.${String(segment)}`;
  }, "$");
}

function schemaErrorCodeFromZodIssue(
  issue: z.core.$ZodIssue,
): SchemaErrorCode {
  if (issue.code === "unrecognized_keys") {
    return "unknown-field";
  }
  return "validation";
}

export function parseRecord<T>(codec: RecordCodec<T>, value: unknown): T {
  if (typeof value !== "object" || value === null) {
    throw new SchemaError("$", "expected object", "validation");
  }

  const record = value as Record<string, unknown>;
  const schemaVersion = record.schemaVersion;
  if (typeof schemaVersion !== "string") {
    throw new SchemaError("schemaVersion", "required string", "validation");
  }

  if (!codec.supportedVersions.includes(schemaVersion)) {
    throw new SchemaError(
      "schemaVersion",
      `unsupported schema version: ${schemaVersion}`,
      "unsupported-version",
    );
  }

  const result = codec.schema.safeParse(value);
  if (!result.success) {
    const issue = result.error.issues[0];
    if (!issue) {
      throw new SchemaError("$", "validation failed", "validation");
    }

    throw new SchemaError(
      formatZodIssuePath(issue.path),
      issue.message,
      schemaErrorCodeFromZodIssue(issue),
    );
  }

  return result.data;
}
