import "server-only";

import {
  bool,
  dateOf,
  dateOrNull,
  field,
  idOf,
  isoOrNull,
  list,
  num,
  relation,
  str,
  strOrNull,
} from "@/lib/cms/api";
import type { ContentStatus } from "@/lib/cms/types";
import type { BlogDefaults } from "@/features/settings/schemas";

import {
  blogFormSchema,
  ctaButtonStyles,
  inferCtaButtonStyle,
  type BlogEditorMeta,
  type CtaButtonStyle,
  type BlogFormValues,
  type SaveBlogResult,
} from "./schemas";

/**
 * Maps Laravel CMS blog payloads to the editor's types.
 *
 * Staging returns raw `blog_posts` rows (snake_case, tags with `pivot`);
 * docs/laravel-blog-api.md §8.4 describes `{values, meta}`. Both are read.
 */

const statuses: ContentStatus[] = ["DRAFT", "SCHEDULED", "PUBLISHED"];

export const statusOf = (row: unknown): ContentStatus => {
  const value = String(
    field(row, "status") ?? "",
  ).toUpperCase() as ContentStatus;
  return statuses.includes(value) ? value : "DRAFT";
};

const tagNames = (row: unknown) =>
  list(row, "tags")
    .map((tag) => (typeof tag === "string" ? tag : str(tag, "name")))
    .filter(Boolean);

const byPosition = (a: unknown, b: unknown) =>
  num(a, "position") - num(b, "position");

const faqsOf = (row: unknown) =>
  [...list(row, "faqs")].sort(byPosition).map((faq) => ({
    id: idOf(faq) || crypto.randomUUID(),
    question: str(faq, "question"),
    answer: str(faq, "answer"),
  }));

/** Related picks: `related_blog_ids`, or a relation of rows (`related_blogs` / `related`). */
function relatedIdsOf(row: unknown) {
  const ids = field<unknown[]>(row, "related_blog_ids");
  if (Array.isArray(ids)) return ids.map(String);
  const rows = list(row, "related_blogs").length
    ? list(row, "related_blogs")
    : list(row, "related");
  return [...rows]
    .sort(byPosition)
    .map((item) =>
      typeof item === "string"
        ? item
        : str(item, "related_blog_id") ||
          str(field(item, "pivot"), "related_blog_id") ||
          idOf(item),
    )
    .filter(Boolean);
}

/** `cta_button_style` when Laravel stores it; otherwise worked out from the link. */
function ctaButtonStyleOf(row: unknown): CtaButtonStyle {
  const stored = str(row, "cta_button_style").toUpperCase();
  return ctaButtonStyles.includes(stored as CtaButtonStyle)
    ? (stored as CtaButtonStyle)
    : inferCtaButtonStyle(str(row, "cta_button_url"));
}

/** Form values from a Laravel row. */
export function rowToFormValues(row: unknown): BlogFormValues {
  return {
    title: str(row, "title"),
    slug: str(row, "slug"),
    excerpt: str(row, "excerpt"),
    content: str(row, "content"),
    featuredImage: str(row, "featured_image"),
    featuredImageAlt: str(row, "featured_image_alt"),
    featuredImageTitle: str(row, "featured_image_title"),
    categoryId: strOrNull(row, "category_id"),
    authorId: strOrNull(row, "author_id"),
    tags: tagNames(row),
    publishedAt: isoOrNull(row, "published_at"),
    seoTitle: str(row, "seo_title"),
    metaDescription: str(row, "meta_description"),
    canonicalUrl: str(row, "canonical_url"),
    focusKeyword: str(row, "focus_keyword"),
    ogTitle: str(row, "og_title"),
    ogDescription: str(row, "og_description"),
    ogImage: str(row, "og_image"),
    robotsIndex: bool(row, "robots_index", true),
    robotsFollow: bool(row, "robots_follow", true),
    faqs: faqsOf(row),
    faqSchemaEnabled: bool(row, "faq_schema_enabled", true),
    ctaEnabled: bool(row, "cta_enabled"),
    ctaTitle: str(row, "cta_title"),
    ctaDescription: str(row, "cta_description"),
    ctaButtonText: str(row, "cta_button_text"),
    ctaButtonUrl: str(row, "cta_button_url"),
    ctaButtonStyle: ctaButtonStyleOf(row),
    ctaImage: str(row, "cta_image"),
    relatedMode:
      str(row, "related_mode").toUpperCase() === "MANUAL"
        ? "MANUAL"
        : "AUTOMATIC",
    relatedBlogIds: relatedIdsOf(row),
  };
}

/** Editor metadata from a Laravel row (or the spec's `meta`). */
export function rowToEditorMeta(row: unknown): BlogEditorMeta {
  const status = statusOf(row);
  const slug = str(row, "slug");
  const liveSlug = field(row, "live_slug");
  return {
    id: idOf(row),
    status,
    slug,
    version: num(row, "version", 1),
    publishedAt: isoOrNull(row, "published_at"),
    scheduledAt: isoOrNull(row, "scheduled_at"),
    updatedAt: isoOrNull(row, "updated_at") ?? new Date(0).toISOString(),
    autosavedAt: isoOrNull(row, "autosaved_at"),
    liveSlug:
      liveSlug !== undefined
        ? ((liveSlug as string | null) ?? null)
        : status === "DRAFT"
          ? null
          : slug,
    revisionCount: num(row, "revision_count", num(row, "revisions_count")),
  };
}

/** The autosave snapshot (an object, or JSON text), merged over the saved values. */
export function pendingFromRow(row: unknown, saved: BlogFormValues) {
  let value = field(row, "autosave");
  if (typeof value === "string") {
    try {
      value = JSON.parse(value);
    } catch {
      return null;
    }
  }
  return parseSnapshot(value, saved);
}

/**
 * Reads any blog payload the CMS sends back: `{values, meta}`, `{blog, values}`,
 * `{blog: row}` or a bare row.
 */
export function readBlogPayload(
  data: unknown,
): SaveBlogResult & { row: unknown } {
  const record = (data ?? {}) as Record<string, unknown>;
  if (record.meta && record.values) {
    const meta = rowToEditorMeta(record.meta);
    return {
      blog: meta,
      values:
        parseSnapshot(record.values, rowToFormValues({})) ??
        rowToFormValues(record.values),
      row: record.meta,
    };
  }
  if (record.blog && record.values) {
    return {
      blog: rowToEditorMeta(record.blog),
      values:
        parseSnapshot(record.values, rowToFormValues(record.blog)) ??
        rowToFormValues(record.blog),
      row: record.blog,
    };
  }
  const row =
    record.blog && typeof record.blog === "object" ? record.blog : data;
  return { blog: rowToEditorMeta(row), values: rowToFormValues(row), row };
}

/** A blog row's category and author, from nested relations or the option lists. */
export function rowRelations(
  row: unknown,
  names: {
    categories: Map<string, string>;
    authors: Map<string, { name: string; profileImage: string | null }>;
  },
) {
  const categoryId = strOrNull(row, "category_id");
  const authorId = strOrNull(row, "author_id");
  const category = relation(row, "category");
  const author = relation(row, "author");
  const updatedBy = relation(row, "updated_by") ?? relation(row, "updater");

  const categoryName = category
    ? str(category, "name")
    : categoryId
      ? names.categories.get(categoryId)
      : undefined;
  const authorInfo = author
    ? {
        name: str(author, "name"),
        profileImage: strOrNull(author, "profile_image"),
      }
    : authorId
      ? names.authors.get(authorId)
      : undefined;

  return {
    category:
      categoryId && categoryName !== undefined
        ? { id: categoryId, name: categoryName }
        : null,
    author: authorId && authorInfo ? { id: authorId, ...authorInfo } : null,
    updatedBy: updatedBy ? { name: str(updatedBy, "name") } : null,
  };
}

/** The columns the list and dashboard show, from a Laravel row. */
export function rowToListFields(row: unknown) {
  return {
    id: idOf(row),
    title: str(row, "title"),
    slug: str(row, "slug"),
    status: statusOf(row),
    featuredImage: strOrNull(row, "featured_image"),
    featuredImageAlt: strOrNull(row, "featured_image_alt"),
    publishedAt: dateOrNull(row, "published_at"),
    scheduledAt: dateOrNull(row, "scheduled_at"),
    createdAt: dateOf(row, "created_at"),
    updatedAt: dateOf(row, "updated_at"),
    autosavedAt: dateOrNull(row, "autosaved_at"),
  };
}

/** A blank article, prefilled from the Blog defaults settings. */
export function emptyFormValues(defaults: BlogDefaults): BlogFormValues {
  return {
    title: "",
    slug: "",
    excerpt: "",
    content: "",
    featuredImage: "",
    featuredImageAlt: "",
    featuredImageTitle: "",
    categoryId: defaults.defaultCategoryId,
    authorId: defaults.defaultAuthorId,
    tags: [],
    publishedAt: null,
    seoTitle: "",
    metaDescription: "",
    canonicalUrl: "",
    focusKeyword: "",
    ogTitle: "",
    ogDescription: "",
    ogImage: "",
    robotsIndex: true,
    robotsFollow: true,
    faqs: [],
    faqSchemaEnabled: defaults.faqSchemaEnabled,
    ctaEnabled: defaults.ctaEnabled,
    ctaTitle: defaults.ctaTitle,
    ctaDescription: defaults.ctaDescription,
    ctaButtonText: defaults.ctaButtonText,
    ctaButtonUrl: defaults.ctaButtonUrl,
    ctaButtonStyle: inferCtaButtonStyle(defaults.ctaButtonUrl),
    ctaImage: defaults.ctaImage ?? "",
    relatedMode: "AUTOMATIC",
    relatedBlogIds: [],
  };
}

/**
 * Parses a stored snapshot (autosave or revision). Snapshots written before a
 * field existed are filled in from `fallback`. Null values become the fallback's.
 */
export function parseSnapshot(value: unknown, fallback: BlogFormValues) {
  if (!value || typeof value !== "object") return null;
  const cleaned = Object.fromEntries(
    Object.entries(value as Record<string, unknown>).filter(
      ([key, item]) =>
        item !== null ||
        key === "categoryId" ||
        key === "authorId" ||
        key === "publishedAt",
    ),
  );
  const parsed = blogFormSchema.safeParse({ ...fallback, ...cleaned });
  return parsed.success ? parsed.data : null;
}

export type RevisionReason =
  "published" | "updated" | "unpublished" | "restored" | "auto-published";
