/**
 * meshkee.com public site API — see https://api.meshkee.com/docs/website
 *
 * Tenants are resolved by apex domain (`raoofigroup.com`), then public data
 * lives under `/tenants/{domain}/...` with no auth required.
 *
 * Note on categories: the tenant-level `/categories` endpoint exists but is
 * unrelated to portfolios in practice (it returns an empty product-category
 * list even though every portfolio item already carries its own categoryId/
 * categoryName). Category tabs are therefore derived directly from the
 * portfolio items themselves — the moment the client assigns real categories
 * to projects in their CMS, the filter picks them up with no code change.
 */

const API_BASE = "https://api.meshkee.com/api/v1";
const SITE_DOMAIN = "raoofigroup.com";

/** How long fetched portfolio data is cached before Next.js revalidates it. */
const REVALIDATE_SECONDS = 3600;

/**
 * api.meshkee.com has been observed to hang for minutes at a time rather than
 * error out quickly. Without a bound, a single slow request can stall page
 * generation for as long as the upstream host allows — a real, repeatable
 * failure mode, not a hypothetical one. Failing fast here at least turns that
 * into a clean error instead of a multi-minute hang.
 */
const REQUEST_TIMEOUT_MS = 10_000;

export type ApiPortfolioItem = {
  id: string;
  businessId: string;
  title: string;
  titleFa: string;
  titleEn: string;
  slug: string;
  abstract: string;
  mainTextHtml: string;
  status: string;
  categoryId: string;
  categoryName: string;
  tags: string[];
  titleImageUrl: string | null;
  featuredMediaId: string | null;
  gallery: { mediaId: string; url: string }[];
  galleryMediaIds: string[];
  sortOrder: number;
  publishedAt: string;
  createdAt: string;
  updatedAt: string;
};

type PortfoliosResponse = {
  items: ApiPortfolioItem[];
  total: number;
  page: number;
  pageSize: number;
};

async function apiGet<T>(path: string): Promise<T> {
  let res: Response;
  try {
    res = await fetch(`${API_BASE}${path}`, {
      headers: { Accept: "application/json" },
      next: { revalidate: REVALIDATE_SECONDS },
      signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
    });
  } catch (cause) {
    throw new Error(`meshkee API ${path} timed out or was unreachable`, { cause });
  }

  if (!res.ok) {
    throw new Error(`meshkee API ${path} responded ${res.status}`);
  }

  return res.json() as Promise<T>;
}

/** All published portfolio items, every page merged, in the CMS's own order. */
export async function getPortfolios(): Promise<ApiPortfolioItem[]> {
  const first = await apiGet<PortfoliosResponse>(`/tenants/${SITE_DOMAIN}/portfolios`);
  const pageCount = Math.ceil(first.total / first.pageSize);

  const rest = await Promise.all(
    Array.from({ length: Math.max(0, pageCount - 1) }, (_, i) =>
      apiGet<PortfoliosResponse>(`/tenants/${SITE_DOMAIN}/portfolios?page=${i + 2}`),
    ),
  );

  return [first, ...rest]
    .flatMap((page) => page.items)
    .filter((item) => item.status === "published")
    .sort((a, b) => a.sortOrder - b.sortOrder);
}

export async function getPortfolioBySlug(slug: string): Promise<ApiPortfolioItem | null> {
  const items = await getPortfolios();
  return items.find((item) => item.slug === slug) ?? null;
}

export type PortfolioCategory = { id: string; name: string; count: number };

/** Distinct categories actually present on published portfolio items. */
export function deriveCategories(items: ApiPortfolioItem[]): PortfolioCategory[] {
  const byId = new Map<string, PortfolioCategory>();

  for (const item of items) {
    const existing = byId.get(item.categoryId);
    if (existing) {
      existing.count += 1;
    } else {
      byId.set(item.categoryId, { id: item.categoryId, name: item.categoryName, count: 1 });
    }
  }

  return [...byId.values()];
}

/* ---------------------------------------------------------------------------
 * Display adapter — the shape every gallery/card component in the UI expects,
 * independent of the raw API response.
 * ------------------------------------------------------------------------- */

export type DisplayProject = {
  slug: string;
  name: string;
  nameLatin: string;
  type: string;
  categoryId: string;
  image: string;
  gallery: string[];
  bodyHtml: string;
  publishedAt: string;
};

const FALLBACK_IMAGE =
  "https://meshkee-storage.sas.amin.parminstorage.ir/meshkee/businesses/9/media/9582b492-970a-4165-b893-7c36ff3d5b4f.jpg";

/**
 * Some portfolio bodies carry a broken video-embed paragraph: whoever pasted
 * an Aparat/iframe embed into the CMS's rich text editor ended up with the
 * markup HTML-escaped (`&lt;script&gt;` instead of a real `<script>` tag), so
 * it renders as visible garbled text instead of a video. React's
 * dangerouslySetInnerHTML wouldn't execute a real <script> tag either way, so
 * there's nothing worth recovering here — the paragraph is just dropped.
 * (Real videos now go through ProjectVideo / projectVideos instead.)
 */
function stripBrokenEmbedArtifacts(html: string): string {
  return html.replace(
    /<p[^>]*>(?:(?!<\/p>)[\s\S])*&lt;(?:script|iframe)\b(?:(?!<\/p>)[\s\S])*<\/p>\s*/gi,
    "",
  );
}

export function toDisplayProject(item: ApiPortfolioItem): DisplayProject {
  return {
    slug: item.slug,
    name: item.titleFa || item.title,
    nameLatin: item.titleEn || item.titleFa || item.title,
    type: item.categoryName,
    categoryId: item.categoryId,
    image: item.titleImageUrl ?? FALLBACK_IMAGE,
    gallery: item.gallery.map((g) => g.url),
    bodyHtml: stripBrokenEmbedArtifacts(item.mainTextHtml),
    publishedAt: item.publishedAt,
  };
}
