<?php
namespace App\Controllers;

use App\Core\{Auth, Csrf, Database, Response, Upload};
use App\Models\{Artist, BankAccount, CommunityPost, CreatorBalance, CreatorLedger, Order, Package, Profile, Setting, Story, Subscription, User, WithdrawalRequest};
use App\Services\Security\MusicCopyrightScanner;
use App\Services\Security\ZaloroxShield;
use App\Services\Translate\ZaloroxTranslateBot;
use PDO;
use Throwable;
use function App\Core\url;

class StudioController extends BaseController
{
    public function dashboard(): string
    {
        Auth::requireUser();

        return $this->view('studio', 'studio/index', [
            'title' => 'Studio',
            'section' => 'dashboard',
            'data' => $this->samples(),
            'testMode' => $this->testMode(),
        ]);
    }

    public function section(?string $routeId = null): string
    {
        Auth::requireUser();
        [$section, $id] = $this->routeSection($routeId);

        if ($section === 'abonelikler') {
            return $this->subscriptions();
        }

        if ($section === 'kazanclarim') {
            return $this->earnings();
        }

        if ($section === 'para-cek') {
            return $this->withdrawals();
        }

        if ($section === 'bilgilerim') {
            return $this->billingInfo();
        }

        if ($section === 'profilim') {
            return $this->profileSettings();
        }

        if (in_array($section, ['artist', 'artist-basvuru'], true)) {
            return $this->artistConnect();
        }

        if ($section === 'ayarlar') {
            return $this->settings();
        }

        if ($section === 'mod-yukle') {
            return $this->view('studio', 'studio/upload-mod', $this->studioUploadData('mod') + [
                'title' => 'Studio - Mod Yukle',
                'mode' => 'create',
                'section' => $section,
            ]);
        }

        if ($section === 'mod-duzenle') {
            $mod = $this->findOwnedMod((int) $id);

            return $this->view('studio', 'studio/edit-mod', $this->studioUploadData('mod', $mod) + [
                'title' => 'Studio - Mod Duzenle',
                'mode' => 'edit',
                'section' => $section,
                'mod' => $mod,
            ]);
        }

        if ($section === 'muzik-yukle') {
            return $this->view('studio', 'studio/upload-music', $this->studioUploadData('music') + [
                'title' => 'Studio - Muzik Yukle',
                'mode' => 'create',
                'section' => $section,
            ]);
        }

        if ($section === 'muzik-duzenle') {
            $track = $this->findOwnedMusic((int) $id);

            return $this->view('studio', 'studio/edit-music', $this->studioUploadData('music', $track) + [
                'title' => 'Studio - Muzik Duzenle',
                'mode' => 'edit',
                'section' => $section,
                'track' => $track,
            ]);
        }

        if ($section === 'modlarim') {
            return $this->view('studio', 'studio/my-mods', [
                'title' => 'Studio - Modlarim',
                'section' => $section,
                'mods' => $this->ownedMods(),
            ]);
        }

        if ($section === 'muziklerim') {
            return $this->view('studio', 'studio/my-music', [
                'title' => 'Studio - Muziklerim',
                'section' => $section,
                'tracks' => $this->ownedMusic(),
            ]);
        }

        return $this->view('studio', 'studio/index', [
            'title' => 'Studio',
            'section' => $section,
            'data' => $this->samples(),
            'testMode' => $this->testMode(),
        ]);
    }

    public function saveMod(?string $id = null): Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Giris yapmalisin.'], 403);
        }

        if (!Csrf::verify((string) ($_POST['_csrf'] ?? ''))) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $modId = (int) ($id ?? 0);
        $existing = $modId > 0 ? $this->findOwnedMod($modId) : null;
        if ($modId > 0 && !$existing) {
            return Response::json(['ok' => false, 'message' => 'Mod bulunamadi veya yetkin yok.'], 404);
        }

        $title = $this->clean((string) ($_POST['title'] ?? ''), 180);
        $description = trim((string) ($_POST['description'] ?? ''));
        $externalLink = $this->clean((string) ($_POST['external_link'] ?? ''), 500);
        $tags = $this->normalizeTags((string) ($_POST['tags'] ?? ''));
        $action = (string) ($_POST['submit_action'] ?? 'publish');
        $errors = [];

        if ($title === '') {
            $errors['title'] = 'Mod basligi zorunlu.';
        }

        if ($action !== 'draft' && $externalLink === '') {
            $errors['external_link'] = 'Yayin icin indirme linki zorunlu.';
        }

        if ($externalLink !== '' && filter_var($externalLink, FILTER_VALIDATE_URL) === false) {
            $errors['external_link'] = 'Gecerli bir URL yaz.';
        }

        if ($errors !== []) {
            return Response::json(['ok' => false, 'message' => 'Alanlari kontrol et.', 'errors' => $errors], 422);
        }

        try {
            $gate = $this->shieldGate($externalLink, $title, $description, 'mod', $modId ?: null);
            if (!empty($gate['blocked'])) {
                return Response::json([
                    'ok' => false,
                    'message' => 'Zalorox Shield bu kaydi engelledi.',
                    'errors' => ['external_link' => 'Link veya icerik guvenli degil.'],
                    'shield' => $gate,
                ], 422);
            }

            $pdo = Database::connection($this->config);
            $user = Auth::user() ?? [];
            $userId = (int) ($user['id'] ?? 0);
            $categoryId = max(0, (int) ($_POST['category_id'] ?? 0)) ?: null;
            $privacy = $this->normalizePrivacy((string) ($_POST['privacy'] ?? 'public'));
            $packageId = $privacy === 'package' ? max(0, (int) ($_POST['package_id'] ?? 0)) : null;
            $dbPrivacy = $privacy === 'package' ? 'subscriber' : $privacy;
            $accessType = in_array($dbPrivacy, ['subscriber', 'premium', 'private'], true) || $packageId ? 'subscriber' : 'free';
            $status = $action === 'draft' ? 'draft' : (!empty($gate['review']) ? 'pending' : 'published');
            $cover = (string) ($existing['cover_image'] ?? $existing['cover_path'] ?? '');

            if ((string) ($_POST['remove_cover'] ?? '') === '1') {
                $cover = '';
            }

            if (!empty($_FILES['cover']['name'])) {
                $cover = Upload::storeImage($_FILES['cover'], (string) $this->config['MOD_COVER_UPLOAD_PATH'], (int) $this->config['MOD_MAX_COVER_MB']);
            }

            $summary = $this->clean((string) ($_POST['summary'] ?? ''), 255);
            if ($summary === '') {
                $summary = $this->clean(strip_tags($description), 255);
            }

            $slug = $existing['slug'] ?? $this->uniqueSlug('mods', $title);
            $pdo->beginTransaction();

            if ($existing) {
                $statement = $pdo->prepare(
                    'UPDATE mods
                     SET category_id = :category_id,
                         title = :title,
                         summary = :summary,
                         description = :description,
                         external_link = :external_link,
                         external_link_status = :external_link_status,
                         external_link_risk_score = :external_link_risk_score,
                         cover_path = :cover_path,
                         cover_image = :cover_image,
                         privacy = :privacy,
                         package_id = :package_id,
                         file_url = :file_url,
                         access_type = :access_type,
                         status = :status,
                         published_at = IF(:status_published = "published", COALESCE(published_at, NOW()), published_at),
                         updated_at = NOW()
                     WHERE id = :id'
                );
                $statement->execute([
                    'id' => (int) $existing['id'],
                    'category_id' => $categoryId,
                    'title' => $title,
                    'summary' => $summary,
                    'description' => $description,
                    'external_link' => $externalLink ?: null,
                    'external_link_status' => $this->modLinkStatus((string) ($gate['link_status'] ?? 'safe')),
                    'external_link_risk_score' => (int) ($gate['risk_score'] ?? 0),
                    'cover_path' => $cover ?: null,
                    'cover_image' => $cover ?: null,
                    'privacy' => $dbPrivacy,
                    'package_id' => $packageId,
                    'file_url' => $externalLink ?: null,
                    'access_type' => $accessType,
                    'status' => $status,
                    'status_published' => $status,
                ]);
                $modId = (int) $existing['id'];
            } else {
                $statement = $pdo->prepare(
                    'INSERT INTO mods
                        (user_id, creator_id, category_id, title, slug, summary, description, external_link,
                         external_link_status, external_link_risk_score, cover_path, cover_image, privacy,
                         package_id, file_url, access_type, status, published_at, created_at, updated_at)
                     VALUES
                        (:user_id, :creator_id, :category_id, :title, :slug, :summary, :description, :external_link,
                         :external_link_status, :external_link_risk_score, :cover_path, :cover_image, :privacy,
                         :package_id, :file_url, :access_type, :status, IF(:status_published = "published", NOW(), NULL), NOW(), NOW())'
                );
                $statement->execute([
                    'user_id' => $userId,
                    'creator_id' => $userId,
                    'category_id' => $categoryId,
                    'title' => $title,
                    'slug' => $slug,
                    'summary' => $summary,
                    'description' => $description,
                    'external_link' => $externalLink ?: null,
                    'external_link_status' => $this->modLinkStatus((string) ($gate['link_status'] ?? 'safe')),
                    'external_link_risk_score' => (int) ($gate['risk_score'] ?? 0),
                    'cover_path' => $cover ?: null,
                    'cover_image' => $cover ?: null,
                    'privacy' => $dbPrivacy,
                    'package_id' => $packageId,
                    'file_url' => $externalLink ?: null,
                    'access_type' => $accessType,
                    'status' => $status,
                    'status_published' => $status,
                ]);
                $modId = (int) $pdo->lastInsertId();
            }

            $this->syncModImages($pdo, $modId);
            $this->syncModVideos($pdo, $modId);
            $this->syncModModels($pdo, $modId);
            $this->persistTags($pdo, 'mods', $modId, $tags);
            try {
                (new ZaloroxTranslateBot($this->config, null, $pdo))->createDrafts('mod', $modId, $title, $summary, $description, $userId);
            } catch (Throwable $translationException) {
                $this->logException($translationException, 'studio.translate.mod');
            }
            $pdo->commit();

            return Response::json([
                'ok' => true,
                'message' => $status === 'pending' ? 'Mod incelemeye gonderildi.' : 'Mod kaydedildi.',
                'redirect_url' => url('/studyo/modlarim'),
                'status' => $status,
                'shield' => $gate,
            ]);
        } catch (Throwable $exception) {
            if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
                $pdo->rollBack();
            }

            $this->logException($exception, 'studio.saveMod');

            return Response::json(['ok' => false, 'message' => 'Mod kaydedilemedi.'], 500);
        }
    }

    public function saveMusic(?string $id = null): Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Giris yapmalisin.'], 403);
        }

        if (!Csrf::verify((string) ($_POST['_csrf'] ?? ''))) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $trackId = (int) ($id ?? 0);
        $existing = $trackId > 0 ? $this->findOwnedMusic($trackId) : null;
        if ($trackId > 0 && !$existing) {
            return Response::json(['ok' => false, 'message' => 'Muzik bulunamadi veya yetkin yok.'], 404);
        }

        $artistProfile = null;
        try {
            $artistProfile = (new Artist())->forUser((int) (Auth::id() ?? 0));
        } catch (Throwable) {
            $artistProfile = null;
        }
        $artistStatus = (string) ($artistProfile['status'] ?? (!empty($artistProfile['verified']) ? 'approved' : 'missing'));
        if (!$existing && $artistStatus !== 'approved') {
            $message = match ($artistStatus) {
                'pending' => 'Sanatci profilin inceleme bekliyor.',
                'rejected' => 'Sanatci profilin reddedildi. ZPanel incelemesi gerekiyor.',
                default => 'Muzik yuklemek icin once Zalorox Artist Connect profili olusturmalisin.',
            };

            return Response::json(['ok' => false, 'message' => $message, 'redirect_url' => url('/studyo/artist')], 403);
        }

        $title = $this->clean((string) ($_POST['title'] ?? ''), 180);
        $artist = $this->clean((string) ($_POST['artist_name'] ?? ''), 160);
        if ($artistStatus === 'approved' && !empty($artistProfile['artist_name'])) {
            $artist = $this->clean((string) $artistProfile['artist_name'], 160);
        }
        $description = trim((string) ($_POST['description'] ?? ''));
        $tags = $this->normalizeTags((string) ($_POST['tags'] ?? ''));
        $action = (string) ($_POST['submit_action'] ?? 'publish');
        $errors = [];

        if ($title === '') {
            $errors['title'] = 'Sarki basligi zorunlu.';
        }

        if ($artist === '') {
            $errors['artist_name'] = 'Sanatci adi zorunlu.';
        }

        $hasNewAudio = !empty($_FILES['audio_file']['name']);
        $existingAudio = (string) ($existing['audio_file'] ?? '');
        $removeAudio = (string) ($_POST['remove_audio'] ?? '') === '1';
        if ($action !== 'draft' && !$hasNewAudio && ($existingAudio === '' || $removeAudio)) {
            $errors['audio_file'] = 'Yayin icin audio dosyasi zorunlu.';
        }

        foreach (['youtube_url', 'spotify_url', 'apple_music_url', 'external_link'] as $urlField) {
            $urlValue = trim((string) ($_POST[$urlField] ?? ''));
            if ($urlValue !== '' && filter_var($urlValue, FILTER_VALIDATE_URL) === false) {
                $errors[$urlField] = 'Gecerli bir URL yaz.';
            }
        }

        if ($errors !== []) {
            return Response::json(['ok' => false, 'message' => 'Alanlari kontrol et.', 'errors' => $errors], 422);
        }

        try {
            $contentScan = (new ZaloroxShield($this->config))->scanMusicContent($title, $description, Auth::id(), $trackId ?: null);
            if (($contentScan['status'] ?? 'safe') === 'blocked') {
                return Response::json([
                    'ok' => false,
                    'message' => 'Zalorox Shield bu muzik kaydini engelledi.',
                    'errors' => ['description' => 'Baslik veya aciklama guvenlik kontrolunden gecemedi.'],
                    'shield' => $contentScan,
                ], 422);
            }

            $pdo = Database::connection($this->config);
            $userId = (int) (Auth::id() ?? 0);
            $categoryId = max(0, (int) ($_POST['genre_id'] ?? $_POST['category_id'] ?? 0)) ?: null;
            $privacy = $this->normalizeMusicPrivacy((string) ($_POST['privacy'] ?? 'public'));
            $packageId = null;
            $dbPrivacy = $privacy;
            $status = $action === 'draft' ? 'draft' : (($contentScan['status'] ?? 'safe') === 'review' ? 'pending_review' : 'published');
            $audioFile = $existingAudio;
            $cover = (string) ($existing['cover_image'] ?? '');
            $copyrightScan = ['status' => 'clean', 'risk_score' => 0, 'reasons' => [], 'file_hash' => null, 'provider' => 'local_hash'];

            if ($removeAudio) {
                $audioFile = '';
            }

            if ((string) ($_POST['remove_cover'] ?? '') === '1') {
                $cover = '';
            }

            if ($hasNewAudio) {
                $audioFile = Upload::storeAudio(
                    $_FILES['audio_file'],
                    (string) $this->config['MUSIC_UPLOAD_PATH'],
                    (array) $this->config['MUSIC_ALLOWED_EXTENSIONS'],
                    (int) $this->config['MUSIC_MAX_UPLOAD_MB']
                );

                $copyrightScan = (new MusicCopyrightScanner($this->config))->scanUpload($_FILES['audio_file'], [
                    'stored_path' => $audioFile,
                    'title' => $title,
                    'artist_name' => $artist,
                    'description' => $description,
                    'user_id' => $userId,
                    'track_id' => $trackId ?: null,
                    'artist_approved' => $artistStatus === 'approved',
                    'rights_confirmed' => !empty($artistProfile['rights_confirmed_at']),
                ]);

                if (($copyrightScan['status'] ?? 'clean') === 'blocked') {
                    $status = 'blocked';
                } elseif (in_array((string) ($copyrightScan['status'] ?? 'clean'), ['review', 'duplicate', 'pending'], true) && $status !== 'draft') {
                    $status = 'pending_review';
                }
            }

            if (!empty($_FILES['cover']['name'])) {
                $cover = Upload::storeImage($_FILES['cover'], (string) $this->config['MUSIC_COVER_UPLOAD_PATH'], (int) $this->config['MOD_MAX_COVER_MB']);
            }

            $slug = $existing['slug'] ?? $this->uniqueSlug('music_tracks', $title);
            $pdo->beginTransaction();

            $payload = [
                'user_id' => $userId,
                'category_id' => $categoryId,
                'title' => $title,
                'slug' => $slug,
                'artist_name' => $artist,
                'description' => $description,
                'cover_image' => $cover ?: null,
                'audio_file' => $audioFile ?: null,
                'external_link' => $this->clean((string) ($_POST['external_link'] ?? ''), 500) ?: null,
                'youtube_url' => $this->clean((string) ($_POST['youtube_url'] ?? ''), 500) ?: null,
                'spotify_url' => $this->clean((string) ($_POST['spotify_url'] ?? ''), 500) ?: null,
                'apple_music_url' => $this->clean((string) ($_POST['apple_music_url'] ?? ''), 500) ?: null,
                'privacy' => $dbPrivacy,
                'package_id' => $packageId,
                'status' => $status,
            ];

            if ($existing) {
                $statement = $pdo->prepare(
                    'UPDATE music_tracks
                     SET category_id = :category_id,
                         title = :title,
                         artist_name = :artist_name,
                         description = :description,
                         cover_image = :cover_image,
                         audio_file = :audio_file,
                         external_link = :external_link,
                         youtube_url = :youtube_url,
                         spotify_url = :spotify_url,
                         apple_music_url = :apple_music_url,
                         privacy = :privacy,
                         package_id = :package_id,
                         status = :status,
                         updated_at = NOW()
                     WHERE id = :id'
                );
                $statement->execute([
                    'id' => (int) $existing['id'],
                    'category_id' => $payload['category_id'],
                    'title' => $payload['title'],
                    'artist_name' => $payload['artist_name'],
                    'description' => $payload['description'],
                    'cover_image' => $payload['cover_image'],
                    'audio_file' => $payload['audio_file'],
                    'external_link' => $payload['external_link'],
                    'youtube_url' => $payload['youtube_url'],
                    'spotify_url' => $payload['spotify_url'],
                    'apple_music_url' => $payload['apple_music_url'],
                    'privacy' => $payload['privacy'],
                    'package_id' => $payload['package_id'],
                    'status' => $payload['status'],
                ]);
                $trackId = (int) $existing['id'];
            } else {
                $statement = $pdo->prepare(
                    'INSERT INTO music_tracks
                        (user_id, category_id, title, slug, artist_name, description, cover_image, audio_file,
                         external_link, youtube_url, spotify_url, apple_music_url, privacy, package_id, status,
                         created_at, updated_at)
                     VALUES
                        (:user_id, :category_id, :title, :slug, :artist_name, :description, :cover_image, :audio_file,
                         :external_link, :youtube_url, :spotify_url, :apple_music_url, :privacy, :package_id, :status,
                         NOW(), NOW())'
                );
                $statement->execute($payload);
                $trackId = (int) $pdo->lastInsertId();
            }

            if ($artistStatus === 'approved' && !empty($artistProfile['id']) && $this->tableHasColumn($pdo, 'music_tracks', 'artist_id')) {
                $pdo->prepare('UPDATE music_tracks SET artist_id = :artist_id WHERE id = :id')
                    ->execute(['artist_id' => (int) $artistProfile['id'], 'id' => $trackId]);
            }

            $this->syncMusicGenre($pdo, $trackId, $categoryId);
            $this->persistTags($pdo, 'music_tracks', $trackId, $tags);
            $copyrightScan['track_id'] = $trackId;
            $copyrightScan['user_id'] = $userId;
            $copyrightScan['title'] = $title;
            $copyrightScan['artist_name'] = $artist;
            try {
                (new ZaloroxTranslateBot($this->config, null, $pdo))->createDrafts('music', $trackId, $title, $description, $description, $userId);
            } catch (Throwable $translationException) {
                $this->logException($translationException, 'studio.translate.music');
            }
            (new MusicCopyrightScanner($this->config, $pdo))->logResult($copyrightScan);
            $pdo->commit();

            return Response::json([
                'ok' => true,
                'message' => in_array($status, ['pending', 'pending_review'], true) ? 'Muzik incelemeye gonderildi.' : ($status === 'blocked' ? 'Muzik telif kontrolunde engellendi.' : 'Muzik kaydedildi.'),
                'redirect_url' => url('/studyo/muziklerim'),
                'status' => $status,
                'shield' => $contentScan,
                'copyright' => $copyrightScan,
            ]);
        } catch (Throwable $exception) {
            if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
                $pdo->rollBack();
            }

            $this->logException($exception, 'studio.saveMusic');

            return Response::json(['ok' => false, 'message' => 'Muzik kaydedilemedi.'], 500);
        }
    }

    public function deleteMod(string $id): Response
    {
        return $this->archiveOwned('mods', (int) $id, 'Mod arsivlendi.', '/studyo/modlarim');
    }

    public function deleteMusic(string $id): Response
    {
        return $this->archiveOwned('music_tracks', (int) $id, 'Muzik arsivlendi.', '/studyo/muziklerim');
    }

    public function createMod(): Response
    {
        return $this->saveMod(null);
    }

    public function updateMod(): Response
    {
        $id = $this->postedContentId();
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Mod ID gecersiz.', 'errors' => ['id' => 'Mod ID gecersiz.']], 422);
        }

        return $this->saveMod((string) $id);
    }

    public function deleteModRequest(): Response
    {
        $id = $this->postedContentId();
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Mod ID gecersiz.'], 422);
        }

        return $this->deleteMod((string) $id);
    }

    public function createMusic(): Response
    {
        return $this->saveMusic(null);
    }

    public function updateMusic(): Response
    {
        $id = $this->postedContentId();
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Muzik ID gecersiz.', 'errors' => ['id' => 'Muzik ID gecersiz.']], 422);
        }

        return $this->saveMusic((string) $id);
    }

    public function deleteMusicRequest(): Response
    {
        $id = $this->postedContentId();
        if ($id < 1) {
            return Response::json(['ok' => false, 'message' => 'Muzik ID gecersiz.'], 422);
        }

        return $this->deleteMusic((string) $id);
    }

    public function updateProfile(): Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Giris yapmalisin.'], 403);
        }

        if (!Csrf::verify((string) ($_POST['_csrf'] ?? ''))) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $userId = (int) (Auth::id() ?? 0);
        $displayName = $this->clean((string) ($_POST['display_name'] ?? ''), 120);
        $nickname = strtolower($this->clean((string) ($_POST['nickname'] ?? ''), 80));
        $nickname = preg_replace('~[^a-z0-9._-]+~', '-', $nickname) ?: '';
        $nickname = trim($nickname, '-');
        $about = trim((string) ($_POST['about'] ?? ''));
        $privacy = (string) ($_POST['privacy'] ?? 'public');
        $messagePrivacy = (string) ($_POST['message_privacy'] ?? 'everyone');
        $errors = [];

        if (strlen($displayName) < 2) {
            $errors['display_name'] = 'Gorunen ad en az 2 karakter olmali.';
        }

        if (!preg_match('~^[a-z0-9._-]{3,80}$~', $nickname)) {
            $errors['nickname'] = 'Takma ad 3-80 karakter, harf/rakam/nokta/tire/alt tire olabilir.';
        }

        if (strlen($about) > 2000) {
            $errors['about'] = 'Hakkimda alani 2000 karakteri gecemez.';
        }

        if (!in_array($privacy, ['public', 'logged_in', 'private'], true)) {
            $errors['privacy'] = 'Profil gorunurlugu gecersiz.';
        }

        if (!in_array($messagePrivacy, ['everyone', 'friends', 'none'], true)) {
            $errors['message_privacy'] = 'Mesaj gizliligi gecersiz.';
        }

        $linkFields = [
            'instagram' => 'instagram',
            'youtube' => 'youtube',
            'profile_website' => 'website',
        ];
        foreach ($linkFields as $input => $field) {
            $value = trim((string) ($_POST[$input] ?? ''));
            if ($value === '') {
                continue;
            }

            if (!preg_match('~^https?://~i', $value)) {
                $value = 'https://' . ltrim($value, '/');
            }

            if (!filter_var($value, FILTER_VALIDATE_URL)) {
                $errors[$input] = 'Gecerli bir URL yaz.';
            }
        }

        try {
            $profileModel = new Profile();
            if ($nickname !== '' && !$profileModel->nicknameAvailable($nickname, $userId)) {
                $errors['nickname'] = 'Bu takma ad kullaniliyor.';
            }

            if ($errors !== []) {
                return Response::json(['ok' => false, 'message' => 'Profil alanlarini kontrol et.', 'errors' => $errors], 422);
            }

            $profile = $profileModel->updateEditable($userId, [
                'display_name' => $displayName,
                'nickname' => $nickname,
                'about' => $about,
                'instagram' => $_POST['instagram'] ?? '',
                'youtube' => $_POST['youtube'] ?? '',
                'website' => $_POST['profile_website'] ?? '',
                'privacy' => $privacy,
                'message_privacy' => $messagePrivacy,
            ]);
            Auth::refreshSessionProfile($profile);

            return Response::json([
                'ok' => true,
                'message' => 'Profil kaydedildi.',
                'profile' => $profile,
                'profile_url' => url('/kullanici/' . ($profile['nickname'] ?? $nickname)),
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.profile.update');

            return Response::json(['ok' => false, 'message' => 'Profil su anda kaydedilemedi.'], 500);
        }
    }

    public function uploadProfileAvatar(): Response
    {
        return $this->uploadProfileImage('avatar', 'PROFILE_AVATAR_UPLOAD_PATH', '/public/uploads/avatars/');
    }

    public function uploadProfileCover(): Response
    {
        return $this->uploadProfileImage('cover', 'PROFILE_COVER_UPLOAD_PATH', '/public/uploads/profile-covers/');
    }

    public function saveArtistApplication(): Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Giris yapmalisin.'], 403);
        }

        if (!Csrf::verify((string) ($_POST['_csrf'] ?? ''))) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        try {
            $artist = (new Artist())->upsertApplication((int) Auth::id(), $_POST);

            return Response::json([
                'ok' => true,
                'message' => 'Artist Connect basvurun incelemeye gonderildi.',
                'artist' => $artist,
                'redirect_url' => url('/studyo/artist'),
            ]);
        } catch (\InvalidArgumentException $exception) {
            return Response::json(['ok' => false, 'message' => $exception->getMessage()], 422);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.artist.save');

            return Response::json(['ok' => false, 'message' => 'Artist basvurusu kaydedilemedi.'], 500);
        }
    }

    public function changePassword(): Response
    {
        if ($response = $this->guardAccountAction()) {
            return $response;
        }

        $current = (string) ($_POST['current_password'] ?? '');
        $password = (string) ($_POST['password'] ?? '');
        $confirmation = (string) ($_POST['password_confirmation'] ?? '');
        $errors = [];

        if (strlen($password) < 8) {
            $errors['password'] = 'Yeni sifre en az 8 karakter olmali.';
        }
        if ($password !== $confirmation) {
            $errors['password_confirmation'] = 'Sifre tekrari eslesmiyor.';
        }

        try {
            $user = $this->currentUserRow();
            if (!$user || !password_verify($current, (string) ($user['password_hash'] ?? ''))) {
                $errors['current_password'] = 'Mevcut sifre hatali.';
            }
            if ($errors !== []) {
                return Response::json(['ok' => false, 'message' => 'Sifre alanlarini kontrol et.', 'errors' => $errors], 422);
            }

            (new User())->updatePassword((int) Auth::id(), password_hash($password, PASSWORD_DEFAULT));

            return Response::json(['ok' => true, 'message' => 'Sifre guncellendi.']);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.account.password');

            return Response::json(['ok' => false, 'message' => 'Sifre guncellenemedi.'], 500);
        }
    }

    public function changeEmail(): Response
    {
        if ($response = $this->guardAccountAction()) {
            return $response;
        }

        $email = strtolower(trim((string) ($_POST['email'] ?? '')));
        $currentPassword = (string) ($_POST['current_password'] ?? '');
        $errors = [];

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = 'Gecerli bir e-posta yaz.';
        }

        try {
            $user = $this->currentUserRow();
            if (!$user || !password_verify($currentPassword, (string) ($user['password_hash'] ?? ''))) {
                $errors['current_password'] = 'Mevcut sifre hatali.';
            }
            if ((new User())->emailExists($email) && strtolower((string) ($user['email'] ?? '')) !== $email) {
                $errors['email'] = 'Bu e-posta kullaniliyor.';
            }
            if ($errors !== []) {
                return Response::json(['ok' => false, 'message' => 'E-posta alanlarini kontrol et.', 'errors' => $errors], 422);
            }

            $pdo = Database::connection($this->config);
            $pdo->prepare('UPDATE users SET email = :email, email_verified_at = NULL, updated_at = NOW() WHERE id = :id')
                ->execute(['email' => $email, 'id' => (int) Auth::id()]);
            $updated = $this->currentUserRow();
            if ($updated) {
                Auth::login($updated);
            }

            return Response::json(['ok' => true, 'message' => 'E-posta guncellendi. Tekrar dogrulama TODO.']);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.account.email');

            return Response::json(['ok' => false, 'message' => 'E-posta guncellenemedi.'], 500);
        }
    }

    public function requestAccountDeletion(): Response
    {
        if ($response = $this->guardAccountAction()) {
            return $response;
        }

        $currentPassword = (string) ($_POST['current_password'] ?? '');

        try {
            $user = $this->currentUserRow();
            if (!$user || !password_verify($currentPassword, (string) ($user['password_hash'] ?? ''))) {
                return Response::json(['ok' => false, 'message' => 'Mevcut sifre hatali.', 'errors' => ['current_password' => 'Mevcut sifre hatali.']], 422);
            }

            $pdo = Database::connection($this->config);
            if ($this->tableHasColumn($pdo, 'users', 'deletion_requested_at')) {
                $pdo->prepare('UPDATE users SET status = "deleted", deletion_requested_at = NOW(), updated_at = NOW() WHERE id = :id')
                    ->execute(['id' => (int) Auth::id()]);
            } else {
                $pdo->prepare('UPDATE users SET status = "deleted", updated_at = NOW() WHERE id = :id')
                    ->execute(['id' => (int) Auth::id()]);
            }

            return Response::json(['ok' => true, 'message' => 'Hesap silme talebi alindi. Hesap kalici silinmedi, pasife alindi.', 'redirect_url' => url('/cikis')]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.account.delete');

            return Response::json(['ok' => false, 'message' => 'Hesap silme talebi alinamadi.'], 500);
        }
    }

    private function subscriptions(): string
    {
        $user = Auth::user() ?? [];
        $userId = (int) ($user['id'] ?? 0);
        $isCreator = in_array((string) ($user['role'] ?? 'user'), ['creator', 'admin'], true);
        $packages = [];
        $subscribers = [];
        $mySubscriptions = [];

        try {
            $subscriptionModel = new Subscription();
            $mySubscriptions = $subscriptionModel->listForSubscriber($userId);

            if ($isCreator) {
                $packages = (new Package())->listForCreator($userId, true);
                $subscribers = $subscriptionModel->listSubscribersForCreator($userId);
            }
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.subscriptions');
        }

        return $this->view('studio', 'studio/subscriptions', [
            'title' => 'Studio - Abonelik / Destek',
            'section' => 'abonelikler',
            'user' => $user,
            'isCreator' => $isCreator,
            'packages' => $packages,
            'subscribers' => $subscribers,
            'mySubscriptions' => $mySubscriptions,
        ]);
    }

    private function earnings(): string
    {
        $userId = (int) (Auth::id() ?? 0);

        try {
            $settings = (new Setting())->getFinancialSettings();

            return $this->view('studio', 'studio/earnings', [
                'title' => 'Studio - Kazanclarim',
                'section' => 'kazanclarim',
                'csrfToken' => Csrf::token(),
                'settings' => $settings,
                'balance' => (new CreatorBalance())->findOrCreate($userId, (string) $settings['currency']),
                'sales' => (new Order())->recentSalesForCreator($userId, 40),
                'ledger' => (new CreatorLedger())->recentForUser($userId, 40),
                'withdrawals' => (new WithdrawalRequest())->listForUser($userId, 20),
                'bankAccounts' => (new BankAccount())->listForUser($userId),
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.earnings');

            return $this->view('studio', 'studio/earnings', [
                'title' => 'Studio - Kazanclarim',
                'section' => 'kazanclarim',
                'csrfToken' => Csrf::token(),
                'settings' => Setting::financialDefaults(),
                'balance' => [],
                'sales' => [],
                'ledger' => [],
                'withdrawals' => [],
                'bankAccounts' => [],
                'loadError' => 'Kazanc bilgileri su anda yuklenemedi.',
            ]);
        }
    }

    private function withdrawals(): string
    {
        $userId = (int) (Auth::id() ?? 0);

        try {
            $settings = (new Setting())->getFinancialSettings();

            return $this->view('studio', 'studio/withdrawals', [
                'title' => 'Studio - Para Cek',
                'section' => 'para-cek',
                'csrfToken' => Csrf::token(),
                'settings' => $settings,
                'balance' => (new CreatorBalance())->findOrCreate($userId, (string) $settings['currency']),
                'bankAccounts' => (new BankAccount())->listForUser($userId),
                'withdrawals' => (new WithdrawalRequest())->listForUser($userId, 50),
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.withdrawals');

            return $this->view('studio', 'studio/withdrawals', [
                'title' => 'Studio - Para Cek',
                'section' => 'para-cek',
                'csrfToken' => Csrf::token(),
                'settings' => Setting::financialDefaults(),
                'balance' => [],
                'bankAccounts' => [],
                'withdrawals' => [],
                'loadError' => 'Para cekme bilgileri su anda yuklenemedi.',
            ]);
        }
    }

    private function billingInfo(): string
    {
        try {
            return $this->view('studio', 'studio/billing-info', [
                'title' => 'Studio - Banka Bilgilerim',
                'section' => 'bilgilerim',
                'csrfToken' => Csrf::token(),
                'bankAccounts' => (new BankAccount())->listForUser((int) (Auth::id() ?? 0)),
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.billingInfo');

            return $this->view('studio', 'studio/billing-info', [
                'title' => 'Studio - Banka Bilgilerim',
                'section' => 'bilgilerim',
                'csrfToken' => Csrf::token(),
                'bankAccounts' => [],
                'loadError' => 'Banka bilgileri yuklenemedi.',
            ]);
        }
    }

    private function profileSettings(): string
    {
        $userId = (int) (Auth::id() ?? 0);

        try {
            $profile = (new Profile())->editableForUser($userId);
            $communityPosts = [];
            $stories = [];
            try {
                $communityPosts = (new CommunityPost())->listForOwnerStudio($userId, 8);
                $stories = (new Story())->listActiveForUser($userId, $userId);
            } catch (Throwable $communityException) {
                $this->logException($communityException, 'studio.profile.community');
            }

            return $this->view('studio', 'studio/profile', [
                'title' => 'Studio - Profilim',
                'section' => 'profilim',
                'csrfToken' => Csrf::token(),
                'profile' => $profile,
                'communityPosts' => $communityPosts,
                'stories' => $stories,
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.profile.view');

            return $this->view('studio', 'studio/profile', [
                'title' => 'Studio - Profilim',
                'section' => 'profilim',
                'csrfToken' => Csrf::token(),
                'profile' => [
                    'display_name' => Auth::user()['display_name'] ?? Auth::user()['nickname'] ?? Auth::user()['username'] ?? '',
                    'nickname' => Auth::user()['username'] ?? '',
                    'about' => '',
                    'avatar' => '',
                    'cover_image' => '',
                    'instagram' => '',
                    'youtube' => '',
                    'website' => '',
                    'privacy' => 'public',
                    'message_privacy' => 'everyone',
                ],
                'communityPosts' => [],
                'stories' => [],
                'loadError' => 'Profil bilgileri su anda yuklenemedi.',
            ]);
        }
    }

    private function settings(): string
    {
        return $this->view('studio', 'studio/settings', [
            'title' => 'Studio - Ayarlar',
            'section' => 'ayarlar',
            'csrfToken' => Csrf::token(),
            'user' => Auth::user() ?? [],
        ]);
    }

    private function artistConnect(): string
    {
        $artist = null;
        $loadError = null;

        try {
            $artist = (new Artist())->forUser((int) (Auth::id() ?? 0));
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.artist.view');
            $loadError = 'Artist Connect bilgileri yuklenemedi. Migration calistirilmis olmali.';
        }

        return $this->view('studio', 'studio/artist', [
            'title' => 'Studio - Zalorox Artist Connect',
            'section' => 'artist',
            'csrfToken' => Csrf::token(),
            'artist' => $artist,
            'loadError' => $loadError,
        ]);
    }

    private function uploadProfileImage(string $field, string $configKey, string $fallbackPath): Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Giris yapmalisin.'], 403);
        }

        if (!Csrf::verify((string) ($_POST['_csrf'] ?? ''))) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        $userId = (int) (Auth::id() ?? 0);
        $removeKey = $field === 'avatar' ? 'remove_avatar' : 'remove_cover';
        $fileKey = $field === 'avatar' ? 'avatar' : 'cover';

        try {
            $path = '';
            if ((string) ($_POST[$removeKey] ?? '') !== '1') {
                if (empty($_FILES[$fileKey]['name'])) {
                    return Response::json(['ok' => false, 'message' => 'Dosya secmelisin.', 'errors' => [$fileKey => 'Dosya secmelisin.']], 422);
                }

                $path = Upload::storeImage(
                    $_FILES[$fileKey],
                    (string) ($this->config[$configKey] ?? $fallbackPath),
                    (int) ($this->config['PROFILE_IMAGE_MAX_MB'] ?? 12)
                );
            }

            $profileModel = new Profile();
            $profile = $field === 'avatar'
                ? $profileModel->updateAvatar($userId, $path)
                : $profileModel->updateCover($userId, $path);
            Auth::refreshSessionProfile($profile);

            return Response::json([
                'ok' => true,
                'message' => $field === 'avatar' ? 'Profil resmi guncellendi.' : 'Kapak resmi guncellendi.',
                'path' => $path,
                'profile' => $profile,
            ]);
        } catch (Throwable $exception) {
            $this->logException($exception, $field === 'avatar' ? 'studio.profile.avatar' : 'studio.profile.cover');

            return Response::json(['ok' => false, 'message' => 'Gorsel kaydedilemedi.'], 500);
        }
    }

    private function guardAccountAction(): ?Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Giris yapmalisin.'], 403);
        }

        if (!Csrf::verify((string) ($_POST['_csrf'] ?? ''))) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        return null;
    }

    private function currentUserRow(): ?array
    {
        $id = (int) (Auth::id() ?? 0);
        if ($id < 1) {
            return null;
        }

        $statement = Database::connection($this->config)->prepare('SELECT * FROM users WHERE id = :id LIMIT 1');
        $statement->execute(['id' => $id]);
        $row = $statement->fetch();

        return $row ?: null;
    }

    private function routeSection(?string $routeId): array
    {
        $path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
        $segments = array_values(array_filter(explode('/', trim($path, '/'))));

        if (($segments[0] ?? '') === 'studyo') {
            array_shift($segments);
        }

        return [$segments[0] ?? 'dashboard', $routeId ?? ($segments[1] ?? null)];
    }

    private function studioUploadData(string $type, ?array $content = null): array
    {
        $userId = (int) (Auth::id() ?? 0);
        $artist = null;
        if ($type === 'music') {
            try {
                $artist = (new Artist())->forUser($userId);
            } catch (Throwable) {
                $artist = null;
            }
        }
        $artistStatus = $artist ? (string) ($artist['status'] ?? (!empty($artist['verified']) ? 'approved' : 'pending')) : 'missing';

        return [
            'csrfToken' => Csrf::token(),
            'categories' => $this->categoryTree($type),
            'packages' => $this->creatorPackages($userId),
            'artistGate' => [
                'status' => $artistStatus,
                'artist' => $artist,
            ],
            'configForUpload' => [
                'modMaxCoverMb' => (int) $this->config['MOD_MAX_COVER_MB'],
                'modMaxGalleryItems' => (int) $this->config['MOD_MAX_GALLERY_ITEMS'],
                'musicMaxUploadMb' => (int) $this->config['MUSIC_MAX_UPLOAD_MB'],
                'musicAllowedExtensions' => implode(', ', (array) $this->config['MUSIC_ALLOWED_EXTENSIONS']),
                'modelMaxUploadMb' => (int) ($this->config['MODEL_MAX_UPLOAD_MB'] ?? 100),
                'modelAllowedExtensions' => implode(', ', (array) ($this->config['MODEL_ALLOWED_EXTENSIONS'] ?? ['glb', 'gltf', 'usdz'])),
            ],
        ];
    }

    private function categoryTree(string $type): array
    {
        $type = $type === 'music' ? 'music' : 'mod';

        try {
            $statement = Database::connection($this->config)->prepare(
                'SELECT * FROM categories WHERE type = :type AND is_active = 1 ORDER BY parent_id IS NOT NULL, sort_order ASC, name ASC'
            );
            $statement->execute(['type' => $type]);
            $rows = $statement->fetchAll();
        } catch (Throwable) {
            $rows = [];
        }

        if ($rows === []) {
            return $type === 'music'
                ? [
                    ['id' => -1, 'name' => 'Rap', 'slug' => 'rap', 'children' => []],
                    ['id' => -2, 'name' => 'Arabesk Rap', 'slug' => 'arabesk-rap', 'children' => []],
                    ['id' => -3, 'name' => 'Phonk', 'slug' => 'phonk', 'children' => []],
                    ['id' => -4, 'name' => 'Turku', 'slug' => 'turku', 'children' => []],
                    ['id' => -5, 'name' => 'Karadeniz', 'slug' => 'karadeniz', 'children' => []],
                    ['id' => -6, 'name' => 'Remix', 'slug' => 'remix', 'children' => []],
                    ['id' => -7, 'name' => 'Trap', 'slug' => 'trap', 'children' => []],
                    ['id' => -8, 'name' => 'Oyun Muzigi', 'slug' => 'oyun-muzigi', 'children' => []],
                    ['id' => -9, 'name' => 'Diger', 'slug' => 'diger', 'children' => []],
                ]
                : [['id' => -1, 'name' => 'FS25 Mods', 'slug' => 'fs25-mods', 'children' => [['id' => -2, 'name' => 'Tractors', 'slug' => 'tractors']]]];
        }

        $roots = [];
        foreach ($rows as $row) {
            $row['children'] = [];
            if ($row['parent_id'] === null) {
                $roots[(int) $row['id']] = $row;
            }
        }

        foreach ($rows as $row) {
            $parentId = $row['parent_id'] === null ? null : (int) $row['parent_id'];
            if ($parentId !== null && isset($roots[$parentId])) {
                $roots[$parentId]['children'][] = $row;
            }
        }

        return array_values($roots);
    }

    private function creatorPackages(int $userId): array
    {
        try {
            return (new Package())->listForCreator($userId, false);
        } catch (Throwable) {
            return [];
        }
    }

    private function findOwnedMod(int $id): ?array
    {
        if ($id <= 0) {
            return null;
        }

        try {
            $user = Auth::user() ?? [];
            $sql = 'SELECT * FROM mods WHERE id = :id';
            $params = ['id' => $id];
            if (($user['role'] ?? '') !== 'admin') {
                $sql .= ' AND (creator_id = :user_id OR user_id = :user_id)';
                $params['user_id'] = (int) ($user['id'] ?? 0);
            }
            $sql .= ' LIMIT 1';

            $statement = Database::connection($this->config)->prepare($sql);
            $statement->execute($params);
            $mod = $statement->fetch();

            if (!$mod) {
                return null;
            }

            $mod['images'] = $this->modImages((int) $mod['id']);
            $mod['videos'] = $this->modVideos((int) $mod['id']);
            $mod['models'] = $this->modModels((int) $mod['id']);

            return $mod;
        } catch (Throwable) {
            return null;
        }
    }

    private function findOwnedMusic(int $id): ?array
    {
        if ($id <= 0) {
            return null;
        }

        try {
            $user = Auth::user() ?? [];
            $sql = 'SELECT * FROM music_tracks WHERE id = :id';
            $params = ['id' => $id];
            if (($user['role'] ?? '') !== 'admin') {
                $sql .= ' AND user_id = :user_id';
                $params['user_id'] = (int) ($user['id'] ?? 0);
            }
            $sql .= ' LIMIT 1';

            $statement = Database::connection($this->config)->prepare($sql);
            $statement->execute($params);
            $track = $statement->fetch();
            if (!$track) {
                return null;
            }

            $genres = Database::connection($this->config)->prepare('SELECT genre_id FROM music_track_genres WHERE track_id = :track_id');
            $genres->execute(['track_id' => (int) $track['id']]);
            $track['genre_ids'] = array_map('intval', array_column($genres->fetchAll(), 'genre_id'));

            return $track;
        } catch (Throwable) {
            return null;
        }
    }

    private function ownedMods(): array
    {
        try {
            $user = Auth::user() ?? [];
            $sql = 'SELECT m.*, c.name AS category_name
                    FROM mods m
                    LEFT JOIN categories c ON c.id = m.category_id';
            $params = [];
            if (($user['role'] ?? '') !== 'admin') {
                $sql .= ' WHERE (m.creator_id = :user_id OR m.user_id = :user_id)';
                $params['user_id'] = (int) ($user['id'] ?? 0);
            }
            $sql .= ' ORDER BY m.updated_at DESC LIMIT 80';

            $statement = Database::connection($this->config)->prepare($sql);
            $statement->execute($params);

            return $statement->fetchAll();
        } catch (Throwable) {
            return [];
        }
    }

    private function ownedMusic(): array
    {
        try {
            $user = Auth::user() ?? [];
            $sql = 'SELECT mt.*, c.name AS category_name
                    FROM music_tracks mt
                    LEFT JOIN categories c ON c.id = mt.category_id';
            $params = [];
            if (($user['role'] ?? '') !== 'admin') {
                $sql .= ' WHERE mt.user_id = :user_id';
                $params['user_id'] = (int) ($user['id'] ?? 0);
            }
            $sql .= ' ORDER BY mt.updated_at DESC LIMIT 80';

            $statement = Database::connection($this->config)->prepare($sql);
            $statement->execute($params);

            return $statement->fetchAll();
        } catch (Throwable) {
            return [];
        }
    }

    private function modImages(int $modId): array
    {
        try {
            $statement = Database::connection($this->config)->prepare('SELECT * FROM mod_images WHERE mod_id = :mod_id ORDER BY sort_order ASC, id ASC');
            $statement->execute(['mod_id' => $modId]);

            return $statement->fetchAll();
        } catch (Throwable) {
            return [];
        }
    }

    private function modVideos(int $modId): array
    {
        try {
            $statement = Database::connection($this->config)->prepare('SELECT * FROM mod_videos WHERE mod_id = :mod_id ORDER BY sort_order ASC, id ASC');
            $statement->execute(['mod_id' => $modId]);

            return $statement->fetchAll();
        } catch (Throwable) {
            return [];
        }
    }

    private function modModels(int $modId): array
    {
        try {
            $statement = Database::connection($this->config)->prepare('SELECT * FROM mod_models WHERE mod_id = :mod_id ORDER BY sort_order ASC, id ASC');
            $statement->execute(['mod_id' => $modId]);

            return $statement->fetchAll();
        } catch (Throwable) {
            return [];
        }
    }

    private function shieldGate(string $url, string $title, string $description, string $type, ?int $id): array
    {
        $shield = new ZaloroxShield($this->config);
        $linkResult = $url !== '' ? $shield->scanLink($url, Auth::id(), $type . '_link', $id) : ['status' => 'safe', 'risk_score' => 0, 'reasons' => []];
        $contentResult = $type === 'mod'
            ? $shield->scanModContent($title, $description, Auth::id(), $id)
            : $shield->scanMusicContent($title, $description, Auth::id(), $id);

        $blocked = ($linkResult['status'] ?? 'safe') === 'blocked' || ($contentResult['status'] ?? 'safe') === 'blocked';
        $review = ($linkResult['status'] ?? 'safe') === 'review' || ($contentResult['status'] ?? 'safe') === 'review';

        return [
            'blocked' => $blocked,
            'review' => $review,
            'link_status' => (string) ($linkResult['status'] ?? 'safe'),
            'content_status' => (string) ($contentResult['status'] ?? 'safe'),
            'risk_score' => max((int) ($linkResult['risk_score'] ?? 0), (int) ($contentResult['risk_score'] ?? 0)),
            'reasons' => array_values(array_unique(array_merge($linkResult['reasons'] ?? [], $contentResult['reasons'] ?? []))),
        ];
    }

    private function syncModImages(PDO $pdo, int $modId): void
    {
        $max = (int) ($this->config['MOD_MAX_GALLERY_ITEMS'] ?? 10);
        $existingImages = [];

        foreach ((array) ($_POST['existing_gallery'] ?? []) as $path) {
            $path = $this->clean((string) $path, 255);
            if (str_starts_with($path, '/uploads/')) {
                $existingImages[] = $path;
            }
        }

        $uploadedImages = [];
        foreach (Upload::normalizeMultiple($_FILES['gallery'] ?? []) as $file) {
            if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
                continue;
            }

            if ((count($existingImages) + count($uploadedImages)) >= $max) {
                break;
            }

            $uploadedImages[] = Upload::storeImage($file, (string) $this->config['MOD_GALLERY_UPLOAD_PATH'], (int) $this->config['MOD_GALLERY_MAX_MB']);
        }

        $images = [];
        $existingMap = array_fill_keys($existingImages, true);
        foreach ((array) ($_POST['gallery_order'] ?? []) as $order) {
            $order = (string) $order;
            if (str_starts_with($order, 'existing:')) {
                $path = substr($order, 9);
                if (isset($existingMap[$path])) {
                    $images[] = $path;
                }
            } elseif (str_starts_with($order, 'new:')) {
                $index = (int) substr($order, 4);
                if (isset($uploadedImages[$index])) {
                    $images[] = $uploadedImages[$index];
                }
            }
        }

        foreach (array_merge($existingImages, $uploadedImages) as $path) {
            if (!in_array($path, $images, true)) {
                $images[] = $path;
            }
        }

        $images = array_values(array_unique(array_slice($images, 0, $max)));
        $pdo->prepare('DELETE FROM mod_images WHERE mod_id = :mod_id')->execute(['mod_id' => $modId]);
        $insert = $pdo->prepare('INSERT INTO mod_images (mod_id, image_path, sort_order, created_at) VALUES (:mod_id, :image_path, :sort_order, NOW())');

        foreach ($images as $index => $image) {
            $insert->execute(['mod_id' => $modId, 'image_path' => $image, 'sort_order' => $index + 1]);
        }
    }

    private function syncModVideos(PDO $pdo, int $modId): void
    {
        $videos = [];
        foreach ((array) ($_POST['videos'] ?? []) as $video) {
            $video = $this->clean((string) $video, 500);
            if ($video !== '' && filter_var($video, FILTER_VALIDATE_URL) && $this->isSupportedVideoUrl($video)) {
                $videos[] = $video;
            }
        }

        $videos = array_values(array_unique(array_slice($videos, 0, 8)));
        $pdo->prepare('DELETE FROM mod_videos WHERE mod_id = :mod_id')->execute(['mod_id' => $modId]);
        $insert = $pdo->prepare('INSERT INTO mod_videos (mod_id, video_url, provider, sort_order, created_at) VALUES (:mod_id, :video_url, :provider, :sort_order, NOW())');

        foreach ($videos as $index => $video) {
            $insert->execute([
                'mod_id' => $modId,
                'video_url' => $video,
                'provider' => $this->videoProvider($video),
                'sort_order' => $index + 1,
            ]);
        }
    }

    private function syncModModels(PDO $pdo, int $modId): void
    {
        if (!$this->tableExists($pdo, 'mod_models')) {
            return;
        }

        $allowed = (array) ($this->config['MODEL_ALLOWED_EXTENSIONS'] ?? ['glb', 'gltf', 'usdz']);
        $maxMb = (int) ($this->config['MODEL_MAX_UPLOAD_MB'] ?? 100);
        $targetPath = (string) ($this->config['MODEL_UPLOAD_PATH'] ?? '/public/uploads/models/');
        $items = [];

        $existingPaths = (array) ($_POST['existing_model_paths'] ?? []);
        $existingTitles = (array) ($_POST['existing_model_titles'] ?? []);
        $existingThumbs = (array) ($_POST['existing_model_thumbnails'] ?? []);

        foreach ($existingPaths as $index => $path) {
            $path = $this->clean((string) $path, 500);
            if (!$this->isSafeModelReference($path, $allowed)) {
                continue;
            }

            $items[] = [
                'path' => $path,
                'type' => $this->modelTypeFromPath($path),
                'title' => $this->clean((string) ($existingTitles[$index] ?? '3D Model'), 160) ?: '3D Model',
                'thumbnail' => $this->clean((string) ($existingThumbs[$index] ?? ''), 255) ?: null,
            ];
        }

        $thumbnail = '';
        if (!empty($_FILES['model_thumbnail']['name'])) {
            $thumbnail = Upload::storeImage($_FILES['model_thumbnail'], (string) ($this->config['MOD_GALLERY_UPLOAD_PATH'] ?? '/public/uploads/mod-gallery/'), (int) ($this->config['MOD_GALLERY_MAX_MB'] ?? 12));
        }

        $newTitle = $this->clean((string) ($_POST['model_title'] ?? ''), 160);
        foreach (Upload::normalizeMultiple($_FILES['models'] ?? []) as $file) {
            if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
                continue;
            }

            $path = Upload::storeModel($file, $targetPath, $allowed, $maxMb);
            $items[] = [
                'path' => $path,
                'type' => $this->modelTypeFromPath($path),
                'title' => $newTitle !== '' ? $newTitle : $this->clean(pathinfo((string) ($file['name'] ?? '3D Model'), PATHINFO_FILENAME), 160),
                'thumbnail' => $thumbnail ?: null,
            ];
        }

        foreach ((array) ($_POST['model_links'] ?? []) as $link) {
            $link = $this->clean((string) $link, 500);
            if ($link === '' || !filter_var($link, FILTER_VALIDATE_URL) || !$this->isSafeModelReference($link, $allowed)) {
                continue;
            }

            $items[] = [
                'path' => $link,
                'type' => $this->modelTypeFromPath($link),
                'title' => $newTitle !== '' ? $newTitle : 'Harici 3D Model',
                'thumbnail' => $thumbnail ?: null,
            ];
        }

        $seen = [];
        $items = array_values(array_filter($items, static function (array $item) use (&$seen): bool {
            $key = strtolower((string) $item['path']);
            if ($key === '' || isset($seen[$key])) {
                return false;
            }
            $seen[$key] = true;

            return true;
        }));
        $items = array_slice($items, 0, 12);

        $pdo->prepare('DELETE FROM mod_models WHERE mod_id = :mod_id')->execute(['mod_id' => $modId]);
        $insert = $pdo->prepare(
            'INSERT INTO mod_models (mod_id, model_path, model_type, thumbnail_path, title, sort_order, created_at)
             VALUES (:mod_id, :model_path, :model_type, :thumbnail_path, :title, :sort_order, NOW())'
        );

        foreach ($items as $index => $item) {
            $insert->execute([
                'mod_id' => $modId,
                'model_path' => $item['path'],
                'model_type' => $item['type'],
                'thumbnail_path' => $item['thumbnail'],
                'title' => $item['title'],
                'sort_order' => $index + 1,
            ]);
        }
    }

    private function syncMusicGenre(PDO $pdo, int $trackId, ?int $categoryId): void
    {
        $pdo->prepare('DELETE FROM music_track_genres WHERE track_id = :track_id')->execute(['track_id' => $trackId]);
        if ($categoryId === null) {
            return;
        }

        $genre = $pdo->prepare('SELECT id FROM music_genres WHERE slug = (SELECT slug FROM categories WHERE id = :category_id LIMIT 1) LIMIT 1');
        $genre->execute(['category_id' => $categoryId]);
        $genreId = (int) ($genre->fetchColumn() ?: 0);

        if ($genreId > 0) {
            $pdo->prepare('INSERT IGNORE INTO music_track_genres (track_id, genre_id) VALUES (:track_id, :genre_id)')
                ->execute(['track_id' => $trackId, 'genre_id' => $genreId]);
        }
    }

    private function archiveOwned(string $table, int $id, string $message, string $redirect): Response
    {
        if (!Auth::check()) {
            return Response::json(['ok' => false, 'message' => 'Giris yapmalisin.'], 403);
        }

        if (!Csrf::verify((string) ($_POST['_csrf'] ?? ''))) {
            return Response::json(['ok' => false, 'message' => 'Guvenlik oturumu yenilenmeli.'], 419);
        }

        try {
            $table = $table === 'mods' ? 'mods' : 'music_tracks';
            $user = Auth::user() ?? [];
            $ownerColumn = $table === 'mods' ? 'creator_id' : 'user_id';
            $sql = "UPDATE {$table} SET status = \"archived\", updated_at = NOW() WHERE id = :id";
            $params = ['id' => $id];
            if (($user['role'] ?? '') !== 'admin') {
                $sql .= " AND {$ownerColumn} = :user_id";
                $params['user_id'] = (int) ($user['id'] ?? 0);
            }
            $statement = Database::connection($this->config)->prepare($sql);
            $statement->execute($params);

            return Response::json(['ok' => true, 'message' => $message, 'redirect_url' => url($redirect)]);
        } catch (Throwable $exception) {
            $this->logException($exception, 'studio.archiveOwned');

            return Response::json(['ok' => false, 'message' => 'Kayit silinemedi.'], 500);
        }
    }

    private function normalizePrivacy(string $privacy): string
    {
        return in_array($privacy, ['public', 'free', 'subscriber', 'premium', 'private', 'package'], true) ? $privacy : 'public';
    }

    private function normalizeMusicPrivacy(string $privacy): string
    {
        return in_array($privacy, ['public', 'free', 'premium', 'private'], true) ? $privacy : 'public';
    }

    private function modLinkStatus(string $status): string
    {
        return match ($status) {
            'blocked' => 'blocked',
            'review' => 'suspicious',
            default => 'safe',
        };
    }

    private function postedContentId(): int
    {
        $input = $_POST;
        $raw = file_get_contents('php://input');
        if (is_string($raw) && $raw !== '') {
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                $input = array_merge($input, $decoded);
            }
        }

        return max(0, (int) ($input['id'] ?? $input['content_id'] ?? 0));
    }

    private function persistTags(PDO $pdo, string $table, int $id, string $tags): void
    {
        $table = $table === 'music_tracks' ? 'music_tracks' : 'mods';
        if (!$this->tableHasColumn($pdo, $table, 'tags')) {
            return;
        }

        $statement = $pdo->prepare("UPDATE {$table} SET tags = :tags WHERE id = :id");
        $statement->execute([
            'tags' => $tags !== '' ? $tags : null,
            'id' => $id,
        ]);
    }

    private function tableHasColumn(PDO $pdo, string $table, string $column): bool
    {
        static $cache = [];
        $key = $table . '.' . $column;
        if (array_key_exists($key, $cache)) {
            return $cache[$key];
        }

        try {
            $statement = $pdo->prepare("SHOW COLUMNS FROM {$table} LIKE :column");
            $statement->execute(['column' => $column]);

            return $cache[$key] = (bool) $statement->fetchColumn();
        } catch (Throwable) {
            return $cache[$key] = false;
        }
    }

    private function tableExists(PDO $pdo, string $table): bool
    {
        static $cache = [];
        if (array_key_exists($table, $cache)) {
            return $cache[$table];
        }

        try {
            $statement = $pdo->prepare('SHOW TABLES LIKE :table');
            $statement->execute(['table' => $table]);

            return $cache[$table] = (bool) $statement->fetchColumn();
        } catch (Throwable) {
            return $cache[$table] = false;
        }
    }

    private function isSafeModelReference(string $path, array $allowed): bool
    {
        $extensionPath = (string) (parse_url($path, PHP_URL_PATH) ?: $path);
        $extension = strtolower(pathinfo($extensionPath, PATHINFO_EXTENSION));

        return $extension !== ''
            && in_array($extension, array_map('strtolower', $allowed), true)
            && !preg_match('~\.(php|phtml|phar|js|html?|svg)(?:$|[?#])~i', $path);
    }

    private function modelTypeFromPath(string $path): string
    {
        $extensionPath = (string) (parse_url($path, PHP_URL_PATH) ?: $path);
        $extension = strtolower(pathinfo($extensionPath, PATHINFO_EXTENSION));

        return in_array($extension, ['glb', 'gltf', 'usdz'], true) ? $extension : 'model';
    }

    private function normalizeTags(string $value): string
    {
        $tags = preg_split('/[,\n]+/', $value) ?: [];
        $seen = [];
        $clean = [];

        foreach ($tags as $tag) {
            $tag = trim(preg_replace('/\s+/', ' ', (string) $tag) ?: '');
            $tag = trim($tag, " \t\n\r\0\x0B#.;:");
            if ($tag === '') {
                continue;
            }

            $tag = function_exists('mb_strtolower') ? mb_strtolower($tag, 'UTF-8') : strtolower($tag);
            $key = $this->tagKey($tag);
            if ($key === '' || isset($seen[$key])) {
                continue;
            }

            $seen[$key] = true;
            $clean[] = $this->clean($tag, 40);
            if (count($clean) >= 20) {
                break;
            }
        }

        return implode(', ', $clean);
    }

    private function tagKey(string $value): string
    {
        $value = function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
        $value = strtr($value, [
            'ç' => 'c',
            'ğ' => 'g',
            'ı' => 'i',
            'i̇' => 'i',
            'ö' => 'o',
            'ş' => 's',
            'ü' => 'u',
        ]);
        $value = preg_replace('/[^a-z0-9]+/u', ' ', $value) ?: '';

        return trim(preg_replace('/\s+/', ' ', $value) ?: '');
    }

    private function uniqueSlug(string $table, string $title): string
    {
        $base = $this->slugify($title);
        $slug = $base;
        $suffix = 2;
        $pdo = Database::connection($this->config);

        do {
            $statement = $pdo->prepare("SELECT id FROM {$table} WHERE slug = :slug LIMIT 1");
            $statement->execute(['slug' => $slug]);
            $exists = (bool) $statement->fetchColumn();
            if ($exists) {
                $slug = $base . '-' . $suffix++;
            }
        } while ($exists);

        return $slug;
    }

    private function slugify(string $value): string
    {
        $value = strtr($value, [
            'Ç' => 'C', 'Ğ' => 'G', 'İ' => 'I', 'Ö' => 'O', 'Ş' => 'S', 'Ü' => 'U',
            'ç' => 'c', 'ğ' => 'g', 'ı' => 'i', 'ö' => 'o', 'ş' => 's', 'ü' => 'u',
        ]);
        $value = strtolower($value);
        $value = preg_replace('~[^a-z0-9]+~', '-', $value) ?: '';
        $value = trim($value, '-');

        return $value !== '' ? substr($value, 0, 170) : 'icerik-' . strtolower(bin2hex(random_bytes(3)));
    }

    private function clean(string $value, int $limit): string
    {
        $value = trim(preg_replace('/\s+/', ' ', $value) ?: '');

        return function_exists('mb_substr') ? mb_substr($value, 0, $limit) : substr($value, 0, $limit);
    }

    private function videoProvider(string $url): string
    {
        $host = strtolower((string) (parse_url($url, PHP_URL_HOST) ?: ''));

        if (str_contains($host, 'youtube.com') || str_contains($host, 'youtu.be')) {
            return 'youtube';
        }

        if (str_contains($host, 'vimeo.com')) {
            return 'vimeo';
        }

        return 'video';
    }

    private function isSupportedVideoUrl(string $url): bool
    {
        $host = strtolower((string) (parse_url($url, PHP_URL_HOST) ?: ''));

        return str_contains($host, 'youtube.com')
            || str_contains($host, 'youtu.be')
            || str_contains($host, 'vimeo.com');
    }

    private function testMode(): bool
    {
        return !empty($this->config['FEATURE_TEST_MODE']) && (string) ($this->config['APP_ENV'] ?? '') !== 'production';
    }
}
