import { z } from 'zod';

const IFRAME_LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);

export function isAllowedIframeUrl(url: string): boolean {
  try {
    const parsed = new URL(url);
    if (parsed.protocol === 'https:') return true;
    if (parsed.protocol !== 'http:') return false;
    return IFRAME_LOCAL_HOSTS.has(parsed.hostname.toLowerCase());
  } catch {
    return false;
  }
}

export const ImageBlockSchema = z.object({
  src: z.string(),
  alt: z.string().optional(),
  caption: z.string().optional(),
});

export type ImageBlock = z.infer<typeof ImageBlockSchema>;

export const EmbedBlockSchema = z.object({
  provider: z.enum(['youtube', 'figma', 'tweet', 'generic']),
  url: z.string().url(),
  caption: z.string().optional(),
});

export type EmbedBlock = z.infer<typeof EmbedBlockSchema>;

export const IframeBlockSchema = z.object({
  url: z.string().url().refine(isAllowedIframeUrl, {
    message: 'Iframe URLs must use https:// or local http://localhost / 127.0.0.1.',
  }),
  title: z.string().optional(),
  caption: z.string().optional(),
  height: z.number().int().min(240).max(1600).optional(),
  allow: z.string().optional(),
});

export type IframeBlock = z.infer<typeof IframeBlockSchema>;

export const ChartBlockSchema = z.object({
  chart: z.enum(['line', 'bar', 'pie']),
  title: z.string().optional(),
  data: z.array(z.record(z.string(), z.unknown())).optional(),
  source: z.string().optional(),
  x: z.string(),
  y: z.string(),
});

export type ChartBlock = z.infer<typeof ChartBlockSchema>;

export const TableBlockSchema = z.object({
  columns: z.array(z.string()),
  data: z.array(z.record(z.string(), z.unknown())),
  title: z.string().optional(),
});

export type TableBlock = z.infer<typeof TableBlockSchema>;

export const CalendarEventSchema = z.object({
  summary: z.string().optional(),
  start: z.object({
    dateTime: z.string().optional(),
    date: z.string().optional(),
  }).optional(),
  end: z.object({
    dateTime: z.string().optional(),
    date: z.string().optional(),
  }).optional(),
  location: z.string().optional(),
  htmlLink: z.string().optional(),
  conferenceLink: z.string().optional(),
  source: z.string().optional(),
});

export type CalendarEvent = z.infer<typeof CalendarEventSchema>;

export const CalendarBlockSchema = z.object({
  title: z.string().optional(),
  events: z.array(CalendarEventSchema),
  showJoinButton: z.boolean().optional(),
});

export type CalendarBlock = z.infer<typeof CalendarBlockSchema>;

export const EmailBlockSchema = z.object({
  threadId: z.string().optional(),
  threadUrl: z.string().url().optional(),
  summary: z.string().optional(),
  subject: z.string().optional(),
  from: z.string().optional(),
  to: z.string().optional(),
  date: z.string().optional(),
  latest_email: z.string().optional(),
  past_summary: z.string().optional(),
  draft_response: z.string().optional(),
  response_mode: z.enum(['inline', 'assistant', 'both']).optional(),
});

export type EmailBlock = z.infer<typeof EmailBlockSchema>;

export const GmailAttachmentSchema = z.object({
  filename: z.string(),
  mimeType: z.string().optional(),
  sizeBytes: z.number().int().nonnegative().optional(),
  savedPath: z.string(),
  // Gmail identifiers used to fetch the attachment on demand when it hasn't
  // been downloaded to disk yet (e.g. attachments on search results).
  messageId: z.string().optional(),
  attachmentId: z.string().optional(),
});

export type GmailAttachment = z.infer<typeof GmailAttachmentSchema>;

export const GmailThreadMessageSchema = z.object({
  id: z.string().optional(),
  from: z.string().optional(),
  to: z.string().optional(),
  cc: z.string().optional(),
  date: z.string().optional(),
  subject: z.string().optional(),
  body: z.string().optional(),
  bodyHtml: z.string().optional(),
  unread: z.boolean().optional(),
  bodyHeight: z.number().int().positive().optional(),
  attachments: z.array(GmailAttachmentSchema).optional(),
  messageIdHeader: z.string().optional(),
  // Set on the unsent draft message within a thread (used by the Drafts view).
  isDraft: z.boolean().optional(),
  // The draft's own stored In-Reply-To / References headers. Only set on
  // draft messages: a Drafts-view pseudo-thread contains just the draft, so
  // the composer can't rebuild the reply chain from thread messages and must
  // reuse what the draft already carries.
  inReplyToHeader: z.string().optional(),
  referencesHeader: z.string().optional(),
});

export type GmailThreadMessage = z.infer<typeof GmailThreadMessageSchema>;

export const GmailThreadSchema = EmailBlockSchema.extend({
  threadId: z.string(),
  threadUrl: z.string().url(),
  unread: z.boolean().optional(),
  importance: z.enum(['important', 'other']).optional(),
  gmail_draft: z.string().optional(),
  // Gmail-side draft id, present on entries returned by the Drafts list so the
  // composer can update/delete that exact draft.
  draftId: z.string().optional(),
  messages: z.array(GmailThreadMessageSchema),
});

export type GmailThread = z.infer<typeof GmailThreadSchema>;

export const EmailsBlockSchema = z.object({
  title: z.string().optional(),
  emails: z.array(EmailBlockSchema),
});

export type EmailsBlock = z.infer<typeof EmailsBlockSchema>;

export const TranscriptBlockSchema = z.object({
  transcript: z.string(),
});

export type TranscriptBlock = z.infer<typeof TranscriptBlockSchema>;

export const SuggestedTopicBlockSchema = z.object({
  title: z.string(),
  description: z.string(),
  category: z.string().optional(),
});

export type SuggestedTopicBlock = z.infer<typeof SuggestedTopicBlockSchema>;
