import type { Operation } from '@api-client/json/patch/types.js'
import { JsonDocument } from '@api-client/json/patch/json_document.js'
import { nanoid } from '../nanoid.js'
import type { MediaPatchInfo, PatchInfo } from './types.js'

/**
 * Options for the PatchClient constructor.
 *
 * @typeParam T The type of the document being patched.
 */
export interface PatchClientOptions<T> {
  /**
   * The initial document.  A deep copy is made internally.
   */
  document: T
  /**
   * The name of the application creating the patches.
   */
  app: string
  /**
   * The version of the application creating the patches.
   */
  appVersion: string
  /**
   * When version is required for patching, it should be set with the constructor and
   * updated whenever the document changes.
   */
  version?: number
}

/**
 * A client for managing and applying JSON patches to a document.
 * This class simplifies the process of generating, applying, and comparing patches
 * using the `@api-client/json` library. It also provides a mechanism to notify
 * listeners when the document has been changed after applying a patch.
 *
 * @typeParam T The type of the document being patched.
 */
export class PatchClient<T extends object> extends JsonDocument<T> {
  /**
   * The application name generating the patch.  Used for tracking/debugging.
   */
  app: string
  /**
   * The application version generating the patch. Used for tracking/debugging.
   */
  appVersion: string

  /**
   * When version is required for patching, it should be set with the constructor and
   * updated whenever the document changes.
   */
  version?: number

  /**
   * Creates a new PatchClient instance.
   *
   * @param options Options for initializing the client.
   * @param options.document The initial document.  A deep copy is made internally.
   * @param options.app The name of the application creating the patches.
   * @param options.appVersion The version of the application creating the patches.
   */
  constructor(options: PatchClientOptions<T>) {
    super(options.document)
    this.app = options.app
    this.appVersion = options.appVersion
    this.version = options.version
  }

  /**
   * Generates a patch information object.
   *
   * @param operations The patch operations to include.
   * @returns A `IPatchInfo` object containing the patch data, timestamp, app name, app version, and a unique ID.
   */
  generatePatch(operations: Operation[]): PatchInfo | MediaPatchInfo {
    const result: PatchInfo | MediaPatchInfo = {
      timestamp: Date.now(),
      patch: operations,
      app: this.app,
      appVersion: this.appVersion,
      id: nanoid(),
    }
    if (this.version !== undefined) {
      const casted = result as MediaPatchInfo
      casted.version = this.version
    }
    return result
  }
}
