import type {
  Category as UiCategory,
  ContentOverview,
  ContentWindowHighlight,
  FriendLink as UiFriendLink,
  HumanVerificationMode,
  Post as UiPost,
  PopularPostHighlight,
  SiteSettings,
  Tag as UiTag,
} from "../types";

const DEV_API_BASE_URL = "http://127.0.0.1:5150/api";
const PROD_DEFAULT_API_PORT = "5150";

function normalizeApiBaseUrl(value?: string | null) {
  return value?.trim().replace(/\/$/, "") ?? "";
}

function getRuntimeEnv(
  name:
    | "PUBLIC_API_BASE_URL"
    | "INTERNAL_API_BASE_URL"
    | "PUBLIC_COMMENT_TURNSTILE_SITE_KEY"
    | "PUBLIC_WEB_PUSH_VAPID_PUBLIC_KEY",
) {
  const runtimeProcess = (
    globalThis as typeof globalThis & {
      process?: {
        env?: Record<string, string | undefined>;
      };
    }
  ).process;

  return normalizeApiBaseUrl(runtimeProcess?.env?.[name]);
}

function toUrlLike(value: string | URL) {
  return value instanceof URL ? value : new URL(value);
}

function normalizeVerificationMode(
  value: string | null | undefined,
  fallback: HumanVerificationMode,
): HumanVerificationMode {
  switch ((value ?? "").trim().toLowerCase()) {
    case "off":
      return "off";
    case "captcha":
    case "normal":
    case "simple":
      return "captcha";
    case "turnstile":
      return "turnstile";
    default:
      return fallback;
  }
}

const buildTimePublicApiBaseUrl = normalizeApiBaseUrl(
  import.meta.env.PUBLIC_API_BASE_URL,
);
const buildTimeCommentTurnstileSiteKey =
  import.meta.env.PUBLIC_COMMENT_TURNSTILE_SITE_KEY?.trim() ?? "";
const buildTimeWebPushVapidPublicKey =
  import.meta.env.PUBLIC_WEB_PUSH_VAPID_PUBLIC_KEY?.trim() ?? "";

export function resolvePublicApiBaseUrl(requestUrl?: string | URL) {
  const runtimePublicApiBaseUrl = getRuntimeEnv("PUBLIC_API_BASE_URL");
  if (runtimePublicApiBaseUrl) {
    return runtimePublicApiBaseUrl;
  }

  if (buildTimePublicApiBaseUrl) {
    return buildTimePublicApiBaseUrl;
  }

  if (import.meta.env.DEV) {
    return DEV_API_BASE_URL;
  }

  if (requestUrl) {
    const { protocol, hostname } = toUrlLike(requestUrl);
    return `${protocol}//${hostname}:${PROD_DEFAULT_API_PORT}/api`;
  }

  return DEV_API_BASE_URL;
}

export function resolveInternalApiBaseUrl(requestUrl?: string | URL) {
  const runtimeInternalApiBaseUrl = getRuntimeEnv("INTERNAL_API_BASE_URL");
  if (runtimeInternalApiBaseUrl) {
    return runtimeInternalApiBaseUrl;
  }

  return resolvePublicApiBaseUrl(requestUrl);
}

export function resolvePublicCommentTurnstileSiteKey() {
  return (
    getRuntimeEnv("PUBLIC_COMMENT_TURNSTILE_SITE_KEY") ||
    buildTimeCommentTurnstileSiteKey
  );
}

export function resolvePublicWebPushVapidPublicKey() {
  return (
    getRuntimeEnv("PUBLIC_WEB_PUSH_VAPID_PUBLIC_KEY") ||
    buildTimeWebPushVapidPublicKey
  );
}

export const API_BASE_URL = resolvePublicApiBaseUrl();

export interface ApiPost {
  id: number;
  title: string;
  slug: string;
  description: string;
  content: string;
  category: string;
  tags: string[];
  post_type: "article" | "tweet";
  image: string | null;
  images: string[] | null;
  pinned: boolean;
  status: string | null;
  visibility: "public" | "unlisted" | "private" | null;
  publish_at: string | null;
  unpublish_at: string | null;
  canonical_url: string | null;
  noindex: boolean | null;
  og_image: string | null;
  redirect_from: string[] | null;
  redirect_to: string | null;
  created_at: string;
  updated_at: string;
}

export interface Comment {
  id: number;
  post_id: string | null;
  post_slug: string | null;
  author: string | null;
  email: string | null;
  avatar: string | null;
  ip_address: string | null;
  user_agent: string | null;
  referer: string | null;
  content: string | null;
  reply_to: string | null;
  reply_to_comment_id: number | null;
  scope: "article" | "paragraph";
  paragraph_key: string | null;
  paragraph_excerpt: string | null;
  approved: boolean | null;
  created_at: string;
  updated_at: string;
}

export interface CreateCommentInput {
  postSlug: string;
  nickname: string;
  email?: string;
  content: string;
  scope?: "article" | "paragraph";
  paragraphKey?: string;
  paragraphExcerpt?: string;
  replyTo?: string | null;
  replyToCommentId?: number | null;
  turnstileToken?: string;
  captchaToken?: string;
  captchaAnswer?: string;
  website?: string;
}

export interface CommentCaptchaChallenge {
  token: string;
  question: string;
  expires_in_seconds: number;
}

export interface ParagraphCommentSummary {
  paragraph_key: string;
  count: number;
}

export interface ApiTag {
  id: number;
  name: string;
  slug: string;
  count?: number;
  description?: string | null;
  cover_image?: string | null;
  accent_color?: string | null;
  seo_title?: string | null;
  seo_description?: string | null;
}

export interface ApiCategory {
  id: number;
  name: string;
  slug: string;
  count: number;
  description?: string | null;
  cover_image?: string | null;
  accent_color?: string | null;
  seo_title?: string | null;
  seo_description?: string | null;
}

export interface ApiFriendLink {
  id: number;
  site_name: string;
  site_url: string;
  avatar_url: string | null;
  description: string | null;
  category: string | null;
  status: "pending" | "approved" | "rejected";
  created_at: string;
  updated_at: string;
}

export interface CreateFriendLinkInput {
  siteName: string;
  siteUrl: string;
  avatarUrl?: string;
  description: string;
  category?: string;
}

export interface PublicSubscriptionResponse {
  ok: boolean;
  subscription_id: number;
  status: string;
  requires_confirmation: boolean;
  message: string;
}

export interface PublicManagedSubscription {
  created_at: string;
  updated_at: string;
  id: number;
  channel_type: string;
  target: string;
  display_name: string | null;
  status: string;
  filters: Record<string, unknown> | null;
  metadata: Record<string, unknown> | null;
  verified_at: string | null;
  last_notified_at: string | null;
  last_delivery_status: string | null;
  manage_token: string | null;
}

export interface PublicSubscriptionManageResponse {
  ok: boolean;
  subscription: PublicManagedSubscription;
}

export interface ApiSiteSettings {
  id: number;
  site_name: string | null;
  site_short_name: string | null;
  site_url: string | null;
  site_title: string | null;
  site_description: string | null;
  hero_title: string | null;
  hero_subtitle: string | null;
  owner_name: string | null;
  owner_title: string | null;
  owner_bio: string | null;
  owner_avatar_url: string | null;
  social_github: string | null;
  social_twitter: string | null;
  social_email: string | null;
  location: string | null;
  tech_stack: string[] | null;
  music_enabled?: boolean | null;
  music_playlist: Array<{
    title: string;
    artist?: string | null;
    album?: string | null;
    url: string;
    cover_image_url?: string | null;
    accent_color?: string | null;
    description?: string | null;
  }> | null;
  ai_enabled: boolean;
  paragraph_comments_enabled: boolean;
  comment_verification_mode?: HumanVerificationMode | null;
  comment_turnstile_enabled: boolean;
  subscription_verification_mode?: HumanVerificationMode | null;
  subscription_turnstile_enabled: boolean;
  web_push_enabled: boolean;
  turnstile_site_key: string | null;
  web_push_vapid_public_key: string | null;
  subscription_popup_enabled: boolean;
  subscription_popup_title: string | null;
  subscription_popup_description: string | null;
  subscription_popup_delay_seconds: number | null;
  seo_favicon_url: string | null;
  seo_default_og_image: string | null;
  seo_default_twitter_handle: string | null;
  seo_wechat_share_qr_enabled: boolean;
}

export interface ContentAnalyticsInput {
  eventType: "page_view" | "read_progress" | "read_complete";
  path: string;
  postSlug?: string;
  sessionId?: string;
  durationMs?: number;
  progressPercent?: number;
  metadata?: Record<string, unknown>;
  referrer?: string;
}

export interface ApiHomePagePayload {
  site_settings: ApiSiteSettings;
  posts: ApiPost[];
  tags: ApiTag[];
  friend_links: ApiFriendLink[];
  categories: ApiCategory[];
  content_overview?: {
    total_page_views: number;
    page_views_last_24h: number;
    page_views_last_7d: number;
    total_read_completes: number;
    read_completes_last_7d: number;
    avg_read_progress_last_7d: number;
    avg_read_duration_ms_last_7d: number | null;
  };
  popular_posts?: Array<{
    slug: string;
    title: string;
    page_views: number;
    read_completes: number;
    avg_progress_percent: number;
    avg_duration_ms: number | null;
  }>;
  content_ranges?: Array<{
    key: string;
    label: string;
    days: number;
    overview: {
      page_views: number;
      read_completes: number;
      avg_read_progress: number;
      avg_read_duration_ms: number | null;
    };
    popular_posts: Array<{
      slug: string;
      title: string;
      page_views: number;
      read_completes: number;
      avg_progress_percent: number;
      avg_duration_ms: number | null;
    }>;
  }>;
}

export interface AiSource {
  slug: string;
  href: string;
  title: string;
  excerpt: string;
  score: number;
  chunk_index: number;
}

export interface AiAskResponse {
  question: string;
  answer: string;
  sources: AiSource[];
  indexed_chunks: number;
  last_indexed_at: string | null;
}

export interface ApiSearchResult {
  id: number;
  title: string | null;
  slug: string;
  description: string | null;
  content: string | null;
  category: string | null;
  tags: string[] | null;
  post_type: "article" | "tweet" | null;
  image: string | null;
  pinned: boolean | null;
  created_at: string;
  updated_at: string;
  rank: number;
}

export interface ApiPagedResponse<T> {
  items: T[];
  page: number;
  page_size: number;
  total: number;
  total_pages: number;
  sort_by: string;
  sort_order: string;
}

export interface ApiPagedSearchResponse extends ApiPagedResponse<ApiSearchResult> {
  query: string;
}

export interface Review {
  id: number;
  title: string;
  review_type: "game" | "anime" | "music" | "book" | "movie";
  rating: number;
  review_date: string;
  status: "published" | "draft" | "completed" | "in-progress" | "dropped";
  description: string;
  tags: string;
  cover: string;
  link_url: string | null;
  created_at: string;
  updated_at: string;
}

export type AppFriendLink = UiFriendLink & {
  status: ApiFriendLink["status"];
};

export const DEFAULT_SITE_SETTINGS: SiteSettings = {
  id: "1",
  siteName: "InitCool",
  siteShortName: "Termi",
  siteUrl: "https://init.cool",
  siteTitle: "InitCool · 技术笔记与内容档案",
  siteDescription: "一个认真折腾、偶尔整活的小站。",
  heroTitle: "欢迎光临，先随便翻翻",
  heroSubtitle: "这里像个边修边长的工具箱，偶尔掉装备，偶尔掉灵感，先逛再说。",
  ownerName: "InitCool",
  ownerTitle: "负责把脑洞拧成页面的人",
  ownerBio:
    "一个喜欢把问题拆开、记下、再慢慢拼回去的人。这里不急着自报家门，先看内容，合胃口再认识。",
  location: "Hong Kong",
  social: {
    github: "https://github.com/limitcool",
    twitter: "",
    email: "mailto:initcoool@gmail.com",
  },
  techStack: ["Rust", "Go", "Python", "Svelte", "Astro", "Loco.rs"],
  musicEnabled: true,
  musicPlaylist: [
    {
      title: "山中来信",
      artist: "InitCool Radio",
      album: "站点默认歌单",
      url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
      coverImageUrl:
        "https://images.unsplash.com/photo-1510915228340-29c85a43dcfe?auto=format&fit=crop&w=600&q=80",
      accentColor: "#2f6b5f",
      description: "适合文章阅读时循环播放的轻氛围曲。",
    },
    {
      title: "风吹松声",
      artist: "InitCool Radio",
      album: "站点默认歌单",
      url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3",
      coverImageUrl:
        "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=600&q=80",
      accentColor: "#8a5b35",
      description: "偏木质感的器乐氛围，适合深夜浏览。",
    },
    {
      title: "夜航小记",
      artist: "InitCool Radio",
      album: "站点默认歌单",
      url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3",
      coverImageUrl:
        "https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?auto=format&fit=crop&w=600&q=80",
      accentColor: "#375a7f",
      description: "节奏更明显一点，适合切换阅读状态。",
    },
  ],
  ai: {
    enabled: false,
  },
  comments: {
    paragraphsEnabled: true,
    verificationMode: "captcha",
    turnstileEnabled: false,
    turnstileSiteKey: undefined,
  },
  subscriptions: {
    popupEnabled: true,
    popupTitle: "订阅更新",
    popupDescription:
      "有新文章或汇总简报时，通过邮件第一时间收到提醒。需要先确认邮箱，可随时退订。",
    popupDelaySeconds: 18,
    verificationMode: "off",
    turnstileEnabled: false,
    turnstileSiteKey: undefined,
    webPushEnabled: false,
    webPushVapidPublicKey: undefined,
  },
  seo: {
    faviconUrl: undefined,
    defaultOgImage: undefined,
    defaultTwitterHandle: undefined,
    wechatShareQrEnabled: false,
  },
};

const formatPostDate = (dateString: string) => dateString.slice(0, 10);

const estimateReadTime = (content: string | null | undefined) => {
  const text = content?.trim() || "";
  const minutes = Math.max(1, Math.ceil(text.length / 300));
  return `${minutes} 分钟`;
};

const normalizePost = (post: ApiPost): UiPost => ({
  id: String(post.id),
  slug: post.slug,
  title: post.title,
  description: post.description,
  content: post.content,
  date: formatPostDate(post.created_at),
  createdAt: post.created_at,
  updatedAt: post.updated_at,
  readTime: estimateReadTime(post.content || post.description),
  type: post.post_type,
  tags: post.tags ?? [],
  category: post.category,
  image: post.image ?? undefined,
  images: post.images ?? undefined,
  pinned: post.pinned,
  status: post.status ?? undefined,
  visibility: post.visibility ?? undefined,
  publishAt: post.publish_at ?? undefined,
  unpublishAt: post.unpublish_at ?? undefined,
  canonicalUrl: post.canonical_url ?? undefined,
  noindex: post.noindex ?? undefined,
  ogImage: post.og_image ?? undefined,
  redirectFrom: post.redirect_from ?? undefined,
  redirectTo: post.redirect_to ?? undefined,
});

const normalizeTag = (tag: ApiTag): UiTag => ({
  id: String(tag.id),
  name: tag.name,
  slug: tag.slug,
  count: tag.count,
  description: tag.description ?? undefined,
  coverImage: tag.cover_image ?? undefined,
  accentColor: tag.accent_color ?? undefined,
  seoTitle: tag.seo_title ?? undefined,
  seoDescription: tag.seo_description ?? undefined,
});

const normalizeCategory = (category: ApiCategory): UiCategory => ({
  id: String(category.id),
  name: category.name,
  slug: category.slug,
  count: category.count,
  description: category.description ?? undefined,
  coverImage: category.cover_image ?? undefined,
  accentColor: category.accent_color ?? undefined,
  seoTitle: category.seo_title ?? undefined,
  seoDescription: category.seo_description ?? undefined,
});

const normalizeAvatarUrl = (value: string | null | undefined) => {
  if (!value) {
    return undefined;
  }

  try {
    const host = new URL(value).hostname.toLowerCase();
    const isReservedExampleHost =
      host === "example.com" ||
      host === "example.org" ||
      host === "example.net" ||
      host.endsWith(".example.com") ||
      host.endsWith(".example.org") ||
      host.endsWith(".example.net");

    return isReservedExampleHost ? undefined : value;
  } catch {
    return undefined;
  }
};

const normalizeTagToken = (value: string) => value.trim().toLowerCase();

const normalizeFriendLink = (friendLink: ApiFriendLink): AppFriendLink => ({
  id: String(friendLink.id),
  name: friendLink.site_name,
  url: friendLink.site_url,
  avatar: normalizeAvatarUrl(friendLink.avatar_url),
  description: friendLink.description ?? undefined,
  category: friendLink.category ?? undefined,
  status: friendLink.status,
});

const normalizeSiteSettings = (settings: ApiSiteSettings): SiteSettings => {
  const commentVerificationMode = normalizeVerificationMode(
    settings.comment_verification_mode,
    settings.comment_turnstile_enabled ? "turnstile" : "captcha",
  );
  const subscriptionVerificationMode = normalizeVerificationMode(
    settings.subscription_verification_mode,
    settings.subscription_turnstile_enabled ? "turnstile" : "off",
  );
  const musicEnabled = settings.music_enabled ?? true;
  const normalizedMusicPlaylist = settings.music_playlist?.filter(
    (item) => item?.title?.trim() && item?.url?.trim(),
  )?.length
    ? settings.music_playlist
        .filter((item) => item.title.trim() && item.url.trim())
        .map((item) => ({
          title: item.title,
          artist: item.artist ?? undefined,
          album: item.album ?? undefined,
          url: item.url,
          coverImageUrl: item.cover_image_url ?? undefined,
          accentColor: item.accent_color ?? undefined,
          description: item.description ?? undefined,
        }))
    : DEFAULT_SITE_SETTINGS.musicPlaylist;

  return {
    id: String(settings.id),
    siteName: settings.site_name || DEFAULT_SITE_SETTINGS.siteName,
    siteShortName:
      settings.site_short_name || DEFAULT_SITE_SETTINGS.siteShortName,
    siteUrl: settings.site_url || DEFAULT_SITE_SETTINGS.siteUrl,
    siteTitle: settings.site_title || DEFAULT_SITE_SETTINGS.siteTitle,
    siteDescription:
      settings.site_description || DEFAULT_SITE_SETTINGS.siteDescription,
    heroTitle: settings.hero_title || DEFAULT_SITE_SETTINGS.heroTitle,
    heroSubtitle: settings.hero_subtitle || DEFAULT_SITE_SETTINGS.heroSubtitle,
    ownerName: settings.owner_name || DEFAULT_SITE_SETTINGS.ownerName,
    ownerTitle: settings.owner_title || DEFAULT_SITE_SETTINGS.ownerTitle,
    ownerBio: settings.owner_bio || DEFAULT_SITE_SETTINGS.ownerBio,
    ownerAvatarUrl: settings.owner_avatar_url ?? undefined,
    location: settings.location || DEFAULT_SITE_SETTINGS.location,
    social: {
      github: settings.social_github || DEFAULT_SITE_SETTINGS.social.github,
      twitter: settings.social_twitter || DEFAULT_SITE_SETTINGS.social.twitter,
      email: settings.social_email || DEFAULT_SITE_SETTINGS.social.email,
    },
    techStack: settings.tech_stack?.length
      ? settings.tech_stack
      : DEFAULT_SITE_SETTINGS.techStack,
    musicEnabled,
    musicPlaylist: musicEnabled ? normalizedMusicPlaylist : [],
    ai: {
      enabled: Boolean(settings.ai_enabled),
    },
    comments: {
      verificationMode: commentVerificationMode,
      paragraphsEnabled: settings.paragraph_comments_enabled ?? true,
      turnstileEnabled: commentVerificationMode === "turnstile",
      turnstileSiteKey:
        settings.turnstile_site_key ||
        resolvePublicCommentTurnstileSiteKey() ||
        undefined,
    },
    subscriptions: {
      popupEnabled:
        settings.subscription_popup_enabled ??
        DEFAULT_SITE_SETTINGS.subscriptions.popupEnabled,
      popupTitle:
        settings.subscription_popup_title ||
        DEFAULT_SITE_SETTINGS.subscriptions.popupTitle,
      popupDescription:
        settings.subscription_popup_description ||
        DEFAULT_SITE_SETTINGS.subscriptions.popupDescription,
      popupDelaySeconds:
        settings.subscription_popup_delay_seconds ??
        DEFAULT_SITE_SETTINGS.subscriptions.popupDelaySeconds,
      verificationMode: subscriptionVerificationMode,
      turnstileEnabled: subscriptionVerificationMode === "turnstile",
      turnstileSiteKey:
        settings.turnstile_site_key ||
        resolvePublicCommentTurnstileSiteKey() ||
        undefined,
      webPushEnabled: Boolean(settings.web_push_enabled),
      webPushVapidPublicKey:
        settings.web_push_vapid_public_key ||
        resolvePublicWebPushVapidPublicKey() ||
        undefined,
    },
    seo: {
      faviconUrl: settings.seo_favicon_url ?? undefined,
      defaultOgImage: settings.seo_default_og_image ?? undefined,
      defaultTwitterHandle: settings.seo_default_twitter_handle ?? undefined,
      wechatShareQrEnabled: Boolean(settings.seo_wechat_share_qr_enabled),
    },
  };
};

const normalizeContentOverview = (
  overview: ApiHomePagePayload["content_overview"] | undefined,
): ContentOverview => ({
  totalPageViews: overview?.total_page_views ?? 0,
  pageViewsLast24h: overview?.page_views_last_24h ?? 0,
  pageViewsLast7d: overview?.page_views_last_7d ?? 0,
  totalReadCompletes: overview?.total_read_completes ?? 0,
  readCompletesLast7d: overview?.read_completes_last_7d ?? 0,
  avgReadProgressLast7d: overview?.avg_read_progress_last_7d ?? 0,
  avgReadDurationMsLast7d: overview?.avg_read_duration_ms_last_7d ?? undefined,
});

const CONTENT_WINDOW_META = [
  { key: "24h", label: "24h", days: 1 },
  { key: "7d", label: "7d", days: 7 },
  { key: "30d", label: "30d", days: 30 },
] as const;

const normalizePopularPost = (
  item: {
    slug: string;
    title: string;
    page_views: number;
    read_completes: number;
    avg_progress_percent: number;
    avg_duration_ms: number | null;
  },
  postsBySlug: Map<string, UiPost>,
): PopularPostHighlight => ({
  slug: item.slug,
  title: item.title,
  pageViews: item.page_views,
  readCompletes: item.read_completes,
  avgProgressPercent: item.avg_progress_percent,
  avgDurationMs: item.avg_duration_ms ?? undefined,
  post: postsBySlug.get(item.slug),
});

const normalizeContentRanges = (
  ranges: ApiHomePagePayload["content_ranges"] | undefined,
  overview: ApiHomePagePayload["content_overview"] | undefined,
  popularPosts: ApiHomePagePayload["popular_posts"] | undefined,
  postsBySlug: Map<string, UiPost>,
): ContentWindowHighlight[] => {
  const normalizedRanges = new Map(
    (ranges ?? []).map((item) => [
      item.key,
      {
        key: item.key,
        label: item.label,
        days: item.days,
        overview: {
          pageViews: item.overview?.page_views ?? 0,
          readCompletes: item.overview?.read_completes ?? 0,
          avgReadProgress: item.overview?.avg_read_progress ?? 0,
          avgReadDurationMs: item.overview?.avg_read_duration_ms ?? undefined,
        },
        popularPosts: (item.popular_posts ?? []).map((popularItem) =>
          normalizePopularPost(popularItem, postsBySlug),
        ),
      },
    ]),
  );

  return CONTENT_WINDOW_META.map((meta) => {
    const existing = normalizedRanges.get(meta.key);
    if (existing) {
      return existing;
    }

    if (meta.key === "7d") {
      return {
        key: meta.key,
        label: meta.label,
        days: meta.days,
        overview: {
          pageViews: overview?.page_views_last_7d ?? 0,
          readCompletes: overview?.read_completes_last_7d ?? 0,
          avgReadProgress: overview?.avg_read_progress_last_7d ?? 0,
          avgReadDurationMs:
            overview?.avg_read_duration_ms_last_7d ?? undefined,
        },
        popularPosts: (popularPosts ?? []).map((item) =>
          normalizePopularPost(item, postsBySlug),
        ),
      };
    }

    if (meta.key === "24h") {
      return {
        key: meta.key,
        label: meta.label,
        days: meta.days,
        overview: {
          pageViews: overview?.page_views_last_24h ?? 0,
          readCompletes: 0,
          avgReadProgress: 0,
          avgReadDurationMs: undefined,
        },
        popularPosts: [],
      };
    }

    return {
      key: meta.key,
      label: meta.label,
      days: meta.days,
      overview: {
        pageViews: 0,
        readCompletes: 0,
        avgReadProgress: 0,
        avgReadDurationMs: undefined,
      },
      popularPosts: [],
    };
  });
};

class ApiClient {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  private async fetch<T>(path: string, options?: RequestInit): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`, {
      ...options,
      headers: {
        "Content-Type": "application/json",
        ...options?.headers,
      },
    });

    if (!response.ok) {
      const errorText = await response.text().catch(() => "");
      throw new Error(
        errorText || `API error: ${response.status} ${response.statusText}`,
      );
    }

    if (response.status === 204) {
      return undefined as T;
    }

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

  async getRawPosts(): Promise<ApiPost[]> {
    return this.fetch<ApiPost[]>("/posts");
  }

  async getPosts(): Promise<UiPost[]> {
    const posts = await this.getRawPosts();
    return posts.map(normalizePost);
  }

  async getPostsPage(options?: {
    page?: number;
    pageSize?: number;
    search?: string;
    category?: string;
    tag?: string;
    postType?: string;
    sortBy?: string;
    sortOrder?: string;
  }): Promise<{
    items: UiPost[];
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
    sortBy: string;
    sortOrder: string;
  }> {
    const params = new URLSearchParams();
    if (options?.page) params.set("page", String(options.page));
    if (options?.pageSize) params.set("page_size", String(options.pageSize));
    if (options?.search) params.set("search", options.search);
    if (options?.category) params.set("category", options.category);
    if (options?.tag) params.set("tag", options.tag);
    if (options?.postType) params.set("type", options.postType);
    if (options?.sortBy) params.set("sort_by", options.sortBy);
    if (options?.sortOrder) params.set("sort_order", options.sortOrder);

    const payload = await this.fetch<ApiPagedResponse<ApiPost>>(
      `/posts/page?${params.toString()}`,
    );
    return {
      items: payload.items.map(normalizePost),
      page: payload.page,
      pageSize: payload.page_size,
      total: payload.total,
      totalPages: payload.total_pages,
      sortBy: payload.sort_by,
      sortOrder: payload.sort_order,
    };
  }

  async getPost(id: number): Promise<UiPost> {
    const post = await this.fetch<ApiPost>(`/posts/${id}`);
    return normalizePost(post);
  }

  async getPostBySlug(slug: string): Promise<UiPost | null> {
    const response = await fetch(
      `${this.baseUrl}/posts/slug/${encodeURIComponent(slug)}`,
      {
        headers: {
          "Content-Type": "application/json",
        },
      },
    );

    if (response.status === 404) {
      return null;
    }

    if (!response.ok) {
      const errorText = await response.text().catch(() => "");
      throw new Error(
        errorText || `API error: ${response.status} ${response.statusText}`,
      );
    }

    return normalizePost((await response.json()) as ApiPost);
  }

  async recordContentEvent(input: ContentAnalyticsInput): Promise<void> {
    await this.fetch<{ recorded: boolean }>("/analytics/content", {
      method: "POST",
      body: JSON.stringify({
        event_type: input.eventType,
        path: input.path,
        post_slug: input.postSlug,
        session_id: input.sessionId,
        duration_ms: input.durationMs,
        progress_percent: input.progressPercent,
        metadata: input.metadata,
        referrer: input.referrer,
      }),
    });
  }

  async getComments(
    postSlug: string,
    options?: {
      approved?: boolean;
      scope?: "article" | "paragraph";
      paragraphKey?: string;
    },
  ): Promise<Comment[]> {
    const params = new URLSearchParams({ post_slug: postSlug });
    if (options?.approved !== undefined) {
      params.set("approved", String(options.approved));
    }
    if (options?.scope) {
      params.set("scope", options.scope);
    }
    if (options?.paragraphKey) {
      params.set("paragraph_key", options.paragraphKey);
    }
    return this.fetch<Comment[]>(`/comments?${params.toString()}`);
  }

  async getParagraphCommentSummary(
    postSlug: string,
  ): Promise<ParagraphCommentSummary[]> {
    const params = new URLSearchParams({ post_slug: postSlug });
    return this.fetch<ParagraphCommentSummary[]>(
      `/comments/paragraphs/summary?${params.toString()}`,
    );
  }

  async createComment(comment: CreateCommentInput): Promise<Comment> {
    return this.fetch<Comment>("/comments", {
      method: "POST",
      body: JSON.stringify({
        postSlug: comment.postSlug,
        nickname: comment.nickname,
        email: comment.email,
        content: comment.content,
        scope: comment.scope,
        paragraphKey: comment.paragraphKey,
        paragraphExcerpt: comment.paragraphExcerpt,
        replyTo: comment.replyTo,
        replyToCommentId: comment.replyToCommentId,
        turnstileToken: comment.turnstileToken,
        captchaToken: comment.captchaToken,
        captchaAnswer: comment.captchaAnswer,
        website: comment.website,
      }),
    });
  }

  async getCommentCaptcha(): Promise<CommentCaptchaChallenge> {
    return this.fetch<CommentCaptchaChallenge>("/comments/captcha");
  }

  async getReviews(): Promise<Review[]> {
    return this.fetch<Review[]>("/reviews");
  }

  async getReview(id: number): Promise<Review> {
    return this.fetch<Review>(`/reviews/${id}`);
  }

  async getRawFriendLinks(): Promise<ApiFriendLink[]> {
    return this.fetch<ApiFriendLink[]>("/friend_links");
  }

  async getFriendLinks(): Promise<AppFriendLink[]> {
    const friendLinks = await this.getRawFriendLinks();
    return friendLinks.map(normalizeFriendLink);
  }

  async createFriendLink(
    friendLink: CreateFriendLinkInput,
  ): Promise<ApiFriendLink> {
    return this.fetch<ApiFriendLink>("/friend_links", {
      method: "POST",
      body: JSON.stringify(friendLink),
    });
  }

  async subscribe(input: {
    email: string;
    displayName?: string;
    source?: string;
    turnstileToken?: string;
    captchaToken?: string;
    captchaAnswer?: string;
  }): Promise<PublicSubscriptionResponse> {
    return this.fetch<PublicSubscriptionResponse>("/subscriptions", {
      method: "POST",
      body: JSON.stringify({
        email: input.email,
        displayName: input.displayName,
        source: input.source,
        turnstileToken: input.turnstileToken,
        captchaToken: input.captchaToken,
        captchaAnswer: input.captchaAnswer,
      }),
    });
  }

  async confirmSubscription(
    token: string,
  ): Promise<PublicSubscriptionManageResponse> {
    return this.fetch<PublicSubscriptionManageResponse>(
      "/subscriptions/confirm",
      {
        method: "POST",
        body: JSON.stringify({ token }),
      },
    );
  }

  async getManagedSubscription(
    token: string,
  ): Promise<PublicSubscriptionManageResponse> {
    return this.fetch<PublicSubscriptionManageResponse>(
      `/subscriptions/manage?token=${encodeURIComponent(token)}`,
    );
  }

  async updateManagedSubscription(input: {
    token: string;
    displayName?: string | null;
    status?: string | null;
    filters?: Record<string, unknown> | null;
  }): Promise<PublicSubscriptionManageResponse> {
    return this.fetch<PublicSubscriptionManageResponse>(
      "/subscriptions/manage",
      {
        method: "PATCH",
        body: JSON.stringify({
          token: input.token,
          displayName: input.displayName,
          status: input.status,
          filters: input.filters,
        }),
      },
    );
  }

  async unsubscribeSubscription(
    token: string,
  ): Promise<PublicSubscriptionManageResponse> {
    return this.fetch<PublicSubscriptionManageResponse>(
      "/subscriptions/unsubscribe",
      {
        method: "POST",
        body: JSON.stringify({ token }),
      },
    );
  }

  async getRawTags(): Promise<ApiTag[]> {
    return this.fetch<ApiTag[]>("/tags");
  }

  async getTags(): Promise<UiTag[]> {
    const tags = await this.getRawTags();
    return tags.map(normalizeTag);
  }

  async getRawSiteSettings(): Promise<ApiSiteSettings> {
    return this.fetch<ApiSiteSettings>("/site_settings");
  }

  async getSiteSettings(): Promise<SiteSettings> {
    const settings = await this.getRawSiteSettings();
    return normalizeSiteSettings(settings);
  }

  async getHomePageData(): Promise<{
    siteSettings: SiteSettings;
    posts: UiPost[];
    tags: UiTag[];
    friendLinks: AppFriendLink[];
    categories: UiCategory[];
    contentOverview: ContentOverview;
    contentRanges: ContentWindowHighlight[];
    popularPosts: PopularPostHighlight[];
  }> {
    const payload = await this.fetch<ApiHomePagePayload>("/site_settings/home");
    const posts = (payload.posts ?? []).map(normalizePost);
    const postsBySlug = new Map(posts.map((post) => [post.slug, post]));
    const popularPosts = (payload.popular_posts ?? []).map((item) =>
      normalizePopularPost(item, postsBySlug),
    );

    return {
      siteSettings: normalizeSiteSettings(payload.site_settings),
      posts,
      tags: (payload.tags ?? []).map(normalizeTag),
      friendLinks: (payload.friend_links ?? []).map(normalizeFriendLink),
      categories: (payload.categories ?? []).map(normalizeCategory),
      contentOverview: normalizeContentOverview(payload.content_overview),
      contentRanges: normalizeContentRanges(
        payload.content_ranges,
        payload.content_overview,
        payload.popular_posts,
        postsBySlug,
      ),
      popularPosts,
    };
  }

  async getCategories(): Promise<UiCategory[]> {
    const categories = await this.fetch<ApiCategory[]>("/categories");
    return categories.map(normalizeCategory);
  }

  async getPostsByCategory(category: string): Promise<UiPost[]> {
    const posts = await this.getPosts();
    return posts.filter(
      (post) => post.category?.toLowerCase() === category.toLowerCase(),
    );
  }

  async getPostsByTag(tag: string): Promise<UiPost[]> {
    const posts = await this.getPosts();
    const normalizedTag = normalizeTagToken(tag);
    return posts.filter((post) =>
      post.tags?.some((item) => normalizeTagToken(item) === normalizedTag),
    );
  }

  async searchPosts(query: string, limit = 20): Promise<UiPost[]> {
    const params = new URLSearchParams({
      q: query,
      limit: String(limit),
    });
    const results = await this.fetch<ApiSearchResult[]>(
      `/search?${params.toString()}`,
    );

    return results.map((result) =>
      normalizePost({
        id: result.id,
        title: result.title || "Untitled",
        slug: result.slug,
        description: result.description || "",
        content: result.content || "",
        category: result.category || "",
        tags: result.tags ?? [],
        post_type: result.post_type || "article",
        image: result.image,
        images: null,
        pinned: result.pinned ?? false,
        status: null,
        visibility: null,
        publish_at: null,
        unpublish_at: null,
        canonical_url: null,
        noindex: null,
        og_image: null,
        redirect_from: null,
        redirect_to: null,
        created_at: result.created_at,
        updated_at: result.updated_at,
      }),
    );
  }

  async searchPostsPage(options: {
    query: string;
    page?: number;
    pageSize?: number;
    category?: string;
    tag?: string;
    postType?: string;
    sortBy?: string;
    sortOrder?: string;
  }): Promise<{
    query: string;
    items: UiPost[];
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
    sortBy: string;
    sortOrder: string;
  }> {
    const params = new URLSearchParams({ q: options.query });
    if (options.page) params.set("page", String(options.page));
    if (options.pageSize) params.set("page_size", String(options.pageSize));
    if (options.category) params.set("category", options.category);
    if (options.tag) params.set("tag", options.tag);
    if (options.postType) params.set("type", options.postType);
    if (options.sortBy) params.set("sort_by", options.sortBy);
    if (options.sortOrder) params.set("sort_order", options.sortOrder);

    const payload = await this.fetch<ApiPagedSearchResponse>(
      `/search/page?${params.toString()}`,
    );
    return {
      query: payload.query,
      items: payload.items.map((result) =>
        normalizePost({
          id: result.id,
          title: result.title || "Untitled",
          slug: result.slug,
          description: result.description || "",
          content: result.content || "",
          category: result.category || "",
          tags: result.tags ?? [],
          post_type: result.post_type || "article",
          image: result.image,
          images: null,
          pinned: result.pinned ?? false,
          status: null,
          visibility: null,
          publish_at: null,
          unpublish_at: null,
          canonical_url: null,
          noindex: null,
          og_image: null,
          redirect_from: null,
          redirect_to: null,
          created_at: result.created_at,
          updated_at: result.updated_at,
        }),
      ),
      page: payload.page,
      pageSize: payload.page_size,
      total: payload.total,
      totalPages: payload.total_pages,
      sortBy: payload.sort_by,
      sortOrder: payload.sort_order,
    };
  }

  async askAi(question: string): Promise<AiAskResponse> {
    return this.fetch<AiAskResponse>("/ai/ask", {
      method: "POST",
      body: JSON.stringify({ question }),
    });
  }
}

export function createApiClient(options?: {
  baseUrl?: string;
  requestUrl?: string | URL;
}) {
  return new ApiClient(
    options?.baseUrl ?? resolveInternalApiBaseUrl(options?.requestUrl),
  );
}

export const api = createApiClient();
export const apiClient = api;
