import React, { useMemo } from "react";
import { AtProtoRecord } from "../core/AtProtoRecord";
import { BlueskyPostRenderer } from "../renderers/BlueskyPostRenderer";
import type { FeedPostRecord, ProfileRecord } from "../types/bluesky";
import { useDidResolution } from "../hooks/useDidResolution";
import { useAtProtoRecord } from "../hooks/useAtProtoRecord";
import { useBlob } from "../hooks/useBlob";
import { BLUESKY_PROFILE_COLLECTION } from "./BlueskyProfile";
import { getAvatarCid } from "../utils/profile";
import { formatDidForLabel } from "../utils/at-uri";
import type { BlobWithCdn } from "../hooks/useBlueskyAppview";

/**
 * Props for rendering a single Bluesky post with optional customization hooks.
 */
export interface BlueskyPostProps {
	/**
	 * Decentralized identifier for the repository that owns the post.
	 */
	did: string;
	/**
	 * Record key identifying the specific post within the collection.
	 */
	rkey: string;
	/**
	 * Prefetched post record. When provided, skips fetching the post from the network.
	 * Note: Profile and avatar data will still be fetched unless a custom renderer is used.
	 */
	record?: FeedPostRecord;
	/**
	 * Custom renderer component that receives resolved post data and status flags.
	 */
	renderer?: React.ComponentType<BlueskyPostRendererInjectedProps>;
	/**
	 * React node shown while the post query has not yet produced data or an error.
	 */
	fallback?: React.ReactNode;
	/**
	 * React node displayed while the post fetch is actively loading.
	 */
	loadingIndicator?: React.ReactNode;
	/**
	 * Preferred color scheme to pass through to renderers.
	 */
	colorScheme?: "light" | "dark" | "system";
	/**
	 * Whether the default renderer should show the Bluesky icon.
	 * Defaults to `true`.
	 */
	showIcon?: boolean;
	/**
	 * Placement strategy for the icon when it is rendered.
	 * Defaults to `'timestamp'`.
	 */
	iconPlacement?: "cardBottomRight" | "timestamp" | "linkInline";
}

/**
 * Values injected by `BlueskyPost` into a downstream renderer component.
 */
export type BlueskyPostRendererInjectedProps = {
	/**
	 * Resolved record payload for the post.
	 */
	record: FeedPostRecord;
	/**
	 * `true` while network operations are in-flight.
	 */
	loading: boolean;
	/**
	 * Error encountered during loading, if any.
	 */
	error?: Error;
	/**
	 * The author's public handle derived from the DID.
	 */
	authorHandle: string;
	/**
	 * The DID that owns the post record.
	 */
	authorDid: string;
	/**
	 * Resolved URL for the author's avatar blob, if available.
	 */
	avatarUrl?: string;
	/**
	 * Preferred color scheme bubbled down to children.
	 */
	colorScheme?: "light" | "dark" | "system";
	/**
	 * Placement strategy for the Bluesky icon.
	 */
	iconPlacement?: "cardBottomRight" | "timestamp" | "linkInline";
	/**
	 * Controls whether the icon should render at all.
	 */
	showIcon?: boolean;
	/**
	 * Fully qualified AT URI of the post, when resolvable.
	 */
	atUri?: string;
	/**
	 * Optional override for the rendered embed contents.
	 */
	embed?: React.ReactNode;
};

/** NSID for the canonical Bluesky feed post collection. */
export const BLUESKY_POST_COLLECTION = "app.bsky.feed.post";

/**
 * Fetches a Bluesky feed post, resolves metadata such as author handle and avatar,
 * and renders it via a customizable renderer component.
 *
 * @param did - DID of the repository that stores the post.
 * @param rkey - Record key for the post within the feed collection.
 * @param renderer - Optional renderer component to override the default.
 * @param fallback - Node rendered before the first fetch attempt resolves.
 * @param loadingIndicator - Node rendered while the post is loading.
 * @param colorScheme - Preferred color scheme forwarded to downstream components.
 * @param showIcon - Controls whether the Bluesky icon should render alongside the post. Defaults to `true`.
 * @param iconPlacement - Determines where the icon is positioned in the rendered post. Defaults to `'timestamp'`.
 * @returns A component that renders loading/fallback states and the resolved post.
 */
export const BlueskyPost: React.FC<BlueskyPostProps> = ({
	did: handleOrDid,
	rkey,
	record,
	renderer,
	fallback,
	loadingIndicator,
	colorScheme,
	showIcon = true,
	iconPlacement = "timestamp",
}) => {
	const {
		did: resolvedDid,
		handle,
		loading: resolvingIdentity,
		error: resolutionError,
	} = useDidResolution(handleOrDid);
	const repoIdentifier = resolvedDid ?? handleOrDid;
	const { record: profile } = useAtProtoRecord<ProfileRecord>({
		did: repoIdentifier,
		collection: BLUESKY_PROFILE_COLLECTION,
		rkey: "self",
	});
	// Check if the avatar has a CDN URL from the appview (preferred)
	const avatar = profile?.avatar;
	const avatarCdnUrl = isBlobWithCdn(avatar) ? avatar.cdnUrl : undefined;
	const avatarCid = !avatarCdnUrl ? getAvatarCid(profile) : undefined;

	const Comp: React.ComponentType<BlueskyPostRendererInjectedProps> = useMemo(
		() => renderer ?? ((props) => <BlueskyPostRenderer {...props} />),
		[renderer]
	);

	const displayHandle =
		handle ?? (handleOrDid.startsWith("did:") ? undefined : handleOrDid);
	const authorHandle =
		displayHandle ?? formatDidForLabel(resolvedDid ?? handleOrDid);
	const atUri = resolvedDid
		? `at://${resolvedDid}/${BLUESKY_POST_COLLECTION}/${rkey}`
		: undefined;

	const Wrapped = useMemo(() => {
		const WrappedComponent: React.FC<{
			record: FeedPostRecord;
			loading: boolean;
			error?: Error;
		}> = (props) => {
			const { url: avatarUrlFromBlob } = useBlob(repoIdentifier, avatarCid);
			// Use CDN URL from appview if available, otherwise use blob URL
			const avatarUrl = avatarCdnUrl || avatarUrlFromBlob;
			return (
				<Comp
					{...props}
					authorHandle={authorHandle}
					authorDid={repoIdentifier}
					avatarUrl={avatarUrl}
					colorScheme={colorScheme}
					iconPlacement={iconPlacement}
					showIcon={showIcon}
					atUri={atUri}
				/>
			);
		};
		WrappedComponent.displayName = "BlueskyPostWrappedRenderer";
		return WrappedComponent;
	}, [
		Comp,
		repoIdentifier,
		avatarCid,
		avatarCdnUrl,
		authorHandle,
		colorScheme,
		iconPlacement,
		showIcon,
		atUri,
	]);

	if (!displayHandle && resolvingIdentity) {
		return <div style={{ padding: 8 }}>Resolving handle…</div>;
	}
	if (!displayHandle && resolutionError) {
		return (
			<div style={{ padding: 8, color: "crimson" }}>
				Could not resolve handle.
			</div>
		);
	}


	if (record !== undefined) {
		return (
			<AtProtoRecord<FeedPostRecord>
				record={record}
				renderer={Wrapped}
				fallback={fallback}
				loadingIndicator={loadingIndicator}
			/>
		);
	}

	return (
		<AtProtoRecord<FeedPostRecord>
			did={repoIdentifier}
			collection={BLUESKY_POST_COLLECTION}
			rkey={rkey}
			renderer={Wrapped}
			fallback={fallback}
			loadingIndicator={loadingIndicator}
		/>
	);
};

/**
 * Type guard to check if a blob has a CDN URL from appview.
 */
function isBlobWithCdn(value: unknown): value is BlobWithCdn {
	if (typeof value !== "object" || value === null) return false;
	const obj = value as Record<string, unknown>;
	return (
		obj.$type === "blob" &&
		typeof obj.cdnUrl === "string" &&
		typeof obj.ref === "object" &&
		obj.ref !== null &&
		typeof (obj.ref as { $link?: unknown }).$link === "string"
	);
}

export default BlueskyPost;
