<?php

declare(strict_types=1);

namespace Inpsyde\MultilingualPress\Attachment;

use stdClass;
use wpdb;

/**
 * @psalm-type filePaths = array{dir: string, files: string[]}
 */
class Collection
{
    public const DEFAULT_LIMIT = 0;
    public const DEFAULT_OFFSET = 0;
    public const META_KEY_ATTACHMENTS = '_wp_attachment_metadata';
    public const META_KEY_ATTACHED_FILE = '_wp_attached_file';

    /**
     * @var wpdb
     */
    protected $wpdb;

    public function __construct(wpdb $wpdb)
    {
        $this->wpdb = $wpdb;
    }

    /**
     * Retrieves the list of all registered attachments names and paths from the database.
     *
     * Only files referenced in the database are trustworthy, and will therefore get copied.
     *
     * @param int $offset The offset.
     * @param int $limit The limit.
     * @return array The list of file directories and file names.
     * @psalm-return list<filePaths>
     */
    public function list(
        int $offset = self::DEFAULT_OFFSET,
        int $limit = self::DEFAULT_LIMIT
    ): array {

        $attachedFileMetaKey = self::META_KEY_ATTACHED_FILE;

        $selectAttachedFile = "(
            SELECT meta_value
            FROM {$this->wpdb->postmeta}
            WHERE meta_key = '{$attachedFileMetaKey}' AND post_id = m.post_id
        ) AS wp_attached_file";

        $sql = "
            SELECT post_id, meta_value, {$selectAttachedFile}
            FROM {$this->wpdb->postmeta} m
            WHERE meta_key = %s
            LIMIT %d OFFSET %d";

        /** @var stdClass[] $metadata */
        //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
        $metadata = $this->wpdb->get_results(
            $this->wpdb->prepare(
                $sql,
                self::META_KEY_ATTACHMENTS,
                $limit,
                $offset
            )
        );
        // phpcs:enable

        if (!$metadata) {
            return [];
        }

        $paths = [];
        foreach ($metadata as $metadataValue) {
            $filesPaths = $this->filePaths($metadataValue);

            if (empty($filesPaths['dir']) || empty($filesPaths['files'])) {
                continue;
            }

            $paths[] = $filesPaths;
        }

        return $paths;
    }

    /**
     * @return int
     */
    public function count(): int
    {
        $sql = "SELECT COUNT(*) FROM {$this->wpdb->postmeta} WHERE meta_key = %s";
        /** @var stdClass[] $metadata */
        //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
        $count = (int)$this->wpdb->get_var(
            $this->wpdb->prepare($sql, self::META_KEY_ATTACHMENTS)
        );
        // phpcs:enable

        return (int)ceil($count);
    }

    /**
     * Retrieves the file paths from given attachment metadata object.
     *
     * @param stdClass $metadata The attachment metadata object.
     * @return array<string, string|array> The file directory and the list of file names.
     * @psalm-return filePaths
     */
    protected function filePaths(stdClass $metadata): array
    {
        $metaValue = maybe_unserialize($metadata->meta_value);

        $file = is_array($metaValue) ? ($metaValue['file'] ?? $metadata->wp_attached_file ?? '') : '';
        $dir = $file ? dirname($file) : null;
        if (!$dir) {
            return [null, null];
        }

        $sizes = $metaValue['sizes'] ?? [];

        $files = $sizes && is_array($sizes) ? array_column($sizes, 'file') : [];
        array_unshift($files, basename($file));
        array_unshift($files, basename($this->backupFile((int)$metadata->post_id)));

        return ['dir' => $dir, 'files' => array_filter($files)];
    }

    /**
     * Get the Attachment backup file.
     *
     * When the image is edited in WordPress media editor, the original image will be backed up and
     * stored in _wp_attachment_backup_sizes meta, so the users can restore the original image later.
     * When copying the site attachments to a new site with MLP and "Based On Site" option we also need to check the
     * backup files and copy them as well, so in a new site the users will be also able to restore the original images.
     *
     * @param int $postId The attachment post ID
     * @return string The backed up file.
     */
    protected function backupFile(int $postId): string
    {
        $backupFileMeta = maybe_unserialize(get_post_meta($postId, '_wp_attachment_backup_sizes', true));

        return !empty($backupFileMeta['full-orig']) && !empty($backupFileMeta['full-orig']['file'])
            ? $backupFileMeta['full-orig']['file']
            : '';
    }
}
