<?php

namespace IGD;

defined( 'ABSPATH' ) || exit;

class Search {

	/**
	 * Singleton instance
	 *
	 * @var self|null
	 */
	private static $instance = null;

	/**
	 * Account ID
	 *
	 * @var string
	 */
	private $account_id;

	/**
	 * Account data
	 *
	 * @var array
	 */
	private $account;

	/**
	 * App instance
	 *
	 * @var App
	 */
	private $app;

	/**
	 * Current root folder
	 *
	 * @var array|null
	 */
	private $folder = null;

	/**
	 * Constructor
	 *
	 * @param string|null $account_id
	 */
	private function __construct( $account_id = null ) {

		$this->account_id = $account_id ?: Account::instance()->get_active_account_id();
		$this->account    = Account::instance()->get_accounts( $this->account_id );
		$this->app        = App::instance( $this->account_id );
	}

	/**
	 * Search files
	 *
	 * @param array $posted
	 *
	 * @return array
	 */
	public function get_files( array $posted = [] ): array {

		$folders          = $posted['folders'] ?? [];
		$filters          = $posted['filters'] ?? [];
		$sort             = $posted['sort'] ?? [ 'sortBy' => 'name', 'sortDirection' => 'asc' ];
		$keyword          = ! empty( $posted['keyword'] ) ? sanitize_text_field( wp_unslash( $posted['keyword'] ) ) : '';
		$full_text_search = filter_var( $posted['fullTextSearch'] ?? true, FILTER_VALIDATE_BOOLEAN );

		if ( ! empty( $folders ) ) {
			$this->folder = $folders[0];
		}

		$this->log_search( $keyword );

		$parent_ids = $this->get_search_folder_ids( $folders );
		$args       = $this->build_base_query_args();

		$files = [];

		if ( ! empty( $parent_ids ) ) {
			foreach ( array_chunk( $parent_ids, 99 ) as $parents ) {
				$query     = $this->build_query( $keyword, $parents, $full_text_search );
				$args['q'] = $query;
				$files     = array_merge( $files, $this->fetch_files( $args ) );
			}
		} else {
			$args['q'] = $this->build_query( $keyword, [], $full_text_search );
			$files     = $this->fetch_files( $args );
		}

		return $this->app->apply_filters_and_sorting( $files, $filters, $sort );
	}

	/* -------------------------------------------------------------------------
	 * Query helpers
	 * ---------------------------------------------------------------------- */

	/**
	 * Base query arguments
	 *
	 * @return array
	 */
	private function build_base_query_args(): array {

		$drive_id = $this->folder['driveId'] ?? '';

		$args = [
			'fields'                    => $this->app->list_fields,
			'pageSize'                  => 500,
			'supportsAllDrives'         => true,
			'includeItemsFromAllDrives' => ! empty( $drive_id ),
			'corpora'                   => empty( $drive_id ) ? 'user' : 'drive',
		];

		if ( ! empty( $drive_id ) ) {
			$args['driveId'] = $drive_id;
		}

		if ( ! empty( $this->account['scope'] ) && 'app_folder' === $this->account['scope'] ) {
			$args['spaces'] = 'appDataFolder';
		}

		return $args;
	}

	/**
	 * Build Drive search query
	 *
	 * @param string $keyword
	 * @param array $parents
	 * @param bool $full_text
	 *
	 * @return string
	 */
	private function build_query( string $keyword, array $parents, bool $full_text ): string {

		$conditions = [];

		$conditions[] = $full_text
			? "(fullText contains '{$keyword}' or name contains '{$keyword}')"
			: "name contains '{$keyword}'";

		if ( ! empty( $parents ) ) {
			if ( 1 === count( $parents ) ) {
				$conditions[] = "'{$parents[0]}' in parents";
			} else {
				$conditions[] = "('" . implode( "' in parents or '", $parents ) . "' in parents)";
			}
		}

		$conditions[] = 'trashed = false';

		return implode( ' and ', $conditions );
	}

	/**
	 * Fetch paginated files from Drive
	 *
	 * @param array $args
	 *
	 * @return array
	 */
	private function fetch_files( array $args ): array {

		$files      = [];
		$page_token = '';

		do {
			try {
				if ( $page_token ) {
					$args['pageToken'] = $page_token;
				}

				$response   = $this->app->service->files->listFiles( $args );
				$page_token = $response->getNextPageToken() ?: '';

				foreach ( (array) $response->getFiles() as $item ) {
					$files[] = Helpers::file_map( $item, $this->account_id );
				}

			} catch ( \Exception $e ) {
				error_log( 'IGD Search Error: ' . $e->getMessage() );

				return [];
			}

		} while ( $page_token );

		return $files;
	}

	/* -------------------------------------------------------------------------
	 * Folder helpers
	 * ---------------------------------------------------------------------- */

	/**
	 * Resolve valid folder IDs for searching
	 *
	 * @param array $folders
	 *
	 * @return array
	 */
	private function get_search_folder_ids( array $folders ): array {

		$ids = [];

		foreach ( $folders as $folder ) {

			if ( $this->is_invalid_search_folder( $folder ) ) {
				continue;
			}

			if ( ! empty( $folder['shortcutDetails']['targetId'] ) ) {
				$folder = $this->app->get_file_by_id( $folder['shortcutDetails']['targetId'] );
			}

			if ( empty( $folder['id'] ) ) {
				continue;
			}

			$ids[] = $folder['id'];

			$children = Helpers::get_all_child_folders( $folder );
			if ( $children ) {
				$ids = array_merge( $ids, wp_list_pluck( $children, 'id' ) );
			}
		}

		return array_values( array_unique( $ids ) );
	}

	/**
	 * Check if folder is invalid for searching
	 *
	 * @param array $folder
	 *
	 * @return bool
	 */
	private function is_invalid_search_folder( array $folder ): bool {

		if ( empty( $folder['id'] ) || ! Helpers::is_dir( $folder ) ) {
			return true;
		}

		$invalid_ids = [ 'root', 'computers', 'shared-drives', 'shared', 'starred' ];

		if ( 'entire_drive' === ( $this->account['scope'] ?? '' ) ) {
			$invalid_ids[] = $this->account['root_id'];
		}

		return in_array( $folder['id'], $invalid_ids, true );
	}

	/* -------------------------------------------------------------------------
	 * Utilities
	 * ---------------------------------------------------------------------- */

	/**
	 * Log search action
	 *
	 * @param string $keyword
	 */
	private function log_search( string $keyword ): void {
		do_action(
			'igd_insert_log',
			[
				'type'    => 'search',
				'file_id' => $keyword,
				'account' => $this->account_id,
			]
		);
	}

	/**
	 * Singleton accessor
	 *
	 * @param string|null $account_id
	 *
	 * @return self
	 */
	public static function instance( $account_id = null ): self {
		if ( null === self::$instance ) {
			self::$instance = new self( $account_id );
		}

		return self::$instance;
	}
}
