import { ProcessedHeaderFooterHomeData } from "@/src/lib/types/header";
import { ProcessedProgram } from "@/src/lib/types/programs";
import { cleanFranchiseeId, extractDomain, extractFranchiseeId, extractFranchiseeIdFromHeaders } from "./utils/domain";
import { headerFooterService } from "./services/headerFooterService";
import { programsService } from "./services/programsService";
import { workshopService, type Activity } from "./services/workshopService";
import {
  cachedByTenant,
  DegradedResponseError,
  TTL,
  type TenantContext,
} from "./cache/tenantCache";



/**
 * Fetch header/footer/banner data for PAGES
 * Accepts searchParams from page props
 */
export async function fetchHeaderFooterHomeData(
  searchParams?: Record<string, string | string[]>
): Promise<ProcessedHeaderFooterHomeData> {
  const domain = await extractDomain();

  // Note: We still pass rawId from searchParams here to ensure immediate page-level context,
  // but the API client will also fallback to headers/cookies automatically.
  const rawId = searchParams ? extractFranchiseeId(searchParams) : null;
  const cleanedId = cleanFranchiseeId(rawId);

  return headerFooterService.fetchByDomain(domain, cleanedId);
}

/**
 * Fetch header/footer/banner data for LAYOUTS
 * Reads franchiseeId from middleware headers automatically
 */
export async function fetchHeaderFooterForLayout(): Promise<ProcessedHeaderFooterHomeData> {
  const domain = await extractDomain();

  // We don't need to manually extract franchiseeId from headers here anymore!
  // The headerFooterService uses apiClient, which has an interceptor 
  // that will automatically inject the x-franchisee-id header context.
  return headerFooterService.fetchByDomain(domain);
}

/**
 * The three calls the root layout needs to draw nav chrome.
 */
export interface LayoutData {
  headerFooter: ProcessedHeaderFooterHomeData;
  programs: ProcessedProgram[];
  workshops: Activity[];
}

/**
 * Resolve the tenant context from request headers.
 *
 * MUST be called in request scope, before entering any cached fetcher -
 * `headers()` is unavailable inside `unstable_cache`, and the apiClient
 * interceptor silently swallows that failure. See lib/cache/tenantCache.ts.
 */
export async function getTenantContext(): Promise<TenantContext> {
  const [domain, { franchiseeId, lang }] = await Promise.all([
    extractDomain(),
    getGlobalParams(),
  ]);

  return { domain, franchiseeId, lang };
}

/**
 * The uncached path. Services are called directly with explicit
 * domain/franchiseeId/lang rather than through getProgramsAction() and
 * fetchWorkshopActivities(), because those resolve the domain themselves and
 * pass `null` for franchiseeId, relying on the interceptor - the exact
 * dependency that breaks inside a cached fetcher.
 */
async function fetchLayoutDataUncached({
  domain,
  franchiseeId,
  lang,
}: TenantContext): Promise<LayoutData> {
  const [headerFooter, programs, workshops] = await Promise.all([
    headerFooterService.fetchByDomain(domain, franchiseeId, lang),
    programsService.fetchByDomain(domain, franchiseeId, lang),
    workshopService.fetchActivitiesByDomain(domain, franchiseeId, lang),
  ]);

  return { headerFooter, programs, workshops };
}

const cachedLayoutData = cachedByTenant<LayoutData>(
  "layout",
  async (ctx) => {
    const data = await fetchLayoutDataUncached(ctx);

    // The services never throw on a backend problem - they return empty/static
    // fallbacks, which the cache cannot tell apart from a real result. Storing
    // one would freeze a broken state in for the whole TTL, so a 30-second
    // backend restart would leave every tenant with an empty nav menu for five
    // minutes after the backend had already recovered.
    //
    // Requiring BOTH lists to be empty keeps the false-positive rate down: a
    // tenant legitimately having no activities is normal, having neither
    // programs nor activities means the site could not render its nav anyway.
    if (data.programs.length === 0 && data.workshops.length === 0) {
      throw new DegradedResponseError("empty programs and activities");
    }

    return data;
  },
  {
    revalidate: TTL.CHROME,
    types: ["header-footer", "programs", "activities"],
  },
);

/**
 * Header/footer, programs and activities for the root layout, cached per
 * tenant for TTL.CHROME and purged on publish via /api/revalidate.
 */
export async function fetchLayoutData(): Promise<LayoutData> {
  const ctx = await getTenantContext();

  try {
    return await cachedLayoutData(ctx);
  } catch (error) {
    if (error instanceof DegradedResponseError) {
      // Serve it, but do not cache it - the rejection above already kept it out
      // of the store, so the next request retries immediately instead of
      // waiting out the TTL.
      console.warn(`[layout] ${error.message} for ${ctx.domain} - serving uncached`);
      return fetchLayoutDataUncached(ctx);
    }

    // DomainNotRecognizedError still reaches layout.tsx, which redirects.
    throw error;
  }
}

/**
 * Retrieves the current global context from middleware headers.
 * Used as a fallback when URL parameters are missing (Silent Persistence).
 */
export async function getGlobalParams() {
  const { headers } = await import("next/headers");
  const headersList = await headers();

  const lang = headersList.get("x-lang") || "default";
  const franchiseeId = headersList.get("x-franchisee-id") || null;

  return { lang, franchiseeId };
}
