/* global WebTorrent, angular, moment */

const VERSION = "1.1";

// A smaller set of WebSocket trackers for reliability.
const trackers = [
  "wss://tracker.btorrent.xyz",
  "wss://tracker.openwebtorrent.com",
];

// Basic torrent options.
const torrentOpts = { announce: trackers };
const trackerOpts = { announce: trackers };

// Simple debug logger.
function dbg(msg) {
  console.log("[DEBUG]", msg);
}

// Create a WebTorrent client.
const client = new WebTorrent({ tracker: trackerOpts });

// Angular app definition.
const app = angular.module("BTorrent", [
  "ngRoute",
  "ui.grid",
  "ui.grid.resizeColumns",
  "ui.grid.selection",
  "ngFileUpload",
  "ngNotify",
]);

/**
 * Optional: inline CSS for row lines in the grid.
 */
const styleEl = document.createElement("style");
styleEl.textContent = `
  .ui-grid-row {
    border-bottom: 1px solid #ccc;
  }
`;
document.head.appendChild(styleEl);

// Configure Angular routes.
app.config([
  "$compileProvider",
  "$locationProvider",
  "$routeProvider",
  function ($compileProvider, $locationProvider, $routeProvider) {
    // Allow magnet, blob, etc. in Angular URLs.
    $compileProvider.aHrefSanitizationWhitelist(
      /^\s*(https?|magnet|blob|javascript):/
    );
    $locationProvider.html5Mode(false).hashPrefix("");

    // Define basic routes.
    $routeProvider
      .when("/view", {
        templateUrl: "views/view.html",
        controller: "ViewCtrl",
      })
      .when("/download", {
        templateUrl: "views/download.html",
        controller: "DownloadCtrl",
      })
      .otherwise({
        templateUrl: "views/full.html",
        controller: "FullCtrl",
      });
  },
]);

// Warn user before they unload if torrents are still active.
app.run([
  "$rootScope",
  function ($rootScope) {
    window.addEventListener("beforeunload", (e) => {
      if ($rootScope.client && $rootScope.client.torrents.length > 0) {
        e.preventDefault();
        e.returnValue =
          "Transfers are in progress. Are you sure you want to leave or refresh?";
        return e.returnValue;
      }
    });
  },
]);

// Main BTorrent controller.
app.controller("BTorrentCtrl", [
  "$scope",
  "$rootScope",
  "$http",
  "$log",
  "ngNotify",
  function ($scope, $rootScope, $http, $log, ngNotify) {
    dbg("Starting app.js version " + VERSION);

    if (!WebTorrent.WEBRTC_SUPPORT) {
      ngNotify.set("Please use a browser with WebRTC support.", "error");
    }

    $rootScope.client = client;
    $rootScope.selectedTorrent = null;
    $rootScope.processing = false;

    // Global error handler.
    client.on("error", (err) => {
      dbg("Torrent client error: " + err);
      ngNotify.set(err.message || err, "error");
      $rootScope.processing = false;
    });

    /**
     * Called whenever a new torrent is added or we seed files.
     */
    $rootScope.onTorrent = function (torrent, isSeed) {
      dbg("Torrent added: " + torrent.magnetURI);
      $rootScope.processing = false;

      if (!isSeed) {
        ngNotify.set(`Received ${torrent.name} metadata`);
      }
      if (!$rootScope.selectedTorrent) {
        $rootScope.selectedTorrent = torrent;
      }

      // Generate file.blobURL for direct downloading in the file table.
      torrent.files.forEach((file) => {
        file.getBlobURL((err, url) => {
          if (!err) {
            file.url = url;
          }
        });
      });
    };

    /**
     * Add a magnet link or .torrent URL.
     */
    $rootScope.addMagnet = function (magnet) {
      if (!magnet) return;
      $rootScope.processing = true;
      dbg("Adding magnet: " + magnet);
      client.add(magnet, torrentOpts, (torrent) => {
        $rootScope.onTorrent(torrent, false);
        $scope.$applyAsync();
      });
    };

    /**
     * Seed local files.
     */
    $rootScope.seedFiles = function (files) {
      if (!files || !files.length) return;
      $rootScope.processing = true;
      dbg(`Seeding ${files.length} file(s)`);
      client.seed(files, torrentOpts, (torrent) => {
        $rootScope.onTorrent(torrent, true);
        $scope.$applyAsync();
      });
    };

    /**
     * Remove/destroy a selected torrent.
     */
    $rootScope.destroyedTorrent = function (err) {
      if (err) {
        console.error("Failed to destroy torrent:", err);
      }
      dbg("Destroyed torrent", $rootScope.selectedTorrent);
      $rootScope.selectedTorrent = null;
      $rootScope.processing = false;
    };

    /**
     * Change the priority of an individual file (high, low, or don't download).
     */
    $rootScope.changePriority = function (file) {
      if (file.priority === "-1") {
        file.deselect();
        dbg("Deselected file", file);
      } else {
        file.select(file.priority);
        dbg(`Selected with priority ${file.priority}`, file);
      }
    };

    /**
     * Download all files in the selected torrent if it's 100% done.
     * Creates a blob for each file, triggers a native download with <a>.
     */
    $rootScope.downloadAll = function (torrent) {
      if (!torrent) return;
      if (torrent.progress < 1) {
        alert("Torrent is not finished downloading yet.");
        return;
      }
      torrent.files.forEach((file) => {
        file.getBlob((err, blob) => {
          if (err) {
            console.error("Failed to get blob for file:", file.name, err);
            return;
          }
          // Create an anchor to trigger the download.
          const blobUrl = URL.createObjectURL(blob);
          const a = document.createElement("a");
          a.style.display = "none";
          a.href = blobUrl;
          a.download = file.name;
          // Append, click, remove, revoke.
          document.body.appendChild(a);
          a.click();
          document.body.removeChild(a);
          URL.revokeObjectURL(blobUrl);
        });
      });
    };

    /**
     * Copy the magnet URI of a torrent to the clipboard.
     */
    $rootScope.copyMagnetURI = function (torrent) {
      if (!torrent || !torrent.magnetURI) return;
      const magnetURI = torrent.magnetURI;
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard
          .writeText(magnetURI)
          .then(() =>
            ngNotify.set("Magnet URI copied to clipboard!", "success")
          )
          .catch((err) => {
            console.error("Clipboard error:", err);
            ngNotify.set("Failed to copy magnet URI", "error");
          });
      } else {
        try {
          const textarea = document.createElement("textarea");
          textarea.value = magnetURI;
          textarea.style.position = "fixed";
          textarea.style.left = "-9999px";
          document.body.appendChild(textarea);
          textarea.select();
          document.execCommand("copy");
          document.body.removeChild(textarea);
          ngNotify.set("Magnet URI copied (fallback)!", "success");
        } catch (err) {
          console.error("Clipboard fallback error:", err);
          ngNotify.set("Failed to copy magnet URI", "error");
        }
      }
    };

    /**
     * Save the .torrent file itself (via torrent.torrentFileBlobURL).
     */
    $rootScope.saveTorrentFile = function (torrent) {
      if (!torrent || !torrent.torrentFileBlobURL) return;
      const fileName = torrent.fileName || `${torrent.name}.torrent`;
      // Create a hidden <a> to force download of the .torrent file.
      const a = document.createElement("a");
      a.style.display = "none";
      a.href = torrent.torrentFileBlobURL;
      a.download = fileName;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    };
  },
]);

// FullCtrl: sets up the ui-grid and magnet input for /full route.
app.controller("FullCtrl", [
  "$scope",
  "$rootScope",
  "$location",
  "ngNotify",
  function ($scope, $rootScope, $location, ngNotify) {
    // Handle magnet input
    $scope.addMagnet = function () {
      $rootScope.addMagnet($scope.torrentInput);
      $scope.torrentInput = "";
    };

    // If we have a #magnet in the URL, add it automatically
    if ($location.hash()) {
      $rootScope.addMagnet($location.hash());
    }

    // Define columns for ui-grid.
    $scope.columns = [
      { field: "name", displayName: "Name", minWidth: 200 },
      {
        field: "progress",
        displayName: "Progress",
        cellFilter: "progress",
        width: 100,
      },
      {
        field: "downloadSpeed",
        displayName: "↓ Speed",
        cellFilter: "pbytes:1",
        width: 100,
      },
      {
        field: "uploadSpeed",
        displayName: "↑ Speed",
        cellFilter: "pbytes:1",
        width: 100,
      },
      { field: "numPeers", displayName: "Peers", width: 80 },
      {
        field: "ratio",
        displayName: "Ratio",
        cellFilter: "number:2",
        width: 80,
      },
    ];

    // Create gridOptions and update each second.
    $scope.gridOptions = {
      columnDefs: $scope.columns,
      enableColumnResizing: true,
      enableColumnMenus: false,
      enableRowSelection: true,
      enableRowHeaderSelection: false,
      multiSelect: false,
      data: [],
    };

    setInterval(() => {
      $scope.gridOptions.data =
        ($rootScope.client && $rootScope.client.torrents) || [];
      $scope.$applyAsync();
    }, 1000);

    // On row selection, set the selectedTorrent
    $scope.gridOptions.onRegisterApi = function (gridApi) {
      $scope.gridApi = gridApi;
      gridApi.selection.on.rowSelectionChanged($scope, function (row) {
        if (row.isSelected) {
          $rootScope.selectedTorrent = row.entity;
        } else {
          $rootScope.selectedTorrent = null;
        }
      });
    };
  },
]);

// DownloadCtrl / ViewCtrl are minimal in this example.
app.controller("DownloadCtrl", [
  "$scope",
  "$rootScope",
  "$location",
  function ($scope, $rootScope, $location) {
    $scope.addMagnet = function () {
      $rootScope.addMagnet($scope.torrentInput);
      $scope.torrentInput = "";
    };
    if ($location.hash()) {
      $rootScope.addMagnet($location.hash());
    }
  },
]);

app.controller("ViewCtrl", [
  "$scope",
  "$rootScope",
  "$location",
  function ($scope, $rootScope, $location) {
    $scope.addMagnet = function () {
      $rootScope.addMagnet($scope.torrentInput);
      $scope.torrentInput = "";
    };
    if ($location.hash()) {
      $rootScope.addMagnet($location.hash());
    }
  },
]);

// Angular filters for size, progress, etc.
app.filter("pbytes", function () {
  return function (num, speed) {
    if (isNaN(num)) return "";
    if (num < 1) return speed ? "" : "0 B";
    const units = ["B", "kB", "MB", "GB", "TB"];
    const exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), 4);
    const val = (num / Math.pow(1000, exponent)).toFixed(1) * 1;
    return `${val} ${units[exponent]}${speed ? "/s" : ""}`;
  };
});

app.filter("progress", function () {
  return function (val) {
    if (typeof val !== "number") return "";
    return (val * 100).toFixed(1) + "%";
  };
});

app.filter("html", [
  "$sce",
  function ($sce) {
    return function (input) {
      return $sce.trustAsHtml(input);
    };
  },
]);

app.filter("humanTime", function () {
  return function (millis) {
    if (millis < 1000) return "";
    const duration = moment.duration(millis).humanize();
    return duration.charAt(0).toUpperCase() + duration.slice(1);
  };
});
