import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";

import Ajv, { type ErrorObject, type ValidateFunction } from "ajv";
import Ajv2020 from "ajv/dist/2020.js";
import addFormats from "ajv-formats";

export type SchemaName =
  | "agent"
  | "task"
  | "review"
  | "session"
  | "skill"
  | "tmpa-governance-object"
  | "tmpa-lifecycle-profile"
  | "tmpa-reader-result"
  | "tmpa-conformance-result"
  | "tmpa-s0.5-governance-object"
  | "tmpa-s0.5-lifecycle-profile"
  | "tmpa-s0.5-reader-result"
  | "tmpa-s0.5-conformance-result"
  | "tmpa-s0.6-governance-object"
  | "tmpa-s0.6-lifecycle-profile"
  | "tmpa-s0.6-reader-result"
  | "tmpa-s0.6-conformance-result"
  | "tmpa-s1.0-governance-object"
  | "tmpa-s1.0-lifecycle-profile"
  | "tmpa-s1.0-reader-result"
  | "tmpa-s1.0-conformance-result";

const SCHEMA_NAMES: SchemaName[] = [
  "agent",
  "task",
  "review",
  "session",
  "skill",
  "tmpa-governance-object",
  "tmpa-lifecycle-profile",
  "tmpa-reader-result",
  "tmpa-conformance-result",
  "tmpa-s0.5-governance-object",
  "tmpa-s0.5-lifecycle-profile",
  "tmpa-s0.5-reader-result",
  "tmpa-s0.5-conformance-result",
  "tmpa-s0.6-governance-object",
  "tmpa-s0.6-lifecycle-profile",
  "tmpa-s0.6-reader-result",
  "tmpa-s0.6-conformance-result",
  "tmpa-s1.0-governance-object",
  "tmpa-s1.0-lifecycle-profile",
  "tmpa-s1.0-reader-result",
  "tmpa-s1.0-conformance-result",
];

const SCHEMA_PATHS: Record<SchemaName, string[]> = {
  agent: ["agent.schema.json"],
  task: ["task.schema.json"],
  review: ["review.schema.json"],
  session: ["session.schema.json"],
  skill: ["skill.schema.json"],
  "tmpa-governance-object": ["tmpa", "s1.0", "governance-object.schema.json"],
  "tmpa-lifecycle-profile": ["tmpa", "s1.0", "lifecycle-profile.schema.json"],
  "tmpa-reader-result": ["tmpa", "s1.0", "reader-result.schema.json"],
  "tmpa-conformance-result": ["tmpa", "s1.0", "conformance-result.schema.json"],
  "tmpa-s0.5-governance-object": ["tmpa", "s0.5", "governance-object.schema.json"],
  "tmpa-s0.5-lifecycle-profile": ["tmpa", "s0.5", "lifecycle-profile.schema.json"],
  "tmpa-s0.5-reader-result": ["tmpa", "s0.5", "reader-result.schema.json"],
  "tmpa-s0.5-conformance-result": ["tmpa", "s0.5", "conformance-result.schema.json"],
  "tmpa-s0.6-governance-object": ["tmpa", "s0.6", "governance-object.schema.json"],
  "tmpa-s0.6-lifecycle-profile": ["tmpa", "s0.6", "lifecycle-profile.schema.json"],
  "tmpa-s0.6-reader-result": ["tmpa", "s0.6", "reader-result.schema.json"],
  "tmpa-s0.6-conformance-result": ["tmpa", "s0.6", "conformance-result.schema.json"],
  "tmpa-s1.0-governance-object": ["tmpa", "s1.0", "governance-object.schema.json"],
  "tmpa-s1.0-lifecycle-profile": ["tmpa", "s1.0", "lifecycle-profile.schema.json"],
  "tmpa-s1.0-reader-result": ["tmpa", "s1.0", "reader-result.schema.json"],
  "tmpa-s1.0-conformance-result": ["tmpa", "s1.0", "conformance-result.schema.json"],
};

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const SCHEMAS_DIR = path.resolve(__dirname, "..", "schemas");

const ajv = new Ajv({
  allErrors: true,
  strict: false,
});
addFormats(ajv);

const ajv2020 = new Ajv2020({
  allErrors: true,
  strict: true,
});
addFormats(ajv2020);

const validatorCache = new Map<SchemaName, ValidateFunction>();

export function loadSchemaSync(name: SchemaName): object {
  const schemaPath = path.join(SCHEMAS_DIR, ...SCHEMA_PATHS[name]);
  const raw = readFileSync(schemaPath, "utf-8");
  return JSON.parse(raw) as object;
}

export async function loadSchema(name: SchemaName): Promise<object> {
  return loadSchemaSync(name);
}

export function getValidatorSync(name: SchemaName): ValidateFunction {
  const cached = validatorCache.get(name);
  if (cached) return cached;
  const schema = loadSchemaSync(name);
  const schemaId = (schema as { $id?: unknown }).$id;
  const existing =
    name.startsWith("tmpa-") && typeof schemaId === "string"
      ? ajv2020.getSchema(schemaId)
      : undefined;
  const fn =
    existing ??
    (name.startsWith("tmpa-") ? ajv2020.compile(schema) : ajv.compile(schema));
  validatorCache.set(name, fn);
  return fn;
}

export async function getValidator(name: SchemaName): Promise<ValidateFunction> {
  return getValidatorSync(name);
}

export interface ValidationResult {
  valid: boolean;
  errors: ErrorObject[] | null;
}

export async function validate(
  name: SchemaName,
  data: unknown,
): Promise<ValidationResult> {
  const fn = await getValidator(name);
  const valid = fn(data) as boolean;
  return { valid, errors: valid ? null : fn.errors ?? null };
}

export function validateSync(
  name: SchemaName,
  data: unknown,
): ValidationResult {
  const fn = getValidatorSync(name);
  const valid = fn(data) as boolean;
  return { valid, errors: valid ? null : fn.errors ?? null };
}

export function isSchemaName(s: string): s is SchemaName {
  return (SCHEMA_NAMES as string[]).includes(s);
}
