use miden::core::word
use miden::protocol::active_account
use miden::protocol::native_account
use miden::standards::access::authority
use miden::standards::access::pausable

# DEPENDENCY NOTE
# This manager owns mint, burn, send, and receive policy state in a single component. The
# `set_*_policy` procedures consult the account-wide [`Authority`] component (installed
# separately) via `exec.authority::assert_authorized`, which dispatches between
# `AuthControlled`, `OwnerControlled`, and `RbacControlled` modes. The manager itself stores no
# authority discriminator.
#
# Mint and burn policies are dispatched internally via `dynexec` from `execute_mint_policy` /
# `execute_burn_policy`. Send and receive policies are flattened: the active policy root is
# written directly into the protocol-reserved callback slots
# (`miden::protocol::faucet::callback::on_before_asset_added_to_account` and
# `..._to_note`) and the kernel invokes them via `call`.

# CONSTANTS
# ================================================================================================

# Active mint policy root slot. Layout: [PROC_ROOT]
const ACTIVE_MINT_POLICY_PROC_ROOT_SLOT=word("miden::standards::faucets::policies::policy_manager::active_mint_policy_proc_root")

# Active burn policy root slot. Layout: [PROC_ROOT]
const ACTIVE_BURN_POLICY_PROC_ROOT_SLOT=word("miden::standards::faucets::policies::policy_manager::active_burn_policy_proc_root")

# Protocol-reserved callback slot holding the active receive policy proc root. Written by
# `set_receive_policy`, read by the kernel when dispatching `on_before_asset_added_to_account`.
const ON_BEFORE_ASSET_ADDED_TO_ACCOUNT_SLOT=word("miden::protocol::faucet::callback::on_before_asset_added_to_account")

# Protocol-reserved callback slot holding the active send policy proc root. Written by
# `set_send_policy`, read by the kernel when dispatching `on_before_asset_added_to_note`.
const ON_BEFORE_ASSET_ADDED_TO_NOTE_SLOT=word("miden::protocol::faucet::callback::on_before_asset_added_to_note")

# Allowlist map slot for mint policy roots. Map entries: [PROC_ROOT] -> [1, 0, 0, 0]
const ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT=word("miden::standards::faucets::policies::policy_manager::allowed_mint_policy_proc_roots")

# Allowlist map slot for burn policy roots. Map entries: [PROC_ROOT] -> [1, 0, 0, 0]
const ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT=word("miden::standards::faucets::policies::policy_manager::allowed_burn_policy_proc_roots")

# Allowlist map slot for send policy roots. Map entries: [PROC_ROOT] -> [1, 0, 0, 0]
const ALLOWED_SEND_POLICY_PROC_ROOTS_SLOT=word("miden::standards::faucets::policies::policy_manager::allowed_send_policy_proc_roots")

# Allowlist map slot for receive policy roots. Map entries: [PROC_ROOT] -> [1, 0, 0, 0]
const ALLOWED_RECEIVE_POLICY_PROC_ROOTS_SLOT=word("miden::standards::faucets::policies::policy_manager::allowed_receive_policy_proc_roots")

# Local memory pointer used to pass a policy root to `dynexec`.
const POLICY_PROC_ROOT_PTR=0

# ERRORS
# ================================================================================================

const ERR_MINT_POLICY_ROOT_IS_ZERO="mint policy root is zero"
const ERR_MINT_POLICY_ROOT_NOT_IN_ACCOUNT="mint policy root is not a procedure of this account"
const ERR_MINT_POLICY_ROOT_NOT_ALLOWED="mint policy root is not allowed"

const ERR_BURN_POLICY_ROOT_IS_ZERO="burn policy root is zero"
const ERR_BURN_POLICY_ROOT_NOT_IN_ACCOUNT="burn policy root is not a procedure of this account"
const ERR_BURN_POLICY_ROOT_NOT_ALLOWED="burn policy root is not allowed"

const ERR_SEND_POLICY_ROOT_IS_ZERO="send policy root is zero"
const ERR_SEND_POLICY_ROOT_NOT_IN_ACCOUNT="send policy root is not a procedure of this account"
const ERR_SEND_POLICY_ROOT_NOT_ALLOWED="send policy root is not allowed"

const ERR_RECEIVE_POLICY_ROOT_IS_ZERO="receive policy root is zero"
const ERR_RECEIVE_POLICY_ROOT_NOT_IN_ACCOUNT="receive policy root is not a procedure of this account"
const ERR_RECEIVE_POLICY_ROOT_NOT_ALLOWED="receive policy root is not allowed"

# MINT INTERNAL PROCEDURES
# ================================================================================================

#! Reads active mint policy root from storage.
#!
#! Inputs:  []
#! Outputs: [MINT_POLICY_ROOT]
#!
#! Invocation: exec
proc get_mint_policy_root
    push.ACTIVE_MINT_POLICY_PROC_ROOT_SLOT[0..2] exec.active_account::get_item
    # => [MINT_POLICY_ROOT]
end

#! Validates a mint policy root at config time.
#!
#! Inputs:  [MINT_POLICY_ROOT]
#! Outputs: [MINT_POLICY_ROOT]
#!
#! Panics if:
#! - policy root is zero.
#! - policy root is not present in this account's procedures.
#!
#! Invocation: exec
proc assert_existing_mint_policy_root
    exec.word::testz
    assertz.err=ERR_MINT_POLICY_ROOT_IS_ZERO
    # => [MINT_POLICY_ROOT]

    dupw exec.active_account::has_procedure
    assert.err=ERR_MINT_POLICY_ROOT_NOT_IN_ACCOUNT
    # => [MINT_POLICY_ROOT]
end

#! Validates that the mint policy root is one of the allowed mint policy roots.
#!
#! Inputs:  [MINT_POLICY_ROOT]
#! Outputs: [MINT_POLICY_ROOT]
#!
#! Panics if:
#! - policy root is not in the allowed mint policy roots map.
#!
#! Invocation: exec
proc assert_allowed_mint_policy_root
    dupw
    push.ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT[0..2]
    exec.active_account::get_map_item
    # => [ALLOWED_FLAG, MINT_POLICY_ROOT]

    exec.word::eqz
    assertz.err=ERR_MINT_POLICY_ROOT_NOT_ALLOWED
    # => [MINT_POLICY_ROOT]
end

# BURN INTERNAL PROCEDURES
# ================================================================================================

#! Reads active burn policy root from storage.
#!
#! Inputs:  []
#! Outputs: [BURN_POLICY_ROOT]
#!
#! Invocation: exec
proc get_burn_policy_root
    push.ACTIVE_BURN_POLICY_PROC_ROOT_SLOT[0..2] exec.active_account::get_item
    # => [BURN_POLICY_ROOT]
end

#! Validates a burn policy root at config time.
#!
#! Inputs:  [BURN_POLICY_ROOT]
#! Outputs: [BURN_POLICY_ROOT]
#!
#! Panics if:
#! - policy root is zero.
#! - policy root is not present in this account's procedures.
#!
#! Invocation: exec
proc assert_existing_burn_policy_root
    exec.word::testz
    assertz.err=ERR_BURN_POLICY_ROOT_IS_ZERO
    # => [BURN_POLICY_ROOT]

    dupw exec.active_account::has_procedure
    assert.err=ERR_BURN_POLICY_ROOT_NOT_IN_ACCOUNT
    # => [BURN_POLICY_ROOT]
end

#! Validates that the burn policy root is one of the allowed burn policy roots.
#!
#! Inputs:  [BURN_POLICY_ROOT]
#! Outputs: [BURN_POLICY_ROOT]
#!
#! Panics if:
#! - policy root is not in the allowed burn policy roots map.
#!
#! Invocation: exec
proc assert_allowed_burn_policy_root
    dupw
    push.ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT[0..2]
    exec.active_account::get_map_item
    # => [ALLOWED_FLAG, BURN_POLICY_ROOT]

    exec.word::eqz
    assertz.err=ERR_BURN_POLICY_ROOT_NOT_ALLOWED
    # => [BURN_POLICY_ROOT]
end

# SEND INTERNAL PROCEDURES
# ================================================================================================

#! Validates a send policy root at config time.
#!
#! Inputs:  [SEND_POLICY_ROOT]
#! Outputs: [SEND_POLICY_ROOT]
#!
#! Panics if:
#! - policy root is zero.
#! - policy root is not present in this account's procedures.
#!
#! Invocation: exec
proc assert_existing_send_policy_root
    exec.word::testz
    assertz.err=ERR_SEND_POLICY_ROOT_IS_ZERO
    # => [SEND_POLICY_ROOT]

    dupw exec.active_account::has_procedure
    assert.err=ERR_SEND_POLICY_ROOT_NOT_IN_ACCOUNT
    # => [SEND_POLICY_ROOT]
end

#! Validates that the send policy root is one of the allowed send policy roots.
#!
#! Inputs:  [SEND_POLICY_ROOT]
#! Outputs: [SEND_POLICY_ROOT]
#!
#! Panics if:
#! - policy root is not in the allowed send policy roots map.
#!
#! Invocation: exec
proc assert_allowed_send_policy_root
    dupw
    push.ALLOWED_SEND_POLICY_PROC_ROOTS_SLOT[0..2]
    exec.active_account::get_map_item
    # => [ALLOWED_FLAG, SEND_POLICY_ROOT]

    exec.word::eqz
    assertz.err=ERR_SEND_POLICY_ROOT_NOT_ALLOWED
    # => [SEND_POLICY_ROOT]
end

# RECEIVE INTERNAL PROCEDURES
# ================================================================================================

#! Validates a receive policy root at config time.
#!
#! Inputs:  [RECEIVE_POLICY_ROOT]
#! Outputs: [RECEIVE_POLICY_ROOT]
#!
#! Panics if:
#! - policy root is zero.
#! - policy root is not present in this account's procedures.
#!
#! Invocation: exec
proc assert_existing_receive_policy_root
    exec.word::testz
    assertz.err=ERR_RECEIVE_POLICY_ROOT_IS_ZERO
    # => [RECEIVE_POLICY_ROOT]

    dupw exec.active_account::has_procedure
    assert.err=ERR_RECEIVE_POLICY_ROOT_NOT_IN_ACCOUNT
    # => [RECEIVE_POLICY_ROOT]
end

#! Validates that the receive policy root is one of the allowed receive policy roots.
#!
#! Inputs:  [RECEIVE_POLICY_ROOT]
#! Outputs: [RECEIVE_POLICY_ROOT]
#!
#! Panics if:
#! - policy root is not in the allowed receive policy roots map.
#!
#! Invocation: exec
proc assert_allowed_receive_policy_root
    dupw
    push.ALLOWED_RECEIVE_POLICY_PROC_ROOTS_SLOT[0..2]
    exec.active_account::get_map_item
    # => [ALLOWED_FLAG, RECEIVE_POLICY_ROOT]

    exec.word::eqz
    assertz.err=ERR_RECEIVE_POLICY_ROOT_NOT_ALLOWED
    # => [RECEIVE_POLICY_ROOT]
end

# MINT PUBLIC INTERFACE
# ================================================================================================

#! Executes active mint policy by dynamic execution.
#!
#! Inputs:  [amount, tag, note_type, RECIPIENT]
#! Outputs: [amount, tag, note_type, RECIPIENT]
#!
#! Panics if:
#! - the account is paused (`exec.pausable::assert_not_paused` — TokenPolicyManager requires the
#!   [`Pausable`] component to be installed; the slot lookup panics on accounts that did not
#!   install it).
#! - active mint policy predicate fails.
#!
#! The active policy root is validated at config time (in `set_mint_policy` and on initial
#! storage construction) so we trust it here without re-checking existence / allowed-ness.
#!
#! Invocation: exec
@locals(4)
pub proc execute_mint_policy
    exec.pausable::assert_not_paused
    # => [amount, tag, note_type, RECIPIENT]

    exec.get_mint_policy_root
    # => [MINT_POLICY_ROOT, amount, tag, note_type, RECIPIENT]

    loc_storew_le.POLICY_PROC_ROOT_PTR dropw
    # => [amount, tag, note_type, RECIPIENT]

    locaddr.POLICY_PROC_ROOT_PTR
    # => [policy_root_ptr, amount, tag, note_type, RECIPIENT]

    dynexec
    # => [amount, tag, note_type, RECIPIENT]
end

#! Returns active mint policy root.
#!
#! Inputs:  [pad(16)]
#! Outputs: [MINT_POLICY_ROOT, pad(12)]
#!
#! Invocation: call
pub proc get_mint_policy
    exec.get_mint_policy_root
    # => [MINT_POLICY_ROOT, pad(12)]
end

#! Sets active mint policy root.
#!
#! Inputs:  [NEW_POLICY_ROOT, pad(12)]
#! Outputs: [pad(16)]
#!
#! Panics if:
#! - the caller is not authorized per the installed [`Authority`].
#! - NEW_POLICY_ROOT is zero.
#! - NEW_POLICY_ROOT is not a procedure of this account.
#! - NEW_POLICY_ROOT is not in the allowed roots map.
#!
#! Invocation: call
pub proc set_mint_policy
    exec.authority::assert_authorized
    # => [NEW_POLICY_ROOT, pad(12)]

    exec.assert_existing_mint_policy_root
    # => [NEW_POLICY_ROOT, pad(12)]

    exec.assert_allowed_mint_policy_root
    # => [NEW_POLICY_ROOT, pad(12)]

    push.ACTIVE_MINT_POLICY_PROC_ROOT_SLOT[0..2] exec.native_account::set_item dropw
    # => [pad(16)]
end

# BURN PUBLIC INTERFACE
# ================================================================================================

#! Executes active burn policy for the provided asset by dynamic execution.
#!
#! Inputs:  [ASSET_KEY, ASSET_VALUE]
#! Outputs: []
#!
#! Panics if:
#! - the account is paused.
#! - active burn policy predicate fails.
#!
#! The active policy root is validated at config time (in `set_burn_policy` and on initial
#! storage construction) so we trust it here without re-checking existence / allowed-ness.
#!
#! Invocation: exec
@locals(4)
pub proc execute_burn_policy
    exec.pausable::assert_not_paused
    # => [ASSET_KEY, ASSET_VALUE]

    exec.get_burn_policy_root
    # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE]

    loc_storew_le.POLICY_PROC_ROOT_PTR dropw
    # => [ASSET_KEY, ASSET_VALUE]

    locaddr.POLICY_PROC_ROOT_PTR
    # => [policy_root_ptr, ASSET_KEY, ASSET_VALUE]

    dynexec
    # => []
end

#! Returns active burn policy root.
#!
#! Inputs:  [pad(16)]
#! Outputs: [BURN_POLICY_ROOT, pad(12)]
#!
#! Invocation: call
pub proc get_burn_policy
    exec.get_burn_policy_root
    # => [BURN_POLICY_ROOT, pad(12)]
end

#! Sets active burn policy root.
#!
#! Inputs:  [NEW_POLICY_ROOT, pad(12)]
#! Outputs: [pad(16)]
#!
#! Panics if:
#! - the caller is not authorized per the installed [`Authority`].
#! - NEW_POLICY_ROOT is zero.
#! - NEW_POLICY_ROOT is not a procedure of this account.
#! - NEW_POLICY_ROOT is not in the allowed roots map.
#!
#! Invocation: call
pub proc set_burn_policy
    exec.authority::assert_authorized
    # => [NEW_POLICY_ROOT, pad(12)]

    exec.assert_existing_burn_policy_root
    # => [NEW_POLICY_ROOT, pad(12)]

    exec.assert_allowed_burn_policy_root
    # => [NEW_POLICY_ROOT, pad(12)]

    push.ACTIVE_BURN_POLICY_PROC_ROOT_SLOT[0..2] exec.native_account::set_item dropw
    # => [pad(16)]
end

# SEND PUBLIC INTERFACE
# ================================================================================================

#! Returns active send policy root (reads the protocol-reserved callback slot).
#!
#! Inputs:  [pad(16)]
#! Outputs: [SEND_POLICY_ROOT, pad(12)]
#!
#! Invocation: call
pub proc get_send_policy
    push.ON_BEFORE_ASSET_ADDED_TO_NOTE_SLOT[0..2] exec.active_account::get_item
    # => [SEND_POLICY_ROOT, pad(12)]
end

#! Sets active send policy root by writing directly into the protocol-reserved callback slot
#! `miden::protocol::faucet::callback::on_before_asset_added_to_note`. The kernel will dispatch
#! to whatever root is stored there via `call` the next time an asset with the callback flag is
#! added to an output note.
#!
#! Inputs:  [NEW_POLICY_ROOT, pad(12)]
#! Outputs: [pad(16)]
#!
#! Panics if:
#! - the caller is not authorized per the installed [`Authority`].
#! - NEW_POLICY_ROOT is zero.
#! - NEW_POLICY_ROOT is not a procedure of this account.
#! - NEW_POLICY_ROOT is not in the allowed roots map.
#!
#! Invocation: call
pub proc set_send_policy
    exec.authority::assert_authorized
    # => [NEW_POLICY_ROOT, pad(12)]

    exec.assert_existing_send_policy_root
    # => [NEW_POLICY_ROOT, pad(12)]

    exec.assert_allowed_send_policy_root
    # => [NEW_POLICY_ROOT, pad(12)]

    push.ON_BEFORE_ASSET_ADDED_TO_NOTE_SLOT[0..2] exec.native_account::set_item dropw
    # => [pad(16)]
end

# RECEIVE PUBLIC INTERFACE
# ================================================================================================

#! Returns active receive policy root (reads the protocol-reserved callback slot).
#!
#! Inputs:  [pad(16)]
#! Outputs: [RECEIVE_POLICY_ROOT, pad(12)]
#!
#! Invocation: call
pub proc get_receive_policy
    push.ON_BEFORE_ASSET_ADDED_TO_ACCOUNT_SLOT[0..2] exec.active_account::get_item
    # => [RECEIVE_POLICY_ROOT, pad(12)]
end

#! Sets active receive policy root by writing directly into the protocol-reserved callback slot
#! `miden::protocol::faucet::callback::on_before_asset_added_to_account`. The kernel will
#! dispatch to whatever root is stored there via `call` the next time an asset with the callback
#! flag is added to an account vault.
#!
#! Inputs:  [NEW_POLICY_ROOT, pad(12)]
#! Outputs: [pad(16)]
#!
#! Panics if:
#! - the caller is not authorized per the installed [`Authority`].
#! - NEW_POLICY_ROOT is zero.
#! - NEW_POLICY_ROOT is not a procedure of this account.
#! - NEW_POLICY_ROOT is not in the allowed roots map.
#!
#! Invocation: call
pub proc set_receive_policy
    exec.authority::assert_authorized
    # => [NEW_POLICY_ROOT, pad(12)]

    exec.assert_existing_receive_policy_root
    # => [NEW_POLICY_ROOT, pad(12)]

    exec.assert_allowed_receive_policy_root
    # => [NEW_POLICY_ROOT, pad(12)]

    push.ON_BEFORE_ASSET_ADDED_TO_ACCOUNT_SLOT[0..2] exec.native_account::set_item dropw
    # => [pad(16)]
end
