/**
 * Sibling folding — collapse repetitive structure.
 *
 * Always-on, applied to every node list. Structurally-identical sibling
 * chains (table rows, list items, grid cells) collapse to "first N +
 * a collapsed marker". This is what makes a 100-row data table fit the
 * token budget — without it a 100-row table snapshot is ~10x over.
 *
 */

import type { CSTNode } from '../cst/types';
import { hasChildren } from '../cst/types';

/** How many leading instances of a repeated chain to keep. */
const KEEP_LEADING = 3;
/** A chain shorter than this is never folded. */
const MIN_CHAIN = 5;

/** Result of a folding pass. */
export interface FoldResult {
  /** The folded tree. */
  children: CSTNode[];
  /** How many sibling chains were folded. */
  foldedCount: number;
}

/**
 * A shallow structural hash of a node — its shape, not its content.
 * Two nodes with the same hash are "the same kind of thing" (e.g. two
 * table rows), regardless of the text they hold.
 */
function structuralHash(node: CSTNode): string {
  switch (node.type) {
    case 'text':
      return 't';
    case 'interactive':
      return `i:${node.role}`;
    case 'container':
      return `c:${node.role}:[${node.children
        .map(structuralHash)
        .join(',')}]`;
    case 'root':
      return 'root';
  }
}

/** Counter threaded through a recursive fold so totals aggregate. */
interface FoldCtx {
  folded: number;
}

/** Fold one list of sibling nodes. */
function foldList(nodes: CSTNode[], ctx: FoldCtx): CSTNode[] {
  // Recurse first so nested lists are folded before the outer pass.
  const deep = nodes.map((n) => foldNode(n, ctx));

  const out: CSTNode[] = [];
  let i = 0;
  while (i < deep.length) {
    const hash = structuralHash(deep[i]);
    // Extend the run of identically-shaped siblings.
    let j = i + 1;
    while (j < deep.length && structuralHash(deep[j]) === hash) j++;
    const runLength = j - i;

    if (runLength >= MIN_CHAIN) {
      // Keep the leading N, collapse the rest into one marker.
      for (let k = i; k < i + KEEP_LEADING; k++) out.push(deep[k]);
      const collapsed = runLength - KEEP_LEADING;
      out.push({
        type: 'text',
        content: `[… ${collapsed} more similar items collapsed]`,
      });
      ctx.folded++;
    } else {
      for (let k = i; k < j; k++) out.push(deep[k]);
    }
    i = j;
  }
  return out;
}

/** Recursively fold a single node's children. */
function foldNode(node: CSTNode, ctx: FoldCtx): CSTNode {
  if (!hasChildren(node)) return node;
  return { ...node, children: foldList(node.children, ctx) };
}

/**
 * Fold repetitive sibling chains within a node list.
 */
export function foldSiblings(children: CSTNode[]): FoldResult {
  const ctx: FoldCtx = { folded: 0 };
  const folded = foldList(children, ctx);
  return { children: folded, foldedCount: ctx.folded };
}
