import type { PostEntry } from "./cms";
import {
  getPosts,
  getPostBySlug,
  getTags,
  getTextFragmentBySlug,
  getCalendarBySlug,
  getCalendarItemBySlug,
} from "./cms";
import type { RowContentLayout } from "./block-types";
import type { CalendarItemData } from "./block-types";
/** rusty-image nur dynamisch importieren, damit client-seitige Importe (z. B. PostOverviewBlock) nicht node:crypto in den Browser ziehen. */

/** Aus der Tag-API: Anzeigename plus optionales Icon und Farbe (CMS-Felder icon / color). */
export type TagMeta = {
  name: string;
  icon?: string;
  color?: string;
};

/** Slug → TagMeta aus der Tag-API. Für Auflösung von post.postTag. */
export async function getTagsMap(): Promise<Map<string, TagMeta>> {
  const tags = await getTags();
  const m = new Map<string, TagMeta>();
  for (const t of tags) {
    const slug =
      (t as { _slug?: string })._slug ?? (t as { slug?: string }).slug ?? "";
    if (!slug) continue;
    const name = (t as { name?: string }).name?.trim() ?? slug;
    const icon = (t as { icon?: string }).icon?.trim();
    const color = (t as { color?: string }).color?.trim();
    m.set(slug, {
      name,
      ...(icon ? { icon } : {}),
      ...(color ? { color } : {}),
    });
  }
  return m;
}

export type ResolvedPostTag = {
  _slug: string;
  name: string;
  icon?: string;
  color?: string;
};

/** Setzt post.postTag auf { _slug, name, icon?, color? }[]; Metadaten aus slugToMeta bzw. bereits aufgelösten Refs. */
export function resolvePostTagsInPost(
  post: PostEntry,
  slugToMeta: Map<string, TagMeta>,
): void {
  const raw = post.postTag ?? [];
  if (!Array.isArray(raw)) return;
  (post as { postTag?: ResolvedPostTag[] }).postTag = raw.map((t) => {
    if (typeof t === "string") {
      const meta = slugToMeta.get(t);
      return {
        _slug: t,
        name: meta?.name ?? t,
        ...(meta?.icon ? { icon: meta.icon } : {}),
        ...(meta?.color ? { color: meta.color } : {}),
      };
    }
    const o = t as {
      _slug?: string;
      name?: string;
      icon?: string;
      color?: string;
    };
    const slug = o._slug ?? o?.name ?? "";
    const meta = slug ? slugToMeta.get(slug) : undefined;
    const icon = (o.icon ?? meta?.icon)?.trim();
    const color = (o.color ?? meta?.color)?.trim();
    return {
      _slug: slug,
      name: o?.name ?? meta?.name ?? slug,
      ...(icon ? { icon } : {}),
      ...(color ? { color } : {}),
    };
  });
}

const POSTS_PER_PAGE = 10;

export function getPostsPerPage(): number {
  return POSTS_PER_PAGE;
}

/** Filtert Posts mit hideFromListing: true aus Übersichten heraus. */
export function filterHiddenPosts(posts: PostEntry[]): PostEntry[] {
  return posts.filter(
    (p) => !(p as PostEntry & { hideFromListing?: boolean }).hideFromListing,
  );
}

/** Sortiert Posts nach Datum (neueste zuerst). */
export function sortPostsByDate(posts: PostEntry[]): PostEntry[] {
  return [...posts].sort((a, b) => {
    const da = a.created ?? a.date ?? "";
    const db = b.created ?? b.date ?? "";
    if (!da) return 1;
    if (!db) return -1;
    return new Date(db).getTime() - new Date(da).getTime();
  });
}

/** Filtert Posts nach Tag-Slug (post.postTag enthält den Slug). */
export function filterPostsByTag(
  posts: PostEntry[],
  tagSlug: string | null,
): PostEntry[] {
  if (!tagSlug) return posts;
  return posts.filter((p) => {
    const tags = p.postTag ?? [];
    return tags.some((t) => {
      const slug = typeof t === "string" ? t : (t as { _slug?: string })?._slug;
      return slug === tagSlug;
    });
  });
}

/** URL aus post.postImage (RustyCMS img mit src, oder legacy file.url). */
export function getPostImageUrl(
  postImage: PostEntry["postImage"],
): string | null {
  if (!postImage || typeof postImage === "string") return null;
  const obj = postImage as {
    src?: string;
    file?: { url?: string };
    fields?: { file?: { url?: string } };
  };
  const url = obj?.src ?? obj?.file?.url ?? obj?.fields?.file?.url;
  return typeof url === "string"
    ? url.startsWith("//")
      ? `https:${url}`
      : url
    : null;
}

/** Filtert Posts: behält nur solche, die mindestens einen der Tag-Slugs haben. */
export function filterPostsByTagSlugs(
  posts: PostEntry[],
  tagSlugs: string[],
): PostEntry[] {
  if (!tagSlugs?.length) return posts;
  return posts.filter((p) => {
    const tags = p.postTag ?? [];
    return tags.some((t) => {
      const slug = typeof t === "string" ? t : (t as { _slug?: string })?._slug;
      return tagSlugs.includes(slug ?? "");
    });
  });
}

export function getTotalPages(count: number, perPage: number): number {
  return Math.max(1, Math.ceil(count / perPage));
}

export function paginate<T>(items: T[], page: number, perPage: number): T[] {
  const start = (page - 1) * perPage;
  return items.slice(start, start + perPage);
}

const POST_BASE = "/post";

/** Post-URL: slug ist die URL; führender Slash entfernt. */
export function postHref(post: PostEntry): string {
  const slug = post.slug ?? post._slug ?? "";
  const norm = (slug ?? "").replace(/^\//, "").trim();
  return norm ? `${POST_BASE}/${encodeURIComponent(norm)}` : POST_BASE;
}

export function formatPostDate(value: string | undefined): string {
  if (!value) return "";
  try {
    const d = new Date(value);
    return Number.isNaN(d.getTime())
      ? ""
      : d.toLocaleDateString("de-DE", {
          year: "numeric",
          month: "short",
          day: "numeric",
        });
  } catch {
    return "";
  }
}

/** Prüft, ob ein Block ein Post-Overview-Block ist (mit _type "post_overview"). */
function isPostOverviewBlock(item: unknown): item is {
  _type?: string;
  allPosts?: boolean;
  posts?: unknown[];
  filterByTag?: unknown[];
  numberItems?: number;
  postsResolved?: unknown[];
} {
  return (
    typeof item === "object" &&
    item !== null &&
    (item as { _type?: string })._type === "post_overview"
  );
}

/**
 * Lädt Posts für alle post_overview-Blöcke im Layout und setzt postsResolved.
 * Nutzt die globale Tag-API (getTagsMap()) für Slug → TagMeta.
 * Gibt die tagsMap zurück (z. B. für resolvePostTagsInPost auf [slug].astro).
 */
export async function resolvePostOverviewBlocks(
  layout: RowContentLayout | null | undefined,
): Promise<Map<string, TagMeta>> {
  const tagsMap = await getTagsMap();

  if (!layout) return tagsMap;
  const rows = [
    layout.row1Content ?? [],
    layout.row2Content ?? [],
    layout.row3Content ?? [],
  ];
  let allPosts: PostEntry[] | null = null;

  for (const content of rows) {
    if (!Array.isArray(content)) continue;
    for (const item of content) {
      if (!isPostOverviewBlock(item)) continue;
      if (item.allPosts) {
        if (allPosts === null)
          allPosts = await getPosts({
            _sort: "created",
            _order: "desc",
            resolve: "all",
          });
        let list = filterHiddenPosts(sortPostsByDate(allPosts));
        const rawFilter = item.filterByTag ?? [];
        const tagSlugs = Array.isArray(rawFilter)
          ? rawFilter
              .map((t) =>
                typeof t === "string"
                  ? t
                  : ((t as { _slug?: string })?._slug ?? ""),
              )
              .filter(Boolean)
          : [];
        if (tagSlugs.length) list = filterPostsByTagSlugs(list, tagSlugs);
        const limit =
          typeof item.numberItems === "number" ? item.numberItems : 9999;
        item.postsResolved = list.slice(0, limit);
      } else if (Array.isArray(item.posts) && item.posts.length > 0) {
        const first = item.posts[0];
        const isResolved =
          typeof first === "object" &&
          first !== null &&
          "_slug" in first &&
          ("headline" in first || "linkName" in first);
        if (isResolved) {
          item.postsResolved = item.posts as PostEntry[];
        } else {
          const slugs = item.posts
            .map((p) =>
              typeof p === "string"
                ? p
                : ((p as { _slug?: string })?._slug ?? ""),
            )
            .filter(Boolean);
          const limit =
            typeof item.numberItems === "number" ? item.numberItems : 9999;
          const resolved: PostEntry[] = [];
          for (const slug of slugs.slice(0, limit)) {
            const post = await getPostBySlug(slug, {
              locale: "de",
              resolve: ["all"],
            });
            if (post) resolved.push(post);
          }
          item.postsResolved = resolved;
        }
      }
      if (Array.isArray(item.postsResolved)) {
        const posts = item.postsResolved as PostEntry[];
        if (posts.length > 0) {
          for (const post of posts) resolvePostTagsInPost(post, tagsMap);
        }
        for (const post of item.postsResolved as PostEntry[]) {
          const raw = getPostImageUrl(post.postImage);
          if (raw && import.meta.env.SSR) {
            try {
              const { ensureTransformedImage } = await import("./rusty-image");
              const url = await ensureTransformedImage(raw, {
                width: 400,
                height: 267,
                fit: "cover",
                format: "webp",
              });
              (
                post as PostEntry & { _resolvedImageUrl?: string }
              )._resolvedImageUrl = url;
            } catch {
              // Bild optional
            }
          }
        }
      }
    }
  }
  return tagsMap;
}

function isCalendarBlock(
  item: unknown,
): item is { _type?: string; _slug?: string; items?: unknown[] } {
  return (
    typeof item === "object" &&
    item !== null &&
    (item as { _type?: string })._type === "calendar"
  );
}

/**
 * Löst Calendar-Blöcke auf: Lädt Kalender per Slug und setzt block.items
 * mit den aufgelösten calendar_item-Einträgen (title, terminZeit, description, link).
 */
export async function resolveCalendarBlocks(
  layout: RowContentLayout,
): Promise<void> {
  const rows = [
    layout.row1Content ?? [],
    layout.row2Content ?? [],
    layout.row3Content ?? [],
  ];

  for (const content of rows) {
    if (!Array.isArray(content)) continue;
    for (const item of content) {
      if (!isCalendarBlock(item)) continue;
      const slug = item._slug;
      if (!slug) continue;
      const rawItems = item.items ?? [];
      const first = rawItems[0];
      const alreadyResolved =
        typeof first === "object" &&
        first !== null &&
        "terminZeit" in first &&
        "title" in first;
      if (alreadyResolved) continue;

      const calendar = await getCalendarBySlug(slug, {
        locale: "de",
        resolve: "items",
      });
      const raw = calendar?.items ?? rawItems;
      const resolved: CalendarItemData[] = [];
      for (const x of raw) {
        if (typeof x === "object" && x !== null && "terminZeit" in x && "title" in x) {
          resolved.push(x as CalendarItemData);
        } else {
          const islug = typeof x === "string" ? x : (x as { _slug?: string })?._slug;
          if (islug) {
            const entry = await getCalendarItemBySlug(islug, { locale: "de" });
            if (entry) resolved.push(entry);
          }
        }
      }
      (item as { items?: CalendarItemData[] }).items = resolved;
    }
  }
}

function isSearchableTextBlock(
  item: unknown,
): item is { _type?: string; textFragments?: unknown[] } {
  return (
    typeof item === "object" &&
    item !== null &&
    (item as { _type?: string })._type === "searchable_text"
  );
}

/** Prüft, ob ein Fragment bereits aufgelöst ist (hat title oder text). */
function isResolvedFragment(f: unknown): boolean {
  if (typeof f !== "object" || f === null) return false;
  const o = f as { title?: string; text?: string };
  return "title" in o || "text" in o;
}

/**
 * Normalisiert tags eines Fragments zu Anzeigenamen (string[]).
 * Nutzt slugToMeta für Slug → Anzeigename; Objekte mit name/_slug werden unterstützt.
 */
function resolveFragmentTags(
  tagsRaw: unknown[] | undefined,
  slugToMeta: Map<string, TagMeta>,
): string[] {
  if (!Array.isArray(tagsRaw)) return [];
  return tagsRaw
    .map((t) => {
      if (typeof t === "string") return slugToMeta.get(t)?.name ?? t;
      const o = t as { _slug?: string; name?: string };
      const slug = o._slug ?? "";
      return (o.name ?? slugToMeta.get(slug)?.name ?? slug).trim();
    })
    .filter(Boolean);
}

/**
 * Lädt für alle searchable_text-Blöcke im Layout die textFragments nach
 * (wenn sie als Slugs/Refs kommen), setzt die aufgelösten Einträge und
 * löst Tag-Slugs/Refs in Anzeigenamen auf.
 * @param slugToMeta Optionale Map Slug → TagMeta (z. B. von resolvePostOverviewBlocks); sonst getTagsMap().
 */
export async function resolveSearchableTextBlocks(
  layout: RowContentLayout | null | undefined,
  slugToMeta?: Map<string, TagMeta>,
): Promise<void> {
  if (!layout) return;
  const tagsMap = slugToMeta ?? (await getTagsMap());
  const rows = [
    layout.row1Content ?? [],
    layout.row2Content ?? [],
    layout.row3Content ?? [],
  ];
  for (const content of rows) {
    if (!Array.isArray(content)) continue;
    for (const item of content) {
      if (!isSearchableTextBlock(item)) continue;
      const raw = item.textFragments ?? [];
      if (!Array.isArray(raw) || raw.length === 0) continue;
      const needsResolve = raw.some((f) => !isResolvedFragment(f));
      if (!needsResolve) continue;
      const resolved: unknown[] = [];
      for (const f of raw) {
        const slug =
          typeof f === "string" ? f : ((f as { _slug?: string })?._slug ?? "");
        if (!slug) {
          if (isResolvedFragment(f)) resolved.push(f);
          continue;
        }
        const frag = await getTextFragmentBySlug(slug, { locale: "de" });
        if (frag) {
          const fragObj = frag as { tags?: unknown[] };
          const tagNames = resolveFragmentTags(fragObj.tags, tagsMap);
          resolved.push({ ...fragObj, tags: tagNames });
        }
      }
      (item as { textFragments?: unknown[] }).textFragments = resolved;
    }
  }
}
