<?php
/**
 * Media Folder Service
 *
 * Business logic for media folder operations: batch moves, folder statistics,
 * and batch deletions.
 *
 * @package FleekDash\Services
 * @since   2.3.2
 */

namespace FleekDash\Services;

use FleekDash\Taxonomies\MediaFolderTaxonomy;

defined('ABSPATH') || exit;

class MediaFolderService
{
    /**
     * Move multiple media items to a folder.
     *
     * @param  int[]    $media_ids  Array of attachment IDs.
     * @param  int|null $folder_id  Target folder term ID (null = remove from all folders).
     * @return array{moved: int, failed: int, errors: string[]}
     */
    public function batchMove(array $media_ids, ?int $folder_id): array
    {
        $moved  = 0;
        $failed = 0;
        $errors = [];

        if ($folder_id !== null && $folder_id > 0) {
            $term = \get_term($folder_id, MediaFolderTaxonomy::TAXONOMY);
            if (\is_wp_error($term) || !$term) {
                return ['moved' => 0, 'failed' => count($media_ids), 'errors' => ['Target folder does not exist.']];
            }
        }

        foreach ($media_ids as $id) {
            $id = (int) $id;
            $attachment = \get_post($id);

            if (!$attachment || $attachment->post_type !== 'attachment') {
                $failed++;
                $errors[] = "ID {$id} is not a valid attachment.";
                continue;
            }

            $terms = ($folder_id !== null && $folder_id > 0) ? [$folder_id] : [];
            $result = \wp_set_object_terms($id, $terms, MediaFolderTaxonomy::TAXONOMY);

            if (\is_wp_error($result)) {
                $failed++;
                $errors[] = "Failed to move ID {$id}: " . $result->get_error_message();
            } else {
                $moved++;
            }
        }

        return compact('moved', 'failed', 'errors');
    }

    /**
     * Map REST API media_type to post_mime_type LIKE pattern.
     *
     * @param string $media_type One of: image, video, audio, application.
     * @return string|null MIME pattern (e.g. 'image/%') or null for no filter.
     */
    private function getMimePatternForMediaType(?string $media_type): ?string
    {
        if (empty($media_type) || $media_type === 'all') {
            return null;
        }
        $map = [
            'image'       => 'image/%',
            'video'       => 'video/%',
            'audio'       => 'audio/%',
            'application' => 'application/%',
        ];
        return $map[$media_type] ?? null;
    }

    /**
     * Get item counts per folder (including "unsorted" count).
     * Optionally filter by media_type (image, video, audio, application).
     *
     * @param string|null $media_type Filter counts by media type.
     * @return array{folders: array, unsorted: int, total: int}
     */
    public function getFolderStats(?string $media_type = null): array
    {
        $terms = \get_terms([
            'taxonomy'   => MediaFolderTaxonomy::TAXONOMY,
            'hide_empty' => false,
        ]);

        if (\is_wp_error($terms)) {
            $terms = [];
        }

        $mime_pattern = $this->getMimePatternForMediaType($media_type);

        $folders = [];
        foreach ($terms as $term) {
            $order   = (int) \get_term_meta($term->term_id, 'menu_order', true);
            // Always count attachments via SQL — $term->count is stale for attachment taxonomies.
            $count = $this->countAttachmentsInFolder($term->term_id, $mime_pattern);
            $folders[] = [
                'id'         => $term->term_id,
                'name'       => $term->name,
                'slug'       => $term->slug,
                'parent'     => (int) $term->parent,
                'count'      => $count,
                'menu_order' => $order,
            ];
        }

        $this->sortFoldersByOrder($folders);

        $count_object = \wp_count_posts('attachment');
        $inherit_total  = (\is_object($count_object) && isset($count_object->inherit))
            ? (int) $count_object->inherit
            : 0;
        $total_attachments = $mime_pattern
            ? $this->countAttachmentsByMime($mime_pattern)
            : $inherit_total;
        $assigned = 0;
        foreach ($folders as $f) {
            $assigned += $f['count'];
        }
        $unsorted = max(0, $total_attachments - $assigned);

        return [
            'folders'  => $folders,
            'unsorted' => $unsorted,
            'total'    => $total_attachments,
        ];
    }

    /**
     * Sort folders array by parent, then menu_order, then id (in place).
     *
     * @param array<int, array{id: int, parent: int, menu_order?: int}> $folders
     */
    private function sortFoldersByOrder(array &$folders): void
    {
        \usort($folders, function ($a, $b) {
            if ((int) $a['parent'] !== (int) $b['parent']) {
                return (int) $a['parent'] <=> (int) $b['parent'];
            }
            $order_a = isset($a['menu_order']) ? (int) $a['menu_order'] : 0;
            $order_b = isset($b['menu_order']) ? (int) $b['menu_order'] : 0;
            if ($order_a !== $order_b) {
                return $order_a <=> $order_b;
            }
            return ($a['id'] ?? 0) <=> ($b['id'] ?? 0);
        });
    }

    /**
     * Reorder folders under a parent by setting menu_order for each term.
     * Does not change parent; only order among siblings.
     *
     * @param int   $parent_id   Parent term ID (0 for root).
     * @param int[] $ordered_ids Sibling term IDs in desired order.
     * @return array{success: bool, updated: int, errors: string[]}
     */
    public function reorderFolders(int $parent_id, array $ordered_ids): array
    {
        $updated = 0;
        $errors  = [];
        $taxonomy = MediaFolderTaxonomy::TAXONOMY;

        foreach ($ordered_ids as $index => $term_id) {
            $term_id = (int) $term_id;
            $term = \get_term($term_id, $taxonomy);
            if (\is_wp_error($term) || !$term) {
                $errors[] = "Term {$term_id} not found.";
                continue;
            }
            if ((int) $term->parent !== $parent_id) {
                $errors[] = "Term {$term_id} is not a child of parent {$parent_id}.";
                continue;
            }
            $new = $index;
            \update_term_meta($term_id, 'menu_order', $new);
            $updated++;
        }

        return [
            'success'  => empty($errors),
            'updated'  => $updated,
            'errors'   => $errors,
        ];
    }

    /**
     * Count attachments in a folder, optionally filtered by MIME pattern.
     *
     * @param string|null $mime_pattern e.g. 'image/%', or null for all attachment types.
     */
    private function countAttachmentsInFolder(int $term_id, ?string $mime_pattern): int
    {
        global $wpdb;
        $taxonomy = MediaFolderTaxonomy::TAXONOMY;
        $tt_id   = $wpdb->get_var($wpdb->prepare(
            "SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE term_id = %d AND taxonomy = %s",
            $term_id,
            $taxonomy
        ));
        if (!$tt_id) {
            return 0;
        }

        $sql = "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p
             INNER JOIN {$wpdb->term_relationships} tr ON p.ID = tr.object_id AND tr.term_taxonomy_id = %d
             WHERE p.post_type = 'attachment' AND p.post_status = 'inherit'";

        if ($mime_pattern !== null) {
            return (int) $wpdb->get_var($wpdb->prepare(
                $sql . ' AND p.post_mime_type LIKE %s',
                $tt_id,
                $mime_pattern
            ));
        }

        return (int) $wpdb->get_var($wpdb->prepare($sql, $tt_id));
    }

    /**
     * Get total counts per media type for filter badges.
     *
     * @return array{all: int, image: int, video: int, audio: int, application: int}
     */
    public function getCountsByType(): array
    {
        global $wpdb;
        $table = $wpdb->posts;
        $total = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$table} WHERE post_type = 'attachment' AND post_status = 'inherit'"
        );
        $image = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table} WHERE post_type = 'attachment' AND post_status = 'inherit' AND post_mime_type LIKE %s",
            'image/%'
        ));
        $video = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table} WHERE post_type = 'attachment' AND post_status = 'inherit' AND post_mime_type LIKE %s",
            'video/%'
        ));
        $audio = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table} WHERE post_type = 'attachment' AND post_status = 'inherit' AND post_mime_type LIKE %s",
            'audio/%'
        ));
        $application = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table} WHERE post_type = 'attachment' AND post_status = 'inherit' AND post_mime_type LIKE %s",
            'application/%'
        ));

        return [
            'all'         => $total,
            'image'       => $image,
            'video'       => $video,
            'audio'       => $audio,
            'application' => $application,
        ];
    }

    /**
     * Count all attachments matching a MIME pattern.
     */
    private function countAttachmentsByMime(string $mime_pattern): int
    {
        global $wpdb;
        return (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_status = 'inherit' AND post_mime_type LIKE %s",
            $mime_pattern
        ));
    }

    /**
     * Count media items matching a search term.
     * Used to show "X results outside this folder" when searching within a folder.
     *
     * @param string      $search      Search term (searches title, excerpt, description).
     * @param int|null    $folder_id   If set, count only items in this folder. Null = all media.
     * @param string|null $media_type  Optional: image, video, audio, application.
     * @return array{total: int, in_folder?: int}
     */
    public function getSearchCounts(string $search, ?int $folder_id = null, ?string $media_type = null): array
    {
        $search = \sanitize_text_field($search);
        if ($search === '') {
            return ['total' => 0, 'in_folder' => 0];
        }

        $base_args = [
            'post_type'      => 'attachment',
            'post_status'    => 'inherit',
            'posts_per_page' => 1,
            'fields'         => 'ids',
            's'              => $search,
        ];

        if ($media_type && $media_type !== 'all') {
            $mime_map = ['image' => 'image', 'video' => 'video', 'audio' => 'audio', 'application' => 'application'];
            if (isset($mime_map[$media_type])) {
                $base_args['post_mime_type'] = $mime_map[$media_type];
            }
        }

        $total_args = $base_args;

        if ($folder_id !== null && $folder_id > 0) {
            $in_folder_args = array_merge($base_args, [
                'tax_query' => [
                    [
                        'taxonomy' => MediaFolderTaxonomy::TAXONOMY,
                        'field'    => 'term_id',
                        'terms'    => $folder_id,
                    ],
                ],
            ]);
            $in_folder_query = new \WP_Query($in_folder_args);
            $in_folder       = (int) $in_folder_query->found_posts;

            $total_query = new \WP_Query($total_args);
            $total       = (int) $total_query->found_posts;

            return [
                'total'     => $total,
                'in_folder' => $in_folder,
            ];
        }

        $total_query = new \WP_Query($total_args);
        $total       = (int) $total_query->found_posts;

        return ['total' => $total];
    }

    /**
     * Delete multiple media items permanently.
     *
     * @param  int[]  $media_ids
     * @param  bool   $force  Whether to bypass trash.
     * @return array{deleted: int, failed: int, errors: string[]}
     */
    public function batchDelete(array $media_ids, bool $force = true): array
    {
        $deleted = 0;
        $failed  = 0;
        $errors  = [];

        foreach ($media_ids as $id) {
            $id = (int) $id;
            $result = \wp_delete_attachment($id, $force);

            if ($result === false || $result === null) {
                $failed++;
                $errors[] = "Failed to delete attachment ID {$id}.";
            } else {
                $deleted++;
            }
        }

        return compact('deleted', 'failed', 'errors');
    }
}
