<?php
/**
 * ImageService — product and category image management.
 *
 * Uses WordPress wp_handle_upload / wp_get_image_editor for uploads and
 * thumbnail generation. Stores metadata in tc_images and tc_image_links.
 *
 * @package TitanCart\Modules\Products
 */

namespace TitanCart\Modules\Products;

use TitanCart\DB\Database;

defined( 'ABSPATH' ) || exit;

class ImageService {

    /**
     * Thumbnail sizes generated on upload.
     * Format: name => [ width, height ]
     */
    public const SIZES = [
        'thumb'  => [ 50,   50   ],
        'small'  => [ 200,  200  ],
        'medium' => [ 500,  500  ],
        'large'  => [ 1000, 1000 ],
    ];

    /**
     * Base upload sub-directory (inside wp-content/uploads/).
     */
    private const UPLOAD_DIR = 'titancart';

    /**
     * Return the WordPress image size name appropriate for cart / checkout /
     * order thumbnails based on the configured product_image_shape setting.
     *
     * 'tall' (3:4)  → 'medium' — proportional resize, preserves original ratio.
     * 'square' (1:1) → 'thumbnail' — 150×150 hard crop, correct for square.
     * 'wide' (4:3)  → 'medium' — proportional source; the storefront CSS
     *                  aspect-ratio (4/3) + object-fit:cover crops it visually.
     *
     * @return string  WordPress registered image size name.
     */
    public static function cart_thumb_size(): string {
        $shape = \TitanCart\Services\SettingsService::get( 'thumbnails', 'product_image_shape', 'tall' );
        return $shape === 'square' ? 'thumbnail' : 'medium';
    }

    /* ──────────────────────────────────────────────────────────────────────────
     * Upload & thumbnail generation
     * ────────────────────────────────────────────────────────────────────────── */

    /**
     * Validate an upload against the configured app-level size limit.
     *
     * Reads `thumbnails.image_max_file_size` (megabytes). When the setting is
     * blank, zero, or negative, no app-level limit applies and validation
     * passes — PHP's `upload_max_filesize` remains the only ceiling.
     *
     * Call this from EVERY image upload entry point (ImageService::upload_image,
     * ProductsController::upload_image, future category banner / gift-card image
     * endpoints) BEFORE WP's media handler runs.
     *
     * @param  array $file  A single $_FILES-shaped entry: ['name','tmp_name','size',...]
     * @return null|\WP_Error  null on pass; WP_Error('tc_upload_too_large') on fail.
     */
    public static function validate_upload_size( array $file ): ?\WP_Error {
        $max_mb = \TitanCart\Services\SettingsService::float( 'thumbnails', 'image_max_file_size', 0.0 );
        if ( $max_mb <= 0 || ! isset( $file['size'] ) ) {
            return null;
        }
        $max_bytes = (int) ( $max_mb * 1024 * 1024 );
        if ( (int) $file['size'] <= $max_bytes ) {
            return null;
        }
        return new \WP_Error(
            'tc_upload_too_large',
            sprintf(
                /* translators: %s = configured maximum size in megabytes */
                __( 'File exceeds the maximum upload size of %s MB.', 'titancart-lite' ),
                rtrim( rtrim( number_format( $max_mb, 2, '.', '' ), '0' ), '.' )
            ),
            [ 'status' => 413 ]
        );
    }

    /**
     * Handle an uploaded file, generate thumbnails, and create a tc_images record.
     *
     * @param array $file  A single entry from $_FILES, i.e. ['name'=>..., 'tmp_name'=>..., etc.]
     * @return int|WP_Error  tc_images.image_id on success, WP_Error on failure.
     */
    public static function upload_image( array $file ) {
        // ── S128 #9: validate against thumbnails.image_max_file_size (MB) ──
        $size_check = self::validate_upload_size( $file );
        if ( \is_wp_error( $size_check ) ) {
            return $size_check;
        }

        // Redirect uploads into our own sub-folder.
        add_filter( 'upload_dir', [ __CLASS__, 'custom_upload_dir' ] );

        if ( ! function_exists( 'wp_handle_upload' ) ) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        $overrides  = [ 'test_form' => false ];
        $upload     = wp_handle_upload( $file, $overrides );

        remove_filter( 'upload_dir', [ __CLASS__, 'custom_upload_dir' ] );

        if ( isset( $upload['error'] ) ) {
            return new \WP_Error( 'upload_failed', $upload['error'] );
        }

        // Generate thumbnails
        $thumbs = self::generate_thumbnails( $upload['file'] );

        // Persist to tc_images
        global $wpdb;
        $size = @filesize( $upload['file'] );

        // Get image dimensions
        $dims   = @getimagesize( $upload['file'] );
        $width  = $dims ? $dims[0] : 0;
        $height = $dims ? $dims[1] : 0;

        $wpdb->insert( 'tc_images', [
            'filename'   => basename( $upload['file'] ),
            'filepath'   => $upload['file'],
            'url'        => $upload['url'],
            'alt_text'   => '',
            'width'      => $width,
            'height'     => $height,
            'file_size'  => $size ?: 0,
            'mime_type'  => $upload['type'],
            'sizes_json' => wp_json_encode( $thumbs ),
            'created_at' => current_time( 'mysql' ),
        ] );

        return (int) $wpdb->insert_id;
    }

    /**
     * Generate thumbnail sizes for a given file path using WP_Image_Editor.
     *
     * @param  string $file_path Full path to the original image.
     * @return array  Associative: size_name => [ file, url, width, height ]
     */
    private static function generate_thumbnails( string $file_path ): array {
        $thumbs = [];
        $editor = wp_get_image_editor( $file_path );

        if ( is_wp_error( $editor ) ) {
            return $thumbs;
        }

        $upload_dir = wp_upload_dir();
        $base_dir   = trailingslashit( $upload_dir['basedir'] ) . self::UPLOAD_DIR . '/' . gmdate( 'Y/m' );
        $base_url   = trailingslashit( $upload_dir['baseurl'] ) . self::UPLOAD_DIR . '/' . gmdate( 'Y/m' );

        wp_mkdir_p( $base_dir );

        $info     = pathinfo( $file_path );
        $filename = $info['filename'];
        $ext      = strtolower( $info['extension'] ?? 'jpg' );

        foreach ( self::SIZES as $name => [ $w, $h ] ) {
            $thumb_file = "{$base_dir}/{$filename}-{$w}x{$h}.{$ext}";

            $editor->resize( $w, $h, false );
            $saved = $editor->save( $thumb_file );

            if ( ! is_wp_error( $saved ) ) {
                $thumbs[ $name ] = [
                    'file'   => $saved['path'],
                    'url'    => "{$base_url}/{$filename}-{$w}x{$h}.{$ext}",
                    'width'  => $saved['width'],
                    'height' => $saved['height'],
                ];
            }

            // Reset editor for next size
            $editor = wp_get_image_editor( $file_path );
            if ( is_wp_error( $editor ) ) {
                break;
            }
        }

        return $thumbs;
    }

    /* ──────────────────────────────────────────────────────────────────────────
     * Link management
     * ────────────────────────────────────────────────────────────────────────── */

    /**
     * Link an image to an object (product, category, variant).
     *
     * @param string $object_type  'product' | 'category' | 'variant'
     * @param int    $object_id
     * @param int    $image_id
     * @param string $type         'main' | 'additional'
     * @param int    $position
     * @return int   link_id
     */
    public static function link_image(
        string $object_type,
        int $object_id,
        int $image_id,
        string $type = 'additional',
        int $position = 0
    ): int {
        global $wpdb;

        // If type=main, demote any existing main to additional
        if ( $type === 'main' ) {
            $wpdb->update(
                'tc_image_links',
                [ 'type' => 'additional' ],
                [ 'object_type' => $object_type, 'object_id' => $object_id, 'type' => 'main' ]
            );
        }

        $wpdb->insert( 'tc_image_links', [
            'object_type' => $object_type,
            'object_id'   => $object_id,
            'image_id'    => $image_id,
            'type'        => $type,
            'position'    => $position,
        ] );

        return (int) $wpdb->insert_id;
    }

    /**
     * Remove a link record (does NOT delete the file).
     */
    public static function unlink_image( int $link_id ): bool {
        global $wpdb;
        return (bool) $wpdb->delete( 'tc_image_links', [ 'link_id' => $link_id ] );
    }

    /**
     * Delete an image file and all its links.
     */
    public static function delete_image( int $image_id ): bool {
        global $wpdb;

        $img = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM `tc_images` WHERE image_id = %d', $image_id ) );
        if ( ! $img ) {
            return false;
        }

        // Delete original file + thumbnails
        if ( ! empty( $img->filepath ) && file_exists( $img->filepath ) ) {
            wp_delete_file( $img->filepath );
        }

        if ( ! empty( $img->sizes_json ) ) {
            $sizes = json_decode( $img->sizes_json, true );
            if ( is_array( $sizes ) ) {
                foreach ( $sizes as $size ) {
                    if ( isset( $size['file'] ) && file_exists( $size['file'] ) ) {
                        wp_delete_file( $size['file'] );
                    }
                }
            }
        }

        $wpdb->delete( 'tc_image_links', [ 'image_id' => $image_id ] );
        $wpdb->delete( 'tc_images',      [ 'image_id' => $image_id ] );

        return true;
    }

    /* ──────────────────────────────────────────────────────────────────────────
     * Retrieval
     * ────────────────────────────────────────────────────────────────────────── */

    /**
     * Get all images linked to an object, ordered by position.
     *
     * @param  string $object_type
     * @param  int    $object_id
     * @return array
     */
    public static function get_images( string $object_type, int $object_id ): array {
        global $wpdb;
        return $wpdb->get_results( $wpdb->prepare(
            'SELECT il.*, i.filename, i.url, i.alt_text, i.width, i.height, i.sizes_json
               FROM `tc_image_links` il
               JOIN `tc_images` i ON i.image_id = il.image_id
              WHERE il.object_type = %s AND il.object_id = %d
           ORDER BY il.type = "main" DESC, il.position ASC',
            $object_type, $object_id
        ) );
    }

    /**
     * Get the main image for an object (falls back to first available).
     *
     * @param  string $object_type
     * @param  int    $object_id
     * @return object|null
     */
    public static function get_main_image( string $object_type, int $object_id ): ?object {
        global $wpdb;
        $row = $wpdb->get_row( $wpdb->prepare(
            'SELECT il.*, i.filename, i.url, i.alt_text, i.width, i.height, i.sizes_json
               FROM `tc_image_links` il
               JOIN `tc_images` i ON i.image_id = il.image_id
              WHERE il.object_type = %s AND il.object_id = %d
           ORDER BY il.type = "main" DESC, il.position ASC
              LIMIT 1',
            $object_type, $object_id
        ) );
        return $row ?: null;
    }

    /**
     * Reorder images by providing ordered list of image_ids.
     *
     * @param string $object_type
     * @param int    $object_id
     * @param array  $image_ids   Ordered array of image_id values.
     */
    public static function reorder_images( string $object_type, int $object_id, array $image_ids ): void {
        global $wpdb;
        foreach ( $image_ids as $position => $image_id ) {
            $wpdb->update(
                'tc_image_links',
                [ 'position' => $position ],
                [ 'object_type' => $object_type, 'object_id' => $object_id, 'image_id' => (int) $image_id ]
            );
        }
    }

    /**
     * Return the URL for a specific size of an image.
     *
     * @param  int    $image_id
     * @param  string $size     'thumb' | 'small' | 'medium' | 'large' | 'original'
     * @return string|null
     */
    public static function get_image_url( int $image_id, string $size = 'medium' ): ?string {
        global $wpdb;
        $img = $wpdb->get_row( $wpdb->prepare( 'SELECT url, sizes_json FROM `tc_images` WHERE image_id = %d', $image_id ) );
        if ( ! $img ) {
            return null;
        }

        if ( $size === 'original' || empty( $img->sizes_json ) ) {
            return $img->url;
        }

        $sizes = json_decode( $img->sizes_json, true );
        return $sizes[ $size ]['url'] ?? $img->url;
    }

    /* ──────────────────────────────────────────────────────────────────────────
     * Upload dir filter
     * ────────────────────────────────────────────────────────────────────────── */

    /**
     * @internal  Redirect WordPress upload dir to titancart/YYYY/MM/
     */
    public static function custom_upload_dir( array $dirs ): array {
        $suffix         = '/' . self::UPLOAD_DIR . '/' . gmdate( 'Y/m' );
        $dirs['subdir'] = $suffix;
        $dirs['path']   = $dirs['basedir'] . $suffix;
        $dirs['url']    = $dirs['baseurl'] . $suffix;
        return $dirs;
    }
}
