import { getCache } from '../cache.js';
import { buildResourcePaths, checkCachedResource } from '../hub.js';
import { get_files } from './get_files.js';
import { get_pipeline_files } from './get_pipeline_files.js';

/**
 * @typedef {Object} FileCacheStatus
 * @property {string} file - The file path
 * @property {boolean} cached - Whether the file is cached
 */

/**
 * @typedef {Object} CacheCheckResult
 * @property {boolean} allCached - Whether all files are cached
 * @property {FileCacheStatus[]} files - Array of files with their cache status
 */

/**
 * Internal helper to check cache status for a list of files
 * @private
 * @param {string} modelId - The model id
 * @param {string[]} files - List of file paths to check
 * @param {Object} options - Options including cache_dir
 * @returns {Promise<CacheCheckResult>}
 */
async function check_files_cache(modelId, files, options = {}) {
    const cache = await getCache(options?.cache_dir);

    if (!cache) {
        const fileStatuses = files.map((filename) => ({ file: filename, cached: false }));
        // No cache available, all files considered not cached
        return { allCached: false, files: fileStatuses };
    }

    const fileStatuses = await Promise.all(
        files.map(async (filename) => {
            const { localPath, proposedCacheKey } = buildResourcePaths(modelId, filename, options, cache);
            const cached = await checkCachedResource(cache, localPath, proposedCacheKey);
            return { file: filename, cached: !!cached };
        }),
    );

    return { allCached: fileStatuses.every((f) => f.cached), files: fileStatuses };
}

/**
 * Internal helper to check whether a single file is cached.
 * @private
 * @param {string} modelId - The model id
 * @param {string} filename - The file path to check
 * @param {Object} options - Options including cache_dir
 * @returns {Promise<boolean>}
 */
async function is_file_cached(modelId, filename, options = {}) {
    const cache = await getCache(options?.cache_dir);
    if (!cache) return false;
    const { localPath, proposedCacheKey } = buildResourcePaths(modelId, filename, options, cache);
    return !!(await checkCachedResource(cache, localPath, proposedCacheKey));
}

/**
 * Quickly checks if a model is cached by verifying that `config.json` is present,
 * then confirming all required files are cached.
 * Returns a plain boolean — use `is_cached_files` if you need per-file detail.
 *
 * @param {string} modelId The model id (e.g., "Xenova/gpt2")
 * @param {Object} [options] Optional parameters
 * @param {string} [options.cache_dir] Custom cache directory
 * @param {string} [options.revision] Model revision (default: 'main')
 * @param {import('../../configs.js').PretrainedConfig} [options.config] Pre-loaded config
 * @param {import('../dtypes.js').DataType|Record<string, import('../dtypes.js').DataType>} [options.dtype] Override dtype
 * @param {import('../devices.js').DeviceType|Record<string, import('../devices.js').DeviceType>} [options.device] Override device
 * @returns {Promise<boolean>} Whether all required files are cached
 */
export async function is_cached(modelId, options = {}) {
    if (!modelId) {
        throw new Error('modelId is required');
    }

    // Fast early-exit: if config.json is missing we can expect the rest not to be cached too.
    if (!(await is_file_cached(modelId, 'config.json', options))) {
        return false;
    }

    const files = await get_files(modelId, options);
    const result = await check_files_cache(modelId, files, options);
    return result.allCached;
}

/**
 * Checks if all files for a given model are already cached, with per-file detail.
 * Automatically determines which files are needed using get_files().
 *
 * @param {string} modelId The model id (e.g., "Xenova/gpt2")
 * @param {Object} [options] Optional parameters
 * @param {string} [options.cache_dir] Custom cache directory
 * @param {string} [options.revision] Model revision (default: 'main')
 * @param {import('../../configs.js').PretrainedConfig} [options.config] Pre-loaded config
 * @param {import('../dtypes.js').DataType|Record<string, import('../dtypes.js').DataType>} [options.dtype] Override dtype
 * @param {import('../devices.js').DeviceType|Record<string, import('../devices.js').DeviceType>} [options.device] Override device
 * @returns {Promise<CacheCheckResult>} Object with allCached boolean and files array with cache status
 */
export async function is_cached_files(modelId, options = {}) {
    if (!modelId) {
        throw new Error('modelId is required');
    }

    const files = await get_files(modelId, options);
    return await check_files_cache(modelId, files, options);
}

/**
 * Quickly checks if all files for a specific pipeline task are cached by verifying
 * that `config.json` is present, then confirming all required files are cached.
 * Returns a plain boolean — use `is_pipeline_cached_files` if you need per-file detail.
 *
 * @param {string} task - The pipeline task (e.g., "text-generation", "image-classification")
 * @param {string} modelId - The model id (e.g., "Xenova/gpt2")
 * @param {Object} [options] - Optional parameters
 * @param {string} [options.cache_dir] - Custom cache directory
 * @param {string} [options.revision] - Model revision (default: 'main')
 * @param {import('../../configs.js').PretrainedConfig} [options.config] - Pre-loaded config
 * @param {import('../dtypes.js').DataType|Record<string, import('../dtypes.js').DataType>} [options.dtype] - Override dtype
 * @param {import('../devices.js').DeviceType|Record<string, import('../devices.js').DeviceType>} [options.device] - Override device
 * @returns {Promise<boolean>} Whether all required files are cached
 */
export async function is_pipeline_cached(task, modelId, options = {}) {
    if (!task) {
        throw new Error('task is required');
    }
    if (!modelId) {
        throw new Error('modelId is required');
    }

    // Fast early-exit: if config.json is missing we can expect the rest not to be cached too.
    if (!(await is_file_cached(modelId, 'config.json', options))) {
        return false;
    }

    const files = await get_pipeline_files(task, modelId, options);
    const result = await check_files_cache(modelId, files, options);
    return result.allCached;
}

/**
 * Checks if all files for a specific pipeline task are already cached, with per-file detail.
 * Automatically determines which components are needed based on the task.
 *
 * @param {string} task - The pipeline task (e.g., "text-generation", "image-classification")
 * @param {string} modelId - The model id (e.g., "Xenova/gpt2")
 * @param {Object} [options] - Optional parameters
 * @param {string} [options.cache_dir] - Custom cache directory
 * @param {string} [options.revision] - Model revision (default: 'main')
 * @param {import('../../configs.js').PretrainedConfig} [options.config] - Pre-loaded config
 * @param {import('../dtypes.js').DataType|Record<string, import('../dtypes.js').DataType>} [options.dtype] - Override dtype
 * @param {import('../devices.js').DeviceType|Record<string, import('../devices.js').DeviceType>} [options.device] - Override device
 * @returns {Promise<CacheCheckResult>} Object with allCached boolean and files array with cache status
 */
export async function is_pipeline_cached_files(task, modelId, options = {}) {
    if (!task) {
        throw new Error('task is required');
    }
    if (!modelId) {
        throw new Error('modelId is required');
    }

    const files = await get_pipeline_files(task, modelId, options);
    return await check_files_cache(modelId, files, options);
}
