/**
 * ProxyModel definition schema.
 * Defines the structure of proxymodel YAML files and provides
 * runtime validation.
 */
import type { ContentType } from './types.js';

/** Single stage reference within a proxymodel pipeline. */
export interface StageSpec {
  type: string;
  config?: Record<string, unknown>;
}

/** Parsed and validated proxymodel definition. */
export interface ProxyModelDefinition {
  kind: 'ProxyModel';
  metadata: { name: string };
  spec: {
    controller: string;
    controllerConfig?: Record<string, unknown>;
    stages: StageSpec[];
    appliesTo: ContentType[];
    cacheable: boolean;
  };
  /** Where this model was loaded from. */
  source: 'built-in' | 'local';
}

/** Validate a raw parsed object as a ProxyModelDefinition. */
export function validateProxyModel(raw: unknown, source: 'built-in' | 'local' = 'local'): ProxyModelDefinition {
  if (!raw || typeof raw !== 'object') {
    throw new Error('ProxyModel must be an object');
  }

  const obj = raw as Record<string, unknown>;

  if (obj.kind !== undefined && obj.kind !== 'ProxyModel') {
    throw new Error(`Invalid kind: expected 'ProxyModel', got '${String(obj.kind)}'`);
  }

  // metadata.name
  const metadata = obj.metadata as Record<string, unknown> | undefined;
  if (!metadata || typeof metadata !== 'object' || typeof metadata.name !== 'string' || !metadata.name) {
    throw new Error('ProxyModel must have metadata.name (string)');
  }
  const name = metadata.name;

  // spec
  const spec = obj.spec as Record<string, unknown> | undefined;
  if (!spec || typeof spec !== 'object') {
    throw new Error('ProxyModel must have a spec object');
  }

  // spec.controller
  const controller = typeof spec.controller === 'string' ? spec.controller : 'gate';

  // spec.controllerConfig
  let controllerConfig: Record<string, unknown> | undefined;
  if (spec.controllerConfig !== undefined && spec.controllerConfig !== null) {
    if (typeof spec.controllerConfig !== 'object') {
      throw new Error('spec.controllerConfig must be an object');
    }
    controllerConfig = spec.controllerConfig as Record<string, unknown>;
  }

  // spec.stages
  if (!Array.isArray(spec.stages) || spec.stages.length === 0) {
    throw new Error('spec.stages must be a non-empty array');
  }
  const stages: StageSpec[] = spec.stages.map((s: unknown, i: number) => {
    if (!s || typeof s !== 'object') {
      throw new Error(`spec.stages[${i}] must be an object`);
    }
    const stage = s as Record<string, unknown>;
    if (typeof stage.type !== 'string' || !stage.type) {
      throw new Error(`spec.stages[${i}].type must be a non-empty string`);
    }
    const result: StageSpec = { type: stage.type };
    if (stage.config !== undefined && stage.config !== null) {
      if (typeof stage.config !== 'object') {
        throw new Error(`spec.stages[${i}].config must be an object`);
      }
      result.config = stage.config as Record<string, unknown>;
    }
    return result;
  });

  // spec.appliesTo
  const validContentTypes: ContentType[] = ['prompt', 'toolResult', 'resource'];
  let appliesTo: ContentType[];
  if (spec.appliesTo !== undefined) {
    if (!Array.isArray(spec.appliesTo)) {
      throw new Error('spec.appliesTo must be an array');
    }
    for (const ct of spec.appliesTo) {
      if (!validContentTypes.includes(ct as ContentType)) {
        throw new Error(`Invalid appliesTo value '${String(ct)}'. Must be one of: ${validContentTypes.join(', ')}`);
      }
    }
    appliesTo = spec.appliesTo as ContentType[];
  } else {
    appliesTo = ['prompt', 'toolResult'];
  }

  // spec.cacheable
  const cacheable = spec.cacheable !== undefined ? Boolean(spec.cacheable) : true;

  const result: ProxyModelDefinition = {
    kind: 'ProxyModel',
    metadata: { name },
    spec: {
      controller,
      stages,
      appliesTo,
      cacheable,
    },
    source,
  };

  if (controllerConfig) {
    result.spec.controllerConfig = controllerConfig;
  }

  return result;
}
