modules/visualize/visualize.js

import stats from "../analyze/components/stats.js";
import * as divisors from "./divisors.js";
import { googleCdn } from "../../external/googlecharts/googlecharts.js";

window.loaded = false;
var g = googleCdn();

/**
 * Module for visualization of charts and tables.
 * @class visualize
 */

/**
 * Creates interactive charts with Google Charts library for hydrological data visualization.
 * Supports multiple chart types including line, scatter, column, histogram, timeline, and more.
 * Handles large datasets with automatic pagination and provides extensive customization options.
 * 
 * @function chart
 * @memberof visualize
 * @param {Object} options - Configuration object for chart creation
 * @param {Object} options.params - Chart parameters and settings
 * @param {string} [options.params.chartType="line"] - Chart type: 'line', 'scatter', 'column', 'histogram', 'timeline', 'combo', 'area', 'pie', 'bar'
 * @param {string} [options.params.id="visualize"] - HTML element ID for chart container
 * @param {boolean} [options.params.partition=false] - Enable data pagination for large datasets
 * @param {number} [options.params.maxPoints=1000] - Maximum points per page when partition is enabled
 * @param {string[]|string} [options.params.names=[]] - Series names/labels (array or comma-separated string)
 * @param {Object} [options.params.options={}] - Google Charts options object
 * @param {Object} [options.args] - Additional arguments (currently unused)
 * @param {Array} options.data - Chart data in various supported formats
 * @returns {void} Creates and renders chart in specified container
 * 
 * @example
 * // Create a simple line chart for streamflow data
 * const timeData = [
 *   ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'],
 *   [100.5, 95.2, 110.8, 88.3]
 * ];
 * 
 * hydro.visualize.chart({
 *   params: {
 *     chartType: 'line',
 *     id: 'streamflow-chart',
 *     names: ['Streamflow (cfs)']
 *   },
 *   data: timeData
 * });
 * 
 * @example
 * // Create multi-series line chart with custom styling
 * const multiSeriesData = [
 *   ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'],
 *   [100.5, 95.2, 110.8, 88.3],  // Streamflow
 *   [2.1, 2.0, 2.3, 1.9],        // Stage height
 *   [0.2, 0.8, 0.0, 0.5]         // Precipitation
 * ];
 * 
 * hydro.visualize.chart({
 *   params: {
 *     chartType: 'line',
 *     id: 'multi-parameter-chart',
 *     names: ['Streamflow (cfs)', 'Stage Height (ft)', 'Precipitation (in)'],
 *     options: {
 *       title: 'Hydrological Parameters Over Time',
 *       width: 900,
 *       height: 500,
 *       vAxes: {
 *         0: { title: 'Flow & Stage' },
 *         1: { title: 'Precipitation' }
 *       },
 *       series: {
 *         0: { color: '#1f77b4', lineWidth: 3 },
 *         1: { color: '#ff7f0e', lineWidth: 2 },
 *         2: { color: '#2ca02c', targetAxisIndex: 1, type: 'columns' }
 *       }
 *     }
 *   },
 *   data: multiSeriesData
 * });
 * 
 * @example
 * // Create scatter plot for correlation analysis
 * const correlationData = [
 *   [1.2, 2.1, 3.5, 4.1, 5.8],  // Flow values
 *   [0.8, 1.5, 2.2, 2.9, 3.6]   // Stage values
 * ];
 * 
 * hydro.visualize.chart({
 *   params: {
 *     chartType: 'scatter',
 *     id: 'flow-stage-correlation',
 *     names: ['Flow vs Stage'],
 *     options: {
 *       title: 'Flow-Stage Relationship',
 *       hAxis: { title: 'Streamflow (cfs)' },
 *       vAxis: { title: 'Stage Height (ft)' },
 *       pointSize: 8,
 *       trendlines: { 0: { type: 'linear', showR2: true } }
 *     }
 *   },
 *   data: correlationData
 * });
 * 
 * @example
 * // Create column chart for monthly precipitation
 * const monthlyPrecip = [
 *   ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
 *   [2.1, 1.8, 3.2, 4.5, 5.1, 3.8]
 * ];
 * 
 * hydro.visualize.chart({
 *   params: {
 *     chartType: 'column',
 *     id: 'monthly-precip',
 *     names: ['Precipitation (inches)'],
 *     options: {
 *       title: 'Monthly Precipitation',
 *       colors: ['#2E7DD7'],
 *       bar: { groupWidth: '80%' }
 *     }
 *   },
 *   data: monthlyPrecip
 * });
 * 
 * @example
 * // Large dataset with pagination
 * const largeDataset = generateLargeTimeSeriesData(); // Assume 5000+ points
 * 
 * hydro.visualize.chart({
 *   params: {
 *     chartType: 'line',
 *     id: 'large-dataset-chart',
 *     partition: true,
 *     maxPoints: 500,
 *     names: ['Continuous Streamflow Data'],
 *     options: {
 *       title: 'Long-term Streamflow Record',
 *       explorer: { actions: ['dragToZoom', 'rightClickToReset'] }
 *     }
 *   },
 *   data: largeDataset
 * });
 * 
 * @example
 * // Nested time series data format
 * const nestedTimeSeriesData = [
 *   [['Time', '2023-01-01', '2023-01-02', '2023-01-03'], ['Flow', 100, 95, 110]],
 *   [['Time', '2023-01-01', '2023-01-02', '2023-01-03'], ['Stage', 2.1, 2.0, 2.3]]
 * ];
 * 
 * hydro.visualize.chart({
 *   params: {
 *     chartType: 'line',
 *     id: 'nested-series-chart',
 *     names: ['Flow', 'Stage']
 *   },
 *   data: nestedTimeSeriesData
 * });
 */

function chart({ params, args, data } = {}) {
  if (!Array.isArray(data)) return;

  let {
    chartType = "line",
    returnEle,
    partition = false,
    maxPoints = 1000,
    names = [],
    id = "visualize",
    options = {}
  } = params || {};

  if (typeof names === "string") {
    names = names.split(",").map(s => s.trim());
  }
  if (!Array.isArray(names)) {
    names = [];
  }

  let resizer;
  let currentChunkIndex = 0;
  let chunks = [];

  function normalizeChartData(raw) {
    if (!Array.isArray(raw)) return [];
  
    // Check for deeply nested time series: [[["t", ...], ["v", ...]], ...]
    if (
      Array.isArray(raw[0]) &&
      Array.isArray(raw[0][0]) &&
      typeof raw[0][0][0] === "string"
    ) {
      const series = raw.map(entry => {
        const [timeArr, valArr] = entry;
        const x = timeArr.slice(1);
        const y = valArr.slice(1).map(Number);
        return x.map((t, i) => [t, y[i]]);
      });
  
      // Assume all series are aligned on same timestamps
      const timestamps = series[0].map(row => row[0]);
      const result = [timestamps];
      for (let s of series) {
        result.push(s.map(row => row[1]));
      }
  
      return result;
    }
  
    // Fallbacks for older formats
    if (raw.every(x => typeof x === "number")) {
      return [[...Array(raw.length).keys()], raw];
    }
  
    let series = [], baseLength = 0;
    for (let entry of raw) {
      if (!Array.isArray(entry)) continue;
  
      if (Array.isArray(entry[0]) && typeof entry[0][0] === 'string') {
        let time = entry[0].slice(1);
        let values = entry[1].slice(1).map(Number);
        baseLength = Math.max(baseLength, time.length);
        series.push([time, values]);
      } else if (typeof entry[0] === 'string') {
        let values = entry.slice(1).map(Number);
        baseLength = Math.max(baseLength, values.length);
        series.push([null, values]);
      } else {
        baseLength = Math.max(baseLength, entry.length);
        series.push([null, entry.map(Number)]);
      }
    }
  
    let xBase = [...Array(baseLength).keys()];
    let result = [xBase];
  
    for (let [x, y] of series) {
      if (!x) x = xBase;
      x = x.concat(Array(baseLength - x.length).fill(null));
      y = y.concat(Array(baseLength - y.length).fill(null));
      result.push(y);
    }
  
    return result;
  }

  function splitChunks(data, size) {
    const length = data[0].length;
    let result = [];

    for (let start = 0; start < length; start += size) {
      result.push(data.map(arr => arr.slice(start, start + size)));
    }

    return result;
  }

  function drawChart(subData) {
    const container = document.getElementById(id);
    const dataMatrix = [];

    const headers = ['X', ...subData.slice(1).map((_, i) => names[i] || 'Series ' + (i + 1))];
    dataMatrix.push(headers);

    for (let i = 0; i < subData[0].length; i++) {
      const row = [subData[0][i]];
      for (let j = 1; j < subData.length; j++) {
        row.push(subData[j][i]);
      }
      dataMatrix.push(row);
    }

    const chartData = google.visualization.arrayToDataTable(dataMatrix);

        const chartClassMap = {
      line: 'LineChart',
      column: 'ColumnChart',
      scatter: 'ScatterChart',
      timeline: 'Timeline',
      combo: 'ComboChart',
      histogram: 'Histogram',
      area: 'AreaChart',
      pie: 'PieChart',
      bar: 'BarChart'
    };

    if (!window._chartObjects) window._chartObjects = {};
    if (!window._chartObjects[id]) window._chartObjects[id] = new google.visualization[chartClassMap[chartType]](container);


    const chart = window._chartObjects[id];
    chart.draw(chartData, options);

    if (!window._resizeHandlers) window._resizeHandlers = {};
    if (!window._resizeHandlers[id]) {
      window._resizeHandlers[id] = true;
      window.addEventListener("resize", () => {
        clearTimeout(resizer);
        resizer = setTimeout(() => chart.draw(chartData, options), 100);
      });
    }
  }

  function createButtons(container) {
    if (chunks.length <= 1 || container._navCreated) return;

    const nav = document.createElement("div");
    nav.style.position = "absolute";
    nav.style.bottom = "10px";
    nav.style.right = "10px";
    nav.style.zIndex = "10";

    const prev = document.createElement("button");
    const next = document.createElement("button");
    const info = document.createElement("span");

    prev.innerText = "◀";
    next.innerText = "▶";
    nav.append(prev, info, next);
    container.appendChild(nav);

    function updateButtons() {
      prev.disabled = currentChunkIndex === 0;
      next.disabled = currentChunkIndex === chunks.length - 1;
      info.textContent = `${currentChunkIndex + 1} / ${chunks.length}`;
    }

    prev.onclick = () => {
      if (currentChunkIndex > 0) {
        currentChunkIndex--;
        drawChart(chunks[currentChunkIndex]);
        updateButtons();
      }
    };

    next.onclick = () => {
      if (currentChunkIndex < chunks.length - 1) {
        currentChunkIndex++;
        drawChart(chunks[currentChunkIndex]);
        updateButtons();
      }
    };

    updateButtons();
    container._navCreated = true;
  }

  const normalized = normalizeChartData(data);
  if (!document.getElementById(id)) {
    const div = document.createElement("div");
    div.id = id;
    document.body.appendChild(div);
  }

  const container = document.getElementById(id);

  if (partition && normalized[0].length > maxPoints) {
    chunks = splitChunks(normalized, maxPoints);
    drawChart(chunks[currentChunkIndex]);
    createButtons(container);
  } else {
    drawChart(normalized);
  }
}

/**
 * Creates interactive data tables using Google Charts Table visualization.
 * Displays hydrological data in a structured, sortable, and filterable table format.
 * Supports various data types and provides extensive formatting options.
 * 
 * @function table
 * @memberof visualize
 * @param {Object} options - Configuration object for table creation
 * @param {Object} options.params - Table parameters and settings
 * @param {string} options.params.id - HTML element ID for table container
 * @param {string[]} options.params.datatype - Array of column data types: 'string', 'number', 'date', 'boolean'
 * @param {Object} [options.params.options] - Google Charts Table options for styling and behavior
 * @param {Object} [options.args] - Additional arguments (currently unused)
 * @param {Array} options.data - Table data as nested array [headers, row1, row2, ...]
 * @returns {void} Creates and renders table in specified container
 * 
 * @example
 * // Create a simple streamflow data table
 * const streamflowData = [
 *   [['Date', 'Streamflow (cfs)', 'Stage (ft)', 'Quality Code']],
 *   [
 *     ['2023-01-01', 125.5, 2.1, 'A'],
 *     ['2023-01-02', 118.3, 2.0, 'A'],
 *     ['2023-01-03', 132.7, 2.3, 'A'],
 *     ['2023-01-04', 109.2, 1.9, 'E']
 *   ]
 * ];
 * 
 * hydro.visualize.table({
 *   params: {
 *     id: 'streamflow-table',
 *     datatype: ['string', 'number', 'number', 'string'],
 *     options: {
 *       title: 'Daily Streamflow Data',
 *       width: '100%',
 *       height: 400,
 *       alternatingRowStyle: true,
 *       sortColumn: 0,
 *       sortAscending: false
 *     }
 *   },
 *   data: streamflowData
 * });
 * 
 * @example
 * // Create water quality parameters table with custom formatting
 * const waterQualityData = [
 *   [['Station ID', 'Temperature (°C)', 'pH', 'Dissolved Oxygen (mg/L)', 'Turbidity (NTU)', 'Sample Date']],
 *   [
 *     ['USGS-01646500', 18.5, 7.2, 8.9, 12.3, new Date('2023-06-15')],
 *     ['USGS-01647000', 19.1, 7.4, 8.7, 15.1, new Date('2023-06-15')],
 *     ['USGS-01648000', 17.8, 6.9, 9.2, 8.7, new Date('2023-06-15')]
 *   ]
 * ];
 * 
 * hydro.visualize.table({
 *   params: {
 *     id: 'water-quality-table',
 *     datatype: ['string', 'number', 'number', 'number', 'number', 'date'],
 *     options: {
 *       title: 'Water Quality Monitoring Results',
 *       width: '100%',
 *       height: 300,
 *       allowHtml: true,
 *       cssClassNames: {
 *         'oddTableRow': 'odd-row',
 *         'evenTableRow': 'even-row',
 *         'headerRow': 'header-row'
 *       },
 *       numberFormat: '#,##0.0'
 *     }
 *   },
 *   data: waterQualityData
 * });
 * 
 * @example
 * // Create flood damage assessment table
 * const floodDamageData = [
 *   [['Property ID', 'Address', 'Flood Depth (ft)', 'Damage Estimate ($)', 'Building Type', 'Mitigation Recommended']],
 *   [
 *     ['P001', '123 River St', 2.5, 45000, 'Residential', true],
 *     ['P002', '456 Flood Ave', 1.8, 28000, 'Residential', false],
 *     ['P003', '789 Water Blvd', 4.2, 125000, 'Commercial', true],
 *     ['P004', '321 Stream Dr', 0.5, 8500, 'Residential', false]
 *   ]
 * ];
 * 
 * hydro.visualize.table({
 *   params: {
 *     id: 'flood-damage-table',
 *     datatype: ['string', 'string', 'number', 'number', 'string', 'boolean'],
 *     options: {
 *       title: 'Flood Damage Assessment Results',
 *       width: '100%',
 *       height: 350,
 *       sortColumn: 3,
 *       sortAscending: false,
 *       pageSize: 10,
 *       pagingButtons: 'auto'
 *     }
 *   },
 *   data: floodDamageData
 * });
 * 
 * @example
 * // Create precipitation summary table with conditional formatting
 * const precipitationData = [
 *   [['Month', 'Precipitation (in)', 'Days with Rain', 'Max Daily (in)', 'Departure from Normal (in)']],
 *   [
 *     ['January', 2.1, 8, 0.8, -0.3],
 *     ['February', 1.8, 6, 0.6, -0.5],
 *     ['March', 4.2, 12, 1.5, +1.2],
 *     ['April', 3.8, 10, 1.2, +0.8],
 *     ['May', 5.1, 14, 2.1, +1.5],
 *     ['June', 3.6, 9, 1.8, -0.2]
 *   ]
 * ];
 * 
 * hydro.visualize.table({
 *   params: {
 *     id: 'precipitation-summary',
 *     datatype: ['string', 'number', 'number', 'number', 'number'],
 *     options: {
 *       title: 'Monthly Precipitation Summary',
 *       width: '100%',
 *       height: 300,
 *       allowHtml: true,
 *       alternatingRowStyle: true
 *     }
 *   },
 *   data: precipitationData
 * });
 */
function table({ params, args, data } = {}) {
  data = data[0]
  
  const drawTable = () => {
    // Create container for table and call the data types required for table generation
    var container,
      t1 = eval(g[2]["data"]),
      t2 = eval(g[2]["view"]),
      t3 = eval(g[2]["table"]),
      types = params.datatype,
      dat = new t1(),
      columns = [],
      tr = stats.arrchange({ data: data });

    container = document.getElementById(params.id);

    for (var k = 0; k < types.length; k++) {
      dat.addColumn(types[k]);
    }

    var tr = stats.arrchange({ data: data });
    for (var l = 1; l < tr.length; l++) {
      columns.push(tr[l]);
    }

    dat.addRows(columns);

    var view = new t2(dat),
      table = new t3(container);

    //Draw table.
    if (params.hasOwnProperty("options")) {
      var options = params.options;
      table.draw(view, options);
    } else {
      table.draw(view);
    }
    return console.log(`Table ${params.id} drawn on the given parameters.`);
  };
  drawTable();
}

/**
 * Unified drawing function with preset configurations for charts, tables, and JSON visualization.
 * Provides an easy-to-use interface for creating visualizations with sensible defaults and
 * extensive customization options. Automatically handles Google Charts library loading.
 * 
 * @function draw
 * @memberof visualize
 * @param {Object} options - Configuration object for visualization
 * @param {Object} [options.params={}] - Drawing parameters and settings
 * @param {string} options.params.type - Visualization type: 'chart', 'table', or 'json'
 * @param {string} [options.params.id] - HTML element ID (auto-generated if not provided)
 * @param {string} [options.params.name] - Display name/title for the visualization
 * @param {boolean} [options.params.returnEle=false] - Whether to return the DOM element
 * @param {boolean} [options.params.partition=false] - Enable data pagination for large datasets
 * @param {number} [options.params.maxPoints=1000] - Maximum points per page when partition enabled
 * @param {Object} [options.args={}] - Visualization-specific arguments
 * @param {string} [options.args.charttype="line"] - Chart type for chart visualizations
 * @param {string[]|string} [options.args.names] - Series names/labels
 * @param {string} [options.args.font="monospace"] - Font family for text elements
 * @param {Object} [options.args.vAxes] - Vertical axes configuration
 * @param {Object} [options.args.series] - Series-specific styling
 * @param {Array} [options.data=[]] - Data to visualize in supported format
 * @returns {void|Element} Creates visualization or returns DOM element if returnEle is true
 * 
 * @example
 * // Create a simple line chart with default styling
 * const timeSeriesData = [
 *   [0, 1, 2, 3, 4, 5],
 *   [100.5, 95.2, 110.8, 88.3, 102.1, 97.6]
 * ];
 * 
 * hydro.visualize.draw({
 *   params: {
 *     type: 'chart',
 *     name: 'Daily Streamflow'
 *   },
 *   args: {
 *     charttype: 'line',
 *     names: ['Streamflow (cfs)']
 *   },
 *   data: timeSeriesData
 * });
 * 
 * @example
 * // Create a multi-series chart with custom styling
 * const multiParameterData = [
 *   ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04'],
 *   [125.5, 118.3, 132.7, 109.2],  // Streamflow
 *   [2.1, 2.0, 2.3, 1.9],          // Stage
 *   [0.0, 0.2, 0.8, 0.1]           // Precipitation
 * ];
 * 
 * hydro.visualize.draw({
 *   params: {
 *     type: 'chart',
 *     name: 'Hydrological Parameters',
 *     id: 'multi-param-chart'
 *   },
 *   args: {
 *     charttype: 'line',
 *     names: ['Streamflow (cfs)', 'Stage Height (ft)', 'Precipitation (in)'],
 *     font: 'Arial',
 *     backgroundColor: '#f8f9fa',
 *     titleColor: '#2c3e50',
 *     vAxes: {
 *       0: { 
 *         title: 'Flow & Stage',
 *         titleTextStyle: { color: '#1f77b4' }
 *       },
 *       1: { 
 *         title: 'Precipitation',
 *         titleTextStyle: { color: '#2ca02c' }
 *       }
 *     },
 *     series: {
 *       0: { color: '#1f77b4', lineWidth: 3 },
 *       1: { color: '#ff7f0e', lineWidth: 2 },
 *       2: { color: '#2ca02c', targetAxisIndex: 1, type: 'columns' }
 *     }
 *   },
 *   data: multiParameterData
 * });
 * 
 * @example
 * // Create a column chart for monthly data
 * const monthlyData = [
 *   ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
 *   [2.1, 1.8, 3.2, 4.5, 5.1, 3.8]
 * ];
 * 
 * hydro.visualize.draw({
 *   params: {
 *     type: 'chart',
 *     name: 'Monthly Precipitation',
 *     id: 'monthly-precip'
 *   },
 *   args: {
 *     charttype: 'column',
 *     names: ['Precipitation (inches)'],
 *     backgroundColor: '#ffffff',
 *     chartAreaWidth: '80%',
 *     chartAreaHeight: '70%',
 *     barWidth: '80%'
 *   },
 *   data: monthlyData
 * });
 * 
 * @example
 * // Create a data table
 * const tableData = [
 *   [['Date', 'Flow (cfs)', 'Stage (ft)']],
 *   [
 *     ['2023-01-01', 125.5, 2.1],
 *     ['2023-01-02', 118.3, 2.0],
 *     ['2023-01-03', 132.7, 2.3]
 *   ]
 * ];
 * 
 * hydro.visualize.draw({
 *   params: {
 *     type: 'table',
 *     name: 'Streamflow Data',
 *     id: 'data-table'
 *   },
 *   data: tableData
 * });
 * 
 * @example
 * // Render JSON data for inspection
 * const jsonData = {
 *   station: {
 *     id: 'USGS-01646500',
 *     name: 'Potomac River at Washington, DC',
 *     coordinates: { lat: 38.9495, lon: -77.0458 },
 *     parameters: ['streamflow', 'stage', 'temperature']
 *   },
 *   data: {
 *     recent: [125.5, 118.3, 132.7],
 *     quality: ['A', 'A', 'A']
 *   }
 * };
 * 
 * hydro.visualize.draw({
 *   params: {
 *     type: 'json',
 *     name: 'Station Metadata',
 *     id: 'station-info'
 *   },
 *   data: jsonData
 * });
 * 
 * @example
 * // Large dataset with pagination
 * const largeTimeSeries = generateLargeDataset(5000); // Assume 5000 data points
 * 
 * hydro.visualize.draw({
 *   params: {
 *     type: 'chart',
 *     name: 'Long-term Streamflow Record',
 *     partition: true,
 *     maxPoints: 500
 *   },
 *   args: {
 *     charttype: 'line',
 *     names: ['Historical Streamflow'],
 *     chartAreaHeight: '80%'
 *   },
 *   data: largeTimeSeries
 * });
 */

function draw({ params = {}, args = {}, data = [] } = {}) {
  if (!window.loaded) {
    (() => {
      google.charts.load("current", {
        packages: ["corechart", "table", "line", "timeline"],
      });
    })();
  }
  window.loaded = true;

  let {
    type,
    id = `chart-${Math.floor(Math.random() * 100)}-gen`,
    name,
    returnEle = false,
  } = params;
  name === undefined ? (name = id) : name;

  var dat = data,
    pm;
  if (type !== "json") {
    //change the input in case its just a 1d array
    if (Array.isArray(data) && typeof data[0] === "number") {
      // Single array of numbers, convert to [x, y]
      const x = Array.from({ length: data.length }, (_, i) => i);
      data = [x, data];
    }
  }

  let container = document.getElementById(id)

  // Get container dimensions if provided
  const containerWidth = container?.offsetWidth || '100%';
  const containerHeight = container?.offsetHeight || '100%';


  //Chart drawing options.
  // Chart drawing options
  if (type === "chart") {
    let { charttype = "line", names } = args;

    // Base options
    const baseOptions = {
      width: containerWidth,
      height: containerHeight,
      title: name,
      fontName: args.font || "monospace",
      chartArea: {
        width: args.chartAreaWidth || '85%',
        height: args.chartAreaHeight || '75%',
        left: args.chartAreaLeft || '15%',
        top: args.chartAreaTop || '15%',
        right: args.chartAreaRight || '5%',
        bottom: args.chartAreaBottom || '20%'
      },
      backgroundColor: args.backgroundColor || '#fff',
      titleTextStyle: {
        color: args.titleColor || '#333',
        fontSize: args.titleFontSize || 16,
        bold: args.titleBold !== false
      },
      legend: {
        position: args.legendPosition || 'bottom',
        textStyle: {
          color: args.legendTextColor || '#666',
          fontSize: args.legendFontSize || 12
        }
      }
    };

    // Chart type specific options
    const chartSpecificOptions = {
      line: {
        curveType: args.curveType || 'function',
        lineWidth: args.lineWidth || 2,
        pointSize: args.pointSize || 5,
        explorer: {
          actions: ['dragToZoom', 'rightClickToReset']
        },
        vAxes: args.vAxes || {},
        series: args.series || {}
      },
      column: {
        bar: { groupWidth: args.barWidth || '75%' },
        isStacked: args.stacked || false,
        vAxes: args.vAxes || {},
        series: args.series || {}
      },
      scatter: {
        pointShape: args.pointShape || 'circle',
        pointSize: args.pointSize || 5,
        trendlines: args.trendlines || {},
        vAxes: args.vAxes || {},
        series: args.series || {}
      },
      pie: {
        pieHole: args.donut ? 0.4 : 0,
        pieSliceText: 'percentage'
      }
    };

    var pm = {
      chartType: charttype,
      id,
      name,
      options: {
        ...baseOptions,
        ...(chartSpecificOptions[charttype] || {})
      },
      names: names,
      partition: params.partition || false,
      maxPoints: params.maxPoints || 1000
    };

    setTimeout(() => chart({ params: pm, data: dat }), 200);
    return;
  }

  //Table options
  else if (type === "table") {
    // var datatype = [];
    // //MOMENTARY CORRECTION
    // dat[0][1][0] = "Value";
    // datatype.push("string");
    // datatype.push("number");
    // //Customizable chart for two columns. Will be expanded to n columns.
    pm = {
      id: id,
      //datatype: datatype,
      options: {
        title: id,
        width: "100%",
        height: "80%",
      },
    };
    // setTimeout(() => table({ params: pm, data: dat }), 200);
    // return;
    drawHtmlTable({params: pm, data: dat })
  }
  //JSON options.
  else if (type === "json") {
    return prettyPrint({ params: params, data: data });
  }
}

/**
 * Creates an interactive JSON viewer for exploring complex JavaScript objects and data structures.
 * Uses the renderjson library to provide collapsible, hierarchical display of JSON data with
 * syntax highlighting and navigation controls. Ideal for data inspection and debugging.
 * 
 * @function prettyPrint
 * @memberof visualize
 * @param {Object} options - Configuration object for JSON rendering
 * @param {Object} [options.params] - Rendering parameters and settings
 * @param {string} [options.params.id="jsonrender"] - HTML element ID for JSON container
 * @param {string} [options.params.title] - Title to display above the JSON viewer
 * @param {string} [options.params.input] - Input mode: 'all' to display localStorage items, or omit for data parameter
 * @param {string} [options.params.type="JSON"] - Data type (currently only JSON supported)
 * @param {Object} [options.args] - Additional arguments (currently unused)
 * @param {Object|Array} [options.data] - JavaScript object or array to render as JSON
 * @returns {void} Creates and displays interactive JSON viewer in specified container
 * 
 * @example
 * // Display API response data for inspection
 * const apiResponse = {
 *   station: {
 *     id: 'USGS-01646500',
 *     name: 'Potomac River at Washington, DC',
 *     coordinates: { latitude: 38.9495, longitude: -77.0458 },
 *     drainage_area: 11570.0,
 *     status: 'active'
 *   },
 *   timeSeries: [
 *     {
 *       variable: { code: '00060', name: 'Streamflow', unit: 'ft3/s' },
 *       values: [
 *         { dateTime: '2023-01-01T12:00:00Z', value: 2850, qualifiers: ['A'] },
 *         { dateTime: '2023-01-01T12:15:00Z', value: 2840, qualifiers: ['A'] }
 *       ]
 *     }
 *   ]
 * };
 * 
 * hydro.visualize.prettyPrint({
 *   params: {
 *     id: 'api-response-viewer',
 *     title: 'USGS API Response Data'
 *   },
 *   data: apiResponse
 * });
 * 
 * @example
 * // Display flood modeling results
 * const floodModelResults = {
 *   scenario: {
 *     return_period: '100-year',
 *     peak_discharge: 15000,
 *     flood_stage: 18.5,
 *     duration_hours: 72
 *   },
 *   affected_areas: [
 *     {
 *       name: 'Downtown District',
 *       area_sq_km: 2.3,
 *       max_depth_ft: 4.2,
 *       buildings_affected: 245,
 *       estimated_damage: 8500000
 *     },
 *     {
 *       name: 'Residential Zone A',
 *       area_sq_km: 5.1,
 *       max_depth_ft: 2.8,
 *       buildings_affected: 189,
 *       estimated_damage: 3200000
 *     }
 *   ],
 *   mitigation_measures: {
 *     levees: { effectiveness: 0.85, cost: 12000000 },
 *     flood_walls: { effectiveness: 0.75, cost: 8500000 },
 *     early_warning: { effectiveness: 0.60, cost: 500000 }
 *   }
 * };
 * 
 * hydro.visualize.prettyPrint({
 *   params: {
 *     id: 'flood-results-viewer',
 *     title: 'Flood Model Results - 100 Year Event'
 *   },
 *   data: floodModelResults
 * });
 * 
 * @example
 * // Display water quality monitoring metadata
 * const waterQualityMeta = {
 *   monitoring_program: {
 *     name: 'Chesapeake Bay Water Quality Monitoring',
 *     agency: 'EPA',
 *     start_date: '1985-01-01',
 *     frequency: 'monthly'
 *   },
 *   parameters: [
 *     { code: 'TEMP', name: 'Temperature', unit: 'degrees Celsius', precision: 0.1 },
 *     { code: 'DO', name: 'Dissolved Oxygen', unit: 'mg/L', precision: 0.01 },
 *     { code: 'PH', name: 'pH', unit: 'standard units', precision: 0.01 },
 *     { code: 'TURB', name: 'Turbidity', unit: 'NTU', precision: 0.1 }
 *   ],
 *   quality_codes: {
 *     'A': 'Approved for publication',
 *     'P': 'Provisional data',
 *     'E': 'Estimated',
 *     'R': 'Revised'
 *   },
 *   stations: {
 *     total: 156,
 *     active: 142,
 *     discontinued: 14
 *   }
 * };
 * 
 * hydro.visualize.prettyPrint({
 *   params: {
 *     id: 'wq-metadata-viewer',
 *     title: 'Water Quality Program Metadata'
 *   },
 *   data: waterQualityMeta
 * });
 * 
 * @example
 * // Display all objects stored in localStorage
 * hydro.visualize.prettyPrint({
 *   params: {
 *     input: 'all',
 *     id: 'localStorage-viewer',
 *     title: 'Stored Data Objects'
 *   }
 * });
 * 
 * @example
 * // Display complex nested analysis results
 * const analysisResults = {
 *   analysis_type: 'flow_duration_curve',
 *   station_info: {
 *     usgs_id: '01646500',
 *     period_of_record: { start: '1930-10-01', end: '2023-09-30' },
 *     years_of_data: 93
 *   },
 *   statistics: {
 *     percentiles: {
 *       p05: 8420, p10: 5280, p25: 2650, p50: 1580,
 *       p75: 980, p90: 640, p95: 480
 *     },
 *     annual_stats: {
 *       mean: 2180, median: 1580, std_dev: 3420,
 *       min: 112, max: 32600, skewness: 4.2
 *     }
 *   },
 *   data_quality: {
 *     percent_complete: 98.7,
 *     missing_days: 425,
 *     estimated_values: 1250,
 *     quality_flags: { good: 95.2, estimated: 3.8, poor: 1.0 }
 *   }
 * };
 * 
 * hydro.visualize.prettyPrint({
 *   params: {
 *     id: 'analysis-results-viewer',
 *     title: 'Flow Duration Analysis Results'
 *   },
 *   data: analysisResults
 * });
 */

function prettyPrint({ params, args, data } = {}) {
  // Get the container ID from params or use default
  const containerId = params.id || "jsonrender";

  // Find the container in the reports area
  let container = document.getElementById(containerId);
  if (!container) {
    // If the container doesn't exist, create it in the reports area
    const reportContent = document.querySelector('.report-content');
    if (reportContent) {
      container = document.createElement('div');
      container.id = containerId;
      container.className = 'jsonrender';
      reportContent.appendChild(container);
    } else {
      console.error('Report content area not found');
      return;
    }
  }

  // Clear existing content in the container
  container.innerHTML = '';

  // Using external library to render json on screen
  var src = "https://cdn.rawgit.com/caldwell/renderjson/master/renderjson.js";

  var sc = divisors.createScript({ params: { src: src, name: "jsonrender" } });
  sc.addEventListener("load", () => {
    renderjson.set_icons("+", "-");
    renderjson.set_show_to_level(1);

    if (container) {
      var name;
      if (data) {
        // Render the JSON object passed to the function
        name = document.createTextNode(params.title || "");
        container.appendChild(name);
        container.appendChild(renderjson(data));
      } else {
        // Render the objects saved in local storage
        if (window.localStorage.length === 0) {
          return alert("No items stored!");
        }
        for (var i = 0; i < Object.keys(window.localStorage).length; i++) {
          name = document.createTextNode(Object.keys(window.localStorage)[i]);
          container.appendChild(name);
          container.appendChild(
            renderjson(
              JSON.parse(
                window.localStorage[Object.keys(window.localStorage)[i]]
              )
            )
          );
        }
      }
    }
  });
}

/**
 * Creates comprehensive HTML reports with interactive JSON exploration capabilities.
 * Generates structured reports with summary statistics, data previews, and export functionality.
 * Ideal for data analysis documentation and sharing results with stakeholders.
 * 
 * @function generateReport
 * @memberof visualize
 * @param {Object} options - Configuration object for report generation
 * @param {Object} [options.params] - Report parameters and settings
 * @param {string} [options.params.id="report-container"] - HTML element ID for report container
 * @param {string} [options.params.title] - Title for the report
 * @param {Object} [options.args] - Additional arguments (currently unused)
 * @param {Object|Array} options.data - Data object to include in the report
 * @returns {void} Creates and displays interactive report in specified container
 * 
 * @example
 * // Generate report for streamflow analysis results
 * const streamflowAnalysis = {
 *   metadata: {
 *     station_id: 'USGS-01646500',
 *     analysis_period: '2020-2023',
 *     record_length: '4 years',
 *     data_completeness: '99.2%'
 *   },
 *   statistics: {
 *     mean_flow: 2180.5,
 *     median_flow: 1820.0,
 *     max_flow: 18500.0,
 *     min_flow: 245.0,
 *     flow_percentiles: {
 *       p10: 680, p25: 1120, p75: 2890, p90: 4560
 *     }
 *   },
 *   seasonal_patterns: {
 *     spring_avg: 3450, summer_avg: 1820,
 *     fall_avg: 1680, winter_avg: 2280
 *   }
 * };
 * 
 * hydro.visualize.generateReport({
 *   params: {
 *     id: 'streamflow-report',
 *     title: 'Potomac River Streamflow Analysis Report'
 *   },
 *   data: streamflowAnalysis
 * });
 * 
 * @example
 * // Generate flood risk assessment report
 * const floodRiskData = {
 *   assessment_info: {
 *     study_area: 'Cedar Rapids, IA',
 *     flood_scenarios: ['10-yr', '25-yr', '50-yr', '100-yr', '500-yr'],
 *     assessment_date: '2023-08-15',
 *     methodology: 'HEC-RAS 2D modeling'
 *   },
 *   risk_summary: {
 *     total_structures: 2847,
 *     at_risk_100yr: 456,
 *     estimated_damages_100yr: 125000000,
 *     population_at_risk: 1230
 *   },
 *   mitigation_options: [
 *     { type: 'Levee System', cost: 45000000, benefit_cost_ratio: 2.8 },
 *     { type: 'Floodwall', cost: 28000000, benefit_cost_ratio: 2.1 },
 *     { type: 'Property Buyouts', cost: 15000000, benefit_cost_ratio: 3.2 }
 *   ]
 * };
 * 
 * hydro.visualize.generateReport({
 *   params: {
 *     id: 'flood-risk-report',
 *     title: 'Cedar Rapids Flood Risk Assessment'
 *   },
 *   data: floodRiskData
 * });
 */
function generateReport({ params, args, data } = {}) {
  const containerId = params.id || "report-container";
  let container = document.getElementById(containerId);

  if (!container) {
    const reportContent = document.querySelector('.report-content');
    if (reportContent) {
      container = document.createElement('div');
      container.id = containerId;
      container.className = 'report-container';
      reportContent.appendChild(container);
    } else {
      console.error('Report content area not found');
      return;
    }
  }

  container.innerHTML = '';

  const reportHeader = document.createElement('div');
  reportHeader.className = 'report-header';
  reportHeader.textContent = params.title || 'JSON Report';
  container.appendChild(reportHeader);

  const reportBody = document.createElement('div');
  reportBody.className = 'report-body';

  const summarySection = document.createElement('div');
  summarySection.className = 'report-section';
  summarySection.innerHTML = `
    <h4>Summary</h4>
    <p>Type: ${typeof data}</p>
    <p>Keys: ${Object.keys(data).length}</p>
  `;
  reportBody.appendChild(summarySection);

  const previewSection = document.createElement('div');
  previewSection.className = 'report-section';
  previewSection.innerHTML = `
    <h4>JSON Preview</h4>
    <div class="json-preview"></div>
  `;
  const jsonPreview = previewSection.querySelector('.json-preview');
  reportBody.appendChild(previewSection);

  function renderJson(obj, parent, maxInitialKeys = 50, loadBatchSize = 50) {
    const ul = document.createElement('ul');
    ul.className = 'json-list';

    const entries = Object.entries(obj);
    let shownCount = 0;

    const renderChunk = (start, end) => {
      for (let i = start; i < end && i < entries.length; i++) {
        const [key, value] = entries[i];
        const li = document.createElement('li');
        li.className = 'json-item';

        const keySpan = document.createElement('span');
        keySpan.className = 'json-key';
        keySpan.textContent = key + ': ';
        li.appendChild(keySpan);

        if (typeof value === 'object' && value !== null) {
          const toggle = document.createElement('span');
          toggle.className = 'json-toggle';
          toggle.textContent = '[+]';
          let ulChild;

          toggle.addEventListener('click', () => {
            if (!ulChild) {
              ulChild = document.createElement('ul');
              ulChild.className = 'json-list';
              renderJson(value, ulChild, maxInitialKeys, loadBatchSize);
              li.appendChild(ulChild);
            }
            const isHidden = ulChild.style.display === 'none';
            ulChild.style.display = isHidden ? 'block' : 'none';
            toggle.textContent = isHidden ? '[-]' : '[+]';
          });

          li.appendChild(toggle);
        } else {
          const valueSpan = document.createElement('span');
          valueSpan.className = 'json-value';
          valueSpan.textContent = JSON.stringify(value);
          li.appendChild(valueSpan);
        }

        ul.appendChild(li);
      }
    };

    renderChunk(0, maxInitialKeys);
    shownCount = maxInitialKeys;

    const loadMoreBtn = document.createElement('button');
    loadMoreBtn.textContent = `Load more (${entries.length - shownCount} remaining)`;
    loadMoreBtn.className = 'json-load-more';
    loadMoreBtn.style.marginTop = '5px';
    loadMoreBtn.onclick = () => {
      renderChunk(shownCount, shownCount + loadBatchSize);
      shownCount += loadBatchSize;
      if (shownCount >= entries.length) {
        loadMoreBtn.remove();
      } else {
        loadMoreBtn.textContent = `Load more (${entries.length - shownCount} remaining)`;
        ul.appendChild(loadMoreBtn); // Append again at the end
      }
    };

    if (entries.length > maxInitialKeys) {
      ul.appendChild(loadMoreBtn);
    }

    parent.appendChild(ul);
  }

  const jsonContainer = document.createElement('div');
  jsonContainer.className = 'json-container';
  renderJson(data, jsonContainer);
  jsonPreview.appendChild(jsonContainer);

  const downloadSection = document.createElement('div');
  downloadSection.className = 'report-section';
  downloadSection.innerHTML = `
    <h4>Export JSON</h4>
    <button class="download-button">Download as JSON</button>
  `;
  const downloadButton = downloadSection.querySelector('.download-button');
  downloadButton.addEventListener('click', () => {
    const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${params.title || 'data'}.json`;
    a.click();
    URL.revokeObjectURL(url);
  });
  reportBody.appendChild(downloadSection);

  container.appendChild(reportBody);
}

/**
 * Creates clean, responsive HTML tables with built-in styling for displaying key-value pairs.
 * Generates lightweight tables without external dependencies, ideal for summary data display
 * and quick data presentation in reports or dashboards.
 * 
 * @function drawHtmlTable
 * @memberof visualize
 * @param {Object} options - Configuration object for HTML table creation
 * @param {Object} [options.params] - Table parameters and settings
 * @param {string} [options.params.id="visualize"] - HTML element ID for table container
 * @param {Array} options.data - Data array containing [labels, values] pairs
 * @returns {void} Creates and displays HTML table in specified container
 * 
 * @example
 * // Create station summary table
 * const stationSummary = [
 *   ['Station ID', 'Station Name', 'Latitude', 'Longitude', 'Drainage Area', 'Record Start'],
 *   ['USGS-01646500', 'Potomac River at Washington, DC', '38.9495°N', '77.0458°W', '11,570 sq mi', '1930-10-01']
 * ];
 * 
 * hydro.visualize.drawHtmlTable({
 *   params: { id: 'station-info-table' },
 *   data: stationSummary
 * });
 * 
 * @example
 * // Create current conditions table
 * const currentConditions = [
 *   ['Parameter', 'Current Reading', 'Date/Time', 'Quality', 'Units'],
 *   [
 *     'Streamflow', '2,450', '2023-08-15 14:30', 'Approved', 'ft³/s',
 *     'Stage Height', '8.25', '2023-08-15 14:30', 'Approved', 'ft',
 *     'Water Temperature', '22.8', '2023-08-15 14:30', 'Provisional', '°C'
 *   ]
 * ];
 * 
 * hydro.visualize.drawHtmlTable({
 *   params: { id: 'current-conditions' },
 *   data: currentConditions
 * });
 * 
 * @example
 * // Create flood damage summary table
 * const floodSummary = [
 *   ['Flood Scenario', 'Peak Discharge', 'Affected Properties', 'Estimated Damage', 'Population at Risk'],
 *   [
 *     '25-year', '8,500 ft³/s', '125', '$2.8M', '340',
 *     '100-year', '12,800 ft³/s', '456', '$8.5M', '980', 
 *     '500-year', '18,200 ft³/s', '1,250', '$25.2M', '2,400'
 *   ]
 * ];
 * 
 * hydro.visualize.drawHtmlTable({
 *   params: { id: 'flood-damage-summary' },
 *   data: floodSummary
 * });
 * 
 * @example
 * // Create water quality results table
 * const waterQualityResults = [
 *   ['Test Parameter', 'Result', 'EPA Standard', 'Status', 'Collection Date'],
 *   [
 *     'pH', '7.2', '6.5 - 8.5', 'Within Range', '2023-08-10',
 *     'Dissolved Oxygen', '8.9 mg/L', '>5.0 mg/L', 'Good', '2023-08-10',
 *     'Turbidity', '3.2 NTU', '<4.0 NTU', 'Acceptable', '2023-08-10',
 *     'E. coli', '45 CFU/100mL', '<126 CFU/100mL', 'Safe', '2023-08-10'
 *   ]
 * ];
 * 
 * hydro.visualize.drawHtmlTable({
 *   params: { id: 'water-quality-results' },
 *   data: waterQualityResults
 * });
 * 
 * @example
 * // Create simple key-value pairs table
 * const metadata = [
 *   ['Data Source', 'Analysis Period', 'Total Records', 'Missing Data', 'Quality Score'],
 *   ['USGS NWIS', '2020-2023', '1,461 days', '12 days (0.8%)', '99.2%']
 * ];
 * 
 * hydro.visualize.drawHtmlTable({
 *   params: { id: 'dataset-metadata' },
 *   data: metadata
 * });
 */
function drawHtmlTable({ params, data } = {}) {
  data = data[0]
  const containerId = params.id || "visualize";
  const container = document.getElementById(containerId);

  if (!container || !Array.isArray(data) || data.length < 2) {
    console.error("Invalid container or data structure.");
    return;
  }

  const [labels, values] = data;

  container.innerHTML = ""; // Clear previous content

  const style = document.createElement("style");
  style.textContent = `
    #${containerId} .mini-table {
      width: 100%;
      max-width: 500px;
      margin: 0 auto;
      border-collapse: collapse;
      font-family: sans-serif;
      font-size: 14px;
      color: #333;
    }

    #${containerId} .mini-table th,
    #${containerId} .mini-table td {
      padding: 8px 12px;
      border-bottom: 1px solid #eee;
    }

    #${containerId} .mini-table th {
      text-align: left;
      font-weight: 600;
      background: #f9f9f9;
    }

    #${containerId} .mini-table tr:hover {
      background-color: #f0f0f0;
    }
  `;
  document.head.appendChild(style);

  const table = document.createElement("table");
  table.className = "mini-table";

  for (let i = 0; i < labels.length; i++) {
    const row = document.createElement("tr");

    const th = document.createElement("th");
    th.textContent = labels[i];

    const td = document.createElement("td");
    td.textContent = values[i];

    row.appendChild(th);
    row.appendChild(td);
    table.appendChild(row);
  }

  container.appendChild(table);
}


export { draw, generateReport };