//
//
// Main functions
//
// 

async function searchResults(query) {
  const encodeQuery = keyword => encodeURIComponent(keyword);

  const decodeHtmlEntities = (str) => {
    if (!str) return str;
    return str.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
              .replace(/&quot;/g, '"')
              .replace(/&amp;/g, '&')
              .replace(/&lt;/g, '<')
              .replace(/&gt;/g, '>');
  };

  const fuzzyMatch = (query, title) => {
    const q = query.toLowerCase().trim();
    const t = title.toLowerCase().trim();
    
    if (t === q) return 1000;
    
    if (t.startsWith(q + ' ') || t.startsWith(q + ':') || t.startsWith(q + '-')) return 950;
    
    const wordBoundaryRegex = new RegExp(`\\b${q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`);
    if (wordBoundaryRegex.test(t)) return 900;
    
    const qTokens = q.split(/\s+/).filter(token => token.length > 0);
    const tTokens = t.split(/[\s\-:]+/).filter(token => token.length > 0);
    
    const stopwords = new Set(['the', 'a', 'an', 'and', 'or', 'of', 'in', 'on', 'at', 'to', 'for', 'with']);
    
    let score = 0;
    let exactMatches = 0;
    let partialMatches = 0;
    let significantMatches = 0;
    
    qTokens.forEach(qToken => {
      const isStopword = stopwords.has(qToken);
      let bestMatch = 0;
      let hasExactMatch = false;
      
      tTokens.forEach(tToken => {
        let matchScore = 0;
        
        if (tToken === qToken) {
          matchScore = isStopword ? 25 : 120;
          hasExactMatch = true;
          if (!isStopword) significantMatches++;
        }
        else if (qToken.includes(tToken) && tToken.length >= 3 && qToken.length <= tToken.length + 2) {
          matchScore = isStopword ? 8 : 40;
          if (!isStopword) significantMatches++;
        }
        else if (tToken.startsWith(qToken) && qToken.length >= 3) {
          matchScore = isStopword ? 12 : 70;
          if (!isStopword) significantMatches++;
        }
        else if (qToken.length >= 4 && tToken.length >= 4) {
          const dist = levenshteinDistance(qToken, tToken);
          const maxLen = Math.max(qToken.length, tToken.length);
          const similarity = 1 - (dist / maxLen);
          
          if (similarity > 0.8) {
            matchScore = Math.floor(similarity * 60);
            if (!isStopword) significantMatches++;
          }
        }
        
        bestMatch = Math.max(bestMatch, matchScore);
      });
      
      if (bestMatch > 0) {
        score += bestMatch;
        if (hasExactMatch) exactMatches++;
        else partialMatches++;
      }
    });
    
    const significantTokens = qTokens.filter(t => !stopwords.has(t)).length;
    
    const requiredMatches = Math.max(1, Math.ceil(significantTokens * 0.8));
    if (significantMatches < requiredMatches) {
      return 0;
    }
    
    if (exactMatches + partialMatches >= qTokens.length) {
      score += 80;
    }
    
    score += exactMatches * 20;
    
    const extraWords = tTokens.length - qTokens.length;
    if (extraWords > 2) {
      score -= (extraWords - 2) * 25;
    }
    
    let orderBonus = 0;
    for (let i = 0; i < qTokens.length - 1; i++) {
      const currentTokenIndex = tTokens.findIndex(t => t.includes(qTokens[i]));
      const nextTokenIndex = tTokens.findIndex(t => t.includes(qTokens[i + 1]));
      
      if (currentTokenIndex !== -1 && nextTokenIndex !== -1 && currentTokenIndex < nextTokenIndex) {
        orderBonus += 15;
      }
    }
    score += orderBonus;
    
    return Math.max(0, score);
  };

  const levenshteinDistance = (a, b) => {
    const matrix = [];
    
    for (let i = 0; i <= b.length; i++) {
      matrix[i] = [i];
    }
    
    for (let j = 0; j <= a.length; j++) {
      matrix[0][j] = j;
    }
    
    for (let i = 1; i <= b.length; i++) {
      for (let j = 1; j <= a.length; j++) {
        if (b.charAt(i - 1) === a.charAt(j - 1)) {
          matrix[i][j] = matrix[i - 1][j - 1];
        } else {
          matrix[i][j] = Math.min(
            matrix[i - 1][j - 1] + 1, 
            matrix[i][j - 1] + 1,     
            matrix[i - 1][j] + 1     
          );
        }
      }
    }
    
    return matrix[b.length][a.length];
  };

  const animekaiSearch = async () => {
    const searchBaseUrl = "https://animekai.to/browser?keyword=";
    const baseUrl = "https://animekai.to";

    const posterHrefRegex = /href="[^"]*" class="poster"/g;
    const titleRegex = /class="title"[^>]*title="[^"]*"/g;
    const imageRegex = /data-src="[^"]*"/g;
    const extractHrefRegex = /href="([^"]*)"/;
    const extractImageRegex = /data-src="([^"]*)"/;
    const extractTitleRegex = /title="([^"]*)"/;

    const extractResultsFromHTML = (htmlText) => {
      const results = [];
      const posterMatches = htmlText.match(posterHrefRegex) || [];
      const titleMatches = htmlText.match(titleRegex) || [];
      const imageMatches = htmlText.match(imageRegex) || [];
      const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);

      for (let i = 0; i < minLength; i++) {
        const hrefMatch = posterMatches[i].match(extractHrefRegex);
        const fullHref = hrefMatch ? (hrefMatch[1].startsWith("http") ? hrefMatch[1] : baseUrl + hrefMatch[1]) : null;

        const imageMatch = imageMatches[i].match(extractImageRegex);
        const imageSrc = imageMatch ? imageMatch[1] : null;

        const titleMatch = titleMatches[i].match(extractTitleRegex);
        const cleanTitle = titleMatch ? decodeHtmlEntities(titleMatch[1]) : null;

        if (fullHref && imageSrc && cleanTitle) {
          results.push({
            href: `Animekai:${fullHref}`,
            image: imageSrc,
            title: cleanTitle
          });
        }
      }

      return results;
    };

    try {
      const encodedQuery = encodeQuery(query);
      const urls = [
        `${searchBaseUrl}${encodedQuery}`,
        `${searchBaseUrl}${encodedQuery}&page=2`,
        `${searchBaseUrl}${encodedQuery}&page=3`
      ];

      const responses = await Promise.all(urls.map(url => fetchv2(url)));
      const htmlTexts = await Promise.all(responses.map(res => res.text()));

      const allResults = [];
      htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html)));
      return allResults;
    } catch (error) {
      console.error("Animekai search error:" + error);
      return [];
    }
  };

  const oneMoviesSearch = async () => {
    const searchBaseUrl = "https://1movies.bz/browser?keyword=";
    const baseUrl = "https://1movies.bz";

    const posterHrefRegex = /href="([^"]*)" class="poster"/g;
    const titleRegex = /class="title" href="[^"]*">([^<]*)</g;
    const imageRegex = /data-src="([^"]*)"/g;

    const extractResultsFromHTML = (htmlText) => {
      const results = [];
      const posterMatches = [...htmlText.matchAll(posterHrefRegex)];
      const titleMatches = [...htmlText.matchAll(titleRegex)];
      const imageMatches = [...htmlText.matchAll(imageRegex)];
      const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);

      for (let i = 0; i < minLength; i++) {
        const href = posterMatches[i][1];
        const fullHref = href.startsWith("http") ? href : baseUrl + href;

        const imageSrc = imageMatches[i][1];
        const title = decodeHtmlEntities(titleMatches[i][1]);

        results.push({ href: fullHref, image: imageSrc, title });
      }
      return results;
    };

    try {
      const encodedQuery = encodeQuery(query);
      const urls = [
        `${searchBaseUrl}${encodedQuery}`,
        `${searchBaseUrl}${encodedQuery}&page=2`,
        `${searchBaseUrl}${encodedQuery}&page=3`
      ];

      const responses = await Promise.all(urls.map(url => fetchv2(url)));
      const htmlTexts = await Promise.all(responses.map(res => res.text()));

      const allResults = [];
      htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html)));
      return allResults;
    } catch (error) {
      console.error("1Movies search error:" + error);
      return [];
    }
  };

  try {
    const [animekaiResults, oneMoviesResults] = await Promise.all([
      animekaiSearch(),
      oneMoviesSearch()
    ]);

    const mergedResults = [...animekaiResults, ...oneMoviesResults];

    const scoredResults = mergedResults.map(r => ({
      ...r,
      score: fuzzyMatch(query, r.title)
    }));

    const filteredResults = scoredResults
    .filter(r => r.score > 50) 
    .sort((a, b) => b.score - a.score) 
    .map(({ score, ...rest }) => rest);

    return JSON.stringify(filteredResults.length > 0 ? filteredResults : [{
      href: "",
      image: "",
      title: "No results found, please refine query."
    }]);
  } catch (error) {
    return JSON.stringify([{
      href: "",
      image: "",
      title: "Search failed: " + error.message
    }]);
  }
}

async function extractDetails(url) {
  
  if (url.startsWith("Animekai:")) {
    const actualUrl = url.replace("Animekai:", "").trim();
    
    try {
      const response = await fetchv2(actualUrl);
      const htmlText = await response.text();
      
      const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
      const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
      
      return JSON.stringify([{
        description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
        aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not available",
        airdate: "If stream doesn't load try later or disable VPN/DNS"
      }]);
    } catch (error) {
      console.error("Error fetching Animekai details:" + error);
      return JSON.stringify([{
        description: "Error loading description",
        aliases: "Aliases: Unknown",
        airdate: "Aired: Unknown"
      }]);
    }
  } else {    
    try {
      const response = await fetchv2(url);
      const htmlText = await response.text();

      const descriptionMatch = (/<div class="description text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
      const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
      const airdateMatch = (/<li>Released:\s*<span[^>]*>(.*?)<\/span>/.exec(htmlText) || [])[1];

      return JSON.stringify([{
        description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
        aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not aliases",
        airdate: airdateMatch ? cleanHtmlSymbols(airdateMatch) : "Not available"
      }]);
    } catch (error) {
      console.error("Error fetching 1Movies details:"+ error);
      return JSON.stringify([{
        description: "Error loading description",
        aliases: "Not available",
        airdate: "Not available"
      }]);
    }
  }
}

async function extractEpisodes(url) {  
  try {
    if (url.startsWith("Animekai:")) {
      const actualUrl = url.replace("Animekai:", "").trim();
      const htmlText = await (await fetchv2(actualUrl)).text();
      const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
      if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]);

      const tokenResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(animeIdMatch)}`);
      const tokenData = await tokenResponse.json();
      const token = tokenData.result;

      const episodeListUrl = `https://animekai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
      const episodeListData = await (await fetchv2(episodeListUrl)).json();
      const cleanedHtml = cleanJsonHtml(episodeListData.result);

      const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
      const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];

      const episodes = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
        number: parseInt(episodeNum, 10),
        href: `Animekai:https://animekai.to/ajax/links/list?token=${episodeToken}&_=ENCRYPT_ME`
      }));

      return JSON.stringify(episodes);
    } else {
      const htmlText = await (await fetchv2(url)).text();
      const movieIDMatch = (htmlText.match(/<div class="detail-lower"[^>]*id="movie-rating"[^>]*data-id="([^"]+)"/) || [])[1];
      if (!movieIDMatch) return JSON.stringify([{ error: "MovieID not found" }]);

      const tokenResponse = await fetchv2("https://enc-dec.app/api/enc-movies-flix?text=" + encodeURIComponent(movieIDMatch));
      const temp = await tokenResponse.json();
      const token = temp.result;

      const episodeListUrl = `https://1movies.bz/ajax/episodes/list?id=${movieIDMatch}&_=${token}`;
      const episodeListData = await (await fetchv2(episodeListUrl)).json();
      const cleanedHtml = cleanJsonHtml(episodeListData.result);

      const episodeRegex = /<a[^>]+eid="([^"]+)"[^>]+num="([^"]+)"[^>]*>/g;
      const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];

      const episodes = episodeMatches.map(([_, episodeToken, episodeNum]) => ({
        number: parseInt(episodeNum, 10),
        href: `https://1movies.bz/ajax/links/list?eid=${episodeToken}&_=ENCRYPT_ME`
      }));

      return JSON.stringify(episodes);
    }
  } catch (err) {
    console.error("Error fetching episodes:" + err);
    return JSON.stringify([{ number: 1, href: "Error fetching episodes" }]);
  }
}

async function extractStreamUrl(url) {
  let source, actualUrl;
  
  if (url.startsWith("Animekai:")) {
    source = "Animekai";
    actualUrl = url.replace("Animekai:", "").trim();
  } else if (url.includes("1movies.bz")) {
    source = "1Movies";
    actualUrl = url.trim();
  } else {
    console.log("Failed to match URL:", url);
    return "Invalid URL format: " + url;
  }
  
  if (source === "Animekai") {
    try {
      const tokenMatch = actualUrl.match(/token=([^&]+)/);
      if (tokenMatch && tokenMatch[1]) {
        const rawToken = tokenMatch[1];
        const encryptResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(rawToken)}`);
        const encryptData = await encryptResponse.json();
        const encryptedToken = encryptData.result;
        actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
      }
      
      const response = await fetchv2(actualUrl);
      const text = await response.text();
      const cleanedHtml = cleanJsonHtml(text);
      const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/;
      const softsubRegex = /<div class="server-items lang-group" data-id="softsub"[^>]*>([\s\S]*?)<\/div>/;
      const dubRegex = /<div class="server-items lang-group" data-id="dub"[^>]*>([\s\S]*?)<\/div>/;
      const subMatch = subRegex.exec(cleanedHtml);
      const softsubMatch = softsubRegex.exec(cleanedHtml);
      const dubMatch = dubRegex.exec(cleanedHtml);
      const subContent = subMatch ? subMatch[1].trim() : "";
      const softsubContent = softsubMatch ? softsubMatch[1].trim() : "";
      const dubContent = dubMatch ? dubMatch[1].trim() : "";
      const serverSpanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>Server 1<\/span>/;
      const serverIdDub = serverSpanRegex.exec(dubContent)?.[1];
      const serverIdSoftsub = serverSpanRegex.exec(softsubContent)?.[1];
      const serverIdSub = serverSpanRegex.exec(subContent)?.[1];

      const tokenRequestData = [
        { name: "Dub", data: serverIdDub },
        { name: "Softsub", data: serverIdSoftsub },
        { name: "Sub", data: serverIdSub }
      ].filter(item => item.data);

      const tokenPromises = tokenRequestData.map(item => 
        fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(item.data)}`)
          .then(res => res.json())
          .then(json => ({ name: item.name, data: json.result }))
          .catch(err => ({ name: item.name, error: err.toString() }))
      );
      const tokenResults = await Promise.all(tokenPromises);

      const streamUrls = tokenResults.map(result => {
        const serverIdMap = {
          "Dub": serverIdDub,
          "Softsub": serverIdSoftsub,
          "Sub": serverIdSub
        };
        return {
          type: result.name,
          url: `https://animekai.to/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data}`
        };
      });

      const processStreams = async (streamUrls) => {
        const streamResponses = await Promise.all(
          streamUrls.map(async ({ type, url }) => {
            try {
              const res = await fetchv2(url);
              const json = await res.json();
              return {
                type: type,
                result: json.result
              };
            } catch (error) {
              console.log(`Error fetching ${type} stream:` + error);
              return {
                type: type,
                result: null
              };
            }
          })
        );

        const decryptRequestData = streamResponses
          .filter(item => item.result)
          .map(item => ({
            name: item.type,
            data: item.result
          }));

        if (decryptRequestData.length === 0) {
          return {};
        }

        const decryptPromises = decryptRequestData.map(item => 
          fetchv2(`https://enc-dec.app/api/dec-kai?text=${encodeURIComponent(item.data)}`)
            .then(res => res.json())
            .then(json => ({ name: item.name, data: JSON.stringify(json.result) }))
            .catch(err => ({ name: item.name, error: err.toString() }))
        );
        const decryptResults = await Promise.all(decryptPromises);

        const finalResults = {};
        decryptResults.forEach(result => {
          try {
            const parsed = JSON.parse(result.data);
            finalResults[result.name] = parsed.url;
            console.log(`decrypted${result.name} URL:` + parsed.url);
          } catch (error) {
            console.log(`Error parsing ${result.name} result:` + error);
            finalResults[result.name] = null;
          }
        });

        return finalResults;
      };

      const decryptedUrls = await processStreams(streamUrls);
      const decryptedSub = decryptedUrls.Sub;
      const decryptedDub = decryptedUrls.Dub;
      const decryptedRaw = decryptedUrls.Softsub;

      const headers = {
        "Referer": "https://animekai.to/",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
      };

      async function getStream(url) {
        try {
          const response = await fetchv2(url.replace("/e/", "/media/"), headers);
          const responseJson = await response.json();

          const result = responseJson?.result;

          const postData = {
            "text": result,
            "Useragent": headers["User-Agent"]
          };

          const finalResponse = await fetchv2(
            "https://ilovekai.simplepostrequest.workers.dev/ilovebush",
            {},
            "POST",
            JSON.stringify(postData)
          );

          const finalJson = await finalResponse.json();
          return finalJson?.result?.sources?.[0]?.file || null;
        } catch {
          return null;
        }
      }

      const streams = [];

      const subStream = decryptedSub ? await getStream(decryptedSub.replace("megaup22", "megaup.site")) : null;
      if (subStream) streams.push("Hardsub English", subStream);

      const dubStream = decryptedDub ? await getStream(decryptedDub.replace("megaup22", "megaup.site")) : null;
      if (dubStream) streams.push("Dubbed English", dubStream);

      const rawStream = decryptedRaw ? await getStream(decryptedRaw.replace("megaup22", "megaup.site")) : null;
      if (rawStream) streams.push("Original audio", rawStream);

      const final = {
        streams,
        subtitles: ""
      };

      console.log("RETURN: " + JSON.stringify(final));
      return JSON.stringify(final);

    } catch (error) {
      console.log("Animekai fetch error:" + error);
      return "https://error.org";
    }
  } else if (source === "1Movies") {
    try {
      const eidMatch = actualUrl.match(/eid=([^&]+)/);
      if (eidMatch && eidMatch[1]) {
        const rawEpisodeToken = eidMatch[1];
        const encryptResponse = await fetchv2(`https://enc-dec.app/api/enc-movies-flix?text=${rawEpisodeToken}`);
        const encryptData = await encryptResponse.json();
        const encryptedToken = encryptData.result;
        actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
      }
      
      const response = await fetchv2(actualUrl);
      const responseData = await response.json();
      const cleanedHtml = cleanJsonHtml(responseData.result);
      
      const server1Regex = /<div class="server wnav-item"[^>]*data-lid="([^"]+)"[^>]*>\s*<span>Server 1<\/span>/;
      const server1Match = server1Regex.exec(cleanedHtml);
      
      if (!server1Match) {
        console.log("Server 1 not found");
        return "error";
      }
      
      const serverId = server1Match[1];
      
      const tokenRequestData = [{ name: "Server1", data: serverId }];
      
      const tokenBatchResponse = await fetchv2(
        "https://ilovekai.simplepostrequest.workers.dev/ilovethighs", 
        {}, 
        "POST", 
        JSON.stringify(tokenRequestData)
      );
      const tokenResults = await tokenBatchResponse.json();
      const token = tokenResults[0]?.data;
      
      if (!token) {
        console.log("Token not found");
        return "error";
      }
      
      const streamUrl = `https://1movies.bz/ajax/links/view?id=${serverId}&_=${token}`;
      const streamResponse = await fetchv2(streamUrl);
      const streamData = await streamResponse.json();
      
      if (!streamData.result) {
        console.log("Stream result not found");
        return "error";
      }
      
      const decryptRequestData = [{ name: "Server1", data: streamData.result }];
      
      const decryptBatchResponse = await fetchv2(
        "https://ilovekai.simplepostrequest.workers.dev/iloveboobs", 
        {}, 
        "POST", 
        JSON.stringify(decryptRequestData)
      );
      const decryptedResponse = await decryptBatchResponse.json();
      console.log("Decrypted response:" + JSON.stringify(decryptedResponse));
      const decryptedUrl = decryptedResponse[0]?.data.url;

        const subListEncoded = decryptedUrl.split("sub.list=")[1]?.split("&")[0];
        let subtitles = "N/A";

        if (subListEncoded) {
        try {
            const subListUrl = decodeURIComponent(subListEncoded);
            const subResponse = await fetchv2(subListUrl);
            subtitles = await subResponse.json();
        } catch {
            subtitles = "N/A";
        }
        }

      const englishSubUrl = Array.isArray(subtitles)
        ? subtitles.find(sub => sub.label === "English")?.file.replace(/\\\//g, "/")
        : "N/A";

      if (!decryptedUrl) {
        console.log("Decryption failed");
        return "error";
      }
      
      const headers = {
        "Referer": "https://1movies.bz/",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
      };
      
      const mediaResponse = await fetchv2(decryptedUrl.replace("/e/", "/media/"), headers);
      const mediaJson = await mediaResponse.json();
      
      const result = mediaJson?.result;
      if (!result) {
        console.log("Media result not found");
        return "error";
      }
      
      const postData = {
        "text": result,
        "Useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
      };
      
      const finalResponse = await fetchv2("https://ilovekai.simplepostrequest.workers.dev/iloveass", {}, "POST", JSON.stringify(postData));
      const finalJson = await finalResponse.json();

      const m3u8Link = finalJson?.result?.sources?.[0]?.file;

      const m3u8Response = await fetchv2(m3u8Link);
      const m3u8Text = await m3u8Response.text();

      const baseUrl = m3u8Link.substring(0, m3u8Link.lastIndexOf('/') + 1);
      
      const streams = [];
      const lines = m3u8Text.split('\n');
      
      for (let i = 0; i < lines.length; i++) {
        const line = lines[i].trim();
        if (line.startsWith('#EXT-X-STREAM-INF:')) {
          const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
          let quality = 'Unknown';
          
          if (resolutionMatch) {
            const [width, height] = resolutionMatch[1].split('x');
            quality = `${height}p`;
          }
          
          if (i + 1 < lines.length) {
            const streamPath = lines[i + 1].trim();
            const streamUrl = baseUrl + streamPath;
            
            streams.push({
              title: quality,
              streamUrl: streamUrl
            });
          }
        }
      }

      const returnValue = {
        streams: streams,
        subtitle: englishSubUrl !== "N/A" ? englishSubUrl : ""
      };
      console.log("RETURN: " + JSON.stringify(returnValue));
      return JSON.stringify(returnValue);
    } catch (error) {
      console.log("1Movies fetch error:" + error);
      return "https://error.org";
    }
  }
}

///
///
/// Helper functions
///
///

function cleanHtmlSymbols(string) {
  if (!string) {
    return "";
  }
  return string
    .replace(/&#8217;/g, "'")
    .replace(/&#8211;/g, "-")
    .replace(/&#[0-9]+;/g, "")
    .replace(/\r?\n|\r/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function cleanJsonHtml(jsonHtml) {
  if (!jsonHtml) {
    return "";
  }
  return jsonHtml
    .replace(/\\"/g, "\"")
    .replace(/\\'/g, "'")
    .replace(/\\\\/g, "\\")
    .replace(/\\n/g, "\n")
    .replace(/\\t/g, "\t")
    .replace(/\\r/g, "\r");
}

function decodeHtmlEntities(text) {
  if (!text) {
    return "";
  }
  return text
    .replace(/&#039;/g, "'")
    .replace(/&quot;/g, "\"")
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&nbsp;/g, " ");
}