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

  var Common = root.SafeSceneCommon || require("./common.js");

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

  FilmiwayClient.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;
  };

  FilmiwayClient.prototype.fetchHtml = async function fetchHtml(relativeUrl) {
    var self = this;
    return this.getCached(relativeUrl, async function loadHtml() {
      var response = await self.fetchImpl(self.baseUrl + relativeUrl, {
        headers: {
          "accept": "text/html,application/xhtml+xml"
        }
      });

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

      return response.text();
    });
  };

  function parseNextData(html) {
    var match = String(html || "").match(/<script id="__NEXT_DATA__" type="application\/json">([\s\S]*?)<\/script>/i);

    if (!match) {
      return null;
    }

    try {
      return JSON.parse(match[1]);
    } catch (error) {
      return null;
    }
  }

  function parseMovieIndex(html) {
    var nextData = parseNextData(html);
    var allMovies = nextData &&
      nextData.props &&
      nextData.props.pageProps &&
      Array.isArray(nextData.props.pageProps.allMovies)
      ? nextData.props.pageProps.allMovies
      : [];

    return allMovies.map(function mapMovie(movie) {
      return {
        title: String(movie.title || "").trim(),
        imdbId: String(movie.imdbID || "").trim(),
        collectionSlug: String(movie.slug || "").trim(),
        movieSlug: String(movie.movieSlug || "").trim(),
        year: Number.parseInt(movie.year, 10) || null
      };
    }).filter(function filterMovie(movie) {
      return Boolean(movie.title && movie.movieSlug);
    });
  }

  function normalizeTitle(value) {
    return Common.normalizeText(String(value || "").replace(/[^a-zA-Z0-9]+/g, " "));
  }

  function buildMoviePagePath(movie) {
    return "/movie/" + movie.movieSlug;
  }

  function pickBestMovieMatch(movies, options) {
    var imdbId = String(options && options.imdbId || "").trim();
    var releaseYear = Number(options && options.releaseYear);
    var titles = [options && options.title].concat(options && options.aliases || []).filter(Boolean);
    var normalizedTitles = titles.map(normalizeTitle).filter(Boolean);
    var best = null;
    var bestScore = -Infinity;

    movies.forEach(function scoreMovie(movie) {
      var score = 0;
      var movieTitle = normalizeTitle(movie.title);

      if (imdbId) {
        if (movie.imdbId === imdbId) {
          score += 1000;
        } else {
          return;
        }
      }

      normalizedTitles.forEach(function addTitleScore(candidateTitle) {
        score = Math.max(score, Common.scoreTextSimilarity(movieTitle, candidateTitle));
        if (movieTitle === candidateTitle) {
          score += 40;
        }
      });

      if (Number.isFinite(releaseYear) && Number.isFinite(movie.year)) {
        if (movie.year === releaseYear) {
          score += 25;
        } else {
          score -= Math.min(Math.abs(movie.year - releaseYear), 5) * 5;
        }
      }

      if (score > bestScore) {
        bestScore = score;
        best = movie;
      }
    });

    if (!best) {
      return null;
    }

    if (imdbId) {
      return bestScore >= 1000 ? best : null;
    }

    return bestScore >= 120 ? best : null;
  }

  function mapSceneTypeToCategory(typeValue) {
    var normalized = Common.normalizeText(typeValue);

    if (normalized.indexOf("nudity") !== -1) {
      return "nudity";
    }

    if (normalized.indexOf("sex") !== -1 || normalized.indexOf("sexual") !== -1) {
      return "sex";
    }

    if (normalized.indexOf("violence") !== -1 || normalized.indexOf("gore") !== -1) {
      return "gore";
    }

    return null;
  }

  function shouldIncludeCategory(category, categorySelection) {
    if (category === "nudity") {
      return categorySelection.skipNudity;
    }

    if (category === "sex") {
      return categorySelection.skipSex || categorySelection.skipNudity;
    }

    if (category === "gore") {
      return categorySelection.skipGore;
    }

    return false;
  }

  function parseSensitiveScenes(html, sourceUrl, categorySelection) {
    var nextData = parseNextData(html);
    var movie = nextData &&
      nextData.props &&
      nextData.props.pageProps &&
      nextData.props.pageProps.movie
      ? nextData.props.pageProps.movie
      : null;
    var sceneRows = movie && Array.isArray(movie.resolvedSensitiveScenes)
      ? movie.resolvedSensitiveScenes
      : [];

    return sceneRows.map(function mapScene(scene, index) {
      var category = mapSceneTypeToCategory(scene.type);
      var startSeconds = Common.parseTimecodeToSeconds(scene.start);
      var endSeconds = Common.parseTimecodeToSeconds(scene.end);

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

      return {
        id: "filmiway-" + (movie && movie.imdbID ? movie.imdbID : "unknown") + "-" + (index + 1),
        category: category,
        start: startSeconds,
        end: endSeconds,
        startSeconds: startSeconds,
        endSeconds: endSeconds,
        confidence: "manually-verified",
        notes: [scene.type, scene.severity].filter(Boolean).join(" • "),
        sourceName: "Filmiway",
        sourceUrl: sourceUrl
      };
    }).filter(function filterScene(scene) {
      return Boolean(scene && shouldIncludeCategory(scene.category, categorySelection));
    }).sort(function sortScenes(left, right) {
      return left.startSeconds - right.startSeconds;
    });
  }

  FilmiwayClient.prototype.loadMovieIndex = async function loadMovieIndex() {
    var html = await this.fetchHtml("/search");
    return parseMovieIndex(html);
  };

  FilmiwayClient.prototype.lookupTitle = async function lookupTitle(options) {
    var categorySelection = Object.assign({
      skipNudity: true,
      skipSex: true,
      skipGore: false
    }, options && options.categorySelection ? options.categorySelection : {});
    var movieIndex = await this.loadMovieIndex();
    var movie = pickBestMovieMatch(movieIndex, options || {});

    if (!movie) {
      return null;
    }

    var pagePath = buildMoviePagePath(movie);
    var sourceUrl = this.baseUrl + pagePath;
    var html = await this.fetchHtml(pagePath);
    var exactRanges = parseSensitiveScenes(html, sourceUrl, categorySelection);

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

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

  var api = {
    FilmiwayClient: FilmiwayClient,
    parseNextData: parseNextData,
    parseMovieIndex: parseMovieIndex,
    parseSensitiveScenes: parseSensitiveScenes
  };

  root.SafeSceneFilmiway = api;

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