/**
 * Extract Facebook Post data from GraphQL JSON responses.
 */

import type { FbPostData } from "../../../types.js";
import { dig, asStr, asNum, asObj, edgeNodes, epochToIso } from "./helpers.js";

export function extractPosts(
  body: Record<string, unknown>,
): FbPostData[] {
  try {
    // Multiple known paths for timeline posts
    const feedEdges =
      findFeedEdges(body, "data", "node", "timeline_list_feed_units") ??
      findFeedEdges(body, "data", "node", "timeline_feed_units") ??
      findFeedEdges(body, "data", "page", "timeline_feed_units") ??
      findFeedEdges(body, "node", "timeline_list_feed_units");

    if (!feedEdges || feedEdges.length === 0) {
      // Try single-post response
      const singlePost = extractSinglePost(body);
      return singlePost ? [singlePost] : [];
    }

    const posts: FbPostData[] = [];
    for (const node of feedEdges) {
      const post = nodeToPost(node);
      if (post) posts.push(post);
    }
    return posts;
  } catch (err) {
    console.warn("[extractPosts] Unexpected error:", (err as Error).message);
    return [];
  }
}

function findFeedEdges(
  body: Record<string, unknown>,
  ...path: string[]
): Array<Record<string, unknown>> | null {
  const connection = dig(body, ...path);
  if (!connection) return null;
  const nodes = edgeNodes<Record<string, unknown>>(connection);
  return nodes.length > 0 ? nodes : null;
}

function extractSinglePost(
  body: Record<string, unknown>,
): FbPostData | null {
  const node =
    (dig(body, "data", "node") as Record<string, unknown> | undefined) ??
    (dig(body, "data", "story") as Record<string, unknown> | undefined);
  if (!node) return null;
  const typename = asStr(node, "__typename");
  if (typename && !["Story", "Post"].includes(typename)) return null;
  return nodeToPost(node);
}

function nodeToPost(node: Record<string, unknown>): FbPostData | null {
  const postId =
    asStr(node, "post_id") ??
    asStr(node, "id") ??
    asStr(node, "story_id");
  if (!postId) {
    console.warn("[extractPosts] Skipping node without post_id");
    return null;
  }

  // Extract text from various nested shapes
  const text =
    asStr(dig(node, "message", "text") as Record<string, unknown> | undefined) ??
    asStr(dig(node, "body", "text") as Record<string, unknown> | undefined) ??
    asStr(dig(node, "comet_sections", "content", "story", "message", "text") as Record<string, unknown> | undefined) ??
    asStr(node, "message");

  // Author info
  const author =
    (dig(node, "author") as Record<string, unknown> | undefined) ??
    (dig(node, "comet_sections", "context_layout", "story", "comet_sections", "actor_photo", "story", "actors", "0") as Record<string, unknown> | undefined);
  const authorId = asStr(author, "id");
  const authorName = asStr(author, "name");

  // Timestamp
  const publishedAt =
    epochToIso(dig(node, "created_time")) ??
    epochToIso(dig(node, "creation_time"));

  // Media
  const { mediaUrls, videoUrls, linkUrl, postType } = extractMedia(node);

  // Engagement
  const feedback =
    (dig(node, "feedback") as Record<string, unknown> | undefined) ??
    (dig(node, "comet_sections", "feedback", "story", "feedback_context", "feedback_target_with_context") as Record<string, unknown> | undefined);

  const reactionCounts = extractReactionSummary(feedback);
  const commentCount =
    asNum(feedback, "comment_count") ??
    asNum(dig(feedback, "comments", "total_count") as Record<string, unknown> | undefined, "count") ??
    asNum(dig(feedback, "comment_rendering_instance", "comments", "total_count") as Record<string, unknown> | undefined);
  const shareCount =
    asNum(feedback, "share_count") ??
    asNum(dig(feedback, "share_count") as Record<string, unknown> | undefined, "count");

  return {
    post_id: postId,
    author_id: authorId,
    author_name: authorName,
    text,
    post_type: postType,
    published_at: publishedAt,
    media_urls: mediaUrls.length > 0 ? mediaUrls : undefined,
    video_urls: videoUrls.length > 0 ? videoUrls : undefined,
    link_url: linkUrl,
    reaction_counts: reactionCounts,
    comment_count: commentCount,
    share_count: shareCount,
    raw: asObj(node),
  };
}

function extractMedia(node: Record<string, unknown>): {
  mediaUrls: string[];
  videoUrls: string[];
  linkUrl?: string;
  postType?: FbPostData["post_type"];
} {
  const mediaUrls: string[] = [];
  const videoUrls: string[] = [];
  let linkUrl: string | undefined;
  let postType: FbPostData["post_type"];

  // Standard attachments
  const attachments =
    edgeNodes<Record<string, unknown>>(dig(node, "attachments")) ??
    edgeNodes<Record<string, unknown>>(
      dig(node, "comet_sections", "content", "story", "attachments"),
    );

  for (const att of attachments) {
    const media = (dig(att, "media") ?? att) as Record<string, unknown>;
    const typename = asStr(media, "__typename") ?? "";

    if (typename.includes("Video") || dig(media, "playable_url")) {
      const url = asStr(media, "playable_url") ?? asStr(media, "playable_url_quality_hd");
      if (url) videoUrls.push(url);
      postType = "video";
    } else if (typename.includes("Photo") || dig(media, "image")) {
      const url =
        asStr(dig(media, "image", "uri") as Record<string, unknown> | undefined) ??
        asStr(dig(media, "photo_image", "uri") as Record<string, unknown> | undefined) ??
        asStr(media, "uri");
      if (url) mediaUrls.push(url);
      postType = postType ?? "photo";
    }

    // Link attachment
    const story = dig(att, "story_attachment") as Record<string, unknown> | undefined;
    if (story) {
      const url = asStr(dig(story, "url") as Record<string, unknown> | undefined) ?? asStr(story, "url");
      if (url) {
        linkUrl = url;
        postType = postType ?? "link";
      }
    }
  }

  if (!postType) {
    const text = dig(node, "message", "text") ?? dig(node, "body", "text");
    postType = text ? "text" : undefined;
  }

  return { mediaUrls, videoUrls, linkUrl, postType };
}

function extractReactionSummary(
  feedback: Record<string, unknown> | undefined,
): Record<string, number> | undefined {
  if (!feedback) return undefined;

  const topReactions =
    edgeNodes<Record<string, unknown>>(
      dig(feedback, "top_reactions"),
    ) ??
    edgeNodes<Record<string, unknown>>(
      dig(feedback, "reaction_count", "edges"),
    );

  if (topReactions.length > 0) {
    const counts: Record<string, number> = {};
    for (const r of topReactions) {
      const key =
        asStr(dig(r, "node", "reaction_type") as Record<string, unknown> | undefined) ??
        asStr(r, "reaction_type") ??
        "unknown";
      const count = asNum(r, "reaction_count") ?? asNum(r, "count") ?? 0;
      counts[key.toLowerCase()] = count;
    }
    return Object.keys(counts).length > 0 ? counts : undefined;
  }

  // Fallback: total reaction count
  const total = asNum(feedback, "reaction_count") ?? asNum(dig(feedback, "reactors", "count") as Record<string, unknown> | undefined);
  if (total !== undefined) {
    return { total };
  }

  return undefined;
}
