(function attachCleanStreamClient(root) {
  "use strict";

  function CleanStreamClient(fetchImpl, options) {
    this.fetchImpl = fetchImpl;
    this.baseUrl = options && options.baseUrl ? options.baseUrl : "https://cleanstream.elfhosted.com";
    this.cacheTtlMs = options && options.cacheTtlMs ? options.cacheTtlMs : 6 * 60 * 60 * 1000;
    this.cache = new Map();
  }

  CleanStreamClient.prototype.getCached = async function getCached(key, loader) {
    var existing = this.cache.get(key);
    var now = Date.now();

    if (existing && existing.expiresAt > now) {
      return existing.value;
    }

    var value = await loader();
    this.cache.set(key, {
      value: value,
      expiresAt: now + this.cacheTtlMs
    });
    return value;
  };

  CleanStreamClient.prototype.requestJson = async function requestJson(pathname, searchParams) {
    var self = this;
    var query = searchParams ? "?" + new URLSearchParams(searchParams).toString() : "";
    var cacheKey = pathname + query;

    return this.getCached(cacheKey, async function loadJson() {
      var response = await self.fetchImpl(self.baseUrl + pathname + query, {
        headers: {
          "accept": "application/json"
        }
      });

      if (!response.ok) {
        throw new Error("CleanStream request failed: " + response.status + " " + pathname);
      }

      return response.json();
    });
  };

  function buildQuery(categorySelection) {
    return {
      nudity: categorySelection.skipNudity ? "low" : "off",
      sex: (categorySelection.skipSex || categorySelection.skipNudity) ? "low" : "off",
      violence: categorySelection.skipGore ? "low" : "off"
    };
  }

  function mapCategory(value) {
    if (value === "violence") {
      return "gore";
    }

    return value === "nudity" || value === "sex" ? value : null;
  }

  function normalizeRanges(skips, imdbId, sourceUrl) {
    return (Array.isArray(skips) ? skips : []).map(function mapSkip(skip) {
      var category = mapCategory(String(skip.category || "").trim());
      var startSeconds = Number(skip.startMs || 0) / 1000;
      var endSeconds = Number(skip.endMs || 0) / 1000;

      if (!category || !Number.isFinite(startSeconds) || !Number.isFinite(endSeconds) || endSeconds <= startSeconds) {
        return null;
      }

      return {
        id: "cleanstream-" + imdbId + "-" + String(skip.id || (skip.startMs + "-" + skip.endMs)),
        category: category,
        start: startSeconds,
        end: endSeconds,
        startSeconds: startSeconds,
        endSeconds: endSeconds,
        confidence: String(skip.contributor || "").indexOf("videoskip") !== -1 ? "mirrored-videoskip" : "community-api",
        notes: [skip.description, skip.severity].filter(Boolean).join(" • "),
        sourceName: "CleanStream",
        sourceUrl: sourceUrl
      };
    }).filter(Boolean).sort(function sortByStart(left, right) {
      return left.startSeconds - right.startSeconds;
    });
  }

  CleanStreamClient.prototype.lookupTitle = async function lookupTitle(options) {
    var imdbId = String(options && options.imdbId || "").trim();
    var categorySelection = Object.assign({
      skipNudity: true,
      skipSex: true,
      skipGore: false
    }, options && options.categorySelection ? options.categorySelection : {});

    if (!imdbId) {
      return null;
    }

    var sourceUrl = this.baseUrl + "/api/filters/" + imdbId;
    var payload = await this.requestJson("/api/skips/" + imdbId, buildQuery(categorySelection));
    var exactRanges = normalizeRanges(payload && payload.skips, imdbId, sourceUrl);

    if (!exactRanges.length) {
      return null;
    }

    return {
      sourceName: "CleanStream",
      sourceUrl: sourceUrl,
      exactRanges: exactRanges,
      warningOnly: [],
      matchingSceneCount: exactRanges.length,
      imdbId: imdbId
    };
  };

  var api = {
    CleanStreamClient: CleanStreamClient,
    normalizeRanges: normalizeRanges
  };

  root.SafeSceneCleanStream = api;

  if (typeof module !== "undefined" && module.exports) {
    module.exports = api;
  }
}(typeof globalThis !== "undefined" ? globalThis : this));
