<?php
/**
 * Brevo contact lists and folders resource.
 *
 * @package RewindBrevoFormBridge
 */

namespace RewindBrevoFormBridge\Api;

use RewindBrevoFormBridge\Support\Errors;

defined( 'ABSPATH' ) || exit;

/**
 * Reads lists and folders (paginated, cached) and creates lists.
 *
 * @since 0.2.0
 */
class Lists {

	/**
	 * Brevo's hard per-page maximum for these endpoints.
	 *
	 * @var int
	 */
	const PER_PAGE = 50;

	/**
	 * Safety valve: never loop more than this many pages (2000 items).
	 *
	 * @var int
	 */
	const MAX_PAGES = 40;

	/**
	 * API client.
	 *
	 * @var Client
	 */
	private Client $client;

	/**
	 * Cache.
	 *
	 * @var Cache
	 */
	private Cache $cache;

	/**
	 * Constructor.
	 *
	 * @since 0.2.0
	 * @param Client $client API client.
	 * @param Cache  $cache  Transient cache.
	 */
	public function __construct( Client $client, Cache $cache ) {
		$this->client = $client;
		$this->cache  = $cache;
	}

	/**
	 * Every list in the account as ['id' => int, 'name' => string,
	 * 'folder_id' => int].
	 *
	 * Deliberately does NOT surface totalSubscribers / totalBlacklisted /
	 * uniqueSubscribers — Brevo deprecated them and they now return 0.
	 *
	 * @since 0.2.0
	 * @param bool $force Bypass the cache (explicit refresh only).
	 * @return array|\WP_Error List rows, or WP_Error on API failure.
	 */
	/**
	 * Cache-only read — never calls the API. For admin page renders, which
	 * must not trigger requests on their own (guideline 7).
	 *
	 * @since 0.3.0
	 * @return array|null Null when nothing is cached.
	 */
	public function cached(): ?array {
		$cached = $this->cache->get( 'lists' );

		return is_array( $cached ) ? $cached : null;
	}

	public function all( bool $force = false ) {
		if ( ! $force ) {
			$cached = $this->cache->get( 'lists' );
			if ( is_array( $cached ) ) {
				return $cached;
			}
		}

		$rows = $this->paginate( '/contacts/lists', 'lists' );

		if ( is_wp_error( $rows ) ) {
			return $rows;
		}

		$lists = array();
		foreach ( $rows as $row ) {
			$lists[] = array(
				'id'        => isset( $row['id'] ) ? (int) $row['id'] : 0,
				'name'      => isset( $row['name'] ) ? (string) $row['name'] : '',
				'folder_id' => isset( $row['folderId'] ) ? (int) $row['folderId'] : 0,
			);
		}

		$this->cache->set( 'lists', $lists );

		return $lists;
	}

	/**
	 * Every folder as ['id' => int, 'name' => string].
	 *
	 * @since 0.2.0
	 * @param bool $force Bypass the cache (explicit refresh only).
	 * @return array|\WP_Error Folder rows, or WP_Error on API failure.
	 */
	public function folders( bool $force = false ) {
		if ( ! $force ) {
			$cached = $this->cache->get( 'folders' );
			if ( is_array( $cached ) ) {
				return $cached;
			}
		}

		$rows = $this->paginate( '/contacts/folders', 'folders' );

		if ( is_wp_error( $rows ) ) {
			return $rows;
		}

		$folders = array();
		foreach ( $rows as $row ) {
			$folders[] = array(
				'id'   => isset( $row['id'] ) ? (int) $row['id'] : 0,
				'name' => isset( $row['name'] ) ? (string) $row['name'] : '',
			);
		}

		$this->cache->set( 'folders', $folders );

		return $folders;
	}

	/**
	 * Create a list.
	 *
	 * Brevo REQUIRES a folderId on this endpoint — a list cannot be created
	 * outside a folder. Any create-list UI must therefore fetch folders()
	 * first and have the user (or a default) pick one; passing 0 here fails
	 * fast without an API call.
	 *
	 * @since 0.2.0
	 * @param string $name      List name.
	 * @param int    $folder_id Existing Brevo folder id. Required, > 0.
	 * @return array|\WP_Error ['id' => int] of the new list, or WP_Error.
	 */
	public function create( string $name, int $folder_id ) {
		$name = trim( $name );

		if ( '' === $name ) {
			return new \WP_Error( 'rbfb_invalid_name', __( 'The list name cannot be empty.', 'rewind-form-bridge-for-brevo' ) );
		}

		if ( $folder_id <= 0 ) {
			return new \WP_Error( 'rbfb_missing_folder', __( 'Brevo requires a folder for every list. Choose a folder first.', 'rewind-form-bridge-for-brevo' ) );
		}

		$response = $this->client->post(
			'/contacts/lists',
			array(
				'name'     => $name,
				'folderId' => $folder_id,
			)
		);

		if ( ! $response->is_success() ) {
			return new \WP_Error( 'rbfb_api_error', Errors::translate( $response ) );
		}

		$body = $response->body();

		$this->cache->delete( 'lists' );

		return array( 'id' => isset( $body['id'] ) ? (int) $body['id'] : 0 );
	}

	/**
	 * Fetch every page of a Brevo collection endpoint.
	 *
	 * limit maxes out at 50 server-side; loops until `count` items are
	 * collected or a page comes back empty, hard-capped at MAX_PAGES.
	 *
	 * @since 0.2.0
	 * @param string $path       API path.
	 * @param string $collection Body key holding the rows.
	 * @return array|\WP_Error All raw rows, or WP_Error on any page failure.
	 */
	private function paginate( string $path, string $collection ) {
		$rows  = array();
		$total = null;

		for ( $page = 0; $page < self::MAX_PAGES; $page++ ) {
			$offset   = $page * self::PER_PAGE;
			$response = $this->client->get( $path . '?limit=' . self::PER_PAGE . '&offset=' . $offset );

			if ( ! $response->is_success() ) {
				return new \WP_Error( 'rbfb_api_error', Errors::translate( $response ) );
			}

			$body  = $response->body();
			$batch = ( isset( $body[ $collection ] ) && is_array( $body[ $collection ] ) ) ? $body[ $collection ] : array();

			if ( null === $total && isset( $body['count'] ) ) {
				$total = (int) $body['count'];
			}

			if ( 0 === count( $batch ) ) {
				break;
			}

			$rows = array_merge( $rows, $batch );

			if ( null !== $total && count( $rows ) >= $total ) {
				break;
			}
		}

		if ( self::MAX_PAGES * self::PER_PAGE <= count( $rows ) ) {
			// Safety valve tripped: the account has 2000+ items. Note it for
			// debugging rather than looping forever.
			do_action( 'rbfb_debug', sprintf( 'Pagination cap hit for %s — first %d items returned.', $path, count( $rows ) ) );
		}

		return $rows;
	}
}
