import { z } from "zod";

export const PROTOCOL_VERSION = "0.0.1";

// ============================================================================
// Brand redaction layer
//
// v0.1 is heuristic (string replace); v1 will run in gateway and operate on
// structured upstream errors. Migration plan in docs/proposals/.
//
// NOTE: this lives in index.ts (not errors.ts) because tsx 4.19 + ESM
// workspace symlinks misbehave on cross-file re-exports — single-file keeps
// dev mode working. See .learnings/ERRORS/ERR-20260510-003.
// ============================================================================

export const SERVICE_LABEL = "AI 服务";
export const MODEL_LABEL = "AI 模型";
export const GATEWAY_UNAVAILABLE = "AI 服务暂时不可用";
export const UNAUTHORIZED = "未授权";
export const INTERNAL_ERROR = `${SERVICE_LABEL}内部错误`;

// Order matters: longer terms before shorter so the alternation matches
// `api.anthropic.com` *before* `anthropic` swallows it.
const REDACTION_PATTERN = /api\.anthropic\.com|anthropic|claude/gi;
const REDACTION_MAP: Record<string, string> = {
  "api.anthropic.com": SERVICE_LABEL,
  "anthropic": SERVICE_LABEL,
  "claude": MODEL_LABEL,
};

export function brandSafeError(err: unknown): string {
  const raw = err instanceof Error ? err.message : String(err);
  const sanitized = raw.replace(
    REDACTION_PATTERN,
    (m) => REDACTION_MAP[m.toLowerCase()] ?? m,
  );
  return sanitized || INTERNAL_ERROR;
}

// ============================================================================
// WebSocket protocol
// ============================================================================

// Client → Server
export const ClientMessageSchema = z.discriminatedUnion("type", [
  z.object({
    type: z.literal("user_prompt"),
    /** Client-assigned id. Server echoes it on every related ServerMessage. */
    id: z.string(),
    prompt: z.string(),
  }),
  z.object({
    type: z.literal("abort"),
    id: z.string(),
  }),
]);
export type ClientMessage = z.infer<typeof ClientMessageSchema>;

// Server → Client
export const ServerMessageSchema = z.discriminatedUnion("type", [
  z.object({ type: z.literal("ready"), protocol: z.string() }),
  z.object({
    type: z.literal("turn_started"),
    id: z.string(),
    session_id: z.string(),
  }),
  z.object({
    type: z.literal("assistant_text"),
    id: z.string(),
    text: z.string(),
  }),
  z.object({
    type: z.literal("tool_use"),
    id: z.string(),
    tool_use_id: z.string(),
    tool: z.string(),
    input: z.unknown(),
  }),
  z.object({
    type: z.literal("tool_result"),
    id: z.string(),
    tool_use_id: z.string(),
    output: z.string(),
    is_error: z.boolean().default(false),
  }),
  z.object({
    type: z.literal("turn_done"),
    id: z.string(),
    success: z.boolean(),
    summary: z.string().optional(),
    usage: z
      .object({
        input_tokens: z.number().optional(),
        output_tokens: z.number().optional(),
        total_cost_usd: z.number().optional(),
      })
      .optional(),
  }),
  z.object({
    type: z.literal("error"),
    id: z.string().optional(),
    message: z.string(),
  }),
]);
export type ServerMessage = z.infer<typeof ServerMessageSchema>;

// ============================================================================
// Helpers
// ============================================================================

export const safeParseClientMessage = (raw: unknown) =>
  ClientMessageSchema.safeParse(raw);
export const safeParseServerMessage = (raw: unknown) =>
  ServerMessageSchema.safeParse(raw);

export type ParseResult<T> =
  | { success: true; data: T }
  | { success: false; reason: "invalid_json" | "invalid_schema" };

/** JSON-string → ClientMessage with single-shot parse + schema check. */
export function safeParseClientMessageJson(
  raw: string,
): ParseResult<ClientMessage> {
  let obj: unknown;
  try {
    obj = JSON.parse(raw);
  } catch {
    return { success: false, reason: "invalid_json" };
  }
  const r = ClientMessageSchema.safeParse(obj);
  return r.success
    ? { success: true, data: r.data }
    : { success: false, reason: "invalid_schema" };
}

/** JSON-string → ServerMessage with single-shot parse + schema check. */
export function safeParseServerMessageJson(
  raw: string,
): ParseResult<ServerMessage> {
  let obj: unknown;
  try {
    obj = JSON.parse(raw);
  } catch {
    return { success: false, reason: "invalid_json" };
  }
  const r = ServerMessageSchema.safeParse(obj);
  return r.success
    ? { success: true, data: r.data }
    : { success: false, reason: "invalid_schema" };
}

/** Generate a turn id (short, sortable, low-collision for demo scale). */
export function newTurnId(): string {
  const time = Date.now().toString(36);
  const rand = Math.random().toString(36).slice(2, 6);
  return `t_${time}_${rand}`;
}
