<?php
declare(strict_types=1);

function storage_user_root(string $storagePath, int $userId): string {
  $p = $storagePath . '/u' . $userId;
  if (!is_dir($p)) @mkdir($p, 0775, true);
  return $p;
}

function ensure_root_folder(PDO $db, int $userId): int {
  $stmt = $db->prepare("SELECT id FROM folders WHERE owner_user_id = ? AND parent_id IS NULL AND name = '/' LIMIT 1");
  $stmt->execute([$userId]);
  $row = $stmt->fetch();
  if ($row) return (int)$row['id'];
  $stmt = $db->prepare("INSERT INTO folders(owner_user_id,parent_id,name,created_at) VALUES(?,?,?,?)");
  $stmt->execute([$userId, null, '/', now_iso()]);
  return (int)$db->lastInsertId();
}

function get_folder(PDO $db, int $userId, ?int $folderId): array {
  if ($folderId === null) $folderId = ensure_root_folder($db, $userId);
  $stmt = $db->prepare("SELECT * FROM folders WHERE id = ? AND owner_user_id = ?");
  $stmt->execute([$folderId, $userId]);
  $f = $stmt->fetch();
  if (!$f) throw new Exception("Carpeta no encontrada.");
  return $f;
}

function folder_breadcrumb(PDO $db, int $userId, int $folderId): array {
  $crumbs = [];
  $current = $folderId;
  while (true) {
    $stmt = $db->prepare("SELECT id, parent_id, name FROM folders WHERE id = ? AND owner_user_id = ?");
    $stmt->execute([$current, $userId]);
    $row = $stmt->fetch();
    if (!$row) break;
    $crumbs[] = ['id'=>(int)$row['id'], 'name'=>$row['name']];
    if ($row['parent_id'] === null) break;
    $current = (int)$row['parent_id'];
  }
  return array_reverse($crumbs);
}

function list_folder(PDO $db, int $userId, int $folderId): array {
  $folders = $db->prepare("SELECT id, name, created_at FROM folders WHERE owner_user_id = ? AND parent_id = ? ORDER BY name COLLATE NOCASE");
  $folders->execute([$userId, $folderId]);
  $files = $db->prepare("SELECT id, orig_name, size_bytes, mime, created_at FROM files WHERE owner_user_id = ? AND folder_id = ? ORDER BY orig_name COLLATE NOCASE");
  $files->execute([$userId, $folderId]);
  return ['folders' => $folders->fetchAll(), 'files' => $files->fetchAll()];
}

function create_folder(PDO $db, int $userId, int $parentId, string $name): void {
  $name = trim($name);
  if ($name === '' || $name === '/' || str_contains($name, '..') || str_contains($name, '/')) {
    throw new Exception("Nombre de carpeta inválido.");
  }
  $stmt = $db->prepare("INSERT INTO folders(owner_user_id,parent_id,name,created_at) VALUES(?,?,?,?)");
  $stmt->execute([$userId, $parentId, $name, now_iso()]);
}

function move_folder(PDO $db, int $userId, int $folderId, int $newParentId): void {
  if ($folderId === $newParentId) throw new Exception("Destino inválido.");
  // Prevent moving root
  $stmt = $db->prepare("SELECT parent_id,name FROM folders WHERE id=? AND owner_user_id=?");
  $stmt->execute([$folderId,$userId]);
  $f = $stmt->fetch();
  if (!$f) throw new Exception("Carpeta no encontrada.");
  if ($f['parent_id'] === null) throw new Exception("No se puede mover la carpeta raíz.");
  // Prevent cycles: ensure newParent not descendant of folderId
  $cur = $newParentId;
  while (true) {
    if ($cur === $folderId) throw new Exception("Destino inválido (ciclo).");
    $st = $db->prepare("SELECT parent_id FROM folders WHERE id=? AND owner_user_id=?");
    $st->execute([$cur,$userId]);
    $r = $st->fetch();
    if (!$r || $r['parent_id'] === null) break;
    $cur = (int)$r['parent_id'];
  }
  $stmt = $db->prepare("UPDATE folders SET parent_id=? WHERE id=? AND owner_user_id=?");
  $stmt->execute([$newParentId,$folderId,$userId]);
}

function rename_folder(PDO $db, int $userId, int $folderId, string $newName): void {
  $newName = trim($newName);
  if ($newName === '' || $newName === '/' || str_contains($newName,'..') || str_contains($newName,'/')) throw new Exception("Nombre inválido.");
  $stmt = $db->prepare("UPDATE folders SET name=? WHERE id=? AND owner_user_id=? AND parent_id IS NOT NULL");
  $stmt->execute([$newName,$folderId,$userId]);
  if ($stmt->rowCount() === 0) throw new Exception("No se puede renombrar la carpeta raíz o no existe.");
}

function delete_folder(PDO $db, int $userId, int $folderId): void {
  $stmt = $db->prepare("SELECT parent_id FROM folders WHERE id=? AND owner_user_id=?");
  $stmt->execute([$folderId,$userId]);
  $f = $stmt->fetch();
  if (!$f) throw new Exception("Carpeta no encontrada.");
  if ($f['parent_id'] === null) throw new Exception("No se puede borrar la carpeta raíz.");
  // Cascade deletes subfolders; files will set folder_id null (FK) so handle files in subtree manually:
  // We'll first collect file ids in subtree and delete them (and their storage files), then delete folder.
  $ids = folder_collect_descendants($db, $userId, $folderId);
  $ids[] = $folderId;
  foreach ($ids as $fid) {
    $fs = $db->prepare("SELECT id FROM files WHERE owner_user_id=? AND folder_id=?");
    $fs->execute([$userId,$fid]);
    foreach ($fs->fetchAll() as $row) {
      delete_file($db, $userId, (int)$row['id']);
    }
  }
  $stmt = $db->prepare("DELETE FROM folders WHERE id=? AND owner_user_id=?");
  $stmt->execute([$folderId,$userId]);
}

function folder_collect_descendants(PDO $db, int $userId, int $folderId): array {
  $out = [];
  $st = $db->prepare("SELECT id FROM folders WHERE owner_user_id=? AND parent_id=?");
  $st->execute([$userId,$folderId]);
  $rows = $st->fetchAll();
  foreach ($rows as $r) {
    $cid = (int)$r['id'];
    $out[] = $cid;
    $out = array_merge($out, folder_collect_descendants($db,$userId,$cid));
  }
  return $out;
}

function upload_file(PDO $db, array $config, int $userId, int $folderId, array $file): void {
  if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
    throw new Exception("Error de subida (código ".($file['error'] ?? -1).").");
  }
  $orig = $file['name'] ?? 'archivo';
  $tmp = $file['tmp_name'] ?? '';
  if (!is_uploaded_file($tmp)) throw new Exception("Subida inválida.");

  $finfo = new finfo(FILEINFO_MIME_TYPE);
  $mime = $finfo->file($tmp) ?: 'application/octet-stream';
  $size = (int)($file['size'] ?? 0);

  $sha = hash_file('sha256', $tmp);
  $stored = $sha . '_' . bin2hex(random_bytes(8));

  $userRoot = storage_user_root($config['storage_path'], $userId);
  $dest = $userRoot . '/' . $stored;
  if (!move_uploaded_file($tmp, $dest)) throw new Exception("No se pudo guardar el archivo.");

  $stmt = $db->prepare("INSERT INTO files(owner_user_id,folder_id,orig_name,stored_name,size_bytes,mime,sha256,created_at)
                        VALUES(?,?,?,?,?,?,?,?)");
  $stmt->execute([$userId, $folderId, $orig, $stored, $size, $mime, $sha, now_iso()]);
}

function get_file(PDO $db, int $fileId): ?array {
  $st = $db->prepare("SELECT f.*, u.username AS owner_username FROM files f JOIN users u ON u.id=f.owner_user_id WHERE f.id=?");
  $st->execute([$fileId]);
  $row = $st->fetch();
  return $row ?: null;
}

function user_can_access_file(PDO $db, int $userId, int $fileId, string $needPerm='read'): bool {
  $f = get_file($db, $fileId);
  if (!$f) return false;
  if ((int)$f['owner_user_id'] === $userId) return true;

  $st = $db->prepare("SELECT perm FROM shares WHERE file_id=? AND to_user_id=? LIMIT 1");
  $st->execute([$fileId,$userId]);
  $s = $st->fetch();
  if (!$s) return false;
  if ($needPerm === 'read') return in_array($s['perm'], ['read','write'], true);
  if ($needPerm === 'write') return $s['perm'] === 'write';
  return false;
}

function delete_file(PDO $db, int $userId, int $fileId): void {
  $f = get_file($db, $fileId);
  if (!$f) throw new Exception("Archivo no encontrado.");
  if ((int)$f['owner_user_id'] !== $userId) throw new Exception("No puedes borrar un archivo que no es tuyo.");
  // Delete file in storage
  $config = $GLOBALS['IMASTORAGE']['config'];
  $path = storage_user_root($config['storage_path'], $userId) . '/' . $f['stored_name'];
  if (is_file($path)) @unlink($path);

  $st = $db->prepare("DELETE FROM files WHERE id=? AND owner_user_id=?");
  $st->execute([$fileId,$userId]);
}

function rename_file(PDO $db, int $userId, int $fileId, string $newName): void {
  $newName = trim($newName);
  if ($newName === '' || str_contains($newName,'..') || str_contains($newName,'/')) throw new Exception("Nombre inválido.");
  $f = get_file($db, $fileId);
  if (!$f) throw new Exception("Archivo no encontrado.");
  if ((int)$f['owner_user_id'] !== $userId) {
    // If shared with write permission allow rename (logical rename)
    if (!user_can_access_file($db, $userId, $fileId, 'write')) throw new Exception("No tienes permiso.");
  }
  $st = $db->prepare("UPDATE files SET orig_name=? WHERE id=?");
  $st->execute([$newName,$fileId]);
}

function move_file(PDO $db, int $userId, int $fileId, int $newFolderId): void {
  $f = get_file($db,$fileId);
  if (!$f) throw new Exception("Archivo no encontrado.");
  $owner = (int)$f['owner_user_id'];
  if ($owner !== $userId) {
    if (!user_can_access_file($db,$userId,$fileId,'write')) throw new Exception("No tienes permiso.");
    // Move is only within your own folders (for shared files we create a link? Simpler: forbid moving shared)
    throw new Exception("No se puede mover un archivo compartido que no es tuyo. Puedes descargarlo y subirlo.");
  }
  // Validate folder belongs to owner
  $folder = get_folder($db, $owner, $newFolderId);
  $st = $db->prepare("UPDATE files SET folder_id=? WHERE id=? AND owner_user_id=?");
  $st->execute([$newFolderId,$fileId,$owner]);
}

function share_file(PDO $db, int $fromUserId, int $fileId, int $toUserId, string $perm): void {
  if ($fromUserId === $toUserId) throw new Exception("No tiene sentido compartir contigo mismo.");
  $perm = ($perm === 'write') ? 'write' : 'read';
  $f = get_file($db,$fileId);
  if (!$f) throw new Exception("Archivo no encontrado.");
  if ((int)$f['owner_user_id'] !== $fromUserId) throw new Exception("Solo el dueño puede compartir.");

  // SQLite compatibility: avoid UPSERT syntax. First try update, then insert if not exists.
  $upd = $db->prepare("UPDATE shares SET perm = ?, created_at = ? WHERE file_id = ? AND to_user_id = ?");
  $upd->execute([$perm, now_iso(), $fileId, $toUserId]);

  if ($upd->rowCount() === 0) {
    $ins = $db->prepare("INSERT INTO shares(file_id,from_user_id,to_user_id,perm,created_at) VALUES(?,?,?,?,?)");
    $ins->execute([$fileId, $fromUserId, $toUserId, $perm, now_iso()]);
  }
}

function revoke_share(PDO $db, int $fromUserId, int $fileId, int $toUserId): void {
  $f = get_file($db,$fileId);
  if (!$f) throw new Exception("Archivo no encontrado.");
  if ((int)$f['owner_user_id'] !== $fromUserId) throw new Exception("Solo el dueño puede revocar.");
  $st = $db->prepare("DELETE FROM shares WHERE file_id=? AND to_user_id=?");
  $st->execute([$fileId,$toUserId]);
}

function list_shared_with_me(PDO $db, int $userId): array {
  $st = $db->prepare("
    SELECT s.perm, s.created_at AS shared_at,
           f.id, f.orig_name, f.size_bytes, f.mime, f.created_at,
           u.username AS owner_username
    FROM shares s
    JOIN files f ON f.id = s.file_id
    JOIN users u ON u.id = f.owner_user_id
    WHERE s.to_user_id = ?
    ORDER BY s.created_at DESC
  ");
  $st->execute([$userId]);
  return $st->fetchAll();
}

function list_shares_of_my_file(PDO $db, int $userId, int $fileId): array {
  $f = get_file($db,$fileId);
  if (!$f) return [];
  if ((int)$f['owner_user_id'] !== $userId) return [];
  $st = $db->prepare("
    SELECT s.to_user_id, s.perm, s.created_at, u.username, u.email
    FROM shares s JOIN users u ON u.id=s.to_user_id
    WHERE s.file_id=?
    ORDER BY u.username COLLATE NOCASE
  ");
  $st->execute([$fileId]);
  return $st->fetchAll();
}

function stream_file(PDO $db, array $config, int $viewerId, int $fileId, bool $download=false): void {
  if (!user_can_access_file($db,$viewerId,$fileId,'read')) {
    http_response_code(403); echo "No autorizado"; exit;
  }
  $f = get_file($db,$fileId);
  if (!$f) { http_response_code(404); echo "No encontrado"; exit; }

  $owner = (int)$f['owner_user_id'];
  $path = storage_user_root($config['storage_path'], $owner) . '/' . $f['stored_name'];
  if (!is_file($path)) { http_response_code(404); echo "No encontrado"; exit; }

  $mime = $f['mime'] ?: 'application/octet-stream';
  header('Content-Type: ' . $mime);
  header('Accept-Ranges: bytes');

  $filename = $f['orig_name'];
  $disp = $download ? 'attachment' : 'inline';
  header('Content-Disposition: ' . $disp . '; filename="' . addslashes($filename) . '"');

  $size = filesize($path);
  $start = 0; $end = $size - 1;

  // Support basic Range requests for media streaming
  if (isset($_SERVER['HTTP_RANGE']) && preg_match('/bytes=(\d+)-(\d*)/', $_SERVER['HTTP_RANGE'], $m)) {
    $start = (int)$m[1];
    if ($m[2] !== '') $end = (int)$m[2];
    if ($end > $size - 1) $end = $size - 1;
    if ($start > $end) { http_response_code(416); exit; }
    http_response_code(206);
    header("Content-Range: bytes $start-$end/$size");
    header("Content-Length: " . ($end - $start + 1));
  } else {
    header("Content-Length: $size");
  }

  $fp = fopen($path, 'rb');
  fseek($fp, $start);
  $chunk = 8192;
  while (!feof($fp) && ftell($fp) <= $end) {
    $pos = ftell($fp);
    $read = min($chunk, $end - $pos + 1);
    echo fread($fp, $read);
    flush();
  }
  fclose($fp);
  exit;
}
