/**
 * Data-transform scaffolding for the editor (docs/architecture/08 → editor
 * augmentation) — the "quick transform" affordance. It offers the popular Vega-Lite
 * transforms (filter, calculate, aggregate, bin, timeUnit, …) as ready-to-fill
 * snippets, each seeded with a type-appropriate column from the data in scope. The
 * facts the schema can't supply — *where a view's pipeline is* and *what a field-typed
 * skeleton looks like* — are pure core (`core/spec-data-transforms`); this is the thin
 * Monaco glue.
 *
 * **Two surfaces, mirroring the composition transforms (`spec-transform-actions`):**
 *   - the **CodeLens** (`installSpecTransformScaffoldCodeLens`, per editor — its
 *     commands need the editor handle) is the *discoverable* home: an `＋ Add
 *     transform` lens on a view with no pipeline, and per-step `＋ filter`/`＋
 *     aggregate`/… lenses on the array once it exists. Clicking splices the step in
 *     via Monaco's snippet engine, so the field-typed placeholders are Tab-through.
 *   - the **completion** (`configureSpecTransformScaffold`, global-once) is the
 *     type-to-filter *accelerator* on the same catalog, for when the cursor is
 *     already in a `transform[]` slot.
 *
 * Both are gated to the active draft — the published view is a read-only reference.
 * The scaffold is deliberately the one home the schema leaves bare: the schema already
 * completes the inline channel transforms (`bin`/`timeUnit` on an encoding field) and
 * the `transform` key itself, so this adds no second source there.
 */

import * as monaco from 'monaco-editor/esm/vs/editor/edcore.main';
import {
  DATA_TRANSFORMS,
  transformPlacementAt,
  transformSiteAt,
  type TransformSite,
} from '@core/spec-data-transforms';
import { appendEntryEdit, createArrayPropertyEdit } from '@core/spec-snippet';
import { boundColumnsAt } from './active-dataset';
import { installCursorLens } from './editor-cursor-lens';
import { insertSnippetAt, scaffoldSlotAt, scaffoldSuggestion } from './editor-snippet';
import { useSnippetStore } from '../stores/SnippetStore';

/** The transforms surfaced as CodeLens buttons — the common few; the completion has the rest. */
const COMMON_STEP_IDS = ['filter', 'aggregate', 'calculate', 'bin', 'timeUnit'] as const;

let registered = false;

/** Register the `transform[]` step-scaffold completion provider once. */
export function configureSpecTransformScaffold(): void {
  if (registered) return;
  registered = true;

  monaco.languages.registerCompletionItemProvider('json', {
    provideCompletionItems(model, position) {
      // Scaffolding only edits the draft; the published view is read-only.
      if (useSnippetStore.getState().editorView !== 'draft') return { suggestions: [] };

      const text = model.getValue();
      const offset = model.getOffsetAt(position);
      const placement = transformPlacementAt(text, offset);
      if (placement.kind !== 'step') return { suggestions: [] };

      const slot = scaffoldSlotAt(model, position, text, offset);
      const fields = boundColumnsAt(text, offset);
      const shared = placement.scope === 'shared';

      const suggestions = DATA_TRANSFORMS.map((t, i) =>
        scaffoldSuggestion(slot, i, {
          label: t.label,
          detail: t.detail,
          body: t.step(fields),
          // A shared step (on a composition parent) transforms the data feeding every
          // child view, not just one — surface that so the placement isn't a surprise.
          documentation: shared
            ? {
                value:
                  '_Applies to all views below — this `transform` sits on a composition parent._',
              }
            : undefined,
        }),
      );
      return { suggestions };
    },
  });
}

/**
 * Splice a field-typed step into the `transform` array at `arrayOffset`, via Monaco's
 * snippet engine so the placeholders are Tab-through. Re-resolves the array from the
 * live text (the lens args may be a version stale) and skips silently if it's gone.
 */
function runAddStep(
  editor: monaco.editor.IStandaloneCodeEditor,
  id: string,
  arrayOffset: number,
): void {
  const model = editor.getModel();
  if (!model) return;
  const text = model.getValue();
  const site = transformSiteAt(text, arrayOffset + 1);
  const transform = DATA_TRANSFORMS.find((t) => t.id === id);
  if (!site?.array || !transform) return;

  const fields = boundColumnsAt(text, site.array.offset);
  const edit = appendEntryEdit(text, site.array, transform.step(fields));
  insertSnippetAt(editor, edit.offset, edit.snippet);
}

/**
 * Insert an empty `transform: []` as the view's first property and drop the cursor
 * between its brackets (`$0`), so the per-step lenses appear next and the first step
 * fills the array in place. Placement, indentation, and the comma are
 * `createArrayPropertyEdit`'s tested math.
 */
function runAddTransform(editor: monaco.editor.IStandaloneCodeEditor, viewOffset: number): void {
  const model = editor.getModel();
  if (!model) return;
  const text = model.getValue();
  const site = transformSiteAt(text, viewOffset + 1);
  if (!site || site.array) return; // gone, or already has a pipeline

  const tab = model.getOptions().tabSize || 2;
  const edit = createArrayPropertyEdit(text, site.view.offset, tab, 'transform', '$0');
  insertSnippetAt(editor, edit.offset, edit.snippet);
}

/**
 * Install the cursor-aware transform-scaffold CodeLens (per editor — its commands
 * need this editor's handle to apply the edit, exactly like the composition CodeLens
 * in `spec-transform-actions`). Over the view the cursor sits in it shows `＋ Add
 * transform` when there is no pipeline, or `＋ filter`/`＋ aggregate`/… on the array
 * when there is. Returns a disposable; dispose on unmount.
 */
export function installSpecTransformScaffoldCodeLens(
  editor: monaco.editor.IStandaloneCodeEditor,
): monaco.IDisposable {
  const addTransformCmd = editor.addCommand(0, (_a, viewOffset: number) =>
    runAddTransform(editor, viewOffset),
  );
  const addStepCmd = editor.addCommand(0, (_a, id: string, arrayOffset: number) =>
    runAddStep(editor, id, arrayOffset),
  );

  return installCursorLens<TransformSite>(editor, {
    resolve: transformSiteAt,
    // Refresh when the enclosing view or its step count changes, not on every keystroke.
    keyOf: (site) => JSON.stringify([site.view.offset, site.array?.count ?? -1]),
    lensesOf: (site, model, lens) => {
      if (!site.array) {
        const line = model.getPositionAt(site.view.offset).lineNumber;
        return [lens(line, '$(add) Add transform', addTransformCmd, [site.view.offset])];
      }
      const line = model.getPositionAt(site.array.offset).lineNumber;
      return COMMON_STEP_IDS.map((id) =>
        lens(line, `$(add) ${id}`, addStepCmd, [id, site.array!.offset]),
      );
    },
  });
}
