import axios from "axios";
import { cache } from "react";
import { unstable_cache } from "next/cache";
import { getStrapiImageUrl } from "./getStrapiUrl";
import type { MeilisearchSearchHit } from "./meilisearch";
import {
  getPrimaryTypeSlugForUrl,
  normalizeTypesFromStrapi,
  type PropertyTypePageRef,
} from "./propertyTypes";

// Single point for Strapi URL. Use STRAPI_URL/STRAPI_INTERNAL_URL for build/SSR when CMS is on same server (avoids ENOTFOUND).
const STRAPI_URL =
  process.env.NEXT_PUBLIC_STRAPI_URL ||
  "https://cms.navana-realestate.com";
const STRAPI_API_TOKEN = process.env.STRAPI_API_TOKEN;

/**
 * REST collection name for Property Page entries (hero/SEO per /properties/[slug]).
 * Strapi `api::property-page` → `/api/property-pages`.
 * Override if you use a different UID (e.g. listing-property-page → listing-property-pages).
 */
const LISTING_PROPERTY_PAGES_API =
  process.env.NEXT_PUBLIC_STRAPI_LISTING_PROPERTY_PAGES_API ||
  "property-pages";

const isAxios404 = (error: unknown): boolean =>
  axios.isAxiosError(error) && error.response?.status === 404;

// Create axios instance with timeout and retry configuration for production stability
const axiosInstance = axios.create({
  timeout: 30000, // 30 seconds timeout
  headers: {
    "Content-Type": "application/json",
  },
});

// Helper function to safely make axios requests with proper error handling
async function safeAxiosGet(url: string, retries = 3): Promise<any> {
  let lastError: any;
  const bearerToken =
    STRAPI_API_TOKEN && STRAPI_API_TOKEN.trim()
      ? STRAPI_API_TOKEN.trim()
      : undefined;
  let allowAuthHeader = !!bearerToken;
  
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await axiosInstance.get(url, {
        ...(allowAuthHeader && bearerToken
          ? { headers: { Authorization: `Bearer ${bearerToken}` } }
          : {}),
      });
      
      // Validate response structure
      if (response && response.status >= 200 && response.status < 300) {
        return response;
      }
      
      throw new Error(`Invalid response status: ${response.status}`);
    } catch (error: any) {
      lastError = error;

      // If a configured token is invalid, retry once without auth.
      // This keeps public endpoints working in local/dev even with stale tokens.
      if (error?.response?.status === 401 && allowAuthHeader) {
        allowAuthHeader = false;
        continue;
      }
      
      // Don't retry on 4xx errors (client errors)
      if (error.response && error.response.status >= 400 && error.response.status < 500) {
        throw error;
      }
      
      // Log retry attempts
      if (attempt < retries) {
        console.warn(
          `[Strapi API] Request failed (attempt ${attempt}/${retries}): ${url.substring(0, 100)}... Error: ${error.message}`
        );
        // Wait before retrying (exponential backoff)
        await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
      }
    }
  }
  
  // All retries failed
  console.error(
    `[Strapi API] All ${retries} attempts failed for: ${url.substring(0, 100)}...`,
    lastError?.message || lastError
  );
  throw lastError;
}

/** Map filter status values to Strapi propertyStatus enum (on-going -> ongoing) */
function normalizeStatusForApi(status: string | undefined): string | undefined {
  if (!status || status === "all") return undefined;
  if (status === "on-going") return "ongoing";
  return status;
}

/** SEO component from Strapi (shared.seo: title, description, image, keywords) */
export interface SeoData {
  title?: string;
  description?: string;
  imageUrl?: string | null;
  keywords?: string;
}

function getStrapiMediaUrl(rel: string | undefined): string | null {
  if (!rel || typeof rel !== "string") return null;
  if (rel.startsWith("http")) return rel;
  const base = STRAPI_URL || "";
  return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : null;
}

/** Extract relative or absolute URL from Strapi media (v4/v5: single, array, nested). */
function getMediaUrlFromAny(img: any): string | null {
  if (!img) return null;
  let u: string | undefined;
  if (typeof img.url === "string") u = img.url;
  else if (img.data) {
    const d = img.data;
    if (Array.isArray(d) && d[0]) {
      u = d[0].url ?? d[0].attributes?.url;
    } else if (d && typeof d === "object") {
      u = d.url ?? d.attributes?.url;
    }
  }
  if (!u || typeof u !== "string") u = img.attributes?.url;
  if (!u || typeof u !== "string") return null;
  if (u.startsWith("http")) return u;
  return getStrapiMediaUrl(u);
}

/** Build SeoData from Strapi page data. Pass getMediaUrlFrom when available (from fetch), else image URL is resolved via getMediaUrlFromAny. */
function normalizeSeoFromData(
  data: any,
  getMediaUrlFrom?: (img: any) => string | null
): SeoData | undefined {
  const seo = data?.SEO ?? data?.seo;
  if (!seo) return undefined;
  const imageUrl =
    (getMediaUrlFrom ? getMediaUrlFrom(seo.image) : null) ??
    getMediaUrlFromAny(seo.image);
  return {
    title: seo.title ?? undefined,
    description: seo.description ?? undefined,
    imageUrl: imageUrl ?? undefined,
    keywords: seo.keywords ?? undefined,
  };
}

export interface OfficeAddress {
  title: string;
  phone: string;
  email: string;
  address: string;
  locationUrl?: string;
}

export interface ContactPageData {
  heroTitle: string;
  heroDescription: string;
  heroImageMobileUrl?: string | null;
  heroDesktopSmallImageUrl?: string | null;
  heroDesktopBigImageUrl?: string | null;
  offices: OfficeAddress[];
  seo?: SeoData;
}

export interface CustomerContactFormData {
  name: string;
  email: string;
  phone: string;
  size: string;
  location: string;
  message: string;
}

export interface LandOwnerContactFormData {
  name: string;
  contact_person: string;
  email: string;
  phone: string;
  size: string;
  location: string;
  message: string;
}

export const submitCustomerContact = async (
  data: CustomerContactFormData
): Promise<{ success: boolean; error?: string }> => {
  try {
    const response = await axios.post(`${STRAPI_URL}/api/customer-contacts`, {
      data: {
        name: data.name,
        email: data.email,
        phone: data.phone,
        size: data.size,
        location: data.location,
        message: data.message,
      },
    });

    if (response.data?.data) {
      return { success: true };
    }
    return { success: false, error: "Unexpected response format" };
  } catch (error: any) {
    console.error("Error submitting customer contact:", error);
    const errorMessage =
      error.response?.data?.error?.message ||
      error.message ||
      "Failed to submit form. Please try again.";
    return { success: false, error: errorMessage };
  }
};

export const submitLandOwnerContact = async (
  data: LandOwnerContactFormData
): Promise<{ success: boolean; error?: string }> => {
  try {
    const response = await axios.post(`${STRAPI_URL}/api/land-owner-contacts`, {
      data: {
        name: data.name,
        contact_person: data.contact_person,
        email: data.email,
        phone: data.phone,
        size: data.size,
        location: data.location,
        message: data.message,
      },
    });

    if (response.data?.data) {
      return { success: true };
    }
    return { success: false, error: "Unexpected response format" };
  } catch (error: any) {
    console.error("Error submitting land owner contact:", error);
    const errorMessage =
      error.response?.data?.error?.message ||
      error.message ||
      "Failed to submit form. Please try again.";
    return { success: false, error: errorMessage };
  }
};

export interface SocialMediaLink {
  social?: string;
  link: string;
}

export interface FooterMenuItem {
  text: string;
  link: string;
  icon?: {
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
  highlight?: string;
}

export interface FooterMenuColumn {
  title: string;
  items: FooterMenuItem[];
}

export interface FooterData {
  logo?: {
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
  socialMediaLinks?: SocialMediaLink[];
  contactInfo?: FooterMenuColumn;
  menuColumns?: FooterMenuColumn[];
  copyrightText?: string;
  privacyLink?: string;
  termsLink?: string;
}

/** Strapi v5 flat media → shape expected by Footer (`logo.data.attributes.url`). */
function normalizeFooterLogo(logo: any): FooterData["logo"] {
  if (!logo) return undefined;
  const url =
    (typeof logo.url === "string" && logo.url) ||
    logo.data?.attributes?.url ||
    logo.attributes?.url;
  if (!url) return undefined;
  const alternativeText =
    logo.alternativeText ??
    logo.data?.attributes?.alternativeText ??
    logo.attributes?.alternativeText ??
    "";
  return {
    data: {
      attributes: {
        url,
        alternativeText,
      },
    },
  };
}

function normalizeFooterItemIcon(icon: any): FooterMenuItem["icon"] {
  if (!icon) return undefined;
  const url =
    (typeof icon.url === "string" && icon.url) ||
    icon.data?.attributes?.url ||
    icon.attributes?.url;
  if (!url) return undefined;
  return {
    data: {
      attributes: {
        url,
        alternativeText:
          icon.alternativeText ??
          icon.data?.attributes?.alternativeText ??
          "",
      },
    },
  };
}

function normalizeFooterMenuColumn(col: any): FooterMenuColumn {
  if (!col) return { title: "", items: [] };
  const items = Array.isArray(col.items) ? col.items : [];
  return {
    title: (col.title ?? "").toString(),
    items: items.map((item: any) => ({
      text: (item.text ?? "").toString(),
      link: (item.link ?? "#").toString(),
      ...(item.highlight != null && item.highlight !== ""
        ? { highlight: String(item.highlight) }
        : {}),
      icon: normalizeFooterItemIcon(item.icon),
    })),
  };
}

/** Map Strapi v5 (flat) / v4 (nested) footer payload to FooterData. */
function normalizeFooterFromStrapi(raw: any): FooterData | null {
  if (!raw) return null;
  return {
    logo: normalizeFooterLogo(raw.logo),
    socialMediaLinks: Array.isArray(raw.socialMediaLinks)
      ? raw.socialMediaLinks.map((s: any) => ({
          social: s.social,
          link: (s.link ?? "").toString(),
        }))
      : [],
    ...(raw.contactInfo && {
      contactInfo: normalizeFooterMenuColumn(raw.contactInfo),
    }),
    menuColumns: Array.isArray(raw.menuColumns)
      ? raw.menuColumns.map(normalizeFooterMenuColumn)
      : [],
    copyrightText:
      raw.copyrightText != null ? String(raw.copyrightText) : undefined,
    privacyLink:
      raw.privacyLink != null ? String(raw.privacyLink) : undefined,
    termsLink: raw.termsLink != null ? String(raw.termsLink) : undefined,
  };
}

const _fetchFooter = async (): Promise<FooterData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    // Strapi 5: `populate=*` omits nested component items for footer menus/contact list.
    // Explicitly populate `items` for both columns so labels/links render.
    const populateStrapi5 =
      "populate[logo][fields][0]=url" +
      "&populate[logo][fields][1]=alternativeText" +
      "&populate[socialMediaLinks]=*" +
      "&populate[contactInfo][populate][items][populate]=*" +
      "&populate[menuColumns][populate][items][populate]=*";

    let response;
    try {
      response = await axios.get(
        `${STRAPI_URL}/api/footer?${populateStrapi5}`
      );
    } catch (err: any) {
      if (axios.isAxiosError(err) && err.response?.status === 400) {
        console.warn(
          "[footer] Primary populate failed; retrying with populate=* (items may be missing)."
        );
        response = await axios.get(`${STRAPI_URL}/api/footer?populate=*`);
      } else {
        throw err;
      }
    }

    const raw =
      response?.data?.data?.attributes ?? response?.data?.data ?? null;
    if (!raw) return null;
    return normalizeFooterFromStrapi(raw);
  } catch (error: any) {
    console.error("Error fetching footer data:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
    }
    return null;
  }
};

const fetchFooterCached = unstable_cache(
  async () => _fetchFooter(),
  ["footer"],
  {
    tags: ["footer"],
    revalidate: false, // Production: on-demand revalidate via tag `footer` (Strapi → /api/revalidate)
  }
);

/** Fresh Strapi data in dev; cached in production unless tag `footer` is revalidated. */
export const fetchFooter = async (): Promise<FooterData | null> => {
  // In local/dev, always bypass unstable_cache to avoid stale CMS content.
  const bypassDevCache = process.env.NODE_ENV !== "production";
  if (bypassDevCache) {
    return _fetchFooter();
  }
  return fetchFooterCached();
};

export const subscribeNewsletter = async (
  email: string
): Promise<{ success: boolean; error?: string }> => {
  try {
    const response = await axios.post(
      `${STRAPI_URL}/api/newsletter-subscriptions`,
      {
        data: {
          email: email,
          subscribedAt: new Date().toISOString(),
        },
      }
    );

    if (response.data?.data) {
      return { success: true };
    }
    return { success: false, error: "Unexpected response format" };
  } catch (error: any) {
    console.error("Error subscribing to newsletter:", error);
    const errorMessage =
      error.response?.data?.error?.message ||
      error.message ||
      "Failed to subscribe. Please try again.";
    return { success: false, error: errorMessage };
  }
};

const _fetchContactPage = async (): Promise<ContactPageData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    const baseUrl = `${STRAPI_URL}/api/contact-page`;

    // Single API call: Hero images + SEO image + offices in one request
    const populate =
      "populate[Hero][populate][imageMobile][fields][0]=url" +
      "&populate[Hero][populate][imageMobile][fields][1]=alternativeText" +
      "&populate[Hero][populate][smallImageDesktop][fields][0]=url" +
      "&populate[Hero][populate][smallImageDesktop][fields][1]=alternativeText" +
      "&populate[Hero][populate][bigImageDesktop][fields][0]=url" +
      "&populate[Hero][populate][bigImageDesktop][fields][1]=alternativeText" +
      "&populate[SEO][populate][image][fields][0]=url" +
      "&populate[SEO][populate][image][fields][1]=alternativeText" +
      "&populate[offices]=*";

    const response = await axios.get(`${baseUrl}?${populate}`);
    let data =
      response?.data?.data?.attributes ??
      response?.data?.data ??
      response?.data;

    if (!data) return null;

    const toFullUrl = (rel: string | undefined) => {
      if (!rel) return "";
      if (rel.startsWith("http")) return rel;
      const base = STRAPI_URL || "";
      return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : rel;
    };
    const getMediaUrlFrom = (img: any): string | null => {
      if (!img) return null;
      const url = img.url ?? img.data?.url ?? img.data?.attributes?.url ?? img.attributes?.url;
      if (typeof url !== "string") return null;
      return toFullUrl(url) || null;
    };

    // Hero from component (shared.hero: title, description, imageMobile, smallImageDesktop, bigImageDesktop) or flat fields
    const hero = data.Hero ?? data.hero;
    const heroTitle = hero?.title ?? data.title ?? "Contact Us";
    const heroDescription = hero?.description ?? data.description ?? "";
    const heroImageMobileUrl =
      getMediaUrlFrom(hero?.imageMobile) ?? getMediaUrlFrom(data.heroBackgroundImage);
    const heroDesktopSmallImageUrl =
      getMediaUrlFrom(hero?.smallImageDesktop) ?? getMediaUrlFrom(data.heroBackgroundImage);
    const heroDesktopBigImageUrl =
      getMediaUrlFrom(hero?.bigImageDesktop) ?? getMediaUrlFrom(data.heroBackgroundImage);

    const offices = Array.isArray(data.offices)
      ? data.offices.map((office: any) => ({
          title: office.title || "",
          phone: office.phone || "",
          email: office.email || "",
          address: office.address || "",
          locationUrl: office.locationUrl || "#",
        }))
      : [];

    const seo = normalizeSeoFromData(data, getMediaUrlFrom);

    return {
      heroTitle,
      heroDescription,
      heroImageMobileUrl: heroImageMobileUrl ?? null,
      heroDesktopSmallImageUrl: heroDesktopSmallImageUrl ?? null,
      heroDesktopBigImageUrl: heroDesktopBigImageUrl ?? null,
      offices,
      ...(seo && { seo }),
    };
  } catch (error: any) {
    console.error("Error fetching contact page data:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
      console.error("Request URL:", error.config?.url);
    }
    return null;
  }
};

export const fetchContactPage = unstable_cache(
  async () => _fetchContactPage(),
  ["contact-page"],
  {
    tags: ["contact-page"],
    revalidate: false, // No time-based revalidation - relies on on-demand revalidation from Strapi
  }
);

/** Listing page (Property pages) — hero + SEO for /properties/[slug] */
export interface ListingPropertyPageData {
  slug: string;
  name: string;
  heroTitle: string;
  heroDescription: string;
  heroImageMobileUrl?: string | null;
  heroDesktopSmallImageUrl?: string | null;
  heroDesktopBigImageUrl?: string | null;
  seo?: SeoData;
}

const _fetchListingPropertyPageBySlug = async (
  slug: string
): Promise<ListingPropertyPageData | null> => {
  try {
    if (!STRAPI_URL) return null;
    const enc = encodeURIComponent(slug);
    const populate =
      "populate[hero][populate][imageMobile][fields][0]=url&populate[hero][populate][imageMobile][fields][1]=alternativeText" +
      "&populate[hero][populate][smallImageDesktop][fields][0]=url&populate[hero][populate][smallImageDesktop][fields][1]=alternativeText" +
      "&populate[hero][populate][bigImageDesktop][fields][0]=url&populate[hero][populate][bigImageDesktop][fields][1]=alternativeText" +
      "&populate[seo][populate][image][fields][0]=url&populate[seo][populate][image][fields][1]=alternativeText";
    const url = `${STRAPI_URL}/api/${LISTING_PROPERTY_PAGES_API}?filters[slug][$eq]=${enc}&publicationState=live&${populate}`;
    const response = await safeAxiosGet(url);
    const raw = response?.data?.data;
    const rows = Array.isArray(raw) ? raw : raw != null ? [raw] : [];
    if (rows.length === 0) return null;
    const row = rows[0];
    const attrs = row.attributes ?? row;

    const toFullUrl = (rel: string | undefined) => {
      if (!rel || typeof rel !== "string") return "";
      if (rel.startsWith("http")) return rel;
      const base = STRAPI_URL || "";
      return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : rel;
    };
    const getMediaUrlFrom = (img: any): string | null => {
      if (!img) return null;
      const urlRaw =
        img.url ??
        img.data?.url ??
        img.data?.attributes?.url ??
        img.attributes?.url;
      if (typeof urlRaw !== "string") return null;
      return toFullUrl(urlRaw) || null;
    };

    const hero = attrs.hero ?? attrs.Hero;
    const name = attrs.name ?? slug;
    const heroTitle = hero?.title ?? name;
    const heroDescription = hero?.description ?? "";
    const heroImageMobileUrl =
      getMediaUrlFrom(hero?.imageMobile) ?? null;
    const heroDesktopSmallImageUrl =
      getMediaUrlFrom(hero?.smallImageDesktop) ?? null;
    const heroDesktopBigImageUrl =
      getMediaUrlFrom(hero?.bigImageDesktop) ?? null;
    const seo = normalizeSeoFromData(attrs, getMediaUrlFrom);

    return {
      slug: attrs.slug ?? slug,
      name,
      heroTitle,
      heroDescription,
      heroImageMobileUrl,
      heroDesktopSmallImageUrl,
      heroDesktopBigImageUrl,
      ...(seo && { seo }),
    };
  } catch (e) {
    console.error("[Strapi] fetchListingPropertyPageBySlug:", e);
    return null;
  }
};

const fetchListingPropertyPageBySlugCached = cache(
  async (slug: string): Promise<ListingPropertyPageData | null> => {
    if (process.env.NODE_ENV !== "production") {
      return _fetchListingPropertyPageBySlug(slug);
    }
    return unstable_cache(
      async () => _fetchListingPropertyPageBySlug(slug),
      [`listing-property-page-${slug}`],
      { tags: ["properties"], revalidate: false }
    )();
  }
);

export async function fetchListingPropertyPageBySlug(slug: string) {
  // Request-scoped dedupe: generateMetadata + page share one call per slug.
  // Production: cross-request ISR via `properties` tag (Strapi → /api/revalidate).
  return fetchListingPropertyPageBySlugCached(slug);
}

/** Published listing page slugs (for sitemap / validation). */
const _fetchPublishedListingPropertyPageSlugs = async (): Promise<string[]> => {
  try {
    if (!STRAPI_URL) return [];
    const url = `${STRAPI_URL}/api/${LISTING_PROPERTY_PAGES_API}?publicationState=live&pagination[limit]=100&fields[0]=slug`;
    const response = await safeAxiosGet(url);
    const raw = response?.data?.data;
    const rows = Array.isArray(raw) ? raw : raw != null ? [raw] : [];
    if (rows.length === 0) return [];
    return rows
      .map((row: any) => {
        const a = row.attributes ?? row;
        return typeof a.slug === "string" ? a.slug : row.slug;
      })
      .filter(Boolean);
  } catch {
    return [];
  }
};

const fetchPublishedListingPropertyPageSlugsCached = cache(
  async (): Promise<string[]> => {
    if (process.env.NODE_ENV !== "production") {
      return _fetchPublishedListingPropertyPageSlugs();
    }
    return unstable_cache(
      async () => _fetchPublishedListingPropertyPageSlugs(),
      ["published-listing-property-page-slugs"],
      { tags: ["properties"], revalidate: false }
    )();
  }
);

export async function fetchPublishedListingPropertyPageSlugs(): Promise<
  string[]
> {
  // Request-scoped dedupe for metadata + page execution path.
  return fetchPublishedListingPropertyPageSlugsCached();
}

export interface StatisticItem {
  title: string;
  value: number;
  extraClass?: string;
}

export interface ServiceItem {
  image?: {
    url?: string;
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
  title: string;
  description: string;
  boxColor?: string;
}

export interface ProjectItem {
  image?: {
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
  title: string;
  description: string;
}

export interface ClientReview {
  quote: string;
  name: string;
  profession: string;
  address: string;
}

export interface PortfolioImage {
  image?: {
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
}

export interface DesignoPageData {
  heroTitle?: string;
  heroDescription?: string;
  /** From Hero.logo or flat logo (designo has logo, not title). */
  heroLogoUrl?: string | null;
  heroImageMobileUrl?: string | null;
  heroDesktopSmallImageUrl?: string | null;
  heroDesktopBigImageUrl?: string | null;
  seo?: SeoData;
  statistics?: StatisticItem[];
  servicesTitle?: string;
  /** Hero image for the services section (Strapi `servicesImage`). */
  servicesImageUrl?: string | null;
  services?: ServiceItem[];
  projectsTitle?: string;
  projects?: ProjectItem[];
  reviewsTitle?: string;
  reviews?: ClientReview[];
  portfolioTitle?: string;
  portfolioImages?: PortfolioImage[];
  contactBackgroundImage?: {
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
  contactTitle?: string;
  contactHotline?: string;
  contactEmail?: string;
  contactAddress?: string;
}

const _fetchDesignoPage = async (): Promise<DesignoPageData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    const baseUrl = `${STRAPI_URL}/api/designo-page`;

    // Single API call: all relations and media in one request
    // Schema notes (Strapi 5): `services` has no `image` (400 if nested populate[image]);
    // `portfolioImages` needs `populate[portfolioImages][populate]=*` (not image-only + *=* at root);
    // `projects` may be absent from the single type — omit populate to avoid 400.
    const populateBase =
      "populate[Hero][populate][logo][fields][0]=url" +
      "&populate[Hero][populate][logo][fields][1]=alternativeText" +
      "&populate[Hero][populate][imageMobile][fields][0]=url" +
      "&populate[Hero][populate][imageMobile][fields][1]=alternativeText" +
      "&populate[Hero][populate][smallImageDesktop][fields][0]=url" +
      "&populate[Hero][populate][smallImageDesktop][fields][1]=alternativeText" +
      "&populate[Hero][populate][bigImageDesktop][fields][0]=url" +
      "&populate[Hero][populate][bigImageDesktop][fields][1]=alternativeText" +
      "&populate[SEO][populate][image][fields][0]=url" +
      "&populate[SEO][populate][image][fields][1]=alternativeText" +
      "&populate[services]=*" +
      "&populate[servicesImage][fields][0]=url" +
      "&populate[servicesImage][fields][1]=alternativeText" +
      "&populate[portfolioImages][populate]=*" +
      "&populate[contactBackgroundImage][fields][0]=url" +
      "&populate[contactBackgroundImage][fields][1]=alternativeText" +
      "&populate[statistics]=*" +
      "&populate[reviews]=true";

    const response = await axios.get(`${baseUrl}?${populateBase}`);
    let data =
      response?.data?.data?.attributes ??
      response?.data?.data ??
      response?.data;

    if (!data) return null;

    const toFullUrl = (rel: string | undefined) => {
      if (!rel) return "";
      if (rel.startsWith("http")) return rel;
      const base = STRAPI_URL || "";
      return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : rel;
    };
    const getMediaUrlFrom = (img: any): string | null => {
      if (!img) return null;
      const url = img.url ?? img.data?.url ?? img.data?.attributes?.url ?? img.attributes?.url;
      if (typeof url !== "string") return null;
      return toFullUrl(url) || null;
    };

    // Hero from component (shared.hero-designo: logo, description, imageMobile, smallImageDesktop, bigImageDesktop) or flat fields
    const hero = data.Hero ?? data.hero;
    const heroTitle = data.heroTitle ?? "";
    const heroDescription = hero?.description ?? data.heroDescription ?? "";
    const heroLogoUrl =
      getMediaUrlFrom(hero?.logo) ?? getMediaUrlFrom(data.logo);
    const heroImageMobileUrl =
      getMediaUrlFrom(hero?.imageMobile) ?? getMediaUrlFrom(data.heroBackgroundImage);
    const heroDesktopSmallImageUrl =
      getMediaUrlFrom(hero?.smallImageDesktop) ?? getMediaUrlFrom(data.heroBackgroundImage);
    const heroDesktopBigImageUrl =
      getMediaUrlFrom(hero?.bigImageDesktop) ?? getMediaUrlFrom(data.heroBackgroundImage);

    let seo = normalizeSeoFromData(data, getMediaUrlFrom);

    return {
      heroTitle,
      heroDescription,
      heroLogoUrl: heroLogoUrl ?? null,
      heroImageMobileUrl: heroImageMobileUrl ?? null,
      heroDesktopSmallImageUrl: heroDesktopSmallImageUrl ?? null,
      heroDesktopBigImageUrl: heroDesktopBigImageUrl ?? null,
      ...(seo && { seo }),
      statistics: Array.isArray(data.statistics)
        ? data.statistics.map((stat: any) => ({
            title: stat.title || "",
            value: stat.value || 0,
            extraClass: stat.extraClass || "",
          }))
        : [],
      servicesTitle: data.servicesTitle,
      servicesImageUrl: (() => {
        const u = getMediaUrlFrom(data.servicesImage);
        if (!u) return null;
        if (u.startsWith("http://") || u.startsWith("https://")) return u;
        return getStrapiImageUrl(u) || null;
      })(),
      services: (() => {
        const list = data.services ?? data.Services;
        return Array.isArray(list)
          ? list.map((service: any) => ({
              image: service.image,
              title: service.title || "",
              description: service.description || "",
              boxColor:
                (typeof service.boxColor === "string" && service.boxColor.trim()) ||
                (typeof service.box_color === "string" && service.box_color.trim()) ||
                undefined,
            }))
          : [];
      })(),
      projectsTitle: data.projectsTitle,
      projects: (() => {
        const list = data.projects ?? data.Projects;
        return Array.isArray(list)
          ? list.map((project: any) => ({
              image: project.image,
              title: project.title || "",
              description: project.description || "",
            }))
          : [];
      })(),
      reviewsTitle: data.reviewsTitle,
      reviews: (() => {
        const raw = data.reviews ?? data.Reviews;
        const list = Array.isArray(raw)
          ? raw
          : Array.isArray(raw?.data)
            ? raw.data
            : [];
        return list.map((item: any) => {
          const r = item?.attributes ?? item;
          return {
            quote:
              (r.quote ?? r.body ?? r.text ?? "").toString().trim() || "",
            name: (r.name ?? r.clientName ?? "").toString().trim() || "",
            profession:
              (r.profession ?? r.role ?? "").toString().trim() || "",
            address: (r.address ?? "").toString().trim() || "",
          };
        });
      })(),
      portfolioTitle: data.portfolioTitle,
      portfolioImages: (() => {
        const list = data.portfolioImages ?? data.PortfolioImages;
        return Array.isArray(list)
          ? list.map((item: any) => ({
              image: item.image,
            }))
          : [];
      })(),
      contactBackgroundImage: data.contactBackgroundImage,
      contactTitle: data.contactTitle,
      contactHotline: data.contactHotline,
      contactEmail: data.contactEmail,
      contactAddress: data.contactAddress,
    };
  } catch (error: any) {
    console.error("Error fetching designo page data:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
      console.error("Request URL:", error.config?.url);
    }
    return null;
  }
};

const fetchDesignoPageCached = unstable_cache(
  async () => _fetchDesignoPage(),
  ["designo-page"],
  {
    tags: ["designo-page"],
    revalidate: false, // No time-based revalidation - relies on on-demand revalidation from Strapi
  }
);

/** Fresh Strapi data in dev; cached in production (on-demand revalidate via tag). */
export const fetchDesignoPage = async (): Promise<DesignoPageData | null> => {
  if (process.env.NODE_ENV !== "production") {
    return _fetchDesignoPage();
  }
  return fetchDesignoPageCached();
};

export interface FmdServiceItem {
  image?: {
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
  title: string;
  description: string;
}

export interface VerticalStackCardItem {
  icon?: {
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
  text: string;
}

export interface FmdPageData {
  heroTitle?: string;
  heroDescription?: string;
  heroSubtitle?: string;
  heroImageMobileUrl?: string | null;
  heroDesktopSmallImageUrl?: string | null;
  heroDesktopBigImageUrl?: string | null;
  seo?: SeoData;
  statistics?: StatisticItem[];
  servicesTitle?: string;
  services?: FmdServiceItem[];
  additionalServicesTitle?: string;
  /** Additional services from Strapi (component vertical-stack-card-item: icon + text) */
  additionalServices?: VerticalStackCardItem[];
  contactBackgroundImage?: {
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
  contactTitle?: string;
  contactHotline?: string;
  contactEmail?: string;
  contactAddress?: string;
}

export interface BespokeProjectItem {
  property?: PropertyData;
  title?: string;
  address?: string;
  tagline?: string;
  image?: { url?: string; data?: any } | any;
  images?: Array<{
    url?: string;
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  } | any>;
}

export interface BespokePageData {
  heroTitle?: string;
  heroDescription?: string;
  heroImageMobileUrl?: string | null;
  heroDesktopSmallImageUrl?: string | null;
  heroDesktopBigImageUrl?: string | null;
  heroImageMobile?: {
    url?: string;
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  } | any;
  heroDesktopSmallImage?: {
    url?: string;
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  } | any;
  heroDesktopBigImage?: {
    url?: string;
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  } | any;
  projects?: BespokeProjectItem[];
  seo?: SeoData;
}

export interface NewsItem {
  type: "Article" | "Blog";
  title: string;
  image?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  /** Fully resolved image URL (including Strapi base URL) for convenience */
  imageUrl?: string | null;
  date: string;
  link: string;
}

export interface NewsPageData {
  heroTitle?: string;
  heroDescription?: string;
  heroImageMobileUrl?: string | null;
  heroDesktopSmallImageUrl?: string | null;
  heroDesktopBigImageUrl?: string | null;
  seo?: SeoData;
  newsItems?: NewsItem[];
}

const _fetchFmdPage = async (): Promise<FmdPageData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    const baseUrl = `${STRAPI_URL}/api/fmd-page`;

    // Single API call: all relations and media in one request
    const populate =
      "populate[Hero][populate][imageMobile][fields][0]=url" +
      "&populate[Hero][populate][imageMobile][fields][1]=alternativeText" +
      "&populate[Hero][populate][smallImageDesktop][fields][0]=url" +
      "&populate[Hero][populate][smallImageDesktop][fields][1]=alternativeText" +
      "&populate[Hero][populate][bigImageDesktop][fields][0]=url" +
      "&populate[Hero][populate][bigImageDesktop][fields][1]=alternativeText" +
      "&populate[SEO][populate][image][fields][0]=url" +
      "&populate[SEO][populate][image][fields][1]=alternativeText" +
      "&populate[services][populate][image][fields][0]=url" +
      "&populate[services][populate][image][fields][1]=alternativeText" +
      "&populate[additionalServices][populate][icon][fields][0]=url" +
      "&populate[additionalServices][populate][icon][fields][1]=alternativeText" +
      "&populate[contactBackgroundImage][fields][0]=url" +
      "&populate[contactBackgroundImage][fields][1]=alternativeText" +
      "&populate[statistics]=*";

    const response = await axios.get(`${baseUrl}?${populate}`);
    let data =
      response?.data?.data?.attributes ??
      response?.data?.data ??
      response?.data;

    if (!data) return null;

    const toFullUrl = (rel: string | undefined) => {
      if (!rel) return "";
      if (rel.startsWith("http")) return rel;
      const base = STRAPI_URL || "";
      return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : rel;
    };
    const getMediaUrlFrom = (img: any): string | null => {
      if (!img) return null;
      const url = img.url ?? img.data?.url ?? img.data?.attributes?.url ?? img.attributes?.url;
      if (typeof url !== "string") return null;
      return toFullUrl(url) || null;
    };

    // Hero from component (shared.hero: title, description, imageMobile, smallImageDesktop, bigImageDesktop) or flat fields
    const hero = data.Hero ?? data.hero;
    const heroTitle =
      hero?.title ??
      ([data.heroTitleLine1, data.heroTitleLine2].filter(Boolean).join(" ") ||
        "Facility Maintenance");
    const heroDescription = hero?.description ?? data.heroDescription ?? "";
    const heroSubtitle = data.heroSubtitle ?? "";
    const heroImageMobileUrl =
      getMediaUrlFrom(hero?.imageMobile) ?? getMediaUrlFrom(data.heroBackgroundImage);
    const heroDesktopSmallImageUrl =
      getMediaUrlFrom(hero?.smallImageDesktop) ?? getMediaUrlFrom(data.heroBackgroundImage);
    const heroDesktopBigImageUrl =
      getMediaUrlFrom(hero?.bigImageDesktop) ?? getMediaUrlFrom(data.heroBackgroundImage);

    let seo = normalizeSeoFromData(data, getMediaUrlFrom);

    return {
      heroTitle,
      heroDescription,
      heroSubtitle,
      heroImageMobileUrl: heroImageMobileUrl ?? null,
      heroDesktopSmallImageUrl: heroDesktopSmallImageUrl ?? null,
      heroDesktopBigImageUrl: heroDesktopBigImageUrl ?? null,
      ...(seo && { seo }),
      statistics: Array.isArray(data.statistics)
        ? data.statistics.map((stat: any) => ({
            title: stat.title || "",
            value: stat.value || 0,
            extraClass: stat.extraClass || "",
          }))
        : [],
      servicesTitle: data.servicesTitle,
      services: (() => {
        const list = data.services ?? data.Services;
        return Array.isArray(list)
          ? list.map((service: any) => {
            const serviceImage = service.image
              ? {
                  data: {
                    attributes: {
                      url:
                        service.image.url ||
                        service.image.data?.attributes?.url ||
                        "",
                      alternativeText:
                        service.image.alternativeText ||
                        service.image.data?.attributes?.alternativeText,
                    },
                  },
                }
              : undefined;
            return {
              image: serviceImage,
              title: service.title || "",
              description: service.description || "",
            };
            })
          : [];
      })(),
      additionalServicesTitle: data.additionalServicesTitle,
      additionalServices: (() => {
        const list = data.additionalServices ?? data.additional_services;
        return Array.isArray(list)
          ? list.map((item: any) => {
            const cardIcon = item.icon
              ? {
                  data: {
                    attributes: {
                      url:
                        item.icon.url ||
                        item.icon.data?.attributes?.url ||
                        "",
                      alternativeText:
                        item.icon.alternativeText ||
                        item.icon.data?.attributes?.alternativeText,
                    },
                  },
                }
              : undefined;
            return { icon: cardIcon, text: item.text || "" };
            })
          : [];
      })(),
      contactBackgroundImage: data.contactBackgroundImage
        ? {
            data: {
              attributes: {
                url:
                  data.contactBackgroundImage.url ||
                  data.contactBackgroundImage.data?.attributes?.url ||
                  "",
                alternativeText:
                  data.contactBackgroundImage.alternativeText ||
                  data.contactBackgroundImage.data?.attributes?.alternativeText,
              },
            },
          }
        : undefined,
      contactTitle: data.contactTitle,
      contactHotline: data.contactHotline,
      contactEmail: data.contactEmail,
      contactAddress: data.contactAddress,
    };
  } catch (error: any) {
    console.error("Error fetching FMD page data:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
      console.error("Request URL:", error.config?.url);
    }
    return null;
  }
};

export const fetchFmdPage = unstable_cache(
  async () => _fetchFmdPage(),
  ["fmd-page"],
  {
    tags: ["fmd-page"],
    revalidate: false, // No time-based revalidation - relies on on-demand revalidation from Strapi
  }
);

const _fetchBespokePage = async (): Promise<BespokePageData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    const baseUrl = `${STRAPI_URL}/api/bespoke-page`;

    // 1) Fetch page with Hero and projects. Strapi v5: use populate=* then optionally populate Hero media.
    let mainRes = await axios.get(`${baseUrl}?populate=*`);
    let data = mainRes?.data?.data?.attributes ?? mainRes?.data?.data ?? mainRes?.data;

    // If Hero exists but its media might not be populated, try explicit Hero populate (Strapi v5)
    const heroFromFirst = data?.Hero ?? data?.hero;
    if (heroFromFirst && !heroFromFirst.imageMobile && !heroFromFirst.smallImageDesktop && !heroFromFirst.bigImageDesktop) {
      try {
        const heroRes = await axios.get(`${baseUrl}?populate[Hero][populate]=*`);
        const heroData = heroRes?.data?.data?.attributes ?? heroRes?.data?.data ?? heroRes?.data;
        const heroFromApi = heroData?.Hero ?? heroData?.hero;
        if (heroFromApi) data = { ...data, Hero: heroFromApi };
      } catch (_) {}
    }

    const seoFromFirstBespoke = data?.SEO ?? data?.seo;
    if (seoFromFirstBespoke && !seoFromFirstBespoke.image?.url && !seoFromFirstBespoke.image?.data) {
      try {
        const seoRes = await axios.get(`${baseUrl}?populate[SEO][populate]=*`);
        const seoData = seoRes?.data?.data?.attributes ?? seoRes?.data?.data ?? seoRes?.data;
        const seoFromApi = seoData?.SEO ?? seoData?.seo;
        if (seoFromApi) data = { ...data, SEO: seoFromApi };
      } catch (_) {}
    }

    // 2) If projects need images, fetch again with projects populated
    const initialProjects = data?.projects?.data ?? data?.projects ?? [];
    const projectsList = Array.isArray(initialProjects) ? initialProjects : [];
    const needsProjectImages = projectsList.length > 0 && !(projectsList[0]?.images ?? projectsList[0]?.image);

    if (needsProjectImages) {
      try {
        const projRes = await axios.get(`${baseUrl}?populate[projects][populate]=*`);
        const projData = projRes?.data?.data?.attributes ?? projRes?.data?.data ?? projRes?.data;
        if (projData?.projects) data = { ...data, projects: projData.projects };
      } catch (_) {}
    }

    if (!data) {
      console.error("Bespoke: No data found in API response");
      return null;
    }

    // Process projects array (Strapi v5: projects can be { data: [...] } or array)
    const projects: BespokeProjectItem[] = [];
    const rawProjects = data.projects?.data ?? data.projects ?? [];
    const projectsData = Array.isArray(rawProjects) ? rawProjects : [];
    
    if (projectsData.length > 0) {
      projectsData.forEach((project: any) => {
        // Handle different project data structures
        const projectData = project.attributes || project;
        
        // Get property data if referenced
        let propertyData: PropertyData | undefined = undefined;
        if (projectData.property) {
          const prop = projectData.property.data?.attributes || projectData.property.attributes || projectData.property;
          if (prop) {
            const normImg = (img: any) => {
              if (!img) return undefined;
              const u = img.url || img.data?.attributes?.url;
              return u ? { url: u, data: img.data || { attributes: { url: u, alternativeText: img.data?.attributes?.alternativeText } } } : undefined;
            };
            const bespokeTypes = normalizeTypesFromStrapi(prop.types);
            propertyData = {
              id: prop.id || projectData.property.id || projectData.property.data?.id,
              slug: prop.slug || "",
              title: prop.title || "",
              ...(bespokeTypes.length > 0 && { types: bespokeTypes }),
              propertyType: getPrimaryTypeSlugForUrl({
                types: bespokeTypes,
                propertyType: prop.propertyType,
              }),
              propertyStatus: prop.propertyStatus || "upcoming",
              address: prop.address || "",
              description: prop.description || "",
              location: prop.location
                ? {
                    id: prop.location.id || prop.location.data?.id,
                    name: prop.location.name || prop.location.data?.attributes?.name || "",
                    slug: prop.location.slug || prop.location.data?.attributes?.slug || "",
                    priority: prop.location.priority || prop.location.data?.attributes?.priority || 0,
                  }
                : undefined,
              cardImage: normImg(prop.cardImage?.data?.attributes || prop.cardImage?.attributes || prop.cardImage),
              cardHoverImage: normImg(prop.cardHoverImage?.data?.attributes || prop.cardHoverImage?.attributes || prop.cardHoverImage),
            };
          }
        }

        // Handle images: API returns image (singular) or images (array) - use shared normalizeImage for consistency
        const rawImages = projectData.images ?? projectData.image;
        let imagesData: any[] = [];
        if (rawImages) {
          if (Array.isArray(rawImages)) {
            imagesData = rawImages;
          } else if (rawImages.data && Array.isArray(rawImages.data)) {
            imagesData = rawImages.data;
          } else {
            imagesData = [rawImages];
          }
        }
        // Build full URLs on server so client doesn't need NEXT_PUBLIC_STRAPI_URL at build time
        const toFullUrl = (rel: string | undefined) => {
          if (!rel) return "";
          if (rel.startsWith("http")) return rel;
          const base = STRAPI_URL || "";
          return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : rel;
        };
        // API returns images as flat objects: { id, url, name, formats: { large, medium, ... }, ... }
        const images = imagesData
          .map((img: any) => {
            const raw = img?.attributes ?? img?.data?.attributes ?? img;
            const norm = normalizeImage(raw);
            if (!norm?.url) return null;
            const fullUrl = toFullUrl(norm.url);
            const formats = raw?.formats;
            const fullFormats = formats
              ? {
                  large: formats.large ? { ...formats.large, url: toFullUrl(formats.large.url) } : undefined,
                  medium: formats.medium ? { ...formats.medium, url: toFullUrl(formats.medium.url) } : undefined,
                  small: formats.small ? { ...formats.small, url: toFullUrl(formats.small.url) } : undefined,
                }
              : undefined;
            return { ...norm, url: fullUrl, formats: fullFormats ?? formats };
          })
          .filter((img) => !!img && !!img.url) as Array<{ url: string; data: any; formats?: any }>;

        projects.push({
          property: propertyData,
          title: projectData.title || propertyData?.title || "",
          address: projectData.address || propertyData?.address || "",
          tagline: projectData.tagline || "",
          image: images[0],
          images: images,
        });
      });
    }

    // Build full image URL (Strapi v4: data.attributes.url, v5: url or data.url)
    const toFullUrl = (rel: string | undefined) => {
      if (!rel) return "";
      if (rel.startsWith("http")) return rel;
      const base = STRAPI_URL || "";
      return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : rel;
    };
    const getMediaUrlFrom = (img: any): string | null => {
      if (!img) return null;
      const url = img.url ?? img.data?.url ?? img.data?.attributes?.url ?? img.attributes?.url;
      if (typeof url !== "string") return null;
      const full = toFullUrl(url);
      return full || null;
    };

    // Hero: schema has Hero (shared.hero: title, description, imageMobile, smallImageDesktop, bigImageDesktop)
    // and flat fields heroTitle, heroDescription, heroImageMobile, heroDesktopSmallImage, heroDesktopBigImage
    const hero = data.Hero ?? data.hero;
    const heroTitle = hero?.title ?? data.heroTitle ?? "";
    const heroDescription = hero?.description ?? data.heroDescription ?? "";
    const heroImageMobileUrl =
      getMediaUrlFrom(hero?.imageMobile) ?? getMediaUrlFrom(data.heroImageMobile);
    const heroDesktopSmallImageUrl =
      getMediaUrlFrom(hero?.smallImageDesktop) ?? getMediaUrlFrom(data.heroDesktopSmallImage);
    const heroDesktopBigImageUrl =
      getMediaUrlFrom(hero?.bigImageDesktop) ?? getMediaUrlFrom(data.heroDesktopBigImage);

    const seo = normalizeSeoFromData(data, getMediaUrlFrom);

    const result = {
      heroTitle,
      heroDescription,
      heroImageMobileUrl,
      heroDesktopSmallImageUrl,
      heroDesktopBigImageUrl,
      projects,
      ...(seo && { seo }),
    };

    return result;
  } catch (error: any) {
    console.error("Error fetching bespoke page data:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
      console.error("Request URL:", error.config?.url);
    }
    return null;
  }
};

export const fetchBespokePage = unstable_cache(
  async () => _fetchBespokePage(),
  ["bespoke-page"],
  {
    tags: ["bespoke-page"],
    revalidate: false,
  }
);

const _fetchNewsPage = async (): Promise<NewsPageData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    const baseUrl = `${STRAPI_URL}/api/news-page`;
    let response: any = null;
    let lastError: any = null;


      try {
        const populate =
          // Hero component images
          "populate[Hero][populate][imageMobile][fields][0]=url" +
          "&populate[Hero][populate][imageMobile][fields][1]=alternativeText" +
          "&populate[Hero][populate][smallImageDesktop][fields][0]=url" +
          "&populate[Hero][populate][smallImageDesktop][fields][1]=alternativeText" +
          "&populate[Hero][populate][bigImageDesktop][fields][0]=url" +
          "&populate[Hero][populate][bigImageDesktop][fields][1]=alternativeText" +

          // SEO image
          "&populate[SEO][populate][image][fields][0]=url" +
          "&populate[SEO][populate][image][fields][1]=alternativeText" +
          // News item images
          "&populate[newsItems][populate][image][fields][0]=url" +
          "&populate[newsItems][populate][image][fields][1]=alternativeText";

        response = await axios.get(`${baseUrl}?${populate}`);
      
        // Try 3: fallback without populate (data may be partial, UI will use local fallbacks)
      } catch (err: any) {
          lastError = err;
          throw err;
    }

    const raw = response?.data?.data;
    const data = raw?.attributes ?? raw ?? response?.data;

    if (!data) {
      if (lastError) {
        console.error("Error fetching News page data:", lastError);
      }
      return null;
    }

    const toFullUrl = (rel: string | undefined) => {
      if (!rel) return "";
      if (rel.startsWith("http")) return rel;
      const base = STRAPI_URL || "";
      return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : rel;
    };
    const getMediaUrlFrom = (img: any): string | null => {
      if (!img) return null;
      const url = img.url ?? img.data?.url ?? img.data?.attributes?.url ?? img.attributes?.url;
      if (typeof url !== "string") return null;
      return toFullUrl(url) || null;
    };

    // Hero from component (shared.hero: title, description, imageMobile, smallImageDesktop, bigImageDesktop) or flat fields
    const hero = data.Hero ?? data.hero;
    const heroTitle = hero?.title ?? data.heroTitle ?? "News & Events";
    const heroDescription = hero?.description ?? data.heroDescription ?? "";
    const heroImageMobileUrl =
      getMediaUrlFrom(hero?.imageMobile) ?? getMediaUrlFrom(data.heroBackgroundImage);
    const heroDesktopSmallImageUrl =
      getMediaUrlFrom(hero?.smallImageDesktop) ?? getMediaUrlFrom(data.heroBackgroundImage);
    const heroDesktopBigImageUrl =
      getMediaUrlFrom(hero?.bigImageDesktop) ?? getMediaUrlFrom(data.heroBackgroundImage);

    const newsItems = Array.isArray(data.newsItems)
      ? data.newsItems.map((item: any) => {
          const itemImage = item.image
            ? {
                url: item.image.url || item.image.data?.attributes?.url || "",
                data: item.image.data || {
                  attributes: {
                    url:
                      item.image.url ||
                      item.image.data?.attributes?.url ||
                      "",
                    alternativeText:
                      item.image.alternativeText ||
                      item.image.data?.attributes?.alternativeText,
                  },
                },
              }
            : undefined;

          // Use the most robust media resolver so this works
          // regardless of Strapi media nesting/array shape.
          const imageUrl = getMediaUrlFromAny(item.image);

          return {
            type: item.type || "Blog",
            title: item.title || "",
            image: itemImage,
            imageUrl,
            date: item.date || "",
            link: item.link || "#",
          };
        })
      : [];

    const seo = normalizeSeoFromData(data, getMediaUrlFrom);

    return {
      heroTitle,
      heroDescription,
      heroImageMobileUrl: heroImageMobileUrl ?? null,
      heroDesktopSmallImageUrl: heroDesktopSmallImageUrl ?? null,
      heroDesktopBigImageUrl: heroDesktopBigImageUrl ?? null,
      ...(seo && { seo }),
      newsItems,
    };
  } catch (error: any) {
    console.error("Error fetching News page data:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
      console.error("Request URL:", error.config?.url);
    }
    return null;
  }
};

export const fetchNewsPage = unstable_cache(
  async () => _fetchNewsPage(),
  ["news-page"],
  {
    tags: ["news-page"],
    revalidate: false, // No time-based revalidation - relies on on-demand revalidation from Strapi
  }
);

export interface NarrativeImage {
  image?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  alt?: string;
}

export interface GuidingPrincipleItem {
  icon?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  label: string;
}

export interface LeaderItem {
  image?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  quote: string;
  name: string;
  position: string;
}

export interface AwardItem {
  logo?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  name: string;
  sector: string;
  image?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
}

export interface StoryPageData {
  heroTitle?: string;
  heroBackgroundImageDesktop?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  heroBackgroundImageMobile?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  heroDescription?: string;
  heroYears?: string;
  heroSubtitle?: string;
  /** Hero tagline (The Brand); may contain HTML e.g. `<br />` */
  tagline?: string;
  narrative?: {
    title?: string;
    blocks?: {
      image?:
        | {
            url?: string;
            data?: {
              attributes: {
                url: string;
                alternativeText?: string;
              };
            };
          }
        | any;
      headline?: string;
      text?: string;
    }[];
  };
  navanaWay?: {
    title?: string;
    description?: string;
    visionTitle?: string;
    visionSubtitle?: string;
    visionDescription?: string;
    missionTitle?: string;
    missionSubtitle?: string;
    missionDescription?: string;
    topImage?:
      | {
          url?: string;
          data?: {
            attributes: {
              url: string;
              alternativeText?: string;
            };
          };
        }
      | any;
    bottomImage?:
      | {
          url?: string;
          data?: {
            attributes: {
              url: string;
              alternativeText?: string;
            };
          };
        }
      | any;
  };
  principlesTitle?: string;
  principlesSubtitle?: string;
  principles?: GuidingPrincipleItem[];
  principlesFooterText?: string;
  leadershipTitle?: string;
  leaders?: LeaderItem[];
  awardsTitle?: string;
  awards?: AwardItem[];
  logoTransformationTitle?: string;
  logoTransformationParagraph1?: string;
  logoTransformationParagraph2?: string;
  logoTransformationParagraph3?: string;
  logoTransformationVideo?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  seo?: SeoData;
}

const _fetchStoryPage = async (): Promise<StoryPageData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    const baseUrl = `${STRAPI_URL}/api/story-page`;

    // Single API call: explicit populate (same pattern as designo/fmd)
    const basePopulate =
      "populate[heroBackgroundImageDesktop][fields][0]=url" +
      "&populate[heroBackgroundImageDesktop][fields][1]=alternativeText" +
      "&populate[heroBackgroundImageMobile][fields][0]=url" +
      "&populate[heroBackgroundImageMobile][fields][1]=alternativeText" +
      "&populate[SEO][populate][image][fields][0]=url" +
      "&populate[SEO][populate][image][fields][1]=alternativeText" +
      "&populate[principles][populate][icon][fields][0]=url" +
      "&populate[principles][populate][icon][fields][1]=alternativeText" +
      "&populate[leaders][populate][image][fields][0]=url" +
      "&populate[leaders][populate][image][fields][1]=alternativeText" +
      "&populate[awards][populate][logo][fields][0]=url" +
      "&populate[awards][populate][logo][fields][1]=alternativeText" +
      "&populate[awards][populate][image][fields][0]=url" +
      "&populate[awards][populate][image][fields][1]=alternativeText" +
      "&populate[logoTransformationVideo][fields][0]=url";
    const navanaWayPopulateNew =
      "&populate[navanaWay][populate][topImage][fields][0]=url" +
      "&populate[navanaWay][populate][topImage][fields][1]=alternativeText" +
      "&populate[navanaWay][populate][bottomImage][fields][0]=url" +
      "&populate[navanaWay][populate][bottomImage][fields][1]=alternativeText";
    const narrativePopulateNew =
      "&populate[narrative][populate][blocks][populate][image][fields][0]=url" +
      "&populate[narrative][populate][blocks][populate][image][fields][1]=alternativeText";
    const storyNarrativePopulateNew =
      "&populate[storyNarrative][populate][images][populate][image][fields][0]=url" +
      "&populate[storyNarrative][populate][images][populate][image][fields][1]=alternativeText" +
      "&populate[storyNarrative][populate][broadenImage][fields][0]=url" +
      "&populate[storyNarrative][populate][broadenImage][fields][1]=alternativeText";
    const navanaWayPopulateLegacy =
      "&populate[navanaWaySection][populate][topImage][fields][0]=url" +
      "&populate[navanaWaySection][populate][topImage][fields][1]=alternativeText" +
      "&populate[navanaWaySection][populate][bottomImage][fields][0]=url" +
      "&populate[navanaWaySection][populate][bottomImage][fields][1]=alternativeText";
    const storyNarrativePopulateLegacy =
      "&populate[narrativeSection][populate][images][populate][image][fields][0]=url" +
      "&populate[narrativeSection][populate][images][populate][image][fields][1]=alternativeText" +
      "&populate[narrativeSection][populate][broadenImage][fields][0]=url" +
      "&populate[narrativeSection][populate][broadenImage][fields][1]=alternativeText";

    const populateVariants = [
      `${basePopulate}${navanaWayPopulateNew}${narrativePopulateNew}`,
      `${basePopulate}${navanaWayPopulateNew}${storyNarrativePopulateNew}`,
      `${basePopulate}${navanaWayPopulateLegacy}${storyNarrativePopulateLegacy}`,
      `${basePopulate}${narrativePopulateNew}`,
      `${basePopulate}${navanaWayPopulateNew}`,
      `${basePopulate}${storyNarrativePopulateNew}`,
      `${basePopulate}${navanaWayPopulateLegacy}`,
      `${basePopulate}${storyNarrativePopulateLegacy}`,
      basePopulate,
    ];

    let response: any = null;
    let lastError: any = null;
    for (const populate of populateVariants) {
      try {
        response = await axios.get(`${baseUrl}?${populate}`);
        lastError = null;
        break;
      } catch (err: any) {
        lastError = err;
        const invalidPopulateKey = err?.response?.data?.error?.details?.key;
        if (
          err?.response?.status === 400 &&
          (invalidPopulateKey === "navanaWay" ||
            invalidPopulateKey === "navanaWaySection" ||
            invalidPopulateKey === "narrative" ||
            invalidPopulateKey === "storyNarrative" ||
            invalidPopulateKey === "narrativeSection")
        ) {
          continue;
        }
        throw err;
      }
    }
    if (!response && lastError) throw lastError;
    let data =
      response?.data?.data?.attributes ??
      response?.data?.data ??
      response?.data;

    if (!data) return null;

    const toFullUrl = (rel: string | undefined) => {
      if (!rel) return "";
      if (rel.startsWith("http")) return rel;
      const base = STRAPI_URL || "";
      return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : rel;
    };
    const getMediaUrlFrom = (img: any): string | null => {
      if (!img) return null;
      const url = img.url ?? img.data?.url ?? img.data?.attributes?.url ?? img.attributes?.url;
      if (typeof url !== "string") return null;
      return toFullUrl(url) || null;
    };

    const normalizeImage = (img: any) => {
      if (!img) return undefined;
      const absUrl = getMediaUrlFrom(img) ?? "";
      const alt = img.alternativeText ?? img.data?.attributes?.alternativeText ?? img.attributes?.alternativeText ?? "";
      return {
        url: absUrl,
        data: {
          attributes: {
            url: absUrl,
            alternativeText: alt,
          },
        },
      };
    };

    let seo = normalizeSeoFromData(data, getMediaUrlFrom);
    let storyNarrativeRaw =
      data?.narrative?.attributes ??
      data?.narrative?.data?.attributes ??
      data?.narrative?.data ??
      data?.narrative ??
      data?.storyNarrative?.attributes ??
      data?.storyNarrative?.data?.attributes ??
      data?.storyNarrative?.data ??
      data?.storyNarrative ??
      data?.narrativeSection?.attributes ??
      data?.narrativeSection?.data?.attributes ??
      data?.narrativeSection?.data ??
      data?.narrativeSection;
    let navanaWayRaw =
      data?.navanaWay?.attributes ??
      data?.navanaWay?.data?.attributes ??
      data?.navanaWay?.data ??
      data?.navanaWay ??
      data?.navanaWaySection?.attributes ??
      data?.navanaWaySection?.data?.attributes ??
      data?.navanaWaySection?.data ??
      data?.navanaWaySection;

    // Some Strapi environments reject explicit populate key for this component.
    // If block is missing, try a minimal fallback query to retrieve component data.
    if (!navanaWayRaw || !storyNarrativeRaw) {
      try {
        const fallbackResp = await axios.get(`${baseUrl}?populate=*`);
        const fallbackData =
          fallbackResp?.data?.data?.attributes ??
          fallbackResp?.data?.data ??
          fallbackResp?.data;
        if (fallbackData) {
          data = fallbackData;
          seo = normalizeSeoFromData(data, getMediaUrlFrom);
          storyNarrativeRaw =
            fallbackData?.narrative?.attributes ??
            fallbackData?.narrative?.data?.attributes ??
            fallbackData?.narrative?.data ??
            fallbackData?.narrative ??
            fallbackData?.storyNarrative?.attributes ??
            fallbackData?.storyNarrative?.data?.attributes ??
            fallbackData?.storyNarrative?.data ??
            fallbackData?.storyNarrative ??
            fallbackData?.narrativeSection?.attributes ??
            fallbackData?.narrativeSection?.data?.attributes ??
            fallbackData?.narrativeSection?.data ??
            fallbackData?.narrativeSection;
          navanaWayRaw =
            fallbackData?.navanaWay?.attributes ??
            fallbackData?.navanaWay?.data?.attributes ??
            fallbackData?.navanaWay?.data ??
            fallbackData?.navanaWay ??
            fallbackData?.navanaWaySection?.attributes ??
            fallbackData?.navanaWaySection?.data?.attributes ??
            fallbackData?.navanaWaySection?.data ??
            fallbackData?.navanaWaySection;
        }
      } catch {
        // Keep primary response data if fallback request fails.
      }
    }

    const toArray = (arr: any): any[] =>
      Array.isArray(arr) ? arr : Array.isArray(arr?.data) ? arr.data : [];
    const narrativeBlocks = toArray(storyNarrativeRaw?.blocks).map(
      (item: any) => {
        const attrs = item?.attributes ?? item;
        return {
          image: normalizeImage(attrs.image ?? item.image),
          headline: (attrs.headline ?? item.headline) || "",
          text: (attrs.text ?? item.text) || "",
        };
      }
    );
    const narrativeTitleStr =
      typeof storyNarrativeRaw?.title === "string"
        ? storyNarrativeRaw.title.trim()
        : "";
    const hasNarrativeBlocks = narrativeBlocks.some(
      (b) => b.image?.url || b.headline || b.text,
    );
    const narrative =
      storyNarrativeRaw && (narrativeTitleStr.length > 0 || hasNarrativeBlocks)
        ? {
            title: storyNarrativeRaw?.title,
            blocks: narrativeBlocks,
          }
        : undefined;

      return {
        ...(seo && { seo }),
        heroTitle: data.heroTitle,
        heroBackgroundImageDesktop: normalizeImage(
          data.heroBackgroundImageDesktop
        ),
        heroBackgroundImageMobile: normalizeImage(
          data.heroBackgroundImageMobile
        ),
        heroDescription: data.heroDescription,
        heroYears: data.heroYears,
        heroSubtitle: data.heroSubtitle,
        tagline: data.tagline,
        narrative,
        navanaWay: navanaWayRaw
          ? {
              title: navanaWayRaw.title,
              description: navanaWayRaw.description,
              visionTitle: navanaWayRaw.visionTitle,
              visionSubtitle: navanaWayRaw.visionSubtitle,
              visionDescription: navanaWayRaw.visionDescription,
              missionTitle: navanaWayRaw.missionTitle,
              missionSubtitle: navanaWayRaw.missionSubtitle,
              missionDescription: navanaWayRaw.missionDescription,
              topImage: normalizeImage(navanaWayRaw.topImage),
              bottomImage: normalizeImage(navanaWayRaw.bottomImage),
            }
          : undefined,
        principlesTitle: data.principlesTitle,
        principlesSubtitle: data.principlesSubtitle,
        principles: toArray(data.principles).map((item: any) => {
          const attrs = item?.attributes ?? item;
          return {
            icon: normalizeImage(attrs.icon ?? item.icon),
            label: (attrs.label ?? item.label) || "",
          };
        }),
        principlesFooterText: data.principlesFooterText,
        leadershipTitle: data.leadershipTitle,
        leaders: toArray(data.leaders).map((item: any) => {
          const attrs = item?.attributes ?? item;
          return {
            image: normalizeImage(attrs.image ?? item.image),
            quote: (attrs.quote ?? item.quote) || "",
            name: (attrs.name ?? item.name) || "",
            position: (attrs.position ?? item.position) || "",
          };
        }),
        awardsTitle: data.awardsTitle,
        awards: toArray(data.awards).map((item: any) => {
          const attrs = item?.attributes ?? item;
          return {
            logo: normalizeImage(attrs.logo ?? item.logo),
            name: (attrs.name ?? item.name) || "",
            sector: (attrs.sector ?? item.sector) || "",
            image: normalizeImage(attrs.image ?? item.image),
          };
        }),
        logoTransformationTitle: data.logoTransformationTitle,
        logoTransformationParagraph1: data.logoTransformationParagraph1,
        logoTransformationParagraph2: data.logoTransformationParagraph2,
        logoTransformationParagraph3: data.logoTransformationParagraph3,
        logoTransformationVideo: normalizeImage(data.logoTransformationVideo),
      };
  } catch (error: any) {
    console.error("Error fetching Story page data:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
      console.error("Request URL:", error.config?.url);
    }
    return null;
  }
};

const fetchStoryPageCached = unstable_cache(
  async () => _fetchStoryPage(),
  ["story-page"],
  {
    tags: ["story-page"],
    revalidate: false, // No time-based revalidation - relies on on-demand revalidation from Strapi
  }
);

export const fetchStoryPage = async () => {
  // In local/dev, bypass cache so Strapi edits are reflected immediately.
  if (process.env.NODE_ENV !== "production") {
    return _fetchStoryPage();
  }
  return fetchStoryPageCached();
};

export interface PropertyFeatureItem {
  icon?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  label: string;
  value: string;
}

export interface AmenitySliderItem {
  image?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  title: string;
}

export interface AmenityGridItem {
  icon?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  value: string;
}

export interface PropertyPortfolioImage {
  image?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
}

export interface LocationData {
  id: number;
  name: string;
  slug: string;
  priority?: number;
  mapImage?:
    | {
        url?: string;
        data?: { attributes: { url: string; alternativeText?: string } };
      }
    | any;
}

export interface PropertyData {
  id: number;
  slug: string;
  title: string;
  /** Listing page slugs from Strapi relation `types` (preferred for URLs and filters). */
  types?: PropertyTypePageRef[];
  /** Primary slug for links; legacy enum or first `types` slug when populated. */
  propertyType?: string;
  propertyStatus: "upcoming" | "ongoing" | "completed";
  /**
   * Legacy ordering fields used by Strapi `sort[0]=priority` (status=all)
   * and app fallbacks when override fields are missing.
   */
  priority?: number;
  statusPriority?: number;
  /** Tie-breaker used when sorting list results locally. */
  createdAt?: string;
  displayOrder?: number;
  address: string;
  location?: LocationData;
  unitSize?: number;
  description: string;
  brochure?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  heroBackgroundImageDesktop?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  heroBackgroundImageMobile?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  features?: PropertyFeatureItem[];
  showcaseVideo?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  amenitiesTitle?: string;
  amenitiesDescription?: string;
  amenitiesSlider?: AmenitySliderItem[];
  amenitiesGrid?: AmenityGridItem[];
  locationPinLat?: number;
  locationPinLng?: number;
  portfolioImages?: PropertyPortfolioImage[];
  /**
   * Override ordering fields for mixed-type properties:
   * - `residential*` used on `/properties/residential` pages
   * - `commercial*` used on `/properties/commercial` pages
   *
   * Effective logic (app-side): overrideValue if provided (non-zero) else legacy priority/statusPriority.
   */
  residentialPriority?: number;
  commercialPriority?: number;
  residentialStatusPriority?: number;
  commercialStatusPriority?: number;
  cardImage?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  cardHoverImage?:
    | {
        url?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  /** Related projects (from Strapi projects relation), normalized for ProjectsWithin component */
  projectsWithin?: PropertyProjectItem[];
}

/** Normalized project item for ProjectsWithin section (maps from Strapi projects relation) */
export interface PropertyProjectItem {
  image: string;
  hoverImage?: string;
  title: string;
  status: string;
  statusIcon?: string;
  slug?: string;
  propertyType?: string;
}

// Helper function to normalize featured properties (used by both fetchHeader and fetchHomePage)
// Strapi featured-property-item has only "property" relation; no component-level image or displayOrder
const normalizeFeaturedProperties = (items: any[]): FeaturedPropertyItem[] => {
  const list = Array.isArray(items) ? items : items != null ? [items] : [];

  return list
    .map((item: any, index: number) => {
      const entry = item?.attributes ?? item;
      const itemId = item?.id ?? entry?.id ?? index + 1;

      let propertyData = null;
      if (entry.property) {
        const prop = entry.property;
        if (prop.data?.attributes) {
          propertyData = { ...prop.data.attributes, id: prop.data.id ?? prop.data.attributes?.id };
        } else if (prop.data && typeof prop.data === "object" && (prop.data.title != null || prop.data.slug != null || prop.data.attributes?.title != null)) {
          propertyData = prop.data.attributes ?? prop.data;
          if (propertyData && prop.data?.id != null) propertyData.id = prop.data.id;
        } else if (prop.attributes) {
          propertyData = { ...prop.attributes, id: prop.id ?? prop.attributes?.id };
        } else if (prop && (prop.title != null || prop.slug != null)) {
          propertyData = prop;
        }
      }

      const typesNorm = normalizeTypesFromStrapi(propertyData.types);
      const property = propertyData
        ? {
            id: propertyData.id ?? entry.property?.id ?? entry.property?.data?.id,
            slug: propertyData.slug || "",
            title: propertyData.title || "",
            types: typesNorm.length ? typesNorm : undefined,
            propertyType: getPrimaryTypeSlugForUrl({
              types: typesNorm,
              propertyType: propertyData.propertyType,
            }),
            propertyStatus: propertyData.propertyStatus || "",
            address: propertyData.address || "",
            description: propertyData.description || "",
            location: (() => {
              const loc = propertyData.location;
              if (!loc) return undefined;
              const data = loc?.data?.attributes ?? loc?.data ?? loc?.attributes ?? loc;
              if (!data || typeof data !== "object") return undefined;
              return {
                id: data.id ?? loc?.id ?? loc?.data?.id,
                name: data.name ?? "",
                slug: data.slug ?? "",
                priority: data.priority ?? 0,
              };
            })(),
            cardImage: normalizeImage(
              propertyData.cardImage?.data?.attributes ||
                propertyData.cardImage?.attributes ||
                propertyData.cardImage
            ),
            cardHoverImage: normalizeImage(
              propertyData.cardHoverImage?.data?.attributes ||
                propertyData.cardHoverImage?.attributes ||
                propertyData.cardHoverImage
            ),
          }
        : undefined;

      // Image from property.cardImage only (component has no image field)
      const imageData =
        propertyData?.cardImage?.data?.attributes ||
        propertyData?.cardImage?.attributes ||
        propertyData?.cardImage;
      const image = normalizeImage(imageData);

      return {
        id: itemId,
        property,
        image,
        displayOrder: index,
      };
    })
    .sort((a, b) => (a.displayOrder ?? 0) - (b.displayOrder ?? 0));
};

// Helper function to normalize image structure
const normalizeImage = (img: any) => {
  if (!img) return undefined;
  
  // Handle Strapi v4/v5 media field structures
  // Can be: { data: { id, attributes: { url } } } or { url } or { data: { url } }
  // Also handle component fields which might have different nesting
  let url = "";
  
  if (typeof img === "string") {
    url = img;
  } else if (img.url) {
    url = img.url;
  } else if (img.data) {
    if (Array.isArray(img.data) && img.data[0]) {
      // Array format: { data: [{ attributes: { url } }] }
      const firstItem = img.data[0];
      url = firstItem.url || firstItem.attributes?.url || firstItem.data?.attributes?.url || "";
    } else if (img.data.attributes) {
      // Standard v4 format: { data: { attributes: { url } } }
      url = img.data.attributes.url || img.data.url || "";
    } else if (img.data.url) {
      // Alternative format: { data: { url } }
      url = img.data.url;
    } else if (typeof img.data === "object") {
      // Try to find url anywhere in the data object
      url = img.data.url || img.data.attributes?.url || "";
    }
  } else if (img.attributes?.url) {
    url = img.attributes.url;
  } else if (typeof img === "object") {
    // Last resort: try to find url property anywhere in the object
    const findUrl = (obj: any): string => {
      if (!obj || typeof obj !== "object") return "";
      if (obj.url && typeof obj.url === "string") return obj.url;
      for (const key in obj) {
        if (key === "url" && typeof obj[key] === "string") return obj[key];
        if (typeof obj[key] === "object") {
          const found = findUrl(obj[key]);
          if (found) return found;
        }
      }
      return "";
    };
    url = findUrl(img);
  }
  
  if (!url || url.trim() === "") return undefined;

  const absoluteUrl = getStrapiImageUrl(url);

  return {
    url: absoluteUrl,
    data: img.data || {
      attributes: {
        url: absoluteUrl,
        alternativeText:
          img.alternativeText || img.data?.attributes?.alternativeText || img.attributes?.alternativeText,
      },
    },
  };
};

const _fetchLocations = async (): Promise<LocationData[]> => {
  try {
    const pageSize = 100;
    let page = 1;
    let pageCount = 1;
    const allLocations: LocationData[] = [];

    while (page <= pageCount) {
      const response = await axios.get(
        `${STRAPI_URL}/api/locations?sort[0]=priority:asc&sort[1]=name:asc&publicationState=live&pagination[page]=${page}&pagination[pageSize]=${pageSize}`
      );

      const pageData = response?.data?.data ?? [];
      if (!Array.isArray(pageData) || pageData.length === 0) {
        break;
      }

      allLocations.push(
        ...pageData.map((item: any) => ({
          id: item.id,
          name: item.name || item.attributes?.name || "",
          slug: item.slug || item.attributes?.slug || "",
          priority: item.priority || item.attributes?.priority || 0,
        }))
      );

      pageCount = response?.data?.meta?.pagination?.pageCount ?? page;
      page += 1;
    }

    return allLocations;
  } catch (error: any) {
    console.error("Error fetching locations:", error);
    return [];
  }
};

/**
 * Server-only: cached with `properties` tag for listing pages (ISR + on-demand revalidate).
 * Client components should keep using {@link fetchLocations}.
 */
export const fetchLocationsForListingPages = async (): Promise<
  LocationData[]
> => {
  if (process.env.NODE_ENV !== "production") {
    return _fetchLocations();
  }
  return unstable_cache(
    async () => _fetchLocations(),
    ["locations-all-for-listings"],
    { tags: ["properties"], revalidate: false }
  )();
};

/**
 * Fetch all locations
 * Note: This function is called from client components, so it cannot use unstable_cache
 */
export const fetchLocations = async (): Promise<LocationData[]> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return [];
    }

    // Get STRAPI_URL at runtime for client components
    const strapiUrl = process.env.NEXT_PUBLIC_STRAPI_URL || STRAPI_URL;
    
    // Configure axios with timeout for client-side requests
    const axiosConfig = {
      timeout: 30000, // 30 seconds timeout
    };

    const pageSize = 100;
    let page = 1;
    let pageCount = 1;
    const allLocations: LocationData[] = [];

    while (page <= pageCount) {
      const response = await axios.get(
        `${strapiUrl}/api/locations?sort[0]=priority:asc&sort[1]=name:asc&publicationState=live&pagination[page]=${page}&pagination[pageSize]=${pageSize}`,
        axiosConfig
      );

      const pageData = response?.data?.data ?? [];
      if (!Array.isArray(pageData) || pageData.length === 0) {
        break;
      }

      allLocations.push(
        ...pageData.map((item: any) => ({
          id: item.id,
          name: item.name || item.attributes?.name || "",
          slug: item.slug || item.attributes?.slug || "",
          priority: item.priority || item.attributes?.priority || 0,
        }))
      );

      pageCount = response?.data?.meta?.pagination?.pageCount ?? page;
      page += 1;
    }

    return allLocations;
  } catch (error: any) {
    console.error("Error fetching locations:", error);
    return [];
  }
};

export interface PropertiesResponse {
  properties: PropertyData[];
  total: number;
  page: number;
  pageSize: number;
  pageCount: number;
}

/** Map Meilisearch listing hit to minimal PropertyData so we can skip the Strapi round-trip for list view. */
function meilisearchHitToPropertyData(hit: MeilisearchSearchHit): PropertyData {
  const typeSlugs = Array.isArray((hit as MeilisearchSearchHit).typeSlugs)
    ? ((hit as MeilisearchSearchHit).typeSlugs as string[])
    : [];
  const typesFromMeili = typeSlugs.map((s) => ({ slug: s }));
  const pt =
    typesFromMeili.length > 0
      ? typesFromMeili[0]!.slug
      : hit.category &&
          ["residential", "commercial", "condominium", "land"].includes(
            hit.category
          )
        ? hit.category
        : "residential";
  const ps =
    hit.status === "ongoing" || hit.status === "on-going"
      ? "ongoing"
      : hit.status === "upcoming" || hit.status === "completed"
        ? (hit.status as PropertyData["propertyStatus"])
        : "ongoing";
  const thumbUrl = getStrapiImageUrl(hit.thumbnail ?? undefined);
  const img = thumbUrl
    ? { url: thumbUrl, data: { attributes: { url: thumbUrl, alternativeText: "" } } }
    : undefined;
  return {
    id: parseInt(hit.id, 10),
    slug: hit.slug ?? "",
    title: hit.title ?? "",
    ...(typesFromMeili.length > 0 && { types: typesFromMeili }),
    propertyType: pt,
    propertyStatus: ps,
    address: hit.location ?? "",
    location:
      hit.location || hit.locationSlug
        ? {
            id: 0,
            name: hit.location ?? "",
            slug: hit.locationSlug ?? "",
            priority: 0,
          }
        : undefined,
    description: "",
    cardImage: img,
    cardHoverImage: img,
  };
}

/**
 * Fetch properties with pagination metadata
 * Uses Meilisearch when searchQuery is provided for faster search (list from Meilisearch only — no Strapi call).
 */
const _fetchPropertiesWithPagination = async (
  type?: string,
  filters?: {
    status?: string;
    location?: string;
    size?: string;
    searchQuery?: string;
    page?: number;
    pageSize?: number;
  }
): Promise<PropertiesResponse> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return {
        properties: [],
        total: 0,
        page: filters?.page || 1,
        pageSize: filters?.pageSize || 8,
        pageCount: 0,
      };
    }

    const page = filters?.page || 1;
    const pageSize = filters?.pageSize || 8;
    const start = (page - 1) * pageSize;

    // We need app-side effective sorting (overrideValue fallback) on these pages.
    // Meilisearch sorting does not know about the new `residential*/commercial*` override fields.
    // But when user has a search query, prefer Meilisearch for responsiveness.
    const needsLocalOverrideSort = type === "residential" || type === "commercial";

    const USE_MEILISEARCH = process.env.ENABLE_MEILISEARCH === "true";
    const hasSearchQuery = Boolean(filters?.searchQuery?.trim());
    if (USE_MEILISEARCH && hasSearchQuery && filters) {
      try {
        const searchQ = filters.searchQuery?.trim() ?? "";
        if (process.env.NODE_ENV === "development") {
          console.log("[Meilisearch] Calling search API, query:", searchQ, "location:", filters.location);
        }
        const { searchPropertiesWithFilters } = await import("./meilisearch");
        const apiStatus = normalizeStatusForApi(filters.status);
        const meiliResult = await searchPropertiesWithFilters({
          query: searchQ,
          filters: {
            propertyType: type && type !== "all" ? type : undefined,
            status: apiStatus,
            locationSlug:
              filters.location && filters.location !== "all"
                ? filters.location
                : undefined,
          },
          page,
          pageSize,
        });

        if (meiliResult && meiliResult.hits && meiliResult.hits.length > 0) {
          if (process.env.NODE_ENV === "development") {
            console.log("[Meilisearch] Using search results (list from Meilisearch only), count:", meiliResult.hits.length);
          }
          // Build list from Meilisearch hits only — no Strapi round-trip (faster)
          const properties = meiliResult.hits.map((hit) => meilisearchHitToPropertyData(hit));
          return {
            properties,
            total: meiliResult.total,
            page,
            pageSize,
            pageCount: meiliResult.pageCount,
          };
        } else {
          if (process.env.NODE_ENV === "development") {
            const reason = !meiliResult
              ? "search API returned null (client unconfigured or error)"
              : "0 hits from Meilisearch";
            console.log("[Meilisearch] Empty —", reason, "→ using Strapi fallback");
          }
        }
      } catch (meiliError) {
        if (process.env.NODE_ENV === "development") {
          console.warn("[Meilisearch] Error, using Strapi fallback:", meiliError instanceof Error ? meiliError.message : meiliError);
        }
      }
    } else if (hasSearchQuery && process.env.NODE_ENV === "development") {
      console.log("[Meilisearch] Skipped: ENABLE_MEILISEARCH=", process.env.ENABLE_MEILISEARCH, "→ using Strapi");
    }

    const apiStatus = normalizeStatusForApi(filters?.status);
    const sortField = apiStatus ? "statusPriority" : "priority";
    // Fallback to Strapi API (original implementation)
    let url = `${STRAPI_URL}/api/properties?publicationState=live&sort[0]=${sortField}:asc&sort[1]=createdAt:desc&pagination[start]=${start}&pagination[limit]=${pageSize}`;

    // Add filters
    if (type && type !== "all") {
      url += `&filters[types][slug][$eq]=${encodeURIComponent(type)}`;
    }

    if (apiStatus) {
      url += `&filters[propertyStatus][$eq]=${apiStatus}`;
    }

    if (filters?.location && filters.location !== "all") {
      // Filter by location slug
      url += `&filters[location][slug][$eq]=${filters.location}`;
    }

    // Add unit size filter (A–D + E: 4000-5000, F: 5000-6000 sqft)
    const SIZE_RANGES: Record<string, { min: number; max: number }> = {
      A: { min: 0, max: 1500 },
      B: { min: 1500, max: 2000 },
      C: { min: 2000, max: 3000 },
      D: { min: 3000, max: 4000 },
      E: { min: 4000, max: 5000 },
      F: { min: 5000, max: 6000 },
      G: { min: 6000, max: 7000 },
    };
    if (filters?.size && filters.size !== "all" && SIZE_RANGES[filters.size]) {
      const { min, max } = SIZE_RANGES[filters.size];
      url += `&filters[unitSize][$gte]=${min}&filters[unitSize][$lte]=${max}`;
    }

    // Add search query filter (search in title, address, and description)
    if (filters?.searchQuery && filters.searchQuery.trim()) {
      const searchTerm = encodeURIComponent(filters.searchQuery.trim());
      // Strapi v5 $or syntax - search in title, address, and description
      url += `&filters[$or][0][title][$containsi]=${searchTerm}`;
      url += `&filters[$or][1][address][$containsi]=${searchTerm}`;
      url += `&filters[$or][2][description][$containsi]=${searchTerm}`;
    }

    if (!needsLocalOverrideSort) {
      url += `&filters[${sortField}][$gt]=0`;
    }

    // Single explicit populate - one API call (no waterfall)
    const listPopulate =
      "populate[heroBackgroundImageDesktop][fields][0]=url&populate[heroBackgroundImageDesktop][fields][1]=alternativeText" +
      "&populate[heroBackgroundImageMobile][fields][0]=url&populate[heroBackgroundImageMobile][fields][1]=alternativeText" +
      "&populate[cardImage][fields][0]=url&populate[cardImage][fields][1]=alternativeText" +
      "&populate[cardHoverImage][fields][0]=url&populate[cardHoverImage][fields][1]=alternativeText" +
      "&populate[features][populate][icon][fields][0]=url&populate[features][populate][icon][fields][1]=alternativeText" +
      "&populate[amenitiesSlider][populate][image][fields][0]=url&populate[amenitiesSlider][populate][image][fields][1]=alternativeText" +
      "&populate[amenitiesGrid][populate][icon][fields][0]=url&populate[amenitiesGrid][populate][icon][fields][1]=alternativeText" +
      "&populate[portfolioImages][populate][image][fields][0]=url&populate[portfolioImages][populate][image][fields][1]=alternativeText" +
      "&populate[location][populate][mapImage][fields][0]=url&populate[location][populate][mapImage][fields][1]=alternativeText" +
      "&populate[brochure][fields][0]=url" +
      "&populate[location][fields][0]=name&populate[location][fields][1]=slug&populate[location][fields][2]=priority" +
      "&populate[types][fields][0]=slug&populate[types][fields][1]=name";
    if (needsLocalOverrideSort && type) {
      const typeForOverride = type === "residential" ? "residential" : "commercial";
      const overridePriorityKey =
        typeForOverride === "residential"
          ? "residentialPriority"
          : "commercialPriority";
      const overrideStatusPriorityKey =
        typeForOverride === "residential"
          ? "residentialStatusPriority"
          : "commercialStatusPriority";

      const isOverrideProvided = (v: unknown): v is number =>
        typeof v === "number" && !Number.isNaN(v) && v !== 0;

      const mapPropertyItem = (item: any): PropertyData => {
        const attrs = item.attributes || item;
        const typesNorm = normalizeTypesFromStrapi(attrs.types ?? item.types);
        const loc = attrs.location || item.location;
        const features = attrs.features ?? item.features ?? [];
        const amenitiesSlider = attrs.amenitiesSlider ?? item.amenitiesSlider ?? [];
        const amenitiesGrid = attrs.amenitiesGrid ?? item.amenitiesGrid ?? [];
        const portfolioImages = attrs.portfolioImages ?? item.portfolioImages ?? [];

        return {
          id: item.id,
          slug: attrs.slug ?? item.slug,
          title: attrs.title ?? item.title,
          ...(typesNorm.length > 0 && { types: typesNorm }),
          propertyType: getPrimaryTypeSlugForUrl({
            types: typesNorm,
            propertyType: attrs.propertyType ?? item.propertyType,
          }),
          propertyStatus: attrs.propertyStatus ?? item.propertyStatus ?? "",
          priority: attrs.priority ?? item.priority,
          createdAt: attrs.createdAt ?? item.createdAt,
          statusPriority: attrs.statusPriority ?? item.statusPriority ?? 0,
          residentialPriority:
            attrs.residentialPriority ?? item.residentialPriority,
          commercialPriority:
            attrs.commercialPriority ?? item.commercialPriority,
          residentialStatusPriority:
            attrs.residentialStatusPriority ?? item.residentialStatusPriority,
          commercialStatusPriority:
            attrs.commercialStatusPriority ?? item.commercialStatusPriority,
          displayOrder: attrs.displayOrder ?? item.displayOrder ?? 0,
          address: attrs.address ?? item.address,
          location: loc
            ? {
                id: loc.id,
                name: loc.name || "",
                slug: loc.slug || "",
                priority: loc.priority || 0,
                mapImage: normalizeImage(loc.mapImage),
              }
            : undefined,
          unitSize: attrs.unitSize ?? item.unitSize,
          description: attrs.description ?? item.description,
          brochure: normalizeImage(attrs.brochure ?? item.brochure),
          heroBackgroundImageDesktop: normalizeImage(
            attrs.heroBackgroundImageDesktop ?? item.heroBackgroundImageDesktop
          ),
          heroBackgroundImageMobile: normalizeImage(
            attrs.heroBackgroundImageMobile ?? item.heroBackgroundImageMobile
          ),
          features: Array.isArray(features)
            ? features.map((feat: any) => {
                const f = feat.attributes || feat;
                return {
                  icon: normalizeImage(f.icon ?? feat.icon),
                  label: f.label ?? feat.label ?? "",
                  value: f.value ?? feat.value ?? "",
                };
              })
            : [],
          showcaseVideo:
            typeof (attrs.showcaseVideo ?? item.showcaseVideo) === "string"
              ? (attrs.showcaseVideo ?? item.showcaseVideo)
              : normalizeImage(attrs.showcaseVideo ?? item.showcaseVideo),
          amenitiesTitle: attrs.amenitiesTitle ?? item.amenitiesTitle,
          amenitiesDescription: attrs.amenitiesDescription ?? item.amenitiesDescription,
          amenitiesSlider: Array.isArray(amenitiesSlider)
            ? amenitiesSlider.map((s: any) => {
                const sd = s.attributes || s;
                return {
                  image: normalizeImage(sd.image ?? s.image),
                  title: sd.title ?? s.title ?? "",
                };
              })
            : [],
          amenitiesGrid: Array.isArray(amenitiesGrid)
            ? amenitiesGrid.map((g: any) => {
                const gd = g.attributes || g;
                return {
                  icon: normalizeImage(gd.icon ?? g.icon),
                  value: gd.value ?? g.value ?? "",
                };
              })
            : [],
          locationPinLat: attrs.locationPinLat ?? item.locationPinLat,
          locationPinLng: attrs.locationPinLng ?? item.locationPinLng,
          portfolioImages: Array.isArray(portfolioImages)
            ? portfolioImages.map((p: any) => {
                const pd = p.attributes || p;
                return { image: normalizeImage(pd.image ?? p.image) };
              })
            : [],
          cardImage: normalizeImage(attrs.cardImage ?? item.cardImage),
          cardHoverImage: normalizeImage(attrs.cardHoverImage ?? item.cardHoverImage),
        };
      };

      // Residential / commercial: apply override sort client-side. A single Strapi listing
      // (same filters as `url`, no per-partition pagination) avoids merge gaps. `total` must
      // be merged.length after filter — Strapi totals count rows that getEffectiveSortValue
      // filters out, which used to show extra empty pages.

      const startIndex = (page - 1) * pageSize;

      const effectiveOverrideField = apiStatus ? overrideStatusPriorityKey : overridePriorityKey;

      const urlWithoutPagination = url.replace(
        `&pagination[start]=${start}&pagination[limit]=${pageSize}`,
        ""
      );

      const getEffectiveSortValue = (p: PropertyData): number => {
        const overrideVal = (p as any)[effectiveOverrideField] as unknown;
        if (isOverrideProvided(overrideVal)) return overrideVal;
        return apiStatus
          ? typeof p.statusPriority === "number"
            ? p.statusPriority
            : 0
          : typeof p.priority === "number"
            ? p.priority
            : 0;
      };

      const hasRenderableEffectiveSortValue = (p: PropertyData): boolean =>
        getEffectiveSortValue(p) > 0;

      const rcMergeCacheKey = JSON.stringify({
        branch: "rc-list-merge",
        type,
        sortField,
        apiStatus: apiStatus ?? null,
        location: filters?.location || "all",
        status: filters?.status || "all",
        size: filters?.size || "all",
        searchQuery: (filters?.searchQuery || "").trim(),
      });

      const STRAPI_LIST_BATCH = 100;

      const merged = await unstable_cache(
        async () => {
          const rows: PropertyData[] = [];
          let listStart = 0;
          let reportedTotal = 0;

          while (true) {
            const paged =
              `${urlWithoutPagination}&pagination[start]=${listStart}&pagination[limit]=${STRAPI_LIST_BATCH}`;
            const resp = await safeAxiosGet(`${paged}&${listPopulate}`);
            const raw = resp?.data?.data;
            const chunkArr = Array.isArray(raw) ? raw : [];
            if (listStart === 0) {
              reportedTotal = resp?.data?.meta?.pagination?.total ?? 0;
            }
            rows.push(...chunkArr.map(mapPropertyItem));
            if (chunkArr.length === 0) break;
            if (chunkArr.length < STRAPI_LIST_BATCH) break;
            if (reportedTotal > 0 && rows.length >= reportedTotal) break;
            listStart += STRAPI_LIST_BATCH;
          }

          return rows
            .filter(hasRenderableEffectiveSortValue)
            .sort((a, b) => {
              const keyA = getEffectiveSortValue(a);
              const keyB = getEffectiveSortValue(b);
              if (keyA !== keyB) return keyA - keyB;

              const timeA = a.createdAt ? Date.parse(a.createdAt) : 0;
              const timeB = b.createdAt ? Date.parse(b.createdAt) : 0;
              const timeANaN = Number.isNaN(timeA);
              const timeBNaN = Number.isNaN(timeB);

              if (!timeANaN && !timeBNaN && timeA !== timeB) return timeB - timeA;

              return (a.title || "").localeCompare(b.title || "") || a.id - b.id;
            });
        },
        [`properties-rc-merge-${rcMergeCacheKey}`],
        { tags: ["properties"], revalidate: false }
      )();

      const total = merged.length;
      const properties = merged.slice(startIndex, startIndex + pageSize);
      const pageCount = Math.ceil(total / pageSize) || 0;

      return {
        properties,
        total,
        page,
        pageSize,
        pageCount,
      };
    }

    const response = await safeAxiosGet(`${url}&${listPopulate}`);

    // Validate response before processing
    if (!response || !response.data) {
      console.error(
        `[Strapi API] Invalid response structure for properties API:`,
        {
          url: url.substring(0, 200),
          hasResponse: !!response,
          hasData: !!response?.data,
        }
      );
      return {
        properties: [],
        total: 0,
        page,
        pageSize,
        pageCount: 0,
      };
    }


    if (response.data && Array.isArray(response.data.data)) {
      const properties = response.data.data.map((item: any) => {
        const attrs = item.attributes || item;
        const typesNorm = normalizeTypesFromStrapi(attrs.types ?? item.types);
        const loc = attrs.location || item.location;
        const features = attrs.features ?? item.features ?? [];
        const amenitiesSlider = attrs.amenitiesSlider ?? item.amenitiesSlider ?? [];
        const amenitiesGrid = attrs.amenitiesGrid ?? item.amenitiesGrid ?? [];
        const portfolioImages = attrs.portfolioImages ?? item.portfolioImages ?? [];
        return {
          id: item.id,
          slug: attrs.slug ?? item.slug,
          title: attrs.title ?? item.title,
          ...(typesNorm.length > 0 && { types: typesNorm }),
          propertyType: getPrimaryTypeSlugForUrl({
            types: typesNorm,
            propertyType: attrs.propertyType ?? item.propertyType,
          }),
          propertyStatus: attrs.propertyStatus ?? item.propertyStatus ?? "",
          statusPriority: attrs.statusPriority ?? item.statusPriority ?? 0,
          displayOrder: attrs.displayOrder ?? item.displayOrder ?? 0,
          address: attrs.address ?? item.address,
          location: loc
            ? {
                id: loc.id,
                name: loc.name || "",
                slug: loc.slug || "",
                priority: loc.priority || 0,
                mapImage: normalizeImage(loc.mapImage),
              }
            : undefined,
          unitSize: attrs.unitSize ?? item.unitSize,
          description: attrs.description ?? item.description,
          brochure: normalizeImage(attrs.brochure ?? item.brochure),
          heroBackgroundImageDesktop: normalizeImage(
            attrs.heroBackgroundImageDesktop ?? item.heroBackgroundImageDesktop
          ),
          heroBackgroundImageMobile: normalizeImage(
            attrs.heroBackgroundImageMobile ?? item.heroBackgroundImageMobile
          ),
          features: Array.isArray(features)
            ? features.map((feat: any) => {
                const f = feat.attributes || feat;
                return {
                  icon: normalizeImage(f.icon ?? feat.icon),
                  label: f.label ?? feat.label ?? "",
                  value: f.value ?? feat.value ?? "",
                };
              })
            : [],
          showcaseVideo: typeof (attrs.showcaseVideo ?? item.showcaseVideo) === "string"
            ? (attrs.showcaseVideo ?? item.showcaseVideo)
            : normalizeImage(attrs.showcaseVideo ?? item.showcaseVideo),
          amenitiesTitle: attrs.amenitiesTitle ?? item.amenitiesTitle,
          amenitiesDescription: attrs.amenitiesDescription ?? item.amenitiesDescription,
          amenitiesSlider: Array.isArray(amenitiesSlider)
            ? amenitiesSlider.map((s: any) => {
                const sd = s.attributes || s;
                return {
                  image: normalizeImage(sd.image ?? s.image),
                  title: sd.title ?? s.title ?? "",
                };
              })
            : [],
          amenitiesGrid: Array.isArray(amenitiesGrid)
            ? amenitiesGrid.map((g: any) => {
                const gd = g.attributes || g;
                return {
                  icon: normalizeImage(gd.icon ?? g.icon),
                  value: gd.value ?? g.value ?? "",
                };
              })
            : [],
          locationPinLat: attrs.locationPinLat ?? item.locationPinLat,
          locationPinLng: attrs.locationPinLng ?? item.locationPinLng,
          portfolioImages: Array.isArray(portfolioImages)
            ? portfolioImages.map((p: any) => {
                const pd = p.attributes || p;
                return { image: normalizeImage(pd.image ?? p.image) };
              })
            : [],
          cardImage: normalizeImage(attrs.cardImage ?? item.cardImage),
          cardHoverImage: normalizeImage(attrs.cardHoverImage ?? item.cardHoverImage),
        };
      });

      // Get pagination metadata from Strapi response
      const meta = response.data.meta || {};
      const total = meta.pagination?.total || properties.length;
      const pageCount =
        meta.pagination?.pageCount || Math.ceil(total / pageSize);

      return {
        properties,
        total,
        page,
        pageSize,
        pageCount,
      };
    }

    return {
      properties: [],
      total: 0,
      page,
      pageSize,
      pageCount: 0,
    };
  } catch (error: any) {
    console.error("[Strapi API] Error fetching properties with pagination:", {
      type,
      filters,
      error: error?.message || error,
      code: error?.code,
      status: error?.response?.status,
      statusText: error?.response?.statusText,
      url: error?.config?.url?.substring(0, 200),
      isTimeout: error?.code === "ECONNABORTED",
      isConnectionReset: error?.code === "ECONNRESET",
      responseData: error?.response?.data,
    });
    return {
      properties: [],
      total: 0,
      page: filters?.page || 1,
      pageSize: filters?.pageSize || 8,
      pageCount: 0,
    };
  }
};

export const fetchPropertiesWithPagination = async (
  type?: string,
  filters?: {
    status?: string;
    location?: string;
    size?: string;
    searchQuery?: string;
    page?: number;
    pageSize?: number;
  }
): Promise<PropertiesResponse> => {
  // Create a stable cache key from parameters
  const cacheKey = JSON.stringify({
    type,
    filters,
    orderingLogicVersion: "priority-override-v2",
  });
  return unstable_cache(
    async () => _fetchPropertiesWithPagination(type, filters),
    [`properties-pagination-${cacheKey}`],
    {
      tags: ["properties"],
      revalidate: false, // No time-based revalidation - relies on on-demand revalidation from Strapi
    }
  )();
};

const _fetchProperties = async (
  type?: string,
  filters?: {
    status?: string;
    location?: string;
    size?: string;
    searchQuery?: string;
    page?: number;
    pageSize?: number;
  }
): Promise<PropertyData[]> => {
  try {
    const page = filters?.page || 1;
    const pageSize = filters?.pageSize || 8;
    const start = (page - 1) * pageSize;

    const apiStatus = normalizeStatusForApi(filters?.status);
    const sortField = apiStatus ? "statusPriority" : "priority";
    let url = `${STRAPI_URL}/api/properties?publicationState=live&sort[0]=${sortField}:asc&sort[1]=createdAt:desc&pagination[start]=${start}&pagination[limit]=${pageSize}`;

    // Add filters
    if (type && type !== "all") {
      url += `&filters[types][slug][$eq]=${encodeURIComponent(type)}`;
    }

    if (apiStatus) {
      url += `&filters[propertyStatus][$eq]=${apiStatus}`;
    }

    if (filters?.location && filters.location !== "all") {
      // Filter by location slug
      url += `&filters[location][slug][$eq]=${filters.location}`;
    }

    // Add search query filter (search in title and address)
    if (filters?.searchQuery && filters.searchQuery.trim()) {
      const searchTerm = encodeURIComponent(filters.searchQuery.trim());
      // Strapi v5 $or syntax
      url += `&filters[$or][0][title][$containsi]=${searchTerm}`;
      url += `&filters[$or][1][address][$containsi]=${searchTerm}`;
    }

    // Try different populate syntaxes
    let response;
    try {
      response = await axios.get(`${url}&populate=deep`);
    } catch (err1: any) {
      try {
        response = await axios.get(
          `${url}&populate[heroBackgroundImageDesktop][fields][0]=url&populate[heroBackgroundImageDesktop][fields][1]=alternativeText&populate[heroBackgroundImageMobile][fields][0]=url&populate[heroBackgroundImageMobile][fields][1]=alternativeText&populate[cardImage][fields][0]=url&populate[cardImage][fields][1]=alternativeText&populate[cardHoverImage][fields][0]=url&populate[cardHoverImage][fields][1]=alternativeText&populate[features][populate][icon][fields][0]=url&populate[features][populate][icon][fields][1]=alternativeText&populate[amenitiesSlider][populate][image][fields][0]=url&populate[amenitiesSlider][populate][image][fields][1]=alternativeText&populate[amenitiesGrid][populate][icon][fields][0]=url&populate[amenitiesGrid][populate][icon][fields][1]=alternativeText&populate[portfolioImages][populate][image][fields][0]=url&populate[portfolioImages][populate][image][fields][1]=alternativeText&populate[location][populate][mapImage][fields][0]=url&populate[location][populate][mapImage][fields][1]=alternativeText&populate[showcaseVideo][fields][0]=url&populate[brochure][fields][0]=url&populate[location][fields][0]=name&populate[location][fields][1]=slug&populate[location][fields][2]=priority&populate[types][fields][0]=slug&populate[types][fields][1]=name`
        );
      } catch (err2: any) {
        try {
          response = await axios.get(`${url}&populate=*`);
        } catch (err3: any) {
          response = await axios.get(url);
        }
      }
    }

    if (response?.data?.data) {
      return response.data.data.map((item: any) => {
        const attrs = item.attributes || item;
        const typesNorm = normalizeTypesFromStrapi(attrs.types ?? item.types);
        return {
        id: item.id,
        slug: attrs.slug ?? item.slug,
        title: attrs.title ?? item.title,
        ...(typesNorm.length > 0 && { types: typesNorm }),
        propertyType: getPrimaryTypeSlugForUrl({
          types: typesNorm,
          propertyType: attrs.propertyType ?? item.propertyType,
        }),
        propertyStatus:
          attrs.propertyStatus || item.propertyStatus || "",
        statusPriority: attrs.statusPriority ?? item.statusPriority ?? 0,
        displayOrder: attrs.displayOrder ?? item.displayOrder ?? 0,
        address: attrs.address ?? item.address,
        location: (attrs.location || item.location)
          ? (() => {
              const loc = attrs.location || item.location;
              return {
                id: loc.id,
                name: loc.name || "",
                slug: loc.slug || "",
                priority: loc.priority || 0,
                mapImage: normalizeImage(loc.mapImage),
              };
            })()
          : undefined,
        unitSize: attrs.unitSize ?? item.unitSize,
        description: attrs.description ?? item.description,
        brochure: normalizeImage(attrs.brochure ?? item.brochure),
        heroBackgroundImageDesktop: normalizeImage(
          attrs.heroBackgroundImageDesktop ?? item.heroBackgroundImageDesktop
        ),
        heroBackgroundImageMobile: normalizeImage(
          attrs.heroBackgroundImageMobile ?? item.heroBackgroundImageMobile
        ),
        features: Array.isArray(attrs.features ?? item.features)
          ? (attrs.features ?? item.features).map((feat: any) => ({
              icon: normalizeImage(feat.icon),
              label: feat.label || "",
              value: feat.value || "",
            }))
          : [],
        showcaseVideo:
          typeof (attrs.showcaseVideo ?? item.showcaseVideo) === "string"
            ? (attrs.showcaseVideo ?? item.showcaseVideo)
            : normalizeImage(attrs.showcaseVideo ?? item.showcaseVideo),
        amenitiesTitle: attrs.amenitiesTitle ?? item.amenitiesTitle,
        amenitiesDescription:
          attrs.amenitiesDescription ?? item.amenitiesDescription,
        amenitiesSlider: Array.isArray(
          attrs.amenitiesSlider ?? item.amenitiesSlider
        )
          ? (attrs.amenitiesSlider ?? item.amenitiesSlider).map((s: any) => ({
              image: normalizeImage(s.image),
              title: s.title || "",
            }))
          : [],
        amenitiesGrid: Array.isArray(attrs.amenitiesGrid ?? item.amenitiesGrid)
          ? (attrs.amenitiesGrid ?? item.amenitiesGrid).map((g: any) => ({
              icon: normalizeImage(g.icon),
              value: g.value || "",
            }))
          : [],
        locationPinLat: attrs.locationPinLat ?? item.locationPinLat,
        locationPinLng: attrs.locationPinLng ?? item.locationPinLng,
        portfolioImages: Array.isArray(
          attrs.portfolioImages ?? item.portfolioImages
        )
          ? (attrs.portfolioImages ?? item.portfolioImages).map((img: any) => ({
              image: normalizeImage(img.image),
            }))
          : [],
        cardImage: normalizeImage(attrs.cardImage ?? item.cardImage),
        cardHoverImage: normalizeImage(
          attrs.cardHoverImage ?? item.cardHoverImage
        ),
      };
      });
    }
    return [];
  } catch (error: any) {
    console.error("Error fetching properties:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
    }
    return [];
  }
};

/**
 * Fetch properties
 * Note: This function is called from client components, so it cannot use unstable_cache
 */
export const fetchProperties = async (
  type?: string,
  filters?: {
    status?: string;
    location?: string;
    size?: string;
    searchQuery?: string;
    page?: number;
    pageSize?: number;
  }
): Promise<PropertyData[]> => {
  try {
    // Get STRAPI_URL at runtime for client components
    const strapiUrl = process.env.NEXT_PUBLIC_STRAPI_URL || STRAPI_URL;
    
    if (!strapiUrl) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return [];
    }

    // Configure axios with timeout for client-side requests
    const axiosConfig = {
      timeout: 30000, // 30 seconds timeout
    };

    const page = filters?.page || 1;
    const pageSize = filters?.pageSize || 8;
    const start = (page - 1) * pageSize;

    const apiStatus = normalizeStatusForApi(filters?.status);
    const sortField = apiStatus ? "statusPriority" : "priority";
    let url = `${strapiUrl}/api/properties?publicationState=live&sort[0]=${sortField}:asc&sort[1]=createdAt:desc&pagination[start]=${start}&pagination[limit]=${pageSize}`;

    // Add filters
    if (type && type !== "all") {
      url += `&filters[types][slug][$eq]=${encodeURIComponent(type)}`;
    }

    if (apiStatus) {
      url += `&filters[propertyStatus][$eq]=${apiStatus}`;
    }

    if (filters?.location && filters.location !== "all") {
      // Filter by location slug
      url += `&filters[location][slug][$eq]=${filters.location}`;
    }

    // Add search query filter (search in title and address)
    if (filters?.searchQuery && filters.searchQuery.trim()) {
      const searchTerm = encodeURIComponent(filters.searchQuery.trim());
      // Strapi v5 $or syntax
      url += `&filters[$or][0][title][$containsi]=${searchTerm}`;
      url += `&filters[$or][1][address][$containsi]=${searchTerm}`;
    }

    // Try different populate syntaxes
    let response;
    try {
      response = await axios.get(`${url}&populate=deep`, axiosConfig);
    } catch (err1: any) {
      try {
        response = await axios.get(
          `${url}&populate[heroBackgroundImageDesktop][fields][0]=url&populate[heroBackgroundImageDesktop][fields][1]=alternativeText&populate[heroBackgroundImageMobile][fields][0]=url&populate[heroBackgroundImageMobile][fields][1]=alternativeText&populate[cardImage][fields][0]=url&populate[cardImage][fields][1]=alternativeText&populate[cardHoverImage][fields][0]=url&populate[cardHoverImage][fields][1]=alternativeText&populate[features][populate][icon][fields][0]=url&populate[features][populate][icon][fields][1]=alternativeText&populate[amenitiesSlider][populate][image][fields][0]=url&populate[amenitiesSlider][populate][image][fields][1]=alternativeText&populate[amenitiesGrid][populate][icon][fields][0]=url&populate[amenitiesGrid][populate][icon][fields][1]=alternativeText&populate[portfolioImages][populate][image][fields][0]=url&populate[portfolioImages][populate][image][fields][1]=alternativeText&populate[location][populate][mapImage][fields][0]=url&populate[location][populate][mapImage][fields][1]=alternativeText&populate[showcaseVideo][fields][0]=url&populate[brochure][fields][0]=url&populate[location][fields][0]=name&populate[location][fields][1]=slug&populate[location][fields][2]=priority&populate[types][fields][0]=slug&populate[types][fields][1]=name`,
          axiosConfig
        );
      } catch (err2: any) {
        try {
          response = await axios.get(`${url}&populate=*`, axiosConfig);
        } catch (err3: any) {
          response = await axios.get(url, axiosConfig);
        }
      }
    }

    if (response?.data?.data) {
      return response.data.data.map((item: any) => {
        const attrs = item.attributes || item;
        const typesNorm = normalizeTypesFromStrapi(attrs.types ?? item.types);
        return {
        id: item.id,
        slug: attrs.slug ?? item.slug,
        title: attrs.title ?? item.title,
        ...(typesNorm.length > 0 && { types: typesNorm }),
        propertyType: getPrimaryTypeSlugForUrl({
          types: typesNorm,
          propertyType: attrs.propertyType ?? item.propertyType,
        }),
        propertyStatus:
          attrs.propertyStatus || item.propertyStatus || "",
        statusPriority: attrs.statusPriority ?? item.statusPriority ?? 0,
        displayOrder: attrs.displayOrder ?? item.displayOrder ?? 0,
        address: attrs.address ?? item.address,
        location: (attrs.location || item.location)
          ? (() => {
              const loc = attrs.location || item.location;
              return {
                id: loc.id,
                name: loc.name || "",
                slug: loc.slug || "",
                priority: loc.priority || 0,
                mapImage: normalizeImage(loc.mapImage),
              };
            })()
          : undefined,
        unitSize: attrs.unitSize ?? item.unitSize,
        description: attrs.description ?? item.description,
        brochure: normalizeImage(attrs.brochure ?? item.brochure),
        heroBackgroundImageDesktop: normalizeImage(
          attrs.heroBackgroundImageDesktop ?? item.heroBackgroundImageDesktop
        ),
        heroBackgroundImageMobile: normalizeImage(
          attrs.heroBackgroundImageMobile ?? item.heroBackgroundImageMobile
        ),
        features: Array.isArray(attrs.features ?? item.features)
          ? (attrs.features ?? item.features).map((feat: any) => ({
              icon: normalizeImage(feat.icon),
              label: feat.label || "",
              value: feat.value || "",
            }))
          : [],
        showcaseVideo:
          typeof (attrs.showcaseVideo ?? item.showcaseVideo) === "string"
            ? (attrs.showcaseVideo ?? item.showcaseVideo)
            : normalizeImage(attrs.showcaseVideo ?? item.showcaseVideo),
        amenitiesTitle: attrs.amenitiesTitle ?? item.amenitiesTitle,
        amenitiesDescription:
          attrs.amenitiesDescription ?? item.amenitiesDescription,
        amenitiesSlider: Array.isArray(
          attrs.amenitiesSlider ?? item.amenitiesSlider
        )
          ? (attrs.amenitiesSlider ?? item.amenitiesSlider).map((s: any) => ({
              image: normalizeImage(s.image),
              title: s.title || "",
            }))
          : [],
        amenitiesGrid: Array.isArray(attrs.amenitiesGrid ?? item.amenitiesGrid)
          ? (attrs.amenitiesGrid ?? item.amenitiesGrid).map((g: any) => ({
              icon: normalizeImage(g.icon),
              value: g.value || "",
            }))
          : [],
        locationPinLat: attrs.locationPinLat ?? item.locationPinLat,
        locationPinLng: attrs.locationPinLng ?? item.locationPinLng,
        portfolioImages: Array.isArray(
          attrs.portfolioImages ?? item.portfolioImages
        )
          ? (attrs.portfolioImages ?? item.portfolioImages).map((img: any) => ({
              image: normalizeImage(img.image),
            }))
          : [],
        cardImage: normalizeImage(attrs.cardImage ?? item.cardImage),
        cardHoverImage: normalizeImage(
          attrs.cardHoverImage ?? item.cardHoverImage
        ),
      };
      });
    }
    return [];
  } catch (error: any) {
    console.error("Error fetching properties:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
    }
    return [];
  }
};

export const fetchPropertiesByType = async (
  type: string
): Promise<PropertyData[]> => {
  return fetchProperties(type);
};

// Header/Navbar interfaces and functions
export interface MenuItem {
  id: number;
  title: string;
  href: string;
  hasSubmenu?: boolean;
  displayOrder?: number;
}

export interface FeaturedPropertyItem {
  id: number;
  property?: PropertyData;
  image?:
    | {
        url?: string;
        alternativeText?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  displayOrder?: number;
}

export interface HeaderData {
  logo?:
    | {
        url?: string;
        alternativeText?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  mainMenuItems?: MenuItem[];
  secondaryMenuItems?: MenuItem[];
  masterpiecesSubmenuItems?: MenuItem[];
  featuredProperties?: FeaturedPropertyItem[];
  contactButtonText?: string;
  sidebarContactPhone?: string;
  sidebarContactEmail?: string;
  sidebarImage?: { url?: string; alternativeText?: string; [key: string]: any };
  sidebarImageUrl?: string;
}

/** Map Strapi header document / REST `data` payload to HeaderData (shared by REST + nav-layout bundle). */
function mapHeaderStrapiPayload(data: any): HeaderData | null {
  if (!data) return null;

  const normalizeLogo = (logoData: any) => {
    if (!logoData) return undefined;
    if (logoData.url) return logoData;
    if (logoData.data?.attributes?.url) {
      return {
        url: logoData.data.attributes.url,
        alternativeText: logoData.data.attributes.alternativeText,
      };
    }
    return logoData;
  };

  const normalizeMenuItems = (items: any[]): MenuItem[] => {
    if (!Array.isArray(items)) return [];
    return items
      .map((item: any, index: number) => ({
        id: item.id || index + 1,
        title: item.title || "",
        href: item.href || "#",
        hasSubmenu: item.hasSubmenu || false,
        displayOrder: item.displayOrder ?? index,
      }))
      .sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0));
  };

  const toArray = (raw: any): any[] =>
    Array.isArray(raw)
      ? raw
      : raw?.data != null
        ? Array.isArray(raw.data)
          ? raw.data
          : [raw.data]
        : [];
  const rawFeaturedProps = toArray(data.featuredProperties);

  const sidebarImageRaw = data.sidebarImage;
  const sidebarImageUrl =
    sidebarImageRaw?.url != null
      ? getStrapiImageUrl(sidebarImageRaw.url)
      : undefined;

  return {
    logo: normalizeLogo(data.logo),
    mainMenuItems: normalizeMenuItems(toArray(data.mainMenuItems)),
    secondaryMenuItems: normalizeMenuItems(toArray(data.secondaryMenuItems)),
    masterpiecesSubmenuItems: normalizeMenuItems(
      toArray(data.masterpiecesSubmenuItems)
    ),
    featuredProperties: normalizeFeaturedProperties(rawFeaturedProps),
    contactButtonText: data.contactButtonText || "Contact Us",
    sidebarContactPhone: data.sidebarContactPhone || "+880 2 58815305-11",
    sidebarContactEmail:
      data.sidebarContactEmail || "sales@navana-realestate.com",
    sidebarImage: sidebarImageRaw,
    sidebarImageUrl: sidebarImageUrl || undefined,
  };
}

const _fetchHeader = async (): Promise<HeaderData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    // Try different populate syntaxes for Strapi v5
    let response;
    let lastError: any = null;

    // Try 1: Deep populate syntax
    try {
      response = await axios.get(`${STRAPI_URL}/api/header?populate=deep`);
    } catch (err1: any) {
      lastError = err1;
      // Try 2: Nested populate syntax
      try {
        response = await axios.get(
          `${STRAPI_URL}/api/header?populate[logo][fields][0]=url&populate[logo][fields][1]=alternativeText&populate[mainMenuItems]=*&populate[secondaryMenuItems]=*&populate[masterpiecesSubmenuItems]=*&populate[featuredProperties][populate][property][fields][0]=title&populate[featuredProperties][populate][property][fields][1]=slug&populate[featuredProperties][populate][property][fields][2]=propertyStatus&populate[featuredProperties][populate][property][fields][3]=address&populate[featuredProperties][populate][property][populate][types][fields][0]=slug&populate[featuredProperties][populate][property][populate][types][fields][1]=name&populate[featuredProperties][populate][property][populate][location][fields][0]=name&populate[featuredProperties][populate][property][populate][location][fields][1]=slug&populate[featuredProperties][populate][property][populate][location][fields][2]=priority&populate[featuredProperties][populate][property][populate][cardImage][fields][0]=url&populate[featuredProperties][populate][property][populate][cardImage][fields][1]=alternativeText&populate[featuredProperties][populate][image][fields][0]=url&populate[featuredProperties][populate][image][fields][1]=alternativeText&populate[sidebarImage][fields][0]=url&populate[sidebarImage][fields][1]=alternativeText`
        );
      } catch (err2: any) {
        lastError = err2;
        // Try 3: Simple populate
        try {
          response = await axios.get(`${STRAPI_URL}/api/header?populate=*`);
        } catch (err3: any) {
          lastError = err3;
          // Try 4: Without populate (fallback)
          try {
            response = await axios.get(`${STRAPI_URL}/api/header`);
          } catch (err4: any) {
            throw err4;
          }
        }
      }
    }

    if (response?.data?.data) {
      return mapHeaderStrapiPayload(response.data.data);
    }
    return null;
  } catch (error: any) {
    console.error("Error fetching header data:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
    }
    return null;
  }
};

const _fetchPropertyBySlugCached = async (
  type: string,
  slug: string
): Promise<PropertyData | null> => {
  const cacheKey = `${type}-${slug}`;
  return unstable_cache(
    async () => _fetchPropertyBySlug(type, slug),
    [`property-${cacheKey}`],
    {
      tags: ["properties"],
      revalidate: false, // No time-based revalidation - relies on on-demand revalidation from Strapi
    }
  )();
};

const fetchPropertyBySlugRequestCached = cache(_fetchPropertyBySlugCached);

/**
 * Request-deduped in production; fresh fetch in dev to avoid sticky nulls from
 * transient auth/network issues when using unstable_cache with revalidate=false.
 */
export const fetchPropertyBySlug = async (
  type: string,
  slug: string
): Promise<PropertyData | null> => {
  if (process.env.NODE_ENV !== "production") {
    return _fetchPropertyBySlug(type, slug);
  }
  return fetchPropertyBySlugRequestCached(type, slug);
};

const fetchHeaderCached = unstable_cache(
  async () => _fetchHeader(),
  ["header"],
  {
    tags: ["header"],
    revalidate: false, // Production: on-demand revalidate via tag `header` (Strapi → /api/revalidate)
  }
);

/** Fresh Strapi data in dev; cached in production unless tag `header` is revalidated. */
export const fetchHeader = async (): Promise<HeaderData | null> => {
  // In local/dev, always bypass unstable_cache to avoid stale CMS content.
  const bypassDevCache = process.env.NODE_ENV !== "production";
  if (bypassDevCache) {
    return _fetchHeader();
  }
  return fetchHeaderCached();
};

function unwrapFooterBundleNode(inner: any): any {
  if (!inner) return null;
  return inner.attributes ?? inner;
}

const _fetchLayoutNav = async (): Promise<{
  headerData: HeaderData | null;
  footerData: FooterData | null;
}> => {
  if (!STRAPI_URL) {
    console.error(
      "STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable."
    );
    return { headerData: null, footerData: null };
  }

  try {
    const res = await axios.get(`${STRAPI_URL}/api/nav-layout`, {
      timeout: 30000,
    });
    const bundle = res?.data?.data;
    if (bundle?.header?.data != null && bundle?.footer?.data != null) {
      const headerData = mapHeaderStrapiPayload(bundle.header.data);
      const footerData = normalizeFooterFromStrapi(
        unwrapFooterBundleNode(bundle.footer.data)
      );
      const footerHasItems = Boolean(
        footerData?.contactInfo?.items?.length ||
          footerData?.menuColumns?.some((col) => col.items?.length)
      );
      if (headerData && footerData && footerHasItems) {
        return { headerData, footerData };
      }
    }
  } catch (error: any) {
    if (!isAxios404(error)) {
      console.warn(
        "[layout-nav] GET /api/nav-layout failed; using separate header/footer requests.",
        error?.message
      );
    }
  }

  const [headerData, footerData] = await Promise.all([
    _fetchHeader(),
    _fetchFooter(),
  ]);
  return { headerData, footerData };
};

const fetchLayoutNavCached = unstable_cache(
  async () => _fetchLayoutNav(),
  ["layout-nav"],
  {
    tags: ["header", "footer"],
    revalidate: false,
  }
);

/**
 * One Strapi HTTP request for header + footer when CMS serves GET /api/nav-layout.
 * Falls back to parallel /api/header and /api/footer on older CMS builds.
 */
export const fetchLayoutNav = async (): Promise<{
  headerData: HeaderData | null;
  footerData: FooterData | null;
}> => {
  // In local/dev, always bypass unstable_cache to avoid stale CMS content.
  const bypassDevCache = process.env.NODE_ENV !== "production";
  if (bypassDevCache) {
    return _fetchLayoutNav();
  }
  return fetchLayoutNavCached();
};

const _fetchPropertyBySlug = async (
  type: string,
  slug: string
): Promise<PropertyData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    const baseFilter = `filters[slug][$eq]=${encodeURIComponent(slug)}&publicationState=live`;
    let url = `${STRAPI_URL}/api/properties?${baseFilter}&filters[types][slug][$eq]=${encodeURIComponent(type)}`;

    // Single explicit populate - one API call for fast load (no waterfall or extra top-up requests)
    const populate =
      "populate[heroBackgroundImageDesktop][fields][0]=url&populate[heroBackgroundImageDesktop][fields][1]=alternativeText" +
      "&populate[heroBackgroundImageMobile][fields][0]=url&populate[heroBackgroundImageMobile][fields][1]=alternativeText" +
      "&populate[cardImage][fields][0]=url&populate[cardImage][fields][1]=alternativeText" +
      "&populate[cardHoverImage][fields][0]=url&populate[cardHoverImage][fields][1]=alternativeText" +
      "&populate[features][populate][icon][fields][0]=url&populate[features][populate][icon][fields][1]=alternativeText" +
      "&populate[amenitiesSlider][populate][image][fields][0]=url&populate[amenitiesSlider][populate][image][fields][1]=alternativeText" +
      "&populate[amenitiesGrid][populate][icon][fields][0]=url&populate[amenitiesGrid][populate][icon][fields][1]=alternativeText" +
      "&populate[portfolioImages][populate][image][fields][0]=url&populate[portfolioImages][populate][image][fields][1]=alternativeText" +
      "&populate[location][populate][mapImage][fields][0]=url&populate[location][populate][mapImage][fields][1]=alternativeText" +
      "&populate[brochure][fields][0]=url" +
      "&populate[location][fields][0]=name&populate[location][fields][1]=slug&populate[location][fields][2]=priority" +
      "&populate[types][fields][0]=slug&populate[types][fields][1]=name" +
      "&populate[projects][populate][cardImage][fields][0]=url&populate[projects][populate][cardImage][fields][1]=alternativeText" +
      "&populate[projects][populate][cardHoverImage][fields][0]=url&populate[projects][populate][cardHoverImage][fields][1]=alternativeText" +
      "&populate[projects][populate][types][fields][0]=slug&populate[projects][populate][types][fields][1]=name" +
      "&populate[projects][fields][0]=title&populate[projects][fields][1]=propertyStatus&populate[projects][fields][2]=slug";

    let response = await safeAxiosGet(`${url}&${populate}`);
    if (!response?.data) {
      console.error(
        `[Strapi API] Invalid response structure for property by slug:`,
        {
          url: url.substring(0, 200),
          type,
          slug,
          hasResponse: !!response,
          hasData: !!response?.data,
        }
      );
      return null;
    }

    let rows = response.data.data;
    if ((!Array.isArray(rows) || rows.length === 0) && type) {
      // Fallback: some entries still rely on legacy propertyType without `types` relation.
      const fallbackUrl = `${STRAPI_URL}/api/properties?${baseFilter}`;
      response = await safeAxiosGet(`${fallbackUrl}&${populate}`);
      rows = response?.data?.data;
    }

    if (response.data && Array.isArray(rows) && rows.length > 0) {
      const item = rows[0];
      const attrs = item.attributes || item;
      let featuresData = item.features || attrs.features || [];
      let amenitiesGridData = item.amenitiesGrid || attrs.amenitiesGrid || [];
      let amenitiesSliderData = item.amenitiesSlider || attrs.amenitiesSlider || [];
      let portfolioImagesData = item.portfolioImages || attrs.portfolioImages || [];
      const typesNorm = normalizeTypesFromStrapi(attrs.types ?? item.types);

      return {
        id: item.id,
        slug: item.slug || attrs.slug,
        title: item.title || attrs.title,
        ...(typesNorm.length > 0 && { types: typesNorm }),
        propertyType: getPrimaryTypeSlugForUrl(
          {
            types: typesNorm,
            propertyType: item.propertyType || attrs.propertyType,
          },
          type
        ),
        propertyStatus:
          item.propertyStatus || attrs.propertyStatus || "",
        displayOrder: item.displayOrder || attrs.displayOrder || 0,
        address: item.address || attrs.address,
        location: (item.location || attrs.location)
          ? {
              id: (item.location || attrs.location).id,
              name: (item.location || attrs.location).name || "",
              slug: (item.location || attrs.location).slug || "",
              priority: (item.location || attrs.location).priority || 0,
              mapImage: normalizeImage((item.location || attrs.location).mapImage),
            }
          : undefined,
        unitSize: item.unitSize || attrs.unitSize,
        description: item.description || attrs.description,
        brochure: normalizeImage(item.brochure || attrs.brochure),
        heroBackgroundImageDesktop: normalizeImage(
          item.heroBackgroundImageDesktop || attrs.heroBackgroundImageDesktop
        ),
        heroBackgroundImageMobile: normalizeImage(
          item.heroBackgroundImageMobile || attrs.heroBackgroundImageMobile
        ),
        features: Array.isArray(featuresData)
          ? featuresData.map((feat: any) => {
              // Handle both flat and nested component structures
              const featData = feat.attributes || feat;
              const iconData = featData.icon || feat.icon;
              return {
                icon: normalizeImage(iconData),
                label: featData.label || feat.label || "",
                value: featData.value || feat.value || "",
              };
            })
          : [],
        showcaseVideo: typeof item.showcaseVideo === "string" 
          ? item.showcaseVideo 
          : normalizeImage(item.showcaseVideo),
        amenitiesTitle: item.amenitiesTitle || attrs.amenitiesTitle,
        amenitiesDescription: item.amenitiesDescription || attrs.amenitiesDescription,
        amenitiesSlider: Array.isArray(amenitiesSliderData)
          ? amenitiesSliderData.map((item: any) => {
              const itemData = item.attributes || item;
              return {
                image: normalizeImage(itemData.image || item.image),
                title: itemData.title || item.title || "",
              };
            })
          : [],
        amenitiesGrid: Array.isArray(amenitiesGridData)
          ? amenitiesGridData.map((item: any) => {
              const itemData = item.attributes || item;
              return {
                icon: normalizeImage(itemData.icon || item.icon),
                value: itemData.value || item.value || "",
              };
            })
          : [],
        locationPinLat: item.locationPinLat || attrs.locationPinLat,
        locationPinLng: item.locationPinLng || attrs.locationPinLng,
        portfolioImages: Array.isArray(portfolioImagesData)
          ? portfolioImagesData.map((img: any) => {
              const imgData = img.attributes || img;
              return {
                image: normalizeImage(imgData.image || img.image),
              };
            })
          : [],
        cardImage: normalizeImage(item.cardImage || attrs.cardImage),
        cardHoverImage: normalizeImage(item.cardHoverImage),
        projectsWithin: (() => {
          const raw =
            item.projects?.data ?? item.projects ?? item.attributes?.projects?.data ?? item.attributes?.projects ?? [];
          const list = Array.isArray(raw) ? raw : [];
          return list
            .map((p: any) => {
              const attrs = p?.attributes ?? p;
              if (!attrs?.title && !attrs?.slug) return null;
              const cardImg = normalizeImage(attrs.cardImage);
              const cardHover = normalizeImage(attrs.cardHoverImage);
              const imgUrl = cardImg?.url ? getStrapiImageUrl(cardImg.url) : "";
              const hoverUrl = cardHover?.url ? getStrapiImageUrl(cardHover.url) : undefined;
              const status = (attrs.propertyStatus || "upcoming") as string;
              const statusLabel =
                status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
              const projTypes = normalizeTypesFromStrapi(attrs.types);
              return {
                image: imgUrl || "/images/buildings/featured-2.webp",
                hoverImage: hoverUrl,
                title: attrs.title || "",
                status: statusLabel,
                slug: attrs.slug,
                propertyType: getPrimaryTypeSlugForUrl({
                  types: projTypes,
                  propertyType: attrs.propertyType,
                }),
              } as PropertyProjectItem;
            })
            .filter(Boolean) as PropertyProjectItem[];
        })(),
      };
    }
    return null;
  } catch (error: any) {
    console.error("[Strapi API] Error fetching property by slug:", {
      type,
      slug,
      error: error?.message || error,
      code: error?.code,
      status: error?.response?.status,
      statusText: error?.response?.statusText,
      url: error?.config?.url?.substring(0, 200),
      isTimeout: error?.code === "ECONNABORTED",
      isConnectionReset: error?.code === "ECONNRESET",
      responseData: error?.response?.data,
    });
    return null;
  }
};

// Homepage interfaces and functions
export interface PropertyCategoryItem {
  id: number;
  title: string;
  mainImage?:
    | {
        url?: string;
        alternativeText?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  imageSequence?: Array<
    | {
        url?: string;
        alternativeText?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any
  >;
  link?: string;
  displayOrder?: number;
}

export interface ExcellenceSlideItem {
  id: number;
  number: string;
  title: string;
  description: string;
  image?:
    | {
        url?: string;
        alternativeText?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  url?: string;
  displayOrder?: number;
}

export interface LatestProjectItem {
  id: number;
  property?: PropertyData;
  image?:
    | {
        url?: string;
        alternativeText?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  displayOrder?: number;
}

export interface BusinessUnitItem {
  id: number;
  logo?:
    | {
        url?: string;
        alternativeText?: string;
        data?: {
          attributes: {
            url: string;
            alternativeText?: string;
          };
        };
      }
    | any;
  /** Absolute logo URL (set on server so client works in production without NEXT_PUBLIC_STRAPI_URL) */
  logoUrl?: string;
  companyName: string;
  altText?: string;
  link?: string;
  displayOrder?: number;
}

export interface HomePageData {
  hero?: {
    title?: string;
    mobileBackground?: any;
    mobileImage?: any;
    /** Optional tablet breakpoint image when added in Strapi */
    tabletImage?: any;
    /** Optional hero videos for mobile/tablet breakpoints */
    mobileVideo?: any;
    tabletVideo?: any;
    /** Optional generic video fields */
    video?: any;
    desktopVideo?: any;
  };
  brandCommitment?: {
    title?: string;
    mobileTitle?: string;
    description?: string;
    buttonText?: string;
    buttonLink?: string;
  };
  categories?: PropertyCategoryItem[];
  featuredPropertiesTitle?: string;
  featuredProperties?: FeaturedPropertyItem[];
  excellenceSlides?: ExcellenceSlideItem[];
  latestProjectsTitle?: string;
  latestProjects?: LatestProjectItem[];
  ourPresenceTitle?: string;
  businessUnitTitle?: string;
  businessUnits?: BusinessUnitItem[];
  seo?: SeoData;
}

const _fetchHomePage = async (): Promise<HomePageData | null> => {
  try {
    if (!STRAPI_URL) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return null;
    }

    // Try different populate syntaxes for Strapi v5
    let response;
    let lastError: any = null;

    // Try 1: Deep populate syntax
    try {
      response = await axios.get(`${STRAPI_URL}/api/home-page?populate=deep`);
    } catch (err1: any) {
      lastError = err1;
      // Try 2: Strapi v5 rejects populate[categories] - removed. Categories from populate=* fallback.
      try {
        response = await axios.get(
          `${STRAPI_URL}/api/home-page?` +
            "populate[hero][populate]=*" +
            "&populate[brandCommitment]=*" +
            "&populate[featuredProperties][populate][property][fields][0]=title" +
            "&populate[featuredProperties][populate][property][fields][1]=slug" +
            "&populate[featuredProperties][populate][property][fields][2]=address" +
            "&populate[featuredProperties][populate][property][populate][types][fields][0]=slug" +
            "&populate[featuredProperties][populate][property][populate][types][fields][1]=name" +
            "&populate[featuredProperties][populate][property][populate][location][fields][0]=name" +
            "&populate[featuredProperties][populate][property][populate][location][fields][1]=slug" +
            "&populate[featuredProperties][populate][property][populate][cardImage][fields][0]=url" +
            "&populate[featuredProperties][populate][property][populate][cardImage][fields][1]=alternativeText" +
            "&populate[featuredProperties][populate][property][populate][cardHoverImage][fields][0]=url" +
            "&populate[featuredProperties][populate][property][populate][cardHoverImage][fields][1]=alternativeText" +
            "&populate[excellenceSlides][populate][image][fields][0]=url" +
            "&populate[excellenceSlides][populate][image][fields][1]=alternativeText" +
            "&populate[SEO][fields][0]=title" +
            "&populate[SEO][fields][1]=description" +
            "&populate[SEO][fields][2]=keywords" +
            "&populate[SEO][populate][image][fields][0]=url" +
            "&populate[SEO][populate][image][fields][1]=alternativeText" +
            "&populate[businessUnits][populate][logo][fields][0]=url" +
            "&populate[businessUnits][populate][logo][fields][1]=alternativeText"
        );
      } catch (err2: any) {
        lastError = err2;
        // Try 3: Simple populate
        try {
          response = await axios.get(`${STRAPI_URL}/api/home-page?populate=*`);
        } catch (err3: any) {
          lastError = err3;
          // Try 4: Without populate (fallback)
          try {
            response = await axios.get(`${STRAPI_URL}/api/home-page`);
          } catch (err4: any) {
            throw err4;
          }
        }
      }
    }

    if (response?.data?.data) {
      // Strapi v5 single-type nests fields under attributes
      const raw = response.data.data;
      let data = raw?.attributes ?? raw;

      // Try 2 omits categories; fetch them if missing
      if ((!data.categories || data.categories.length === 0) && STRAPI_URL) {
        try {
          const catRes = await axios.get(`${STRAPI_URL}/api/home-page?populate=*`);
          const catRaw = catRes?.data?.data;
          const catData = catRaw?.attributes ?? catRaw;
          if (catData?.categories?.length) {
            data = { ...data, categories: catData.categories };
          }
        } catch (_) {
          /* keep data as-is */
        }
      }

      // Normalize categories
      const normalizeCategories = (items: any[]): PropertyCategoryItem[] => {
        if (!Array.isArray(items)) return [];
        return items
          .map((item: any, index: number) => ({
            id: item.id || index + 1,
            title: item.title || "",
            mainImage: normalizeImage(item.mainImage),
            imageSequence: Array.isArray(item.imageSequence)
              ? item.imageSequence.map((img: any) => normalizeImage(img))
              : [],
            link: item.link || "",
            displayOrder: item.displayOrder ?? index,
          }))
          .sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0));
      };

      // Normalize excellence slides
      const normalizeExcellenceSlides = (
        items: any[]
      ): ExcellenceSlideItem[] => {
        if (!Array.isArray(items)) return [];
        return items
          .map((item: any, index: number) => ({
            id: item.id || index + 1,
            number: item.number || "",
            title: item.title || "",
            description: item.description || "",
            image: normalizeImage(item.image),
            url: item.url || "",
            displayOrder: item.displayOrder ?? index,
          }))
          .sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0));
      };

      // Normalize latest projects
      const normalizeLatestProjects = (items: any[]): LatestProjectItem[] => {
        if (!Array.isArray(items)) return [];
        return items
          .map((item: any, index: number) => {
            const propertyData =
              item.property?.data?.attributes ||
              item.property?.attributes ||
              item.property;

            const latestTypes = normalizeTypesFromStrapi(propertyData.types);
            const property = propertyData
              ? {
                  id:
                    propertyData.id ||
                    item.property?.id ||
                    item.property?.data?.id,
                  slug: propertyData.slug || "",
                  title: propertyData.title || "",
                  ...(latestTypes.length > 0 && { types: latestTypes }),
                  propertyType: getPrimaryTypeSlugForUrl({
                    types: latestTypes,
                    propertyType: propertyData.propertyType,
                  }),
                  propertyStatus: propertyData.propertyStatus || "",
                  address: propertyData.address || "",
                  description: propertyData.description || "",
                  location: propertyData.location?.data?.attributes
                    ? {
                        id: propertyData.location.data.id,
                        name: propertyData.location.data.attributes.name || "",
                        slug: propertyData.location.data.attributes.slug || "",
                        priority:
                          propertyData.location.data.attributes.priority || 0,
                      }
                    : propertyData.location?.attributes
                    ? {
                        id: propertyData.location.id,
                        name: propertyData.location.attributes.name || "",
                        slug: propertyData.location.attributes.slug || "",
                        priority:
                          propertyData.location.attributes.priority || 0,
                      }
                    : propertyData.location
                    ? {
                        id: propertyData.location.id,
                        name: propertyData.location.name || "",
                        slug: propertyData.location.slug || "",
                        priority: propertyData.location.priority || 0,
                      }
                    : undefined,
                }
              : undefined;

            return {
              id: item.id || index + 1,
              property,
              image: normalizeImage(item.image),
              displayOrder: item.displayOrder ?? index,
            };
          })
          .sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0));
      };

      // Normalize business units: support Strapi v5 relation shape and set absolute logoUrl on server
      const toFullUrl = (rel: string | undefined) => {
        if (!rel || typeof rel !== "string") return undefined;
        if (rel.startsWith("http://") || rel.startsWith("https://")) return rel;
        const base = STRAPI_URL || "";
        return base ? `${base}${rel.startsWith("/") ? rel : `/${rel}`}` : rel;
      };
      const normalizeBusinessUnits = (items: any[]): BusinessUnitItem[] => {
        if (!Array.isArray(items)) return [];
        return items
          .map((item: any, index: number) => {
            const unit = item?.attributes ?? item;
            const logoNorm = normalizeImage(unit?.logo);
            const logoRel = logoNorm?.url || logoNorm?.data?.attributes?.url;
            return {
              id: item?.id || unit?.id || index + 1,
              logo: logoNorm,
              logoUrl: toFullUrl(logoRel) || undefined,
              companyName: unit?.companyName || "",
              altText: unit?.altText || unit?.companyName || "",
              link: unit?.link || "",
              displayOrder: unit?.displayOrder ?? index,
            };
          })
          .sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0));
      };

      // Fallback: populate=* does NOT populate property inside featuredProperties.
      // If we have slots but no property data, fetch featuredProperties separately.
      let rawFeaturedProps = data.featuredProperties?.data ?? data.featuredProperties ?? [];
      const hasPropertyData = Array.isArray(rawFeaturedProps) && rawFeaturedProps.some(
        (it: any) => it?.property != null && (it.property?.title != null || it.property?.slug != null)
      );
      if (Array.isArray(rawFeaturedProps) && rawFeaturedProps.length > 0 && !hasPropertyData) {
        try {
          const fpRes = await axios.get(
            `${STRAPI_URL}/api/home-page?populate[featuredProperties][populate][property][fields][0]=title&populate[featuredProperties][populate][property][fields][1]=slug&populate[featuredProperties][populate][property][fields][2]=address&populate[featuredProperties][populate][property][populate][types][fields][0]=slug&populate[featuredProperties][populate][property][populate][types][fields][1]=name&populate[featuredProperties][populate][property][populate][location][fields][0]=name&populate[featuredProperties][populate][property][populate][location][fields][1]=slug&populate[featuredProperties][populate][property][populate][cardImage][fields][0]=url&populate[featuredProperties][populate][property][populate][cardHoverImage][fields][0]=url`
          );
          const fpData = fpRes?.data?.data;
          const fpAttrs = fpData?.attributes ?? fpData;
          if (fpAttrs?.featuredProperties) {
            rawFeaturedProps = fpAttrs.featuredProperties?.data ?? fpAttrs.featuredProperties ?? rawFeaturedProps;
          }
        } catch (_) {
          // Keep original if fallback fails
        }
      }

      // Fallback: populate=* does NOT populate image inside excellenceSlides.
      let rawExcellenceSlides = data.excellenceSlides || [];
      const hasExcellenceImages = Array.isArray(rawExcellenceSlides) && rawExcellenceSlides.some(
        (it: any) => {
          const img = it?.image;
          return img && (img.url || img.data?.attributes?.url);
        }
      );
      if (Array.isArray(rawExcellenceSlides) && rawExcellenceSlides.length > 0 && !hasExcellenceImages) {
        try {
          const esRes = await axios.get(
            `${STRAPI_URL}/api/home-page?populate[excellenceSlides][populate][image][fields][0]=url&populate[excellenceSlides][populate][image][fields][1]=alternativeText`
          );
          const esData = esRes?.data?.data;
          const esAttrs = esData?.attributes ?? esData;
          if (esAttrs?.excellenceSlides?.length) {
            rawExcellenceSlides = esAttrs.excellenceSlides;
          }
        } catch (_) {
          // Keep original if fallback fails
        }
      }

      // Business units: Strapi v5 can return relation as { data: [...] }; also fetch logos if missing
      let rawBusinessUnits = data.businessUnits?.data ?? data.businessUnits ?? [];
      if (!Array.isArray(rawBusinessUnits)) rawBusinessUnits = [];
      const hasBusinessLogos = rawBusinessUnits.some(
        (it: any) => {
          const logo = it?.logo;
          return logo && (logo.url || logo.data?.attributes?.url);
        }
      );
      if (rawBusinessUnits.length > 0 && !hasBusinessLogos) {
        try {
          const buRes = await axios.get(
            `${STRAPI_URL}/api/home-page?populate[businessUnits][populate][logo][fields][0]=url&populate[businessUnits][populate][logo][fields][1]=alternativeText`
          );
          const buData = buRes?.data?.data;
          const buAttrs = buData?.attributes ?? buData;
          const buList = buAttrs?.businessUnits?.data ?? buAttrs?.businessUnits ?? rawBusinessUnits;
          if (Array.isArray(buList) && buList.length > 0) rawBusinessUnits = buList;
        } catch (_) {
          // Keep original if fallback fails
        }
      }

      const seo = normalizeSeoFromData(data);
      const hero = data.hero ?? data.Hero;
      const brandCommitment = data.brandCommitment;

      return {
        ...(seo && { seo }),
        hero,
        brandCommitment,
        categories: normalizeCategories(data.categories || []),
        featuredPropertiesTitle: data.featuredPropertiesTitle,
        featuredProperties: normalizeFeaturedProperties(rawFeaturedProps),
        excellenceSlides: normalizeExcellenceSlides(rawExcellenceSlides),
        latestProjectsTitle: data.latestProjectsTitle,
        latestProjects: normalizeLatestProjects(data.latestProjects || []),
        ourPresenceTitle: data.ourPresenceTitle,
        businessUnitTitle: data.businessUnitTitle,
        businessUnits: normalizeBusinessUnits(rawBusinessUnits),
      };
    }
    return null;
  } catch (error: any) {
    console.error("Error fetching home page data:", error);
    if (error.response) {
      console.error("Response status:", error.response.status);
      console.error("Response data:", error.response.data);
      console.error("Request URL:", error.config?.url);
    }
    return null;
  }
};

const fetchHomePageCached = unstable_cache(
  async () => _fetchHomePage(),
  ["home-page"],
  {
    tags: ["home-page"],
    revalidate: false, // No time-based revalidation - relies on on-demand revalidation from Strapi
  }
);

/** Fresh Strapi data in dev; cached in production unless tag `home-page` is revalidated. */
export const fetchHomePage = async (): Promise<HomePageData | null> => {
  const bypassDevCache = process.env.NODE_ENV !== "production";
  if (bypassDevCache) {
    return _fetchHomePage();
  }
  return fetchHomePageCached();
};

// Map property interface - minimal data needed for map markers
export interface MapProperty {
  id: number;
  /** Strapi 5 document id — prefer for stable React keys */
  documentId?: string;
  title: string;
  slug: string;
  types?: PropertyTypePageRef[];
  propertyType?: string;
  locationPinLat?: number;
  locationPinLng?: number;
  cardImage?: {
    url?: string;
    data?: {
      attributes: {
        url: string;
        alternativeText?: string;
      };
    };
  };
}

const parseMapCoordinate = (value: unknown): number | null => {
  if (value == null) return null;
  if (typeof value === "number") {
    return Number.isFinite(value) ? value : null;
  }
  if (typeof value === "string") {
    const t = value.trim();
    if (!t) return null;
    const n = parseFloat(t.replace(",", "."));
    return Number.isFinite(n) ? n : null;
  }
  return null;
};

interface FetchPropertiesForMapOptions {
  strapiUrl: string;
  authConfig?: Record<string, unknown>;
}

const MAP_PROPERTIES_PAGE_LIMIT = 250;

/** Strapi v5 Draft & Publish: published documents only (`publicationState` was removed in v5). */
const MAP_PROPERTIES_PUBLISH_PARAMS = `status=published`;

type MapPropertiesQueryTier = "optimal" | "constrained" | "minimal";

const buildPropertiesForMapQuery = (
  strapiUrl: string,
  tier: MapPropertiesQueryTier,
  start: number,
  limit: number
): string => {
  const pagination = `pagination[start]=${start}&pagination[limit]=${limit}`;
  const base = `${strapiUrl}/api/properties?${MAP_PROPERTIES_PUBLISH_PARAMS}&${pagination}`;

  const fieldsAndCoords =
    `&fields[0]=title&fields[1]=slug&fields[2]=propertyType&fields[3]=locationPinLat&fields[4]=locationPinLng`;

  switch (tier) {
    case "optimal":
      return (
        base +
        fieldsAndCoords +
        `&populate[cardImage][fields][0]=url&populate[cardImage][fields][1]=alternativeText` +
        `&populate[cardImage][populate][formats][fields][0]=url` +
        `&populate[types][fields][0]=slug&populate[types][fields][1]=name`
      );
    case "constrained":
      return (
        base +
        fieldsAndCoords +
        `&populate[cardImage][fields][0]=url&populate[types][fields][0]=slug&populate[types][fields][1]=name`
      );
    case "minimal":
      return base;
  }
};

const mapStrapiRowToMapProperty = (item: any, base: string): MapProperty | null => {
  const attributes = item.attributes || item;

  const locationPinLat = parseMapCoordinate(
    attributes.locationPinLat ?? item.locationPinLat
  );
  const locationPinLng = parseMapCoordinate(
    attributes.locationPinLng ?? item.locationPinLng
  );
  if (locationPinLat == null || locationPinLng == null) return null;

  let cardImageUrl: string | null = null;
  const rawCardImage = attributes.cardImage;
  if (rawCardImage) {
    const imageData =
      rawCardImage.data?.attributes ??
      rawCardImage.attributes ??
      rawCardImage;
    const raw =
      (imageData?.url && typeof imageData.url === "string"
        ? imageData.url
        : null) ||
      (imageData?.formats?.medium?.url &&
      typeof imageData.formats.medium.url === "string"
        ? imageData.formats.medium.url
        : null) ||
      (imageData?.formats?.small?.url &&
      typeof imageData.formats.small.url === "string"
        ? imageData.formats.small.url
        : null) ||
      (imageData?.formats?.thumbnail?.url &&
      typeof imageData.formats.thumbnail.url === "string"
        ? imageData.formats.thumbnail.url
        : null) ||
      (typeof rawCardImage.url === "string" ? rawCardImage.url : null);
    if (raw) {
      cardImageUrl = raw.startsWith("http")
        ? raw
        : `${base}${raw.startsWith("/") ? raw : `/${raw}`}`;
    }
  }

  const mapTypes = normalizeTypesFromStrapi(attributes.types);
  return {
    id: item.id,
    ...(typeof item.documentId === "string" && item.documentId
      ? { documentId: item.documentId }
      : {}),
    title: attributes.title || "",
    slug: attributes.slug || "",
    ...(mapTypes.length > 0 && { types: mapTypes }),
    propertyType: getPrimaryTypeSlugForUrl({
      types: mapTypes,
      propertyType: attributes.propertyType,
    }),
    locationPinLat,
    locationPinLng,
    cardImage: cardImageUrl
      ? {
          url: cardImageUrl,
          data: {
            attributes: {
              url: cardImageUrl,
              alternativeText:
                attributes.cardImage?.data?.attributes?.alternativeText,
            },
          },
        }
      : undefined,
  };
};

const _fetchPropertiesForMapBase = async ({
  strapiUrl,
  authConfig,
}: FetchPropertiesForMapOptions): Promise<MapProperty[]> => {
  const baseAxiosConfig = {
    timeout: 25000,
    ...(authConfig || {}),
  };

  const base = strapiUrl.replace(/\/$/, "");

  const tiers: MapPropertiesQueryTier[] = ["optimal", "constrained", "minimal"];
  let workingTier: MapPropertiesQueryTier | null = null;

  for (const tier of tiers) {
    try {
      const probeUrl = buildPropertiesForMapQuery(strapiUrl, tier, 0, 1);
      const probe = await axios.get(probeUrl, baseAxiosConfig);
      if (Array.isArray(probe?.data?.data)) {
        workingTier = tier;
        break;
      }
    } catch (err: any) {
      if (err.code === "ECONNABORTED" || err.code === 23) {
        console.error("Request timeout while fetching properties for map (probe)");
        return [];
      }
      continue;
    }
  }

  if (!workingTier) {
    console.error("Could not reach Strapi for map properties (all query variants failed)");
    return [];
  }

  const rawRows: any[] = [];
  let start = 0;

  try {
    for (;;) {
      const url = buildPropertiesForMapQuery(
        strapiUrl,
        workingTier,
        start,
        MAP_PROPERTIES_PAGE_LIMIT
      );
      const response = await axios.get(url, baseAxiosConfig);
      const batch = response?.data?.data;
      if (!Array.isArray(batch) || batch.length === 0) break;
      rawRows.push(...batch);
      if (batch.length < MAP_PROPERTIES_PAGE_LIMIT) break;
      start += MAP_PROPERTIES_PAGE_LIMIT;
      if (start > 200000) break;
    }
  } catch (err: any) {
    if (err.code === "ECONNABORTED" || err.code === 23) {
      console.error("Request timeout while fetching properties for map (paged)");
    } else {
      console.error("Error fetching properties for map:", err);
    }
    return [];
  }

  const seen = new Set<string>();
  const properties: MapProperty[] = [];
  for (const item of rawRows) {
    const dedupeKey =
      typeof item?.documentId === "string" && item.documentId
        ? `d:${item.documentId}`
        : `i:${item?.id}`;
    if (seen.has(dedupeKey)) continue;
    seen.add(dedupeKey);

    const mapped = mapStrapiRowToMapProperty(item, base);
    if (mapped) properties.push(mapped);
  }

  return properties;
};

/**
 * Fetch all properties for map display
 * Only fetches minimal fields needed: title, slug, types, locationPinLat, locationPinLng, cardImage
 * Note: This function is called from client components, so it cannot use unstable_cache
 */
export const fetchPropertiesForMap = async (): Promise<MapProperty[]> => {
  try {
    // Get STRAPI_URL at runtime for client components
    const strapiUrl = process.env.NEXT_PUBLIC_STRAPI_URL || STRAPI_URL;
    
    if (!strapiUrl) {
      console.error("STRAPI_URL is not set. Please set NEXT_PUBLIC_STRAPI_URL environment variable.");
      return [];
    }

    return _fetchPropertiesForMapBase({ strapiUrl });
  } catch (error: any) {
    console.error("Error fetching properties for map:", error);
    return [];
  }
};

const fetchPropertiesForMapServerCached = unstable_cache(
  async () => {
    return _fetchPropertiesForMapBase({
      strapiUrl: STRAPI_URL,
      authConfig: getStrapiAuthConfig(),
    });
  },
  ["properties-map-pagination-v1"],
  {
    tags: ["properties"],
    revalidate: false,
  }
);

export const fetchPropertiesForMapServer = async (): Promise<MapProperty[]> => {
  const bypassDevCache = process.env.NODE_ENV !== "production";
  if (bypassDevCache) {
    return _fetchPropertiesForMapBase({
      strapiUrl: STRAPI_URL,
      authConfig: getStrapiAuthConfig(),
    });
  }

  const cached = await fetchPropertiesForMapServerCached();
  if (Array.isArray(cached) && cached.length > 0) {
    return cached;
  }

  // If cache currently holds an empty snapshot (e.g. transient upstream timeout),
  // fetch fresh data so production map does not stay blank indefinitely.
  return _fetchPropertiesForMapBase({
    strapiUrl: STRAPI_URL,
    authConfig: getStrapiAuthConfig(),
  });
};

const getStrapiAuthConfig = () => {
  if (!STRAPI_API_TOKEN || !STRAPI_API_TOKEN.trim()) return {};
  return {
    headers: {
      Authorization: `Bearer ${STRAPI_API_TOKEN.trim()}`,
    },
  };
};

const toAbsoluteStrapiUrl = (rawUrl?: string | null): string | null => {
  if (!rawUrl || typeof rawUrl !== "string") return null;
  if (rawUrl.startsWith("http")) return rawUrl;
  const base = STRAPI_URL.replace(/\/$/, "");
  return `${base}${rawUrl.startsWith("/") ? rawUrl : `/${rawUrl}`}`;
};

const getBrochureUrlFromProperty = (property: any): string | null => {
  const attrs = property?.attributes ?? property;
  const brochure = attrs?.brochure;
  const rawUrl =
    brochure?.url ??
    brochure?.data?.attributes?.url ??
    brochure?.data?.url ??
    null;
  return toAbsoluteStrapiUrl(rawUrl);
};

export interface BrochureRequestRecord {
  id: number;
  email: string;
  phone: string;
  propertyName?: string;
  propertyAddress?: string;
  propertyType?: string | null;
  mailSentAt?: string;
  createdAt?: string;
}

const normalizeBrochureRequestRecord = (item: any): BrochureRequestRecord => {
  const attrs = item?.attributes ?? item ?? {};
  return {
    id: item?.id ?? attrs?.id ?? 0,
    email: attrs.email ?? "",
    phone: attrs.phone ?? "",
    propertyName: attrs.propertyName ?? "",
    propertyAddress: attrs.propertyAddress ?? "",
    propertyType: attrs.propertyType ?? null,
    mailSentAt: attrs.mailSentAt,
    createdAt: attrs.createdAt,
  };
};

export type BrochurePropertyType =
  | "residential"
  | "commercial"
  | "condominium"
  | "land";

const VALID_PROPERTY_TYPES: ReadonlySet<BrochurePropertyType> = new Set([
  "residential",
  "commercial",
  "condominium",
  "land",
]);

/** Title, address, area name, and property type for brochure lead (Strapi + sales email). */
export const fetchPropertyContextForBrochureLead = async (
  propertyKey: string
): Promise<{
  propertyKey: string;
  propertyName: string;
  propertyAddress: string;
  propertyType: BrochurePropertyType | null;
} | null> => {
  try {
    if (!propertyKey?.trim()) return null;
    const encodedKey = encodeURIComponent(propertyKey.trim());
    const populate =
      "&populate[location][fields][0]=name&populate[location][fields][1]=slug";
    const fields =
      "&fields[0]=title&fields[1]=address&fields[2]=slug&fields[3]=propertyType";
    const base = `${STRAPI_URL}/api/properties?publicationState=live&pagination[limit]=1${fields}${populate}`;

    const fetchOne = async (filter: string) => {
      const resp = await axios.get(`${base}&${filter}`, getStrapiAuthConfig());
      return resp.data?.data?.[0];
    };

    let item = await fetchOne(`filters[slug][$eq]=${encodedKey}`);
    if (!item) item = await fetchOne(`filters[title][$eq]=${encodedKey}`);
    if (!item) return null;

    const attrs = item?.attributes ?? item ?? {};
    const title = String(attrs.title ?? "").trim();
    const address = String(attrs.address ?? "").trim();
    const rawPropertyType = String(attrs.propertyType ?? "").trim();
    const propertyType: BrochurePropertyType | null = VALID_PROPERTY_TYPES.has(
      rawPropertyType as BrochurePropertyType
    )
      ? (rawPropertyType as BrochurePropertyType)
      : null;

    const locRaw = attrs.location ?? item?.location;
    let locationName = "";
    if (locRaw) {
      const nested =
        locRaw.data?.attributes?.name ??
        locRaw.data?.name ??
        locRaw.attributes?.name ??
        locRaw.name ??
        "";
      locationName = String(nested).trim();
    }

    const addressParts = [address, locationName].filter(Boolean);
    const propertyAddress =
      addressParts.length > 0 ? addressParts.join(", ") : "Not specified";

    return {
      propertyKey: propertyKey.trim(),
      propertyName: title || propertyKey.trim(),
      propertyAddress,
      propertyType,
    };
  } catch (error) {
    console.error("Error fetching property context for brochure lead:", error);
    return null;
  }
};

export const fetchBrochureUrlForPropertyKey = async (
  propertyKey: string
): Promise<string | null> => {
  try {
    if (!propertyKey?.trim()) return null;
    const encodedKey = encodeURIComponent(propertyKey.trim());
    const base =
      `${STRAPI_URL}/api/properties?publicationState=live&pagination[limit]=1` +
      "&populate[brochure][fields][0]=url&populate[brochure][fields][1]=alternativeText";

    const slugUrl = `${base}&filters[slug][$eq]=${encodedKey}`;
    const slugResp = await axios.get(slugUrl, getStrapiAuthConfig());
    const slugMatch = slugResp.data?.data?.[0];
    const slugBrochure = getBrochureUrlFromProperty(slugMatch);
    if (slugBrochure) return slugBrochure;

    const titleUrl = `${base}&filters[title][$eq]=${encodedKey}`;
    const titleResp = await axios.get(titleUrl, getStrapiAuthConfig());
    const titleMatch = titleResp.data?.data?.[0];
    return getBrochureUrlFromProperty(titleMatch);
  } catch (error) {
    console.error("Error fetching brochure URL by property key:", error);
    return null;
  }
};

/**
 * Find an existing brochure request that would collide with the given lead.
 * Matches schema-level unique constraints (email OR phone), so the API layer
 * silently dedupes before Strapi can reject the insert with a 400.
 */
export const fetchLatestBrochureRequest = async ({
  email,
  phone,
}: {
  email: string;
  phone: string;
}): Promise<BrochureRequestRecord | null> => {
  try {
    const query =
      `filters[$or][0][email][$eq]=${encodeURIComponent(email)}` +
      `&filters[$or][1][phone][$eq]=${encodeURIComponent(phone)}` +
      "&sort[0]=createdAt:desc&pagination[limit]=1";
    const response = await axios.get(
      `${STRAPI_URL}/api/brochure-requests?${query}`,
      getStrapiAuthConfig()
    );
    const entry = response.data?.data?.[0];
    return entry ? normalizeBrochureRequestRecord(entry) : null;
  } catch (error) {
    if (isAxios404(error)) {
      console.warn(
        "Strapi collection `brochure-requests` not found. Dedupe is disabled until it is created."
      );
      return null;
    }
    console.error("Error fetching latest brochure request:", error);
    return null;
  }
};

export const createBrochureRequest = async (data: {
  email: string;
  phone: string;
  propertyName: string;
  propertyAddress: string;
  propertyType?: BrochurePropertyType | null;
}): Promise<BrochureRequestRecord | null> => {
  try {
    const payload: Record<string, unknown> = {
      email: data.email,
      phone: data.phone,
      propertyName: data.propertyName,
      propertyAddress: data.propertyAddress,
    };
    if (data.propertyType) {
      payload.propertyType = data.propertyType;
    }
    const response = await axios.post(
      `${STRAPI_URL}/api/brochure-requests`,
      { data: payload },
      getStrapiAuthConfig()
    );
    const entry = response.data?.data;
    return entry ? normalizeBrochureRequestRecord(entry) : null;
  } catch (error) {
    if (isAxios404(error)) {
      console.warn(
        "Strapi collection `brochure-requests` not found. Cannot save brochure request."
      );
      return null;
    }
    console.error("Error creating brochure request:", error);
    return null;
  }
};

export const updateBrochureRequest = async (
  id: number,
  data: Partial<Pick<BrochureRequestRecord, "mailSentAt">>
): Promise<boolean> => {
  try {
    await axios.put(
      `${STRAPI_URL}/api/brochure-requests/${id}`,
      { data },
      getStrapiAuthConfig()
    );
    return true;
  } catch (error) {
    if (isAxios404(error)) {
      console.warn(
        "Strapi collection `brochure-requests` not found. Skipping mail-sent update."
      );
      return false;
    }
    console.error("Error updating brochure request:", error);
    return false;
  }
};
