import type {
  BlogPost,
  CaseStudy,
  Industry,
  PartnerBrand,
  Product,
  SiteSettings,
  Solution
} from "@/types/content";

const API_BASE_URL = (process.env.NEXT_PUBLIC_API_URL ?? "http://127.0.0.1:8000").replace(/\/+$/, "");

const configuredRevalidateSeconds = Number(process.env.NEXT_PUBLIC_CONTENT_REVALIDATE_SECONDS ?? 10);
const contentRevalidateSeconds = Number.isFinite(configuredRevalidateSeconds) ? configuredRevalidateSeconds : 10;

/**
 * Prefix relative media paths (e.g. "/storage/foo.jpg") returned by the API with
 * the API origin so <Image>/<video> can load them. Absolute URLs are left as-is.
 */
function resolveMediaUrls(value: unknown): unknown {
  if (Array.isArray(value)) {
    return value.map(resolveMediaUrls);
  }
  if (value && typeof value === "object") {
    return Object.fromEntries(
      Object.entries(value as Record<string, unknown>).map(([key, entry]) => [
        key,
        key === "url" && typeof entry === "string" && entry.startsWith("/") && !entry.startsWith("//")
          ? `${API_BASE_URL}${entry}`
          : resolveMediaUrls(entry)
      ])
    );
  }
  return value;
}

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

// Cap concurrent requests so a single-threaded backend (e.g. `php artisan serve`)
// is not overwhelmed while many static pages are generated in parallel.
const maxConcurrentRequests = 3;
let inFlight = 0;
const waiters: Array<() => void> = [];

async function acquireSlot() {
  if (inFlight < maxConcurrentRequests) {
    inFlight += 1;
    return;
  }
  await new Promise<void>((resolve) => waiters.push(resolve));
  inFlight += 1;
}

function releaseSlot() {
  inFlight -= 1;
  waiters.shift()?.();
}

async function fetchJsonOnce<T>(path: string): Promise<T> {
  await acquireSlot();
  try {
    const response = await fetch(`${API_BASE_URL}/api/v1${path}`, {
      headers: { Accept: "application/json" },
      next: { revalidate: contentRevalidateSeconds }
    });

    if (!response.ok) {
      throw new Error(`Content API request failed (${response.status}) for ${path}`);
    }

    const payload = (await response.json()) as { data: T } | T;
    const data =
      payload && typeof payload === "object" && "data" in payload ? (payload as { data: T }).data : (payload as T);
    return resolveMediaUrls(data) as T;
  } finally {
    releaseSlot();
  }
}

async function requestJson<T>(path: string): Promise<T> {
  const maxAttempts = 6;
  for (let attempt = 1; ; attempt += 1) {
    try {
      return await fetchJsonOnce<T>(path);
    } catch (error) {
      if (attempt >= maxAttempts) {
        throw error instanceof Error ? error : new Error(`Content API request failed for ${path}`);
      }
      // Retry transient connection failures with exponential backoff.
      await sleep(Math.min(300 * 2 ** (attempt - 1), 5000));
    }
  }
}

// During `next build` the content is a static snapshot, so each endpoint only
// needs to be fetched once per worker process. At runtime (`next start`) we skip
// this in-memory cache so content edits show up on refresh — Next's own fetch
// cache still applies the `revalidate` window (ISR).
const isBuild = process.env.NEXT_PHASE === "phase-production-build";
const requestCache = new Map<string, Promise<unknown>>();

function apiFetch<T>(path: string): Promise<T> {
  if (!isBuild) return requestJson<T>(path);

  let cached = requestCache.get(path) as Promise<T> | undefined;
  if (!cached) {
    cached = requestJson<T>(path);
    requestCache.set(path, cached);
  }
  return cached;
}

type HomepageResponse = {
  heroSlides?: SiteSettings["homepageHeroSlides"];
  overview?: SiteSettings["homepageOverview"];
  showcase?: SiteSettings["homepageShowcase"];
  products?: SiteSettings["homepageProducts"];
  roomDesigner?: SiteSettings["roomDesignerPage"];
};

export async function getSiteSettings(): Promise<SiteSettings> {
  const [settings, homepage] = await Promise.all([
    apiFetch<SiteSettings>("/site-settings"),
    apiFetch<HomepageResponse>("/homepage")
  ]);

  return {
    ...settings,
    homepageHeroSlides: homepage.heroSlides ?? [],
    homepageOverview: homepage.overview,
    homepageShowcase: homepage.showcase,
    homepageProducts: homepage.products,
    roomDesignerPage: homepage.roomDesigner
  };
}

export async function getSolutions(): Promise<Solution[]> {
  return apiFetch<Solution[]>("/solutions");
}

export async function getProducts(): Promise<Product[]> {
  return apiFetch<Product[]>("/products");
}

export async function getIndustries(): Promise<Industry[]> {
  return apiFetch<Industry[]>("/industries");
}

export async function getCaseStudies(): Promise<CaseStudy[]> {
  type RawCaseStudy = Omit<CaseStudy, "productsOrTechnologiesUsed"> & {
    productsOrTechnologiesUsed?: string[] | { en?: string[]; id?: string[] };
  };

  const items = await apiFetch<RawCaseStudy[]>("/case-studies");

  return items.map((item) => ({
    ...item,
    productsOrTechnologiesUsed: Array.isArray(item.productsOrTechnologiesUsed)
      ? item.productsOrTechnologiesUsed
      : item.productsOrTechnologiesUsed?.en ?? item.productsOrTechnologiesUsed?.id ?? []
  }));
}

export async function getPartnerBrands(): Promise<PartnerBrand[]> {
  return apiFetch<PartnerBrand[]>("/partner-brands");
}

export async function getBlogPosts(): Promise<BlogPost[]> {
  const items = await apiFetch<BlogPost[]>("/blog-posts");
  return [...items].sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime());
}
