<?php
#[\AllowDynamicProperties]
class realtimeupload
{   
    //   'module'        => $this->UPLOAD_SETTINGS['recordType'], // $this->CI->input->post('recordType'),
    //'record_id'     => $this->UPLOAD_SETTINGS['recordId']//$this->CI->input->post('recordId'),
    private $UPLOAD_SETTINGS = [
        'uploadFolder' => 'uploads',                 // relative or absolute path
        'maxFileSize' => 2097152,                    // default: bytes (2,097,152 bytes ~ 2MB). You can set bigger.
        'extension' => [''],                         // whitelist extensions (set in init)
        'rename' => true,                            // sanitize name
        'overwrite' => false,                        // false => auto rename if exists
        'maxFolderFiles' => 0,                       // 0 = unlimited
        'maxFolderSize' => 0,                        // bytes, 0 = unlimited
        'protectUploadDirectory' => true,
        'denyUploadDirectoryAccess' => false,
        'uniqueFilename' => true,                    // ✅ safer default (changed)
        'returnLocation' => false,
        'maxWidth' => 0,
        'maxHeight' => 0,
        // DB save options (same as your old code)
        'forignValue' => 0,
        'fileType' => null,                       // logical category used for mime map
        'dbTable' => null,
        'fileTypeColumn' => null,
        'fileColumn' => null,
        'forignKey' => null,
        'extraData' => null,
        'isSaveToDB' => "Y",
        'isUpdate' => "N",
        'primaryKey' => null,
        'primaryValue' => null,
        // Security additions
        'blockedExtensions' => ['php','phtml','phar','cgi','pl','py','sh','bat','cmd','exe','dll','js','jsp','asp','aspx'],
        'company_id'=>null,
        'module'=>null,
        'record_id'=>null,
        'allowedMimeMap' => [                         // ✅ finfo MIME allowlist
            'image' => ['image/jpeg','image/png','image/gif','image/webp'],
            'video' => ['video/mp4','video/quicktime','video/x-msvideo','video/3gpp'],
            'audio' => ['audio/mpeg','audio/wav','audio/mp4','audio/aac','audio/ogg'],
            'file'  => [
                'application/pdf',
                'text/plain',
                'application/msword',
                'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                'application/vnd.ms-excel',
                'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                'application/vnd.ms-powerpoint',
                'application/vnd.openxmlformats-officedocument.presentationml.presentation'
            ],
        ],
    ];

    private $UPLOAD_NAME;
    private $UPLOAD_TMPNAME;
    private $UPLOAD_TYPE;
    private $UPLOAD_ERROR;
    private $UPLOAD_SIZE;
    private $UPLOAD_TOTALSIZE = 0;

    public $live_url = "";
    public $appName = "";
    public $live_base_url = "";

    public function __construct()
    {
        $this->CI = &get_instance();
        $this->CI->load->model('CommonModel');
        $this->CI->load->library('ImageOpt');
        $this->CI->load->helper('url');

        $this->live_base_url = $this->CI->config->item('live_base_url');
    }

    // ----------------------------
    // Getters
    // ----------------------------
    public function name(){ return $this->UPLOAD_NAME; }
    public function tmpname(){ return $this->UPLOAD_TMPNAME; }
    public function type(){ return $this->UPLOAD_TYPE; }
    public function error(){ return $this->UPLOAD_ERROR; }
    public function size(){ return $this->UPLOAD_SIZE; }
    public function totalsize(){ return $this->UPLOAD_TOTALSIZE; }

    // ----------------------------
    // Setup
    // ----------------------------
    public function init($settings = [])
    {
        foreach ($this->UPLOAD_SETTINGS as $key => $value) {
            if (array_key_exists($key, $settings)) {
                $this->UPLOAD_SETTINGS[$key] = $settings[$key];
            }
        }

        // Normalize folder path
        $this->UPLOAD_SETTINGS['uploadFolder'] = rtrim($this->UPLOAD_SETTINGS['uploadFolder'], '/');

        // Create folder
        if (!file_exists($this->UPLOAD_SETTINGS['uploadFolder'])) {
            if (!mkdir($this->UPLOAD_SETTINGS['uploadFolder'], 0755, true)) {
                $this->uploadDisplayError('Upload folder cannot be created');
            }
        }
        if (!is_writable($this->UPLOAD_SETTINGS['uploadFolder'])) {
            $this->uploadDisplayError('Upload folder is not writable');
        }

        // Protect directory (Apache)
        if ($this->UPLOAD_SETTINGS['protectUploadDirectory']) {
            $p = $this->UPLOAD_SETTINGS['uploadFolder'] . '/.htaccess';
            if (!file_exists($p)) {
                $htContent = "RemoveHandler .php\nRemoveHandler .phtml\nRemoveHandler .phar\nOptions -Indexes\n";
                if ($this->UPLOAD_SETTINGS['denyUploadDirectoryAccess']) {
                    $htContent .= "Deny from all\n";
                }

                $ht = @fopen($p, 'c');
                if ($ht) {
                    if (flock($ht, LOCK_EX)) {
                        fwrite($ht, $htContent);
                        fflush($ht);
                        flock($ht, LOCK_UN);
                    }
                    fclose($ht);
                }
            }
        }

        // Start upload
        if (!empty($_FILES)) {
            $this->getFileStatus();
        }
    }

    // Remove .tmp files older than N hours
    public function removeTmp($folder = '', $time = 24)
    {
        if (!$folder) return false;
        $files = glob(rtrim($folder,'/') . "/*");
        $now   = time();
        $counter = 0;

        foreach ($files as $file) {
            if (is_file($file)) {
                if ($now - filemtime($file) >= 60 * 60 * $time && pathinfo($file, PATHINFO_EXTENSION) === 'tmp') {
                    @unlink($file);
                    $counter++;
                }
            }
        }
        return $counter;
    }

    private function getFileStatus()
    {
        foreach ($_FILES as $index => $tmpName) {
            try {
                if (is_array($_FILES[$index]['tmp_name'])) {
                    for ($i = 0, $l = count($_FILES[$index]['tmp_name']); $i < $l; $i++) {
                        $this->UPLOAD_NAME    = $_FILES[$index]['name'][$i];
                        $this->UPLOAD_TMPNAME = $_FILES[$index]['tmp_name'][$i];
                        $this->UPLOAD_TYPE    = $_FILES[$index]['type'][$i];
                        $this->UPLOAD_ERROR   = $_FILES[$index]['error'][$i];
                        $this->UPLOAD_SIZE    = $_FILES[$index]['size'][$i];
                        $this->UPLOAD_TOTALSIZE += $_FILES[$index]['size'][$i];
                        $this->uploadFile();
                    }
                } else {
                    $this->UPLOAD_NAME    = $_FILES[$index]['name'];
                    $this->UPLOAD_TMPNAME = $_FILES[$index]['tmp_name'];
                    $this->UPLOAD_TYPE    = $_FILES[$index]['type'];
                    $this->UPLOAD_ERROR   = $_FILES[$index]['error'];
                    $this->UPLOAD_SIZE    = $_FILES[$index]['size'];
                    $this->UPLOAD_TOTALSIZE += $_FILES[$index]['size'];
                    $this->uploadFile();
                }
            } catch (\Throwable $e) {
                $this->uploadDisplayError('Missing parameters');
            }
        }
    }

    // ----------------------------
    // Core upload
    // ----------------------------
    private function uploadFile()
    {
        $uploaded = false;
        $uploadedFile = '';

        // Validate PHP upload error
        switch ($this->UPLOAD_ERROR) {
            case UPLOAD_ERR_OK:
                break;
            case UPLOAD_ERR_NO_FILE:
                $this->uploadDisplayError('No file sent');
            case UPLOAD_ERR_INI_SIZE:
            case UPLOAD_ERR_FORM_SIZE:
                $this->uploadDisplayError('Exceeded filesize limit');
            default:
                $this->uploadDisplayError('Upload failed');
        }

        // Prefer X-FILENAME if provided (chunk flow)
        $xFilename = $this->getHeader('X-FILENAME');
        if ($xFilename) {
            $this->UPLOAD_NAME = $xFilename;
        }

        // Folder limits
        $this->enforceFolderLimits();
        $extension = strtolower(pathinfo($this->UPLOAD_NAME, PATHINFO_EXTENSION));
        if ($extension === '') {
            $this->uploadDisplayError('Missing file extension');
        }
        // Extension checks
        
        $detectedType = $this->detectFileTypeByExtension($this->UPLOAD_NAME);
        if (
            empty($this->UPLOAD_SETTINGS['fileType']) ||
            $this->UPLOAD_SETTINGS['fileType'] === 'image' // legacy default
        ) {
            $this->UPLOAD_SETTINGS['fileType'] = $detectedType;
        }
        // Block dangerous extensions always
        if (in_array($extension, $this->UPLOAD_SETTINGS['blockedExtensions'], true)) {
            $this->uploadDisplayError('File type not allowed');
        }

        // Whitelist extensions (if configured)
        $allowedExt = array_map('strtolower', $this->UPLOAD_SETTINGS['extension']);
        if (count($allowedExt) > 0 && !(count($allowedExt) === 1 && $allowedExt[0] === '')) {
            if (!in_array($extension, $allowedExt, true)) {
                $this->uploadDisplayError('File extension not allowed');
            }
        } else {
            // Safer behavior: if no whitelist provided, deny by default
            $this->uploadDisplayError('Upload extension whitelist not configured');
        }

        // Filename length
        if (strlen($this->UPLOAD_NAME) > 180) {
            $this->uploadDisplayError('File name is too long');
        }

        // Max size checks (bytes)
        $maxBytes = $this->normalizeMaxFileSizeBytes($this->UPLOAD_SETTINGS['maxFileSize']);
        $xFileSize = $this->getHeader('X-FILESIZE');
        $expectedSize = $xFileSize ? (int)$xFileSize : (int)$this->UPLOAD_SIZE;

        if ($expectedSize <= 0) {
            $this->uploadDisplayError('Invalid file size');
        }
        if ((int)$this->UPLOAD_SIZE > $maxBytes || $expectedSize > $maxBytes) {
            $this->uploadDisplayError('File size is too big');
        }

        // Chunk detection
        $chunks = false;
        if ($xFileSize && (int)$xFileSize > (int)$this->UPLOAD_SIZE) {
            $chunks = true;
        }

        // Cancel chunk upload
        $remove = $this->getHeader('X-REMOVE') ? true : false;

        // Build safe final filename
        $safeBase = $this->UPLOAD_SETTINGS['rename']
            ? $this->sanitizeFilename(basename($this->UPLOAD_NAME, '.' . $extension))
            : $this->UPLOAD_NAME;

        // If uniqueFilename => random name (recommended)
        if ($this->UPLOAD_SETTINGS['uniqueFilename']) {
            $finalName = $this->randomName($extension);
        } else {
            $finalName = $safeBase . '.' . $extension;
        }

        // Avoid overwrites if requested
        if (!$this->UPLOAD_SETTINGS['overwrite'] && !$this->UPLOAD_SETTINGS['uniqueFilename']) {
            $finalName = $this->dedupeFilename($finalName, $extension);
        }

        $folder = $this->UPLOAD_SETTINGS['uploadFolder'];
        $finalPath = $folder . '/' . $finalName;

        // Temp file for chunked upload
        // Optional: support X-UPLOAD-ID to avoid collisions
        $uploadId = $this->getHeader('X-UPLOAD-ID');
        $tmpPath = $finalPath . ($uploadId ? '.' . $this->sanitizeFilename($uploadId) : '') . '.tmp';

        if ($chunks) {
            if ($remove) {
                @unlink($tmpPath);
                $this->respondJson(['status' => 'Cancelled']);
                return;
            }

            // Atomic append with file lock
            $in = @fopen($this->UPLOAD_TMPNAME, 'rb');
            if (!$in) $this->uploadDisplayError('Failed to read uploaded chunk');

            $out = @fopen($tmpPath, 'ab');
            if (!$out) {
                @fclose($in);
                $this->uploadDisplayError('Failed to write chunk');
            }

            if (!flock($out, LOCK_EX)) {
                @fclose($in);
                @fclose($out);
                $this->uploadDisplayError('Upload busy, retry');
            }

            stream_copy_to_stream($in, $out);
            fflush($out);
            flock($out, LOCK_UN);
            fclose($in);
            fclose($out);

            $resultSize = @filesize($tmpPath);
            if ($resultSize === false) $this->uploadDisplayError('Chunk write failed');

            if ($resultSize > $maxBytes) {
                @unlink($tmpPath);
                $this->uploadDisplayError('File size is too big');
            }

            if ((int)$xFileSize === (int)$resultSize) {
                // All chunks received => validate MIME on the assembled file
                $this->validateMimeOnPath($tmpPath, $this->UPLOAD_SETTINGS['fileType']);

                // Image dimension checks
                $this->checkImageSize($tmpPath);

                // Move tmp => final
                @rename($tmpPath, $finalPath);
                @chmod($finalPath, 0644);

                $uploaded = true;
                $uploadedFile = $finalPath;
            } else {
                // Still uploading
                $this->respondJson(['status' => 'Uploading']);
                return;
            }
        } else {
            // Classic upload: validate MIME before move
            $this->validateMimeOnTmp($this->UPLOAD_TMPNAME, $this->UPLOAD_SETTINGS['fileType']);

            if (!move_uploaded_file($this->UPLOAD_TMPNAME, $finalPath)) {
                $this->uploadDisplayError('Failed to move uploaded file');
            }
            @chmod($finalPath, 0644);

            // Image checks
            $this->checkImageSize($finalPath);

            $uploaded = true;
            $uploadedFile = $finalPath;
        }

        // if ($uploaded && $uploadedFile !== '') {
        //     if ($this->UPLOAD_SETTINGS['isSaveToDB'] === "Y") {
        //         $this->updateDatabse($uploadedFile);
        //     }

        //     $json = ['status' => 'File uploaded'];
        //     if ($this->UPLOAD_SETTINGS['returnLocation']) {
        //         $json['url'] = $uploadedFile;
        //     }
        //     $this->respondJson($json);
        //     return;
        // }
        if ($uploaded && $uploadedFile !== '') {

                $fileData = null;

                if ($this->UPLOAD_SETTINGS['isSaveToDB'] === "Y") {
                    $fileData = $this->updateDatabse($uploadedFile);
                    $token = $this->generateFileToken($fileData['id'],$this->UPLOAD_SETTINGS['company_id']);
                }else{
                    $token="";
                }
                $this->respondJson([
                    'flag' => 'S',
                    'msg'  => 'File uploaded',
                    'file' => [
                        'id'          => $fileData['id'] ?? null,
                        'name'        => $fileData['name'] ?? basename($uploadedFile),
                        'size'        => $fileData['size'] ?? filesize($uploadedFile),
                        'mimeType'    => $fileData['mimeType'] ?? mime_content_type($uploadedFile),
                        'viewUrl' => base_url("files/view/".$token),
                        'downloadUrl' => base_url("files/download/".$token),
                        'createdAt'   => $fileData['createdAt'] ?? date('Y-m-d H:i:s'),
                        //'path'        => $uploadedFile,
                    ]
                ]);
                return;
            }

        $this->respondJson(['status' => 'Uploading']);
    }

    // ----------------------------
    // Security helpers
    // ----------------------------
    private function getHeader($name)
    {
        $key = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
        return isset($_SERVER[$key]) && $_SERVER[$key] !== null ? trim((string)$_SERVER[$key]) : null;
    }

    private function normalizeMaxFileSizeBytes($val)
    {
        // Backward safe:
        // - If you pass small values (e.g., 2048) treat as KB
        // - If you pass large values (>= 1MB) treat as bytes
        $n = (int)$val;
        if ($n <= 0) return 0;

        if ($n < 1024 * 1024) {
            // likely KB
            return $n * 1024;
        }
        return $n; // bytes
    }

    private function validateMimeOnTmp($tmp, $typeKey)
    {
        // Some servers may not have fileinfo enabled; fail safe if missing
        if (!function_exists('finfo_open')) return;
        $detectedType = $this->detectFileTypeByExtension($this->UPLOAD_NAME);
        $this->UPLOAD_SETTINGS['fileType'] = $detectedType;
        $allowed = $this->UPLOAD_SETTINGS['allowedMimeMap'][$typeKey] ?? null;
        if (!$allowed) return;

        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        if (!$finfo) return;

        $mime = finfo_file($finfo, $tmp);
        finfo_close($finfo);
        
        if (!$mime || !in_array($mime, $allowed, true)) {
            $this->uploadDisplayError('Invalid file type');
        }
    }

    private function validateMimeOnPath($path, $typeKey)
    {
        if (!function_exists('finfo_open')) return;

        $allowed = $this->UPLOAD_SETTINGS['allowedMimeMap'][$typeKey] ?? null;
        if (!$allowed) return;

        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        if (!$finfo) return;

        $mime = finfo_file($finfo, $path);
        finfo_close($finfo);

        if (!$mime || !in_array($mime, $allowed, true)) {
            @unlink($path);
            $this->uploadDisplayError('Invalid file type');
        }
    }

    private function enforceFolderLimits()
    {
        // Max files in folder
        if (!empty($this->UPLOAD_SETTINGS['maxFolderFiles']) && $this->UPLOAD_SETTINGS['maxFolderFiles'] > 0) {
            $files = array_diff(scandir($this->UPLOAD_SETTINGS['uploadFolder']), [".", "..", ".htaccess"]);
            if (count($files) >= (int)$this->UPLOAD_SETTINGS['maxFolderFiles']) {
                $this->uploadDisplayError('Maximum number of files allowed reached');
            }
        }

        // Max folder size
        $limit = (int)$this->UPLOAD_SETTINGS['maxFolderSize'];
        if ($limit > 0) {
            $size = 0;
            $path = realpath($this->UPLOAD_SETTINGS['uploadFolder']);
            if ($path !== false) {
                foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)) as $object) {
                    $size += $object->getSize();
                }
            }
            if ($size > $limit) {
                $this->uploadDisplayError('Upload folder maximum size reached');
            }
        }
    }

    private function randomName($extension)
    {
        try {
            return bin2hex(random_bytes(16)) . '.' . $extension;
        } catch (\Throwable $e) {
            // fallback
            return uniqid('f_', true) . '.' . $extension;
        }
    }

    private function dedupeFilename($filename, $extension)
    {
        $folder = $this->UPLOAD_SETTINGS['uploadFolder'];
        $base = basename($filename, '.' . $extension);
        $candidate = $filename;
        $i = 1;

        while (file_exists($folder . '/' . $candidate)) {
            $candidate = $base . '(' . $i . ').' . $extension;
            $i++;
        }
        return $candidate;
    }

    // sanitize without extension
    private function sanitizeFilename($filename = '')
    {
        $filename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $filename);
        $filename = trim($filename, '_');
        if ($filename === '') $filename = 'file';
        return $filename;
    }

    private function checkImageSize($file = '')
    {
        if (($this->UPLOAD_SETTINGS['maxWidth'] != 0 || $this->UPLOAD_SETTINGS['maxHeight'] != 0) && is_file($file)) {
            $info = @getimagesize($file);
            if (!$info) return; // not an image or unreadable

            $width = $info[0] ?? 0;
            $height = $info[1] ?? 0;

            if (
                ($this->UPLOAD_SETTINGS['maxWidth'] > 0 && $width > $this->UPLOAD_SETTINGS['maxWidth']) ||
                ($this->UPLOAD_SETTINGS['maxHeight'] > 0 && $height > $this->UPLOAD_SETTINGS['maxHeight'])
            ) {
                @unlink($file);
                $this->uploadDisplayError(
                    'Image cannot exceeds ' . $this->UPLOAD_SETTINGS['maxWidth'] . 'px on ' . $this->UPLOAD_SETTINGS['maxHeight'] . 'px'
                );
            }
        }
    }

   private function uploadDisplayError($error = '')
	{
		$json = array(
			'msg' => $error,
			'flag'=>'F',
            'status'=>200,
		);
		echo json_encode($json);
		exit();
	}

    private function respondJson($data, $status = 200)
    {
        if (!headers_sent()) {
            http_response_code($status);
            header('Content-Type: application/json; charset=utf-8');
            header('X-Content-Type-Options: nosniff');
        }
        echo json_encode($data);
    }

    // ----------------------------
    // DB save (kept from your existing logic with small safety)
    // ----------------------------
    // private function updateDatabse($file = '')
    // {
    //     $extension = strtolower(pathinfo($this->UPLOAD_NAME, PATHINFO_EXTENSION));

    //     $arrayimg  = ['jpg','jpeg','png','gif','webp'];
    //     $arrayVideo = ['mp4','mov','avi','3gp'];
    //     $arrayaudio = ['mp3','wav','m4a','aac','ogg'];
    //     $arraytxt  = ['docx','doc','ppt','pptx','txt','pdf','xls','xlsx'];

    //     $fileType = "file";
    //     if (in_array($extension, $arrayimg, true)) $fileType = "image";
    //     if (in_array($extension, $arrayVideo, true)) $fileType = "video";
    //     if (in_array($extension, $arrayaudio, true)) $fileType = "audio";
    //     if (in_array($extension, $arraytxt, true)) $fileType = "file";

    //     $forignValue = $this->UPLOAD_SETTINGS['forignValue'];
    //     $table = $this->UPLOAD_SETTINGS['dbTable'];

    //     $isUpdate = $this->UPLOAD_SETTINGS['isUpdate'] ?? 'N';

    //     $where = [];
    //     if (!empty($this->UPLOAD_SETTINGS['primaryKey'])) {
    //         $where[$this->UPLOAD_SETTINGS['primaryKey']] = $this->UPLOAD_SETTINGS['primaryValue'];
    //     }

    //     $fileTypeColumn = $this->UPLOAD_SETTINGS['fileTypeColumn'];
    //     $fileColumn     = $this->UPLOAD_SETTINGS['fileColumn'];
    //     $forignKey      = $this->UPLOAD_SETTINGS['forignKey'];

    //     $extraData = !empty($this->UPLOAD_SETTINGS['extraData']) ? $this->UPLOAD_SETTINGS['extraData'] : [];

    //     $imagename = explode("/", $file);
    //     $filename = end($imagename);

    //     // Image optimize + thumbnail
    //     try {
    //         if ($fileType === 'image') {
    //             if ($this->CI->imageopt->optimize_image($file, $file)) {
    //                 $this->CI->imageopt->create_thumbnails($file, $this->UPLOAD_SETTINGS['uploadFolder']);
    //             }
    //         }
    //     } catch (\Throwable $e) {
    //         // don't fail upload if optimization fails
    //     }

    //     $photos = $this->CI->CommonModel->saveFile(
    //         $table,
    //         $fileColumn,
    //         $filename,
    //         $forignValue,
    //         $fileTypeColumn,
    //         $fileType,
    //         $forignKey,
    //         $extraData,
    //         $file,
    //         $isUpdate,
    //         $where
    //     );
    //     if (!$photos) {
    //         $this->respondJson(['flag' => 'F', 'msg' => 'DB save failed'], 500);
    //     }

    //     // 🔥 IMPORTANT: return file info
    //     return [
    //         'id' => $photos['id'] ?? $photos, // adapt to your saveFile return
    //         'name' => $filename,
    //         'size' => filesize($file),
    //         'mimeType' => mime_content_type($file),
    //         'path' => $file,
    //         'url' => base_url($file),
    //         'createdAt' => date('Y-m-d H:i:s'),
    //         'fileType' => $fileType,
    //     ];

    //     // if (!$photos) {
    //     //     $this->respondJson(['flag' => 'F', 'msg' => 'DB save failed'], 500);
    //     // }
    //     // $this->respondJson(['flag' => 'S', 'msg' => 'File Uploaded','data'=>array(
    //     //     'id' => $photos['id'] ?? $photos, // adapt to your saveFile return
    //     //     'name' => $filename,
    //     //     'size' => filesize($file),
    //     //     'mimeType' => mime_content_type($file),
    //     //     'path' => $file,
    //     //     'url' => base_url($file),
    //     //     'createdAt' => date('Y-m-d H:i:s'),
    //     //     'fileType' => $fileType,
    //     // )], 500);

    //     // if (!$photos) {
    //     //     $this->respondJson(['status' => 'fail to upload.'], 500);
    //     // }
    // }
    private function updateDatabse($file = '')
    {
        // -----------------------------
        // Detect extension & type
        // -----------------------------
        $extension = strtolower(pathinfo($this->UPLOAD_NAME, PATHINFO_EXTENSION));
        $img  = ['jpg','jpeg','png','gif','webp'];
        $vid  = ['mp4','mov','avi','3gp'];
        $aud  = ['mp3','wav','m4a','aac','ogg'];

        $fileType = 'file';
        if (in_array($extension, $img, true))  $fileType = 'image';
        if (in_array($extension, $vid, true))  $fileType = 'video';
        if (in_array($extension, $aud, true))  $fileType = 'audio';

        // -----------------------------
        // Basic values
        // -----------------------------
        $table          = $this->UPLOAD_SETTINGS['dbTable'];
        $fileColumn     = $this->UPLOAD_SETTINGS['fileColumn'];     // media_key
        $fileTypeColumn = $this->UPLOAD_SETTINGS['fileTypeColumn']; // file_type
        $forignKey      = $this->UPLOAD_SETTINGS['forignKey'];
        $forignValue    = $this->UPLOAD_SETTINGS['forignValue'];

        $isUpdate = $this->UPLOAD_SETTINGS['isUpdate'] ?? 'N';

        $where = [];
        if (!empty($this->UPLOAD_SETTINGS['primaryKey'])) {
            $where[$this->UPLOAD_SETTINGS['primaryKey']] =
                $this->UPLOAD_SETTINGS['primaryValue'];
        }

        // -----------------------------
        // Stored filename
        // -----------------------------
        $storedName = basename($file);

        // -----------------------------
        // NEW METADATA (IMPORTANT)
        // -----------------------------
        $mimeType = function_exists('mime_content_type')
            ? mime_content_type($file)
            : 'application/octet-stream';

        $fileSize = filesize($file);

        $extraData = [
            // 🔥 new columns
            'original_name' => $this->UPLOAD_NAME,
            'mime_type'     => $mimeType,
            'file_size'     => $fileSize,
            'storage_disk'  => 'local',

            // 🔥 module binding
            'module'        => $this->UPLOAD_SETTINGS['module'], // $this->CI->input->post('recordType'),
            'record_id'     => $this->UPLOAD_SETTINGS['record_id'],//$this->CI->input->post('recordId'),
            'company_id'=> $this->UPLOAD_SETTINGS['company_id'],
            'created_date'  => date('Y-m-d H:i:s'),
        ];
        // Merge any caller-provided extraData
        if (!empty($this->UPLOAD_SETTINGS['extraData'])) {
            $extraData = array_merge($extraData, $this->UPLOAD_SETTINGS['extraData']);
        }

        // -----------------------------
        // Image optimization (unchanged)
        // -----------------------------
        try {
            if ($fileType === 'image') {
                if ($this->CI->imageopt->optimize_image($file, $file)) {
                    $this->CI->imageopt->create_thumbnails(
                        $file,
                        $this->UPLOAD_SETTINGS['uploadFolder']
                    );
                }
            }
        } catch (\Throwable $e) {
            // never break upload
        }

        // -----------------------------
        // SAVE TO DB
        // -----------------------------
        $mediaId = $this->CI->CommonModel->saveFile(
            $table,
            $fileColumn,        // media_key
            $storedName,        // stored filename
            $forignValue,
            $fileTypeColumn,    // file_type
            $fileType,
            $forignKey,
            $extraData,
            $file,
            $isUpdate,
            $where
        );

        if (!$mediaId) {
            $this->respondJson(
                ['flag' => 'F', 'msg' => 'DB save failed'],
                500
            );
        }

        // -----------------------------
        // 🔥 RETURN SAFE FILE OBJECT
        // -----------------------------
        return [
            'id'        => (int)$mediaId,
            'name'      => $this->UPLOAD_NAME,
            'size'      => $fileSize,
            'mimeType'  => $mimeType,
            'fileType'  => $fileType,
            'createdAt' => $extraData['created_date'],
            'token'     => $this->generateFileToken(
                $mediaId,
                $this->UPLOAD_SETTINGS['company_id'],
            ),
        ];
    }
    private function generateFileToken($mediaId, $companyId)
    {
        return rtrim(strtr(
            base64_encode($mediaId . '|' . $companyId),
            '+/', '-_'
        ), '=');
    }
    private function detectFileTypeByExtension($filename)
    {
        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

        if (in_array($ext, ['jpg','jpeg','png','gif','webp'], true)) {
            return 'image';
        }
        if (in_array($ext, ['mp4','mov','avi','3gp'], true)) {
            return 'video';
        }
        if (in_array($ext, ['mp3','wav','m4a','aac','ogg'], true)) {
            return 'audio';
        }
        return 'file';
    }

}
