import Queue from "./queue.js";
import errors from "./errors.js";
import getCrumb from "./getCrumb.js";
const _queue = new Queue();
// deno-lint-ignore no-explicit-any
function assertQueueOptions(queue, opts) {
    opts; //?
    if (typeof opts.concurrency === "number" &&
        queue.concurrency !== opts.concurrency) {
        queue.concurrency = opts.concurrency;
    }
    if (typeof opts.timeout === "number" && queue.timeout !== opts.timeout) {
        queue.timeout = opts.timeout;
    }
}
function substituteVariables(urlBase) {
    return urlBase.replace(/\$\{([^}]+)\}/g, (match, varName) => {
        if (varName === "YF_QUERY_HOST") {
            // const hosts = ["query1.finance.yahoo.com", "query2.finance.yahoo.com"];
            // return hosts[Math.floor(Math.random() * hosts.length)];
            return this._opts.YF_QUERY_HOST || "query2.finance.yahoo.com";
        }
        else {
            // i.e. return unsubstituted original variable expression ${VAR}
            return match;
        }
    });
}
async function yahooFinanceFetch(urlBase, params = {}, moduleOpts = {}, func = "json", needsCrumb = false) {
    if (!(this && this._env)) {
        throw new errors.NoEnvironmentError("yahooFinanceFetch called without this._env set");
    }
    // TODO: adds func type to json schema which is not supported
    const queue = moduleOpts.queue?._queue || _queue;
    // const queue = _queue;
    assertQueueOptions(queue, { ...this._opts.queue, ...moduleOpts.queue });
    const { fetch: envFetch, fetchDevel } = this._env;
    /* istanbul ignore next */
    // no need to force coverage on real network request.
    const fetchFunc = moduleOpts.devel
        ? await fetchDevel()
        : moduleOpts.fetch || envFetch || this._opts.fetch || globalThis.fetch;
    const fetchOptionsBase = {
        ...this._opts.fetchOptions,
        ...moduleOpts.fetchOptions,
        devel: moduleOpts.devel,
        headers: {
            ...this._opts.fetchOptions?.headers,
            ...moduleOpts.fetchOptions?.headers,
        },
    };
    if (needsCrumb) {
        if (!this._opts.cookieJar)
            throw new Error("No cookieJar set");
        if (!this._opts.logger)
            throw new Error("Logger was unset.");
        const crumb = await getCrumb(this._opts.cookieJar, fetchFunc, fetchOptionsBase, this._opts.logger, this._notices);
        if (crumb)
            params.crumb = crumb;
    }
    const urlSearchParams = new URLSearchParams(params);
    const url = substituteVariables.call(this, urlBase) + "?" +
        urlSearchParams.toString();
    // console.log(url);
    // console.log(cookieJar.serializeSync());
    if (!this._opts.cookieJar)
        throw new Error("No cookieJar set");
    const fetchOptions = {
        ...fetchOptionsBase,
        headers: {
            ...fetchOptionsBase.headers,
            cookie: await this._opts.cookieJar.getCookieString(url, {
                allPaths: true,
            }),
        },
    };
    // console.log("fetch", url, fetchOptions);
    // used in moduleExec.ts
    if (func === "csv")
        func = "text";
    const response = (await queue.add(() => fetchFunc(url, fetchOptions)));
    const setCookieHeaders = response.headers.getSetCookie();
    if (setCookieHeaders) {
        if (!this._opts.cookieJar)
            throw new Error("No cookieJar set");
        this._opts.cookieJar.setFromSetCookieHeaders(setCookieHeaders, url);
    }
    const responseText = await response.text();
    let result = null;
    try {
        result = JSON.parse(responseText);
    }
    catch (error) {
        if (response.ok) {
            if (error instanceof Error) {
                throw new Error(`Response.ok where we expect JSON, but the response was not parsable.  ` +
                    `Response: "${responseText}".  Error: "${error.message}"`);
            }
        }
    }
    /*
      {
        finance: {  // or quoteSummary, or any other single key
          result: null,
          error: {
            code: 'Bad Request',
            description: 'Missing required query parameter=q'
          }
        }
      }
     */
    if (result && func === "json") {
        const keys = Object.keys(result);
        if (keys.length === 1) {
            const errorObj = result[keys[0]].error;
            if (errorObj) {
                const errorName = errorObj.code.replace(/ /g, "") + "Error";
                const ErrorClass = errors[errorName] || Error;
                throw new ErrorClass(errorObj.description);
            }
        }
    }
    // We do this last as it generally contains less information (e.g. no desc).
    if (!response.ok) {
        console.error(url);
        const error = new errors.HTTPError(responseText || response.statusText);
        error.code = response.status;
        throw error;
    }
    return result;
}
export { substituteVariables };
export default yahooFinanceFetch;
