import { components } from '@shotstack/schemas';

declare type Clip = components["schemas"]["Clip"];

declare type ClipLocation = {
    trackIndex: number;
    clipIndex: number;
};

/**
 * Reference to a clip from the document (source of truth).
 * Contains original timing values like "auto", "end", and alias references.
 * Used in public SDK events so consumers see the document state.
 */
declare type ClipReference = ClipLocation & {
    clip: Clip;
};

declare type Destination = components["schemas"]["Destinations"];

export declare class Edit {
    private static readonly MAX_HISTORY_SIZE;
    private document;
    private backgroundColor;
    private tracks;
    playbackTime: number;
    totalDuration: number;
    isPlaying: boolean;
    private get clips();
    private internalEvents;
    events: ReadonlyEventEmitter<EditEventMap>;
    private canvas;
    private timingManager;
    private lumaMaskController;
    private playerReconciler;
    private outputSettings;
    private selectionManager;
    private commandHistory;
    private commandIndex;
    private commandQueue;
    private clipsToDispose;
    private clipErrors;
    private playerByClipId;
    private lumaContentRelations;
    private fontMetadata;
    private isBatchingEvents;
    private isExporting;
    private lastResolved;
    /**
     * Create an Edit instance from a template configuration.
     */
    constructor(template: Edit_2);
    /**
     * Load the edit session.
     */
    load(): Promise<void>;
    /**
     * Initialize runtime from the document.
     */
    private initializeFromDocument;
    play(): void;
    pause(): void;
    seek(target: number): void;
    stop(): void;
    /**
     * Reload the edit with a new configuration (hot-reload).
     */
    loadEdit(edit: Edit_2): Promise<void>;
    private loadSoundtrack;
    getEdit(): Edit_2;
    addClip(trackIdx: number, clip: Clip): void | Promise<void>;
    getClip(trackIdx: number, clipIdx: number): Clip | null;
    /**
     * Clear the error for a deleted clip and shift indices for remaining errors.
     */
    private clearClipErrorAndShift;
    deleteClip(trackIdx: number, clipIdx: number): Promise<void>;
    addTrack(trackIdx: number, track: Track): Promise<void>;
    getTrack(trackIdx: number): Track | null;
    deleteTrack(trackIdx: number): void;
    undo(): Promise<void>;
    redo(): Promise<void>;
    /**
     * Add a command to history without executing it.
     */
    private addCommandToHistory;
    updateClip(trackIdx: number, clipIdx: number, updates: Partial<Clip>): Promise<void>;
    /**
     * Handle command result - only add to history if successful.
     */
    private handleCommandResult;
    /**
     * Detects merge field placeholders in the raw edit before substitution.
     */
    private detectMergeFieldBindings;
    /**
     * Recursively walks an object to find merge field placeholders.
     */
    private detectBindingsInObject;
    /**
     * Checks if edit has structural changes requiring full reload.
     *
     * TODO: Expand granular path to handle more cases:
     * - Clip add/remove: Use existing addClip()/deleteClip() commands
     * - Soundtrack changes: Add/remove AudioPlayer via commands
     * - Font changes: Load new fonts incrementally
     * - Merge field changes: Re-resolve affected clips
     */
    private hasStructuralChanges;
    /**
     * Transfers existing clip IDs from the current document to the new edit configuration.
     */
    private preserveClipIdsForGranularUpdate;
    /**
     * Applies granular changes without full reload.
     * @param newEdit - The new edit configuration
     * @param oldTracks - The old tracks (captured before document update)
     * @param oldOutput - The old output settings (captured before document update)
     */
    private applyGranularChanges;
    private queueDisposeClip;
    /**
     * Remove fonts from timeline.fonts that are no longer referenced by any clip.
     */
    private cleanupUnusedFonts;
    /**
     * Extract the filename (without extension) from a font URL.
     */
    private extractFilenameFromUrl;
    private disposeClip;
    private unloadClipAssets;
    private movePlayerToTrackContainer;
    private addPlayer;
    setOutputSize(width: number, height: number): Promise<void>;
    setOutputFps(fps: number): Promise<void>;
    getOutputFps(): number;
    setOutputFormat(format: string): Promise<void>;
    getOutputFormat(): string;
    setOutputDestinations(destinations: Destination[]): Promise<void>;
    getOutputDestinations(): Destination[];
    setOutputResolution(resolution: string): Promise<void>;
    getOutputResolution(): string | undefined;
    setOutputAspectRatio(aspectRatio: string): Promise<void>;
    getOutputAspectRatio(): string | undefined;
    setTimelineBackground(color: string): Promise<void>;
    private setTimelineBackgroundInternal;
    getTimelineBackground(): string;
    /**
     * Find the content clip that best matches a luma (by temporal overlap).
     */
    private findBestContentMatch;
    private setupIntentListeners;
}

declare type Edit_2 = components["schemas"]["Edit"];

declare const EditEvent: {
    readonly PlaybackPlay: "playback:play";
    readonly PlaybackPause: "playback:pause";
    readonly TimelineUpdated: "timeline:updated";
    readonly TimelineBackgroundChanged: "timeline:backgroundChanged";
    readonly ClipAdded: "clip:added";
    readonly ClipSelected: "clip:selected";
    readonly ClipUpdated: "clip:updated";
    readonly ClipDeleted: "clip:deleted";
    readonly ClipRestored: "clip:restored";
    readonly ClipCopied: "clip:copied";
    readonly ClipLoadFailed: "clip:loadFailed";
    readonly ClipUnresolved: "clip:unresolved";
    readonly SelectionCleared: "selection:cleared";
    readonly EditChanged: "edit:changed";
    readonly EditUndo: "edit:undo";
    readonly EditRedo: "edit:redo";
    readonly TrackAdded: "track:added";
    readonly TrackRemoved: "track:removed";
    readonly DurationChanged: "duration:changed";
    readonly OutputResized: "output:resized";
    readonly OutputResolutionChanged: "output:resolutionChanged";
    readonly OutputAspectRatioChanged: "output:aspectRatioChanged";
    readonly OutputFpsChanged: "output:fpsChanged";
    readonly OutputFormatChanged: "output:formatChanged";
    readonly OutputDestinationsChanged: "output:destinationsChanged";
    readonly MergeFieldChanged: "mergefield:changed";
    readonly TimelineResized: "timeline:resized";
};

declare type EditEventMap = {
    [EditEvent.PlaybackPlay]: void;
    [EditEvent.PlaybackPause]: void;
    /** Contains the document (source of truth) with original timing values like "auto", "end" */
    [EditEvent.TimelineUpdated]: {
        current: Edit_2;
    };
    [EditEvent.TimelineBackgroundChanged]: {
        color: string;
    };
    [EditEvent.ClipAdded]: ClipLocation;
    [EditEvent.ClipSelected]: ClipReference;
    [EditEvent.ClipUpdated]: {
        previous: ClipReference;
        current: ClipReference;
    };
    [EditEvent.ClipDeleted]: ClipLocation;
    [EditEvent.ClipRestored]: ClipLocation;
    [EditEvent.ClipCopied]: ClipLocation;
    [EditEvent.ClipLoadFailed]: ClipLocation & {
        error: string;
        assetType: string;
    };
    [EditEvent.ClipUnresolved]: ClipLocation & {
        assetType: string;
        clipId: string;
    };
    [EditEvent.SelectionCleared]: void;
    [EditEvent.EditChanged]: {
        source: string;
        timestamp: number;
    };
    [EditEvent.EditUndo]: {
        command: string;
    };
    [EditEvent.EditRedo]: {
        command: string;
    };
    [EditEvent.TrackAdded]: {
        trackIndex: number;
        totalTracks: number;
    };
    [EditEvent.TrackRemoved]: {
        trackIndex: number;
    };
    [EditEvent.DurationChanged]: {
        duration: number;
    };
    [EditEvent.OutputResized]: {
        width: number;
        height: number;
    };
    [EditEvent.OutputResolutionChanged]: {
        resolution: Output["resolution"];
    };
    [EditEvent.OutputAspectRatioChanged]: {
        aspectRatio: Output["aspectRatio"];
    };
    [EditEvent.OutputFpsChanged]: {
        fps: number;
    };
    [EditEvent.OutputFormatChanged]: {
        format: Output["format"];
    };
    [EditEvent.OutputDestinationsChanged]: {
        destinations: Destination[];
    };
    [EditEvent.MergeFieldChanged]: {
        fields: MergeField[];
    };
    [EditEvent.TimelineResized]: {
        height: number;
    };
};

declare class EventEmitter<TEventPayloadMap extends EventPayloadMap = EventPayloadMap> {
    private readonly events;
    constructor();
    on<TEventName extends keyof TEventPayloadMap>(name: TEventName, listener: Listener<TEventPayloadMap[TEventName]>): () => void;
    once<TEventName extends keyof TEventPayloadMap>(name: TEventName, listener: Listener<TEventPayloadMap[TEventName]>): () => void;
    off<TEventName extends keyof TEventPayloadMap>(name: TEventName, listener: Listener<TEventPayloadMap[TEventName]>): void;
}

declare type EventPayloadMap<TPayload = any> = Record<string, TPayload>;

declare type Listener<TPayload = any> = (payload: TPayload) => void;

/**
 * Merge field types for the Shotstack Studio SDK.
 *
 * Merge fields allow dynamic content substitution using {{ FIELD_NAME }} syntax.
 * Values are replaced at render time, enabling template-based video generation.
 */
/**
 * A merge field definition used throughout the SDK.
 */
export declare interface MergeField {
    /** Field identifier (uppercase convention: MY_FIELD) */
    name: string;
    /** Default value used for preview when no runtime value is provided */
    defaultValue: string;
    /** Optional description for UI display */
    description?: string;
}

export declare class MergeFieldService {
    private fields;
    private events;
    constructor(events: EventEmitter<EditEventMap>);
    /**
     * Register or update a merge field.
     * @param field The merge field to register
     * @param options.silent If true, suppresses event emission (for command-based operations)
     */
    register(field: MergeField, options?: {
        silent?: boolean;
    }): void;
    /**
     * Remove a merge field by name.
     * @param name The field name to remove
     * @param options.silent If true, suppresses event emission (for command-based operations)
     */
    remove(name: string, options?: {
        silent?: boolean;
    }): boolean;
    /** Get a merge field by name */
    get(name: string): MergeField | undefined;
    /** Get all registered merge fields */
    getAll(): MergeField[];
    /** Clear all merge fields */
    clear(): void;
    /**
     * Apply merge field substitutions to a string.
     * Replaces {{ FIELD_NAME }} patterns with their default values.
     */
    resolve(input: string): string;
    /**
     * Resolve a merge field template to a numeric value.
     * Returns the number if successful, or null if:
     * - Input is not a merge field template
     * - Resolved value is not a valid number
     */
    resolveToNumber(input: string): number | null;
    /**
     * Check if a string contains unresolved merge fields.
     * Returns true if any {{ FIELD_NAME }} patterns remain after resolution.
     */
    hasUnresolved(input: string): boolean;
    /**
     * Extract the first merge field name from a string.
     * Returns null if no merge field pattern is found.
     */
    extractFieldName(input: string): string | null;
    /** Check if a string is a merge field template (contains {{ FIELD }}) */
    isMergeFieldTemplate(input: string): boolean;
    /** Create a merge field template string from a field name */
    createTemplate(fieldName: string): string;
    /** Export fields in Shotstack API format ({ find, replace }) */
    toSerializedArray(): SerializedMergeField[];
    /** Import fields from Shotstack API format (does not emit event - called during loadEdit) */
    loadFromSerialized(fields: SerializedMergeField[]): void;
    /** Generate a unique field name with a given prefix (e.g., MEDIA_1, MEDIA_2) */
    generateUniqueName(prefix: string): string;
}

declare type Output = components["schemas"]["Output"];

/**
 * Read-only view of an EventEmitter that only exposes subscription methods.
 * Used as the public `events` type on Edit to prevent consumers from emitting events.
 */
declare interface ReadonlyEventEmitter<TEventPayloadMap extends EventPayloadMap> {
    on<K extends keyof TEventPayloadMap>(name: K, listener: Listener<TEventPayloadMap[K]>): () => void;
    once<K extends keyof TEventPayloadMap>(name: K, listener: Listener<TEventPayloadMap[K]>): () => void;
    off<K extends keyof TEventPayloadMap>(name: K, listener: Listener<TEventPayloadMap[K]>): void;
}

/**
 * Serialized format for JSON export (matches Shotstack API).
 * The replace value can be any type - strings, numbers, booleans, objects.
 */
declare interface SerializedMergeField {
    find: string;
    replace: unknown;
}

/**
 * Extended Edit with Shotstack-specific capabilities.
 *
 * This class is for Shotstack products only.
 * External SDK consumers should use the base `Edit` class.
 */
export declare class ShotstackEdit extends Edit {
    private isUpdatingMergeFields;
    /**
     * Merge field service for managing dynamic content placeholders.
     * Use this to register, update, and query merge fields in templates.
     */
    get mergeFields(): MergeFieldService;
    /**
     * Apply a merge field to a clip property.
     */
    applyMergeField(clipId: string, propertyPath: string, fieldName: string, value: string, originalValue?: string): Promise<void>;
    /**
     * Remove a merge field from a clip property, restoring the original value.
     */
    removeMergeField(clipId: string, propertyPath: string, restoreValue: string): Promise<void>;
    /**
     * Get the merge field name for a clip property, if any.
     */
    getMergeFieldForProperty(clipId: string, propertyPath: string): string | null;
    /**
     * Update the placeholder value of a merge field.
     */
    updateMergeFieldValueLive(fieldName: string, newValue: string): void;
    /**
     * Check if a merge field is used for asset.src in any clip.
     * Used by UI to determine if URL validation should be applied.
     */
    isSrcMergeField(fieldName: string): boolean;
    /**
     * Check if a value is type-compatible with all properties a merge field is bound to.
     * Temporarily swaps the field value, resolves each bound clip, and validates
     * against ClipSchema — the same Zod schema used at load time.
     */
    validateMergeFieldValue(fieldName: string, value: string): string | null;
    /**
     * Check if a value is compatible with a specific clip property via Zod schema validation.
     * Used by the merge field label manager to filter compatible fields in dropdowns.
     */
    isValueCompatibleWithClipProperty(clipId: string, propertyPath: string, value: string): boolean;
    /** Check if any binding in a clip references the given field name. */
    private clipUsesField;
    /**
     * Remove a merge field globally from all clips and the registry.
     */
    deleteMergeFieldGlobally(fieldName: string): Promise<void>;
    /**
     * Convert all text assets and log the resulting template JSON to console.
     */
    convertAllTextAssets(): Promise<{
        richText: number;
        svg: number;
    }>;
    /**
     * Map TextAsset vertical alignment to RichTextAsset vertical alignment.
     * TextAsset uses "center", RichTextAsset uses "middle".
     */
    private mapVerticalAlign;
    /**
     * Convert a text clip to rich-text format (pure JSON transformation).
     */
    private convertTextClipToRichText;
    /**
     * Helper: Update merge field bindings and document clip data for a player.
     * Must update both bindings and clip data because the resolver reads clip data directly.
     */
    private updateMergeFieldBindings;
    /** Helper: Check if and how a clip uses a specific merge field */
    private getMergeFieldUsage;
    /**
     * Helper: Find and restore merge field occurrences in a clip
     */
    private restoreMergeFieldInClip;
}

declare type Track = components["schemas"]["Track"];

export { }
