/**
 * Chart module for retrieving historical price data with extensive customization options.
 *
 * This module provides detailed historical price, volume, and event data for financial
 * instruments, with flexible date ranges, intervals, and data formats. It's the primary
 * source for building charts and conducting technical analysis.
 *
 * @example Basic Usage
 * ```typescript
 * import YahooFinance from "yahoo-finance2";
 * const yahooFinance = new YahooFinance();
 *
 * // Get last year of daily data
 * const result = await yahooFinance.chart('AAPL', {
 *   period1: '2023-01-01',
 *   period2: '2024-01-01'
 * });
 *
 * console.log(result.quotes[0]); // First day's OHLCV data
 * console.log(result.meta.currency); // "USD"
 * ```
 *
 * @example Different Time Intervals
 * ```typescript
 * // Intraday 5-minute data for last 5 days
 * const intraday = await yahooFinance.chart('TSLA', {
 *   period1: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000),
 *   interval: '5m'
 * });
 *
 * // Weekly data for 2 years
 * const weekly = await yahooFinance.chart('NVDA', {
 *   period1: '2022-01-01',
 *   period2: '2024-01-01',
 *   interval: '1wk'
 * });
 *
 * // Monthly data for maximum available period
 * const monthly = await yahooFinance.chart('MSFT', {
 *   period1: new Date('1985-01-01'),
 *   interval: '1mo'
 * });
 * ```
 *
 * @example Object Format for Charting Libraries
 * ```typescript
 * // Get data in object format (better for some charting libraries)
 * const chartData = await yahooFinance.chart('GOOGL', {
 *   period1: '2023-06-01',
 *   period2: '2023-12-01',
 *   return: 'object'
 * });
 *
 * // Access data by timestamp keys
 * const timestamps = chartData.timestamp;
 * const prices = chartData.indicators.quote[0];
 *
 * timestamps.forEach((timestamp, index) => {
 *   console.log(`${new Date(timestamp * 1000)}: $${prices.close[index]}`);
 * });
 * ```
 *
 * @example Working with Events and Dividends
 * ```typescript
 * const data = await yahooFinance.chart('AAPL', {
 *   period1: '2020-01-01',
 *   period2: '2024-01-01',
 *   events: 'div|split|earn'
 * });
 *
 * // Process dividends
 * if (data.events?.dividends) {
 *   data.events.dividends.forEach(dividend => {
 *     console.log(`Dividend: $${dividend.amount} on ${dividend.date}`);
 *   });
 * }
 *
 * // Process stock splits
 * if (data.events?.splits) {
 *   data.events.splits.forEach(split => {
 *     console.log(`Split: ${split.splitRatio} on ${split.date}`);
 *   });
 * }
 * ```
 *
 * @example Cryptocurrency and International Markets
 * ```typescript
 * // Bitcoin data
 * const btc = await yahooFinance.chart('BTC-USD', {
 *   period1: '2024-01-01',
 *   interval: '1h'
 * });
 *
 * // European stocks
 * const asml = await yahooFinance.chart('ASML.AS', {
 *   period1: '2023-01-01'
 * });
 *
 * // Japanese stocks
 * const toyota = await yahooFinance.chart('7203.T', {
 *   period1: '2023-01-01'
 * });
 * ```
 *
 * @example Error Handling and Edge Cases
 * ```typescript
 * try {
 *   const result = await yahooFinance.chart('INVALID', {
 *     period1: '2023-01-01'
 *   });
 * } catch (error) {
 *   if (error.message.includes('No data found')) {
 *     console.log('Symbol may be delisted or invalid');
 *   }
 * }
 *
 * // Handle potential empty results
 * const result = await yahooFinance.chart('AAPL', {
 *   period1: '2025-01-01' // Future date
 * });
 *
 * if (result.quotes.length === 0) {
 *   console.log('No data available for the specified period');
 * }
 * ```
 *
 * @remarks
 * **Data Formats**: The module provides two return formats:
 * - `array` (default): Easier to iterate over with Date objects
 * - `object`: Optimized for charting libraries, with timestamp keys
 *
 * **Intervals**: Intraday intervals (1m, 5m, etc.) have limited historical availability
 * (~60 days for 1-minute data). Daily and longer intervals have extensive history.
 *
 * **Market Hours**: Pre/post market data is included by default but may not be
 * available for all symbols or time periods.
 *
 * **Delisted Stocks**: When a stock is delisted, ALL historical data becomes
 * unavailable through Yahoo Finance, including periods when it was actively traded.
 *
 * @see {@link historical} for a simpler interface focused on OHLCV data
 *
 * @module chart
 */
import { getTypedDefinitions } from "../lib/validate/index.js";
// @yf-schema: see the docs on how this file is automatically updated.
import schema from "./chart.schema.js";
const definitions = getTypedDefinitions(schema);
const queryOptionsDefaults = {
    useYfid: true,
    interval: "1d",
    includePrePost: true,
    events: "div|split|earn",
    lang: "en-US",
    return: "array",
};
/* --- array input, typed output, honor "return" param --- */
// TODO: make this a deprecration passthrough
export const _chart = chart;
export default async function chart(symbol, queryOptionsOverrides, moduleOptions) {
    const returnAs = queryOptionsOverrides?.return || "array";
    const result = (await this._moduleExec({
        moduleName: "chart",
        query: {
            assertSymbol: symbol,
            url: "https://${YF_QUERY_HOST}/v8/finance/chart/" + symbol,
            definitions,
            schemaKey: "#/definitions/ChartOptions",
            defaults: queryOptionsDefaults,
            overrides: queryOptionsOverrides,
            transformWith(queryOptions) {
                if (!queryOptions.period2)
                    queryOptions.period2 = new Date();
                const dates = ["period1", "period2"];
                for (const fieldName of dates) {
                    const value = queryOptions[fieldName];
                    if (value instanceof Date) {
                        queryOptions[fieldName] = Math.floor(value.getTime() / 1000);
                    }
                    else if (typeof value === "string") {
                        const timestamp = new Date(value).getTime();
                        if (isNaN(timestamp)) {
                            throw new Error("yahooFinance.chart() option '" +
                                fieldName +
                                "' invalid date provided: '" +
                                value +
                                "'");
                        }
                        queryOptions[fieldName] = Math.floor(timestamp / 1000);
                    }
                }
                if (queryOptions.period1 === queryOptions.period2) {
                    throw new Error("yahooFinance.chart() options `period1` and `period2` " +
                        "cannot share the same value.");
                }
                // Don't pass this on to Yahoo
                delete queryOptions.return;
                return queryOptions;
            },
        },
        result: {
            definitions,
            schemaKey: "#/definitions/ChartResultObject",
            // deno-lint-ignore no-explicit-any
            transformWith(result) {
                if (!result.chart) {
                    throw new Error("Unexpected result: " + JSON.stringify(result));
                }
                const chart = result.chart.result[0];
                // If there are no quotes, chart.timestamp will be empty, but Yahoo also
                // gives us chart.indicators.quotes = [{}].  Let's clean that up and
                // deliver an empty array rather than an invalid ChartIndicatorQuote/
                if (!chart.timestamp) {
                    if (chart.indicators.quote.length !== 1) {
                        throw new Error("No timestamp with quotes.length !== 1, please report with your query");
                    }
                    if (Object.keys(chart.indicators.quote[0]).length !== 0) {
                        // i.e. {}
                        throw new Error("No timestamp with unexpected quote, please report with your query" +
                            JSON.stringify(chart.indicators.quote[0]));
                    }
                    chart.indicators.quote.pop();
                }
                return chart;
            },
        },
        moduleOptions,
    }));
    if (returnAs === "object") {
        return result;
    }
    else if (returnAs === "array") {
        const timestamp = result.timestamp;
        /*
        seems as though yahoo inserts extra quotes at the event times, so no need.
        if (result.events) {
          for (let event of ["dividends", "splits"]) {
            // @ts-ignore
            if (result.events[event])
              // @ts-ignore
              timestamp = timestamp.filter((ts) => !result.events[event][ts]);
          }
        }
        */
        // istanbul ignore next
        if (timestamp &&
            result?.indicators?.quote &&
            result.indicators.quote[0].high.length !== timestamp.length) {
            console.log({
                origTimestampSize: result.timestamp && result.timestamp.length,
                filteredSize: timestamp.length,
                quoteSize: result.indicators.quote[0].high.length,
            });
            throw new Error("Timestamp count mismatch, please report this with the query you used");
        }
        const result2 = {
            meta: result.meta,
            quotes: timestamp ? new Array(timestamp.length) : [],
        };
        const adjclose = result?.indicators?.adjclose?.[0].adjclose;
        if (timestamp) {
            for (let i = 0; i < timestamp.length; i++) {
                result2.quotes[i] = {
                    date: new Date(timestamp[i] * 1000),
                    high: result.indicators.quote[0].high[i],
                    volume: result.indicators.quote[0].volume[i],
                    open: result.indicators.quote[0].open[i],
                    low: result.indicators.quote[0].low[i],
                    close: result.indicators.quote[0].close[i],
                };
                if (adjclose)
                    result2.quotes[i].adjclose = adjclose[i];
            }
        }
        if (result.events) {
            result2.events = {};
            for (const event of ["dividends", "splits"]) {
                if (result.events[event]) {
                    result2.events[event] = Object.values(result.events[event]);
                }
            }
        }
        return result2;
    }
    // TypeScript runtime validation ensures no other values for
    // "returnAs" are possible.
}
