/**
 * Declarative module YAML schema definition and validation.
 *
 * Defines the structure of .module.yaml files that produce
 * runtime-generated Fastify CRUD routes with no TypeScript needed.
 */

import { z } from 'zod';

// ── Field types supported in YAML definitions ─────────────────────────────

const FieldTypeEnum = z.enum(['string', 'number', 'boolean', 'date', 'array', 'enum']);

// ── Field definition ──────────────────────────────────────────────────────

export const FieldSchema = z.object({
  type: FieldTypeEnum,
  required: z.boolean().default(false),
  default: z.any().optional(),
  min: z.number().optional(),
  max: z.number().optional(),
  minLength: z.number().optional(),
  maxLength: z.number().optional(),
  values: z.array(z.string()).optional(), // for enum type
  items: z.string().optional(), // for array type (item type)
  description: z.string().optional(),
});

// ── Endpoint definition ───────────────────────────────────────────────────

export const EndpointSchema = z.object({
  method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
  path: z.string().min(1),
  auth: z.enum(['none', 'user', 'admin']).default('user'),
  custom: z.boolean().default(false),
  description: z.string().optional(),
});

// ── Module definition (top-level YAML structure) ──────────────────────────

export const ModuleSchema = z.object({
  name: z.string().min(1).max(100),
  container: z.string().min(1).max(100),
  partitionKey: z.string().min(1).max(100).default('/productId'),
  idPrefix: z.string().max(20).default(''),
  fields: z.record(z.string(), FieldSchema),
  endpoints: z.array(EndpointSchema).optional(),
  description: z.string().optional(),
});

// ── Inferred types ────────────────────────────────────────────────────────

export type FieldDef = z.infer<typeof FieldSchema>;
export type EndpointDef = z.infer<typeof EndpointSchema>;
export type ModuleDef = z.infer<typeof ModuleSchema>;

// ── Helpers ───────────────────────────────────────────────────────────────

/**
 * Convert a FieldDef to a Zod schema for runtime validation.
 */
export function fieldToZodSchema(name: string, field: FieldDef): z.ZodTypeAny {
  let schema: z.ZodTypeAny;

  switch (field.type) {
    case 'string': {
      let s = z.string();
      if (field.minLength !== undefined) s = s.min(field.minLength);
      if (field.maxLength !== undefined) s = s.max(field.maxLength);
      schema = s;
      break;
    }
    case 'number': {
      let n = z.number();
      if (field.min !== undefined) n = n.min(field.min);
      if (field.max !== undefined) n = n.max(field.max);
      schema = n;
      break;
    }
    case 'boolean':
      schema = z.boolean();
      break;
    case 'date':
      schema = z.string().datetime({ offset: true }).or(z.string().datetime());
      break;
    case 'enum':
      if (!field.values || field.values.length === 0) {
        throw new Error(`Field "${name}" is enum but has no values`);
      }
      schema = z.enum(field.values as [string, ...string[]]);
      break;
    case 'array':
      schema = z.array(z.any());
      break;
    default:
      schema = z.any();
  }

  if (field.default !== undefined) {
    // .default() already handles undefined → value, so no need for .optional()
    schema = schema.default(field.default);
  } else if (!field.required) {
    schema = schema.optional();
  }

  return schema;
}

/**
 * Build a Zod object schema for create operations from a ModuleDef.
 */
export function buildCreateSchema(mod: ModuleDef): z.ZodObject<Record<string, z.ZodTypeAny>> {
  const shape: Record<string, z.ZodTypeAny> = {};
  for (const [name, field] of Object.entries(mod.fields)) {
    shape[name] = fieldToZodSchema(name, field);
  }
  return z.object(shape);
}

/**
 * Build a Zod object schema for update operations (all fields optional).
 */
export function buildUpdateSchema(mod: ModuleDef): z.ZodObject<Record<string, z.ZodTypeAny>> {
  const shape: Record<string, z.ZodTypeAny> = {};
  for (const [name, field] of Object.entries(mod.fields)) {
    // For updates, make everything optional
    let schema = fieldToZodSchema(name, { ...field, required: false });
    // Remove default on updates
    if (field.default !== undefined) {
      schema = fieldToZodSchema(name, { ...field, required: false, default: undefined });
    }
    shape[name] = schema;
  }
  return z.object(shape);
}

/**
 * Generate default CRUD endpoints for a module if none are specified.
 */
export function defaultEndpoints(mod: ModuleDef): EndpointDef[] {
  const base = `/${mod.name}`;
  return [
    { method: 'GET', path: base, auth: 'user', custom: false, description: `List ${mod.name}` },
    {
      method: 'GET',
      path: `${base}/:id`,
      auth: 'user',
      custom: false,
      description: `Get ${mod.name} by ID`,
    },
    { method: 'POST', path: base, auth: 'user', custom: false, description: `Create ${mod.name}` },
    {
      method: 'PATCH',
      path: `${base}/:id`,
      auth: 'user',
      custom: false,
      description: `Update ${mod.name}`,
    },
    {
      method: 'DELETE',
      path: `${base}/:id`,
      auth: 'admin',
      custom: false,
      description: `Delete ${mod.name}`,
    },
  ];
}
