import { Querier, Transaction, TransactionalDatabase } from '@platform-modules/db';
import { Referral, ReferralLink, AffiliateSchema } from './schema.js';
import { d as FraudHostStore, A as AdapterConfig } from './types-CzYmxGUQ.js';
import { LedgerSchema } from '@platform-modules/ledger';
import { VestingSchema } from '@platform-modules/ledger/vesting';
import 'drizzle-orm';
import 'drizzle-orm/pg-core';

/**
 * Referral attribution helpers.
 *
 * resolveRefCode   — look up an active referral_links row by code.
 * isSelfReferral   — detect same-account or same-phone-blind-index collisions.
 * bindReferralOnSignup — last-click bind of a new referee to the link owner.
 */

/** Adopter DB handle — generic over the host's combined schema. */
type AttributionDb = Querier<Record<string, unknown>>;
declare class ReferralSettingsNotFoundError extends Error {
    readonly code: "REFERRAL_SETTINGS_NOT_FOUND";
    readonly _affiliateError: "ReferralSettingsNotFoundError";
    constructor(message?: string);
}
declare function isReferralSettingsNotFoundError(e: unknown): e is ReferralSettingsNotFoundError;
/**
 * Return the active referral_links row for the given code, or null if not
 * found / inactive.
 */
declare function resolveRefCode(db: AttributionDb, code: string): Promise<ReferralLink | null>;
/**
 * Returns true when the referrer and referee are the same account, OR when
 * they share the same phone blind-index (i.e. the same real phone number
 * registered under two different accounts).
 *
 * Host-data rule (spec §3A, module-wide): the `users` table is host-resident,
 * so the phone-index FETCH routes through the `FraudHostStore`; the COMPARISON
 * (index equality) stays in the module. No raw host-table SQL in core.
 */
declare function isSelfReferral(host: FraudHostStore, args: {
    referrerUserId: string;
    refereeUserId: string;
}): Promise<boolean>;
/**
 * Bind a newly-registered referee to the link owner as a pending referral.
 *
 * Semantics:
 *  - Last-click (cookie overwritten on each touch; most recent click before
 *    signup binds) / one-per-account: referrals.refereeUserId is UNIQUE, so a
 *    second call for the same referee silently returns the pre-existing row.
 *  - Self-referral guard: returns null when the link owner is the referee.
 *  - Inactive link guard: returns null when the link no longer exists or is
 *    inactive.
 *
 * Returns the referrals row (new or pre-existing), or null on guard failure.
 */
interface BindReferralInput {
    refereeUserId: string;
    linkId: string;
    clickedAt?: Date | null;
    refereeEmail?: string;
    refereePhone?: string;
    visitorId?: string;
    ipHash?: string;
    cfBotScore?: number;
    /** Axis-C host store — required for SIGNUP fraud adapters. */
    host: FraudHostStore;
}
declare function bindReferralOnSignup(db: AttributionDb, args: BindReferralInput): Promise<Referral | null>;

/**
 * Host referral settings shape — transitional type for host CommissionPolicy impls.
 * Tier values live in the host (ADR-4); this type is exported for parity harnesses only.
 */
type AdapterOverride = {
    enabled: boolean;
    action?: 'pass' | 'flag' | 'hold' | 'block';
    params?: Record<string, unknown>;
};
type ReferralSettings = {
    affiliatePct: number;
    /** Tier commission rates — % of SALE directly (WYSIWYG: 3 = "3% of purchase amount") */
    tier1Pct: number;
    tier2Pct: number;
    tier3Pct: number;
    tier2MinSales: number;
    tier3MinSales: number;
    referralPct: number;
    affiliateWindowDays: number;
    affiliateMaxOrders: number;
    cookieDays: number;
    rewardAgorot: number;
    refereeDiscountAgorot: number;
    holdDays: number;
    withdrawalMinAgorot: number;
    autoApproveLifetimeAgorot: number;
    brandKeywordBlocklist: string[];
    disallowedRefererHosts: string[];
    tosVersion: string;
    disputeWindowDays: number;
    reservePct: number;
    reserveReleaseDays: number;
    fraudConfig: Record<string, AdapterOverride>;
};

/**
 * Commission engine — host-supplied policy seam.
 *
 * The module computes commission GIVEN a host-provided CommissionPolicy.
 * A typical tier model (resolveAffiliateTierPct / floor(amt·min(pct,10)/100))
 * becomes ONE host CommissionPolicy implementation, not the module's shape.
 *
 * INVARIANT: Vendor payout is NEVER reduced. Platform absorbs the cost.
 * Money = bigint minor units (agorot) throughout — no float.
 */

declare const PLATFORM_FEE_PCT: 10;
declare const SELF_VENDOR_PURCHASE: "SELF_VENDOR_PURCHASE";
interface CommissionContext {
    referrerId: string;
    refereeId: string;
    productId?: string;
    rollingSalesCount?: number;
}
interface CommissionPolicy {
    /** Returns commission in minor units given the settled sale + context. */
    resolve(input: {
        amountPaidMinor: bigint;
        context: CommissionContext;
    }): bigint;
}
interface ComputeCommissionInput {
    amountPaidMinor: bigint;
    context: CommissionContext;
}
/**
 * Compute commission by delegating to the host-supplied policy.
 * Zero or negative sale amounts short-circuit to 0 without calling the policy.
 */
declare function computeCommission(policy: CommissionPolicy, input: ComputeCommissionInput): bigint;
/**
 * Reference percentage helper for host CommissionPolicy implementations.
 * floor(amount · min(pct, capPct) / 100) in bigint minor units.
 *
 * Money stays bigint throughout — `pct` is scaled to integer basis-points
 * (×100, rounded to ≤2 decimal places) so a FRACTIONAL pct (e.g. 7.5) computes
 * the floor faithfully instead of throwing `BigInt(7.5) → RangeError`. For an
 * INTEGER pct this is bit-identical to `floor(amt·min(pct,10)/100)`.
 */
declare function computePercentageCommission(amountPaidMinor: bigint, pct: number, capPct?: number): bigint;
/** Compute the platform's gross net from a paid amount (used for invoicing/reporting). */
declare function computePlatformNet(amountPaidMinor: bigint, feePct?: number): bigint;
/**
 * Resolve the affiliate tier pct (% of sale) for a given trailing-30-day purchase count.
 * Tier locked at accrual — call this once per purchase, store the result as resolved_pct.
 */
declare function resolveAffiliateTierPct(monthlySales: number, settings: Pick<ReferralSettings, 'tier1Pct' | 'tier2Pct' | 'tier3Pct' | 'tier2MinSales' | 'tier3MinSales'>): number;
/**
 * Compute affiliate commission = floor(amountPaid * resolvedTierPct / 100).
 * resolvedTierPct comes from resolveAffiliateTierPct() — it is % of sale.
 */
declare function computeAffiliateCommission(amountPaidMinor: bigint, resolvedTierPct: number): bigint;
/**
 * Compute referral store credit = floor(amountPaid * referralPct / 100).
 * referralPct from settings (% of sale). Applied to referrer's wallet as store credit.
 */
declare function computeReferralCommission(amountPaidMinor: bigint, settings: Pick<ReferralSettings, 'referralPct'>): bigint;
/**
 * Determine whether a purchase is a self-vendor scenario (§M.10a).
 * Returns true when the referrer's userId matches the vendor's ownerUserId.
 */
declare function isSelfVendorPurchase(referrerUserId: string, vendorOwnerUserId: string): boolean;

declare class InsufficientBalanceError extends Error {
    readonly detail?: {
        requiredMinor: bigint;
        availableMinor: bigint;
        bucket: "matured" | "withdrawable";
    } | undefined;
    readonly code: "INSUFFICIENT_BALANCE";
    readonly _affiliateError: "InsufficientBalanceError";
    constructor(message: string, detail?: {
        requiredMinor: bigint;
        availableMinor: bigint;
        bucket: "matured" | "withdrawable";
    } | undefined);
}
declare function isInsufficientBalanceError(e: unknown): e is InsufficientBalanceError;
declare class InvalidPayoutAmountError extends Error {
    readonly amountMinor: bigint;
    readonly code: "INVALID_PAYOUT_AMOUNT";
    readonly _affiliateError: "InvalidPayoutAmountError";
    constructor(amountMinor: bigint);
}
declare function isInvalidPayoutAmountError(e: unknown): e is InvalidPayoutAmountError;
declare class FraudHoldError extends Error {
    readonly detail?: {
        point: string;
        reason: string;
    } | undefined;
    readonly code: "FRAUD_HOLD";
    readonly _affiliateError: "FraudHoldError";
    constructor(message: string, detail?: {
        point: string;
        reason: string;
    } | undefined);
}
declare function isFraudHoldError(e: unknown): e is FraudHoldError;

/**
 * Affiliate credit-ledger accrual — ledger-first, idempotent pending/matured projection.
 *
 * Accrual: append a ledger entry in-tx (appendLedgerEntryTx) + compute the maturation timestamp (computeMatureAt).
 * Insert order: core ledger_entries → affiliate_entries side-row → accrueVesting (same tx).
 */

type CreditEntryType = 'referral_reward' | 'affiliate_commission' | 'redemption' | 'refund_clawback' | 'adjustment';
/** Host precondition (I0): earn sourceIds must be colon-free opaque ids. */
declare class InvalidSourceIdError extends Error {
    readonly code: "INVALID_SOURCE_ID";
    readonly _affiliateError: "InvalidSourceIdError";
    constructor(sourceId: string);
}
declare function isInvalidSourceIdError(e: unknown): e is InvalidSourceIdError;
/** Positive accrual must be an earn entry type — non-earn credits use other entrypoints. */
declare class UnsupportedAccrualEntryTypeError extends Error {
    readonly code: "UNSUPPORTED_ACCRUAL_ENTRY_TYPE";
    readonly _affiliateError: "UnsupportedAccrualEntryTypeError";
    constructor(entryType: CreditEntryType);
}
declare function isUnsupportedAccrualEntryTypeError(e: unknown): e is UnsupportedAccrualEntryTypeError;
type MatureAtInput = {
    kind: 'coupon' | 'physical' | string;
    paidAt: Date;
    expiresAt: Date | null;
    redeemedAt: Date | null;
};
type MatureAtSettings = {
    holdDays: number;
};
/**
 * Compute the mature_at timestamp for a credit ledger entry.
 *
 * - physical deal: paidAt + holdDays
 * - coupon (redeemed): redeemedAt + holdDays
 * - coupon (not yet redeemed): max(paidAt, expiresAt) + holdDays
 */
declare function computeMatureAt(input: MatureAtInput, settings: MatureAtSettings): Date;
interface AccrueCommissionInput {
    userId: string;
    amountMinor: bigint;
    entryType: CreditEntryType;
    sourceType: string;
    sourceId: string;
    matureAt: Date;
    referralId?: string;
    memo?: string;
    resolvedPct?: number;
}
type AccrueCommissionState = 'pending' | 'matured' | 'audit';
interface AccrueCommissionResult {
    /** True when a NEW ledger row was inserted (idempotent no-op → false). */
    inserted: boolean;
    amountMinor: bigint;
    /**
     * Wallet bucket touched — `pending` when hold active, `matured` when instant.
     * `audit` = no vesting bucket claimed: zero-amount rows, non-earn rows, OR a
     * positive-earn idempotent REPLAY no-op (`inserted === false` — nothing moved).
     * A positive NON-earn entryType never reaches here — it throws
     * `UnsupportedAccrualEntryTypeError` at the boundary (no stranded credit).
     */
    state: AccrueCommissionState;
}
/**
 * Accrue commission/reward — insert core ledger row first (idempotent),
 * then affiliate side-row + vesting buckets in the same tx.
 */
declare function accrueCommission<S extends AffiliateSchema & LedgerSchema & VestingSchema>(tx: Transaction<S>, input: AccrueCommissionInput): Promise<AccrueCommissionResult>;

interface ProcessReferralEarnInput {
    host: FraudHostStore;
    policy: CommissionPolicy;
    fraudConfig: Record<string, AdapterConfig>;
    purchaseId: string;
    buyerUserId: string;
    amountPaidMinor: bigint;
    platformNetMinor: bigint;
    matureAt: Date;
}
type ProcessReferralEarnFailureReason = 'NO_REFERRAL' | 'QUARANTINED' | 'FRAUD_HOLD' | 'FRAUD_BLOCK' | 'ZERO_COMMISSION';
type ProcessReferralEarnResult = {
    earned: false;
    reason: ProcessReferralEarnFailureReason;
} | {
    earned: true;
    commissionMinor: bigint;
    inserted: boolean;
    state: AccrueCommissionState;
};
/**
 * Process a settled purchase EARN: fraud EARN gate (fail-closed) first, then commission + accrual.
 */
declare function processReferralEarn<S extends AffiliateSchema & LedgerSchema & VestingSchema>(tx: Transaction<S>, input: ProcessReferralEarnInput): Promise<ProcessReferralEarnResult>;

interface RedemptionFactRef {
    sourceType: string;
    sourceId: string;
}
interface RedemptionFact {
    sourceType: string;
    sourceId: string;
    kind: 'coupon' | 'physical';
    paidAt: Date;
    expiresAt: Date | null;
    redeemedAt: Date | null;
    purchaseCreatedAt: Date;
}
interface MaturityHostStore {
    /** Read-only; joins host purchases/deals; maps host deal_type → normalized kind. */
    getRedemptionFacts(refs: RedemptionFactRef[]): Promise<RedemptionFact[]>;
    getReferralSettings(): Promise<{
        holdDays: number;
        disputeWindowDays: number;
    }>;
}
type WithdrawableAtInput = {
    kind: 'coupon' | 'physical';
    redeemedAt: Date | null;
    purchaseCreatedAt: Date;
};
/**
 * Compute withdrawable_at for a credit ledger entry from normalized redemption facts.
 *
 * - coupon + redeemed → redeemedAt + disputeWindowDays
 * - coupon + unredeemed → null (not withdrawable yet)
 * - physical → purchaseCreatedAt + disputeWindowDays
 */
declare function computeWithdrawableAt(input: WithdrawableAtInput, disputeWindowDays: number): Date | null;
type SweepMaturationResult = {
    promoted: number;
    recomputed: number;
    sweptAt: Date;
};
/**
 * Maturation sweep: promotes pending ledger_entry_vesting rows to matured.
 *
 * NO-OP gate: return early if nothing is ready.
 * Phase 1: Recompute mature_at for coupon rows where coupon has since been redeemed (via host facts).
 * Phase 2: SELECT+net clawbacks in JS → promoteEntries → mark clawbacks consumed (design §3.4).
 */
declare function sweepMaturation<S extends AffiliateSchema & LedgerSchema & VestingSchema>(tx: Transaction<S>, host: MaturityHostStore): Promise<SweepMaturationResult>;
/**
 * Withdrawable sweep: compute withdrawable_at anchors then recompute withdrawableMinor.
 */
declare function sweepWithdrawable<S extends AffiliateSchema & LedgerSchema & VestingSchema>(tx: Transaction<S>, host: MaturityHostStore): Promise<{
    updated: number;
}>;

/**
 * TOCTOU-safe affiliate payout ledger debit — shared by withdraw, fraud hold, and admin release.
 *
 * R4 audit-hardened money path — preserve byte-faithfully; do not casually refactor.
 */

interface PayoutDestination {
    accountId: string;
    [k: string]: string;
}
interface PayoutExecutor {
    /**
     * CONTRACT (hard): execute MUST be idempotent on payoutId — the host adapter keys
     * its provider idempotency (Stripe idempotencyKey, …) on payoutId, so a crash-retry
     * of the SAME payout row never double-pays. The outside-the-tx boundary depends on it.
     */
    execute(req: {
        payoutId: string;
        amountMinor: bigint;
        destination: PayoutDestination;
    }): Promise<{
        ok: true;
        externalRefs: Record<string, string>;
    } | {
        ok: false;
        code: string;
        error: string;
    }>;
}
type SettlePayoutInput = {
    payoutId: string;
};
type SettlePayoutResult = {
    ok: true;
    ledgerEntryId: string | null;
    externalRefs: Record<string, string>;
} | {
    ok: false;
    code: string;
    error: string;
    restored: boolean;
};
/** Sentinel when payout ledger row is missing or amount does not match. */
declare class PayoutLedgerMismatchError extends Error {
    readonly code: "PAYOUT_LEDGER_MISMATCH";
    readonly _affiliateError: "PayoutLedgerMismatchError";
    constructor();
}
declare function isPayoutLedgerMismatchError(e: unknown): e is PayoutLedgerMismatchError;
/**
 * Lock wallet_vesting owner row, verify withdrawable+matured ceilings, append redemption
 * ledger row via core debitWithRead, decrement maturedMinor, recompute withdrawableMinor,
 * and link payout FK.
 *
 * Idempotent: if the ledger row already exists, balance is not double-debited.
 */
declare function debitPayoutInTx<S extends AffiliateSchema & LedgerSchema & VestingSchema>(tx: Transaction<S>, userId: string, payoutId: string, amountMinor: bigint): Promise<string>;
/** Debit at most once — no-op when ledger_entry_id is already set. */
declare function ensurePayoutLedgerDebit<S extends AffiliateSchema & LedgerSchema & VestingSchema>(tx: Transaction<S>, payout: {
    id: string;
    userId: string;
    amountAgorot: bigint;
    ledgerEntryId: string | null;
}): Promise<string>;
/**
 * Admin settlement guard: idempotently ensure the redemption debit exists, then verify the
 * recorded ledger entry matches this payout — entryType 'redemption', sourceType 'affiliate_payout',
 * sourceId = payoutId, and the audit `-delta` equals `-amountAgorot` (bigint). Any mismatch throws
 * PayoutLedgerMismatchError. Returns the ledgerEntryId.
 */
declare function verifyPayoutReadyForSettlement<S extends AffiliateSchema & LedgerSchema & VestingSchema>(tx: Transaction<S>, payout: {
    id: string;
    userId: string;
    amountAgorot: bigint;
    ledgerEntryId: string | null;
}): Promise<string>;
/**
 * Restore wallet_vesting.maturedMinor after a failed/cancelled payout that already debited,
 * then recompute withdrawableMinor. Caller MUST mark the payout failed/cancelled in the same
 * tx before recompute so paidMinor excludes the released payout.
 * Idempotent on adjustment:affiliate_payout_reversal:<payoutId> (canonical key via buildIdempotencyKey; matches the fold + host-bridge reversal so cross-writer reversals dedup, never collide on affiliate_entries_idem_uq).
 */
declare function restorePayoutDebitInTx<S extends AffiliateSchema & LedgerSchema & VestingSchema>(tx: Transaction<S>, userId: string, payoutId: string, amountMinor: bigint): Promise<boolean>;
/**
 * Money-out boundary (R4 audit-hardened): claim tx → executor OUTSIDE tx → persist/restore tx.
 * Idempotent on payoutId — already-paid rows return prior success without re-executing.
 */
declare function settlePayout<S extends AffiliateSchema & LedgerSchema & VestingSchema>(db: TransactionalDatabase<S>, executor: PayoutExecutor, input: SettlePayoutInput): Promise<SettlePayoutResult>;
/**
 * Reclaim payout rows stuck in processing after a worker crash mid-executor call.
 * sweepStuckAffiliatePayouts (R4 audit-hardened) — preserve byte-faithfully.
 *
 * ACCEPTED residual window (spec §5): if the transfer succeeded but the worker crashed
 * before persisting the ref, this restores the debit while funds already moved — accepted trade-off.
 */
declare function sweepStuckPayouts<S extends AffiliateSchema & LedgerSchema & VestingSchema>(db: TransactionalDatabase<S>, olderThanMinutes?: number): Promise<{
    reclaimed: number;
}>;

export { type AccrueCommissionInput, type AccrueCommissionResult, type AccrueCommissionState, type AdapterOverride, type BindReferralInput, type CommissionContext, type CommissionPolicy, type ComputeCommissionInput, type CreditEntryType, FraudHoldError, InsufficientBalanceError, InvalidPayoutAmountError, InvalidSourceIdError, type MatureAtInput, type MatureAtSettings, type MaturityHostStore, PLATFORM_FEE_PCT, type PayoutDestination, type PayoutExecutor, PayoutLedgerMismatchError, type ProcessReferralEarnFailureReason, type ProcessReferralEarnInput, type ProcessReferralEarnResult, type RedemptionFact, type RedemptionFactRef, type ReferralSettings, ReferralSettingsNotFoundError, SELF_VENDOR_PURCHASE, type SettlePayoutInput, type SettlePayoutResult, type SweepMaturationResult, UnsupportedAccrualEntryTypeError, type WithdrawableAtInput, accrueCommission, bindReferralOnSignup, computeAffiliateCommission, computeCommission, computeMatureAt, computePercentageCommission, computePlatformNet, computeReferralCommission, computeWithdrawableAt, debitPayoutInTx, ensurePayoutLedgerDebit, isFraudHoldError, isInsufficientBalanceError, isInvalidPayoutAmountError, isInvalidSourceIdError, isPayoutLedgerMismatchError, isReferralSettingsNotFoundError, isSelfReferral, isSelfVendorPurchase, isUnsupportedAccrualEntryTypeError, processReferralEarn, resolveAffiliateTierPct, resolveRefCode, restorePayoutDebitInTx, settlePayout, sweepMaturation, sweepStuckPayouts, sweepWithdrawable, verifyPayoutReadyForSettlement };
