/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/

use crate::Page;
use jellycommon::{
    api::{LogLevel, LogLine},
    routes::u_admin_log,
};
use markup::raw;
use std::fmt::Write;

impl Page for ServerLogPage<'_> {
    fn title(&self) -> String {
        "Server Log".to_string()
    }
    fn class(&self) -> Option<&'static str> {
        Some("admin_log")
    }
    fn to_render(&self) -> markup::DynRender<'_> {
        markup::new!(@self)
    }
}

markup::define! {
    ServerLogPage<'a>(warnonly: bool, messages: &'a [String]) {
        h1 { "Server Log" }
        a[href=u_admin_log(!warnonly)] { @if *warnonly { "Show everything" } else { "Show only warnings" }}
        code.log[id="log"] {
            table { @for e in *messages {
                @raw(e)
            }}
        }
    }
    ServerLogLine<'a>(e: &'a LogLine) {
        tr[class=format!("level-{}", e.level).to_ascii_lowercase()] {
            td.time { @e.time.to_rfc3339() }
            td.loglevel  { @format_level(e.level) }
            td.module { @e.module }
            td { @markup::raw(vt100_to_html(&e.message)) }
        }
    }
}

pub fn render_log_line(line: &LogLine) -> String {
    ServerLogLine { e: line }.to_string()
}

fn vt100_to_html(s: &str) -> String {
    let mut out = HtmlOut::default();
    let mut st = vte::Parser::new();
    st.advance(&mut out, s.as_bytes());
    out.s
}

fn format_level(level: LogLevel) -> impl markup::Render {
    let (s, c) = match level {
        LogLevel::Debug => ("DEBUG", "blue"),
        LogLevel::Error => ("ERROR", "red"),
        LogLevel::Warn => ("WARN", "yellow"),
        LogLevel::Info => ("INFO", "green"),
        LogLevel::Trace => ("TRACE", "lightblue"),
    };
    markup::new! { span[style=format!("color:{c}")] {@s} }
}

#[derive(Default)]
pub struct HtmlOut {
    s: String,
    color: bool,
}
impl HtmlOut {
    pub fn set_color(&mut self, [r, g, b]: [u8; 3]) {
        self.reset_color();
        self.color = true;
        write!(self.s, "<span style=color:#{:02x}{:02x}{:02x}>", r, g, b).unwrap()
    }
    pub fn reset_color(&mut self) {
        if self.color {
            write!(self.s, "</span>").unwrap();
            self.color = false;
        }
    }
}
impl vte::Perform for HtmlOut {
    fn print(&mut self, c: char) {
        match c {
            'a'..='z' | 'A'..='Z' | '0'..='9' | ' ' => self.s.push(c),
            x => write!(self.s, "&#{};", x as u32).unwrap(),
        }
    }
    fn execute(&mut self, _byte: u8) {}
    fn hook(&mut self, _params: &vte::Params, _i: &[u8], _ignore: bool, _a: char) {}
    fn put(&mut self, _byte: u8) {}
    fn unhook(&mut self) {}
    fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {}
    fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {}
    fn csi_dispatch(
        &mut self,
        params: &vte::Params,
        _intermediates: &[u8],
        _ignore: bool,
        action: char,
    ) {
        let mut k = params.iter();
        #[allow(clippy::single_match)]
        match action {
            'm' => match k.next().unwrap_or(&[0]).first().unwrap_or(&0) {
                c @ (30..=37 | 40..=47) => {
                    let c = if *c >= 40 { *c - 10 } else { *c };
                    self.set_color(match c {
                        30 => [0, 0, 0],
                        31 => [255, 0, 0],
                        32 => [0, 255, 0],
                        33 => [255, 255, 0],
                        34 => [0, 0, 255],
                        35 => [255, 0, 255],
                        36 => [0, 255, 255],
                        37 => [255, 255, 255],
                        _ => unreachable!(),
                    });
                }
                _ => (),
            },
            _ => (),
        }
    }
}
