import { z } from 'zod';

export const HumanSchema = z.object({
  id: z.string(),
  created_at: z.coerce.date(),
  email: z.string().email(),
  email_prefix: z.string(),
  email_notifications_enabled: z.boolean(),
  avatar_object_id: z.string().nullable().optional(),
});

export type Human = z.infer<typeof HumanSchema>;

export const NetworkSchema = z.object({
  id: z.string(),
  name: z.string(),
  admin_human: HumanSchema,
  humans: z.array(HumanSchema),
  created_at: z.coerce.date(),
});

export type Network = z.infer<typeof NetworkSchema>;

export const ListNetworksResponseSchema = z.array(NetworkSchema);
export type ListNetworksResponse = z.infer<typeof ListNetworksResponseSchema>;

// --- Network request/response types ---

export const CreateNetworkRequestSchema = z.object({
  name: z.string(),
});
export type CreateNetworkRequest = z.infer<typeof CreateNetworkRequestSchema>;

export const AddMembersRequestSchema = z.object({
  email_addresses: z.array(z.string().email()),
});
export type AddMembersRequest = z.infer<typeof AddMembersRequestSchema>;

// --- Invitation types ---

export const InvitationSchema = z.object({
  network_id: z.string(),
  network_name: z.string(),
  email: z.string(),
  created_at: z.coerce.date(),
});
export type Invitation = z.infer<typeof InvitationSchema>;

export const ListInvitationsResponseSchema = z.array(InvitationSchema);

export type AcceptInvitationRequest = { network_id: string };
export type RevokeInvitationRequest = { email: string };

// --- Depot types ---

export const PrepareUploadRequestSchema = z.object({
  network_id: z.string(),
  name: z.string(),
  content_type: z.string(),
  content_length: z.number(),
});
export type PrepareUploadRequest = z.infer<typeof PrepareUploadRequestSchema>;

export const PrepareUploadResponseSchema = z.object({
  object_id: z.string(),
  upload_url: z.string(),
  upload_headers: z.record(z.string(), z.string()),
});
export type PrepareUploadResponse = z.infer<typeof PrepareUploadResponseSchema>;

export const DepotObjectSchema = z.object({
  id: z.string(),
  name: z.string(),
  content_type: z.string(),
  content_length: z.number(),
  contains_content: z.boolean(),
  created_at: z.coerce.date(),
});
export type DepotObject = z.infer<typeof DepotObjectSchema>;

// --- Particle property schemas ---

export const StreamPropertiesSchema = z.object({
  name: z.string(),
  description: z.string().optional(),
});
export type StreamProperties = z.infer<typeof StreamPropertiesSchema>;

export const FolderPropertiesSchema = z.object({
  name: z.string(),
  color: z.string().optional(),
});
export type FolderProperties = z.infer<typeof FolderPropertiesSchema>;

const TranscriptWordSchema = z.object({
  word: z.string(),
  start: z.number(),
  end: z.number(),
});

const TranscriptSentenceSchema = z.object({
  text: z.string(),
  start: z.number(),
  end: z.number(),
});

const TranscriptParagraphSchema = z.object({
  sentences: z.array(TranscriptSentenceSchema),
  start: z.number(),
  end: z.number(),
});

export const TranscriptSchema = z.object({
  transcript: z.string(),
  words: z.array(TranscriptWordSchema),
  paragraphs: z.array(TranscriptParagraphSchema),
});
export type Transcript = z.infer<typeof TranscriptSchema>;

export const MediaPropertiesSchema = z.object({
  object_id: z.string(),
  mime_type: z.string(),
  duration_ms: z.number(),
  size_bytes: z.number(),
  transcript: TranscriptSchema.optional(),
  source: z.enum(['camera', 'screen']).optional(),
  // Set by the particle processor worker once an iOS-playable MP4/m4a variant
  // has been produced from a non-iOS-playable original (e.g. WebM from desktop).
  // When present, clients should prefer these over object_id/mime_type for playback.
  transcoded_object_id: z.string().optional(),
  transcoded_mime_type: z.string().optional(),
});
export type MediaProperties = z.infer<typeof MediaPropertiesSchema>;

export const FilePropertiesSchema = z.object({
  object_id: z.string(),
  filename: z.string(),
  mime_type: z.string(),
  size_bytes: z.number(),
});
export type FileProperties = z.infer<typeof FilePropertiesSchema>;

export const TextPropertiesSchema = z.object({
  content: z.string(),
  edited_at: z.coerce.date().optional(),
});
export type TextProperties = z.infer<typeof TextPropertiesSchema>;

export const ChecklistItemSchema = z.object({
  text: z.string(),
  done: z.boolean(),
});
export type ChecklistItem = z.infer<typeof ChecklistItemSchema>;

export const TaskPropertiesSchema = z.object({
  title: z.string(),
  notes: z.string().optional(),
  checklist: z.array(ChecklistItemSchema).optional(),
  // humanId
  assigned_to: z.string().optional(),
  done: z.boolean(),
});
export type TaskProperties = z.infer<typeof TaskPropertiesSchema>;

export const PaperPropertiesSchema = z.object({
  title: z.string(),
  content: z.string(),
});
export type PaperProperties = z.infer<typeof PaperPropertiesSchema>;

// --- Reactions ---

export const ReactionsSchema = z
  .record(z.string(), z.array(z.string()))
  .optional();
export type Reactions = z.infer<typeof ReactionsSchema>;

// --- Tombstone (soft-delete) ---

// Fields added to non-container particles when their creator deletes them.
// We keep the doc around so concurrent viewers can see a "This particle was
// deleted" message in place, rather than being jumped to the next particle.
const TombstoneFields = {
  deleted_at: z.coerce.date().optional(),
  deleted_by_human_id: z.string().optional(),
};

export const REACTION_EMOJIS = [
  '\u{1F44D}',
  '\u{2764}\u{FE0F}',
  '\u{1F525}',
  '\u{1F440}',
  '\u{2705}',
  '\u{2753}',
  '\u{1F602}',
] as const;

export interface ParticlePropertiesMap {
  stream: StreamProperties;
  folder: FolderProperties;
  media: MediaProperties;
  file: FileProperties;
  text: TextProperties;
  task: TaskProperties;
  paper: PaperProperties;
}

// --- Unified Particle types ---

const ParticleBaseSchema = z.object({
  id: z.string(),
  created_at: z.coerce.date(),
  created_by_human_id: z.string(),
  updated_at: z.coerce.date().optional(),
});

export const ParticleSchema = z.discriminatedUnion('type', [
  ParticleBaseSchema.extend({
    type: z.literal('stream'),
    properties: StreamPropertiesSchema,
    // e.g. ["human:human_xxxx", "human:human_yyyy"] - visible only to Aron and John
    // e.g. ["network:xywx"] - visible to everyone in the network
    visible_to: z.array(z.string()),
    // Marks human_id to their `playback_position_at`: where they left off in a conversation
    playback_markers: z.record(z.string(), z.coerce.date()).optional(),
    // Timestamp of the most recent child particle
    //   used for sorting streams by recent activity without needing to query subcollections
    last_child_created_at: z.coerce.date().optional(),
    // Array of humanIds currently in the huddle (updated via LiveKit webhooks)
    huddle_active_participants: z.array(z.string()).optional(),
    status: z.enum(['open', 'closed']).optional(),
  }),
  ParticleBaseSchema.extend({
    type: z.literal('folder'),
    properties: FolderPropertiesSchema,
    // e.g. ["human:human_xxxx", "human:human_yyyy"] - visible only to Aron and John
    // e.g. ["network:123"] - visible to everyone in the network
    visible_to: z.array(z.string()),
  }),
  ParticleBaseSchema.extend({
    type: z.literal('media'),
    properties: MediaPropertiesSchema,
    reactions: ReactionsSchema,
    ...TombstoneFields,
  }),
  ParticleBaseSchema.extend({
    type: z.literal('file'),
    properties: FilePropertiesSchema,
    ...TombstoneFields,
  }),
  ParticleBaseSchema.extend({
    type: z.literal('text'),
    properties: TextPropertiesSchema,
    reactions: ReactionsSchema,
    ...TombstoneFields,
  }),
  ParticleBaseSchema.extend({
    type: z.literal('task'),
    properties: TaskPropertiesSchema,
    reactions: ReactionsSchema,
    ...TombstoneFields,
  }),
  ParticleBaseSchema.extend({
    type: z.literal('paper'),
    properties: PaperPropertiesSchema,
    ...TombstoneFields,
  }),
]);

export type Particle = z.infer<typeof ParticleSchema>;

export type ParticleType = Particle['type'];

/** Container types can have children subcollections */
export const CONTAINER_TYPES: ReadonlySet<ParticleType> = new Set([
  'stream',
  'folder',
]);

export function isContainerType(type: ParticleType): boolean {
  return CONTAINER_TYPES.has(type);
}

/** True when a non-container particle has been soft-deleted (tombstoned). */
export function isParticleDeleted(particle: Particle): boolean {
  return 'deleted_at' in particle && particle.deleted_at != null;
}

// --- LiveKit types ---

export const GetLivekitTokenResponseSchema = z.object({
  token: z.string(),
  server_url: z.string(),
});
export type GetLivekitTokenResponse = z.infer<
  typeof GetLivekitTokenResponseSchema
>;

// --- Auth types ---

export const RequestCodeRequestSchema = z.object({
  email: z.string().email(),
});
export type RequestCodeRequest = z.infer<typeof RequestCodeRequestSchema>;

export const SignInRequestSchema = z.object({
  email: z.string().email(),
  code: z.string(),
});
export type SignInRequest = z.infer<typeof SignInRequestSchema>;

export const SignInResponseSchema = z.object({
  human: HumanSchema,
  token: z.string(),
});
export type SignInResponse = z.infer<typeof SignInResponseSchema>;

export const FirebaseTokenResponseSchema = z.object({
  token: z.string(),
});
export type FirebaseTokenResponse = z.infer<typeof FirebaseTokenResponseSchema>;

// --- Billing types ---

export const BillingCadenceSchema = z.enum(['monthly', 'annual']);
export type BillingCadence = z.infer<typeof BillingCadenceSchema>;

export const NetworkPlanSchema = z.enum(['free', 'pro']);
export type NetworkPlan = z.infer<typeof NetworkPlanSchema>;

// Mirrors Stripe subscription.status plus "active" as the default free-tier value.
export const BillingPlanStatusSchema = z.enum([
  'active',
  'trialing',
  'past_due',
  'canceled',
  'incomplete',
  'incomplete_expired',
  'unpaid',
]);
export type BillingPlanStatus = z.infer<typeof BillingPlanStatusSchema>;

export const BillingStatusSchema = z.object({
  plan: NetworkPlanSchema,
  plan_status: BillingPlanStatusSchema,
  cadence: BillingCadenceSchema.nullable(),
  seats: z.number().int(),
  current_period_end: z.coerce.date().nullable(),
  cancel_at_period_end: z.boolean(),
  price_monthly_cents: z.number().int(),
  price_annual_cents: z.number().int(),
});
export type BillingStatus = z.infer<typeof BillingStatusSchema>;

export const CheckoutSessionResponseSchema = z.object({
  url: z.string().url(),
});
export type CheckoutSessionResponse = z.infer<
  typeof CheckoutSessionResponseSchema
>;

export const PortalSessionResponseSchema = z.object({
  url: z.string().url(),
});
export type PortalSessionResponse = z.infer<typeof PortalSessionResponseSchema>;

export const NetworkUsageSchema = z.object({
  plan: NetworkPlanSchema,
  used: z.number().int().nonnegative(),
  limit: z.number().int().nonnegative().nullable(),
  reset_at: z.coerce.date(),
});
export type NetworkUsage = z.infer<typeof NetworkUsageSchema>;
