<?php
/**
 * KooKoa Folders
 *
 * @package KookoaWPF
 * @license GPL-2.0-or-later
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Migration importer for Real Media Library (devowl.io).
 *
 * RML stores folders in two custom tables, no taxonomy:
 *   - {prefix}realmedialibrary       (id, parent, name, ord, type, …)
 *   - {prefix}realmedialibrary_posts (attachment, fid, isShortcut, …)
 *
 * Schema notes:
 *   - root parent is -1 (NOT 0). We remap to 0 so the abstract BFS
 *     (which expects 0 = root) works unchanged.
 *   - type='0' is a folder. Other types ('1', '2') are collections /
 *     galleries that don't map to KooKoa Folders — skipped in v1.
 *   - isShortcut > 0 means the attachment is duplicated into another
 *     folder (RML supports a file in N folders). Our model is one file
 *     per folder, so we keep only isShortcut = 0 rows.
 *   - fid = -1 means uncategorized; we ignore those rows.
 *
 * Same caching contract as the FileBird Media importer: short TTLs on
 * counts, ephemeral TTL on import-time reads, full cache flush after
 * a successful run().
 *
 * @since 1.1.0
 */
class KookoaWPF_Importer_RealMediaLibrary extends KookoaWPF_Importer_Abstract {

	/**
	 * Object cache group for every query in this class.
	 */
	private const CACHE_GROUP = 'kookoawpf_rml';

	/**
	 * {@inheritDoc}
	 */
	public function get_id(): string {
		return 'rml-media';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_name(): string {
		return __( 'Real Media Library — Media', 'kookoa-folders' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_author(): string {
		return 'devowl.io';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_target_post_type(): string {
		return 'attachment';
	}

	/**
	 * Whether ALL Real Media Library tables required for an import
	 * exist in the current site. The importer needs both the folder
	 * catalog (wp_realmedialibrary) AND the attachment-folder
	 * relations (wp_realmedialibrary_posts); if either is missing the
	 * source is incomplete and the import must not run.
	 *
	 * @return bool
	 */
	private function tables_exist(): bool {
		$cached = wp_cache_get( 'tables_exist', self::CACHE_GROUP );
		if ( false !== $cached ) {
			return '1' === (string) $cached;
		}

		global $wpdb;
		$required = array(
			$wpdb->prefix . 'realmedialibrary',
			$wpdb->prefix . 'realmedialibrary_posts',
		);

		$exists = true;
		foreach ( $required as $table ) {
			// RML has no WP API equivalent — direct SHOW TABLES is
			// the only way to verify.
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			$found = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
			if ( $found !== $table ) {
				$exists = false;
				break;
			}
		}

		wp_cache_set( 'tables_exist', $exists ? '1' : '0', self::CACHE_GROUP, HOUR_IN_SECONDS );
		return $exists;
	}

	/**
	 * {@inheritDoc}
	 */
	public function count_folders(): int {
		if ( ! $this->tables_exist() ) {
			return 0;
		}

		$cached = wp_cache_get( 'count_folders', self::CACHE_GROUP );
		if ( false !== $cached ) {
			return (int) $cached;
		}

		global $wpdb;
		$table = $wpdb->prefix . 'realmedialibrary';
		// type = '0' selects real folders, skipping RML "collection" /
		// "gallery" types that have no equivalent in KooKoa Folders.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
		$count = (int) $wpdb->get_var(
			$wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE type = %s', $table, '0' )
		);

		wp_cache_set( 'count_folders', $count, self::CACHE_GROUP, MINUTE_IN_SECONDS );
		return $count;
	}

	/**
	 * {@inheritDoc}
	 */
	public function count_items(): int {
		if ( ! $this->tables_exist() ) {
			return 0;
		}

		$cached = wp_cache_get( 'count_items', self::CACHE_GROUP );
		if ( false !== $cached ) {
			return (int) $cached;
		}

		global $wpdb;
		$posts_table   = $wpdb->prefix . 'realmedialibrary_posts';
		$folders_table = $wpdb->prefix . 'realmedialibrary';
		// fid > -1 skips uncategorized; isShortcut = 0 skips duplicates;
		// JOIN to folders ensures we only count attachments whose folder
		// is a real folder (type '0'), matching count_folders().
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
		$count = (int) $wpdb->get_var(
			$wpdb->prepare(
				'SELECT COUNT(DISTINCT p.attachment) FROM %i p INNER JOIN %i f ON f.id = p.fid AND f.type = %s WHERE p.fid > %d AND p.isShortcut = %d',
				$posts_table,
				$folders_table,
				'0',
				-1,
				0
			)
		);

		wp_cache_set( 'count_items', $count, self::CACHE_GROUP, MINUTE_IN_SECONDS );
		return $count;
	}

	/**
	 * Read every folder row (type='0'), ordered for BFS. Cached briefly
	 * and explicitly invalidated at the end of run().
	 *
	 * Root parent is remapped from -1 (RML convention) to 0 (the value
	 * the abstract BFS expects).
	 *
	 * @return array<int, array{id: int, parent: int, name: string, ord: int}>
	 */
	private function fetch_folder_rows(): array {
		$cached = wp_cache_get( 'import_folder_rows', self::CACHE_GROUP );
		if ( false !== $cached ) {
			return is_array( $cached ) ? $cached : array();
		}

		global $wpdb;
		$table = $wpdb->prefix . 'realmedialibrary';
		// Reading RML's wp_realmedialibrary table — no WP API exists.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
		$raw = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT id, name, parent, ord FROM %i WHERE type = %s ORDER BY parent ASC, ord ASC, id ASC',
				$table,
				'0'
			),
			ARRAY_A
		);

		$rows = array();
		if ( is_array( $raw ) ) {
			foreach ( $raw as $row ) {
				$parent = (int) $row['parent'];
				$rows[] = array(
					'id'     => (int) $row['id'],
					// RML uses -1 for root; abstract BFS expects 0.
					'parent' => -1 === $parent ? 0 : $parent,
					'name'   => (string) $row['name'],
					'ord'    => (int) $row['ord'],
				);
			}
		}

		wp_cache_set( 'import_folder_rows', $rows, self::CACHE_GROUP, MINUTE_IN_SECONDS );
		return $rows;
	}

	/**
	 * Read every attachment ↔ folder relationship worth importing.
	 * Skips uncategorized (fid = -1) and shortcut/duplicate rows.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	private function fetch_relations(): array {
		$cached = wp_cache_get( 'import_relations', self::CACHE_GROUP );
		if ( false !== $cached ) {
			return is_array( $cached ) ? $cached : array();
		}

		global $wpdb;
		$posts_table = $wpdb->prefix . 'realmedialibrary_posts';
		// Reading RML's wp_realmedialibrary_posts table — no WP API exists.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT fid, attachment FROM %i WHERE fid > %d AND isShortcut = %d ORDER BY attachment ASC, fid ASC',
				$posts_table,
				-1,
				0
			),
			ARRAY_A
		);

		$rows = is_array( $rows ) ? $rows : array();
		wp_cache_set( 'import_relations', $rows, self::CACHE_GROUP, MINUTE_IN_SECONDS );
		return $rows;
	}

	/**
	 * Drop every cache entry written by this class.
	 */
	private function flush_cache(): void {
		wp_cache_delete( 'tables_exist', self::CACHE_GROUP );
		wp_cache_delete( 'count_folders', self::CACHE_GROUP );
		wp_cache_delete( 'count_items', self::CACHE_GROUP );
		wp_cache_delete( 'import_folder_rows', self::CACHE_GROUP );
		wp_cache_delete( 'import_relations', self::CACHE_GROUP );
	}

	/**
	 * Run the import.
	 *
	 * @param string $mode "merge" or "replace".
	 * @return array<string, mixed>|WP_Error
	 */
	public function run( string $mode ) {
		if ( ! $this->tables_exist() ) {
			return new WP_Error( 'kookoawpf_importer_no_source', __( 'Real Media Library tables not found.', 'kookoa-folders' ) );
		}

		$mode = ( 'replace' === $mode ) ? 'replace' : 'merge';

		if ( 'replace' === $mode ) {
			$this->purge_target_taxonomy();
		}

		$rows = $this->fetch_folder_rows();
		if ( empty( $rows ) ) {
			$this->flush_cache();
			return array(
				'folders_created' => 0,
				'items_assigned'  => 0,
				'items_skipped'   => 0,
				'errors'          => array(),
			);
		}

		$errors          = array();
		$folders_created = 0;

		// Map RML ord → KooKoa folder_order termmeta.
		$ord_lookup = array();
		foreach ( $rows as $row ) {
			$ord_lookup[ $row['id'] ] = $row['ord'];
		}

		$create_term = function ( string $name, int $parent_term_id ) use ( &$folders_created, &$errors ) {
			// skip_order = true: we overwrite folder_order from the
			// RML "ord" column right after, so the default lookup
			// inside create() would only waste a query per folder.
			$result = KookoaWPF_Folders::create( $name, $this->get_target_post_type(), $parent_term_id, array( 'skip_order' => true ) );
			if ( is_wp_error( $result ) ) {
				$errors[] = sprintf(
					/* translators: 1: folder name, 2: error message */
					__( 'Could not create folder "%1$s": %2$s', 'kookoa-folders' ),
					$name,
					$result->get_error_message()
				);
				return null;
			}
			++$folders_created;
			return (int) $result['term_id'];
		};

		$id_map = $this->bfs_create_folders( $rows, $create_term, $errors );

		foreach ( $id_map as $source_id => $term_id ) {
			if ( isset( $ord_lookup[ $source_id ] ) ) {
				KookoaWPF_Folders::update_meta( $term_id, 'folder_order', $ord_lookup[ $source_id ] );
			}
		}

		$relations = $this->fetch_relations();

		$by_folder = array();
		$seen_att  = array();
		foreach ( $relations as $rel ) {
			$att_id    = (int) $rel['attachment'];
			$folder_id = (int) $rel['fid'];
			if ( $att_id <= 0 || ! isset( $id_map[ $folder_id ] ) ) {
				continue;
			}
			if ( isset( $seen_att[ $att_id ] ) ) {
				continue;
			}
			$seen_att[ $att_id ]         = true;
			$target_term                 = $id_map[ $folder_id ];
			$by_folder[ $target_term ]   = isset( $by_folder[ $target_term ] ) ? $by_folder[ $target_term ] : array();
			$by_folder[ $target_term ][] = $att_id;
		}

		$total_assigned = 0;
		$total_skipped  = 0;
		// Defer term-count recalculation until every folder is populated, so a
		// large import pays one count update per term instead of one per
		// wp_set_object_terms() call. Mirrors KookoaWPF_Folders::assign_items().
		wp_defer_term_counting( true );
		foreach ( $by_folder as $term_id => $att_ids ) {
			$summary         = $this->assign_items_to_folder( $att_ids, (int) $term_id, $mode );
			$total_assigned += $summary['assigned'];
			$total_skipped  += $summary['skipped'];
		}
		wp_defer_term_counting( false );

		// Mark imported only when at least one folder was actually
		// created. See class-kookoawpf-importer-filebird-media.php for
		// the rationale.
		if ( $folders_created > 0 ) {
			$this->mark_imported();
		}
		$this->flush_cache();

		return array(
			'folders_created' => $folders_created,
			'items_assigned'  => $total_assigned,
			'items_skipped'   => $total_skipped,
			'errors'          => $errors,
		);
	}
}
