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

  var DEFAULT_SETTINGS = {
    autoSkipEnabled: true,
    notifyBeforeSkipSeconds: 10,
    leadInSeconds: 0,
    resumePaddingSeconds: 1,
    skipCategories: ["nudity"],
    debug: false
  };

  var PROVIDER_HOSTS = {
    "netflix.com": "netflix",
    "max.com": "max",
    "hbomax.com": "max",
    "hulu.com": "hulu",
    "primevideo.com": "prime-video",
    "amazon.com": "prime-video",
    "disneyplus.com": "disney-plus",
    "tv.apple.com": "apple-tv-plus",
    "paramountplus.com": "paramount-plus",
    "peacocktv.com": "peacock",
    "crunchyroll.com": "crunchyroll"
  };

  function clone(value) {
    return JSON.parse(JSON.stringify(value));
  }

  function normalizeText(value) {
    return String(value || "")
      .toLowerCase()
      .replace(/['’]/g, "")
      .replace(/[^a-z0-9]+/g, " ")
      .trim();
  }

  function titleCaseFromSlug(value) {
    return String(value || "")
      .split(/[-_]/g)
      .filter(Boolean)
      .map(function mapPart(part) {
        return part.charAt(0).toUpperCase() + part.slice(1);
      })
      .join(" ");
  }

  function coercePositiveInteger(value) {
    var parsed = Number.parseInt(value, 10);
    return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
  }

  function parseTimecodeToSeconds(value) {
    if (typeof value === "number" && Number.isFinite(value)) {
      return value;
    }

    var raw = String(value || "").trim();
    if (!raw) {
      return null;
    }

    var segments = raw.split(":").map(function toNumber(segment) {
      return Number.parseInt(segment, 10);
    });

    if (segments.some(function hasNaN(part) { return Number.isNaN(part); })) {
      return null;
    }

    while (segments.length < 3) {
      segments.unshift(0);
    }

    return (segments[0] * 3600) + (segments[1] * 60) + segments[2];
  }

  function formatSeconds(value) {
    var seconds = Math.max(0, Math.floor(Number(value) || 0));
    var hours = Math.floor(seconds / 3600);
    var minutes = Math.floor((seconds % 3600) / 60);
    var remainder = seconds % 60;
    var pieces = [
      String(minutes).padStart(hours > 0 ? 2 : 1, "0"),
      String(remainder).padStart(2, "0")
    ];

    if (hours > 0) {
      pieces.unshift(String(hours));
    }

    return pieces.join(":");
  }

  function getProviderHint(hostname) {
    var normalizedHost = String(hostname || "").toLowerCase();
    var entries = Object.keys(PROVIDER_HOSTS);
    for (var index = 0; index < entries.length; index += 1) {
      if (normalizedHost === entries[index] || normalizedHost.endsWith("." + entries[index])) {
        return PROVIDER_HOSTS[entries[index]];
      }
    }
    return null;
  }

  function stripStreamingSuffixes(title) {
    var cleaned = String(title || "").trim();
    var suffixPatterns = [
      /\s+\|\s+Netflix$/i,
      /\s+\|\s+Max$/i,
      /\s+-\s+Max$/i,
      /\s+\|\s+Hulu$/i,
      /\s+\|\s+Prime Video$/i,
      /\s+-\s+Prime Video$/i,
      /\s+\|\s+Disney\+$/i,
      /\s+\|\s+Apple TV\+$/i,
      /\s+-\s+Apple TV\+$/i,
      /\s+\|\s+Paramount\+$/i,
      /\s+\|\s+Peacock$/i
    ];

    suffixPatterns.forEach(function strip(pattern) {
      cleaned = cleaned.replace(pattern, "");
    });

    return cleaned.trim();
  }

  function normalizeEpisodeCandidateString(value) {
    return String(value || "")
      .replace(/[–—]/g, "-")
      .replace(/[•·]/g, " - ")
      .replace(/\bEp\.?\s*(\d+)\b/gi, "Episode $1")
      .replace(/\bEps\.?\s*(\d+)\b/gi, "Episode $1")
      .replace(/\bSsn\.?\s*(\d+)\b/gi, "Season $1")
      .replace(/\(([sS]\d+\s*[eE]\d+)\)/g, " $1 ")
      .replace(/\s+/g, " ")
      .trim();
  }

  function cleanupEpisodeTitle(value) {
    return String(value || "")
      .replace(/^\s*["'“”]/, "")
      .replace(/["'“”]\s*$/, "")
      .replace(/\s*[-|:]+\s*$/, "")
      .replace(/\s+/g, " ")
      .trim();
  }

  function buildCandidate(match, source) {
    var showTitle = cleanupEpisodeTitle(match.groups.showTitle);
    var episodeTitle = cleanupEpisodeTitle(match.groups.episodeTitle);
    var seasonNumber = coercePositiveInteger(match.groups.seasonNumber);
    var episodeNumber = coercePositiveInteger(match.groups.episodeNumber);

    if (!showTitle || !seasonNumber || !episodeNumber) {
      return null;
    }

    return {
      showTitle: showTitle,
      seasonNumber: seasonNumber,
      episodeNumber: episodeNumber,
      episodeTitle: episodeTitle || null,
      parseSource: source
    };
  }

  function parseEpisodeCandidatesFromString(value, source) {
    var cleaned = normalizeEpisodeCandidateString(stripStreamingSuffixes(value));
    var patterns = [
      /^(?<showTitle>.+?)\s+Season\s*(?<seasonNumber>\d+)\s+Episode\s*(?<episodeNumber>\d+)(?:\s*[-|:]\s*(?<episodeTitle>.+))?$/i,
      /^(?<showTitle>.+?)\s*[-|:]\s*Season\s*(?<seasonNumber>\d+)\s*,?\s*Episode\s*(?<episodeNumber>\d+)(?:\s*[-|:]\s*(?<episodeTitle>.+))?$/i,
      /^(?<showTitle>.+?)\s*[-|:]\s*Season\s*(?<seasonNumber>\d+)\s*[-|:]\s*Episode\s*(?<episodeNumber>\d+)(?:\s*[-|:]\s*(?<episodeTitle>.+))?$/i,
      /^(?<showTitle>.+?)\s*[-|:]\s*Episode\s*(?<episodeNumber>\d+)\s*[-|:]\s*Season\s*(?<seasonNumber>\d+)(?:\s*[-|:]\s*(?<episodeTitle>.+))?$/i,
      /^(?<showTitle>.+?)\s*[-|:]\s*S(?<seasonNumber>\d+)\s*(?::?\s*E)(?<episodeNumber>\d+)(?:\s*[-|:]\s*(?<episodeTitle>.+))?$/i,
      /^(?<showTitle>.+?)\s*[-|:]\s*S(?<seasonNumber>\d+)\s*[-|:]\s*E(?<episodeNumber>\d+)(?:\s*[-|:]\s*(?<episodeTitle>.+))?$/i,
      /^(?<showTitle>.+?)\s*[-|:]\s*S(?<seasonNumber>\d+)\s+E(?<episodeNumber>\d+)(?:\s*[-|:]\s*(?<episodeTitle>.+))?$/i,
      /^(?<showTitle>.+?)\s*\(?S(?<seasonNumber>\d+)\s*E(?<episodeNumber>\d+)\)?(?:\s*[-|:]\s*(?<episodeTitle>.+))?$/i,
      /^(?<showTitle>.+?)\s*[-|:]\s*(?<seasonNumber>\d+)x(?<episodeNumber>\d+)(?:\s*[-|:]\s*(?<episodeTitle>.+))?$/i,
      /^(?<showTitle>.+?)\s+S(?<seasonNumber>\d+)\s*E(?<episodeNumber>\d+)(?:\s+(?<episodeTitle>.+))?$/i
    ];

    return patterns
      .map(function matchPattern(pattern) {
        return cleaned.match(pattern);
      })
      .filter(Boolean)
      .map(function mapMatch(match) {
        return buildCandidate(match, source);
      })
      .filter(Boolean);
  }

  function parsePathCandidate(pathname, hostname) {
    var provider = getProviderHint(hostname);
    var normalizedPath = String(pathname || "");
    var match = normalizedPath.match(/\/watch\/(?<showSlug>[^/?#]+)\/season-(?<seasonNumber>\d+)\/episode-(?<episodeNumber>\d+)/i);

    if (!match) {
      return null;
    }

    return {
      showTitle: titleCaseFromSlug(match.groups.showSlug),
      seasonNumber: coercePositiveInteger(match.groups.seasonNumber),
      episodeNumber: coercePositiveInteger(match.groups.episodeNumber),
      episodeTitle: null,
      parseSource: provider ? provider + "-pathname" : "pathname"
    };
  }

  function dedupeCandidates(candidates) {
    var seen = new Set();
    return candidates.filter(function filterCandidate(candidate) {
      var key = [
        normalizeText(candidate.showTitle),
        candidate.seasonNumber,
        candidate.episodeNumber,
        normalizeText(candidate.episodeTitle)
      ].join("|");

      if (seen.has(key)) {
        return false;
      }

      seen.add(key);
      return true;
    });
  }

  function parseCandidatesFromContext(pageContext) {
    var candidates = [];
    var stringsToParse = [
      { value: pageContext.title, source: "document.title" },
      { value: pageContext.ogTitle, source: "meta:og:title" },
      { value: pageContext.twitterTitle, source: "meta:twitter:title" },
      { value: pageContext.heading, source: "heading" }
    ];

    stringsToParse.forEach(function parseEntry(entry) {
      parseEpisodeCandidatesFromString(entry.value, entry.source).forEach(function pushCandidate(candidate) {
        candidates.push(candidate);
      });
    });

    var pathCandidate = parsePathCandidate(pageContext.pathname, pageContext.hostname);
    if (pathCandidate) {
      candidates.push(pathCandidate);
    }

    return dedupeCandidates(candidates);
  }

  function scoreTextSimilarity(left, right) {
    var normalizedLeft = normalizeText(left);
    var normalizedRight = normalizeText(right);

    if (!normalizedLeft || !normalizedRight) {
      return 0;
    }

    if (normalizedLeft === normalizedRight) {
      return 100;
    }

    if (normalizedLeft.includes(normalizedRight) || normalizedRight.includes(normalizedLeft)) {
      return 80;
    }

    var leftTokens = new Set(normalizedLeft.split(" "));
    var rightTokens = new Set(normalizedRight.split(" "));
    var overlap = 0;

    leftTokens.forEach(function countOverlap(token) {
      if (rightTokens.has(token)) {
        overlap += 1;
      }
    });

    return Math.round((overlap / Math.max(leftTokens.size, rightTokens.size)) * 60);
  }

  var api = {
    DEFAULT_SETTINGS: DEFAULT_SETTINGS,
    clone: clone,
    normalizeText: normalizeText,
    parseTimecodeToSeconds: parseTimecodeToSeconds,
    formatSeconds: formatSeconds,
    getProviderHint: getProviderHint,
    parseCandidatesFromContext: parseCandidatesFromContext,
    parseEpisodeCandidatesFromString: parseEpisodeCandidatesFromString,
    scoreTextSimilarity: scoreTextSimilarity,
    stripStreamingSuffixes: stripStreamingSuffixes,
    normalizeEpisodeCandidateString: normalizeEpisodeCandidateString
  };

  root.SafeSceneCommon = api;

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