/**
 * Listen to real-time obligation changes via WebSocket.
 *
 * Uses AccountSubscriptionManager for the WS plumbing and
 * KaminoObligation.fromAccountData for decoding + hydration.
 */
import { type Address, type Base58EncodedBytes, type Commitment, type Slot } from '@solana/kit';

import { Obligation } from '../@codegen/klend/accounts';
import { PROGRAM_ID } from '../@codegen/klend/programId';

import {
  type AccountSubscriptionManager,
  type ProgramFilter,
  type SubscribeProgramAccountsOptions,
} from './accountSubscriptionManager';
import type { KaminoMarket } from './market';
import { KaminoObligation } from './obligation';

/** Anchor accounts are prefixed with an 8-byte discriminator */
const ANCHOR_DISCRIMINATOR_BYTES = 8;
const OBLIGATION_ACCOUNT_SIZE = BigInt(Obligation.layout.span + ANCHOR_DISCRIMINATOR_BYTES);

export interface ObligationChangeEvent {
  obligation: KaminoObligation;
  address: Address;
  slot: Slot;
}

export type ObligationChangeCallback = (event: ObligationChangeEvent) => void;

export interface ObligationListenerParams {
  manager: AccountSubscriptionManager;
  /** Map of market address → KaminoMarket, or a getter for lazy access */
  markets: Map<Address, KaminoMarket> | (() => Map<Address, KaminoMarket>);
  owner: Address;
  onChange: ObligationChangeCallback;
  onError?: (error: unknown) => void;
  programId?: Address;
  commitment?: Commitment;
}

export function buildObligationFilters(owner: Address): ProgramFilter[] {
  return [
    { dataSize: OBLIGATION_ACCOUNT_SIZE },
    { memcmp: { offset: 64n, bytes: owner.toString() as Base58EncodedBytes, encoding: 'base58' } },
  ];
}

/**
 * Subscribe to all obligation account changes for a given owner.
 * Returns an unsubscribe function.
 */
export function listenToObligationChanges(params: ObligationListenerParams): () => void {
  const programId = params.programId ?? PROGRAM_ID;
  const filters = buildObligationFilters(params.owner);
  const getMarkets =
    typeof params.markets === 'function' ? params.markets : () => params.markets as Map<Address, KaminoMarket>;

  const options: SubscribeProgramAccountsOptions = {
    // Obligations are low-frequency — use 'processed' for instant delivery
    commitment: params.commitment ?? 'processed',
  };

  return params.manager.subscribeProgramAccounts(
    programId,
    filters,
    (addr: Address, buffer: Uint8Array, slot: Slot) => {
      try {
        const obligation = KaminoObligation.fromAccountData(getMarkets(), addr, buffer, slot);
        if (!obligation) return;
        params.onChange({ obligation, address: addr, slot });
      } catch (e) {
        params.onError?.(e);
      }
    },
    options
  );
}
