#![deny(clippy::string_slice)]
#![allow(
    clippy::type_complexity,
    clippy::needless_borrows_for_generic_args,
    clippy::needless_borrow,
    clippy::manual_strip,
    clippy::explicit_auto_deref,
    clippy::empty_line_after_doc_comments,
)]

//! Mad Hatter — HTTP service framework that eliminates route string literals.
//!
//! # Example
//!
//! ```ignore
//! use mad_hatter::http_service;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize)]
//! struct User { id: u64, name: String }
//!
//! #[derive(Deserialize)]
//! struct CreateUserReq { name: String }
//!
//! http_service! {
//!     service UserApi {
//!         GET    "users/{id}" => get_user(id: u64) -> Json<User>;
//!         POST   "users"     => create_user(body: CreateUserReq) -> Json<User>;
//!         DELETE "users/{id}" => delete_user(id: u64);
//!     }
//! }
//!
//! struct App;
//!
//! impl UserApiService for App {
//!     async fn get_user(&self, id: u64) -> mad_hatter::Result<axum::Json<User>> {
//!         Ok(axum::Json(User { id, name: "Alice".into() }))
//!     }
//!     async fn create_user(&self, body: CreateUserReq) -> mad_hatter::Result<axum::Json<User>> {
//!         Ok(axum::Json(User { id: 1, name: body.name }))
//!     }
//!     async fn delete_user(&self, id: u64) -> mad_hatter::Result<()> {
//!         Ok(())
//!     }
//! }
//! ```

// Re-export the proc macro
// === Public derive macros & proc macros ===
pub use mad_hatter_macros::bind_http;
pub use mad_hatter_macros::bind_server;
pub use mad_hatter_macros::bind_loops;
pub use mad_hatter_macros::main;
pub use mad_hatter_macros::JsonInclude;
pub use mad_hatter_macros::Promptable;
pub use mad_hatter_macros::Parseable;
pub use mad_hatter_macros::tunnel_service;
pub use mad_hatter_macros::json_store;
pub use mad_hatter_macros::unified_params;
pub use mad_hatter_macros::MadError;
pub use mad_hatter_macros::FormatStruct;
pub use mad_hatter_macros::FormatEnum;
pub use mad_hatter_macros::FromRow;
pub use mad_hatter_macros::i18n;
pub use mad_hatter_macros::i18n_fn;
pub use mad_hatter_macros::sealed_fn;
pub use mad_hatter_macros::transaction;

// === Internal: used by macro expansion, not part of public API ===
#[doc(hidden)] pub use mad_hatter_macros::http_service;
#[doc(hidden)] pub use mad_hatter_macros::HttpResponse;
#[doc(hidden)] pub use dao::reconcile_schema;
#[doc(hidden)] pub use dao::DaoConfig;
pub use db::DsConfig;
#[doc(hidden)] pub use db::DynamicDao;
#[doc(hidden)] pub use db::open_test_db;
#[doc(hidden)] pub use db::Db;

// Public constants removed — use ioutil::kb_to_bytes() instead.
// Internal constant kept for log_service retention calculation.
pub(crate) const SECS_PER_DAY: u64 = 86400;

/// Generate a short random hex ID of the given length (max 32).
///
/// Uses UUID v4 as entropy source. All characters are lowercase hex digits.
///
/// # Example
/// ```
/// let id = mad_hatter::short_hex_id();
/// assert_eq!(id.len(), 6);
/// assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
/// ```
pub fn short_hex_id() -> String {
    const LEN: usize = 6;
    uuid::Uuid::new_v4()
        .as_simple()
        .to_string()
        .chars()
        .take(LEN)
        .collect()
}

/// Declarative log configuration macro.
///
/// Generates constants, format functions, and chrono time format functions from declarations.
/// Guardian exempts this macro block — all string literals inside are whitelisted.
///
/// # Example
/// ```ignore
/// mad_hatter::log_config! {
///     consts {
///         CRASH_LOG: &str = "crash.log";
///         ENGINE_LOG: &str = "engine.log";
///         LOG_TIME_FORMAT: &str = "%m/%d %H:%M:%S";
///     }
///     formats {
///         fn thread_name(instance: &str) = "thread-instance-{}";
///         fn crash_log_line(timestamp: &str, msg: &str) = "[{}] {}";
///     }
///     time_formats {
///         fn format_log_time(time: &chrono::NaiveDateTime) = LOG_TIME_FORMAT;
///     }
/// }
/// ```
#[macro_export]
macro_rules! log_config {
    // Three blocks: consts + formats + time_formats
    (
        consts {
            $( $name:ident : $ty:ty = $val:expr; )*
        }
        formats {
            $( fn $fn_name:ident( $($arg:ident : $arg_ty:ty),* ) = $fmt:literal; )*
        }
        time_formats {
            $( fn $tfn_name:ident( $targ:ident : $targ_ty:ty ) = $tfmt:expr; )*
        }
    ) => {
        $( pub const $name: $ty = $val; )*
        $(
            pub fn $fn_name($($arg: $arg_ty),*) -> String {
                format!($fmt, $($arg),*)
            }
        )*
        $(
            pub fn $tfn_name($targ: $targ_ty) -> String {
                $targ.format($tfmt).to_string()
            }
        )*
    };
    // Two blocks: consts + formats (backward compatible)
    (
        consts {
            $( $name:ident : $ty:ty = $val:expr; )*
        }
        formats {
            $( fn $fn_name:ident( $($arg:ident : $arg_ty:ty),* ) = $fmt:literal; )*
        }
    ) => {
        $( pub const $name: $ty = $val; )*
        $(
            pub fn $fn_name($($arg: $arg_ty),*) -> String {
                format!($fmt, $($arg),*)
            }
        )*
    };
}
/// Declarative log service macro — generates a complete logging framework.
///
/// Covers five log modes: tracing (streaming), crash (panic hook),
/// dump (one-shot file writes), rotate (file rotation), and clean (retention).
///
/// # Example
/// ```ignore
/// mad_hatter::log_service! {
///     MyLogService,
///     tracing { time_format: "%Y-%m-%d %H:%M:%S", level: info, }
///     crash { file: "crash.log", }
///     dump beat_infer {
///         dir: "infer",
///         filename: "{timestamp}.{suffix}.log",
///         retention_days: 7,
///     }
///
///     // Named loggers (each logger has fixed filename, no suffix param)
///     loggers infer {
///         dir: "infer",
///         retention_days: 7,
///         beat_out: "{timestamp}.out.log",
///         beat_in: "{timestamp}.in.log",
///     }
///     rotate engine_log {
///         file: "engine.log",
///         max_files: 1,
///     }
/// }
///
/// // Usage:
/// MyLogService::init(&logs_dir);
/// beat_infer::dump("prompt content", "in");
/// MyLogService::rotate();
/// MyLogService::clean();
/// ```
#[macro_export]
macro_rules! log_service {
    // Full form: tracing + crash + dump* + rotate* + optional formats
    (
        $service:ident,
        tracing { time_format: $t_fmt:literal, level: $t_level:ident $(,)? }
        crash { file: $c_file:literal $(,)? }
        $( dump $d_name:ident {
            dir: $d_dir:literal,
            filename: $d_filename:literal,
            retention_days: $d_retention:expr,
        } )*
        $( loggers $lg_name:ident {
            dir: $lg_dir:literal,
            retention_days: $lg_retention:expr,
            $( $l_name:ident : $l_filename:literal, )*
        } )*
        $( rotate $r_name:ident {
            file: $r_file:literal,
            max_files: $r_max:expr,
        } )*
    ) => {
        static __LOG_SERVICE_STATE: std::sync::OnceLock<$crate::log_service::LogState>
            = std::sync::OnceLock::new();

        $(
            pub mod $d_name {
                /// Allocate a timestamp for correlated dump calls.
                pub fn timestamp() -> String {
                    $crate::log_service::make_timestamp()
                }

                /// Dump with auto-generated timestamp, no sub-directory.
                pub fn dump(content: &str, suffix: &str) -> Option<std::path::PathBuf> {
                    let state = super::__LOG_SERVICE_STATE.get()?;
                    $crate::log_service::dump_write(
                        &state.base_dir,
                        $d_dir,
                        $d_filename,
                        content,
                        suffix,
                        None,
                        None,
                    )
                }

                /// Dump with explicit timestamp and optional sub-directory.
                pub fn dump_at(
                    content: &str,
                    suffix: &str,
                    timestamp: &str,
                    sub_dir: Option<&str>,
                ) -> Option<std::path::PathBuf> {
                    let state = super::__LOG_SERVICE_STATE.get()?;
                    $crate::log_service::dump_write(
                        &state.base_dir,
                        $d_dir,
                        $d_filename,
                        content,
                        suffix,
                        sub_dir,
                        Some(timestamp),
                    )
                }

                /// Return the path for a dump file without writing it.
                /// Creates the directory. Use for streaming writes.
                pub fn path(
                    suffix: &str,
                    timestamp: &str,
                    sub_dir: Option<&str>,
                ) -> Option<std::path::PathBuf> {
                    let state = super::__LOG_SERVICE_STATE.get()?;
                    $crate::log_service::dump_path(
                        &state.base_dir,
                        $d_dir,
                        $d_filename,
                        suffix,
                        sub_dir,
                        timestamp,
                    )
                }
            }
        )*

        $(
            pub mod $lg_name {
                /// Allocate a timestamp for correlated logger calls.
                pub fn timestamp() -> String {
                    $crate::log_service::make_timestamp()
                }

                $(
                    pub mod $l_name {
                        /// Dump content to this logger's file.
                        pub fn dump(content: &str) -> Option<std::path::PathBuf> {
                            let state = super::super::__LOG_SERVICE_STATE.get()?;
                            $crate::log_service::dump_write(
                                &state.base_dir,
                                $lg_dir,
                                $l_filename,
                                content,
                                "",
                                None,
                                None,
                            )
                        }

                        /// Dump with explicit timestamp and optional sub-directory.
                        pub fn dump_at(
                            content: &str,
                            timestamp: &str,
                            sub_dir: Option<&str>,
                        ) -> Option<std::path::PathBuf> {
                            let state = super::super::__LOG_SERVICE_STATE.get()?;
                            $crate::log_service::dump_write(
                                &state.base_dir,
                                $lg_dir,
                                $l_filename,
                                content,
                                "",
                                sub_dir,
                                Some(timestamp),
                            )
                        }

                        /// Return the path without writing. Creates directory.
                        pub fn path(
                            timestamp: &str,
                            sub_dir: Option<&str>,
                        ) -> Option<std::path::PathBuf> {
                            let state = super::super::__LOG_SERVICE_STATE.get()?;
                            $crate::log_service::dump_path(
                                &state.base_dir,
                                $lg_dir,
                                $l_filename,
                                "",
                                sub_dir,
                                timestamp,
                            )
                        }
                    }
                )*
            }
        )*

        pub struct $service;

        impl $service {
            /// Initialize the log service: tracing subscriber, crash hook,
            /// directory creation, and initial rotate + clean.
            pub fn init(base_dir: &std::path::Path) {
                let state = $crate::log_service::LogState::new(base_dir);
                let _ = __LOG_SERVICE_STATE.set(state);

                // Create directories
                $( $crate::log_service::ensure_dir(base_dir, $d_dir); )*
                $( $crate::log_service::ensure_dir(base_dir, $lg_dir); )*

                // Initialize tracing
                let level = $crate::log_service::parse_level(stringify!($t_level));
                $crate::log_service::init_tracing($t_fmt, level);

                // Install crash hook
                let crash_path = base_dir.join($c_file);
                $crate::log_service::install_crash_hook(crash_path);

                // Initial rotate + clean
                Self::rotate();
                Self::clean();
            }

            /// Rotate all declared rotate logs.
            pub fn rotate() {
                if let Some(state) = __LOG_SERVICE_STATE.get() {
                    $( $crate::log_service::rotate_file(&state.base_dir, $r_file, $r_max); )*
                }
            }

            /// Clean all declared dump directories by retention policy.
            pub fn clean() {
                if let Some(state) = __LOG_SERVICE_STATE.get() {
                    $( $crate::log_service::clean_dump_dir(&state.base_dir, $d_dir, $d_retention); )*
                    $( $crate::log_service::clean_dump_dir(&state.base_dir, $lg_dir, $lg_retention); )*
                }
            }
        }
    };

    // Minimal form: zero config, all defaults
    ( $service:ident $(,)? ) => {
        $crate::log_service!(
            $service,
            tracing { time_format: "%Y-%m-%d %H:%M:%S", level: info, }
            crash { file: "crash.log", }
        );
    };
}

/// Unwrap an Option or Result with a message.
///
/// Guardian exempts string literals inside `mad_expect!` calls.
///
/// ```rust
/// # use mad_hatter::mad_expect;
/// let x: Option<i32> = Some(42);
/// let v = mad_expect!(x, "should have a value");
/// assert_eq!(v, 42);
/// ```
#[macro_export]
macro_rules! mad_expect {
    ($expr:expr, $msg:literal) => {
        $expr.expect($msg)
    };
}

/// Erase an error with structured logging — responsible error erasure.
///
/// Three things happen:
/// 1. Generate a unique error ID via `short_hex_id()`
/// 2. Log the full source error at ERROR level with file/line info
/// 3. Construct the target error variant with a sealed "Internal error [id]" detail
///
/// The target variant must have a `detail: Sealed<String>` field.
///
/// # Usage
/// ```rust,ignore
/// .map_err(|e| erase_error!(e, ExtensionError::RelayWriteFailed))
/// ```
#[macro_export]
macro_rules! erase_error {
    ($source_err:expr, $target_type:ident :: $variant:ident) => {{
        let __source = $source_err;
        let __err_id = $crate::short_hex_id();
        $crate::tracing::error!(
            "[ERROR-ERASED] err_id={}, source={}, at={}:{}",
            __err_id, __source, file!(), line!()
        );
        $target_type::$variant {
            detail: $crate::Sealed::new(format!("[{}] {}", __err_id, __source))
        }
    }};
}

pub use async_trait::async_trait;
pub use serde_json;
pub use tracing;

pub mod log_service;
pub mod http;
pub mod auth;
pub mod sealed;
pub use sealed::Sealed;
#[doc(hidden)] pub use sealed::format_sealed;
pub mod file_summary;
pub mod http_response;
pub mod shell;
pub mod timestamp;
pub use timestamp::Timestamp;
pub mod llm;
pub mod tunnel;
pub mod tunnel_client;
// === Public types & functions ===
pub use llm::{LlmChannel, OpenAiChannel, InferError, InferErrorCode, InferUsage, CacheStats, cache_stats, CancelScope, cancel_infer};
#[doc(hidden)] pub use llm::StreamInfer;
pub use tunnel::{TunnelCaller, TunnelEndpoint, Dispatch, TunnelError, RpcError};
#[doc(hidden)] pub use tunnel_client::{TunnelClient, TunnelConfig, ReconnectPolicy};
pub use shell::{ShellError, ShellOutput, ShellResult};
pub use proxy_pass::ProxyResolve;
pub use auth::{AuthPolicy, AuthScopedFs, PreAuthHook, PreAuthResult, grant_auth, revoke_auth};

// === Internal: used by macro expansion, not part of public API ===
#[doc(hidden)] pub use llm::Reflectable;
#[doc(hidden)] pub use llm::{StructInput, StructOutput, CachedPrompt};
#[doc(hidden)] pub use proxy_pass::CompiledPatterns;
#[doc(hidden)] pub use tunnel_proxy::{HttpTunnelRequest, HttpTunnelResponse, pack_request, unpack_response, unpack_request, pack_response, pack_response_with_limit, prepare_tunnel_forward};
#[doc(hidden)] pub use tunnel_server::{ServerConnectionHandle, accept_tunnel_connection};
#[doc(hidden)] pub use tunnel_client::http_to_ws_url;
pub use http::{
    content_type_for_extension,
    embedded_html_response,
    build_cookie, build_clear_cookie, extract_cookie_value,
    is_hop_by_hop_header, is_response_hop_by_hop, forward_request_headers, build_proxy_url,
    json_error_response, not_found, bad_request, unauthorized, internal_error,
    rewrite_cookie_path, process_proxy_response_header,
    serve_directory_file, process_multipart_upload, UploadedFile,
    image_media_type, download_as_data_uri,
    file_to_data_uri, resolve_image_to_data_uri,
    AbortResponse,
};
#[doc(hidden)] pub use http::tx_guard;
#[doc(hidden)] pub use http::tx_guard_with_db;
#[doc(hidden)] pub use tunnel::tunnel_tx_guard;
#[doc(hidden)] pub use tunnel::tunnel_tx_guard_with_db;

// Re-export axum types that generated code needs
pub use tokio::select;

#[doc(hidden)]
pub mod __axum {
    // Top-level re-exports used directly by gen.rs generated code
    pub use axum::Router;
    pub use axum::serve;
    pub use axum::Json;

    pub mod routing {
        pub use axum::routing::{get, post, put, delete, patch, any};
    }
    pub mod extract {
        pub use axum::extract::{State, Path, Query, Json, Request};
        pub use axum::extract::{Form, Extension, ConnectInfo, OriginalUri, DefaultBodyLimit, Multipart};
        pub mod ws {
            pub use axum::extract::ws::{WebSocket, WebSocketUpgrade, Message};
        }
    }
    pub mod response {
        pub use axum::response::{IntoResponse, Response, Redirect, Sse};
        pub mod sse {
            pub use axum::response::sse::{Event, KeepAlive};
        }
    }
    pub mod http {
        pub use axum::http::{StatusCode, Uri, Method, HeaderMap, header};
    }
    pub mod middleware {
        pub use axum::middleware::{from_fn, from_fn_with_state, Next};
    }
    pub mod body {
        pub use axum::body::{to_bytes, Body, Bytes};
    }
}

#[doc(hidden)]
pub mod __tokio {
    pub use tokio::runtime;
    pub use tokio::test;
    pub mod net {
        pub use tokio::net::TcpListener;
    }
    pub mod sync {
        pub mod mpsc {
            pub use tokio::sync::mpsc::UnboundedSender;
        }
    }
}

/// Re-exported sqlx DTO and query-builder types for engine use.
/// These types cannot perform IO on their own — they need an Executor
/// (pool/connection) which the framework does not expose.
/// **Do NOT add SqlitePool, Executor, Connection, or any IO-capable type here.**
#[doc(hidden)]
pub mod __sqlx {
    pub use sqlx::FromRow;
    pub use sqlx::query;
    pub use sqlx::query_as;
    // Traits needed by FormatEnum/FormatStruct derive macros
    pub use sqlx::Type;
    pub use sqlx::Encode;
    pub use sqlx::Decode;
    // Traits needed by mad_hatter::FromRow derive macro
    pub use sqlx::Row;
    pub use sqlx::ColumnIndex;
    pub use sqlx::Error;
    pub mod sqlite {
        pub use sqlx::sqlite::{Sqlite, SqliteTypeInfo, SqliteArgumentValue, SqliteValueRef};
    }
    pub mod encode {
        pub use sqlx::encode::IsNull;
    }
    pub mod error {
        pub use sqlx::error::BoxDynError;
    }
}

/// Result of racing two futures. See [`race`].
#[derive(Debug)]
pub enum Either<A, B> {
    /// First future completed first.
    Left(A),
    /// Second future completed first.
    Right(B),
}

/// Race two futures, returning whichever completes first.
///
/// Replacement for `tokio::select!` that works without tokio as a direct dependency.
///
/// ```ignore
/// match mad_hatter::race(fut_a, fut_b).await {
///     mad_hatter::Either::Left(a) => { /* fut_a won */ }
///     mad_hatter::Either::Right(b) => { /* fut_b won */ }
/// }
/// ```
pub async fn race<A, B>(
    a: impl std::future::Future<Output = A>,
    b: impl std::future::Future<Output = B>,
) -> Either<A, B> {
    tokio::pin!(a, b);
    tokio::select! {
        result = &mut a => Either::Left(result),
        result = &mut b => Either::Right(result),
    }
}

use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};

/// HTTP error type for service handlers.
///
/// Contains a status code, machine-readable error code, and a sealed human-readable message.
/// The message is sealed to ensure it flows through the mad-hatter IO boundary.
///
/// Factory methods (`not_found`, `bad_request`, etc.) accept plain strings and
/// seal them internally, so engine code doesn't need to create Sealed values manually.
#[derive(Debug, Clone)]
pub struct HttpError {
    pub status: StatusCode,
    pub error_code: String,
    pub message: Option<Sealed<String>>,
}

impl HttpError {
    pub fn new(status: StatusCode, error_code: impl Into<String>) -> Self {
        Self {
            status,
            error_code: error_code.into(),
            message: None,
        }
    }

    pub fn with_message(mut self, message: Sealed<String>) -> Self {
        self.message = Some(message);
        self
    }

    pub fn bad_request() -> Self {
        Self::new(StatusCode::BAD_REQUEST, "BAD_REQUEST")
    }

    pub fn not_found() -> Self {
        Self::new(StatusCode::NOT_FOUND, "NOT_FOUND")
    }

    pub fn internal() -> Self {
        Self::new(StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR")
    }

    pub fn unauthorized() -> Self {
        Self::new(StatusCode::UNAUTHORIZED, "UNAUTHORIZED")
    }

    pub fn forbidden() -> Self {
        Self::new(StatusCode::FORBIDDEN, "FORBIDDEN")
    }

    pub fn bad_gateway() -> Self {
        Self::new(StatusCode::BAD_GATEWAY, "BAD_GATEWAY")
    }
}

impl std::fmt::Display for HttpError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}: {}", self.status.as_u16(), self.error_code)
    }
}

impl std::error::Error for HttpError {}

impl IntoResponse for HttpError {
    fn into_response(self) -> Response {
        let body = match self.message {
            Some(msg) => serde_json::json!({
                "error": self.error_code,
                "message": msg.unseal(),
            }),
            None => serde_json::json!({
                "error": self.error_code,
            }),
        };
        (self.status, axum::Json(body)).into_response()
    }
}

/// Result type alias for service handlers.
pub type Result<T> = std::result::Result<T, HttpError>;

// ===== Log macros =====
// Internal names with #[macro_export], re-exported as mad_hatter::log::{error,warn,info,debug,trace}

#[macro_export]
#[doc(hidden)]
macro_rules! __mad_error {
    ($fmt:literal $(, $arg:expr)* $(,)?) => {
        $crate::log::__log_write(
            $crate::log::LogLevel::Error,
            format_args!($fmt, $($crate::log::LogParam::as_log_display(&$arg)),*)
        )
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __mad_warn {
    ($fmt:literal $(, $arg:expr)* $(,)?) => {
        $crate::log::__log_write(
            $crate::log::LogLevel::Warn,
            format_args!($fmt, $($crate::log::LogParam::as_log_display(&$arg)),*)
        )
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __mad_info {
    ($fmt:literal $(, $arg:expr)* $(,)?) => {
        $crate::log::__log_write(
            $crate::log::LogLevel::Info,
            format_args!($fmt, $($crate::log::LogParam::as_log_display(&$arg)),*)
        )
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __mad_debug {
    ($fmt:literal $(, $arg:expr)* $(,)?) => {
        $crate::log::__log_write(
            $crate::log::LogLevel::Debug,
            format_args!($fmt, $($crate::log::LogParam::as_log_display(&$arg)),*)
        )
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __mad_trace {
    ($fmt:literal $(, $arg:expr)* $(,)?) => {
        $crate::log::__log_write(
            $crate::log::LogLevel::Trace,
            format_args!($fmt, $($crate::log::LogParam::as_log_display(&$arg)),*)
        )
    };
}


pub mod log;
pub mod dao;
pub mod tunnel_bind;
pub mod proxy_pass;
pub mod tunnel_proxy;
pub mod tunnel_server;
pub mod ioutil;

// IO takeover — Critical type, IO封装, system re-exports
pub mod critical;
pub use critical::{
    ConceptMapToken, Critical, CriticalError, FatalToken, IntoCritical, SuppressibleToken,
    Transaction, TxScope,
};
#[doc(hidden)]
pub use critical::__main_entry;
#[doc(hidden)]
pub use http::__spawn_http_server;
pub mod fs;
pub mod scoped_io;
pub use scoped_io::{ScopedFs, ScopedLlm, ScopedShell};
pub mod db;
pub mod sys;

// State containers — engine-side shared state management
pub mod state;
// AtomicFlag, ConnectionCache, AsyncCell, AsyncLock are pub(crate) — engine must use scope IO
pub use state::channel;
pub use state::{
    CancelToken,
    BackoffConfig, LoopAction, RecoveryAction, LoopHandler, CronHandler, LoopRegistry,
    ItemResult, BatchHandler,
};
#[doc(hidden)] pub use state::{LoopHandle, shutdown_all_loops, BatchLoopConfig, BatchLoopHandle};
// Hub connection management — pub wrappers around pub(crate) state containers
pub use state::{LivenessToken, SwapResult, PeerNetwork, ConnectionRegistry, SharedList};
#[doc(hidden)]
pub use state::__start_framework;
#[doc(hidden)]
pub use state::run_batch_loop;
#[doc(hidden)]
pub use state::run_handler_loop;
#[doc(hidden)]
pub use state::run_cron_loop;

/// Declares a reverse proxy identity — cookie name and other config live here
/// so guardian can exempt them, and core code stays literal-free.
///
/// Declares a reverse proxy identity with full forwarding implementation.
///
/// # Examples
///
/// ```ignore
/// // Minimal — no cookie injection
/// proxy_pass!(SimpleProxy);
///
/// // With cookie injection (extracts named cookie from request headers)
/// proxy_pass!(ApiProxy, cookie_name: "session_token");
/// ```
///
/// Generates a struct with:
/// - `fn new() -> Self`
/// - `async fn forward(&self, method, rest_path, query, headers, body) -> Response`
///
/// `rest_path` format: `"{port}/{path...}"` — the proxy parses the port,
/// validates it (>= 1024), and forwards to `localhost:{port}`.
#[macro_export]
macro_rules! proxy_pass {
    // Tunnel proxy with patterns + cookie_name
    ($name:ident, $state_ty:ty { patterns: [$($pat:expr),* $(,)?], cookie_name: $cookie:expr $(,)? }) => {
        $crate::__proxy_pass_patterns_impl!($name, $state_ty, [$($pat),*], Some($cookie));
    };
    // Tunnel proxy with patterns only
    ($name:ident, $state_ty:ty { patterns: [$($pat:expr),* $(,)?] $(,)? }) => {
        $crate::__proxy_pass_patterns_impl!($name, $state_ty, [$($pat),*], None::<&str>);
    };
    // Direct proxy (existing)
    ($name:ident, prefix: $prefix:expr, cookie_name: $cookie:expr $(,)?) => {
        $crate::__proxy_pass_impl!($name, [$prefix], Some($cookie));
    };
    ($name:ident, prefix: $prefix:expr $(,)?) => {
        $crate::__proxy_pass_impl!($name, [$prefix], None::<&str>);
    };
    ($name:ident, cookie_name: $cookie:expr $(,)?) => {
        $crate::__proxy_pass_impl!($name, [], Some($cookie));
    };
    ($name:ident $(,)?) => {
        $crate::__proxy_pass_impl!($name, [], None::<&str>);
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __proxy_pass_impl {
    ($name:ident, [$($prefix:expr)?], $cookie_opt:expr) => {
        pub struct $name {
            client: $crate::proxy_pass::__ProxyHttpClient,
        }
        impl $name {
            $(
                /// Path prefix for this proxy (auto-generated from declaration).
                pub const PREFIX: &'static str = $prefix;
            )?

            pub fn new() -> Self {
                Self {
                    client: $crate::proxy_pass::__ProxyHttpClient::new(),
                }
            }

            /// Forward a proxied HTTP request.
            ///
            /// - `rest_path`: path after proxy prefix, format `"{port}/{path...}"`
            /// - `query`: optional query string from the original URI
            /// - `headers`: original request headers
            /// - `body`: request body bytes
            pub async fn forward(
                &self,
                method: &$crate::__axum::http::Method,
                rest_path: &str,
                query: Option<&str>,
                headers: &$crate::__axum::http::HeaderMap,
                body: &[u8],
            ) -> $crate::__axum::response::Response {
                $crate::proxy_pass::__forward_impl(
                    &self.client,
                    method,
                    rest_path,
                    query,
                    headers,
                    body,
                    $cookie_opt,
                )
                .await
            }
        }
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __proxy_pass_patterns_impl {
    ($name:ident, $state_ty:ty, [$($pat:expr),*], $cookie_opt:expr) => {
        pub struct $name {
            client: $crate::proxy_pass::__ProxyHttpClient,
        }

        impl $name {
            pub fn new() -> Self {
                Self {
                    client: $crate::proxy_pass::__ProxyHttpClient::new(),
                }
            }

            /// The declared cookie name for authentication (if any).
            pub fn cookie_name() -> Option<&'static str> {
                $cookie_opt
            }

            /// Get compiled URL patterns (lazy-initialized).
            fn compiled_patterns() -> &'static $crate::proxy_pass::CompiledPatterns {
                static __PATTERNS: ::std::sync::OnceLock<$crate::proxy_pass::CompiledPatterns> =
                    ::std::sync::OnceLock::new();
                __PATTERNS.get_or_init(|| {
                    $crate::proxy_pass::CompiledPatterns::compile(&[$($pat),*])
                })
            }

            /// Extract the first parameter from URL path using declared patterns.
            ///
            /// Returns `None` if no pattern matches.
            pub fn extract_param(path: &str) -> Option<String> {
                Self::compiled_patterns().extract_first(path)
            }

            /// Tunnel proxy middleware — matches URL patterns, extracts parameters,
            /// and forwards requests through tunnel via `ProxyResolve` trait.
            ///
            /// Generated by `proxy_pass!` macro. Register with:
            /// ```ignore
            /// .layer(axum::middleware::from_fn_with_state(state, Name::proxy_middleware))
            /// ```
            pub async fn proxy_middleware(
                $crate::__axum::extract::State(state): $crate::__axum::extract::State<$state_ty>,
                req: $crate::__axum::extract::Request,
                next: $crate::__axum::middleware::Next,
            ) -> $crate::__axum::response::Response {
                use $crate::__axum::response::IntoResponse as _;
                use $crate::proxy_pass::ProxyResolve as _;

                let path = req.uri().path().to_string();
                if let Some(param) = Self::extract_param(&path) {
                    if state.should_proxy(&param).await {
                        let (parts, body) = req.into_parts();
                        let body_bytes = $crate::__axum::body::to_bytes(body, usize::MAX)
                            .await
                            .unwrap_or_default();
                        let tunnel_req = $crate::pack_request(
                            &parts.method,
                            parts.uri.path(),
                            parts.uri.query(),
                            &parts.headers,
                            &body_bytes,
                        );
                        match state.proxy_forward(&param, tunnel_req).await {
                            Some(resp) => $crate::unpack_response(resp),
                            None => $crate::HttpError::bad_gateway().into_response(),
                        }
                    } else {
                        next.run(req).await
                    }
                } else {
                    next.run(req).await
                }
            }

            /// Forward a proxied HTTP request (slave-side direct forwarding).
            pub async fn forward(
                &self,
                method: &$crate::__axum::http::Method,
                rest_path: &str,
                query: Option<&str>,
                headers: &$crate::__axum::http::HeaderMap,
                body: &[u8],
            ) -> $crate::__axum::response::Response {
                $crate::proxy_pass::__forward_impl(
                    &self.client,
                    method,
                    rest_path,
                    query,
                    headers,
                    body,
                    $cookie_opt,
                )
                .await
            }
        }
    };
}

// ============================================================
// file_store! — declarative file system layout
// ============================================================

/// Declare a file system layout struct with named directories and optional files.
///
/// Generates a struct with:
/// - `new(base)` constructor
/// - `base()` getter returning the base path
/// - Named getter for each declared directory and file
/// - `ensure_dirs()` to create all declared directories at once
///
/// # Examples
///
/// ```ignore
/// file_store!(InstanceLayout, {
///     dirs {
///         memory: "memory",
///         knowledge: "memory/knowledge",
///         workspace: "workspace",
///     }
///     files {
///         settings: "settings.json",
///     }
/// });
///
/// let layout = InstanceLayout::new("/data/instance-1");
/// layout.ensure_dirs()?;  // creates all dirs
/// let ws = layout.workspace();  // PathBuf("/data/instance-1/workspace")
/// ```
#[macro_export]
macro_rules! file_store {
    // dirs + files
    ($name:ident, {
        dirs {
            $($dir_name:ident: $dir_path:literal),* $(,)?
        }
        files {
            $($file_name:ident: $file_path:literal),* $(,)?
        }
    }) => {
        #[derive(Clone, Debug)]
        pub struct $name {
            base: std::path::PathBuf,
        }

        impl $name {
            pub fn new(base: impl Into<std::path::PathBuf>) -> Self {
                Self { base: base.into() }
            }

            pub fn base(&self) -> &std::path::Path {
                &self.base
            }

            $(
                pub fn $dir_name(&self) -> std::path::PathBuf {
                    self.base.join($dir_path)
                }
            )*

            $(
                pub fn $file_name(&self) -> std::path::PathBuf {
                    self.base.join($file_path)
                }
            )*

            pub fn ensure_dirs(&self) -> $crate::Critical<()> {
                use $crate::IntoCritical;
                (|| -> std::io::Result<()> {
                    $(std::fs::create_dir_all(self.base.join($dir_path))?;)*
                    Ok(())
                })().into_critical("ensure_dirs")
            }
        }
    };

    // dirs only (no files block)
    ($name:ident, {
        dirs {
            $($dir_name:ident: $dir_path:literal),* $(,)?
        }
    }) => {
        #[derive(Clone, Debug)]
        pub struct $name {
            base: std::path::PathBuf,
        }

        impl $name {
            pub fn new(base: impl Into<std::path::PathBuf>) -> Self {
                Self { base: base.into() }
            }

            pub fn base(&self) -> &std::path::Path {
                &self.base
            }

            $(
                pub fn $dir_name(&self) -> std::path::PathBuf {
                    self.base.join($dir_path)
                }
            )*

            pub fn ensure_dirs(&self) -> $crate::Critical<()> {
                use $crate::IntoCritical;
                (|| -> std::io::Result<()> {
                    $(std::fs::create_dir_all(self.base.join($dir_path))?;)*
                    Ok(())
                })().into_critical("ensure_dirs")
            }
        }
    };
}

/// Log an info-level message with transaction context.
///
/// Usage: `tx_log!(scope, "processing instance {}", instance_id);`
///
/// Output: `[TX:{tx_name}:{trace_id}] processing instance ebc381`
#[macro_export]
macro_rules! tx_log {
    ($scope:expr, $($arg:tt)*) => {
        $scope.log(format_args!($($arg)*))
    };
}

/// Log a warn-level message with transaction context.
///
/// Usage: `tx_warn!(scope, "channel switched to {}", new_channel);`
#[macro_export]
macro_rules! tx_warn {
    ($scope:expr, $($arg:tt)*) => {
        $scope.warn(format_args!($($arg)*))
    };
}

/// Log an error-level message with transaction context.
///
/// Usage: `tx_error!(scope, "failed to connect: {}", err);`
#[macro_export]
macro_rules! tx_error {
    ($scope:expr, $($arg:tt)*) => {
        $scope.error(format_args!($($arg)*))
    };
}

/// Log a debug-level message with transaction context.
///
/// Usage: `tx_debug!(scope, "cache hit for {}", key);`
#[macro_export]
macro_rules! tx_debug {
    ($scope:expr, $($arg:tt)*) => {
        $scope.debug(format_args!($($arg)*))
    };
}

