//! Application error types and HTTP response rendering.
//!
//! Defines the unified [`Error`] enum used across all handlers and the
//! [`Result`] type alias.  `Error` implements [`IntoResponse`] via askama's
//! `error.html` template.

use askama::Template;
use axum::http::{StatusCode, header};
use axum::response::{IntoResponse, Response};
use thiserror::Error;

/// Unified error type for the application.
///
/// Each variant maps to an HTTP status code and a rendered error page.
#[derive(Debug, Error)]
pub enum Error {
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error("repository not found: {0}")]
    RepoNotFound(String),
    #[error("not found: {0}")]
    NotFound(String),
    #[error("bad request: {0}")]
    BadRequest(String),
    #[error("corrupt object: {0}")]
    GitCorrupt(String),
}

/// Convenience alias for `std::result::Result<T, Error>`.
pub type Result<T> = std::result::Result<T, Error>;

#[derive(Template)]
#[template(path = "error.html")]
struct ErrorTemplate {
    status: u16,
    kind: &'static str,
    message: String,
    base_url: String,
}

impl IntoResponse for Error {
    fn into_response(self) -> Response {
        let (status, kind) = match &self {
            Self::RepoNotFound(_) | Self::NotFound(_) => (StatusCode::NOT_FOUND, "Not Found"),
            Self::BadRequest(_) => (StatusCode::BAD_REQUEST, "Bad Request"),
            Self::Io(_) | Self::GitCorrupt(_) => {
                (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error")
            }
        };

        let tpl = ErrorTemplate {
            status: status.as_u16(),
            kind,
            message: self.to_string(),
            base_url: crate::base_url().to_string(),
        };

        tpl.render().map_or_else(
            |_| (status, self.to_string()).into_response(),
            |html| ([(header::CONTENT_TYPE, "text/html; charset=utf-8")], html).into_response(),
        )
    }
}
