'use client'

import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import type { Product, Category, ViewType, FilterState } from './types'

export interface CartItem {
  code: string
  qty: number
}

// Distribuidor vinculado à sessão atual. Quando o cliente abre um link
// ?d=slug, carregamos o distribuidor do backend e guardamos aqui — o
// checkout então dispara para o WhatsApp dele (wa.me/{whatsapp}?text=...).
export interface Distributor {
  slug: string
  name: string
  whatsapp: string
}

interface CatalogState {
  // Navigation
  view: ViewType
  selectedProduct: Product | null
  selectedCategory: string
  selectedPage: number

  // Data
  products: Product[]
  categories: Category[]
  loading: boolean

  // Filters
  filters: FilterState
  searchQuery: string

  // UI State — overlays on top of the magazine viewer
  sidebarOpen: boolean      // categories / index drawer
  searchOpen: boolean       // search drawer
  favoritesOpen: boolean    // favorites drawer
  adminOpen: boolean        // admin panel
  cartOpen: boolean         // cart drawer
  showThumbnails: boolean   // bottom thumbnail strip
  fullscreen: boolean
  zoom: number

  // Favorites (persisted)
  favorites: string[] // product codes
  recentlyViewed: string[] // product codes
  recentPages: number[] // page numbers visited (session-only, not persisted)

  // Cart (persisted)
  cart: CartItem[] // product code + qty
  // Last item added to cart — used by the global CartToast to show a
  // confirmation whenever a product is added. NOT persisted (ephemeral).
  // The toast component watches `lastAdded.timestamp` to re-fire.
  lastAdded: { code: string; qty: number; timestamp: number } | null

  // Pin positions on catalog pages (persisted) — relative coords (0-1)
  // Key format: `${page}:${code}` so the same product on different pages
  // can have different fixed positions.
  pinPositions: Record<string, { x: number; y: number }>
  // Manually-added pins tied to a specific page (persisted)
  manualPins: Array<{ code: string; page: number; createdAt: number }>
  // Auto-pins the user has hidden (persisted) — by product code
  hiddenPins: string[]
  // Manage pins mode (UI only, not persisted)
  managePinsMode: boolean

  // ===== Distribuidor (vinculação de WhatsApp) =====
  // `currentDistributor` — distribuidor vinculado à sessão ATUAL. Vem do
  // deep link ?d=slug (carregado do backend). Persistido em localStorage
  // para que o cliente continue no mesmo distribuidor nas próximas visitas.
  currentDistributor: Distributor | null
  // `myDistributorProfile` — perfil do PRÓPRIO distribuidor que se cadastrou
  // neste dispositivo. Guarda o slug gerado para ele poder reabrir o painel
  // "Meu Link" sem precisar cadastrar de novo.
  myDistributorProfile: Distributor | null
  // UI: modal de onboarding / painel "Meu Link" aberto?
  distributorOnboardingOpen: boolean
  // Cliente já viu o popup de boas-vindas (orientação de navegação +
  // compra)? Persistido para mostrar só na 1ª visita de cada cliente.
  clientWelcomeSeen: boolean
  // Token efêmero que muda sempre que o usuário desliza o catálogo no mobile.
  // Os pins assistem a este token e disparam a animação de "queda e tremor".
  swipeAnimationToken: number

  // Actions
  setView: (view: ViewType) => void
  setSelectedProduct: (product: Product | null) => void
  setSelectedCategory: (slug: string) => void
  setSelectedPage: (page: number) => void
  setProducts: (products: Product[]) => void
  setCategories: (categories: Category[]) => void
  setLoading: (loading: boolean) => void
  setFilters: (filters: Partial<FilterState>) => void
  resetFilters: () => void
  setSearchQuery: (q: string) => void
  setSidebarOpen: (open: boolean) => void
  setSearchOpen: (open: boolean) => void
  setFavoritesOpen: (open: boolean) => void
  setAdminOpen: (open: boolean) => void
  setCartOpen: (open: boolean) => void
  setShowThumbnails: (open: boolean) => void
  setFullscreen: (fs: boolean) => void
  setZoom: (zoom: number) => void
  toggleFavorite: (code: string) => void
  addRecentlyViewed: (code: string) => void
  addRecentPage: (page: number) => void
  navigateToProduct: (product: Product) => void
  navigateToCategory: (slug: string) => void
  goToPage: (page: number) => void
  // Cart actions
  addToCart: (code: string, qty?: number) => void
  removeFromCart: (code: string) => void
  setCartQty: (code: string, qty: number) => void
  incrementCart: (code: string) => void
  decrementCart: (code: string) => void
  clearCart: () => void
  cartCount: () => number
  // Pin actions
  setPinPosition: (page: number, code: string, x: number, y: number) => void
  clearPinPosition: (page: number, code: string) => void
  initPinPosition: (page: number, code: string, x: number, y: number) => void
  clearAllPinPositions: () => void
  addManualPin: (code: string, page: number) => void
  removeManualPin: (code: string, page: number) => void
  toggleHiddenPin: (code: string) => void
  setManagePinsMode: (on: boolean) => void
  // Shared pins — fetch from backend on mount so every visitor sees the
  // same dragged positions + manual pins (uploaded by the admin).
  loadSharedPins: () => Promise<void>
  // ===== Distribuidor actions =====
  setCurrentDistributor: (d: Distributor | null) => void
  clearCurrentDistributor: () => void
  setMyDistributorProfile: (d: Distributor | null) => void
  setDistributorOnboardingOpen: (open: boolean) => void
  setClientWelcomeSeen: (seen: boolean) => void
  // Carrega o distribuidor a partir do slug na URL (?d=slug). Retorna o
  // distribuidor encontrado ou null. Em caso de erro de rede, mantém o
  // estado atual (offline-friendly).
  loadDistributorFromUrl: () => Promise<Distributor | null>
  triggerSwipeAnimation: () => void
}

const defaultFilters: FilterState = {
  category: 'all',
  search: '',
  code: '',
  isLaunch: false,
  isPromo: false,
  isLowerPrice: false,
  isBestseller: false,
  fragrance: '',
  activeIngredient: '',
  minPrice: '',
  maxPrice: '',
  sort: 'default',
}

// =========================================================================
// Shared pin persistence helpers (module-level).
//
// Pin positions + manual pins are persisted to the backend so EVERY visitor
// sees the same layout (admin drags a pin → all future visitors see it in
// the new spot). localStorage stays as an offline cache / instant-load
// fallback; the backend is the source of truth.
//
// `schedulePinPut` debounces rapid drag updates into one API call per pin
// (500ms after the last move). `cancelPinPut` clears a pending write when
// the position is cleared before the debounce fires.
// =========================================================================
const pinPutTimers = new Map<string, ReturnType<typeof setTimeout>>()
const PIN_PUT_DEBOUNCE_MS = 500

function schedulePinPut(page: number, code: string, x: number, y: number) {
  const key = `${page}:${code}`
  const existing = pinPutTimers.get(key)
  if (existing) clearTimeout(existing)
  const timer = setTimeout(() => {
    pinPutTimers.delete(key)
    fetch('/api/pins', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ page, code, x, y }),
    }).catch(() => { /* offline — local state is still correct */ })
  }, PIN_PUT_DEBOUNCE_MS)
  pinPutTimers.set(key, timer)
}

function cancelPinPut(page: number, code: string) {
  const key = `${page}:${code}`
  const existing = pinPutTimers.get(key)
  if (existing) {
    clearTimeout(existing)
    pinPutTimers.delete(key)
  }
}

export const useCatalogStore = create<CatalogState>()(
  persist(
    (set, get) => ({
      view: 'home',
      selectedProduct: null,
      selectedCategory: 'all',
      selectedPage: 1,
      products: [],
      categories: [],
      loading: false,
      filters: defaultFilters,
      searchQuery: '',
      sidebarOpen: false,
      searchOpen: false,
      favoritesOpen: false,
      adminOpen: false,
      cartOpen: false,
      showThumbnails: false,
      fullscreen: false,
      zoom: 1,
      favorites: [],
      recentlyViewed: [],
      recentPages: [],
      cart: [],
      lastAdded: null,
      pinPositions: {},
      manualPins: [],
      hiddenPins: [],
      managePinsMode: false,
      currentDistributor: null,
      myDistributorProfile: null,
      distributorOnboardingOpen: false,
      clientWelcomeSeen: false,
      swipeAnimationToken: 0,

      setView: (view) => set({ view }),
      setSelectedProduct: (product) => set({ selectedProduct: product }),
      setSelectedCategory: (slug) => set({ selectedCategory: slug }),
      setSelectedPage: (page) => set({ selectedPage: page }),
      setProducts: (products) => set({ products }),
      setCategories: (categories) => set({ categories }),
      setLoading: (loading) => set({ loading }),
      setFilters: (filters) => set({ filters: { ...get().filters, ...filters } }),
      resetFilters: () => set({ filters: defaultFilters }),
      setSearchQuery: (q) => set({ searchQuery: q }),
      setSidebarOpen: (open) => set({ sidebarOpen: open }),
      setSearchOpen: (open) => set({ searchOpen: open }),
      setFavoritesOpen: (open) => set({ favoritesOpen: open }),
      setAdminOpen: (open) => set({ adminOpen: open }),
      setCartOpen: (open) => set({ cartOpen: open }),
      setShowThumbnails: (open) => set({ showThumbnails: open }),
      setFullscreen: (fs) => set({ fullscreen: fs }),
      setZoom: (zoom) => set({ zoom }),

      toggleFavorite: (code) => {
        const { favorites } = get()
        if (favorites.includes(code)) {
          set({ favorites: favorites.filter(c => c !== code) })
        } else {
          set({ favorites: [...favorites, code] })
        }
      },

      // ===== Cart actions =====
      addToCart: (code, qty = 1) => {
        const { cart } = get()
        const existing = cart.find(i => i.code === code)
        if (existing) {
          set({ cart: cart.map(i => i.code === code ? { ...i, qty: i.qty + qty } : i) })
        } else {
          set({ cart: [...cart, { code, qty }] })
        }
        // Fire the global toast confirmation
        set({ lastAdded: { code, qty, timestamp: Date.now() } })
      },
      removeFromCart: (code) => {
        const { cart } = get()
        set({ cart: cart.filter(i => i.code !== code) })
      },
      setCartQty: (code, qty) => {
        const { cart } = get()
        if (qty <= 0) {
          set({ cart: cart.filter(i => i.code !== code) })
        } else {
          set({ cart: cart.map(i => i.code === code ? { ...i, qty } : i) })
        }
      },
      incrementCart: (code) => {
        const { cart } = get()
        set({ cart: cart.map(i => i.code === code ? { ...i, qty: i.qty + 1 } : i) })
        // Fire the global toast confirmation
        set({ lastAdded: { code, qty: 1, timestamp: Date.now() } })
      },
      decrementCart: (code) => {
        const { cart } = get()
        const existing = cart.find(i => i.code === code)
        if (!existing) return
        if (existing.qty <= 1) {
          set({ cart: cart.filter(i => i.code !== code) })
        } else {
          set({ cart: cart.map(i => i.code === code ? { ...i, qty: i.qty - 1 } : i) })
        }
      },
      clearCart: () => set({ cart: [] }),
      cartCount: () => {
        const { cart } = get()
        return cart.reduce((s, i) => s + i.qty, 0)
      },

      // ===== Pin actions =====
      // Positions are keyed by `${page}:${code}` so each pin has a FIXED
      // spot on its page that never shifts when other pins change.
      // Dragged positions are persisted to the backend (debounced) so all
      // visitors see the same layout.
      setPinPosition: (page, code, x, y) => {
        const { pinPositions } = get()
        const key = `${page}:${code}`
        set({ pinPositions: { ...pinPositions, [key]: { x, y } } })
        schedulePinPut(page, code, x, y)
      },
      clearPinPosition: (page, code) => {
        const { pinPositions } = get()
        const key = `${page}:${code}`
        const next = { ...pinPositions }
        delete next[key]
        set({ pinPositions: next })
        cancelPinPut(page, code)
        fetch(`/api/pins?page=${page}&code=${encodeURIComponent(code)}`, { method: 'DELETE' }).catch(() => {})
      },
      // Set an initial position ONLY if the pin doesn't have one yet.
      // Called once per pin when it first appears on a page so it gets a
      // fixed spot that never shifts when other pins change.
      // NOTE: defaults are NOT persisted to the backend — they're computed
      // deterministically by getDefaultPinPosition(), so every visitor
      // computes the same default. Only explicit drags are shared.
      initPinPosition: (page, code, x, y) => {
        const { pinPositions } = get()
        const key = `${page}:${code}`
        if (pinPositions[key]) return
        set({ pinPositions: { ...pinPositions, [key]: { x, y } } })
      },
      clearAllPinPositions: () => {
        set({ pinPositions: {} })
        // Best-effort: clear all pending PUTs. (We can't easily delete all
        // backend positions in one call without an endpoint — leave backend
        // as-is; this is a rare local-only reset.)
        pinPutTimers.forEach(t => clearTimeout(t))
        pinPutTimers.clear()
      },
      addManualPin: (code, page) => {
        const { manualPins, pinPositions } = get()
        // avoid duplicates (same code + same page)
        if (manualPins.some(m => m.code === code && m.page === page)) return
        // Assign a fixed initial position in the TOP-LEFT corner — a spot
        // that is usually empty on catalog pages (product photos tend to be
        // centered/right), so the freshly added pin is easy to spot.
        const key = `${page}:${code}`
        const initPos = pinPositions[key] ?? { x: 0.12, y: 0.12 }
        const createdAt = Date.now()
        set({
          manualPins: [...manualPins, { code, page, createdAt }],
          pinPositions: { ...pinPositions, [key]: initPos },
        })
        // Persist manual pin + its initial position to backend (shared)
        fetch('/api/pins/manual', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ code, page }),
        }).catch(() => {})
        if (!pinPositions[key]) {
          schedulePinPut(page, code, initPos.x, initPos.y)
        }
      },
      removeManualPin: (code, page) => {
        const { manualPins } = get()
        set({ manualPins: manualPins.filter(m => !(m.code === code && m.page === page)) })
        cancelPinPut(page, code)
        fetch(`/api/pins/manual?page=${page}&code=${encodeURIComponent(code)}`, { method: 'DELETE' }).catch(() => {})
      },
      toggleHiddenPin: (code) => {
        const { hiddenPins } = get()
        const isHidden = hiddenPins.includes(code)
        if (isHidden) {
          // Un-hide (re-show): remove locally + DELETE from backend
          set({ hiddenPins: hiddenPins.filter(c => c !== code) })
          fetch(`/api/pins/hidden?code=${encodeURIComponent(code)}`, { method: 'DELETE' }).catch(() => {})
        } else {
          // Hide: add locally + POST to backend (shared — persists across
          // devices/sessions/visitors)
          set({ hiddenPins: [...hiddenPins, code] })
          fetch('/api/pins/hidden', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ code }),
          }).catch(() => {})
        }
      },
      setManagePinsMode: (on) => set({ managePinsMode: on }),

      // ===== Distribuidor actions =====
      setCurrentDistributor: (d) => set({ currentDistributor: d }),
      clearCurrentDistributor: () => set({ currentDistributor: null }),
      setMyDistributorProfile: (d) => set({ myDistributorProfile: d }),
      setDistributorOnboardingOpen: (open) => set({ distributorOnboardingOpen: open }),
      setClientWelcomeSeen: (seen) => set({ clientWelcomeSeen: seen }),
      loadDistributorFromUrl: async () => {
        if (typeof window === 'undefined') return null
        const params = new URLSearchParams(window.location.search)
        const slug = params.get('d')
        if (!slug) {
          // Sem ?d na URL: limpa o distribuidor vinculado (cliente saiu do link compartilhado)
          set({ currentDistributor: null })
          return null
        }
        try {
          const res = await fetch(`/api/distributors/${encodeURIComponent(slug)}`)
          if (!res.ok) {
            // Distribuidor nao encontrado: limpa o estado (link invalido)
            set({ currentDistributor: null })
            return null
          }
          const json = await res.json()
          if (json?.data) {
            const d: Distributor = {
              slug: json.data.slug,
              name: json.data.name,
              whatsapp: json.data.whatsapp,
            }
            set({ currentDistributor: d })
            return d
          }
          // Resposta valida mas sem dados: limpa o estado
          set({ currentDistributor: null })
        } catch {
          // Erro de rede: mantém estado atual (offline-friendly)
        }
        return null
      },

      triggerSwipeAnimation: () => set({ swipeAnimationToken: Date.now() }),

      // ===== Shared pins sync =====
      // Called once on mount. Fetches all shared pin positions + manual pins
      // from the backend and merges with local state:
      //   - backend positions win on conflict (source of truth)
      //   - local-only positions (admin's unsynced drags) are uploaded
      //   - same merge for manual pins
      // On network failure, local state is kept as-is (offline-friendly).
      loadSharedPins: async () => {
        try {
          const res = await fetch('/api/pins')
          if (!res.ok) {
            console.error('Failed to load shared pins:', await res.text())
            return
          }
          const json = await res.json()
          if (json.status === 'error') {
            console.error('API error loading pins:', json.message)
            return
          }

          const backendPositions: Record<string, { x: number; y: number }> = {}
          for (const p of (json.positions || [])) {
            backendPositions[`${p.page}:${p.code}`] = { x: p.x, y: p.y }
          }
          const backendManual: Array<{ code: string; page: number; createdAt: number }> =
            (json.manualPins || []).map((m: { code: string; page: number; createdAt: number }) => ({
              code: m.code, page: m.page, createdAt: m.createdAt,
            }))

          const { pinPositions: localPos, manualPins: localManual, hiddenPins: localHidden } = get()

          // Merge positions: backend + local-only (uploaded)
          const merged: Record<string, { x: number; y: number }> = { ...backendPositions }
          for (const [key, val] of Object.entries(localPos)) {
            if (!backendPositions[key]) {
              merged[key] = val
              const [pageStr, code] = key.split(':')
              schedulePinPut(parseInt(pageStr, 10), code, val.x, val.y)
            }
          }

          // Merge manual pins: backend + local-only (uploaded)
          const backendManualKeys = new Set(backendManual.map(m => `${m.page}:${m.code}`))
          const mergedManual = [...backendManual]
          for (const m of localManual) {
            if (!backendManualKeys.has(`${m.page}:${m.code}`)) {
              mergedManual.push(m)
              fetch('/api/pins/manual', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ code: m.code, page: m.page }),
              }).catch(() => {})
            }
          }

          // Merge hidden pins: backend (source of truth) + local-only (uploaded).
          // Backend wins on conflict so a pin hidden on another device stays
          // hidden here too.
          const backendHidden: string[] = json.hiddenPins || []
          const backendHiddenSet = new Set(backendHidden)
          const mergedHidden = [...backendHidden]
          for (const code of localHidden) {
            if (!backendHiddenSet.has(code)) {
              mergedHidden.push(code)
              fetch('/api/pins/hidden', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ code }),
              }).catch(() => {})
            }
          }

          set({ pinPositions: merged, manualPins: mergedManual, hiddenPins: mergedHidden })
        } catch {
          // Network error — keep local state (offline mode)
        }
      },

      addRecentlyViewed: (code) => {
        const { recentlyViewed } = get()
        const filtered = recentlyViewed.filter(c => c !== code)
        set({ recentlyViewed: [code, ...filtered].slice(0, 12) })
      },

      addRecentPage: (page) => {
        const { recentPages } = get()
        const filtered = recentPages.filter(p => p !== page)
        set({ recentPages: [page, ...filtered].slice(0, 8) })
      },

      navigateToProduct: (product) => {
        set({ selectedProduct: product })
        get().addRecentlyViewed(product.code)
      },

      navigateToCategory: (slug) => {
        set({ selectedCategory: slug, filters: { ...get().filters, category: slug } })
      },

      goToPage: (page) => {
        const clamped = Math.max(1, Math.min(91, page))
        set({ selectedPage: clamped })
        get().addRecentPage(clamped)
        if (typeof window !== 'undefined') {
          // sync URL
          const url = new URL(window.location.href)
          url.searchParams.set('page', String(clamped))
          url.searchParams.delete('produto')
          window.history.replaceState({}, '', url.toString())
        }
      },
    }),
    {
      name: 'abelha-rainha-catalog',
      version: 7,
      partialize: (state) => ({
        favorites: state.favorites,
        recentlyViewed: state.recentlyViewed,
        cart: state.cart,
        pinPositions: state.pinPositions,
        manualPins: state.manualPins,
        hiddenPins: state.hiddenPins,
        currentDistributor: state.currentDistributor,
        myDistributorProfile: state.myDistributorProfile,
        clientWelcomeSeen: state.clientWelcomeSeen,
      }),
      // v5: pins feature was rewritten from scratch. Clear ALL pin data so
      // the new implementation starts from a clean slate (old buggy default
      // positions, manual pins, and hidden pins are discarded).
      migrate: (persistedState: any, version: number) => {
        if (version < 5) {
          persistedState.pinPositions = {}
          persistedState.manualPins = []
          persistedState.hiddenPins = []
        }
        return persistedState
      },
    }
  )
)
