/**
 * Zod schemas for all AI response types.
 *
 * Every OpenRouter response is validated against its schema before reaching
 * the UI. Validation failures trigger an explicit fallback to MockAIService —
 * no invalid data ever passes through silently.
 *
 * All schemas use .strict() — any unknown key from the AI response triggers
 * immediate validation failure and fallback, preventing hallucinated fields
 * from reaching the UI.
 */
import { z } from 'zod'

// ── Shared primitives ─────────────────────────────────────────────────────────

export const SeveritySchema   = z.enum(['LOW', 'MEDIUM', 'HIGH'])
export const CredibilitySchema = z.enum(['LOW', 'MEDIUM', 'HIGH'])

/** Probability must be strictly within [0, 1] — no -5, no 1.5 */
export const ProbabilitySchema = z.number().min(0).max(1)

/** Match score must be within [0, 100] */
export const ScoreSchema = z.number().min(0).max(100)

// ── 1. Need Parsing ───────────────────────────────────────────────────────────

export const NeedParsingResponseSchema = z
  .object({
    assetType: z
      .enum(['OFFICE', 'RETAIL', 'LOGISTICS', 'PRODUCTION', 'LIGHT_INDUSTRIAL', 'GASTRO', 'MIXED', 'UNKNOWN'])
      .optional()
      .nullable(),
    areaRange: z
      .object({
        min: z.number().min(1, 'minimum area must be ≥ 1 m²').max(100_000),
        max: z.number().min(0).max(100_000),
      })
      .strict()
      .optional()
      .nullable()
      .refine(r => r == null || r.max >= r.min, { message: 'areaRange.max must be >= min' }),
    preferredLocations: z.array(z.string().min(1).max(100)).max(20).optional(),
    budgetRange: z
      .object({
        maxPerSqm: z.number().positive().max(100_000, 'budget > 100k CHF/m²/a is implausible'),
        currency:  z.string().min(1).max(10),
      })
      .strict()
      .optional()
      .nullable(),
    timing: z
      .object({
        earliestMoveIn: z.string().optional(),
        latestMoveIn:   z.string().optional(),
        flexibleTiming: z.boolean().optional(),
      })
      .strict()
      .optional()
      .nullable(),
    mustHaveCriteria: z.array(z.string().min(1).max(200)).max(30).optional(),
    missingFields:    z.array(z.string().min(1)).max(20).optional(),
    assumptions:      z.array(z.string().min(1)).max(20).optional(),
  })
  .strict()

export type NeedParsingResponseRaw = z.infer<typeof NeedParsingResponseSchema>

// ── 2. Follow-Up Questions ────────────────────────────────────────────────────

export const FollowUpQuestionsResponseSchema = z
  .array(
    z
      .object({
        questionText:           z.string().min(5).max(500),
        targetField:            z.string().min(1).max(100),
        reason:                 z.string().min(3).max(300).optional(),
        suggestedAnswerOptions: z.array(z.string().min(1).max(200)).max(10).optional(),
        importance:             z.enum(['required', 'recommended', 'optional']),
      })
      .strict(),
  )
  .max(3)

// ── 3. Trade-Off Summary ──────────────────────────────────────────────────────

export const TradeOffSummaryResponseSchema = z
  .object({
    headline: z.string().min(5).max(300),
    items: z
      .array(
        z
          .object({
            concern:    z.string().min(5).max(300),
            severity:   SeveritySchema,
            mitigation: z.string().max(300).optional(),
          })
          .strict(),
      )
      .max(3),
    overallRisk: SeveritySchema,
  })
  .strict()

// ── 4. Comparison Summary ─────────────────────────────────────────────────────

export const CompareSummaryResponseSchema = z
  .object({
    overallAssessment: z.string().min(10).max(1000),
    recommendation:    z.string().min(5).max(500).optional(),
    strongestOption:   z.string().min(1).max(200).optional(),
  })
  .strict()

// ── 5. Decision Brief ────────────────────────────────────────────────────────

export const DecisionBriefResponseSchema = z
  .object({
    summary: z.string().min(10).max(500),
    sections: z
      .array(
        z
          .object({
            title: z.string().min(1).max(100),
            body:  z.string().min(10).max(1000),
          })
          .strict(),
      )
      .min(1)
      .max(6),
  })
  .strict()

// ── 6. Data Quality Summary ───────────────────────────────────────────────────

export const DataQualitySummaryResponseSchema = z
  .object({
    overallAssessment:     z.string().min(10).max(600),
    missingCriticalFields: z.array(z.string().min(1).max(100)).max(30).optional(),
    recommendation:        z.string().min(5).max(500),
    confidence:            z.number().min(0).max(1),
  })
  .strict()

// ── 7. Market Signal Classification ──────────────────────────────────────────

export const MarketSignalClassificationResponseSchema = z
  .object({
    signalType: z.enum([
      'VACANCY', 'CONSTRUCTION', 'RESTRUCTURING',
      'EXPANSION', 'RELOCATION', 'UNKNOWN',
    ]),
    probability:       ProbabilitySchema,
    timeHorizonMonths: z.number().int().min(1).max(240).optional().nullable(),
    areaSqmEstimate:   z.number().positive().max(1_000_000).optional().nullable(),
    credibility:       CredibilitySchema,
    reasoning:         z.string().min(10).max(1000),
  })
  .strict()

// ── 8. Offer Email ────────────────────────────────────────────────────────────

export const OfferEmailResponseSchema = z
  .object({
    subject: z.string().min(5).max(200),
    body:    z.string().min(50).max(5000),
  })
  .strict()

// ── Validation helper ─────────────────────────────────────────────────────────

/**
 * Validates raw AI JSON against a Zod schema.
 * Returns the parsed value on success, or null on failure.
 * Logs a structured warning on failure — never throws.
 */
export function validateAIResponse<T>(
  schema: z.ZodType<T>,
  raw: unknown,
  label: string,
): T | null {
  const result = schema.safeParse(raw)
  if (result.success) return result.data
  const errors = result.error.flatten()
  console.warn(`[AISchema] ${label} validation failed`, {
    fieldErrors: errors.fieldErrors,
    formErrors:  errors.formErrors,
    receivedKeys: typeof raw === 'object' && raw !== null ? Object.keys(raw as object) : [],
    snippet: JSON.stringify(raw).slice(0, 300),
  })
  return null
}
