/**
 * Spec → view tree (docs/architecture/08 → editor augmentation). Builds the
 * recursive structure of a Vega-Lite spec's composition — the model behind the
 * composition wireframe: nested boxes for `layer`/`hconcat`/`vconcat`/`concat`
 * (array children) and `facet`/`repeat` (a single generated `spec` child), down to
 * the unit views at the leaves.
 *
 * Portable core. Built over `jsonc-parser`'s error-tolerant tree (so it keeps
 * working while the draft is briefly unparseable mid-edit), and each node carries
 * its byte range so a surface can map a box to the editor selection and back. The
 * complement to `spec-insert` (which mutates one composition array): this reads the
 * whole tree at once.
 */

import { findNodeAtLocation, parseTree, type Node } from 'jsonc-parser';
import type { SpecPath } from './spec-insert';
import { ARRAY_COMPOSITIONS } from './spec-transforms';

/** A composition operator. Array ops hold a child array; facet/repeat a `spec`. */
type CompositionOp = 'layer' | 'hconcat' | 'vconcat' | 'concat' | 'facet' | 'repeat';

/** How a composition lays its children out (a hint for the wireframe renderer). */
export type Orientation = 'horizontal' | 'vertical' | 'layered' | 'grid';

/** A node in the view tree: a composition (with children) or a leaf unit view. */
export interface ViewNode {
  kind: 'composition' | 'unit';
  /** The operator, for a composition node. */
  op?: CompositionOp;
  /** Layout hint, for a composition node. */
  orientation?: Orientation;
  /** Path from the root to this node (object keys + array indices). */
  path: SpecPath;
  /** Byte range of the node's object in the source, for editor selection sync. */
  offset: number;
  length: number;
  /** The mark type, for a unit node (string mark or `mark.type`); else undefined. */
  mark?: string;
  children: ViewNode[];
}

const ORIENTATION: Record<CompositionOp, Orientation> = {
  hconcat: 'horizontal',
  vconcat: 'vertical',
  concat: 'grid', // general concat wraps into a grid (honors `columns`)
  layer: 'layered',
  facet: 'grid',
  repeat: 'grid',
};

/** Facet and repeat hold their single child view under `spec` (not an array). */
const SPEC_CHILD_OPS: readonly CompositionOp[] = ['facet', 'repeat'];

/** A unit's mark type: a string `mark`, or `mark.type`; undefined when absent. */
function markOf(node: Node): string | undefined {
  const mark = findNodeAtLocation(node, ['mark']);
  if (!mark) return undefined;
  if (mark.type === 'string') return mark.value as string;
  const type = findNodeAtLocation(mark, ['type']);
  return typeof type?.value === 'string' ? type.value : undefined;
}

function buildNode(node: Node, path: SpecPath): ViewNode {
  const base = { path, offset: node.offset, length: node.length };
  // Array compositions: children are the array elements.
  for (const op of ARRAY_COMPOSITIONS as CompositionOp[]) {
    const arr = findNodeAtLocation(node, [op]);
    if (arr?.type === 'array') {
      return {
        kind: 'composition',
        op,
        orientation: ORIENTATION[op],
        ...base,
        children: (arr.children ?? []).map((el, i) => buildNode(el, [...path, op, i])),
      };
    }
  }
  // Facet / repeat: the single child view lives under `spec`.
  for (const op of SPEC_CHILD_OPS) {
    if (findNodeAtLocation(node, [op])) {
      const spec = findNodeAtLocation(node, ['spec']);
      return {
        kind: 'composition',
        op,
        orientation: ORIENTATION[op],
        ...base,
        children: spec ? [buildNode(spec, [...path, 'spec'])] : [],
      };
    }
  }
  // Otherwise a leaf unit view.
  return { kind: 'unit', ...base, mark: markOf(node), children: [] };
}

/**
 * The view tree for the spec `text`, or null when it has no object root (empty or
 * unparseable beyond recovery). A flat unit spec yields a single `unit` node with
 * no children — the caller treats that as "no composition to show".
 */
export function viewTree(text: string): ViewNode | null {
  const root = parseTree(text);
  if (!root || root.type !== 'object') return null;
  return buildNode(root, []);
}
