import type { VectorClock } from './vector-clock';

export type { VectorClock } from './vector-clock';

export enum OpType {
  Create = 'CRT',
  Update = 'UPD',
  Delete = 'DEL',
  Move = 'MOV', // For list reordering
  Batch = 'BATCH', // For bulk operations (import, mass update)
  /**
   * @deprecated Super Productivity host-specific full-state op string baked
   * into sync-core for backward compatibility. New host integrations should
   * declare their full-state op strings as plain string literals and
   * register them via {@link createFullStateOpTypeHelpers}. This member
   * will be removed from `OpType` once the SP migration completes.
   */
  SyncImport = 'SYNC_IMPORT',
  /**
   * @deprecated Super Productivity host-specific full-state op string baked
   * into sync-core for backward compatibility. New host integrations should
   * declare their full-state op strings as plain string literals and
   * register them via {@link createFullStateOpTypeHelpers}. This member
   * will be removed from `OpType` once the SP migration completes.
   */
  BackupImport = 'BACKUP_IMPORT',
  /**
   * @deprecated Super Productivity host-specific full-state op string baked
   * into sync-core for backward compatibility. New host integrations should
   * declare their full-state op strings as plain string literals and
   * register them via {@link createFullStateOpTypeHelpers}. This member
   * will be removed from `OpType` once the SP migration completes.
   */
  Repair = 'REPAIR',
}

export interface Operation<TOpType extends string = OpType> {
  /**
   * Unique identifier for the operation.
   * Should be a UUID v7 (time-ordered) to allow for rough chronological sorting
   * even without vector clocks, which aids in debugging and indexing.
   */
  id: string;

  // ACTION MAPPING
  /**
   * The action type string this operation replays (e.g., '[Task] Update').
   * Carried as an opaque string here; the host app's action-type enum is the
   * source of truth for valid values.
   */
  actionType: string;

  /**
   * High-level operation category (Create, Update, Delete, etc.).
   * Used for broad logic handling like persistence strategies or conflict resolution rules.
   */
  opType: TOpType;

  // SCOPE
  /**
   * The type of the data model entity being modified.
   * Carried as an opaque string here; the host app supplies the valid set.
   */
  entityType: string;

  /**
   * The specific ID of the entity being modified.
   * Use '*' for singleton entities like global config.
   */
  entityId?: string;

  /**
   * List of entity IDs for batch operations that affect multiple items simultaneously.
   */
  entityIds?: string[];

  // DATA
  /**
   * The actual data change associated with the operation.
   * - For 'CRT', this is the full object.
   * - For 'UPD', this is a partial object (changeset).
   * - For 'DEL', this might be empty or a tombstone.
   */
  payload: unknown;

  // CAUSALITY & ORDERING
  /**
   * The ID of the device/client that generated this operation.
   * Essential for vector clock management and identifying the source of changes.
   */
  clientId: string;

  /**
   * Represents the causal state of the world AFTER this operation was applied.
   * Used to detect concurrency: if two ops have unordered vector clocks, they are concurrent.
   */
  vectorClock: VectorClock;

  /**
   * Wall clock time (epoch ms) from the **originating** device.
   * Used as a tie-breaker for concurrent operations (Last-Write-Wins logic).
   */
  timestamp: number;

  // META
  /**
   * The schema version of the data at the time of operation creation.
   * Allows the system to migrate or transform payloads if the data structure changes in the future.
   */
  schemaVersion: number;
}

export interface OperationLogEntry<TOperation extends Operation<string> = Operation> {
  /**
   * Local, monotonic auto-increment integer (IndexedDB primary key).
   * Strictly orders operations as they arrived or were generated on THIS specific device.
   */
  seq: number;

  /**
   * The operation data itself (the synchronized part).
   */
  op: TOperation;

  /**
   * Local timestamp (epoch ms) indicating when this operation was written to the LOCAL database.
   * Used strictly for local maintenance, such as garbage collection (compaction).
   */
  appliedAt: number;

  /**
   * Origin of the operation:
   * - 'local': Generated by this device.
   * - 'remote': Received from the sync server.
   */
  source: 'local' | 'remote';

  /**
   * Timestamp (epoch ms) when this operation was successfully acknowledged by the remote server.
   * Null/undefined if the operation is still pending upload.
   */
  syncedAt?: number;

  /**
   * Timestamp (epoch ms) if the operation was rejected during conflict resolution.
   * Effectively marks the operation as "dead" but kept for history/debugging.
   */
  rejectedAt?: number;

  /**
   * For remote ops only: tracks whether the op was successfully applied to local state.
   * Used for crash recovery: on startup, any 'pending' or 'failed' remote ops are re-dispatched.
   */
  applicationStatus?: 'pending' | 'applied' | 'failed';

  /**
   * For 'failed' ops: number of retry attempts.
   */
  retryCount?: number;
}

export interface EntityConflict<TOperation extends Operation<string> = Operation> {
  entityType: string;
  entityId: string;
  localOps: TOperation[];
  remoteOps: TOperation[];
  suggestedResolution: 'local' | 'remote' | 'merge' | 'manual';
  mergedPayload?: unknown;
}

export interface ConflictResult<TOperation extends Operation<string> = Operation> {
  nonConflicting: TOperation[];
  conflicts: EntityConflict<TOperation>[];
}

// =============================================================================
// MULTI-ENTITY OPERATIONS
// =============================================================================

/**
 * Represents a single entity change within a multi-entity operation.
 */
export interface EntityChange<TOpType extends string = OpType> {
  entityType: string;
  entityId: string;
  opType: TOpType;
  /**
   * The actual changes:
   * - For Create: Full entity object
   * - For Update: Partial object with only changed fields
   * - For Delete: Minimal tombstone
   */
  changes: unknown;
}

/**
 * Payload wrapper for multi-entity operations.
 * Contains the original action payload plus all entity changes computed from state diff.
 */
export interface MultiEntityPayload<TOpType extends string = OpType> {
  actionPayload: Record<string, unknown>;
  entityChanges: EntityChange<TOpType>[];
}

/**
 * Type guard to check if a payload is a multi-entity payload.
 */
export const isMultiEntityPayload = (payload: unknown): payload is MultiEntityPayload => {
  return (
    typeof payload === 'object' &&
    payload !== null &&
    'entityChanges' in payload &&
    Array.isArray((payload as MultiEntityPayload).entityChanges)
  );
};

/**
 * Extracts the action payload from an operation payload.
 * Handles both multi-entity payloads (new format) and legacy payloads.
 */
export const extractActionPayload = (payload: unknown): Record<string, unknown> => {
  if (isMultiEntityPayload(payload)) {
    return payload.actionPayload;
  }
  return payload as Record<string, unknown>;
};

/**
 * Extracts a full entity from an operation payload.
 *
 * Handles both multi-entity payloads and direct action payloads. If no entity
 * exists under `payloadKey`, direct entity payloads with a top-level `id` are
 * accepted as a fallback.
 */
export const extractEntityFromPayload = (
  payload: unknown,
  payloadKey: string,
): Record<string, unknown> | undefined => {
  const actionPayload = extractActionPayload(payload);
  const entity = actionPayload[payloadKey];
  if (entity && typeof entity === 'object') {
    return entity as Record<string, unknown>;
  }
  if (actionPayload && typeof actionPayload === 'object' && 'id' in actionPayload) {
    return actionPayload as Record<string, unknown>;
  }
  return undefined;
};

/**
 * Extracts the changed fields from an UPDATE payload.
 *
 * Supports entity-adapter style `{ id, changes }` payloads and flat entity
 * payloads, where `id` is removed from the returned changes.
 */
export const extractUpdateChanges = (
  payload: unknown,
  payloadKey: string,
): Record<string, unknown> => {
  const actionPayload = extractActionPayload(payload);
  const entityPayload = actionPayload[payloadKey] as Record<string, unknown> | undefined;
  if (!entityPayload) return {};
  if ('changes' in entityPayload && typeof entityPayload['changes'] === 'object') {
    return entityPayload['changes'] as Record<string, unknown>;
  }
  const changes = { ...entityPayload };
  delete changes['id'];
  return changes;
};
