/**
 * Knowledge Base — Zod schemas, TS types, serializers, and constants.
 *
 * Consumed by:
 *   - apps/zync-api (route validation)
 *   - apps/zync-app (form validation, typed API responses)
 *   - packages/db/src/queries/kb.ts (serializer re-use)
 */
import { z } from 'zod'

// ── File-type allowlist ───────────────────────────────────────────────────────

/**
 * Allowlisted MIME types for KB file/image attachments.
 * SVG is intentionally excluded (XSS risk same-origin).
 */
export const KB_ALLOWED_FILE_TYPES = [
  'application/pdf',
  'image/jpeg',
  'image/png',
  'image/webp',
  'image/gif',
  'video/mp4',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // DOCX
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // XLSX
] as const

export type KbAllowedFileType = (typeof KB_ALLOWED_FILE_TYPES)[number]

// ── Zod schemas ───────────────────────────────────────────────────────────────

const createSpaceBaseSchema = z.object({
  name: z.string().min(1).max(120),
  slug: z.string().min(1).max(120).regex(/^[a-z0-9-]+$/),
  type: z.enum(['internal', 'vault']).default('internal'),
  customer_id: z.string().uuid().nullable().optional(),
  icon: z.string().max(64).nullable().optional(),
  is_public: z.boolean().default(false),
  description: z.string().max(1000).nullable().optional(),
})

export const createSpaceSchema = createSpaceBaseSchema
  .refine((d) => d.type !== 'vault' || !!d.customer_id, {
    message: 'vault space requires customer_id',
    path: ['customer_id'],
  })

export type CreateSpaceInput = z.infer<typeof createSpaceSchema>

export const updateSpaceSchema = createSpaceBaseSchema.partial().omit({ type: true })

export type UpdateSpaceInput = z.infer<typeof updateSpaceSchema>

export const createArticleSchema = z.object({
  space_id: z.string().uuid(),
  parent_id: z.string().uuid().nullable().optional(),
  title: z.string().min(1).max(300),
  slug: z
    .string()
    .min(1)
    .max(300)
    .regex(/^[a-z0-9-]+$/),
  content: z.record(z.any()), // Tiptap v2 JSON document
})

export type CreateArticleInput = z.infer<typeof createArticleSchema>

export const updateArticleSchema = z.object({
  title: z.string().min(1).max(300).optional(),
  content: z.record(z.any()).optional(),
  status: z.enum(['DRAFT', 'PUBLISHED']).optional(), // publish toggle
  parent_id: z.string().uuid().nullable().optional(), // reparent on reorder
  position: z.number().optional(), // fractional reorder
})

export type UpdateArticleInput = z.infer<typeof updateArticleSchema>

export const kbSearchQuerySchema = z.object({ q: z.string().min(1).max(200) })

export type KbSearchQuery = z.infer<typeof kbSearchQuerySchema>

// ── TypeScript interfaces ─────────────────────────────────────────────────────

export interface KbSpace {
  id: string
  tenantId: string
  name: string
  slug: string
  type: 'internal' | 'vault'
  customerId: string | null
  /** Vault spaces: linked customer display name (staff list API). */
  customerName?: string | null
  icon: string | null
  isPublic: boolean
  description: string | null
  position: number
  articleCount: number
  createdBy: string
  createdAt: string
}

export type KbArticleStatus = 'DRAFT' | 'PENDING_REVIEW' | 'PUBLISHED'

export interface KbArticleRejection {
  feedback: string | null
  rejectedAt: string
  rejectedBy: string
}

export interface KbArticle {
  id: string
  tenantId: string
  spaceId: string
  parentId: string | null
  title: string
  slug: string
  content: Record<string, unknown>
  status: KbArticleStatus
  position: number
  viewCount: number
  publishedAt: string | null
  // kb-article-editor additions (null when not yet migrated)
  metaTitle: string | null
  metaDescription: string | null
  createdBy: string
  updatedBy: string | null
  createdAt: string
  updatedAt: string
  /** Present when the article is DRAFT and was most recently rejected from review. */
  latestRejection?: KbArticleRejection | null
}

export interface KbArticleNode extends KbArticle {
  children: KbArticleNode[]
}

export interface KbAttachment {
  id: string
  tenantId: string
  articleId: string
  filename: string
  fileType: string
  fileSizeBytes: number
  createdBy: string
  createdAt: string
  // r2Key is intentionally excluded from the public interface (never sent to clients)
}

export interface KbSearchResult {
  articleId: string
  spaceId: string
  spaceSlug: string
  articleSlug: string
  title: string
  score: number
}

// ── Serializers ───────────────────────────────────────────────────────────────

/** Serialize a DB row into a public KbSpace (formats timestamps ISO). */
export function serializeKbSpace(row: {
  id: string
  tenantId: string
  name: string
  slug: string
  type: string
  customerId: string | null
  icon: string | null
  isPublic: boolean
  description: string | null
  position?: number
  createdBy: string
  createdAt: Date | string
  customerName?: string | null
  articleCount?: number
}): KbSpace {
  return {
    id: row.id,
    tenantId: row.tenantId,
    name: row.name,
    slug: row.slug,
    type: row.type as 'internal' | 'vault',
    customerId: row.customerId ?? null,
    customerName: row.customerName ?? null,
    icon: row.icon ?? null,
    isPublic: row.isPublic,
    description: row.description ?? null,
    position: row.position ?? 0,
    articleCount: row.articleCount ?? 0,
    createdBy: row.createdBy,
    createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : row.createdAt,
  }
}

/** Serialize a DB row into a public KbArticle (omits r2Key and internal cols, formats timestamps). */
export function serializeKbArticle(row: {
  id: string
  tenantId: string
  spaceId: string
  parentId: string | null
  title: string
  slug: string
  content: Record<string, unknown>
  status: string
  position: string | number
  viewCount: number
  publishedAt: Date | string | null
  createdBy: string
  updatedBy: string | null
  createdAt: Date | string
  updatedAt: Date | string
  // kb-article-editor additions (optional — present after migration 0015)
  metaTitle?: string | null
  metaDescription?: string | null
  deletedAt?: Date | string | null
}): KbArticle {
  return {
    id: row.id,
    tenantId: row.tenantId,
    spaceId: row.spaceId,
    parentId: row.parentId ?? null,
    title: row.title,
    slug: row.slug,
    content: row.content,
    status: row.status as KbArticleStatus,
    position: typeof row.position === 'string' ? parseFloat(row.position) : row.position,
    viewCount: row.viewCount,
    publishedAt:
      row.publishedAt instanceof Date
        ? row.publishedAt.toISOString()
        : row.publishedAt ?? null,
    createdBy: row.createdBy,
    updatedBy: row.updatedBy ?? null,
    createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : row.createdAt,
    updatedAt: row.updatedAt instanceof Date ? row.updatedAt.toISOString() : row.updatedAt,
    // editor additions
    metaTitle: row.metaTitle ?? null,
    metaDescription: row.metaDescription ?? null,
  }
}

/** Serialize a DB row into a public KbAttachment (never includes r2_key). */
export function serializeKbAttachment(row: {
  id: string
  tenantId: string
  articleId: string
  filename: string
  fileType: string
  fileSizeBytes: number
  createdBy: string
  createdAt: Date | string
}): KbAttachment {
  return {
    id: row.id,
    tenantId: row.tenantId,
    articleId: row.articleId,
    filename: row.filename,
    fileType: row.fileType,
    fileSizeBytes: row.fileSizeBytes,
    createdBy: row.createdBy,
    createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : row.createdAt,
  }
}
