import type { RecentAddressesState, RecentAddress } from "@ledgerhq/types-live";
import { RecentAddressesArraySchema } from "./entities/recentAddresses";

export const RECENT_ADDRESSES_COUNT_LIMIT = 12;

export type RecentAddressesCache = RecentAddressesState;

export interface RecentAddressesStore {
  addAddress(currency: string, address: string, ensName?: string): void;
  removeAddress(currency: string, address: string): void;
  syncAddresses(cache: RecentAddressesCache): void;
  getAddresses(currency: string): RecentAddress[];
}

let recentAddressesStore: RecentAddressesStore | null = null;

export function getRecentAddressesStore(): RecentAddressesStore {
  if (recentAddressesStore === null) {
    throw new Error(
      "Recent addresses store instance is null, please call function setupRecentAddressesStore in application initialization",
    );
  }
  return recentAddressesStore;
}

export function setupRecentAddressesStore(
  addressesByCurrency: RecentAddressesCache,
  onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void,
): void {
  recentAddressesStore = new RecentAddressesStoreImpl(addressesByCurrency, onAddAddressComplete);
}

class RecentAddressesStoreImpl implements RecentAddressesStore {
  private addressesByCurrency: RecentAddressesCache = {};
  private readonly onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void;

  constructor(
    addressesByCurrency: RecentAddressesCache,
    onAddAddressComplete: (addressesByCurrency: RecentAddressesCache) => void,
  ) {
    this.addressesByCurrency = this.sanitizeCache(addressesByCurrency);
    this.onAddAddressComplete = onAddAddressComplete;
  }

  private sanitizeCache(cache: RecentAddressesCache): RecentAddressesCache {
    const sanitized: RecentAddressesCache = {};
    for (const currency in cache) {
      const entries = cache[currency];
      const result = RecentAddressesArraySchema.safeParse(entries);
      sanitized[currency] = result.success ? result.data : [];
    }
    return sanitized;
  }

  addAddress(currency: string, address: string, ensName?: string): void {
    this.addAddressToCache(currency, address, Date.now(), true, ensName);
  }

  removeAddress(currency: string, address: string): void {
    if (!this.addressesByCurrency[currency]) return;
    const addresses = this.addressesByCurrency[currency];
    const index = addresses.findIndex(entry => entry.address === address);
    if (index !== -1) {
      addresses.splice(index, 1);
      this.addressesByCurrency[currency] = [...addresses];
      this.onAddAddressComplete(this.addressesByCurrency);
    }
  }

  syncAddresses(cache: RecentAddressesCache): void {
    const previousAddresses = { ...this.addressesByCurrency };
    this.addressesByCurrency = this.sanitizeCache(cache);
    for (const currency in previousAddresses) {
      const entries = previousAddresses[currency];
      for (const entry of entries) {
        this.addAddressToCache(currency, entry.address, entry.lastUsed, false, entry.ensName);
      }
    }

    this.onAddAddressComplete(this.addressesByCurrency);
  }

  getAddresses(currency: string): RecentAddress[] {
    const addresses = this.addressesByCurrency[currency];
    if (!addresses) return [];
    return addresses.filter(
      (entry): entry is RecentAddress =>
        !!entry && typeof entry.address === "string" && entry.address.length > 0,
    );
  }

  private addAddressToCache(
    currency: string,
    address: string,
    timestamp: number,
    shouldTriggerCallback: boolean,
    ensName?: string,
  ): void {
    if (!this.addressesByCurrency[currency]) {
      this.addressesByCurrency[currency] = [];
    }

    const addresses = this.addressesByCurrency[currency];
    const addressIndex = addresses.findIndex(entry => entry.address === address);

    if (addressIndex !== -1) {
      addresses.splice(addressIndex, 1);
    } else if (addresses.length >= RECENT_ADDRESSES_COUNT_LIMIT) {
      addresses.pop();
    }

    addresses.unshift({ address, lastUsed: timestamp, ensName });
    this.addressesByCurrency[currency] = [...addresses];

    if (shouldTriggerCallback) {
      this.onAddAddressComplete(this.addressesByCurrency);
    }
  }
}
