/**
 * Recipes Store
 *
 * Manages recipe suggestion state and request parameters using Pinia.
 * Dismissed recipe IDs are persisted to localStorage with a 7-day TTL.
 */

import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'
import { recipesAPI, type RecipeResult, type RecipeSuggestion, type RecipeRequest, type RecipeJobStatusValue, type NutritionFilters } from '../services/api'

const DISMISSED_KEY = 'kiwi:dismissed_recipes'
const DISMISS_TTL_MS = 7 * 24 * 60 * 60 * 1000

const COOK_LOG_KEY = 'kiwi:cook_log'
const COOK_LOG_MAX = 200

const BOOKMARKS_KEY = 'kiwi:bookmarks'
const BOOKMARKS_MAX = 50

const MISSING_MODE_KEY = 'kiwi:builder_missing_mode'
const FILTER_MODE_KEY = 'kiwi:builder_filter_mode'

const CONSTRAINTS_KEY = 'kiwi:constraints'
const ALLERGIES_KEY = 'kiwi:allergies'
const EXCLUDE_INGREDIENTS_KEY = 'kiwi:exclude_ingredients'

function loadConstraints(): string[] {
  try {
    const raw = localStorage.getItem(CONSTRAINTS_KEY)
    return raw ? JSON.parse(raw) : []
  } catch {
    return []
  }
}

function saveConstraints(vals: string[]) {
  localStorage.setItem(CONSTRAINTS_KEY, JSON.stringify(vals))
}

function loadAllergies(): string[] {
  try {
    const raw = localStorage.getItem(ALLERGIES_KEY)
    return raw ? JSON.parse(raw) : []
  } catch {
    return []
  }
}

function saveAllergies(vals: string[]) {
  localStorage.setItem(ALLERGIES_KEY, JSON.stringify(vals))
}

function loadExcludeIngredients(): string[] {
  try {
    const raw = localStorage.getItem(EXCLUDE_INGREDIENTS_KEY)
    return raw ? JSON.parse(raw) : []
  } catch {
    return []
  }
}

function saveExcludeIngredients(vals: string[]) {
  localStorage.setItem(EXCLUDE_INGREDIENTS_KEY, JSON.stringify(vals))
}

type MissingIngredientMode = 'hidden' | 'greyed' | 'add-to-cart'
type BuilderFilterMode = 'text' | 'tags'

// [id, dismissedAtMs]
type DismissEntry = [number, number]

export interface CookLogEntry {
  id: number
  title: string
  cookedAt: number // unix ms
}

function loadDismissed(): Set<number> {
  try {
    const raw = localStorage.getItem(DISMISSED_KEY)
    if (!raw) return new Set()
    const entries: DismissEntry[] = JSON.parse(raw)
    const cutoff = Date.now() - DISMISS_TTL_MS
    return new Set(entries.filter(([, ts]) => ts > cutoff).map(([id]) => id))
  } catch {
    return new Set()
  }
}

function saveDismissed(ids: Set<number>) {
  const now = Date.now()
  const entries: DismissEntry[] = [...ids].map((id) => [id, now])
  localStorage.setItem(DISMISSED_KEY, JSON.stringify(entries))
}

function loadCookLog(): CookLogEntry[] {
  try {
    const raw = localStorage.getItem(COOK_LOG_KEY)
    return raw ? JSON.parse(raw) : []
  } catch {
    return []
  }
}

function saveCookLog(log: CookLogEntry[]) {
  localStorage.setItem(COOK_LOG_KEY, JSON.stringify(log.slice(-COOK_LOG_MAX)))
}

function loadBookmarks(): RecipeSuggestion[] {
  try {
    const raw = localStorage.getItem(BOOKMARKS_KEY)
    return raw ? JSON.parse(raw) : []
  } catch {
    return []
  }
}

function saveBookmarks(bookmarks: RecipeSuggestion[]) {
  localStorage.setItem(BOOKMARKS_KEY, JSON.stringify(bookmarks.slice(0, BOOKMARKS_MAX)))
}

function loadMissingMode(): MissingIngredientMode {
  const raw = localStorage.getItem(MISSING_MODE_KEY)
  if (raw === 'hidden' || raw === 'greyed' || raw === 'add-to-cart') return raw
  return 'greyed'
}

function loadFilterMode(): BuilderFilterMode {
  return localStorage.getItem(FILTER_MODE_KEY) === 'tags' ? 'tags' : 'text'
}

export const useRecipesStore = defineStore('recipes', () => {
  // Suggestion result state
  const result = ref<RecipeResult | null>(null)
  const loading = ref(false)
  const error = ref<string | null>(null)
  const jobStatus = ref<RecipeJobStatusValue | null>(null)

  // Request parameters
  const level = ref(1)
  const constraints = ref<string[]>(loadConstraints())
  const allergies = ref<string[]>(loadAllergies())
  const excludeIngredients = ref<string[]>(loadExcludeIngredients())
  const hardDayMode = ref(false)
  const maxMissing = ref<number | null>(null)
  const styleId = ref<string | null>(null)
  const category = ref<string | null>(null)
  const wildcardConfirmed = ref(false)
  const shoppingMode = ref(false)
  const pantryMatchOnly = ref(false)
  const complexityFilter = ref<string | null>(null)
  const maxTimeMin = ref<number | null>(null)
  const maxTotalMin = ref<number | null>(null)
  const maxActiveMin = ref<number | null>(null)
  const nutritionFilters = ref<NutritionFilters>({
    max_calories: null,
    max_sugar_g: null,
    max_carbs_g: null,
    max_sodium_mg: null,
  })

  // Dismissed IDs: persisted to localStorage, 7-day TTL
  const dismissedIds = ref<Set<number>>(loadDismissed())
  // Seen IDs: session-only, used by Load More to avoid repeating results
  const seenIds = ref<Set<number>>(new Set())
  // Cook log: persisted to localStorage, max COOK_LOG_MAX entries
  const cookLog = ref<CookLogEntry[]>(loadCookLog())
  // Bookmarks: full RecipeSuggestion snapshots, max BOOKMARKS_MAX
  const bookmarks = ref<RecipeSuggestion[]>(loadBookmarks())

  // Build Your Own wizard preferences -- persisted across sessions
  const missingIngredientMode = ref<MissingIngredientMode>(loadMissingMode())
  const builderFilterMode = ref<BuilderFilterMode>(loadFilterMode())

  // Persist wizard prefs on change
  watch(missingIngredientMode, (val) => localStorage.setItem(MISSING_MODE_KEY, val))
  watch(builderFilterMode, (val) => localStorage.setItem(FILTER_MODE_KEY, val))
  watch(constraints, (val) => saveConstraints(val), { deep: true })
  watch(allergies, (val) => saveAllergies(val), { deep: true })
  watch(excludeIngredients, (val) => saveExcludeIngredients(val), { deep: true })

  const dismissedCount = computed(() => dismissedIds.value.size)

  function _buildRequest(
    pantryItems: string[],
    secondaryPantryItems: Record<string, string> = {},
    extraExcluded: number[] = [],
  ): RecipeRequest {
    const excluded = new Set([...dismissedIds.value, ...extraExcluded])
    return {
      pantry_items: pantryItems,
      secondary_pantry_items: secondaryPantryItems,
      level: level.value,
      constraints: constraints.value,
      allergies: allergies.value,
      expiry_first: true,
      hard_day_mode: hardDayMode.value,
      max_missing: maxMissing.value,
      style_id: styleId.value,
      category: category.value,
      wildcard_confirmed: wildcardConfirmed.value,
      nutrition_filters: nutritionFilters.value,
      excluded_ids: [...excluded],
      exclude_ingredients: excludeIngredients.value,
      shopping_mode: shoppingMode.value,
      pantry_match_only: pantryMatchOnly.value,
      complexity_filter: complexityFilter.value,
      max_time_min: maxTimeMin.value,
      max_total_min: maxTotalMin.value,
      max_active_min: maxActiveMin.value,
    }
  }

  function _trackSeen(suggestions: RecipeSuggestion[]) {
    for (const s of suggestions) {
      if (s.id) seenIds.value = new Set([...seenIds.value, s.id])
    }
  }

  async function suggest(pantryItems: string[], secondaryPantryItems: Record<string, string> = {}) {
    loading.value = true
    error.value = null
    jobStatus.value = null
    seenIds.value = new Set()

    try {
      if (level.value >= 3) {
        await _suggestAsync(pantryItems, secondaryPantryItems)
      } else {
        result.value = await recipesAPI.suggest(_buildRequest(pantryItems, secondaryPantryItems))
        _trackSeen(result.value.suggestions)
      }
    } catch (err: unknown) {
      error.value = err instanceof Error ? err.message : 'Failed to get recipe suggestions'
    } finally {
      loading.value = false
      jobStatus.value = null
    }
  }

  async function _suggestAsync(pantryItems: string[], secondaryPantryItems: Record<string, string>) {
    const queued = await recipesAPI.suggestAsync(_buildRequest(pantryItems, secondaryPantryItems))

    // CLOUD_MODE or future sync fallback: server returned result directly (status 200)
    if ('suggestions' in queued) {
      result.value = queued as unknown as RecipeResult
      _trackSeen(result.value.suggestions)
      return
    }

    jobStatus.value = 'queued'
    const { job_id } = queued
    const deadline = Date.now() + 90_000
    const POLL_MS = 2_500

    while (Date.now() < deadline) {
      await new Promise((resolve) => setTimeout(resolve, POLL_MS))
      const poll = await recipesAPI.pollJob(job_id)
      jobStatus.value = poll.status

      if (poll.status === 'done') {
        result.value = poll.result
        if (result.value) _trackSeen(result.value.suggestions)
        return
      }
      if (poll.status === 'failed') {
        throw new Error(poll.error ?? 'Recipe generation failed')
      }
    }

    throw new Error('Recipe generation timed out — the model may be busy. Try again.')
  }

  async function loadMore(pantryItems: string[], secondaryPantryItems: Record<string, string> = {}) {
    if (!result.value || loading.value) return
    loading.value = true
    error.value = null

    try {
      // Exclude everything already shown (dismissed + all seen this session)
      const more = await recipesAPI.suggest(_buildRequest(pantryItems, secondaryPantryItems, [...seenIds.value]))
      if (more.suggestions.length === 0) {
        error.value = 'No more recipes found — try clearing dismissed or adjusting filters.'
      } else {
        result.value = {
          ...result.value,
          suggestions: [...result.value.suggestions, ...more.suggestions],
          grocery_list: [...new Set([...result.value.grocery_list, ...more.grocery_list])],
          grocery_links: [...result.value.grocery_links, ...more.grocery_links],
        }
        _trackSeen(more.suggestions)
      }
    } catch (err: unknown) {
      error.value = err instanceof Error ? err.message : 'Failed to load more recipes'
    } finally {
      loading.value = false
    }
  }

  function dismiss(id: number) {
    dismissedIds.value = new Set([...dismissedIds.value, id])
    saveDismissed(dismissedIds.value)
    // Remove from current results immediately
    if (result.value) {
      result.value = {
        ...result.value,
        suggestions: result.value.suggestions.filter((s) => s.id !== id),
      }
    }
  }

  function undismiss(id: number) {
    dismissedIds.value = new Set([...dismissedIds.value].filter((d) => d !== id))
    saveDismissed(dismissedIds.value)
  }

  function clearDismissed() {
    dismissedIds.value = new Set()
    localStorage.removeItem(DISMISSED_KEY)
  }

  // Orbital cadence: cookedAt anchors to completion, not to a schedule.
  // Days-since display measures from this timestamp — no debt accumulates.
  function logCook(id: number, title: string) {
    const entry: CookLogEntry = { id, title, cookedAt: Date.now() }
    cookLog.value = [...cookLog.value, entry]
    saveCookLog(cookLog.value)
  }

  function clearCookLog() {
    cookLog.value = []
    localStorage.removeItem(COOK_LOG_KEY)
  }

  function lastCookedDaysAgo(recipeId: number): number | null {
    const entries = cookLog.value.filter((e) => e.id === recipeId)
    if (entries.length === 0) return null
    const latestMs = Math.max(...entries.map((e) => e.cookedAt))
    return Math.floor((Date.now() - latestMs) / 86_400_000)
  }

  function isBookmarked(id: number): boolean {
    return bookmarks.value.some((b) => b.id === id)
  }

  function toggleBookmark(recipe: RecipeSuggestion) {
    if (isBookmarked(recipe.id)) {
      bookmarks.value = bookmarks.value.filter((b) => b.id !== recipe.id)
    } else {
      bookmarks.value = [recipe, ...bookmarks.value]
    }
    saveBookmarks(bookmarks.value)
  }

  function clearBookmarks() {
    bookmarks.value = []
    localStorage.removeItem(BOOKMARKS_KEY)
  }

  function clearConstraints() {
    constraints.value = []
    localStorage.removeItem(CONSTRAINTS_KEY)
  }

  function clearAllergies() {
    allergies.value = []
    localStorage.removeItem(ALLERGIES_KEY)
  }

  function clearExcludeIngredients() {
    excludeIngredients.value = []
    localStorage.removeItem(EXCLUDE_INGREDIENTS_KEY)
  }

  function clearResult() {
    result.value = null
    error.value = null
    wildcardConfirmed.value = false
  }

  async function streamSuggest(
    pantryItems: string[],
    secondaryPantryItems: Record<string, string>,
    onChunk: (chunk: string) => void,
    onDone: () => void,
    onError: (err: string) => void,
  ): Promise<void> {
    const req = _buildRequest(pantryItems, secondaryPantryItems)
    await recipesAPI.suggestRecipeStream(req, onChunk, onDone, onError)
  }

  return {
    result,
    loading,
    error,
    jobStatus,
    level,
    constraints,
    allergies,
    excludeIngredients,
    hardDayMode,
    maxMissing,
    styleId,
    category,
    wildcardConfirmed,
    shoppingMode,
    pantryMatchOnly,
    complexityFilter,
    maxTimeMin,
    maxTotalMin,
    maxActiveMin,
    nutritionFilters,
    dismissedIds,
    dismissedCount,
    cookLog,
    logCook,
    clearCookLog,
    lastCookedDaysAgo,
    bookmarks,
    isBookmarked,
    toggleBookmark,
    clearBookmarks,
    clearConstraints,
    clearAllergies,
    clearExcludeIngredients,
    missingIngredientMode,
    builderFilterMode,
    suggest,
    streamSuggest,
    loadMore,
    dismiss,
    undismiss,
    clearDismissed,
    clearResult,
  }
})
