<?php
declare(strict_types=1);

require_once __DIR__ . '/_auth.php';
tpcms_require_admin();

require_once __DIR__ . '/../config.php';

session_start();
header_remove('X-Powered-By');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');

function tpcms_json_out(array $a, int $status = 200): never {
    http_response_code($status);
    header('Content-Type: application/json; charset=UTF-8');
    echo json_encode($a, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

// ---- CSRF（POST時は必須：トークン必須）----
$__csrf = (string)($_POST['_csrf'] ?? $_POST['csrf_token'] ?? '');
if (!function_exists('tpcms_verify_csrf') || !tpcms_verify_csrf($__csrf)) {
    tpcms_json_out(['ok'=>false, 'error'=>'不正なリクエストです（CSRF）', 'code'=>'E_CSRF'], 400);
}
unset($__csrf);

// -------------------- 本処理：POSTのみ --------------------
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    tpcms_json_out(['ok'=>false, 'error'=>'POST以外は許可されていません', 'code'=>'E_METHOD'], 405);
}

if (!isset($_FILES['file']) || !is_array($_FILES['file'])) {
    tpcms_json_out(['ok'=>false, 'error'=>'ファイルを選択してください', 'code'=>'E_NOFILE'], 400);
}

$errCode = $_FILES['file']['error'] ?? UPLOAD_ERR_OK;
if ($errCode !== UPLOAD_ERR_OK) {
    $map = [
        UPLOAD_ERR_INI_SIZE   => 'ファイルが大きすぎます（ini設定）',
        UPLOAD_ERR_FORM_SIZE  => 'ファイルが大きすぎます（フォーム制限）',
        UPLOAD_ERR_PARTIAL    => 'アップロードが途中で中断されました',
        UPLOAD_ERR_NO_FILE    => 'ファイルが選択されていません',
        UPLOAD_ERR_NO_TMP_DIR => '一時ディレクトリが見つかりません',
        UPLOAD_ERR_CANT_WRITE => 'ディスクに書き込めません',
        UPLOAD_ERR_EXTENSION  => '拡張子によってブロックされました',
    ];
    $msg = $map[$errCode] ?? 'アップロードに失敗しました';
    tpcms_json_out(['ok'=>false, 'error'=>$msg, 'code'=>'E_UPERR'], 400);
}

$tmp  = $_FILES['file']['tmp_name'];
$name = $_FILES['file']['name'] ?? 'file';
$size = (int)($_FILES['file']['size'] ?? 0);

if (!is_uploaded_file($tmp)) {
    tpcms_json_out(['ok'=>false, 'error'=>'不正なアップロードです', 'code'=>'E_NOT_UPLOADED'], 400);
}

// ---- 許可MIME / 拡張子 ----
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime  = finfo_file($finfo, $tmp) ?: '';
finfo_close($finfo);

$allowed = [
    'image/jpeg' => 'jpg',
    'image/png'  => 'png',
    'image/gif'  => 'gif',
    'image/webp' => 'webp',
    'image/svg+xml' => 'svg',
    'image/x-icon' => 'ico',
    'image/vnd.microsoft.icon' => 'ico',
    'video/mp4'  => 'mp4',
    'video/webm' => 'webm',
];

if (!isset($allowed[$mime])) {
    tpcms_json_out(['ok'=>false, 'error'=>'許可されていないファイル形式です', 'code'=>'E_MIME'], 400);
}

// ---- サイズ上限 ----
$extByMime = $allowed[$mime];
$isImage = str_starts_with($mime, 'image/');
$isVideo = str_starts_with($mime, 'video/');
$max = $isImage ? (10 * 1024 * 1024) : (30 * 1024 * 1024);
if ($size <= 0 || $size > $max) {
    tpcms_json_out(['ok'=>false, 'error'=> ($isImage ? '画像は10MB以下にしてください' : '動画は30MB以下にしてください'), 'code'=>'E_SIZE'], 400);
}

// ---- 危険なSVG対策（簡易）：<script> や on* 属性を含む場合は拒否 ----
if ($mime === 'image/svg+xml') {
    $svg = file_get_contents($tmp, false, null, 0, 200000); // 200KBまで検査
    if ($svg === false) {
        tpcms_json_out(['ok'=>false, 'error'=>'SVGを読み取れませんでした', 'code'=>'E_SVG_READ'], 400);
    }
    $lower = strtolower($svg);
    if (preg_match('~<\s*script|on\w+\s*=~', $lower)) {
        tpcms_json_out(['ok'=>false, 'error'=>'安全でないSVGが検出されました', 'code'=>'E_SVG_UNSAFE'], 400);
    }
}

// ---- ファイル名生成 ----
$extUp = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$ext   = $extByMime; // MIMEに合わせる（偽装対策）
$ts    = date('Ymd_His');
$rand  = random_int(1000, 9999);
$base  = "{$ts}_{$rand}.{$ext}";
$saveDir = TPCMS_UPLOADS;
$savePath = $saveDir . '/' . $base;

// 念のためアップロード先の存在確認
if (!is_dir($saveDir)) {
    @mkdir($saveDir, 0775, true);
}
if (!is_dir($saveDir) || !is_writable($saveDir)) {
    tpcms_json_out(['ok'=>false, 'error'=>'保存先に書き込めません', 'code'=>'E_SAVE_DIR'], 500);
}

// ---- 保存 ----
if (!move_uploaded_file($tmp, $savePath)) {
    tpcms_json_out(['ok'=>false, 'error'=>'ファイル保存に失敗しました', 'code'=>'E_SAVE'], 500);
}
@chmod($savePath, 0644);

// ---- 画像リサイズ（任意：jpeg/png/webpのみ・縮小のみ） ----
if ($isImage && $mime !== 'image/svg+xml' && extension_loaded('gd')) {
    $maxW = isset($_POST['image_max_width'])  ? (int)$_POST['image_max_width']  : 0;
    $maxH = isset($_POST['image_max_height']) ? (int)$_POST['image_max_height'] : 0;
    $qual = isset($_POST['image_quality'])    ? (int)$_POST['image_quality']    : 82;

// ---- EXIF 自動回転（JPEGのみ） ----
// ※ exif拡張がない環境では自動的にスキップします
if ($mime === 'image/jpeg' && function_exists('exif_read_data')) {
    $ex = @exif_read_data($savePath);
    $orientation = 0;
    if (is_array($ex) && !empty($ex['Orientation'])) {
        $orientation = (int)$ex['Orientation'];
    }
    if ($orientation >= 2 && $orientation <= 8) {
        $src = @imagecreatefromjpeg($savePath);
        if ($src) {
            // imagerotate は反時計回り。数値はEXIFの定義に準拠
            switch ($orientation) {
                case 2: imageflip($src, IMG_FLIP_HORIZONTAL); break;               // 水平反転
                case 3: $src = imagerotate($src, 180, 0); break;                   // 180度
                case 4: imageflip($src, IMG_FLIP_VERTICAL); break;                 // 垂直反転
                case 5: $src = imagerotate($src, 270, 0); imageflip($src, IMG_FLIP_HORIZONTAL); break; // 90度CW+水平反転
                case 6: $src = imagerotate($src, 270, 0); break;                   // 90度CW
                case 7: $src = imagerotate($src, 90, 0);  imageflip($src, IMG_FLIP_HORIZONTAL); break; // 90度CCW+水平反転
                case 8: $src = imagerotate($src, 90, 0);  break;                   // 90度CCW
            }
            // この段階では「向きだけ」直す（原寸のまま）。続く縮小処理があれば再保存されます
            @imagejpeg($src, $savePath, ($qual >= 1 && $qual <= 100) ? $qual : 90);
            @chmod($savePath, 0644);
            imagedestroy($src);
            clearstatcache(true, $savePath);
        }
    }
}

    if ($qual < 1)   $qual = 1;
    if ($qual > 100) $qual = 100;

    // GIFはアニメ喪失回避のためスキップ、WEBPは関数存在チェック
    $resizable = in_array($mime, ['image/jpeg','image/png','image/webp'], true);
    if ($resizable && ($maxW > 0 || $maxH > 0)) {
        $info = @getimagesize($savePath);
        if (is_array($info) && isset($info[0], $info[1]) && $info[0] > 0 && $info[1] > 0) {
            $srcW = (int)$info[0];
            $srcH = (int)$info[1];

            // 縮小率（scale-downのみ、拡大しない）
            $scale = 1.0;
            if ($maxW > 0) $scale = min($scale, $maxW / $srcW);
            if ($maxH > 0) $scale = min($scale, $maxH / $srcH);

            if ($scale < 1.0) {
                $dstW = max(1, (int)floor($srcW * $scale));
                $dstH = max(1, (int)floor($srcH * $scale));

                // ソース読み込み
                $src = null;
                if ($mime === 'image/jpeg') {
                    $src = @imagecreatefromjpeg($savePath);
                } elseif ($mime === 'image/png') {
                    $src = @imagecreatefrompng($savePath);
                } elseif ($mime === 'image/webp' && function_exists('imagecreatefromwebp')) {
                    $src = @imagecreatefromwebp($savePath);
                }

                if ($src) {
                    $dst = imagecreatetruecolor($dstW, $dstH);

                    // 透過維持（PNG/WEBP）
                    if ($mime === 'image/png' || $mime === 'image/webp') {
                        imagealphablending($dst, false);
                        imagesavealpha($dst, true);
                    }

                    imagecopyresampled($dst, $src, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);

                    // 上書き保存
                    $okSave = false;
                    if ($mime === 'image/jpeg') {
                        $okSave = @imagejpeg($dst, $savePath, $qual);
                    } elseif ($mime === 'image/png') {
                        // JPEGのquality(1-100)をPNG圧縮(0-9)へ変換（大雑把だが十分）
                        $comp = (int)round((100 - $qual) / 100 * 9);
                        if ($comp < 0) $comp = 0;
                        if ($comp > 9) $comp = 9;
                        $okSave = @imagepng($dst, $savePath, $comp);
                    } elseif ($mime === 'image/webp' && function_exists('imagewebp')) {
                        $okSave = @imagewebp($dst, $savePath, $qual);
                    }

                    if ($okSave) {
                        @chmod($savePath, 0644);
                        clearstatcache(true, $savePath);
                    }

                    imagedestroy($dst);
                    imagedestroy($src);
                }
            }
        }
    }
}

// ---- 画像サイズ取得（動画/SVGは null） ----
$width = null; $height = null;
if ($isImage && $mime !== 'image/svg+xml') {
    $info = @getimagesize($savePath);
    if (is_array($info) && isset($info[0], $info[1])) {
        $width  = (int)$info[0];
        $height = (int)$info[1];
    }
}

// ---- 応答 ----
// サブフォルダ配備でも壊れないよう、先頭スラッシュ無しの相対URLを返す（例：uploads/xxxx.jpg）
$url = '/uploads/' . $base;
$type = $isImage ? 'image' : ($isVideo ? 'video' : 'file');

tpcms_json_out([
    'ok'     => true,
    'name'   => $base,
    'url'    => $url,
    'type'   => $type,
    'width'  => $width,
    'height' => $height,
], 200);
