<?php
/**
 * Notion API Client
 *
 * @package Sequensy\Integrations\Notion
 * @since 1.0.0
 */

declare(strict_types=1);

namespace Sequensy\Integrations\Notion;

use Sequensy\Integrations\AbstractApiClient;
use WP_Error;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Class NotionApi
 *
 * API client for Notion's REST API.
 */
class NotionApi extends AbstractApiClient {

	/**
	 * Notion internal integration token.
	 *
	 * @var string
	 */
	private string $api_key;

	/**
	 * Constructor.
	 *
	 * @param string $api_key Notion internal integration token.
	 */
	public function __construct( string $api_key ) {
		$this->api_key = $api_key;
		$this->init_circuit_breaker();
	}

	/**
	 * {@inheritDoc}
	 */
	protected function get_base_url(): string {
		return 'https://api.notion.com/v1/';
	}

	/**
	 * {@inheritDoc}
	 */
	protected function get_auth_headers(): array {
		return array(
			'Authorization'  => 'Bearer ' . $this->api_key,
			'Notion-Version' => '2022-06-28',
		);
	}

	/**
	 * {@inheritDoc}
	 */
	protected function get_error_code(): string {
		return 'notion_api_error';
	}

	/**
	 * {@inheritDoc}
	 *
	 * @param array|null $decoded     Decoded JSON response body, or null if parsing failed.
	 * @param int        $status_code HTTP status code from the API response.
	 */
	protected function parse_error_message( ?array $decoded, int $status_code ): string {
		if ( isset( $decoded['message'] ) ) {
			return $decoded['message'];
		}

		return sprintf(
			/* translators: %d: HTTP status code */
			__( 'Notion API error (HTTP %d)', 'sequensy' ),
			$status_code
		);
	}

	/**
	 * {@inheritDoc}
	 */
	protected function get_circuit_key(): string {
		return 'notion_api';
	}

	/**
	 * Validate API credentials by getting the current bot user.
	 *
	 * @return array|WP_Error Validation result or error.
	 */
	public function validate_credentials(): array|WP_Error {
		return $this->request( 'GET', 'users/me' );
	}

	/**
	 * Search for databases the integration has access to.
	 *
	 * @return array|WP_Error Array of databases or error.
	 */
	public function list_databases(): array|WP_Error {
		return $this->request(
			'POST',
			'search',
			array(
				'filter' => array(
					'value'    => 'database',
					'property' => 'object',
				),
			)
		);
	}

	/**
	 * Search for pages the integration has access to.
	 *
	 * @return array|WP_Error Array of pages or error.
	 */
	public function search_pages(): array|WP_Error {
		return $this->request(
			'POST',
			'search',
			array(
				'filter' => array(
					'value'    => 'page',
					'property' => 'object',
				),
			)
		);
	}

	/**
	 * Get a specific database schema.
	 *
	 * @param string $database_id Notion database ID.
	 * @return array|WP_Error Database details or error.
	 */
	public function get_database( string $database_id ): array|WP_Error {
		return $this->request( 'GET', 'databases/' . $database_id );
	}

	/**
	 * Create a new page.
	 *
	 * @param array $data Page data including parent and properties.
	 * @return array|WP_Error Created page data or error.
	 */
	public function create_page( array $data ): array|WP_Error {
		return $this->request( 'POST', 'pages', $data );
	}

	/**
	 * Get a specific page.
	 *
	 * @param string $page_id Notion page ID.
	 * @return array|WP_Error Page details or error.
	 */
	public function get_page( string $page_id ): array|WP_Error {
		return $this->request( 'GET', 'pages/' . $page_id );
	}

	/**
	 * Update an existing page's properties.
	 *
	 * @param string $page_id    Notion page ID.
	 * @param array  $properties Updated properties.
	 * @return array|WP_Error Updated page data or error.
	 */
	public function update_page( string $page_id, array $properties ): array|WP_Error {
		return $this->request(
			'PATCH',
			'pages/' . $page_id,
			array(
				'properties' => $properties,
			)
		);
	}

	/**
	 * Find the title property name from a Notion properties array.
	 *
	 * Every Notion database has exactly one title property, but its name is user-defined.
	 *
	 * @param array $properties Properties array from a database or page response.
	 * @return string The name of the title property.
	 */
	public static function find_title_property_name( array $properties ): string {
		foreach ( $properties as $name => $prop ) {
			if ( isset( $prop['type'] ) && 'title' === $prop['type'] ) {
				return $name;
			}
		}

		return 'Name';
	}

	/**
	 * Append child blocks to a page or block.
	 *
	 * @param string $block_id Page or block ID to append children to.
	 * @param array  $children Array of block objects to append.
	 * @return array|WP_Error Response data or error.
	 */
	public function append_children( string $block_id, array $children ): array|WP_Error {
		return $this->request(
			'PATCH',
			'blocks/' . $block_id . '/children',
			array(
				'children' => $children,
			)
		);
	}

	/**
	 * Query a database with optional filter and sort.
	 *
	 * @param string     $database_id Database ID to query.
	 * @param array|null $filter      Notion filter object.
	 * @param array|null $sorts       Notion sorts array.
	 * @param int|null   $page_size   Number of results to return (max 100).
	 * @return array|WP_Error Query results or error.
	 */
	public function query_database( string $database_id, ?array $filter = null, ?array $sorts = null, ?int $page_size = null ): array|WP_Error {
		$body = array();

		if ( null !== $filter ) {
			$body['filter'] = $filter;
		}

		if ( null !== $sorts ) {
			$body['sorts'] = $sorts;
		}

		if ( null !== $page_size ) {
			$body['page_size'] = min( $page_size, 100 );
		}

		return $this->request( 'POST', 'databases/' . $database_id . '/query', $body );
	}
}
