import type { Proof } from "@cashu/cashu-ts";
import type { NDKCashuToken, NDKEventId } from "@nostr-dev-kit/ndk";
import type { WalletState } from ".";

/**
 * Adds or updates the token in the wallet state, optionally associating proofs with the token
 */
export function addToken(this: WalletState, token: NDKCashuToken) {
    if (!token.mint) throw new Error("BUG: Token has no mint");

    const currentEntry = this.tokens.get(token.id);
    const state = currentEntry?.state ?? "available";

    this.tokens.set(token.id, { token, state });

    // go through the proofs this token is claiming
    let _added = 0;
    let _invalid = 0;
    for (const proof of token.proofs) {
        const val = maybeAssociateProofWithToken(this, proof, token, state);
        if (val === false) {
            _invalid++;
        } else {
            _added++;
        }
    }
}

function maybeAssociateProofWithToken(
    walletState: WalletState,
    proof: Proof,
    token: NDKCashuToken,
    state: "available" | "reserved" | "deleted",
): boolean | null {
    const proofC = proof.C;
    const proofEntry = walletState.proofs.get(proofC);

    if (!proofEntry) {
        walletState.addProof({
            mint: token.mint!,
            state,
            tokenId: token.id,
            timestamp: token.created_at!,
            proof: proof,
        });
        return true;
    }
    // already associated
    if (proofEntry.tokenId) {
        if (proofEntry.tokenId === token.id) {
            // already associated with this token, nothing to do
            return null;
        }

        // different token id, ensure the incoming token is newer
        const existingTokenEntry = walletState.tokens.get(proofEntry.tokenId);
        if (!existingTokenEntry) {
            throw new Error(
                `BUG: Token id ${proofEntry.tokenId} not found, was expected to be associated with proof ${proofC}`,
            );
        }
        const existingToken = existingTokenEntry.token;

        if (existingToken) {
            // existing token didnt have a timestamp or the incoming token is newer
            if (existingToken.created_at && (!token.created_at || token.created_at < existingToken.created_at)) {
                // either the incoming token is older or it doesnt have a timestamp
                return false;
            }
        }

        // update the proof entry
        walletState.updateProof(proof, { tokenId: token.id, state });
        return true;
    }
    // not associated with any token
    walletState.updateProof(proof, { tokenId: token.id, state });
    return true;
}

export function removeTokenId(this: WalletState, tokenId: NDKEventId) {
    const currentEntry = this.tokens.get(tokenId) || {};

    this.tokens.set(tokenId, { ...currentEntry, state: "deleted" });

    // remove all proofs associated with this token
    for (const proofEntry of this.proofs.values()) {
        const { proof } = proofEntry;
        if (proofEntry.tokenId === tokenId) {
            if (!proof) {
                throw new Error("BUG: Proof entry has no proof");
            }

            this.updateProof(proof, { state: "deleted" });
        }
    }
}
