'use client';

import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
import {
  ShopwareLanguage,
  ShopwareCurrency,
  getLanguages,
  getCurrencies,
  getContext,
  updateContext,
} from './shopware-api';

const LANGUAGE_KEY = 'sw-language-id';
const CURRENCY_KEY = 'sw-currency-id';
const CURRENCY_ISO_KEY = 'sw-currency-iso';

function writeCookie(name: string, value: string, days = 30) {
  if (typeof document === 'undefined') return;
  const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
  document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;
}

function readCookie(name: string): string | null {
  if (typeof document === 'undefined') return null;
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const m = document.cookie.match(new RegExp('(?:^|; )' + escaped + '=([^;]*)'));
  return m ? decodeURIComponent(m[1]) : null;
}

// A selectable storefront language, derived server-side from the channel's
// admin-configured domains and served by /api/languages.
export interface StorefrontLanguage {
  languageId: string;
  name: string; // e.g. "Deutsch"
  localeCode: string; // e.g. "de-DE"
  url: string; // full domain URL, e.g. "http://localhost/de-DE"
  currencyId: string; // the currency the admin assigned to this domain
}

interface SalesChannelContextType {
  languages: ShopwareLanguage[];
  // Languages that actually have a domain — this is what the switcher shows.
  storefrontLanguages: StorefrontLanguage[];
  currencies: ShopwareCurrency[];
  currentLanguageId: string | null;
  currentCurrencyId: string | null;
  currentCurrency: ShopwareCurrency | null;
  defaultLanguageId: string | null;
  currentLocale: string | null; // e.g. "de-DE", or null for the default language
  isReady: boolean;
  setLanguage: (id: string) => Promise<void>;
  setCurrency: (id: string) => Promise<void>;
  formatPrice: (price: number) => string;
  localizeHref: (href: string) => string;
}

const SalesChannelContext = createContext<SalesChannelContextType | undefined>(undefined);

interface SalesChannelProviderProps {
  children: React.ReactNode;
  // Server-passed initial values from cookies, so client-component SSR can
  // format prices / build hrefs with the right currency & locale before the
  // first useEffect runs. Client components can't read `next/headers` on the
  // SSR pass, so the root server layout reads cookies and forwards them here.
  initialCurrencyIso?: string | null;
  initialLocale?: string | null;
  // The active domain's URL path prefix (e.g. "/de-DEs"), forwarded from the
  // middleware so the first SSR render builds links that keep the prefix.
  initialPrefix?: string | null;
}

export function SalesChannelProvider({
  children,
  initialCurrencyIso = null,
  initialLocale = null,
  initialPrefix = null,
}: SalesChannelProviderProps) {
  const [languages, setLanguages] = useState<ShopwareLanguage[]>([]);
  const [storefrontLanguages, setStorefrontLanguages] = useState<StorefrontLanguage[]>([]);
  const [currencies, setCurrencies] = useState<ShopwareCurrency[]>([]);
  const [currentLanguageId, setCurrentLanguageId] = useState<string | null>(null);
  const [currentCurrencyId, setCurrentCurrencyId] = useState<string | null>(null);
  const [currentCurrencyIso, setCurrentCurrencyIso] = useState<string | null>(initialCurrencyIso);
  const [defaultLanguageId, setDefaultLanguageId] = useState<string | null>(null);
  const [currentLocale, setCurrentLocale] = useState<string | null>(initialLocale);
  // The active domain's URL path prefix (e.g. "/de-DEs"), or null for the
  // default domain. Drives every localized href. Seeded from the middleware
  // (initialPrefix) for correct SSR, then kept in sync with the active language
  // below using the admin-configured domain URLs.
  const [currentPrefix, setCurrentPrefix] = useState<string | null>(
    initialPrefix && initialPrefix !== '/' ? initialPrefix : null
  );
  const [isReady, setIsReady] = useState(false);

  // Read the locale prefix the middleware found in the URL (passed back via
  // the `sw-locale` cookie). null means the default language.
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const fromCookie = readCookie('sw-locale');
    setCurrentLocale(fromCookie || null);
    const prefixCookie = readCookie('sw-prefix');
    if (prefixCookie && prefixCookie !== '/') setCurrentPrefix(prefixCookie);
  }, []);

  // Load the domain-derived language list (drives the switcher). Languages only
  // appear here if an admin created a domain for them.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await fetch('/api/languages');
        if (!res.ok) return;
        const data = await res.json();
        if (!cancelled && Array.isArray(data.languages)) {
          setStorefrontLanguages(data.languages as StorefrontLanguage[]);
        }
      } catch {
        // Admin API down / not configured — switcher just stays hidden.
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  // Keep the active URL prefix in sync with the active language by reading the
  // admin-configured domain for that language. This is the authoritative source
  // (the cookie only seeds the first paint) and handles any custom prefix, so
  // localized hrefs always point back at the right domain.
  useEffect(() => {
    if (!currentLanguageId || storefrontLanguages.length === 0) return;
    const dom = storefrontLanguages.find((l) => l.languageId === currentLanguageId);
    if (!dom) return;
    try {
      const prefix = new URL(dom.url).pathname.replace(/\/+$/, '');
      setCurrentPrefix(prefix || null);
    } catch {
      /* malformed domain URL — keep the cookie-seeded value */
    }
  }, [currentLanguageId, storefrontLanguages]);

  // One-time migration: users who already have a context token in
  // localStorage from a previous session need it copied to a cookie so
  // server components can read it on the next navigation.
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const fromStorage = localStorage.getItem('sw-context-token');
    if (fromStorage && !readCookie('sw-context-token')) {
      writeCookie('sw-context-token', fromStorage);
    }
    const isoFromStorage = localStorage.getItem(CURRENCY_ISO_KEY);
    if (isoFromStorage && !readCookie(CURRENCY_ISO_KEY)) {
      writeCookie(CURRENCY_ISO_KEY, isoFromStorage);
    }
  }, []);

  useEffect(() => {
    let cancelled = false;

    (async () => {
      try {
        const [langRes, currRes, ctxRes] = await Promise.allSettled([
          getLanguages(),
          getCurrencies(),
          getContext(),
        ]);
        if (cancelled) return;

        if (langRes.status === 'fulfilled') setLanguages(langRes.value.elements || []);
        if (currRes.status === 'fulfilled') setCurrencies(currRes.value.elements || []);

        const savedLang = typeof window !== 'undefined' ? localStorage.getItem(LANGUAGE_KEY) : null;
        const savedCurr = typeof window !== 'undefined' ? localStorage.getItem(CURRENCY_KEY) : null;
        const ctx = ctxRes.status === 'fulfilled' ? ctxRes.value : null;

        const channelDefault = ctx?.salesChannel?.languageId || ctx?.context?.languageId || null;
        const resolvedLang = savedLang || channelDefault;
        const resolvedCurrId = savedCurr || ctx?.currency?.id || ctx?.context?.currencyId || null;
        setDefaultLanguageId(channelDefault);
        setCurrentLanguageId(resolvedLang);
        setCurrentCurrencyId(resolvedCurrId);

        // Reconcile mismatch: user typed /de-DE/ directly but the cookie token
        // still has the default language. Switch the token to match the URL.
        if (typeof window !== 'undefined') {
          const localeFromCookie = readCookie('sw-locale');
          if (localeFromCookie) {
            const langList = langRes.status === 'fulfilled' ? langRes.value.elements || [] : [];
            const targetLang = langList.find((l) => l.locale?.code === localeFromCookie);
            if (targetLang && targetLang.id !== resolvedLang) {
              await updateContext({ languageId: targetLang.id });
              setCurrentLanguageId(targetLang.id);
              localStorage.setItem(LANGUAGE_KEY, targetLang.id);
              window.location.reload();
              return;
            }
          }
        }

        // Persist the active currency ISO so the static formatPrice fallback
        // (used by components that haven't migrated to useSalesChannel) shows
        // the correct symbol after a switch. Mirrored to a cookie so server
        // renders pick it up too.
        const currList = currRes.status === 'fulfilled' ? currRes.value.elements || [] : [];
        const activeIso =
          ctx?.currency?.isoCode ||
          currList.find((c) => c.id === resolvedCurrId)?.isoCode ||
          null;
        if (activeIso) setCurrentCurrencyIso(activeIso);
        if (typeof window !== 'undefined' && activeIso) {
          localStorage.setItem(CURRENCY_ISO_KEY, activeIso);
          writeCookie(CURRENCY_ISO_KEY, activeIso);
        }
      } finally {
        if (!cancelled) setIsReady(true);
      }
    })();

    return () => {
      cancelled = true;
    };
  }, []);

  const setLanguage = useCallback(
    async (id: string) => {
      // Each admin-configured domain ties a language to a currency (and snippet
      // set). Mirror Shopware's default Storefront: switching language also
      // switches to the currency the admin assigned to that language's domain.
      const domainForLang = storefrontLanguages.find((l) => l.languageId === id);
      const targetCurrencyId = domainForLang?.currencyId || null;

      await updateContext(
        targetCurrencyId
          ? { languageId: id, currencyId: targetCurrencyId }
          : { languageId: id }
      );
      setCurrentLanguageId(id);
      if (targetCurrencyId) setCurrentCurrencyId(targetCurrencyId);
      if (typeof window !== 'undefined') {
        localStorage.setItem(LANGUAGE_KEY, id);

        // Persist the domain's currency so the post-navigation render (and the
        // static formatPrice fallback) show the right symbol immediately.
        if (targetCurrencyId) {
          localStorage.setItem(CURRENCY_KEY, targetCurrencyId);
          const iso = currencies.find((c) => c.id === targetCurrencyId)?.isoCode;
          if (iso) {
            localStorage.setItem(CURRENCY_ISO_KEY, iso);
            writeCookie(CURRENCY_ISO_KEY, iso);
          }
        }

        // Strip the prefix of the domain we're currently on (e.g. "/de-DEs")
        // from the path so we can re-prefix it for the target language. SEO
        // slugs are canonical across languages (see enrichProductsCanonical), so
        // the same path stays valid; the middleware re-resolves it in the new
        // language. Uses the active domain's real prefix, not a fixed pattern.
        const { pathname, search, hash } = window.location;
        const basePath =
          currentPrefix &&
          (pathname === currentPrefix || pathname.startsWith(`${currentPrefix}/`))
            ? pathname.slice(currentPrefix.length) || '/'
            : pathname;

        // Preferred path: navigate to the admin-configured DOMAIN for this
        // language. Its URL carries the right path prefix (and, server-side, the
        // right snippet set + currency), matching Shopware's domain routing.
        const domainLang = storefrontLanguages.find((l) => l.languageId === id);
        if (domainLang) {
          try {
            // Use only the domain's PATH prefix (e.g. "/de-DE"); keep the
            // current frontend origin. In a headless setup the domain's host is
            // Shopware's own (often a different port) and isn't where this app
            // is served — only the path prefix drives language routing here.
            const prefix = new URL(domainLang.url).pathname.replace(/\/+$/, ''); // "/de-DE" or ""
            const tail = basePath === '/' ? '' : basePath;
            const targetPath = `${prefix}${tail}` || '/';
            window.location.assign(`${window.location.origin}${targetPath}${search}${hash}`);
            return;
          } catch {
            // Malformed domain URL — fall through to the legacy prefix swap.
          }
        }

        // Legacy fallback (no domain configured for this language): default
        // language → no prefix, otherwise "/{locale}" prefix.
        const target = languages.find((l) => l.id === id);
        const isDefault = defaultLanguageId !== null && id === defaultLanguageId;
        const localeCode = target?.locale?.code;
        const prefix = isDefault || !localeCode ? '' : `/${localeCode}`;
        const nextPath = prefix
          ? basePath === '/'
            ? `${prefix}/`
            : `${prefix}${basePath}`
          : basePath;

        window.location.assign(`${nextPath}${search}${hash}`);
      }
    },
    [languages, storefrontLanguages, defaultLanguageId, currencies, currentPrefix]
  );

  const setCurrency = useCallback(
    async (id: string) => {
      await updateContext({ currencyId: id });
      setCurrentCurrencyId(id);
      if (typeof window !== 'undefined') {
        localStorage.setItem(CURRENCY_KEY, id);
        const iso = currencies.find((c) => c.id === id)?.isoCode;
        if (iso) {
          localStorage.setItem(CURRENCY_ISO_KEY, iso);
          writeCookie(CURRENCY_ISO_KEY, iso);
        }
        window.location.reload();
      }
    },
    [currencies]
  );

  const currentCurrency = currencies.find((c) => c.id === currentCurrencyId) || null;

  // Prepend the active domain's URL prefix to an internal href so navigation
  // stays inside the current language's domain. The prefix is whatever the
  // admin configured (e.g. "/de-DEs"), not derived from the locale code, so it
  // works for any custom prefix. Pass-through for already-prefixed hrefs and
  // external/non-internal links; default domain (no prefix) returns href as-is.
  const localizeHref = useCallback(
    (href: string): string => {
      if (!href || !href.startsWith('/')) return href;
      if (!currentPrefix) return href;
      // Already prefixed?
      if (href === currentPrefix || href.startsWith(`${currentPrefix}/`)) {
        return href;
      }
      const trimmed = href === '/' ? '' : href;
      return `${currentPrefix}${trimmed}`;
    },
    [currentPrefix]
  );

  const formatPrice = useCallback(
    (price: number) => {
      const iso =
        currentCurrency?.isoCode ||
        currentCurrencyIso ||
        process.env.NEXT_PUBLIC_CURRENCY ||
        'EUR';
      const locale = process.env.NEXT_PUBLIC_LOCALE || 'en-GB';
      try {
        return new Intl.NumberFormat(locale, { style: 'currency', currency: iso }).format(price);
      } catch {
        return `${currentCurrency?.symbol || ''}${price.toFixed(2)}`;
      }
    },
    [currentCurrency, currentCurrencyIso]
  );

  return (
    <SalesChannelContext.Provider
      value={{
        languages,
        storefrontLanguages,
        currencies,
        currentLanguageId,
        currentCurrencyId,
        currentCurrency,
        defaultLanguageId,
        currentLocale,
        isReady,
        setLanguage,
        setCurrency,
        formatPrice,
        localizeHref,
      }}
    >
      {children}
    </SalesChannelContext.Provider>
  );
}

export function useSalesChannel() {
  const ctx = useContext(SalesChannelContext);
  if (!ctx) throw new Error('useSalesChannel must be used within SalesChannelProvider');
  return ctx;
}
