/**
 * WordPress API Client
 * Handles all REST API communication with WordPress
 *
 * This module has been refactored to use a modular architecture with
 * domain-specific operations extracted into separate modules under ./operations/
 */

// Use native fetch in Node.js 18+
import FormData from "form-data";
import { getUserAgent } from "@/utils/version.js";
import type {
  IWordPressClient,
  WordPressClientConfig,
  AuthConfig,
  AuthMethod,
  HTTPMethod,
  RequestOptions,
  ClientStats,
} from "@/types/client.js";
import { WordPressAPIError, AuthenticationError, RateLimitError } from "@/types/client.js";
import { config } from "@/config/Config.js";
import type {
  WordPressPost,
  WordPressPage,
  WordPressMedia,
  WordPressUser,
  WordPressComment,
  WordPressCategory,
  WordPressTag,
  WordPressSiteSettings,
  WordPressApplicationPassword,
  PostQueryParams,
  MediaQueryParams,
  UserQueryParams,
  CommentQueryParams,
  CreatePostRequest,
  UpdatePostRequest,
  CreatePageRequest,
  UpdatePageRequest,
  CreateUserRequest,
  UpdateUserRequest,
  CreateCommentRequest,
  UpdateCommentRequest,
  CreateCategoryRequest,
  UpdateCategoryRequest,
  CreateTagRequest,
  UpdateTagRequest,
  UploadMediaRequest,
  UpdateMediaRequest,
  WordPressSiteInfo,
  WordPressSearchResult,
} from "@/types/wordpress.js";
import { LoggerFactory, startTimer } from "@/utils/logger.js";

const log = LoggerFactory.client("API");
import type { QueuedRequest } from "@/types/requests.js";

// Import domain-specific operations
import { PostsOperations } from "./operations/posts.js";
import { PagesOperations } from "./operations/pages.js";
import { MediaOperations } from "./operations/media.js";
import { UsersOperations } from "./operations/users.js";
import { CommentsOperations } from "./operations/comments.js";
import { TaxonomiesOperations } from "./operations/taxonomies.js";
import { SiteOperations } from "./operations/site.js";

/**
 * WordPress REST API Client
 *
 * A comprehensive client for interacting with the WordPress REST API v2.
 * Provides full CRUD operations for posts, pages, media, users, comments,
 * categories, tags, and site settings with robust error handling and performance optimization.
 *
 * Features:
 * - Multiple authentication methods (App Passwords, JWT, Basic Auth, API Key)
 * - Automatic retry logic with exponential backoff
 * - Request rate limiting and queue management
 * - Comprehensive error handling with detailed messages
 * - Performance monitoring and request statistics
 * - Caching support for improved performance
 * - Multi-site configuration support
 * - Modular architecture with domain-specific operations
 *
 * @example
 * ```typescript
 * // Initialize with app password authentication
 * const client = new WordPressClient({
 *   baseUrl: 'https://mysite.com',
 *   auth: {
 *     method: 'app-password',
 *     username: 'admin',
 *     password: 'xxxx xxxx xxxx xxxx xxxx xxxx'
 *   }
 * });
 *
 * // Create a new post
 * const post = await client.createPost({
 *   title: 'My New Post',
 *   content: '<p>This is the content</p>',
 *   status: 'publish'
 * });
 *
 * // List posts with filtering
 * const posts = await client.getPosts({
 *   search: 'WordPress',
 *   status: 'publish',
 *   per_page: 10
 * });
 * ```
 *
 * @since 1.0.0
 * @author MCP WordPress Team
 * @implements {IWordPressClient}
 */
export class WordPressClient implements IWordPressClient {
  private baseUrl: string;
  private apiUrl: string;
  private timeout: number;
  private maxRetries: number;
  private auth: AuthConfig;
  private requestQueue: QueuedRequest[] = [];
  private lastRequestTime: number = 0;
  private requestInterval: number;
  private authenticated: boolean = false;
  private jwtToken: string | null = null;
  private _stats: ClientStats;

  // Domain-specific operation handlers
  private readonly postsOps: PostsOperations;
  private readonly pagesOps: PagesOperations;
  private readonly mediaOps: MediaOperations;
  private readonly usersOps: UsersOperations;
  private readonly commentsOps: CommentsOperations;
  private readonly taxonomiesOps: TaxonomiesOperations;
  private readonly siteOps: SiteOperations;

  /**
   * Creates a new WordPress API client instance.
   *
   * Initializes the client with configuration options for connecting to a WordPress site.
   * Supports multiple authentication methods and automatic environment variable detection.
   *
   * @param {Partial<WordPressClientConfig>} [options={}] - Configuration options for the client
   * @param {string} [options.baseUrl] - WordPress site URL (falls back to WORDPRESS_SITE_URL env var)
   * @param {number} [options.timeout=30000] - Request timeout in milliseconds
   * @param {number} [options.maxRetries=3] - Maximum number of retry attempts for failed requests
   * @param {AuthConfig} [options.auth] - Authentication configuration (auto-detected from env if not provided)
   * @param {boolean} [options.enableCache=true] - Whether to enable response caching
   * @param {number} [options.cacheMaxAge=300000] - Cache max age in milliseconds (5 minutes default)
   *
   * @throws {Error} When required configuration is missing or invalid
   *
   * @since 1.0.0
   */
  constructor(options: Partial<WordPressClientConfig> = {}) {
    const cfg = config();
    const baseUrl = options.baseUrl || cfg.wordpress.siteUrl || "";

    // Validate and sanitize base URL
    this.baseUrl = this.validateAndSanitizeUrl(baseUrl);
    this.apiUrl = "";
    this.timeout = options.timeout || cfg.wordpress.timeout;
    this.maxRetries = options.maxRetries || cfg.wordpress.maxRetries;

    // Authentication configuration
    if (options.auth) {
      // If auth is provided but without method, infer it
      if (!options.auth.method) {
        const auth = options.auth as AuthConfig & {
          username?: string;
          appPassword?: string;
          password?: string;
          secret?: string;
          apiKey?: string;
        };
        if (auth.username && auth.appPassword) {
          this.auth = { ...auth, method: "app-password" };
        } else if (auth.username && auth.password && auth.secret) {
          this.auth = { ...auth, method: "jwt" };
        } else if (auth.username && auth.password) {
          this.auth = { ...auth, method: "basic" };
        } else if (auth.apiKey) {
          this.auth = { ...auth, method: "api-key" };
        } else {
          this.auth = { ...auth, method: "app-password" }; // default
        }
      } else {
        this.auth = options.auth;
      }
    } else {
      this.auth = this.getAuthFromEnv();
    }

    // Rate limiting
    this.requestInterval = 60000 / cfg.security.rateLimit;

    // Initialize stats
    this._stats = {
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      averageResponseTime: 0,
      rateLimitHits: 0,
      authFailures: 0,
      errors: 0,
    };

    // Validate configuration
    this.validateConfig();

    // Initialize domain-specific operations with this client as the base
    this.postsOps = new PostsOperations(this);
    this.pagesOps = new PagesOperations(this);
    this.mediaOps = new MediaOperations(this);
    this.usersOps = new UsersOperations(this);
    this.commentsOps = new CommentsOperations(this);
    this.taxonomiesOps = new TaxonomiesOperations(this);
    this.siteOps = new SiteOperations(this);
  }

  get config(): WordPressClientConfig {
    return {
      baseUrl: this.baseUrl,
      auth: this.auth,
      timeout: this.timeout,
      maxRetries: this.maxRetries,
    };
  }

  get isAuthenticated(): boolean {
    return this.authenticated;
  }

  get stats(): ClientStats {
    return { ...this._stats };
  }

  getSiteUrl(): string {
    return this.baseUrl;
  }

  /**
   * Validate and sanitize URL for security
   */
  private validateAndSanitizeUrl(url: string): string {
    if (!url) {
      throw new Error("WordPress site URL is required");
    }

    try {
      const parsed = new URL(url);

      // Only allow HTTP/HTTPS protocols
      if (!["http:", "https:"].includes(parsed.protocol)) {
        throw new Error("Only HTTP and HTTPS protocols are allowed");
      }

      // Prevent localhost/private IP access unless explicitly allowed
      // Set ALLOW_PRIVATE_URLS=true for local development with a local WordPress
      if (process.env.ALLOW_PRIVATE_URLS !== "true") {
        const hostname = parsed.hostname.toLowerCase();
        if (
          hostname === "localhost" ||
          hostname === "127.0.0.1" ||
          hostname === "::1" ||
          hostname.match(/^10\./) ||
          hostname.match(/^172\.(1[6-9]|2[0-9]|3[01])\./) ||
          hostname.match(/^192\.168\./)
        ) {
          throw new Error("Private/localhost URLs not allowed. Set ALLOW_PRIVATE_URLS=true for local development.");
        }
      }

      // Return clean URL without query parameters or fragments
      return `${parsed.protocol}//${parsed.host}${parsed.pathname}`.replace(/\/$/, "");
    } catch (_error) {
      if (_error instanceof TypeError) {
        throw new Error("Invalid WordPress site URL format");
      }
      throw _error;
    }
  }

  private getAuthFromEnv(): AuthConfig {
    const cfg = config();
    const wp = cfg.wordpress;
    const authMethod = wp.authMethod as AuthMethod;

    // Use explicit auth method if set
    if (authMethod === "app-password" && wp.username && wp.appPassword) {
      return {
        method: "app-password",
        username: wp.username,
        appPassword: wp.appPassword,
      };
    }

    // Try Application Password first (fallback)
    if (wp.username && wp.appPassword) {
      return {
        method: "app-password",
        username: wp.username,
        appPassword: wp.appPassword,
      };
    }

    // Try JWT
    if (wp.jwtSecret && wp.username && wp.password) {
      return {
        method: "jwt",
        secret: wp.jwtSecret,
        username: wp.username,
        password: wp.password,
      };
    }

    // Try API Key
    if (wp.apiKey) {
      return {
        method: "api-key",
        apiKey: wp.apiKey,
      };
    }

    // Try Cookie
    if (wp.cookieNonce) {
      return {
        method: "cookie",
        nonce: wp.cookieNonce,
      };
    }

    // Default to basic authentication
    return {
      method: "basic",
      username: wp.username || "",
      password: wp.password || wp.appPassword || "",
    };
  }

  private validateConfig(): void {
    if (!this.baseUrl) {
      throw new Error("WordPress configuration is incomplete: baseUrl is required");
    }

    // Ensure URL doesn't end with slash and add API path
    this.baseUrl = this.baseUrl.replace(/\/$/, "");
    this.apiUrl = `${this.baseUrl}/wp-json/wp/v2`;

    log.debug(`WordPress API Client initialized for: ${this.apiUrl}`);
  }

  async initialize(): Promise<void> {
    await this.authenticate();
  }

  async disconnect(): Promise<void> {
    this.authenticated = false;
    this.jwtToken = null;
    log.debug("WordPress client disconnected");
  }

  /**
   * Add authentication headers to request
   */
  private addAuthHeaders(headers: Record<string, string>): void {
    const method = this.auth.method?.toLowerCase() as AuthMethod;

    switch (method) {
      case "app-password":
        if (this.auth.username && this.auth.appPassword) {
          const credentials = Buffer.from(`${this.auth.username}:${this.auth.appPassword}`).toString("base64");
          headers["Authorization"] = `Basic ${credentials}`;
        }
        break;
      case "basic":
        if (this.auth.username && this.auth.password) {
          const credentials = Buffer.from(`${this.auth.username}:${this.auth.password}`).toString("base64");
          headers["Authorization"] = `Basic ${credentials}`;
        }
        break;

      case "jwt":
        if (this.jwtToken) {
          headers["Authorization"] = `Bearer ${this.jwtToken}`;
        }
        break;

      case "api-key":
        if (this.auth.apiKey) {
          headers["X-API-Key"] = this.auth.apiKey;
        }
        break;

      case "cookie":
        if (this.auth.nonce) {
          headers["X-WP-Nonce"] = this.auth.nonce;
        }
        break;
    }
  }

  /**
   * Rate limiting implementation
   */
  private async rateLimit(): Promise<void> {
    const now = Date.now();
    const timeSinceLastRequest = now - this.lastRequestTime;

    if (timeSinceLastRequest < this.requestInterval) {
      const delay = this.requestInterval - timeSinceLastRequest;
      await this.delay(delay);
    }

    this.lastRequestTime = Date.now();
  }

  /**
   * Delay utility
   */
  private delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async authenticate(): Promise<boolean> {
    const method = this.auth.method?.toLowerCase() as AuthMethod;

    try {
      switch (method) {
        case "app-password":
        case "basic":
          return await this.authenticateWithBasic();
        case "jwt":
          return await this.authenticateWithJWT();
        case "cookie":
          return await this.authenticateWithCookie();
        case "api-key":
          // API key auth doesn't require separate authentication step
          this.authenticated = true;
          return true;
        default:
          throw new Error(`Unsupported authentication method: ${method}`);
      }
    } catch (_error) {
      this._stats.authFailures++;
      log.error(_error as Error, { method });
      throw _error;
    }
  }

  /**
   * Authenticate using Basic/Application Password
   */
  private async authenticateWithBasic(): Promise<boolean> {
    const hasCredentials =
      this.auth.username && (this.auth.method === "app-password" ? this.auth.appPassword : this.auth.password);

    if (!hasCredentials) {
      const methodName = this.auth.method === "app-password" ? "Application Password" : "Basic";
      const passwordField = this.auth.method === "app-password" ? "app password" : "password";
      throw new AuthenticationError(
        `Username and ${passwordField} are required for ${methodName} authentication`,
        this.auth.method,
      );
    }

    try {
      // Test authentication by getting current user
      await this.request<WordPressUser>("GET", "users/me");
      this.authenticated = true;
      log.debug("Basic/Application Password authentication successful");
      return true;
    } catch (_error) {
      throw new AuthenticationError(`Basic authentication failed: ${(_error as Error).message}`, this.auth.method);
    }
  }

  /**
   * Authenticate using JWT
   */
  private async authenticateWithJWT(): Promise<boolean> {
    if (!this.auth.secret || !this.auth.username || !this.auth.password) {
      throw new AuthenticationError(
        "JWT secret, username, and password are required for JWT authentication",
        this.auth.method,
      );
    }

    try {
      const response = await fetch(`${this.baseUrl}/wp-json/jwt-auth/v1/token`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          username: this.auth.username,
          password: this.auth.password,
        }),
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const data = (await response.json()) as { token: string };
      this.jwtToken = data.token;
      this.authenticated = true;
      log.debug("JWT authentication successful");
      return true;
    } catch (_error) {
      throw new AuthenticationError(`JWT authentication failed: ${(_error as Error).message}`, this.auth.method);
    }
  }

  /**
   * Authenticate using Cookie
   */
  private async authenticateWithCookie(): Promise<boolean> {
    if (!this.auth.nonce) {
      throw new AuthenticationError("Nonce is required for cookie authentication", this.auth.method);
    }
    this.authenticated = true;
    log.debug("Cookie authentication configured");
    return true;
  }

  /**
   * Make authenticated request to WordPress REST API
   */
  async request<T = unknown>(
    method: HTTPMethod,
    endpoint: string,
    data: unknown = null,
    options: RequestOptions = {},
  ): Promise<T> {
    const timer = startTimer();
    this._stats.totalRequests++;

    const cleanEndpoint = endpoint.replace(/^\/+/, "");
    // Validate absolute URLs through the same SSRF guard as the site URL
    const url = endpoint.startsWith("http") ? this.validateAndSanitizeUrl(endpoint) : `${this.apiUrl}/${cleanEndpoint}`;

    const { headers: customHeaders, retries: retryOverride, params: _unusedParams, ...restOptions } = options;
    const baseHeaders: Record<string, string> = {
      "Content-Type": "application/json",
      "User-Agent": getUserAgent(),
      ...(customHeaders || {}),
    };

    this.addAuthHeaders(baseHeaders);

    const requestTimeout = options.timeout || this.timeout;
    const configuredRetries =
      typeof retryOverride === "number" && retryOverride > 0 ? retryOverride : this.maxRetries || 1;
    const canRetryBody = this.isRetryableBody(data);
    const maxAttempts = canRetryBody ? configuredRetries : 1;

    let lastError: Error = new Error("Unknown error");

    for (let attempt = 0; attempt < maxAttempts; attempt++) {
      await this.rateLimit();

      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), requestTimeout);

      const headers = { ...baseHeaders };
      const fetchOptions: RequestInit & { headers: Record<string, string> } = {
        ...restOptions,
        method,
        headers,
        signal: controller.signal,
      };

      if (data && ["POST", "PUT", "PATCH"].includes(method)) {
        this.attachRequestBody(fetchOptions, headers, data);
      }

      try {
        log.debug(`API Request: ${method} ${url}${attempt > 0 ? ` (attempt ${attempt + 1})` : ""}`);

        const response = await fetch(url, fetchOptions);

        if (!response.ok) {
          const fallbackResult = await this.handleErrorResponseWithFallback<T>(
            response,
            url,
            endpoint,
            requestTimeout,
            fetchOptions,
            timer,
          );
          if (fallbackResult !== undefined) {
            clearTimeout(timeoutId);
            return fallbackResult;
          }
          continue;
        }

        const result = await this.parseResponse<T>(response, endpoint, timer);
        clearTimeout(timeoutId);
        return result;
      } catch (_error) {
        clearTimeout(timeoutId);
        if (_error instanceof RateLimitError) {
          lastError = _error;
          break;
        }
        lastError = this.normalizeRequestError(_error, requestTimeout);
        log.debug(`Request failed (attempt ${attempt + 1}): ${lastError.message}`);

        const shouldRetry = this.shouldRetryError(lastError) && attempt < maxAttempts - 1;
        if (!shouldRetry) {
          break;
        }

        await this.delay(1000 * (attempt + 1));
      } finally {
        clearTimeout(timeoutId);
      }
    }

    this._stats.failedRequests++;
    this._stats.errors++;
    timer.end();
    throw new WordPressAPIError(
      `Request failed after ${maxAttempts} attempt${maxAttempts === 1 ? "" : "s"}: ${lastError.message}`,
    );
  }

  private attachRequestBody(
    fetchOptions: RequestInit & { headers: Record<string, string> },
    headers: Record<string, string>,
    data: unknown,
  ): void {
    if (
      data instanceof FormData ||
      (typeof data === "object" && data && "append" in data && typeof (data as FormData).append === "function")
    ) {
      if (typeof (data as { getHeaders?: () => Record<string, string> }).getHeaders === "function") {
        const formHeaders = (data as unknown as { getHeaders(): Record<string, string> }).getHeaders();
        Object.assign(headers, formHeaders);
      } else {
        delete headers["Content-Type"];
      }
      fetchOptions.body = data as FormData;
      return;
    }

    if (Buffer.isBuffer(data)) {
      fetchOptions.body = data;
      return;
    }

    if (typeof data === "string") {
      fetchOptions.body = data;
      return;
    }

    fetchOptions.body = JSON.stringify(data);
  }

  private normalizeRequestError(error: unknown, timeout: number): Error {
    if (error instanceof Error) {
      if (error.name === "AbortError") {
        return new Error(`Request timeout after ${timeout}ms`);
      }
      if (error.message.includes("socket hang up") || error.message.includes("ECONNRESET")) {
        return new Error(`Network connection lost during request: ${error.message}`);
      }
      return error;
    }
    return new Error(typeof error === "string" ? error : "Unknown error");
  }

  private shouldRetryError(error: Error): boolean {
    if (error instanceof WordPressAPIError && error.statusCode !== undefined) {
      // Only retry server-side (5xx) errors; all 4xx are client errors that won't resolve on retry
      return error.statusCode >= 500;
    }
    // Network-level errors worth retrying — includes "Network connection lost" which is the
    // normalized form of both ECONNRESET and "socket hang up" from normalizeRequestError
    const retryablePatterns = ["ECONNRESET", "ETIMEDOUT", "ENOTFOUND", "EAI_AGAIN", "Network connection lost"];
    const message = String(error.message ?? "");
    return retryablePatterns.some((pattern) => message.includes(pattern));
  }

  private isRetryableBody(data: unknown): boolean {
    if (!data) {
      return true;
    }

    if (typeof data === "string" || Buffer.isBuffer(data)) {
      return true;
    }

    if (data instanceof FormData) {
      return false;
    }

    if (typeof data === "object" && data && "pipe" in (data as Record<string, unknown>)) {
      const potentialStream = (data as Record<string, unknown>).pipe;
      if (typeof potentialStream === "function") {
        return false;
      }
    }

    return true;
  }

  private async handleErrorResponseWithFallback<T>(
    response: Response,
    url: string,
    originalEndpoint: string,
    requestTimeout: number,
    fetchOptions: RequestInit & { headers: Record<string, string> },
    timer: ReturnType<typeof startTimer>,
  ): Promise<T | undefined> {
    const errorText = new TextDecoder("utf-8").decode(await response.arrayBuffer());
    let errorMessage: string;

    try {
      const errorData = JSON.parse(errorText);
      errorMessage = errorData.message || errorData.error || `HTTP ${response.status}`;
    } catch {
      errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
    }

    if (response.status === 429) {
      this._stats.rateLimitHits++;
      throw new RateLimitError(errorMessage, Date.now() + 60000);
    }

    if (response.status === 403 && originalEndpoint.includes("media") && fetchOptions.method === "POST") {
      throw new AuthenticationError(
        "Media upload blocked: WordPress REST API media uploads appear to be disabled or restricted by a plugin/security policy. " +
          `Error: ${errorMessage}. ` +
          "Common causes: W3 Total Cache, security plugins, or custom REST API restrictions. " +
          "Please check WordPress admin settings or contact your system administrator.",
        this.auth.method,
      );
    }

    if (response.status === 401 && originalEndpoint.includes("media") && fetchOptions.method === "POST") {
      throw new AuthenticationError(
        `WordPress REST API media upload restriction detected: ${errorMessage}. ` +
          "This typically indicates that media uploads via REST API are disabled by WordPress configuration, " +
          "a security plugin (like W3 Total Cache, Borlabs Cookie), or server policy. " +
          "User has sufficient permissions but WordPress/plugins are blocking the upload.",
        this.auth.method,
      );
    }

    if (response.status === 404 && url.includes("/wp-json/wp/v2")) {
      const fallbackResult = await this.tryIndexPhpFallback<T>(url, requestTimeout, fetchOptions, timer);
      if (fallbackResult !== undefined) {
        return fallbackResult;
      }
    }

    throw new WordPressAPIError(errorMessage, response.status);
  }

  private async tryIndexPhpFallback<T>(
    url: string,
    requestTimeout: number,
    fetchOptions: RequestInit & { headers: Record<string, string> },
    timer: ReturnType<typeof startTimer>,
  ): Promise<T | undefined> {
    log.debug(`404 on pretty permalinks, trying index.php approach`);

    try {
      const urlObj = new URL(url);
      const endpointPath = urlObj.pathname.replace("/wp-json/wp/v2", "");
      const queryParams = urlObj.searchParams.toString();

      let fallbackUrl = `${urlObj.origin}/index.php?rest_route=/wp/v2${endpointPath}`;
      if (queryParams) {
        fallbackUrl += `&${queryParams}`;
      }

      const fallbackController = new AbortController();
      const fallbackTimeoutId = setTimeout(() => {
        fallbackController.abort();
      }, requestTimeout);

      const fallbackOptions = {
        ...fetchOptions,
        signal: fallbackController.signal,
      };
      const fallbackResponse = await fetch(fallbackUrl, fallbackOptions);
      clearTimeout(fallbackTimeoutId);

      if (!fallbackResponse.ok) {
        // Cancel body stream to return the connection to the pool without buffering
        await fallbackResponse.body?.cancel().catch(() => {});
        log.debug(`Fallback also failed with status ${fallbackResponse.status}`);
        return undefined;
      }

      const responseText = new TextDecoder("utf-8").decode(await fallbackResponse.arrayBuffer());
      if (!responseText) {
        this._stats.successfulRequests++;
        const duration = timer.end();
        this.updateAverageResponseTime(duration);
        return null as T;
      }

      const result = JSON.parse(responseText);
      this._stats.successfulRequests++;
      const duration = timer.end();
      this.updateAverageResponseTime(duration);
      return result as T;
    } catch (fallbackError) {
      log.debug(`Fallback request failed: ${(fallbackError as Error).message}`);
      return undefined;
    }
  }

  // Note: Returns null cast as T for empty responses. Callers should handle
  // potential null values when the WordPress API returns empty bodies (e.g. DELETE).
  private async parseResponse<T>(
    response: Response,
    endpoint: string,
    timer: ReturnType<typeof startTimer>,
  ): Promise<T> {
    const responseText = new TextDecoder("utf-8").decode(await response.arrayBuffer());
    if (!responseText) {
      this._stats.successfulRequests++;
      const duration = timer.end();
      this.updateAverageResponseTime(duration);
      return null as T;
    }

    try {
      const result = JSON.parse(responseText);
      this._stats.successfulRequests++;
      const duration = timer.end();
      this.updateAverageResponseTime(duration);
      return result as T;
    } catch (parseError) {
      if (endpoint.includes("users/me") || endpoint.includes("jwt-auth")) {
        throw new WordPressAPIError(`Invalid JSON response: ${(parseError as Error).message}`);
      }
      this._stats.successfulRequests++;
      const duration = timer.end();
      this.updateAverageResponseTime(duration);
      return responseText as T;
    }
  }

  private updateAverageResponseTime(duration: number): void {
    const totalSuccessful = this._stats.successfulRequests;
    this._stats.averageResponseTime =
      (this._stats.averageResponseTime * (totalSuccessful - 1) + duration) / totalSuccessful;
    this._stats.lastRequestTime = Date.now();
  }

  // ============================================================================
  // HTTP Method Helpers
  // ============================================================================

  async get<T = unknown>(endpoint: string, options?: RequestOptions): Promise<T> {
    return this.request<T>("GET", endpoint, null, options);
  }

  async post<T = unknown>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T> {
    return this.request<T>("POST", endpoint, data, options);
  }

  async put<T = unknown>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T> {
    return this.request<T>("PUT", endpoint, data, options);
  }

  async patch<T = unknown>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T> {
    return this.request<T>("PATCH", endpoint, data, options);
  }

  async delete<T = unknown>(endpoint: string, options?: RequestOptions): Promise<T> {
    return this.request<T>("DELETE", endpoint, null, options);
  }

  // ============================================================================
  // Posts Operations (delegated to PostsOperations)
  // ============================================================================

  async getPosts(params?: PostQueryParams): Promise<WordPressPost[]> {
    return this.postsOps.getPosts(params);
  }

  async getPost(id: number, context: "view" | "embed" | "edit" = "view"): Promise<WordPressPost> {
    return this.postsOps.getPost(id, context);
  }

  async createPost(data: CreatePostRequest): Promise<WordPressPost> {
    return this.postsOps.createPost(data);
  }

  async updatePost(data: UpdatePostRequest): Promise<WordPressPost> {
    return this.postsOps.updatePost(data);
  }

  async deletePost(id: number, force = false): Promise<{ deleted: boolean; previous?: WordPressPost }> {
    return this.postsOps.deletePost(id, force);
  }

  async getPostRevisions(id: number): Promise<WordPressPost[]> {
    return this.postsOps.getPostRevisions(id);
  }

  // ============================================================================
  // Pages Operations (delegated to PagesOperations)
  // ============================================================================

  async getPages(params?: PostQueryParams): Promise<WordPressPage[]> {
    return this.pagesOps.getPages(params);
  }

  async getPage(id: number, context: "view" | "embed" | "edit" = "view"): Promise<WordPressPage> {
    return this.pagesOps.getPage(id, context);
  }

  async createPage(data: CreatePageRequest): Promise<WordPressPage> {
    return this.pagesOps.createPage(data);
  }

  async updatePage(data: UpdatePageRequest): Promise<WordPressPage> {
    return this.pagesOps.updatePage(data);
  }

  async deletePage(id: number, force = false): Promise<{ deleted: boolean; previous?: WordPressPage }> {
    return this.pagesOps.deletePage(id, force);
  }

  async getPageRevisions(id: number): Promise<WordPressPage[]> {
    return this.pagesOps.getPageRevisions(id);
  }

  // ============================================================================
  // Media Operations (delegated to MediaOperations)
  // ============================================================================

  async getMedia(params?: MediaQueryParams): Promise<WordPressMedia[]> {
    return this.mediaOps.getMedia(params);
  }

  async getMediaItem(id: number, context: "view" | "embed" | "edit" = "view"): Promise<WordPressMedia> {
    return this.mediaOps.getMediaItem(id, context);
  }

  async uploadMedia(data: UploadMediaRequest): Promise<WordPressMedia> {
    return this.mediaOps.uploadMedia(data);
  }

  async uploadFile(
    fileData: Buffer,
    filename: string,
    mimeType: string,
    meta: Partial<UploadMediaRequest> = {},
    options?: RequestOptions,
  ): Promise<WordPressMedia> {
    return this.mediaOps.uploadFile(fileData, filename, mimeType, meta, options);
  }

  async updateMedia(data: UpdateMediaRequest): Promise<WordPressMedia> {
    return this.mediaOps.updateMedia(data);
  }

  async deleteMedia(id: number, force = false): Promise<{ deleted: boolean; previous?: WordPressMedia }> {
    return this.mediaOps.deleteMedia(id, force);
  }

  // ============================================================================
  // Users Operations (delegated to UsersOperations)
  // ============================================================================

  async getUsers(params?: UserQueryParams): Promise<WordPressUser[]> {
    return this.usersOps.getUsers(params);
  }

  async getUser(id: number | "me", context: "view" | "embed" | "edit" = "view"): Promise<WordPressUser> {
    return this.usersOps.getUser(id, context);
  }

  async createUser(data: CreateUserRequest): Promise<WordPressUser> {
    return this.usersOps.createUser(data);
  }

  async updateUser(data: UpdateUserRequest): Promise<WordPressUser> {
    return this.usersOps.updateUser(data);
  }

  async deleteUser(id: number, reassign?: number): Promise<{ deleted: boolean; previous?: WordPressUser }> {
    return this.usersOps.deleteUser(id, reassign);
  }

  async getCurrentUser(): Promise<WordPressUser> {
    return this.usersOps.getCurrentUser();
  }

  // ============================================================================
  // Comments Operations (delegated to CommentsOperations)
  // ============================================================================

  async getComments(params?: CommentQueryParams): Promise<WordPressComment[]> {
    return this.commentsOps.getComments(params);
  }

  async getComment(id: number, context: "view" | "embed" | "edit" = "view"): Promise<WordPressComment> {
    return this.commentsOps.getComment(id, context);
  }

  async createComment(data: CreateCommentRequest): Promise<WordPressComment> {
    return this.commentsOps.createComment(data);
  }

  async updateComment(data: UpdateCommentRequest): Promise<WordPressComment> {
    return this.commentsOps.updateComment(data);
  }

  async deleteComment(id: number, force = false): Promise<{ deleted: boolean; previous?: WordPressComment }> {
    return this.commentsOps.deleteComment(id, force);
  }

  async approveComment(id: number): Promise<WordPressComment> {
    return this.commentsOps.approveComment(id);
  }

  async rejectComment(id: number): Promise<WordPressComment> {
    return this.commentsOps.rejectComment(id);
  }

  async spamComment(id: number): Promise<WordPressComment> {
    return this.commentsOps.spamComment(id);
  }

  // ============================================================================
  // Taxonomies Operations (delegated to TaxonomiesOperations)
  // ============================================================================

  async getCategories(params?: Record<string, string | number | boolean>): Promise<WordPressCategory[]> {
    return this.taxonomiesOps.getCategories(params);
  }

  async getCategory(id: number): Promise<WordPressCategory> {
    return this.taxonomiesOps.getCategory(id);
  }

  async createCategory(data: CreateCategoryRequest): Promise<WordPressCategory> {
    return this.taxonomiesOps.createCategory(data);
  }

  async updateCategory(data: UpdateCategoryRequest): Promise<WordPressCategory> {
    return this.taxonomiesOps.updateCategory(data);
  }

  async deleteCategory(id: number, force = false): Promise<{ deleted: boolean; previous?: WordPressCategory }> {
    return this.taxonomiesOps.deleteCategory(id, force);
  }

  async getTags(params?: Record<string, string | number | boolean>): Promise<WordPressTag[]> {
    return this.taxonomiesOps.getTags(params);
  }

  async getTag(id: number): Promise<WordPressTag> {
    return this.taxonomiesOps.getTag(id);
  }

  async createTag(data: CreateTagRequest): Promise<WordPressTag> {
    return this.taxonomiesOps.createTag(data);
  }

  async updateTag(data: UpdateTagRequest): Promise<WordPressTag> {
    return this.taxonomiesOps.updateTag(data);
  }

  async deleteTag(id: number, force = false): Promise<{ deleted: boolean; previous?: WordPressTag }> {
    return this.taxonomiesOps.deleteTag(id, force);
  }

  // ============================================================================
  // Site Operations (delegated to SiteOperations)
  // ============================================================================

  async getSiteSettings(): Promise<WordPressSiteSettings> {
    return this.siteOps.getSiteSettings();
  }

  async updateSiteSettings(settings: Partial<WordPressSiteSettings>): Promise<WordPressSiteSettings> {
    return this.siteOps.updateSiteSettings(settings);
  }

  async getSiteInfo(): Promise<WordPressSiteInfo> {
    return this.siteOps.getSiteInfo();
  }

  async getApplicationPasswords(userId: number | "me" = "me"): Promise<WordPressApplicationPassword[]> {
    return this.siteOps.getApplicationPasswords(userId);
  }

  async createApplicationPassword(
    userId: number | "me",
    name: string,
    appId?: string,
  ): Promise<WordPressApplicationPassword> {
    return this.siteOps.createApplicationPassword(userId, name, appId);
  }

  async deleteApplicationPassword(userId: number | "me", uuid: string): Promise<{ deleted: boolean }> {
    return this.siteOps.deleteApplicationPassword(userId, uuid);
  }

  async search(query: string, types?: string[], subtype?: string): Promise<WordPressSearchResult[]> {
    return this.siteOps.search(query, types, subtype);
  }

  async ping(): Promise<boolean> {
    return this.siteOps.ping();
  }

  async getServerInfo(): Promise<Record<string, unknown>> {
    return this.siteOps.getServerInfo();
  }

  // ============================================================================
  // Utility Methods
  // ============================================================================

  validateEndpoint(endpoint: string): boolean {
    return /^[a-zA-Z0-9\/\-_]+$/.test(endpoint);
  }

  buildUrl(endpoint: string, params?: Record<string, unknown>): string {
    const url = `${this.apiUrl}/${endpoint.replace(/^\/+/, "")}`;
    if (params) {
      const normalizedParams = Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)]));
      const searchParams = new URLSearchParams(normalizedParams);
      return `${url}?${searchParams.toString()}`;
    }
    return url;
  }
}
