// file: kb_demo_app/src/demo3.rs

//! Tauri commands for Demo3 local DEX corpus search.
//!
//! Demo3 is intentionally a thin UI-facing wrapper around `kb_lib` local
//! corpus search services. It does not contain DEX decoding or protocol logic.

use tauri::Manager;
use ts_rs::TS;

/// Request payload for a local DEX corpus search.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3LocalDexCorpusSearchRequest {
    /// Optional DEX code or decoded protocol name.
    pub dex_code: std::option::Option<std::string::String>,
    /// Optional Solana program id.
    pub program_id: std::option::Option<std::string::String>,
    /// Optional local pair id.
    #[ts(type = "number | null")]
    pub pair_id: std::option::Option<i64>,
    /// Optional pool account/address.
    pub pool_address: std::option::Option<std::string::String>,
    /// Optional token mint to match as base, quote or decoded mint.
    pub token_mint: std::option::Option<std::string::String>,
    /// Optional transaction signature.
    pub signature: std::option::Option<std::string::String>,
    /// Maximum number of rows to return per sample category.
    pub limit: u32,
}

/// Response payload returned by Demo3 local DEX corpus search.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchPayload.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3LocalDexCorpusSearchPayload {
    /// Open database URL.
    pub database_url: std::string::String,
    /// Pretty JSON representation of the search result.
    pub result_json: std::string::String,
    /// Structured local DEX corpus search result.
    pub result: Demo3LocalDexCorpusSearchResult,
}

/// Structured local DEX corpus search result.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchResult.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3LocalDexCorpusSearchResult {
    /// Normalized search request applied by the backend service.
    pub request: Demo3LocalDexCorpusSearchRequest,
    /// Aggregate counts for the matching local data.
    pub summary: Demo3LocalDexCorpusSearchSummary,
    /// Matching transaction samples.
    pub transaction_samples: std::vec::Vec<Demo3LocalDexCorpusTransactionSample>,
    /// Matching pool/pair samples.
    pub pool_pair_samples: std::vec::Vec<Demo3LocalDexCorpusPoolPairSample>,
    /// Matching decoded event samples.
    pub decoded_event_samples: std::vec::Vec<Demo3LocalDexCorpusDecodedEventSample>,
}

/// Aggregate counts for one local DEX corpus search.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusSearchSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3LocalDexCorpusSearchSummary {
    /// Number of distinct matching transactions.
    #[ts(type = "number")]
    pub transaction_count: i64,
    /// Number of distinct matching instructions.
    #[ts(type = "number")]
    pub instruction_count: i64,
    /// Number of distinct matching decoded DEX events.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Number of distinct matching pools.
    #[ts(type = "number")]
    pub pool_count: i64,
    /// Number of distinct matching pairs.
    #[ts(type = "number")]
    pub pair_count: i64,
    /// Number of distinct matching trade events.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Number of distinct matching candle rows.
    #[ts(type = "number")]
    pub pair_candle_count: i64,
    /// Number of distinct matching protocol candidate rows.
    #[ts(type = "number")]
    pub protocol_candidate_count: i64,
}

/// Matching transaction sample for corpus discovery.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusTransactionSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3LocalDexCorpusTransactionSample {
    /// Transaction id.
    #[ts(type = "number")]
    pub transaction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Optional Solana slot.
    #[ts(type = "number | null")]
    pub slot: std::option::Option<i64>,
    /// Whether the transaction has a non-null error payload.
    pub failed: bool,
    /// Number of persisted instructions for the transaction.
    #[ts(type = "number")]
    pub instruction_count: i64,
    /// Number of persisted decoded DEX events for the transaction.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Number of persisted trade events for the transaction.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Comma-separated distinct program ids seen in the transaction.
    pub program_ids_csv: std::string::String,
}

/// Matching pool/pair sample for corpus discovery.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusPoolPairSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3LocalDexCorpusPoolPairSample {
    /// Optional pool id.
    #[ts(type = "number | null")]
    pub pool_id: std::option::Option<i64>,
    /// Optional pool address.
    pub pool_address: std::option::Option<std::string::String>,
    /// Optional pair id.
    #[ts(type = "number | null")]
    pub pair_id: std::option::Option<i64>,
    /// Optional DEX code.
    pub dex_code: std::option::Option<std::string::String>,
    /// Optional pair symbol.
    pub pair_symbol: std::option::Option<std::string::String>,
    /// Optional base token mint.
    pub base_mint: std::option::Option<std::string::String>,
    /// Optional base token symbol.
    pub base_symbol: std::option::Option<std::string::String>,
    /// Optional quote token mint.
    pub quote_mint: std::option::Option<std::string::String>,
    /// Optional quote token symbol.
    pub quote_symbol: std::option::Option<std::string::String>,
    /// Number of decoded events attached to the pool.
    #[ts(type = "number")]
    pub decoded_event_count: i64,
    /// Number of trade events attached to the pair.
    #[ts(type = "number")]
    pub trade_event_count: i64,
    /// Number of candle rows attached to the pair.
    #[ts(type = "number")]
    pub pair_candle_count: i64,
    /// Latest known founding or activity signature for the pool/pair.
    pub latest_signature: std::option::Option<std::string::String>,
}

/// Matching decoded event sample for corpus discovery.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/Demo3LocalDexCorpusDecodedEventSample.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3LocalDexCorpusDecodedEventSample {
    /// Decoded event id.
    #[ts(type = "number")]
    pub decoded_event_id: i64,
    /// Transaction id.
    #[ts(type = "number")]
    pub transaction_id: i64,
    /// Transaction signature.
    pub signature: std::string::String,
    /// Optional Solana slot.
    #[ts(type = "number | null")]
    pub slot: std::option::Option<i64>,
    /// Protocol name stored on the decoded event.
    pub protocol_name: std::string::String,
    /// Program id stored on the decoded event.
    pub program_id: std::string::String,
    /// Event kind.
    pub event_kind: std::string::String,
    /// Optional pool account.
    pub pool_account: std::option::Option<std::string::String>,
    /// Optional token A mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Optional token B mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Decoded event category extracted from payload JSON.
    pub event_category: std::option::Option<std::string::String>,
    /// Decoded event lifecycle kind extracted from payload JSON.
    pub event_lifecycle_kind: std::option::Option<std::string::String>,
    /// Decoded event actionability extracted from payload JSON.
    pub event_actionability: std::option::Option<std::string::String>,
    /// Whether the decoded event is a trade candidate.
    pub trade_candidate: bool,
    /// Whether the decoded event is a candle candidate.
    pub candle_candidate: bool,
}

/// Opens the `Demo3` local DEX corpus search window.
#[tauri::command]
pub(crate) fn open_demo3_window(app_handle: tauri::AppHandle) -> Result<(), std::string::String> {
    let existing_window_option = app_handle.get_webview_window("demo3");

    let demo_window = match existing_window_option {
        Some(demo_window) => demo_window,
        None => {
            let builder = tauri::WebviewWindowBuilder::new(
                &app_handle,
                "demo3",
                tauri::WebviewUrl::App("demo3.html".into()),
            )
            .title("Demo3 Local DEX Corpus")
            .inner_size(1480.0, 920.0)
            .min_inner_size(1100.0, 720.0)
            .center()
            .visible(true)
            .transparent(false)
            .decorations(true);
            let build_result = builder.build();
            match build_result {
                Ok(window) => window,
                Err(error) => {
                    return Err(format!("cannot create demo3 window: {error:?}"));
                },
            }
        },
    };
    let show_result = demo_window.show();
    if let Err(error) = show_result {
        return Err(format!("cannot show demo3 window: {error:?}"));
    }
    let focus_result = demo_window.set_focus();
    if let Err(error) = focus_result {
        return Err(format!("cannot focus demo3 window: {error:?}"));
    }
    Ok(())
}

/// Searches local DEX corpus candidates from the persisted SQLite pipeline.
#[tauri::command]
pub(crate) async fn demo3_search_local_dex_corpus(
    state: tauri::State<'_, crate::AppState>,
    request: Demo3LocalDexCorpusSearchRequest,
) -> Result<Demo3LocalDexCorpusSearchPayload, std::string::String> {
    let database = state.database.clone();
    let service = kb_lib::LocalDexCorpusSearchService::new(database.clone());
    let search_result = service.search(to_lib_search_request(&request)).await;
    let lib_result = match search_result {
        Ok(result) => result,
        Err(error) => {
            return Err(format!("local DEX corpus search failed: {}", error));
        },
    };
    let ui_result = from_lib_search_result(lib_result);
    let result_json_result = serde_json::to_string_pretty(&ui_result);
    let result_json = match result_json_result {
        Ok(result_json) => result_json,
        Err(error) => {
            return Err(format!("cannot serialize local DEX corpus search result: {}", error));
        },
    };
    Ok(Demo3LocalDexCorpusSearchPayload {
        database_url: database.database_url().to_string(),
        result_json,
        result: ui_result,
    })
}

/// Search request for the static upstream registry exposed through Demo3.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/Demo3UpstreamRegistrySearchRequest.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3UpstreamRegistrySearchRequest {
    /// Optional decoder-code filter.
    pub decoder_code: std::option::Option<std::string::String>,
    /// Optional program-id filter.
    pub program_id: std::option::Option<std::string::String>,
    /// Optional program-family filter.
    pub program_family: std::option::Option<std::string::String>,
    /// Optional surface-kind filter.
    pub surface_kind: std::option::Option<std::string::String>,
    /// Optional entry-kind filter.
    pub entry_kind: std::option::Option<std::string::String>,
    /// Optional proof-status filter.
    pub proof_status: std::option::Option<std::string::String>,
    /// Optional maximum number of entries to return.
    pub limit: std::option::Option<u32>,
}

/// Payload returned by the Demo3 upstream registry command.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3UpstreamRegistryPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3UpstreamRegistryPayload {
    /// Pretty JSON representation of the registry result.
    pub result_json: std::string::String,
    /// Structured registry result.
    pub result: Demo3UpstreamRegistryResult,
}

/// Structured upstream registry result exposed through Demo3.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3UpstreamRegistryResult.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3UpstreamRegistryResult {
    /// Normalized request used by kb_lib.
    pub request: Demo3UpstreamRegistrySearchRequest,
    /// Registry summary.
    pub summary: Demo3UpstreamRegistrySummary,
    /// Matching entries.
    pub entries: std::vec::Vec<Demo3UpstreamRegistryEntry>,
}

/// Summary of the upstream registry snapshot exposed through Demo3.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3UpstreamRegistrySummary.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3UpstreamRegistrySummary {
    /// Total static registry entry count before filtering.
    #[ts(type = "number")]
    pub total_entry_count: usize,
    /// Returned entry count after filtering.
    #[ts(type = "number")]
    pub returned_entry_count: usize,
    /// Number of entries that have a program id.
    #[ts(type = "number")]
    pub entries_with_program_id_count: usize,
    /// Number of entries that have a discriminator.
    #[ts(type = "number")]
    pub entries_with_discriminator_count: usize,
    /// Number of program-level seed entries.
    #[ts(type = "number")]
    pub program_entry_count: usize,
    /// Number of instruction entries.
    #[ts(type = "number")]
    pub instruction_entry_count: usize,
    /// Number of event entries.
    #[ts(type = "number")]
    pub event_entry_count: usize,
    /// Number of account entries.
    #[ts(type = "number")]
    pub account_entry_count: usize,
    /// Number of entries still unverified from upstream Git or seed data.
    #[ts(type = "number")]
    pub upstream_git_unverified_count: usize,
    /// Number of entries mapped into decoders but not locally observed.
    #[ts(type = "number")]
    pub upstream_git_mapped_unverified_count: usize,
    /// Number of entries observed in the local corpus.
    #[ts(type = "number")]
    pub upstream_git_local_corpus_observed_count: usize,
    /// Number of entries materialized in local business tables.
    #[ts(type = "number")]
    pub upstream_git_local_corpus_materialized_count: usize,
    /// Number of layout entries still unverified locally.
    #[ts(type = "number")]
    pub upstream_git_layout_unverified_count: usize,
}

/// One upstream registry entry exposed through Demo3.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3UpstreamRegistryEntry.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3UpstreamRegistryEntry {
    /// Repository name or bootstrap locator that produced the entry.
    pub source_repo: std::option::Option<std::string::String>,
    /// Repository-relative path or bootstrap path that produced the entry.
    pub source_path: std::option::Option<std::string::String>,
    /// Stable decoder code used by the registry.
    pub decoder_code: std::string::String,
    /// Optional Solana program id when already known by the source entry.
    pub program_id: std::option::Option<std::string::String>,
    /// Program family used to group related programs.
    pub program_family: std::string::String,
    /// Surface kind such as AMM, CLMM, launch, aggregator or core Solana.
    pub surface_kind: std::string::String,
    /// Entry kind: instruction, event, account or program.
    pub entry_kind: std::string::String,
    /// Source-level entry name.
    pub entry_name: std::string::String,
    /// Optional discriminator bytes encoded as lowercase hexadecimal.
    pub discriminator_hex: std::option::Option<std::string::String>,
    /// Optional discriminator byte length.
    pub discriminator_len: std::option::Option<u16>,
    /// Current proof status.
    pub proof_status: std::string::String,
    /// Notes that preserve uncertainty and validation requirements.
    pub notes: std::string::String,
}

/// Searches the static upstream registry from Demo3.
#[tauri::command]
pub(crate) fn demo3_search_upstream_registry(
    request: Demo3UpstreamRegistrySearchRequest,
) -> Result<Demo3UpstreamRegistryPayload, std::string::String> {
    let service = kb_lib::UpstreamRegistryService::new();
    let lib_request = to_lib_upstream_registry_request(&request);
    let lib_result = service.search(&lib_request);
    let ui_result = from_lib_upstream_registry_result(lib_result);
    let result_json_result = serde_json::to_string_pretty(&ui_result);
    let result_json = match result_json_result {
        Ok(result_json) => result_json,
        Err(error) => {
            return Err(format!("cannot serialize upstream registry result: {}", error));
        },
    };
    return Ok(Demo3UpstreamRegistryPayload { result_json, result: ui_result });
}

fn to_lib_upstream_registry_request(
    request: &Demo3UpstreamRegistrySearchRequest,
) -> kb_lib::UpstreamRegistrySearchRequestDto {
    return kb_lib::UpstreamRegistrySearchRequestDto {
        decoder_code: normalize_optional_text(request.decoder_code.clone()),
        program_id: normalize_optional_text(request.program_id.clone()),
        program_family: normalize_optional_text(request.program_family.clone()),
        surface_kind: normalize_optional_text(request.surface_kind.clone()),
        entry_kind: normalize_optional_text(request.entry_kind.clone()),
        proof_status: normalize_optional_text(request.proof_status.clone()),
        limit: match request.limit {
            Some(limit) => Some(limit as usize),
            None => None,
        },
    };
}

fn from_lib_upstream_registry_result(
    result: kb_lib::UpstreamRegistrySearchResultDto,
) -> Demo3UpstreamRegistryResult {
    let mut entries = std::vec::Vec::new();
    for entry in result.entries {
        entries.push(from_lib_upstream_registry_entry(entry));
    }
    return Demo3UpstreamRegistryResult {
        request: from_lib_upstream_registry_request(result.request),
        summary: from_lib_upstream_registry_summary(result.summary),
        entries,
    };
}

fn from_lib_upstream_registry_request(
    request: kb_lib::UpstreamRegistrySearchRequestDto,
) -> Demo3UpstreamRegistrySearchRequest {
    return Demo3UpstreamRegistrySearchRequest {
        decoder_code: request.decoder_code,
        program_id: request.program_id,
        program_family: request.program_family,
        surface_kind: request.surface_kind,
        entry_kind: request.entry_kind,
        proof_status: request.proof_status,
        limit: match request.limit {
            Some(limit) => Some(limit as u32),
            None => None,
        },
    };
}

fn from_lib_upstream_registry_summary(
    summary: kb_lib::UpstreamRegistrySummaryDto,
) -> Demo3UpstreamRegistrySummary {
    return Demo3UpstreamRegistrySummary {
        total_entry_count: summary.total_entry_count,
        returned_entry_count: summary.returned_entry_count,
        entries_with_program_id_count: summary.entries_with_program_id_count,
        entries_with_discriminator_count: summary.entries_with_discriminator_count,
        program_entry_count: summary.program_entry_count,
        instruction_entry_count: summary.instruction_entry_count,
        event_entry_count: summary.event_entry_count,
        account_entry_count: summary.account_entry_count,
        upstream_git_unverified_count: summary.upstream_git_unverified_count,
        upstream_git_mapped_unverified_count: summary.upstream_git_mapped_unverified_count,
        upstream_git_local_corpus_observed_count: summary.upstream_git_local_corpus_observed_count,
        upstream_git_local_corpus_materialized_count: summary
            .upstream_git_local_corpus_materialized_count,
        upstream_git_layout_unverified_count: summary.upstream_git_layout_unverified_count,
    };
}

fn from_lib_upstream_registry_entry(
    entry: kb_lib::UpstreamRegistryEntryDto,
) -> Demo3UpstreamRegistryEntry {
    return Demo3UpstreamRegistryEntry {
        source_repo: entry.source_repo,
        source_path: entry.source_path,
        decoder_code: entry.decoder_code,
        program_id: entry.program_id,
        program_family: entry.program_family,
        surface_kind: entry.surface_kind,
        entry_kind: entry.entry_kind,
        entry_name: entry.entry_name,
        discriminator_hex: entry.discriminator_hex,
        discriminator_len: entry.discriminator_len,
        proof_status: entry.proof_status,
        notes: entry.notes,
    };
}

fn to_lib_search_request(
    request: &Demo3LocalDexCorpusSearchRequest,
) -> kb_lib::LocalDexCorpusSearchRequestDto {
    return kb_lib::LocalDexCorpusSearchRequestDto {
        dex_code: normalize_optional_text(request.dex_code.clone()),
        program_id: normalize_optional_text(request.program_id.clone()),
        pair_id: request.pair_id,
        pool_address: normalize_optional_text(request.pool_address.clone()),
        token_mint: normalize_optional_text(request.token_mint.clone()),
        signature: normalize_optional_text(request.signature.clone()),
        limit: request.limit,
    };
}

fn normalize_optional_text(
    value: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
    let value = match value {
        Some(value) => value.trim().to_string(),
        None => return None,
    };
    if value.is_empty() {
        return None;
    }
    return Some(value);
}

fn from_lib_search_result(
    result: kb_lib::LocalDexCorpusSearchResultDto,
) -> Demo3LocalDexCorpusSearchResult {
    let mut transaction_samples = std::vec::Vec::new();
    for sample in result.transaction_samples {
        transaction_samples.push(from_lib_transaction_sample(sample));
    }
    let mut pool_pair_samples = std::vec::Vec::new();
    for sample in result.pool_pair_samples {
        pool_pair_samples.push(from_lib_pool_pair_sample(sample));
    }
    let mut decoded_event_samples = std::vec::Vec::new();
    for sample in result.decoded_event_samples {
        decoded_event_samples.push(from_lib_decoded_event_sample(sample));
    }
    return Demo3LocalDexCorpusSearchResult {
        request: from_lib_request(result.request),
        summary: from_lib_summary(result.summary),
        transaction_samples,
        pool_pair_samples,
        decoded_event_samples,
    };
}

fn from_lib_request(
    request: kb_lib::LocalDexCorpusSearchRequestDto,
) -> Demo3LocalDexCorpusSearchRequest {
    return Demo3LocalDexCorpusSearchRequest {
        dex_code: request.dex_code,
        program_id: request.program_id,
        pair_id: request.pair_id,
        pool_address: request.pool_address,
        token_mint: request.token_mint,
        signature: request.signature,
        limit: request.limit,
    };
}

fn from_lib_summary(
    summary: kb_lib::LocalDexCorpusSearchSummaryDto,
) -> Demo3LocalDexCorpusSearchSummary {
    return Demo3LocalDexCorpusSearchSummary {
        transaction_count: summary.transaction_count,
        instruction_count: summary.instruction_count,
        decoded_event_count: summary.decoded_event_count,
        pool_count: summary.pool_count,
        pair_count: summary.pair_count,
        trade_event_count: summary.trade_event_count,
        pair_candle_count: summary.pair_candle_count,
        protocol_candidate_count: summary.protocol_candidate_count,
    };
}

fn from_lib_transaction_sample(
    sample: kb_lib::LocalDexCorpusTransactionSampleDto,
) -> Demo3LocalDexCorpusTransactionSample {
    return Demo3LocalDexCorpusTransactionSample {
        transaction_id: sample.transaction_id,
        signature: sample.signature,
        slot: sample.slot,
        failed: sample.failed,
        instruction_count: sample.instruction_count,
        decoded_event_count: sample.decoded_event_count,
        trade_event_count: sample.trade_event_count,
        program_ids_csv: sample.program_ids_csv,
    };
}

fn from_lib_pool_pair_sample(
    sample: kb_lib::LocalDexCorpusPoolPairSampleDto,
) -> Demo3LocalDexCorpusPoolPairSample {
    return Demo3LocalDexCorpusPoolPairSample {
        pool_id: sample.pool_id,
        pool_address: sample.pool_address,
        pair_id: sample.pair_id,
        dex_code: sample.dex_code,
        pair_symbol: sample.pair_symbol,
        base_mint: sample.base_mint,
        base_symbol: sample.base_symbol,
        quote_mint: sample.quote_mint,
        quote_symbol: sample.quote_symbol,
        decoded_event_count: sample.decoded_event_count,
        trade_event_count: sample.trade_event_count,
        pair_candle_count: sample.pair_candle_count,
        latest_signature: sample.latest_signature,
    };
}

fn from_lib_decoded_event_sample(
    sample: kb_lib::LocalDexCorpusDecodedEventSampleDto,
) -> Demo3LocalDexCorpusDecodedEventSample {
    return Demo3LocalDexCorpusDecodedEventSample {
        decoded_event_id: sample.decoded_event_id,
        transaction_id: sample.transaction_id,
        signature: sample.signature,
        slot: sample.slot,
        protocol_name: sample.protocol_name,
        program_id: sample.program_id,
        event_kind: sample.event_kind,
        pool_account: sample.pool_account,
        token_a_mint: sample.token_a_mint,
        token_b_mint: sample.token_b_mint,
        event_category: sample.event_category,
        event_lifecycle_kind: sample.event_lifecycle_kind,
        event_actionability: sample.event_actionability,
        trade_candidate: sample.trade_candidate,
        candle_candidate: sample.candle_candidate,
    };
}

/// Request payload for on-chain DEX pair/pool discovery.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexDiscoveryRequest.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexDiscoveryRequest {
    /// Optional DEX code from the support matrix.
    pub dex_code: std::option::Option<std::string::String>,
    /// Optional Solana program id. When absent, dex_code must resolve to a verified program id.
    pub program_id: std::option::Option<std::string::String>,
    /// Optional signature source: `program_id` or `address`.
    pub signature_source: std::option::Option<std::string::String>,
    /// Optional source address used when signature_source is `address`.
    pub source_address: std::option::Option<std::string::String>,
    /// Optional extra source addresses used for multi-pool discovery.
    #[serde(default)]
    pub source_addresses: std::vec::Vec<std::string::String>,
    /// Optional `before` cursor passed to Solana getSignaturesForAddress.
    #[serde(default)]
    pub before_signature: std::option::Option<std::string::String>,
    /// Optional `until` cursor passed to Solana getSignaturesForAddress.
    #[serde(default)]
    pub until_signature: std::option::Option<std::string::String>,
    /// Maximum number of signature pages to fetch per source address.
    #[serde(default)]
    pub max_pages: u32,
    /// Signature processing order: newest_first or oldest_first.
    #[serde(default)]
    pub scan_order: std::option::Option<std::string::String>,
    /// Optional target event family used to find non-swap signatures.
    pub target_event: std::option::Option<std::string::String>,
    /// Optional instruction name filter, e.g. `withdraw` or `raydium_cpmm.withdraw`.
    #[serde(default)]
    pub target_instruction_name: std::option::Option<std::string::String>,
    /// Optional instruction discriminator filter as 16-char lower hex, comma-separated when needed.
    #[serde(default)]
    pub target_discriminator_hex: std::option::Option<std::string::String>,
    /// Whether transactions containing swap-like logs should be skipped.
    pub exclude_swaps: bool,
    /// Whether failed transactions should be returned as candidates.
    pub include_failed: bool,
    /// HTTP role used to query Solana RPC.
    pub http_role: std::string::String,
    /// Maximum number of signatures to inspect.
    pub signature_limit: u32,
    /// Maximum number of transactions to fetch from the signature list.
    pub transaction_limit: u32,
    /// Maximum number of candidate rows to return.
    pub candidate_limit: u32,
}

/// Response payload returned by Demo3 on-chain DEX discovery.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexDiscoveryPayload.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexDiscoveryPayload {
    /// HTTP role used by the request.
    pub http_role: std::string::String,
    /// Pretty JSON representation of the discovery result.
    pub result_json: std::string::String,
    /// Structured discovery result.
    pub result: Demo3OnchainDexDiscoveryResult,
}

/// Structured on-chain DEX discovery result.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexDiscoveryResult.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexDiscoveryResult {
    /// Normalized request used by kb_lib.
    pub request: Demo3OnchainDexDiscoveryRequest,
    /// DEX code resolved from the support matrix when available.
    pub resolved_dex_code: std::option::Option<std::string::String>,
    /// Program id used to filter matched instructions.
    pub resolved_program_id: std::string::String,
    /// Signature source actually used by getSignaturesForAddress.
    pub resolved_signature_source: std::string::String,
    /// Address scanned with getSignaturesForAddress.
    pub resolved_signature_address: std::string::String,
    /// All addresses scanned with getSignaturesForAddress.
    pub resolved_signature_addresses: std::vec::Vec<std::string::String>,
    /// Cursor hints by scanned address.
    pub next_before_by_address: std::vec::Vec<Demo3OnchainDexPaginationCursor>,
    /// Number of signature pages fetched.
    #[ts(type = "number")]
    pub fetched_signature_page_count: usize,
    /// Number of unique fetched signatures after de-duplication.
    #[ts(type = "number")]
    pub unique_fetched_signature_count: usize,
    /// Number of unique candidate signatures.
    #[ts(type = "number")]
    pub unique_signature_count: usize,
    /// Unique signatures ready for signature backfill.
    pub unique_backfill_signatures: std::vec::Vec<std::string::String>,
    /// Rejected candidate summary.
    pub rejected_candidate_summary: std::vec::Vec<Demo3OnchainDexRejectedCandidateSummary>,
    /// Number of signatures returned by Solana RPC.
    #[ts(type = "number")]
    pub fetched_signature_count: usize,
    /// Number of fetched transactions.
    #[ts(type = "number")]
    pub fetched_transaction_count: usize,
    /// Number of getTransaction calls returning null.
    #[ts(type = "number")]
    pub missing_transaction_count: usize,
    /// Number of failed transactions encountered.
    #[ts(type = "number")]
    pub failed_transaction_count: usize,
    /// Number of failed transactions skipped because include_failed is false.
    #[ts(type = "number")]
    pub skipped_failed_transaction_count: usize,
    /// Number of swap-log transactions skipped by the transaction-level swap guard.
    #[ts(type = "number")]
    pub skipped_swap_log_transaction_count: usize,
    /// Number of candidate rows extracted before target-event filtering.
    #[ts(type = "number")]
    pub extracted_candidate_count: usize,
    /// Number of candidate rows rejected by target-event filtering.
    #[ts(type = "number")]
    pub target_rejected_candidate_count: usize,
    /// Number of candidate rows returned.
    #[ts(type = "number")]
    pub candidate_count: usize,
    /// Candidate on-chain rows.
    pub candidates: std::vec::Vec<Demo3OnchainDexPairCandidate>,
}

/// Pagination cursor hint for one scanned source address.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexPaginationCursor.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexPaginationCursor {
    /// Scanned source address.
    pub address: std::string::String,
    /// Signature usable as beforeSignature for the next page window.
    pub next_before_signature: std::option::Option<std::string::String>,
    /// Raw signature count fetched for this address.
    #[ts(type = "number")]
    pub fetched_signature_count: usize,
    /// Page count fetched for this address.
    #[ts(type = "number")]
    pub fetched_page_count: usize,
}

/// Rejected on-chain discovery candidate summary.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/Demo3OnchainDexRejectedCandidateSummary.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexRejectedCandidateSummary {
    /// Candidate kind rejected by target filtering.
    pub candidate_kind: std::string::String,
    /// Optional instruction data prefix.
    pub instruction_data_prefix: std::option::Option<std::string::String>,
    /// Optional instruction name.
    pub instruction_name: std::option::Option<std::string::String>,
    /// Rejection reason.
    pub rejection_reason: std::string::String,
    /// Count of matching rejected candidates.
    #[ts(type = "number")]
    pub count: usize,
}

/// Candidate on-chain transaction/instruction for a DEX program id.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexPairCandidate.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexPairCandidate {
    /// Transaction signature.
    pub signature: std::string::String,
    /// Slot when available.
    #[ts(type = "number | null")]
    pub slot: std::option::Option<u64>,
    /// Block time when available.
    #[ts(type = "number | null")]
    pub block_time: std::option::Option<i64>,
    /// Whether the transaction failed.
    pub failed: bool,
    /// Program id matched by the candidate.
    pub program_id: std::string::String,
    /// DEX code when known.
    pub dex_code: std::option::Option<std::string::String>,
    /// Candidate kind inferred from data/logs.
    pub candidate_kind: std::string::String,
    /// Extraction confidence.
    pub confidence: std::string::String,
    /// Top-level instruction index.
    #[ts(type = "number | null")]
    pub instruction_index: std::option::Option<i64>,
    /// Inner instruction index.
    #[ts(type = "number | null")]
    pub inner_instruction_index: std::option::Option<i64>,
    /// Instruction name inferred from data/logs.
    pub instruction_name: std::option::Option<std::string::String>,
    /// Prefix of the raw base58 instruction data, useful for audit grouping.
    pub instruction_data_prefix: std::option::Option<std::string::String>,
    /// First eight instruction-data bytes as lower hex.
    pub instruction_discriminator_hex: std::option::Option<std::string::String>,
    /// Candidate pool address.
    pub pool_address: std::option::Option<std::string::String>,
    /// Candidate token A mint.
    pub token_a_mint: std::option::Option<std::string::String>,
    /// Candidate token B mint.
    pub token_b_mint: std::option::Option<std::string::String>,
    /// Verified pool address when a DEX decoder or stable layout proves it.
    pub verified_pool_address: std::option::Option<std::string::String>,
    /// Token mints observed generically from transaction token balances.
    pub observed_token_mints: std::vec::Vec<std::string::String>,
    /// Token balance deltas observed through transaction metadata.
    pub token_balance_deltas: std::vec::Vec<Demo3OnchainDexTokenBalanceDelta>,
    /// Program-owned or writable accounts that may be pool/config/state accounts.
    pub candidate_pool_accounts: std::vec::Vec<Demo3OnchainDexCandidateAccount>,
    /// Token accounts that may be pool vaults.
    pub candidate_token_vault_accounts: std::vec::Vec<Demo3OnchainDexCandidateAccount>,
    /// Other candidate accounts attached to the matched instruction.
    pub candidate_program_accounts: std::vec::Vec<Demo3OnchainDexCandidateAccount>,
    /// Short account sample.
    pub account_samples: std::vec::Vec<std::string::String>,
    /// Short log sample.
    pub log_samples: std::vec::Vec<std::string::String>,
    /// Suggested next action.
    pub backfill_hint: std::string::String,
}

/// Token-balance delta observed in one on-chain candidate transaction.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
    export,
    export_to = "../frontend/ts/bindings/Demo3OnchainDexTokenBalanceDelta.ts"
)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexTokenBalanceDelta {
    /// Token account index in the transaction message when available.
    #[ts(type = "number | null")]
    pub account_index: std::option::Option<i64>,
    /// Token account address resolved from the transaction account keys.
    pub account_address: std::option::Option<std::string::String>,
    /// SPL Token or Token-2022 mint address.
    pub mint: std::string::String,
    /// Token account owner when Solana RPC exposes it.
    pub owner: std::option::Option<std::string::String>,
    /// Token program id when Solana RPC exposes it.
    pub token_program: std::option::Option<std::string::String>,
    /// Raw token amount before the transaction.
    pub pre_amount_raw: std::option::Option<std::string::String>,
    /// Raw token amount after the transaction.
    pub post_amount_raw: std::option::Option<std::string::String>,
    /// Signed raw delta when calculable.
    pub delta_raw: std::option::Option<std::string::String>,
}

/// Candidate account inferred from generic on-chain transaction evidence.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/Demo3OnchainDexCandidateAccount.ts")]
#[serde(rename_all = "camelCase")]
pub(crate) struct Demo3OnchainDexCandidateAccount {
    /// Account address.
    pub address: std::string::String,
    /// Account index in the transaction message when known.
    #[ts(type = "number | null")]
    pub account_index: std::option::Option<i64>,
    /// Whether the account is writable in the transaction message when known.
    pub writable: std::option::Option<bool>,
    /// Whether the account is a signer in the transaction message when known.
    pub signer: std::option::Option<bool>,
    /// Generic role inferred by Demo3.
    pub inferred_role: std::string::String,
    /// Confidence of the generic account inference.
    pub confidence: std::string::String,
    /// Short reason explaining why the account is listed.
    pub reason: std::string::String,
}

/// Discovers candidate DEX pairs/pools directly from Solana RPC.
#[tauri::command]
pub(crate) async fn demo3_discover_onchain_dex_pairs(
    state: tauri::State<'_, crate::AppState>,
    request: Demo3OnchainDexDiscoveryRequest,
) -> Result<Demo3OnchainDexDiscoveryPayload, std::string::String> {
    let http_pool = std::sync::Arc::new(state.http_pool.clone());
    let service = kb_lib::OnchainDexPairDiscoveryService::new(http_pool);
    let lib_request = to_lib_onchain_request(&request);
    let discover_result = service.discover(lib_request).await;
    let lib_result = match discover_result {
        Ok(result) => result,
        Err(error) => {
            return Err(format!("on-chain DEX discovery failed: {}", error));
        },
    };
    let ui_result = from_lib_onchain_result(lib_result);
    let result_json_result = serde_json::to_string_pretty(&ui_result);
    let result_json = match result_json_result {
        Ok(result_json) => result_json,
        Err(error) => {
            return Err(format!("cannot serialize on-chain DEX discovery result: {}", error));
        },
    };
    Ok(Demo3OnchainDexDiscoveryPayload {
        http_role: ui_result.request.http_role.clone(),
        result_json,
        result: ui_result,
    })
}

fn to_lib_onchain_request(
    request: &Demo3OnchainDexDiscoveryRequest,
) -> kb_lib::OnchainDexPairDiscoveryRequestDto {
    return kb_lib::OnchainDexPairDiscoveryRequestDto {
        dex_code: normalize_optional_text(request.dex_code.clone()),
        program_id: normalize_optional_text(request.program_id.clone()),
        signature_source: normalize_optional_text(request.signature_source.clone()),
        source_address: normalize_optional_text(request.source_address.clone()),
        source_addresses: request.source_addresses.clone(),
        before_signature: normalize_optional_text(request.before_signature.clone()),
        until_signature: normalize_optional_text(request.until_signature.clone()),
        max_pages: request.max_pages,
        scan_order: normalize_optional_text(request.scan_order.clone()),
        target_event: normalize_optional_text(request.target_event.clone()),
        target_instruction_name: normalize_optional_text(request.target_instruction_name.clone()),
        target_discriminator_hex: normalize_optional_text(request.target_discriminator_hex.clone()),
        exclude_swaps: request.exclude_swaps,
        include_failed: request.include_failed,
        http_role: request.http_role.trim().to_string(),
        signature_limit: request.signature_limit,
        transaction_limit: request.transaction_limit,
        candidate_limit: request.candidate_limit,
    };
}

fn from_lib_onchain_result(
    result: kb_lib::OnchainDexPairDiscoveryResultDto,
) -> Demo3OnchainDexDiscoveryResult {
    let mut candidates = std::vec::Vec::new();
    for candidate in result.candidates {
        candidates.push(from_lib_onchain_candidate(candidate));
    }
    return Demo3OnchainDexDiscoveryResult {
        request: Demo3OnchainDexDiscoveryRequest {
            dex_code: result.request.dex_code,
            program_id: result.request.program_id,
            signature_source: result.request.signature_source,
            source_address: result.request.source_address,
            source_addresses: result.request.source_addresses,
            before_signature: result.request.before_signature,
            until_signature: result.request.until_signature,
            max_pages: result.request.max_pages,
            scan_order: result.request.scan_order,
            target_event: result.request.target_event,
            target_instruction_name: result.request.target_instruction_name,
            target_discriminator_hex: result.request.target_discriminator_hex,
            exclude_swaps: result.request.exclude_swaps,
            include_failed: result.request.include_failed,
            http_role: result.request.http_role,
            signature_limit: result.request.signature_limit,
            transaction_limit: result.request.transaction_limit,
            candidate_limit: result.request.candidate_limit,
        },
        resolved_dex_code: result.resolved_dex_code,
        resolved_program_id: result.resolved_program_id,
        resolved_signature_source: result.resolved_signature_source,
        resolved_signature_address: result.resolved_signature_address,
        resolved_signature_addresses: result.resolved_signature_addresses,
        next_before_by_address: from_lib_onchain_pagination_cursors(result.next_before_by_address),
        fetched_signature_page_count: result.fetched_signature_page_count,
        unique_fetched_signature_count: result.unique_fetched_signature_count,
        unique_signature_count: result.unique_signature_count,
        unique_backfill_signatures: result.unique_backfill_signatures,
        rejected_candidate_summary: from_lib_rejected_candidate_summary(
            result.rejected_candidate_summary,
        ),
        fetched_signature_count: result.fetched_signature_count,
        fetched_transaction_count: result.fetched_transaction_count,
        missing_transaction_count: result.missing_transaction_count,
        failed_transaction_count: result.failed_transaction_count,
        skipped_failed_transaction_count: result.skipped_failed_transaction_count,
        skipped_swap_log_transaction_count: result.skipped_swap_log_transaction_count,
        extracted_candidate_count: result.extracted_candidate_count,
        target_rejected_candidate_count: result.target_rejected_candidate_count,
        candidate_count: result.candidate_count,
        candidates,
    };
}

fn from_lib_onchain_pagination_cursors(
    values: std::vec::Vec<kb_lib::OnchainDexPaginationCursorDto>,
) -> std::vec::Vec<Demo3OnchainDexPaginationCursor> {
    let mut mapped = std::vec::Vec::new();
    for value in values {
        mapped.push(Demo3OnchainDexPaginationCursor {
            address: value.address,
            next_before_signature: value.next_before_signature,
            fetched_signature_count: value.fetched_signature_count,
            fetched_page_count: value.fetched_page_count,
        });
    }
    return mapped;
}

fn from_lib_rejected_candidate_summary(
    values: std::vec::Vec<kb_lib::OnchainDexRejectedCandidateSummaryDto>,
) -> std::vec::Vec<Demo3OnchainDexRejectedCandidateSummary> {
    let mut mapped = std::vec::Vec::new();
    for value in values {
        mapped.push(Demo3OnchainDexRejectedCandidateSummary {
            candidate_kind: value.candidate_kind,
            instruction_data_prefix: value.instruction_data_prefix,
            instruction_name: value.instruction_name,
            rejection_reason: value.rejection_reason,
            count: value.count,
        });
    }
    return mapped;
}

fn from_lib_onchain_candidate(
    candidate: kb_lib::OnchainDexPairCandidateDto,
) -> Demo3OnchainDexPairCandidate {
    return Demo3OnchainDexPairCandidate {
        signature: candidate.signature,
        slot: candidate.slot,
        block_time: candidate.block_time,
        failed: candidate.failed,
        program_id: candidate.program_id,
        dex_code: candidate.dex_code,
        candidate_kind: candidate.candidate_kind,
        confidence: candidate.confidence,
        instruction_index: candidate.instruction_index,
        inner_instruction_index: candidate.inner_instruction_index,
        instruction_name: candidate.instruction_name,
        instruction_data_prefix: candidate.instruction_data_prefix,
        instruction_discriminator_hex: candidate.instruction_discriminator_hex,
        pool_address: candidate.pool_address,
        token_a_mint: candidate.token_a_mint,
        token_b_mint: candidate.token_b_mint,
        verified_pool_address: candidate.verified_pool_address,
        observed_token_mints: candidate.observed_token_mints,
        token_balance_deltas: from_lib_onchain_token_balance_deltas(candidate.token_balance_deltas),
        candidate_pool_accounts: from_lib_onchain_candidate_accounts(
            candidate.candidate_pool_accounts,
        ),
        candidate_token_vault_accounts: from_lib_onchain_candidate_accounts(
            candidate.candidate_token_vault_accounts,
        ),
        candidate_program_accounts: from_lib_onchain_candidate_accounts(
            candidate.candidate_program_accounts,
        ),
        account_samples: candidate.account_samples,
        log_samples: candidate.log_samples,
        backfill_hint: candidate.backfill_hint,
    };
}

fn from_lib_onchain_token_balance_deltas(
    values: std::vec::Vec<kb_lib::OnchainDexTokenBalanceDeltaDto>,
) -> std::vec::Vec<Demo3OnchainDexTokenBalanceDelta> {
    let mut mapped = std::vec::Vec::new();
    for value in values {
        mapped.push(Demo3OnchainDexTokenBalanceDelta {
            account_index: value.account_index,
            account_address: value.account_address,
            mint: value.mint,
            owner: value.owner,
            token_program: value.token_program,
            pre_amount_raw: value.pre_amount_raw,
            post_amount_raw: value.post_amount_raw,
            delta_raw: value.delta_raw,
        });
    }
    return mapped;
}

fn from_lib_onchain_candidate_accounts(
    values: std::vec::Vec<kb_lib::OnchainDexCandidateAccountDto>,
) -> std::vec::Vec<Demo3OnchainDexCandidateAccount> {
    let mut mapped = std::vec::Vec::new();
    for value in values {
        mapped.push(Demo3OnchainDexCandidateAccount {
            address: value.address,
            account_index: value.account_index,
            writable: value.writable,
            signer: value.signer,
            inferred_role: value.inferred_role,
            confidence: value.confidence,
            reason: value.reason,
        });
    }
    return mapped;
}
