import { z } from "zod";
import { AI_CONTRACT_VERSION, UNITS } from "@app/shared-types";

/**
 * AAMOS-uppgiftstyper. Superset av köns jobbtyper (spec §54): vissa jobb i
 * plattformen-workern är rena AAMOS-anrop, andra är deterministiska och anropar
 * aldrig AI. Kontraktet här är versionerat – AAMOS kan byta modeller fritt
 * bakom det (spec §31: "Food API ska få stabila kontrakt oavsett modell").
 */
export const AAMOS_TASK_TYPES = [
  "ANALYZE_FRIDGE_IMAGE",
  "ANALYZE_PANTRY_IMAGE",
  "ANALYZE_MEAL_IMAGE",
  "READ_RECEIPT",
  "READ_NUTRITION_LABEL",
  "READ_EXPIRY_DATE",
  "NORMALIZE_PRODUCTS",
  "DEDUPLICATE_INVENTORY",
  "STRUCTURE_RECIPE_TEXT",
  "GENERATE_RECIPE_OPTIONS",
  "RANK_RECIPES",
  "PARSE_CRAVING",
  "UPDATE_USER_MEMORY",
  "GENERATE_WEEK_PLAN",
  "MODERATE_RECIPE",
  "TRANSLATE_RECIPE",
  "EXPORT_TRAINING_SAMPLE",
] as const;
export type AamosTaskType = (typeof AAMOS_TASK_TYPES)[number];

const unitSchema = z.enum(UNITS);
const confidence = z.number().min(0).max(1);
const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);

/**
 * Ett detekterat objekt från bildanalys (spec §10). AI får alltid säga
 * "osäker/okänd": canonicalIngredientId=null + lågt confidence.
 */
export const detectedItemSchema = z.object({
  detectedName: z.string(),
  canonicalIngredientId: z.string().nullable(),
  brand: z.string().nullable().default(null),
  estimatedQuantity: z.number().nullable(),
  unit: unitSchema.nullable(),
  bestBeforeDate: isoDate.nullable().default(null),
  confidence,
  requiresConfirmation: z.boolean(),
  boundingBox: z
    .object({ x: z.number(), y: z.number(), w: z.number(), h: z.number() })
    .nullable()
    .default(null),
});
export type DetectedItem = z.infer<typeof detectedItemSchema>;

// ---------------------------------------------------------------------------
// Input/Output per uppgiftstyp
// ---------------------------------------------------------------------------

export const analyzeStorageImageInput = z.object({
  imageUrls: z.array(z.url()).min(1).max(6),
  locationType: z.string(),
  marketLocale: z.string().default("sv-SE"),
  knownItems: z.array(z.string()).default([]),
});
export const analyzeStorageImageOutput = z.object({
  items: z.array(detectedItemSchema),
  imageQualityIssues: z.array(z.enum(["dark", "blurry", "occlusion", "too_far"])).default([]),
});

export const analyzeMealImageInput = z.object({
  imageUrls: z.array(z.url()).min(1).max(3),
  recipeContext: z
    .object({
      recipeId: z.string(),
      titleSv: z.string(),
      nutritionPerPortion: z.record(z.string(), z.number()),
      portions: z.number(),
    })
    .nullable()
    .default(null),
  marketLocale: z.string().default("sv-SE"),
});
/**
 * Tallriksfoto (spec §22): intervall + mest sannolikt, aldrig exakt påstående.
 * Kalorierna här är UPPSKATTNINGAR som användaren måste kunna korrigera.
 */
export const analyzeMealImageOutput = z.object({
  matchesRecipeContext: z.boolean().nullable(),
  portionFractionEstimate: z.number().min(0).max(5).nullable(),
  kcalRange: z.object({ min: z.number(), max: z.number(), mostLikely: z.number() }).nullable(),
  components: z
    .array(
      z.object({
        name: z.string(),
        canonicalIngredientId: z.string().nullable(),
        estimatedGrams: z.number().nullable(),
        confidence,
      }),
    )
    .default([]),
  confidence,
});

export const readReceiptInput = z.object({
  imageUrls: z.array(z.url()).min(1).max(4),
  marketLocale: z.string().default("sv-SE"),
});
export const readReceiptOutput = z.object({
  storeName: z.string().nullable(),
  purchaseDate: isoDate.nullable(),
  lines: z.array(
    z.object({
      rawText: z.string(),
      normalizedName: z.string().nullable(),
      canonicalIngredientId: z.string().nullable(),
      quantity: z.number().nullable(),
      unit: unitSchema.nullable(),
      unitPriceMinor: z.number().int().nullable(),
      totalPriceMinor: z.number().int().nullable(),
      isDiscount: z.boolean().default(false),
      confidence,
    }),
  ),
  totalMinor: z.number().int().nullable(),
  discountTotalMinor: z.number().int().nullable(),
  confidence,
});

export const readNutritionLabelInput = z.object({
  imageUrls: z.array(z.url()).min(1).max(3),
  marketLocale: z.string().default("sv-SE"),
});
/** OCR av näringsdeklaration – värden kommer från ETIKETTEN, aldrig från modellens gissning. */
export const readNutritionLabelOutput = z.object({
  basis: z.enum(["per_100_g", "per_100_ml", "per_portion"]).nullable(),
  values: z
    .object({
      kcal: z.number().nullable(),
      proteinG: z.number().nullable(),
      carbsG: z.number().nullable(),
      fatG: z.number().nullable(),
      saturatedFatG: z.number().nullable(),
      fiberG: z.number().nullable(),
      sugarG: z.number().nullable(),
      saltG: z.number().nullable(),
    })
    .nullable(),
  ingredientsText: z.string().nullable(),
  allergensDeclared: z.array(z.string()).default([]),
  gtin: z.string().nullable().default(null),
  productName: z.string().nullable().default(null),
  brand: z.string().nullable().default(null),
  confidence,
});

export const readExpiryDateInput = z.object({
  imageUrls: z.array(z.url()).min(1).max(2),
});
export const readExpiryDateOutput = z.object({
  date: isoDate.nullable(),
  dateKind: z.enum(["best_before", "use_by"]).nullable(),
  confidence,
});

export const normalizeProductsInput = z.object({
  rawNames: z.array(z.string()).min(1).max(100),
  marketLocale: z.string().default("sv-SE"),
});
export const normalizeProductsOutput = z.object({
  matches: z.array(
    z.object({
      raw: z.string(),
      canonicalIngredientId: z.string().nullable(),
      normalizedName: z.string().nullable(),
      confidence,
    }),
  ),
});

export const deduplicateInventoryInput = z.object({
  items: z.array(
    z.object({
      id: z.string(),
      displayName: z.string(),
      canonicalIngredientId: z.string().nullable(),
      quantity: z.number(),
      unit: unitSchema,
      source: z.string(),
      createdAt: z.string(),
    }),
  ),
});
export const deduplicateInventoryOutput = z.object({
  duplicateGroups: z.array(
    z.object({
      itemIds: z.array(z.string()).min(2),
      confidence,
      suggestedAction: z.enum(["merge", "review"]),
      reasonSv: z.string(),
    }),
  ),
});

/** Fritextrecept → struktur (spec §35). Näring räknas ALDRIG här – det gör nutrition-engine. */
export const structureRecipeTextInput = z.object({
  text: z.string().min(20).max(8000),
  marketLocale: z.string().default("sv-SE"),
});
export const structureRecipeTextOutput = z.object({
  titleSv: z.string().nullable(),
  descriptionSv: z.string().nullable(),
  ingredients: z.array(
    z.object({
      rawText: z.string(),
      canonicalIngredientId: z.string().nullable(),
      displayNameSv: z.string(),
      quantity: z.number().nullable(),
      unit: unitSchema.nullable(),
      optional: z.boolean().default(false),
      confidence,
    }),
  ),
  steps: z.array(
    z.object({
      instructionSv: z.string(),
      timerSeconds: z.number().nullable().default(null),
      temperatureC: z.number().nullable().default(null),
    }),
  ),
  prepTimeMinutes: z.number().nullable(),
  cookTimeMinutes: z.number().nullable(),
  portions: z.number().nullable(),
  suggestedCuisine: z.string().nullable(),
  suggestedMealTypes: z.array(z.string()).default([]),
  confidence,
});

export const generateRecipeOptionsInput = z.object({
  cravingText: z.string().nullable(),
  pantrySummary: z.array(
    z.object({ canonicalIngredientId: z.string(), quantity: z.number(), unit: unitSchema }),
  ),
  constraints: z.object({
    allergens: z.array(z.string()),
    dietPattern: z.string().nullable(),
    maxMinutes: z.number().nullable(),
    portions: z.number(),
    spiceLevelMax: z.number().nullable(),
  }),
  marketLocale: z.string().default("sv-SE"),
});
/**
 * AI-assisterade originalförslag (spec §15) – går ALLTID via redaktionell
 * granskning innan de blir permanenta recept. Allergensäkerhet verifieras
 * deterministiskt av recipe-engine innan visning.
 */
export const generateRecipeOptionsOutput = z.object({
  suggestions: z.array(
    z.object({
      titleSv: z.string(),
      descriptionSv: z.string(),
      ingredients: z.array(
        z.object({
          canonicalIngredientId: z.string().nullable(),
          displayNameSv: z.string(),
          quantity: z.number(),
          unit: unitSchema,
        }),
      ),
      steps: z.array(z.string()),
      estimatedTimeMinutes: z.number(),
      confidence,
    }),
  ),
});

export const rankRecipesInput = z.object({
  candidateIds: z.array(z.string()).max(50),
  deterministicScores: z.record(z.string(), z.number()),
  contextSummary: z.string(),
});
export const rankRecipesOutput = z.object({
  rankedIds: z.array(z.string()),
  rationaleSv: z.string().nullable(),
});

export const parseCravingInput = z.object({
  text: z.string().max(500),
  marketLocale: z.string().default("sv-SE"),
});
export const parseCravingOutput = z.object({
  tags: z.array(z.string()),
  cuisine: z.string().nullable(),
  maxKcal: z.number().nullable(),
  confidence,
});

export const updateUserMemoryInput = z.object({
  scope: z.enum(["user", "household"]),
  scopeId: z.string(),
  events: z.array(
    z.object({
      type: z.string(),
      occurredAt: z.string(),
      payload: z.unknown(),
    }),
  ),
  existingMemoryKeys: z.array(z.string()).default([]),
});
export const updateUserMemoryOutput = z.object({
  memoryUpdates: z.array(
    z.object({
      key: z.string(),
      kind: z.enum(["structured_fact", "event", "semantic", "profile_summary", "recipe_memory"]),
      summarySv: z.string(),
      value: z.unknown(),
      origin: z.enum(["observed", "ai_inferred"]),
      confidence,
      expiresAt: z.string().nullable().default(null),
    }),
  ),
});

export const generateWeekPlanInput = z.object({
  days: z.array(z.object({ date: isoDate, mealTypes: z.array(z.string()) })),
  pantrySummary: z.array(
    z.object({
      canonicalIngredientId: z.string(),
      quantity: z.number(),
      unit: unitSchema,
      daysToExpiry: z.number().nullable(),
    }),
  ),
  candidateRecipes: z.array(
    z.object({ id: z.string(), titleSv: z.string(), tags: z.array(z.string()) }),
  ),
  constraints: z.object({
    budgetMinorTotal: z.number().int().nullable(),
    portionsPerMeal: z.number(),
    varietyLevel: z.enum(["low", "medium", "high"]),
    noteSv: z.string().nullable(),
  }),
});
export const generateWeekPlanOutput = z.object({
  entries: z.array(
    z.object({
      date: isoDate,
      mealType: z.string(),
      recipeId: z.string().nullable(),
      useMealBox: z.boolean().default(false),
      motivationSv: z.string().nullable(),
    }),
  ),
  confidence,
});

export const moderateRecipeInput = z.object({
  titleSv: z.string(),
  descriptionSv: z.string(),
  ingredients: z.array(z.string()),
  steps: z.array(z.string()),
});
export const moderateRecipeOutput = z.object({
  flags: z.array(
    z.object({
      code: z.enum([
        "food_safety",
        "unrealistic_quantities",
        "inappropriate_content",
        "spam",
        "copyright_suspect",
        "other",
      ]),
      severity: z.enum(["info", "warning", "reject"]),
      messageSv: z.string(),
    }),
  ),
  recommendation: z.enum(["approve", "review", "reject"]),
  confidence,
});

// ---------------------------------------------------------------------------
// TRANSLATE_RECIPE (i18n-spec §13–14): AI översätter TEXT, aldrig struktur.
// Mängder, ingredient-IDs, tider och allergener bor utanför översättningen och
// kan därför inte ändras av modellen. Deterministisk verifiering sker i worker.
// ---------------------------------------------------------------------------

export const translateRecipeInput = z.object({
  sourceLanguageTag: z.string().default("sv"),
  targetLanguageTag: z.string(),
  title: z.string(),
  description: z.string().nullable(),
  storageGuidance: z.string().nullable(),
  steps: z.array(
    z.object({
      stepNumber: z.number().int().min(1),
      instruction: z.string(),
      tip: z.string().nullable(),
    }),
  ),
});
export const translateRecipeOutput = z.object({
  title: z.string().min(1),
  description: z.string().nullable(),
  storageGuidance: z.string().nullable(),
  steps: z.array(
    z.object({
      stepNumber: z.number().int().min(1),
      instruction: z.string().min(1),
      tip: z.string().nullable(),
    }),
  ),
  confidence,
});

// ---------------------------------------------------------------------------
// EXPORT_TRAINING_SAMPLE: anonymiserade korrigeringar för AAMOS training-pipeline
// Ingen PII, inga bild-URL:er, inga användar-ID:n.
// ---------------------------------------------------------------------------

export const exportTrainingSampleInput = z.object({
  marketLocale: z.string().default("sv-SE"),
  samples: z.array(
    z.object({
      taskType: z.string(),
      aiOutput: z.record(z.string(), z.unknown()),
      userCorrection: z.record(z.string(), z.unknown()),
      modelVersion: z.string().nullable(),
      promptVersion: z.string().nullable(),
    }),
  ),
});
export const exportTrainingSampleOutput = z.object({
  batchId: z.string(),
  accepted: z.number().int().min(0),
  rejected: z.number().int().min(0),
});

// ---------------------------------------------------------------------------
// Kontraktsregister
// ---------------------------------------------------------------------------

export const TASK_CONTRACTS = {
  ANALYZE_FRIDGE_IMAGE: { input: analyzeStorageImageInput, output: analyzeStorageImageOutput },
  ANALYZE_PANTRY_IMAGE: { input: analyzeStorageImageInput, output: analyzeStorageImageOutput },
  ANALYZE_MEAL_IMAGE: { input: analyzeMealImageInput, output: analyzeMealImageOutput },
  READ_RECEIPT: { input: readReceiptInput, output: readReceiptOutput },
  READ_NUTRITION_LABEL: { input: readNutritionLabelInput, output: readNutritionLabelOutput },
  READ_EXPIRY_DATE: { input: readExpiryDateInput, output: readExpiryDateOutput },
  NORMALIZE_PRODUCTS: { input: normalizeProductsInput, output: normalizeProductsOutput },
  DEDUPLICATE_INVENTORY: { input: deduplicateInventoryInput, output: deduplicateInventoryOutput },
  STRUCTURE_RECIPE_TEXT: { input: structureRecipeTextInput, output: structureRecipeTextOutput },
  GENERATE_RECIPE_OPTIONS: {
    input: generateRecipeOptionsInput,
    output: generateRecipeOptionsOutput,
  },
  RANK_RECIPES: { input: rankRecipesInput, output: rankRecipesOutput },
  PARSE_CRAVING: { input: parseCravingInput, output: parseCravingOutput },
  UPDATE_USER_MEMORY: { input: updateUserMemoryInput, output: updateUserMemoryOutput },
  GENERATE_WEEK_PLAN: { input: generateWeekPlanInput, output: generateWeekPlanOutput },
  MODERATE_RECIPE: { input: moderateRecipeInput, output: moderateRecipeOutput },
  TRANSLATE_RECIPE: { input: translateRecipeInput, output: translateRecipeOutput },
  EXPORT_TRAINING_SAMPLE: {
    input: exportTrainingSampleInput,
    output: exportTrainingSampleOutput,
  },
} as const satisfies Record<AamosTaskType, { input: z.ZodType; output: z.ZodType }>;

export type TaskInput<T extends AamosTaskType> = z.infer<(typeof TASK_CONTRACTS)[T]["input"]>;
export type TaskOutput<T extends AamosTaskType> = z.infer<(typeof TASK_CONTRACTS)[T]["output"]>;

/** Kuvert för anrop mot AAMOS. */
export const aamosRequestEnvelopeSchema = z.object({
  taskId: z.string(),
  taskType: z.enum(AAMOS_TASK_TYPES),
  contractVersion: z.string().default(AI_CONTRACT_VERSION),
  input: z.unknown(),
  metadata: z.object({
    correlationId: z.string(),
    /** Lokaliseringskontext (i18n-spec §22): AAMOS svarar på rätt språk och
     *  respekterar region/måttsystem – canonical data förblir språkneutral. */
    localeContext: z
      .object({
        languageTag: z.string(),
        regionCode: z.string(),
        timeZone: z.string(),
        measurementSystem: z.enum(["METRIC", "US_CUSTOMARY", "MIXED"]),
        temperatureUnit: z.enum(["CELSIUS", "FAHRENHEIT"]),
        currencyCode: z.string(),
      })
      .default({
        languageTag: "sv-SE",
        regionCode: "SE",
        timeZone: "Europe/Stockholm",
        measurementSystem: "METRIC",
        temperatureUnit: "CELSIUS",
        currencyCode: "SEK",
      }),
    /** Pseudonymiserat – aldrig e-post eller namn till AAMOS (spec §56). */
    subjectRef: z.string().nullable(),
    priority: z.enum(["low", "normal", "high"]).default("normal"),
    consentFlags: z.object({
      personalization: z.boolean(),
      anonymizedImprovement: z.boolean(),
      imageTraining: z.boolean(),
    }),
  }),
});
export type AamosRequestEnvelope = z.infer<typeof aamosRequestEnvelopeSchema>;

export const aamosResponseEnvelopeSchema = z.object({
  taskId: z.string(),
  taskType: z.enum(AAMOS_TASK_TYPES),
  contractVersion: z.string(),
  status: z.enum(["ok", "uncertain", "failed"]),
  output: z.unknown().nullable(),
  error: z.string().nullable().default(null),
  modelVersion: z.string().nullable().default(null),
  promptVersion: z.string().nullable().default(null),
  latencyMs: z.number().nullable().default(null),
  costUsd: z.number().nullable().default(null),
});
export type AamosResponseEnvelope = z.infer<typeof aamosResponseEnvelopeSchema>;
