import { z } from 'zod';
import {
  RpcCancelSchema,
  RpcErrorSchema,
  RpcRequestSchema,
  RpcResponseSchema,
  type RpcCancel,
  type RpcEnvelope,
  type RpcError,
  type RpcRequest,
  type RpcResponse,
} from '../schemas/envelope.js';
import { KIND_PREFIX } from './version.js';

/** Tagged classification of any incoming envelope. */
export type ClassifiedEnvelope =
  | { kind: 'request'; envelope: RpcRequest }
  | { kind: 'response'; envelope: RpcResponse }
  | { kind: 'error'; envelope: RpcError }
  | { kind: 'cancel'; envelope: RpcCancel };

const RpcAnySchema = z.union([
  RpcRequestSchema,
  RpcResponseSchema,
  RpcErrorSchema,
  RpcCancelSchema,
]);

/** Encode an envelope to JSON plaintext for `Shade.send`. */
export function encodeEnvelope(env: RpcEnvelope): string {
  return JSON.stringify(env);
}

/**
 * Quick-detection: does this plaintext look like an `@shade/files` envelope?
 * Used by `ShadeFileRpcChannel` to skip non-files messages cheaply.
 */
export function looksLikeFileEnvelope(plaintext: string): boolean {
  return plaintext.includes(KIND_PREFIX);
}

/**
 * Parse and classify an incoming plaintext. Returns null on any malformed
 * input — the channel ignores those silently (could be a different
 * protocol on the same Shade.send pipe).
 */
export function tryParseEnvelope(plaintext: string): ClassifiedEnvelope | null {
  let raw: unknown;
  try {
    raw = JSON.parse(plaintext);
  } catch {
    return null;
  }
  const result = RpcAnySchema.safeParse(raw);
  if (!result.success) return null;
  return classify(result.data);
}

export function classify(env: RpcEnvelope): ClassifiedEnvelope {
  if (env.kind === 'shade.fs.cancel/v1') {
    return { kind: 'cancel', envelope: env as RpcCancel };
  }
  if (env.kind === 'shade.fs.error/v1') {
    return { kind: 'error', envelope: env as RpcError };
  }
  if (env.kind.endsWith('.response')) {
    return { kind: 'response', envelope: env as RpcResponse };
  }
  return { kind: 'request', envelope: env as RpcRequest };
}
