import { z } from 'zod';

const CapturePayload = z.object({
  noteId: z.string().min(1),
  rawTextLength: z.number().int().nonnegative(),
  hasMedia: z.boolean()
}).strict();

const AiSucceededPayload = z.object({
  noteId: z.string().min(1),
  durationMs: z.number().nonnegative(),
  attempts: z.number().int().nonnegative()
}).strict();

export const AiFailedReasonSchema = z.enum(['unreachable', 'schema', 'timeout', 'other']);
export type AiFailedReason = z.infer<typeof AiFailedReasonSchema>;

const AiFailedPayload = z.object({
  noteId: z.string().min(1),
  reason: AiFailedReasonSchema,
  attempts: z.number().int().nonnegative()
}).strict();

const NoteIdPayload = z.object({
  noteId: z.string().min(1)
}).strict();

const EmptyTrashPayload = z.object({
  count: z.number().int().nonnegative()
}).strict();

const ExpiredBannerShownPayload = z.object({
  candidateCount: z.number().int().nonnegative()
}).strict();

const ExpiredBatchTrashPayload = z.object({
  count: z.number().int().nonnegative()
}).strict();

const OllamaUnreachablePayload = z.object({
  reason: z.string().min(1).max(500)
}).strict();

const OllamaRecoveredPayload = z.object({
  downtimeMs: z.number().nonnegative()
}).strict();

const EmptyPayload = z.object({}).strict();

const AiRetryManualPayload = z.object({
  failedCount: z.number().int().positive()
}).strict();

const TagVocabHitPayload = z.object({
  tagId: z.number().int().positive(),
  vocabSize: z.number().int().nonnegative()
}).strict();

const TagVocabMissPayload = z.object({
  vocabSize: z.number().int().nonnegative()
}).strict();

const RecallShownPayload = z.object({
  noteId: z.string().min(1),
  ageDays: z.number().int().nonnegative()
}).strict();

export const TelemetryEventSchema = z.discriminatedUnion('kind', [
  z.object({ ts: z.string(), kind: z.literal('capture'), payload: CapturePayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('ai_succeeded'), payload: AiSucceededPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('ai_failed'), payload: AiFailedPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('trash'), payload: NoteIdPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('restore'), payload: NoteIdPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('permanent_delete'), payload: NoteIdPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('empty_trash'), payload: EmptyTrashPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('expired_banner_shown'), payload: ExpiredBannerShownPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('expired_batch_trash'), payload: ExpiredBatchTrashPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('ollama_unreachable'), payload: OllamaUnreachablePayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('ollama_recovered'), payload: OllamaRecoveredPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('ollama_recheck_manual'), payload: EmptyPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('ai_retry_manual'), payload: AiRetryManualPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('tag_vocab_hit'), payload: TagVocabHitPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('tag_vocab_miss'), payload: TagVocabMissPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('recall_shown'), payload: RecallShownPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('recall_opened'), payload: NoteIdPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('recall_dismissed'), payload: NoteIdPayload }).strict(),
  z.object({ ts: z.string(), kind: z.literal('recall_snoozed'), payload: NoteIdPayload }).strict()
]);

export type TelemetryEvent = z.infer<typeof TelemetryEventSchema>;
export type TelemetryKind = TelemetryEvent['kind'];

export function validateEvent(raw: unknown): TelemetryEvent {
  return TelemetryEventSchema.parse(raw);
}

/**
 * v0.2.6 #21 — type predicate helper. payload.noteId 가 있는 event kind 만 narrow.
 * union 확장 시 NO_NOTE_ID_KINDS Set 한 곳만 갱신.
 */
const NO_NOTE_ID_KINDS = new Set<TelemetryKind>([
  'empty_trash',
  'expired_banner_shown',
  'expired_batch_trash',
  'ollama_unreachable',
  'ollama_recovered',
  'ollama_recheck_manual',
  'ai_retry_manual',
  'tag_vocab_hit',
  'tag_vocab_miss'
]);

export function hasNoteId(ev: TelemetryEvent): ev is Extract<TelemetryEvent, { payload: { noteId: string } }> {
  return !NO_NOTE_ID_KINDS.has(ev.kind);
}
