//! Synor Smart Contract Template
//!
//! This template demonstrates common patterns for Synor contracts:
//! - Initialization with parameters
//! - Owner-only functions
//! - Storage patterns (simple values, maps)
//! - Event emission
//! - Error handling
//!
//! # Quick Start
//! 1. Copy this template to a new directory
//! 2. Rename the package in Cargo.toml
//! 3. Modify the storage keys, methods, and logic
//! 4. Build with: `cargo build --target wasm32-unknown-unknown --release`
//!
//! # Methods
//! - `init(name)` - Initialize the contract with a name
//! - `owner()` - Get the contract owner
//! - `get_name()` - Get the contract name
//! - `set_name(name)` - Set the contract name (owner only)
//! - `get_counter()` - Get the current counter value
//! - `increment(amount)` - Increment the counter
//! - `get_value(key)` - Get a value from the map
//! - `set_value(key, value)` - Set a value in the map

#![no_std]

extern crate alloc;

use alloc::string::String;
use alloc::vec::Vec;
use borsh::{BorshDeserialize, BorshSerialize};
use synor_sdk::prelude::*;
use synor_sdk::{require, require_auth};

// =============================================================================
// STORAGE KEYS
// =============================================================================
// Prefix your keys with your contract name to avoid collisions

mod keys {
    /// Contract owner address
    pub const OWNER: &[u8] = b"template:owner";
    /// Whether the contract has been initialized
    pub const INITIALIZED: &[u8] = b"template:initialized";
    /// Contract name
    pub const NAME: &[u8] = b"template:name";
    /// A simple counter
    pub const COUNTER: &[u8] = b"template:counter";
    /// A key-value map prefix
    pub const VALUES: &[u8] = b"template:values";
}

// =============================================================================
// DATA STRUCTURES
// =============================================================================
// Define your custom types here. Use BorshSerialize/BorshDeserialize for storage.

/// Example struct that can be stored
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
pub struct ValueEntry {
    pub value: u64,
    pub updated_at: u64,
    pub updated_by: Address,
}

// =============================================================================
// EVENTS
// =============================================================================
// Events are emitted using the `emit!` macro. Define your event structs here.

#[derive(BorshSerialize)]
pub struct NameChanged {
    pub old_name: String,
    pub new_name: String,
    pub changed_by: Address,
}

#[derive(BorshSerialize)]
pub struct CounterIncremented {
    pub old_value: u64,
    pub new_value: u64,
    pub increment: u64,
}

#[derive(BorshSerialize)]
pub struct ValueSet {
    pub key: String,
    pub value: u64,
    pub setter: Address,
}

// =============================================================================
// STORAGE HELPERS
// =============================================================================
// Helper functions to read/write storage with proper types

fn get_owner() -> Option<Address> {
    storage::get::<Address>(keys::OWNER)
}

fn is_owner(addr: &Address) -> bool {
    get_owner().map(|o| o == *addr).unwrap_or(false)
}

fn is_initialized() -> bool {
    storage::get::<bool>(keys::INITIALIZED).unwrap_or(false)
}

fn get_counter() -> u64 {
    storage::get::<u64>(keys::COUNTER).unwrap_or(0)
}

fn set_counter(value: u64) {
    storage::set(keys::COUNTER, &value);
}

fn get_name() -> String {
    storage::get::<String>(keys::NAME).unwrap_or_default()
}

fn set_name(name: &String) {
    storage::set(keys::NAME, name);
}

/// Get a value from the map using a string key
fn get_map_value(key: &str) -> Option<ValueEntry> {
    storage::get_with_suffix::<ValueEntry>(keys::VALUES, key.as_bytes())
}

/// Set a value in the map
fn set_map_value(key: &str, entry: &ValueEntry) {
    storage::set_with_suffix(keys::VALUES, key.as_bytes(), entry);
}

// =============================================================================
// ENTRY POINTS
// =============================================================================

synor_sdk::entry_point!(init, call);

/// Initialize the contract.
///
/// This is called once when the contract is deployed.
/// Parameters are passed as borsh-serialized bytes.
fn init(params: &[u8]) -> Result<()> {
    // Prevent double initialization
    require!(!is_initialized(), Error::invalid_args("Already initialized"));

    // Parse initialization parameters
    #[derive(BorshDeserialize)]
    struct InitParams {
        name: String,
    }

    let params = InitParams::try_from_slice(params)
        .map_err(|_| Error::invalid_args("Invalid init params: expected (name: String)"))?;

    // Validate parameters
    require!(
        !params.name.is_empty(),
        Error::invalid_args("Name cannot be empty")
    );

    // Set initial state
    storage::set(keys::OWNER, &caller());
    storage::set(keys::INITIALIZED, &true);
    set_name(&params.name);
    set_counter(0);

    Ok(())
}

/// Handle contract method calls.
///
/// The selector is a 4-byte method identifier (first 4 bytes of blake3 hash of method name).
/// Parameters are borsh-serialized bytes specific to each method.
fn call(selector: &[u8], params: &[u8]) -> Result<Vec<u8>> {
    // Generate method selectors at runtime
    // In production, these could be computed at compile time
    let owner_sel = synor_sdk::method_selector("owner");
    let get_name_sel = synor_sdk::method_selector("get_name");
    let set_name_sel = synor_sdk::method_selector("set_name");
    let get_counter_sel = synor_sdk::method_selector("get_counter");
    let increment_sel = synor_sdk::method_selector("increment");
    let get_value_sel = synor_sdk::method_selector("get_value");
    let set_value_sel = synor_sdk::method_selector("set_value");
    let transfer_ownership_sel = synor_sdk::method_selector("transfer_ownership");

    match selector {
        // ===== Read Methods (no state changes) =====

        s if s == owner_sel => {
            // Returns the contract owner address
            let owner = get_owner().unwrap_or(Address::zero());
            Ok(borsh::to_vec(&owner).unwrap())
        }

        s if s == get_name_sel => {
            // Returns the contract name
            let name = get_name();
            Ok(borsh::to_vec(&name).unwrap())
        }

        s if s == get_counter_sel => {
            // Returns the current counter value
            let counter = get_counter();
            Ok(borsh::to_vec(&counter).unwrap())
        }

        s if s == get_value_sel => {
            // Returns a value from the map
            #[derive(BorshDeserialize)]
            struct Args {
                key: String,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (key: String)"))?;

            let entry = get_map_value(&args.key);
            Ok(borsh::to_vec(&entry).unwrap())
        }

        // ===== Write Methods (modify state) =====

        s if s == set_name_sel => {
            // Set the contract name (owner only)
            #[derive(BorshDeserialize)]
            struct Args {
                name: String,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (name: String)"))?;

            // Only owner can change name
            let owner = get_owner().ok_or(Error::Unauthorized)?;
            require_auth!(owner);

            require!(
                !args.name.is_empty(),
                Error::invalid_args("Name cannot be empty")
            );

            let old_name = get_name();
            set_name(&args.name);

            // Emit event
            emit(&NameChanged {
                old_name,
                new_name: args.name,
                changed_by: caller(),
            });

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == increment_sel => {
            // Increment the counter by a specified amount
            #[derive(BorshDeserialize)]
            struct Args {
                amount: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (amount: u64)"))?;

            require!(args.amount > 0, Error::invalid_args("Amount must be > 0"));

            let old_value = get_counter();
            let new_value = old_value.checked_add(args.amount).ok_or(Error::Overflow)?;
            set_counter(new_value);

            // Emit event
            emit(&CounterIncremented {
                old_value,
                new_value,
                increment: args.amount,
            });

            Ok(borsh::to_vec(&new_value).unwrap())
        }

        s if s == set_value_sel => {
            // Set a value in the map
            #[derive(BorshDeserialize)]
            struct Args {
                key: String,
                value: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (key: String, value: u64)"))?;

            require!(
                !args.key.is_empty(),
                Error::invalid_args("Key cannot be empty")
            );

            let entry = ValueEntry {
                value: args.value,
                updated_at: timestamp(),
                updated_by: caller(),
            };
            set_map_value(&args.key, &entry);

            // Emit event
            emit(&ValueSet {
                key: args.key,
                value: args.value,
                setter: caller(),
            });

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == transfer_ownership_sel => {
            // Transfer ownership to a new address
            #[derive(BorshDeserialize)]
            struct Args {
                new_owner: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected (new_owner: Address)"))?;

            let current_owner = get_owner().ok_or(Error::Unauthorized)?;
            require_auth!(current_owner);

            require!(
                args.new_owner != Address::zero(),
                Error::invalid_args("Cannot transfer to zero address")
            );

            storage::set(keys::OWNER, &args.new_owner);

            // Use SDK's built-in ownership transfer event
            emit(&synor_sdk::events::OwnershipTransferred {
                previous_owner: current_owner,
                new_owner: args.new_owner,
            });

            Ok(Vec::new())
        }

        // Unknown method
        _ => Err(Error::InvalidMethod),
    }
}

// =============================================================================
// INTERNAL FUNCTIONS
// =============================================================================
// Add private helper functions here that are shared between methods

// Example: A function that requires owner authorization
#[allow(dead_code)]
fn require_owner() -> Result<Address> {
    let owner = get_owner().ok_or(Error::Unauthorized)?;
    require!(caller() == owner, Error::Unauthorized);
    Ok(owner)
}
