/**
 * Data inspector — input vs. resolved rows (spec §04).
 *
 * Shows the chart's data with a toggle between two views of the same rendered
 * view: **Input** (the parsed source rows, before the spec's transforms) and
 * **Resolved** (the rows the chart draws, after filters / calculated fields /
 * aggregation). Seeing input → output side by side is how you answer "why is my
 * chart empty/wrong" — look at what the transforms did to the data. Both tables
 * come from the live Vega view via the renderer's `RenderHandle.inspectData()`
 * accessor, passed in as `getData` so this component never touches the view (the
 * embedding boundary, arch 05).
 *
 * `DataInspectorPanel` is the reusable shape (an APG disclosure, mirroring the
 * builder's source-rows preview); `DataInspector` binds it to the persisted
 * preview-pane open state for the live-preview pane. The data is read lazily —
 * only while expanded — because listing the view's datasets serializes them (see
 * `RenderHandle.inspectData`), so a collapsed inspector costs nothing.
 */

import { useMemo, useState } from 'react';
import type { InspectedData } from '../services/chart-renderer';
import { useAppStore } from '../stores/AppStore';
import { DataTable } from './DataTable';
import { SegmentedControl, type SegmentedOption } from './SegmentedControl';
import styles from './DataInspector.module.css';

/** Rows shown before truncating — matches the builder's source-preview cap (spec §06). */
const ROW_LIMIT = 50;

type DataView = 'input' | 'resolved';

/** The two views, in input → output order (the natural reading direction). */
const VIEW_OPTIONS: ReadonlyArray<SegmentedOption<DataView>> = [
  // `title` doubles as the accessible name (WCAG 2.5.3 label-in-name; leads with
  // the visible label).
  { value: 'input', label: 'Input', title: 'Input — the source rows before the spec’s transforms' },
  {
    value: 'resolved',
    label: 'Resolved',
    title: 'Resolved — the rows the chart draws, after its transforms',
  },
];

interface DataInspectorPanelProps {
  /** Whether the panel is expanded. */
  open: boolean;
  /** Toggle the expanded state. */
  onToggle: (open: boolean) => void;
  /**
   * Reads the input + resolved rows from the live view, or null when there is no
   * chart to inspect. Must be stable across renders (the read is memoized on
   * `renderEpoch`).
   */
  getData: () => InspectedData | null;
  /** Bumps whenever a render settles, so the open table re-reads the new rows. */
  renderEpoch: number;
  /**
   * Explicit panel height (px) — the live-preview pane sets this from its
   * resizable divider so the table fills the allotted space. Omitted (the builder)
   * leaves the table at its default capped height.
   */
  heightPx?: number;
  /** id of the panel root, for a splitter's `aria-controls` to point at. */
  id?: string;
}

/**
 * The reusable disclosure: a toggle bar (APG disclosure — `aria-expanded`,
 * conditional content, matching the builder's source-rows preview) over an
 * Input | Resolved view switch and the chosen table. Expanded states, each named
 * (council: GOV.UK / NN/g — say what happened and the next step):
 * - no live chart → guidance to render one;
 * - the chosen view's table is empty → say which side and why (the resolved side's
 *   transforms produced nothing; the input side's source is empty);
 * - rows → the grid.
 */
export function DataInspectorPanel({
  open,
  onToggle,
  getData,
  renderEpoch,
  heightPx,
  id,
}: DataInspectorPanelProps) {
  const [view, setView] = useState<DataView>('resolved');

  // Read both tables only while open; re-read when a render settles. `renderEpoch`
  // is an intentional refresh trigger — not read in the body (getData is stable and
  // always reads the latest view), so exhaustive-deps sees it as unnecessary.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const data = useMemo(() => (open ? getData() : null), [open, renderEpoch, getData]);
  const rows = data === null ? null : data[view];

  return (
    <div
      id={id}
      className={`${styles.inspector} ${heightPx !== undefined ? styles.fill : ''}`}
      style={heightPx !== undefined ? { height: heightPx } : undefined}
    >
      <div className={styles.bar}>
        <button
          type="button"
          className={styles.toggle}
          aria-expanded={open}
          onClick={() => onToggle(!open)}
        >
          <span className={styles.caret} aria-hidden="true">
            {open ? '▾' : '▸'}
          </span>
          Data
        </button>
        {open && (
          <>
            <SegmentedControl
              label="Data view"
              options={VIEW_OPTIONS}
              value={view}
              onChange={setView}
            />
            {rows !== null && rows.length > 0 && (
              <span className={styles.meta}>
                {rows.length.toLocaleString()} {rows.length === 1 ? 'row' : 'rows'}
              </span>
            )}
          </>
        )}
      </div>

      {open &&
        (rows === null ? (
          <p className={styles.stateNote}>Render a chart to inspect its data.</p>
        ) : rows.length === 0 ? (
          <p className={styles.stateNote}>
            {view === 'resolved'
              ? 'No rows — the spec’s filters or transforms left nothing to draw.'
              : 'The source data has no rows.'}
          </p>
        ) : (
          <DataTable
            columns={Object.keys(rows[0])}
            rows={rows.slice(0, ROW_LIMIT)}
            total={rows.length}
            ariaLabel="Data rows"
            fill={heightPx !== undefined}
          />
        ))}
    </div>
  );
}

interface DataInspectorProps {
  /** Reads the input + resolved rows from the live preview's view (stable). */
  getData: () => InspectedData | null;
  /** Bumps on each settled render so the open table refreshes. */
  renderEpoch: number;
  /** Panel height (px) from the preview pane's resizable divider (only when open). */
  heightPx?: number;
  /** id of the panel root, for the divider's `aria-controls`. */
  id?: string;
}

/** The live-preview data inspector: the panel bound to the persisted open state. */
export function DataInspector({ getData, renderEpoch, heightPx, id }: DataInspectorProps) {
  const open = useAppStore((s) => s.dataInspectorOpen);
  const setOpen = useAppStore((s) => s.setDataInspectorOpen);
  return (
    <DataInspectorPanel
      open={open}
      onToggle={setOpen}
      getData={getData}
      renderEpoch={renderEpoch}
      heightPx={heightPx}
      id={id}
    />
  );
}
