<?php
namespace R2_Offload\Media;

use R2_Offload\DB;
use R2_Offload\Settings;

defined( 'ABSPATH' ) || exit;

final class URL_Rewriter {

	private Settings $settings;
	private string $local_base_url;
	private string $remote_base_url;
	private string $prefix;

	/** @var array<int,bool> */
	private array $offloaded_cache = [];

	/** @var array<string,bool> */
	private array $path_cache = [];

	public function __construct( Settings $settings ) {
		$this->settings        = $settings;
		$uploads               = wp_get_upload_dir();
		$this->local_base_url  = trailingslashit( $uploads['baseurl'] );
		$this->remote_base_url = $settings->cdn_base_url();
		$this->prefix          = $settings->effective_prefix();
	}

	public function register(): void {
		if ( ! $this->settings->get( 'rewrite_urls' ) || $this->remote_base_url === '' ) {
			return;
		}
		add_filter( 'wp_get_attachment_url', [ $this, 'filter_attachment_url' ], 20, 2 );
		add_filter( 'wp_get_attachment_image_src', [ $this, 'filter_image_src' ], 20, 4 );
		add_filter( 'wp_calculate_image_srcset', [ $this, 'filter_srcset' ], 20, 5 );

		// BuddyBoss / BuddyPress avatars + covers.
		add_filter( 'bp_core_fetch_avatar_url', [ $this, 'filter_plain_url' ], 20 );
		add_filter( 'bp_core_fetch_avatar_url_check', [ $this, 'filter_plain_url' ], 20 );
		add_filter( 'bp_core_fetch_avatar', [ $this, 'filter_avatar_html' ], 20 );
		add_filter( 'bb_get_default_custom_upload_profile_avatar', [ $this, 'filter_plain_url' ], 20 );
		add_filter( 'bb_get_default_custom_upload_group_avatar', [ $this, 'filter_plain_url' ], 20 );

		// Baked-in uploads URLs in content (webp sidecars, avatar HTML, etc.).
		add_filter( 'the_content', [ $this, 'rewrite_content_urls' ], 20 );
		add_filter( 'widget_text', [ $this, 'rewrite_content_urls' ], 20 );
		add_filter( 'bp_get_the_profile_field_value', [ $this, 'rewrite_content_urls' ], 20 );
	}

	private function is_offloaded( int $attachment_id ): bool {
		if ( $attachment_id <= 0 ) {
			return false;
		}
		if ( ! isset( $this->offloaded_cache[ $attachment_id ] ) ) {
			$item = DB::get_item( $attachment_id );
			$this->offloaded_cache[ $attachment_id ] = $item && $item->status === 'offloaded';
		}
		return $this->offloaded_cache[ $attachment_id ];
	}

	private function is_relative_offloaded( string $relative ): bool {
		$relative = ltrim( str_replace( '\\', '/', $relative ), '/' );
		if ( $relative === '' ) {
			return false;
		}
		if ( ! isset( $this->path_cache[ $relative ] ) ) {
			$this->path_cache[ $relative ] = DB::is_relative_offloaded( $relative );
		}
		return $this->path_cache[ $relative ];
	}

	private function to_remote( string $url ): string {
		if ( strpos( $url, $this->local_base_url ) !== 0 ) {
			return $url;
		}
		$relative = ltrim( substr( $url, strlen( $this->local_base_url ) ), '/' );
		// Strip query/hash for object key; preserve them on the final URL.
		$query = '';
		$hash  = '';
		if ( str_contains( $relative, '#' ) ) {
			[ $relative, $hash ] = explode( '#', $relative, 2 );
			$hash                = '#' . $hash;
		}
		if ( str_contains( $relative, '?' ) ) {
			[ $relative, $query ] = explode( '?', $relative, 2 );
			$query                = '?' . $query;
		}
		$object = $this->prefix !== '' ? $this->prefix . '/' . $relative : $relative;
		return $this->remote_base_url . '/' . $object . $query . $hash;
	}

	/**
	 * Rewrite a local uploads URL when the underlying object is offloaded
	 * (attachment, sidecar webp/avif, or extra-dir path like avatars/).
	 */
	public function maybe_rewrite_url( string $url ): string {
		if ( $url === '' || strpos( $url, $this->local_base_url ) !== 0 ) {
			return $url;
		}
		$relative = ltrim( substr( $url, strlen( $this->local_base_url ) ), '/' );
		$relative = strtok( $relative, '?#' ) ?: $relative;
		if ( ! $this->is_relative_offloaded( $relative ) ) {
			// Attachment originals: also allow rewrite when the parent attachment is offloaded
			// and this URL is a known sidecar pattern of an offloaded library file.
			// Covered by files-table rows once offload records sidecars.
			return $url;
		}
		return $this->to_remote( $url );
	}

	public function filter_plain_url( $url ) {
		return $this->maybe_rewrite_url( (string) $url );
	}

	public function filter_avatar_html( $html ) {
		if ( ! is_string( $html ) || $html === '' ) {
			return $html;
		}
		return $this->rewrite_content_urls( $html );
	}

	public function filter_attachment_url( $url, $attachment_id ) {
		$url = (string) $url;
		if ( $this->is_offloaded( (int) $attachment_id ) ) {
			return $this->to_remote( $url );
		}
		return $this->maybe_rewrite_url( $url );
	}

	public function filter_image_src( $image, $attachment_id, $size, $icon ) {
		if ( ! is_array( $image ) || empty( $image[0] ) ) {
			return $image;
		}
		if ( $this->is_offloaded( (int) $attachment_id ) ) {
			$image[0] = $this->to_remote( (string) $image[0] );
			return $image;
		}
		$image[0] = $this->maybe_rewrite_url( (string) $image[0] );
		return $image;
	}

	public function filter_srcset( $sources, $size_array, $image_src, $image_meta, $attachment_id ) {
		if ( ! is_array( $sources ) ) {
			return $sources;
		}
		$force = $this->is_offloaded( (int) $attachment_id );
		foreach ( $sources as &$src ) {
			if ( ! isset( $src['url'] ) ) {
				continue;
			}
			$src['url'] = $force
				? $this->to_remote( (string) $src['url'] )
				: $this->maybe_rewrite_url( (string) $src['url'] );
		}
		return $sources;
	}

	public function rewrite_content_urls( $content ) {
		if ( ! is_string( $content ) || $content === '' || strpos( $content, $this->local_base_url ) === false ) {
			return $content;
		}
		$base = preg_quote( $this->local_base_url, '~' );
		return preg_replace_callback(
			'~(' . $base . '[^\"\'\s>]+)~',
			function ( $m ) {
				return $this->maybe_rewrite_url( $m[1] );
			},
			$content
		);
	}
}
