/**
 * Historical data module for retrieving price history, dividends, and stock splits.
 *
 * This module provides historical price data, dividend payments, and stock split
 * information for financial instruments. While functional, many users prefer
 * the {@link chart} module which offers more flexibility and features.
 *
 * @example Basic Price History
 * ```typescript
 * import YahooFinance from "yahoo-finance2";
 * const yahooFinance = new YahooFinance();
 *
 * // Get 1 year of daily data
 * const history = await yahooFinance.historical('AAPL', {
 *   period1: '2023-01-01',
 *   period2: '2024-01-01'
 * });
 *
 * console.log(history[0]); // Most recent day
 * // { date: Date, open: 150.5, high: 155.2, low: 149.8, close: 154.1, ... }
 * ```
 *
 * @example Different Intervals
 * ```typescript
 * // Weekly data
 * const weekly = await yahooFinance.historical('AAPL', {
 *   period1: '2023-01-01',
 *   interval: '1wk'
 * });
 *
 * // Monthly data
 * const monthly = await yahooFinance.historical('AAPL', {
 *   period1: '2022-01-01',
 *   interval: '1mo'
 * });
 * ```
 *
 * @example Dividends and Splits
 * ```typescript
 * // Get dividend history
 * const dividends = await yahooFinance.historical('AAPL', {
 *   period1: '2023-01-01',
 *   events: 'dividends'
 * });
 * // Returns: [{ date: Date, dividends: 0.24 }, ...]
 *
 * // Get stock splits
 * const splits = await yahooFinance.historical('AAPL', {
 *   period1: '2020-01-01',
 *   events: 'split'
 * });
 * // Returns: [{ date: Date, stockSplits: "4:1" }, ...]
 * ```
 *
 * @remarks
 * **Limitations**:
 * - Intervals limited to daily ("1d"), weekly ("1wk"), monthly ("1mo")
 * - Events (prices, dividends, splits) require separate requests
 * - Consider using {@link chart} module for more flexibility
 *
 * **Performance**: The chart module often provides better performance and
 * more features for historical data needs.
 *
 * @module historical
 */
import validateAndCoerceTypes from "../lib/validateAndCoerceTypes.js";
import { getTypedDefinitions } from "../lib/validate/index.js";
// @yf-schema: see the docs on how this file is automatically updated.
import historicalSchema from "./historical.schema.js";
import chartSchema from "./chart.schema.js";
const historicalDefinitions = getTypedDefinitions(historicalSchema);
const chartDefinitions = getTypedDefinitions(chartSchema);
const queryOptionsDefaults = {
    interval: "1d",
    events: "history",
    includeAdjustedClose: true,
};
// Count number of null values in object (1-level deep)
function nullFieldCount(object) {
    if (object == null) {
        return;
    }
    let nullCount = 0;
    for (const val of Object.values(object))
        if (val === null)
            nullCount++;
    return nullCount;
}
/**
 * Get historical price data, dividends, or stock splits for a financial instrument.
 *
 * This function retrieves historical data from Yahoo Finance. The type of data returned
 * depends on the `events` parameter - price history (default), dividends, or stock splits.
 *
 * @example Price History
 * ```typescript
 * import YahooFinance from "yahoo-finance2";
 * const yahooFinance = new YahooFinance();
 *
 * // Get 1 year of daily price data
 * const prices = await yahooFinance.historical('AAPL', {
 *   period1: '2023-01-01',
 *   period2: '2024-01-01'
 * });
 *
 * prices.forEach(day => {
 *   console.log(`${day.date.toISOString().split('T')[0]}: $${day.close}`);
 * });
 * ```
 *
 * @example Different Time Periods
 * ```typescript
 * // Last 30 days
 * const recent = await yahooFinance.historical('TSLA', {
 *   period1: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
 * });
 *
 * // Specific date range
 * const range = await yahooFinance.historical('GOOGL', {
 *   period1: '2023-06-01',
 *   period2: '2023-12-31',
 *   interval: '1wk'  // Weekly data
 * });
 * ```
 *
 * @example Dividends and Splits
 * ```typescript
 * // Get all dividends in 2023
 * const dividends = await yahooFinance.historical('MSFT', {
 *   period1: '2023-01-01',
 *   period2: '2024-01-01',
 *   events: 'dividends'
 * });
 *
 * // Find stock splits since 2020
 * const splits = await yahooFinance.historical('AAPL', {
 *   period1: '2020-01-01',
 *   events: 'split'
 * });
 *
 * console.log(splits); // [{ date: Date('2020-08-31'), stockSplits: "4:1" }]
 * ```
 *
 * @param symbol - Stock, ETF, or other financial instrument symbol.
 *                 Use search() to find valid symbols.
 * @param queryOptionsOverrides - Required configuration:
 *                                - `period1`: Start date (required)
 *                                - `period2`: End date (optional, defaults to now)
 *                                - `interval`: "1d", "1wk", or "1mo"
 *                                - `events`: "history", "dividends", or "split"
 *                                - `includeAdjustedClose`: Include adjusted prices
 * @param moduleOptions - Optional module configuration (validateResult, etc.)
 *
 * @returns Promise that resolves to:
 *          - Array of HistoricalRowHistory (for price data)
 *          - Array of HistoricalRowDividend (for dividend data)
 *          - Array of HistoricalRowStockSplit (for split data)
 *
 * @throws Will throw an error if:
 *         - Network request fails
 *         - Invalid symbol or date range
 *         - Validation fails (if enabled)
 *
 * @remarks
 * **Limitations:**
 * - Limited to daily/weekly/monthly intervals only
 * - Events (prices, dividends, splits) require separate API calls
 * - Less flexible than the chart module
 *
 * **Alternative**: Consider using {@link chart} module for:
 * - More interval options (1m, 5m, 15m, etc.)
 * - Combined events in single request
 * - Better performance for complex queries
 *
 * **Date Formats**: Accepts Date objects, ISO strings ("2023-01-01"),
 * or Unix timestamps (milliseconds since epoch).
 *
 * @see {@link HistoricalOptions} for all available options
 * @see {@link chart} for a more flexible alternative
 */
export default async function historical(symbol, queryOptionsOverrides, moduleOptions) {
    let _schemaKey;
    this._notices.show("ripHistorical");
    if (!queryOptionsOverrides.events ||
        queryOptionsOverrides.events === "history") {
        _schemaKey = "#/definitions/HistoricalHistoryResult";
    }
    else if (queryOptionsOverrides.events === "dividends") {
        _schemaKey = "#/definitions/HistoricalDividendsResult";
    }
    else if (queryOptionsOverrides.events === "split") {
        _schemaKey = "#/definitions/HistoricalStockSplitsResult";
    }
    else
        throw new Error("No such event type:" + queryOptionsOverrides.events);
    const queryOpts = { ...queryOptionsDefaults, ...queryOptionsOverrides };
    validateAndCoerceTypes({
        source: "historical",
        type: "options",
        object: queryOpts,
        definitions: historicalDefinitions,
        schemaOrSchemaKey: "#/definitions/HistoricalOptions",
        options: this._opts.validation,
        logger: this._opts.logger,
        logObj: this._logObj,
        versionCheck: this._opts.versionCheck,
    });
    // Don't forget that queryOpts are already validated and safe-safe.
    const eventsMap = { history: "", dividends: "div", split: "split" };
    const chartQueryOpts = {
        period1: queryOpts.period1,
        period2: queryOpts.period2,
        interval: queryOpts.interval,
        events: eventsMap[queryOpts.events || "history"],
    };
    validateAndCoerceTypes({
        source: "historical",
        type: "options",
        object: chartQueryOpts,
        definitions: chartDefinitions,
        schemaOrSchemaKey: "#/definitions/ChartOptions",
        options: this._opts.validation,
        logger: this._opts.logger,
        logObj: this._logObj,
        versionCheck: this._opts.versionCheck,
    });
    /*
      throw new Error(
        "Internal error, please report.  historical() provided invalid chart() query options.",
      );
    */
    // TODO: do we even care?
    if (queryOpts.includeAdjustedClose === false) {
        /* */
    }
    const result = await this.chart(symbol, chartQueryOpts, {
        ...moduleOptions,
        validateResult: true,
    });
    let out;
    if (queryOpts.events === "dividends") {
        out = (result.events?.dividends ?? []).map((d) => ({
            date: d.date,
            dividends: d.amount,
        }));
    }
    else if (queryOpts.events === "split") {
        out = (result.events?.splits ?? []).map((s) => ({
            date: s.date,
            stockSplits: s.splitRatio,
        }));
    }
    else {
        out = (result.quotes ?? [])
            .filter((quote) => {
            const fieldCount = Object.keys(quote).length;
            const nullCount = nullFieldCount(quote);
            if (nullCount === 0) {
                // No nulls is a legit (regular) result
                return true;
            }
            else if (nullCount !== fieldCount - 1 /* skip "date" */) {
                // Unhandled case: some but not all values are null.
                // Note: no need to check for null "date", validation does it for us
                console.error(nullCount, quote);
                throw new Error("Historical returned a result with SOME (but not " +
                    "all) null values.  Please report this, and provide the " +
                    "query that caused it.");
            }
            else {
                // All fields (except "date") are null
                return false;
            }
        })
            .map((quote) => {
            if (!quote.adjclose)
                return quote;
            const { adjclose, ...rest } = quote;
            return { ...rest, adjClose: adjclose };
        });
    }
    const validateResult = !moduleOptions ||
        moduleOptions.validateResult === undefined ||
        moduleOptions.validateResult === true;
    const validationOpts = {
        ...this._opts.validation,
        // Set logErrors=false if validateResult=false
        logErrors: validateResult ? this._opts.validation.logErrors : false,
    };
    validateAndCoerceTypes({
        source: "historical",
        type: "result",
        object: out,
        definitions: historicalDefinitions,
        schemaOrSchemaKey: "#/definitions/HistoricalResult",
        options: validationOpts,
        logger: this._opts.logger,
        logObj: this._logObj,
        versionCheck: this._opts.versionCheck,
    });
    return out;
    /*
    // Original historical() retrieval code when Yahoo API still existed.
    return this._moduleExec({
      moduleName: "historical",
  
      query: {
        assertSymbol: symbol,
        url: "https://${YF_QUERY_HOST}/v7/finance/download/" + symbol,
        schemaKey: "#/definitions/HistoricalOptions",
        defaults: queryOptionsDefaults,
        overrides: queryOptionsOverrides,
        fetchType: "csv",
        transformWith(queryOptions: HistoricalOptions) {
          if (!queryOptions.period2) queryOptions.period2 = new Date();
  
          const dates = ["period1", "period2"] as const;
          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 as string).getTime();
  
              if (isNaN(timestamp))
                throw new Error(
                  "yahooFinance.historical() option '" +
                    fieldName +
                    "' invalid date provided: '" +
                    value +
                    "'",
                );
  
              queryOptions[fieldName] = Math.floor(timestamp / 1000);
            }
          }
  
          if (queryOptions.period1 === queryOptions.period2) {
            throw new Error(
              "yahooFinance.historical() options `period1` and `period2` " +
                "cannot share the same value.",
            );
          }
  
          return queryOptions;
        },
      },
  
      result: {
        schemaKey,
        transformWith(result: any) {
          if (result.length === 0) return result;
  
          const filteredResults = [];
          const fieldCount = Object.keys(result[0]).length;
  
          // Count number of null values in object (1-level deep)
          function nullFieldCount(object: Object) {
            let nullCount = 0;
            for (const val of Object.values(object))
              if (val === null) nullCount++;
            return nullCount;
          }
  
          for (const row of result) {
            const nullCount = nullFieldCount(row);
  
            if (nullCount === 0) {
              // No nulls is a legit (regular) result
              filteredResults.push(row);
            } else if (nullCount !== fieldCount - 1 /* skip "date" */
    /*) {
              // Unhandled case: some but not all values are null.
              // Note: no need to check for null "date", validation does it for us
              console.error(nullCount, row);
              throw new Error(
                "Historical returned a result with SOME (but not " +
                  "all) null values.  Please report this, and provide the " +
                  "query that caused it.",
              );
            } else {
              // All fields (except "date") are null: silently skip (no-op)
            }
          }
  
          /*
           * We may consider, for future optimization, to count rows and create
           * new array in advance, and skip consecutive blocks of null results.
           * Of doubtful utility.
           */
    /*
          return filteredResults;
        },
      },
  
      moduleOptions,
    });
    */
}
