/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

// --- Options ---

/** Known option keys accepted by BlissSVGBuilder (kebab-case). */
export interface BlissOptions {
  // Stroke and spacing
  'stroke-width'?: number;
  'dot-extra-width'?: number;
  'char-space'?: number;
  'word-space'?: number;
  'external-glyph-space'?: number;

  // Margins (builder-level)
  margin?: number;
  'margin-top'?: number;
  'margin-bottom'?: number;
  'margin-left'?: number;
  'margin-right'?: number;

  // Sizing
  'min-width'?: number;
  center?: boolean;

  // Cropping (builder-level)
  crop?: number | 'auto' | 'auto-vertical' | 'compact';
  'crop-top'?: number | 'auto';
  'crop-bottom'?: number | 'auto';
  'crop-left'?: number | 'auto';
  'crop-right'?: number | 'auto';

  // Grid (builder-level)
  grid?: boolean;
  'grid-color'?: string;
  'grid-major-color'?: string;
  'grid-medium-color'?: string;
  'grid-minor-color'?: string;
  'grid-sky-color'?: string;
  'grid-earth-color'?: string;
  'grid-stroke-width'?: number;
  'grid-major-stroke-width'?: number;
  'grid-medium-stroke-width'?: number;
  'grid-minor-stroke-width'?: number;
  'grid-sky-stroke-width'?: number;
  'grid-earth-stroke-width'?: number;

  // Colors and background
  color?: string;
  background?: string;
  'background-top'?: string;
  'background-mid'?: string;
  'background-bottom'?: string;

  // Text and metadata
  text?: string;
  'svg-desc'?: string;
  'svg-title'?: string;
  'svg-height'?: number;

  // Error handling
  'error-placeholder'?: boolean;

  // SVG pass-through attributes (any key not in the known set)
  [key: string]: string | number | undefined;
}

/** Cascading option layers: defaults (lowest priority) and overrides (highest priority). */
export interface OptionLayers {
  defaults?: BlissOptions;
  overrides?: BlissOptions;
}

// --- Element snapshots ---

/** Bounding box of an element in absolute SVG coordinates. */
export interface ElementBounds {
  readonly minX: number;
  readonly maxX: number;
  readonly minY: number;
  readonly maxY: number;
  readonly width: number;
  readonly height: number;
}

/** A frozen, read-only snapshot of an element in the composition tree. */
export interface ElementSnapshot {
  readonly key: string;
  /**
   * The input code that produces this element (e.g. `'B431'`, `'Xa'`, `'H'`).
   * At part level, the structural lookup key the user would write (`'B81'`,
   * `'H'`, `'Xa'`, `'TSP'`, `'Xα'`, `'Xhαllo'`). At glyph level, the input
   * code only when the glyph is actually a glyph: B-codes (`'B431'`), single
   * X-codes (`'Xa'`, `'Xα'`), or `define()`d `type:'glyph'` aliases
   * (`'LOVE'`); `''` for composites, bare shape primitives, and
   * multi-character text fallback. Always `''` at group level. Note: this is
   * the live identity. `toString()` and `toJSON()` decompose alias names by
   * default; pass `{ preserve: true }` to keep them in serialized output.
   */
  readonly codeName: string;
  /**
   * The rendered Unicode character for an external glyph (e.g. `'a'` for
   * `Xa`, `'α'` for `Xα`). `''` for B-codes, composites, shape primitives,
   * multi-character text fallback, and non-glyph levels.
   */
  readonly char: string;
  readonly x: number;
  readonly y: number;
  readonly offsetX: number;
  readonly offsetY: number;
  readonly width: number;
  readonly height: number;
  readonly advanceX: number;
  readonly baseWidth: number;
  readonly level: number;
  readonly isRoot: boolean;
  readonly isGroup: boolean;
  readonly isGlyph: boolean;
  readonly isPart: boolean;
  readonly bounds: ElementBounds;
  readonly isIndicator: boolean;
  readonly isShape: boolean;
  readonly isBlissGlyph: boolean;
  readonly isExternalGlyph: boolean;
  readonly isHeadGlyph: boolean;
  readonly isSpaceGroup: boolean;
  readonly index: number;
  readonly parentKey: string | null;
  readonly children: readonly ElementSnapshot[];
}

// --- Element handle (live mutation API) ---

/**
 * A live handle referencing a node in the raw composition object.
 * Returned by `getElementByKey()`, `group()`, `glyph()`, and `part()`.
 * Mutations through a handle trigger a rebuild of the composition.
 */
export declare class ElementHandle {
  /** Structural depth: 1 = group, 2 = glyph, 3+ = part. */
  readonly level: number;

  /** True when level === 1 (a word group). */
  readonly isGroup: boolean;

  /** True when level === 2 (a Bliss character). */
  readonly isGlyph: boolean;

  /** True when level >= 3 (a part within a character). */
  readonly isPart: boolean;

  /**
   * The input code that produces this element (e.g. `'B431'`, `'Xa'`, `'H'`).
   * At part level, the structural lookup key the user would write (`'B81'`,
   * `'H'`, `'Xa'`, `'TSP'`, `'Xα'`, `'Xhαllo'`). At glyph level, the input
   * code only when the glyph is actually a glyph: B-codes (`'B431'`), single
   * X-codes (`'Xa'`, `'Xα'`), or `define()`d `type:'glyph'` aliases
   * (`'LOVE'`); `''` for composites, bare shape primitives, and
   * multi-character text fallback. Always `''` at group level. Note: this is
   * the live identity. `toString()` and `toJSON()` decompose alias names by
   * default; pass `{ preserve: true }` to keep them in serialized output.
   */
  readonly codeName: string;

  /**
   * The rendered Unicode character for an external glyph (e.g. `'a'` for
   * `Xa`, `'α'` for `Xα`). `''` for B-codes, composites, shape primitives,
   * multi-character text fallback, and non-glyph levels.
   */
  readonly char: string;

  /** Stable across mutations. Use with `getElementByKey(key)` to recover a handle to this same node later. */
  readonly key: string;

  /** Whether this part is an indicator. Only true on part-level handles. */
  readonly isIndicator: boolean;

  /** Whether this part is a shape primitive. */
  readonly isShape: boolean;

  /** Whether this glyph is a B-code Bliss character. */
  readonly isBlissGlyph: boolean;

  /** Whether this glyph is an external font character. */
  readonly isExternalGlyph: boolean;

  /** Whether this glyph is the head of its word group. */
  readonly isHeadGlyph: boolean;

  /** Whether this group is a space separator (TSP/QSP). */
  readonly isSpaceGroup: boolean;

  // --- Dimensions (read-only, from snapshot) ---

  /** Absolute x position of this element's origin. */
  readonly x: number;
  /** Absolute y position of this element's origin. */
  readonly y: number;
  /** Position offset relative to the parent. */
  readonly offsetX: number;
  /** Position offset relative to the parent. */
  readonly offsetY: number;
  /** Total width including indicator overhang. */
  readonly width: number;
  /** Total height. */
  readonly height: number;
  /** Absolute bounding box. */
  readonly bounds: ElementBounds;
  /** Horizontal spacing step to next sibling. */
  readonly advanceX: number;
  /** Width excluding indicators. Equals width when no indicators present. */
  readonly baseWidth: number;

  /** Returns all dimension properties at once. */
  measure(): {
    x: number;
    y: number;
    offsetX: number;
    offsetY: number;
    width: number;
    height: number;
    bounds: ElementBounds;
    advanceX: number;
    baseWidth: number;
  };

  // --- Navigation ---

  /** Returns the head glyph handle within this group. Only valid on group handles. */
  headGlyph(): ElementHandle | null;

  /** Returns the glyph at the given index within this group. Negative indices count from the end (-1 = last). Only valid on group handles. */
  glyph(index: number): ElementHandle | null;

  /**
   * Returns the part at the given index. Negative indices count from the end (-1 = last).
   * Valid on glyph handles (returns a part of the glyph) and
   * part handles (returns a nested sub-part).
   */
  part(index: number): ElementHandle | null;

  // --- Mutation: add/insert ---

  /** Appends a glyph to this group. Only valid on group handles. */
  addGlyph(code: string, opts?: BlissOptions | OptionLayers): this;

  /** Inserts a glyph at the given index in this group. Only valid on group handles. */
  insertGlyph(index: number, code: string, opts?: BlissOptions | OptionLayers): this;

  /** Appends a part to this glyph. On group handles, delegates to the last glyph. */
  addPart(code: string, opts?: BlissOptions | OptionLayers): this;

  /** Inserts a part at the given index in this glyph. Only valid on glyph handles. */
  insertPart(index: number, code: string, opts?: BlissOptions | OptionLayers): this;

  // --- Mutation: remove/replace (self) ---

  /** Removes this element. Cascades: removing the last part removes its glyph, etc. */
  remove(): undefined;

  /** Disconnects this element from its parent without cascade cleanup. May leave empty containers. */
  detach(): undefined;

  /** Replaces this element with a new one. Valid on glyph and part handles. */
  replace(code: string, opts?: BlissOptions | OptionLayers): this;

  // --- Mutation: remove/replace (parent-centric, by index) ---

  /** Removes the glyph at the given index in this group. Only valid on group handles. */
  removeGlyph(index: number): this;

  /** Replaces the glyph at the given index in this group. Only valid on group handles. */
  replaceGlyph(index: number, code: string, opts?: BlissOptions | OptionLayers): this;

  /** Removes the part at the given index in this glyph. Only valid on glyph handles. */
  removePart(index: number): this;

  /** Replaces the part at the given index in this glyph. Only valid on glyph handles. */
  replacePart(index: number, code: string, opts?: BlissOptions | OptionLayers): this;

  // --- Mutation: indicators ---

  /** Replaces all indicators on this glyph with the given indicator codes. Preserves semantic indicators by default. Only valid on glyph handles. */
  applyIndicators(code: string, opts?: { stripSemantic?: boolean }): this;

  /** Removes all grammatical indicators from this glyph. Preserves semantic indicators by default. Only valid on glyph handles. */
  clearIndicators(opts?: { stripSemantic?: boolean }): this;

  /** Applies indicators to the head glyph of this group. Preserves semantic indicators by default. Only valid on group handles. */
  applyHeadIndicators(code: string, opts?: { stripSemantic?: boolean }): this;

  /** Removes grammatical indicators from the head glyph of this group. Preserves semantic indicators by default. Only valid on group handles. */
  clearHeadIndicators(opts?: { stripSemantic?: boolean }): this;

  // --- Mutation: space/word structure ---

  /** Splits this word group into two at the glyph boundary, inserting a space between. Only valid on group handles. */
  splitAt(glyphIndex: number): this;

  /** Merges this word group with the next one, removing spaces between them. Only valid on group handles. */
  mergeWithNext(): this;

  // --- Mutation: options ---

  /** Sets or merges options on this element. Accepts flat options (treated as overrides) or { defaults, overrides }. */
  setOptions(opts: BlissOptions | OptionLayers): this;

  /** Removes specific option keys from this element. */
  removeOptions(...keys: string[]): this;
}

// --- Definition types ---

/** Context object passed to custom `getPath` functions. */
export interface ShapeContext {
  [key: string]: any;
}

/** Definition for a custom glyph (composed from existing codes). */
export interface GlyphDefinition {
  type?: 'glyph';
  codeString: string;
  isIndicator?: boolean;
  anchorOffsetX?: number;
  anchorOffsetY?: number;
  width?: number;
  shrinksPrecedingWordSpace?: boolean;
  kerningRules?: Record<string, any>;
  defaultOptions?: BlissOptions;
}

/** Definition for a custom shape (rendered via getPath or codeString). */
export interface ShapeDefinition {
  type?: 'shape';
  getPath?: (ctx: ShapeContext) => string;
  codeString?: string;
  width?: number;
  height?: number;
  x?: number;
  y?: number;
  extraPathOptions?: Record<string, any>;
  defaultOptions?: BlissOptions;
}

/** Definition for an external glyph (custom rendering around a Unicode character). */
export interface ExternalGlyphDefinition {
  type: 'externalGlyph';
  getPath: (ctx: ShapeContext) => string;
  width: number;
  /** The rendered Unicode character (e.g. `'a'` for the external glyph registered as `'Xa'`). */
  char: string;
  y?: number;
  height?: number;
  kerningRules?: Record<string, any>;
  defaultOptions?: BlissOptions;
}

/** Bare alias definition (maps a code name to a code string). */
export interface BareDefinition {
  codeString: string;
  defaultOptions?: BlissOptions;
}

/** Union of all definition types accepted by `BlissSVGBuilder.define()`. */
export type CodeDefinition =
  | GlyphDefinition
  | ShapeDefinition
  | ExternalGlyphDefinition
  | BareDefinition;

/** Definition type identifiers. */
export type DefinitionType = 'shape' | 'glyph' | 'externalGlyph' | 'bare' | 'space';

/** Result returned by `BlissSVGBuilder.define()`. */
export interface DefineResult {
  defined: string[];
  skipped: string[];
  errors: string[];
}

/** Frozen metadata returned by `BlissSVGBuilder.getDefinition()`. */
export interface DefinitionMetadata {
  readonly type: DefinitionType;
  readonly isBuiltIn: boolean;
  readonly [key: string]: any;
}

// --- Serialization output ---

/** Normalized parsed structure returned by `toJSON()`. */
export interface BlissJSON {
  options?: Record<string, string>;
  groups: Array<{
    options?: Record<string, string>;
    glyphs?: Array<{
      codeName?: string;
      options?: Record<string, string>;
      isHeadGlyph?: boolean;
      parts?: Array<{
        codeName: string;
        options?: Record<string, string>;
        x?: number;
        y?: number;
        parts?: Array<any>;
      }>;
    }>;
  }>;
}

// --- Warnings ---

/** A warning generated when the builder encounters an unknown or invalid code. */
export interface Warning {
  /** Warning type identifier (e.g., 'UNKNOWN_CODE'). */
  readonly code: string;
  /** Human-readable description of the issue. */
  readonly message: string;
  /** The problematic DSL code that triggered the warning. */
  readonly source: string;
}

// --- Builder stats ---

export interface BuilderStats {
  groupCount: number;
  glyphCount: number;
}

// --- Main class ---

export declare class BlissSVGBuilder {
  /**
   * Creates an instance of BlissSVGBuilder.
   * @param input - A DSL string, a plain object from `toJSON()`, or omitted for an empty builder
   * @param options - Defaults/overrides to merge, or flat options treated as overrides
   */
  constructor(input?: string | BlissJSON, options?: BlissOptions | OptionLayers);

  /**
   * Library version string (set at build time).
   * Also exported as the named `LIB_VERSION` constant.
   */
  static readonly LIB_VERSION: string;

  // --- SVG output (getters) ---

  /** SVG content (path elements and groups) without the outer `<svg>` wrapper. */
  readonly svgContent: string;

  /** Parsed SVG as a DOM element. Requires a DOM environment. */
  readonly svgElement: SVGSVGElement;

  /** Complete SVG markup without XML declaration. */
  readonly svgCode: string;

  /** Complete SVG markup with XML declaration. */
  readonly standaloneSvg: string;

  // --- Warnings ---

  /** Warnings generated during parsing/rendering (unknown codes, invalid syntax, etc.). */
  readonly warnings: readonly Warning[];

  // --- Element tree (getters) ---

  /** Root element snapshot (frozen tree of all elements). */
  readonly elements: ElementSnapshot;

  /** Non-space group snapshots. */
  readonly groups: readonly ElementSnapshot[];

  /** Group and glyph counts. */
  readonly stats: BuilderStats;

  // --- Traversal and querying ---

  /** Depth-first traversal of all element snapshots. Return `false` to stop early. */
  traverse(callback: (el: ElementSnapshot) => boolean | void): void;

  /** Returns all element snapshots matching the predicate. */
  query(predicate: (el: ElementSnapshot) => boolean): ElementSnapshot[];

  /** Looks up an element handle by its snapshot key. */
  getElementByKey(key: string): ElementHandle | null;

  /** Returns a handle to the non-space group at the given index. Negative indices count from the end (-1 = last). */
  group(index: number): ElementHandle | null;

  /** Returns a handle to any group (including spaces) at the given raw index. Negative indices count from the end (-1 = last). */
  element(index: number): ElementHandle | null;

  /** Total number of raw groups (including space groups). */
  readonly elementCount: number;

  /** Returns a handle to the glyph at the given flat index across all groups. Negative indices count from the end (-1 = last). */
  glyph(flatIndex: number): ElementHandle | null;

  /** Returns a handle to the part at the given flat index across all glyphs. Negative indices count from the end (-1 = last). */
  part(flatIndex: number): ElementHandle | null;

  /** Returns the root element snapshot (alias for `elements`). */
  snapshot(): ElementSnapshot;

  // --- Building and manipulation ---

  /** Appends a new glyph group with automatic space management. */
  addGroup(code: string, opts?: BlissOptions | OptionLayers): this;

  /** Appends a glyph to the last non-space group (creates one if empty). */
  addGlyph(code: string, opts?: BlissOptions | OptionLayers): this;

  /** Appends a part to the last glyph of the last group. */
  addPart(code: string, opts?: BlissOptions | OptionLayers): this;

  /** Inserts a group at the given index. Negative indices count from the end. */
  insertGroup(index: number, code: string, opts?: BlissOptions | OptionLayers): this;

  /** Removes the group at the given index. Negative indices count from the end. */
  removeGroup(index: number): this;

  /** Replaces the group at the given index with new content. Negative indices count from the end. */
  replaceGroup(index: number, code: string, opts?: BlissOptions | OptionLayers): this;

  /** Merges another builder's content into this one. Appends the other builder's groups with a space between. The other builder's global options are discarded. */
  merge(other: BlissSVGBuilder): this;

  /** Splits this builder at the given group index. This builder keeps the left half; a new builder with the right half is returned. Both share the same global options. */
  splitAt(groupIndex: number): BlissSVGBuilder;

  /** Appends a raw group with no automatic space management. SP auto-resolves to TSP/QSP. */
  addElement(code: string, opts?: BlissOptions | OptionLayers): this;

  /** Inserts a raw group at the given index with no automatic space management. SP auto-resolves. */
  insertElement(index: number, code: string, opts?: BlissOptions | OptionLayers): this;

  /** Removes the raw group at the given index (plain splice, no space cleanup). */
  removeElement(index: number): this;

  /** Replaces the raw group at the given index with new content. */
  replaceElement(index: number, code: string, opts?: BlissOptions | OptionLayers): this;

  /** Removes all content from the builder. */
  clear(): this;

  // --- Serialization ---

  /**
   * Returns a portable DSL string. Custom codes are decomposed to built-in
   * codes by default; pass `{ preserve: true }` to keep custom names.
   */
  toString(options?: { preserve?: boolean }): string;

  /**
   * Returns a normalized parsed structure (plain object). Custom glyph codes
   * are resolved to built-in codes by default; pass `{ preserve: true }` to keep them.
   */
  toJSON(options?: { preserve?: boolean; deep?: boolean }): BlissJSON;

  // --- Static: definition management ---

  /**
   * Defines one or more custom codes (glyphs, shapes, external glyphs, or bare aliases).
   * @param definitions - Map of code names to their definitions
   * @param options - Pass `{ overwrite: true }` to replace existing custom definitions
   */
  static define(
    definitions: Record<string, CodeDefinition>,
    options?: { overwrite?: boolean }
  ): DefineResult;

  /** Returns `true` if a code is defined (built-in or custom). */
  static isDefined(code: string): boolean;

  /** Returns frozen metadata for a code, or `null` if not found. */
  static getDefinition(code: string): DefinitionMetadata | null;

  /** Lists all defined codes, optionally filtered by type. */
  static listDefinitions(filter?: { type?: DefinitionType }): string[];

  /** Removes a custom definition. Throws if the code is built-in. */
  static removeDefinition(code: string): boolean;

  /**
   * Patches properties on an existing custom definition.
   * Only keys valid for the definition's type are accepted.
   * Built-in definitions cannot be patched.
   */
  static patchDefinition(code: string, changes: Partial<CodeDefinition>): { patched: true };
}

/**
 * Library version string (set at build time).
 * Also accessible as the `BlissSVGBuilder.LIB_VERSION` static.
 */
export declare const LIB_VERSION: string;

export default BlissSVGBuilder;
