import { db } from "./db.js";
import { Label, logger } from "./logger.js";
import { humanReadableDate } from "./utils.js";
export var IndexerStatus;
(function (IndexerStatus) {
    /**
     * equivalent to null
     */
    IndexerStatus["OK"] = "OK";
    IndexerStatus["RATE_LIMITED"] = "RATE_LIMITED";
    IndexerStatus["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
})(IndexerStatus || (IndexerStatus = {}));
const allFields = {
    id: "id",
    url: "url",
    name: "name",
    apikey: "apikey",
    trackers: "trackers",
    enabled: "enabled",
    status: "status",
    retryAfter: "retry_after",
    searchCap: "search_cap",
    tvSearchCap: "tv_search_cap",
    movieSearchCap: "movie_search_cap",
    musicSearchCap: "music_search_cap",
    audioSearchCap: "audio_search_cap",
    bookSearchCap: "book_search_cap",
    tvIdCaps: "tv_id_caps",
    movieIdCaps: "movie_id_caps",
    catCaps: "cat_caps",
    limitsCaps: "limits_caps",
};
export const ALL_CAPS = {
    limits: {
        default: 100,
        max: 100,
    },
    search: true,
    categories: {
        tv: true,
        movie: true,
        anime: true,
        xxx: true,
        audio: true,
        book: true,
        additional: true,
    },
    tvSearch: true,
    movieSearch: true,
    musicSearch: true,
    audioSearch: true,
    bookSearch: true,
    movieIdSearch: {
        tvdbId: true,
        tmdbId: true,
        imdbId: true,
        tvMazeId: true,
    },
    tvIdSearch: {
        tvdbId: true,
        tmdbId: true,
        imdbId: true,
        tvMazeId: true,
    },
};
export function deserialize(dbIndexer) {
    const { trackers, tvIdCaps, movieIdCaps, catCaps, limitsCaps, ...rest } = dbIndexer;
    return {
        ...rest,
        enabled: Boolean(rest.enabled),
        searchCap: Boolean(rest.searchCap),
        tvSearchCap: Boolean(rest.tvSearchCap),
        movieSearchCap: Boolean(rest.movieSearchCap),
        musicSearchCap: Boolean(rest.musicSearchCap),
        audioSearchCap: Boolean(rest.audioSearchCap),
        bookSearchCap: Boolean(rest.bookSearchCap),
        trackers: JSON.parse(trackers ?? "null"),
        tvIdCaps: JSON.parse(tvIdCaps ?? "null"),
        movieIdCaps: JSON.parse(movieIdCaps ?? "null"),
        categories: JSON.parse(catCaps ?? "null"),
        limits: JSON.parse(limitsCaps ?? "null"),
    };
}
/**
 * All indexers that users currently have configured regardless of whether they are working.
 */
export async function getAllIndexers() {
    return (await db("indexer").select(allFields)).map(deserialize);
}
/**
 * Indexers that are currently working and enabled.
 */
export async function getEnabledIndexers() {
    return (await db("indexer")
        .whereNot({
        search_cap: null,
        tv_search_cap: null,
        movie_search_cap: null,
        music_search_cap: null,
        audio_search_cap: null,
        book_search_cap: null,
        tv_id_caps: null,
        movie_id_caps: null,
        cat_caps: null,
        limits_caps: null,
    })
        .where({ enabled: true, search_cap: true })
        .where((i) => i
        .where({ status: null })
        .orWhere({ status: IndexerStatus.OK })
        .orWhere("retry_after", "<", Date.now()))
        .select(allFields)).map(deserialize);
}
export async function updateIndexerStatus(status, retryAfter, indexerIds, indexerNames) {
    if (indexerIds.length > 0) {
        logger.verbose({
            label: Label.TORZNAB,
            message: `Snoozing indexers [${Array.from(indexerNames).join(", ")}] with ${status} until ${humanReadableDate(retryAfter)}`,
        });
        await db("indexer").whereIn("id", indexerIds).update({
            retry_after: retryAfter,
            status,
        });
    }
}
export async function updateSearchTimestamps(name, indexerIds) {
    for (const indexerId of indexerIds) {
        await db.transaction(async (trx) => {
            const now = Date.now();
            const { id: searchee_id } = (await trx("searchee")
                .where({ name })
                .select("id")
                .first());
            await trx("timestamp")
                .insert({
                searchee_id,
                indexer_id: indexerId,
                last_searched: now,
                first_searched: now,
            })
                .onConflict(["searchee_id", "indexer_id"])
                .merge(["last_searched"]);
        });
    }
}
export async function updateIndexerCapsById(indexerId, caps) {
    await db("indexer")
        .where({ id: indexerId })
        .update({
        search_cap: caps.search,
        tv_search_cap: caps.tvSearch,
        movie_search_cap: caps.movieSearch,
        music_search_cap: caps.musicSearch,
        audio_search_cap: caps.audioSearch,
        book_search_cap: caps.bookSearch,
        movie_id_caps: JSON.stringify(caps.movieIdSearch),
        tv_id_caps: JSON.stringify(caps.tvIdSearch),
        cat_caps: JSON.stringify(caps.categories),
        limits_caps: JSON.stringify(caps.limits),
    });
}
export async function clearIndexerFailures() {
    await db("indexer").update({
        status: null,
        retry_after: null,
    });
}
export async function getHostToNameMap() {
    const hostToName = new Map();
    for (const indexer of await getAllIndexers()) {
        if (!indexer.name || !indexer.trackers)
            continue;
        for (const trackerHost of indexer.trackers) {
            if (hostToName.has(trackerHost))
                continue;
            hostToName.set(trackerHost, indexer.name);
        }
    }
    return hostToName;
}
function indexerProblemId(suffix, indexerId) {
    return `indexer:${suffix}:${indexerId}`;
}
function indexerDisplayName(url, name) {
    return name?.trim() || url;
}
export async function collectIndexerProblems() {
    const problems = [];
    const indexers = await getAllIndexers();
    if (!indexers.length) {
        problems.push({
            id: "indexer:none-configured",
            severity: "error",
            summary: "No indexers configured.",
            details: "Add at least one indexer so cross-seed can search for releases.",
        });
        return problems;
    }
    const enabledIndexers = await getEnabledIndexers();
    if (!enabledIndexers.length) {
        problems.push({
            id: "indexer:none-enabled",
            severity: "error",
            summary: "All configured indexers are disabled.",
            details: "Enable at least one indexer so cross-seed can run searches.",
        });
    }
    const now = Date.now();
    for (const indexer of indexers) {
        const name = indexerDisplayName(indexer.url, indexer.name);
        if (!indexer.searchCap) {
            problems.push({
                id: indexerProblemId("no-search-cap", indexer.id),
                severity: "warning",
                summary: `Indexer "${name}" does not support searching.`,
                details: "Update the indexer's capabilities (caps) in Prowlarr/Jackett or disable it for searching.",
            });
        }
        if (indexer.status === IndexerStatus.RATE_LIMITED &&
            typeof indexer.retryAfter === "number" &&
            indexer.retryAfter > now) {
            problems.push({
                id: indexerProblemId("rate-limited", indexer.id),
                severity: "warning",
                summary: `Indexer "${name}" is rate limited.`,
                details: `Cross-seed will retry after ${humanReadableDate(indexer.retryAfter)}.`,
            });
        }
        if (indexer.status === IndexerStatus.UNKNOWN_ERROR) {
            problems.push({
                id: indexerProblemId("unknown-error", indexer.id),
                severity: "warning",
                summary: `Indexer "${name}" recently failed.`,
                details: "Check logs for the underlying error and verify the indexer configuration.",
            });
        }
        if (!indexer.enabled) {
            problems.push({
                id: indexerProblemId("disabled", indexer.id),
                severity: "info",
                summary: `Indexer "${name}" is disabled.`,
                details: "Re-enable the indexer if you want cross-seed to include it in searches.",
            });
        }
    }
    return problems;
}
//# sourceMappingURL=indexers.js.map