use std as alloc;
use std::borrow::Cow;
#[cfg(target_arch = "wasm32")]
use std::cell::RefCell;
#[cfg(target_arch = "wasm32")]
use std::mem::ManuallyDrop;
#[cfg(target_arch = "wasm32")]
use std::ops::Deref;
use std::ops::DerefMut;
#[cfg(target_arch = "wasm32")]
use std::rc::Rc;
use std::sync::Arc;

#[cfg(target_arch = "wasm32")]
use arc_swap::ArcSwapAny;
use cosmic_jotdown::{Indent, JotdownItem};
use eframe::egui::mutex::{Mutex, RwLock};
use eframe::egui::{
    self, lerp, Align2, Id, Image, ImageSize, ImageSource, LayerId, OpenUrl, Pos2, Rect, Rounding,
    Sense, Vec2,
};
use eframe::egui_wgpu::{self, wgpu};
use egui::{Color32, Frame};
use egui_glyphon::glyphon::cosmic_text;
use egui_glyphon::glyphon::cosmic_text::{Align, BufferRef};
use egui_glyphon::glyphon::{self, Attrs, Color, Edit, Editor, Weight};
use egui_glyphon::{BufferWithTextArea, GlyphonRenderer, GlyphonRendererCallback};
use encase::ShaderType;
use glam::Mat2;
use glyphon::{Buffer, FontSystem, Metrics};
use keyframe::functions;
use rangemap::RangeMap;
use wgpu::util::DeviceExt;

/// We derive Deserialize/Serialize so we can persist app state on shutdown.
pub struct Portfolio {
    custom: Custom3d,
    zoomed: bool,
    image_zoomed: bool,
    last_image_zoomed: Id,
    hovered: bool,
    click_time_offset: f64,
    hover_time_offset: f64,
    from_size: f32,
    to_size: f32,
    from_pos: [f32; 2],
    to_pos: [f32; 2],
    from_beamwidth: f32,
    to_beamwidth: f32,
    font_system: Arc<Mutex<FontSystem>>,
    name_buffers: [(Arc<RwLock<Buffer>>, Vec2); 3],
    window: ContextWindow,
    buffer_size: Vec2,
    max_size: Rect,
    #[cfg(target_arch = "wasm32")]
    states: Rc<RefCell<Vec<State>>>,
    #[cfg(target_arch = "wasm32")]
    next_state: Rc<ArcSwapAny<Option<Rc<State>>>>,
}

#[cfg(target_arch = "wasm32")]
#[derive(Clone, Copy)]
pub enum State {
    Start,
    Home,
    Context(Option<Mat2>, &'static Cow<'static, [JotdownItem<'static>]>),
}

pub struct ContextWindow {
    pub size: Vec2,
    pub text: Vec<ResolvedItem<'static>>,
    pub content: &'static Cow<'static, [JotdownItem<'static>]>,
    #[cfg(target_arch = "wasm32")]
    pub name: &'static str,
}

impl Default for ContextWindow {
    fn default() -> Self {
        Self {
            size: Vec2::default(),
            text: Vec::default(),
            content: &Cow::Borrowed(&[]),
            #[cfg(target_arch = "wasm32")]
            name: "",
        }
    }
}

pub struct ContextIcon {
    pub icon: ImageSource<'static>,
    pub content: &'static Cow<'static, [JotdownItem<'static>]>,
    pub angle_at: f32,
    pub name: &'static str,
}

const CONTEXT_METRICS: Metrics = Metrics::new(18.0, 20.0);
const HOVER_TIME: f64 = 0.5;

const ABOUT_ME: Cow<'static, [JotdownItem<'static>]> =
    include!(concat!(env!("OUT_DIR"), "/about_me.dj"));
const INKSCAPE: Cow<'static, [JotdownItem<'static>]> =
    include!(concat!(env!("OUT_DIR"), "/inkscape.dj"));
const RUST: Cow<'static, [JotdownItem<'static>]> = include!(concat!(env!("OUT_DIR"), "/rust.dj"));
const GIMP: Cow<'static, [JotdownItem<'static>]> = include!(concat!(env!("OUT_DIR"), "/gimp.dj"));
const REAPER: Cow<'static, [JotdownItem<'static>]> =
    include!(concat!(env!("OUT_DIR"), "/reaper.dj"));
const TREE_SITTER: Cow<'static, [JotdownItem<'static>]> =
    include!(concat!(env!("OUT_DIR"), "/tree_sitter.dj"));
const LINUX: Cow<'static, [JotdownItem<'static>]> = include!(concat!(env!("OUT_DIR"), "/linux.dj"));
const PYTHON: Cow<'static, [JotdownItem<'static>]> =
    include!(concat!(env!("OUT_DIR"), "/python.dj"));
const KDENLIVE: Cow<'static, [JotdownItem<'static>]> =
    include!(concat!(env!("OUT_DIR"), "/kdenlive.dj"));
const JAVA: Cow<'static, [JotdownItem<'static>]> = include!(concat!(env!("OUT_DIR"), "/java.dj"));
const SCRATCH: Cow<'static, [JotdownItem<'static>]> =
    include!(concat!(env!("OUT_DIR"), "/scratch.dj"));
const POSTGRESQL: Cow<'static, [JotdownItem<'static>]> =
    include!(concat!(env!("OUT_DIR"), "/postgresql.dj"));
const GIT: Cow<'static, [JotdownItem<'static>]> = include!(concat!(env!("OUT_DIR"), "/git.dj"));

const CONTEXT_ICONS: [ContextIcon; 12] = [
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(
            env!("PHOST"),
            "/images/inkscape.png"
        ))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/inkscape.png")),
        content: &INKSCAPE,
        angle_at: -142.0,
        name: "inkscape",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(env!("PHOST"), "/images/rust.png"))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/rust.png")),
        content: &RUST,
        angle_at: -66.0,
        name: "rust",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(env!("PHOST"), "/images/gimp.png"))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/gimp.png")),
        content: &GIMP,
        angle_at: -223.0,
        name: "gimp",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(env!("PHOST"), "/images/reaper.png"))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/reaper.png")),
        content: &REAPER,
        angle_at: -90.0,
        name: "reaper",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(
            env!("PHOST"),
            "/images/tree_sitter.png"
        ))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/tree_sitter.png")),
        content: &TREE_SITTER,
        angle_at: 37.0,
        name: "tree_sitter",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(env!("PHOST"), "/images/linux.png"))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/linux.png")),
        content: &LINUX,
        angle_at: -270.0,
        name: "linux",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(env!("PHOST"), "/images/python.png"))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/python.png")),
        content: &PYTHON,
        angle_at: -40.0,
        name: "python",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(
            env!("PHOST"),
            "/images/kdenlive.png"
        ))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/kdenlive.png")),
        content: &KDENLIVE,
        angle_at: -205.0,
        name: "kdenlive",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(env!("PHOST"), "/images/java.png"))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/java.png")),
        content: &JAVA,
        angle_at: -105.0,
        name: "java",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(env!("PHOST"), "/images/scratch.png"))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/scratch.png")),
        content: &SCRATCH,
        angle_at: -52.0,
        name: "scratch",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(
            env!("PHOST"),
            "/images/postgresql.png"
        ))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/postgresql.png")),
        content: &POSTGRESQL,
        angle_at: -235.0,
        name: "postgresql",
    },
    ContextIcon {
        #[cfg(target_arch = "wasm32")]
        icon: ImageSource::Uri(Cow::Borrowed(concat!(env!("PHOST"), "/images/git.png"))),
        #[cfg(not(target_arch = "wasm32"))]
        icon: ImageSource::Uri(Cow::Borrowed("file://assets/images/git.png")),
        content: &GIT,
        angle_at: -77.0,
        name: "git",
    },
];

const NAME_PLATE: [&str; 3] = [
    "Isaac Mills",
    "Computer Scientist; Software Engineer",
    "My portfolio",
];

pub struct ResolvedItem<'a> {
    pub indent: Indent,
    pub buffer: Arc<RwLock<Buffer>>,
    pub relative_bounds: Rect,
    pub url_map: Option<RangeMap<usize, Cow<'a, str>>>,
    pub image_urls: Option<(String, String)>,
}

impl ContextWindow {
    pub fn set_content(
        &mut self,
        content: &'static Cow<'static, [JotdownItem<'static>]>,
        #[cfg(target_arch = "wasm32")] transform: Option<Mat2>,
        #[cfg(target_arch = "wasm32")] name: Option<&'static str>,
        font_system: &mut FontSystem,
        max_width: f32,
        #[cfg(target_arch = "wasm32")] states: &mut Vec<State>,
    ) {
        self.size = Vec2::new(max_width / 1.5, 0.0);

        let (size, text) = cosmic_jotdown::resolve_paragraphs(
            content.as_ref(),
            self.size,
            font_system,
            CONTEXT_METRICS,
            None,
            1.0,
            #[cfg(target_arch = "wasm32")]
            env!("PHOST"),
            #[cfg(not(target_arch = "wasm32"))]
            "file://assets",
        );

        self.text = text
            .into_iter()
            .map(|t| ResolvedItem {
                indent: t.indent,
                buffer: Arc::new(RwLock::new(t.buffer)),
                relative_bounds: t.relative_bounds,
                url_map: t.url_map,
                image_urls: t.image_urls,
            })
            .collect();
        self.size = size;
        self.content = content;

        #[cfg(target_arch = "wasm32")]
        if let Some(name) = name {
            self.name = name;
            let state = states.len();
            states.push(State::Context(transform, content));
            web_sys::window()
                .unwrap()
                .history()
                .unwrap()
                .push_state(&wasm_bindgen::JsValue::from(state), name)
                .unwrap();
        }
    }

    pub fn resize(&mut self, max_width: f32, font_system: &mut FontSystem) {
        self.set_content(
            self.content,
            #[cfg(target_arch = "wasm32")]
            None,
            #[cfg(target_arch = "wasm32")]
            None,
            font_system,
            max_width,
            #[cfg(target_arch = "wasm32")]
            &mut Vec::new(),
        )
    }
}

fn make_buffers(
    font_system: &mut FontSystem,
    scale: f32,
) -> (Vec2, [(Arc<RwLock<Buffer>>, Vec2); 3]) {
    let mut buffers = [
        (
            Buffer::new(font_system, Metrics::new(64.0 * scale, 70.0 * scale)),
            Vec2::ZERO,
            Weight::NORMAL,
        ),
        (
            Buffer::new(font_system, Metrics::new(24.0 * scale, 30.0 * scale)),
            Vec2::ZERO,
            Weight::LIGHT,
        ),
        (
            Buffer::new(font_system, Metrics::new(24.0 * scale, 30.0 * scale)),
            Vec2::ZERO,
            Weight::LIGHT,
        ),
    ];
    let mut buffer_size = Vec2::ZERO;
    for buffer in buffers.iter_mut().zip(NAME_PLATE.into_iter()) {
        buffer.0 .0.set_size(font_system, f32::MAX, f32::MAX);
        buffer.0 .0.set_text(
            font_system,
            buffer.1,
            Attrs::new()
                .family(glyphon::Family::SansSerif)
                .weight(buffer.0 .2),
            glyphon::Shaping::Basic,
        );
        buffer.0 .0.shape_until_scroll(font_system, true);
        let size = measure_buffer(&buffer.0 .0, Vec2::INFINITY).size();
        buffer_size.y += size.y;
        buffer_size.x = buffer_size.x.max(size.x);
        buffer.0 .1 = size;
    }
    (
        buffer_size,
        buffers.map(|b| (Arc::new(RwLock::new(b.0)), b.1)),
    )
}

impl Portfolio {
    /// Called once before the first frame.
    pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
        // This is also where you can customize the look and feel of egui using
        // `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.

        // Load previous app state (if any).
        // Note that you must enable the `persistence` feature for this to work.

        #[cfg(target_arch = "wasm32")]
        let states;
        #[cfg(target_arch = "wasm32")]
        let next_state;
        #[cfg(target_arch = "wasm32")]
        {
            states = Rc::new(RefCell::new(Vec::new()));
            let pop_state = Rc::clone(&states);
            next_state = Rc::new(ArcSwapAny::new(None));
            let pop_next_state = Rc::clone(&next_state);
            let popstate_closure =
                ManuallyDrop::new(wasm_bindgen::closure::Closure::<dyn FnMut(_)>::new(
                    move |s: web_sys::PopStateEvent| {
                        let state = s.state();
                        if state.is_null() {
                            pop_next_state.store(Some(Rc::new(State::Home)));
                        } else {
                            let state = state.as_f64().unwrap() as usize;
                            let pop_state = pop_state.borrow();
                            pop_next_state.store(pop_state.get(state).map(|s| Rc::new(*s)));
                        }
                    },
                ));
            web_sys::window()
                .unwrap()
                .set_onpopstate(Some(wasm_bindgen::JsCast::unchecked_ref(
                    popstate_closure.as_ref(),
                )));
            states.borrow_mut().push(State::Start);
            web_sys::window()
                .unwrap()
                .history()
                .unwrap()
                .replace_state(&wasm_bindgen::JsValue::from(0), "Home")
                .unwrap();
        }

        egui_extras::install_image_loaders(&cc.egui_ctx);
        let mut font_system = FontSystem::new();
        font_system
            .db_mut()
            .load_font_source(glyphon::fontdb::Source::Binary(Arc::new(include_bytes!(
                "/usr/share/fonts/ubuntu/Ubuntu-L.ttf"
            ))));
        font_system
            .db_mut()
            .load_font_source(glyphon::fontdb::Source::Binary(Arc::new(include_bytes!(
                "/usr/share/fonts/ubuntu/Ubuntu-R.ttf"
            ))));
        font_system
            .db_mut()
            .load_font_source(glyphon::fontdb::Source::Binary(Arc::new(include_bytes!(
                "/usr/share/fonts/ubuntu/Ubuntu-RI.ttf"
            ))));
        font_system
            .db_mut()
            .load_font_source(glyphon::fontdb::Source::Binary(Arc::new(include_bytes!(
                "/usr/share/fonts/ubuntu/Ubuntu-B.ttf"
            ))));
        font_system
            .db_mut()
            .load_font_source(glyphon::fontdb::Source::Binary(Arc::new(include_bytes!(
                "/usr/share/fonts/TTF/FiraCode-Regular.ttf"
            ))));
        let (buffer_size, name_buffers) = make_buffers(&mut font_system, 1.0);
        let font_system = Arc::new(Mutex::new(font_system));
        GlyphonRenderer::insert(
            cc.wgpu_render_state.as_ref().unwrap(),
            Arc::clone(&font_system),
        );

        Self {
            custom: Custom3d::new(cc).unwrap(),
            zoomed: false,
            image_zoomed: false,
            last_image_zoomed: Id::NULL,
            hovered: false,
            click_time_offset: 0.0,
            hover_time_offset: 0.0,
            from_size: 0.6,
            to_size: 0.6,
            from_pos: [0.0, 0.0],
            to_pos: [0.0, 0.0],
            from_beamwidth: 2.0,
            to_beamwidth: 6.0,
            font_system,
            name_buffers,
            buffer_size,
            window: ContextWindow::default(),
            max_size: Rect::ZERO,
            #[cfg(target_arch = "wasm32")]
            states,
            #[cfg(target_arch = "wasm32")]
            next_state,
        }
    }

    pub fn click(
        &mut self,
        ui: &egui::Ui,
        transform: Option<Mat2>,
        #[cfg(target_arch = "wasm32")] push_state: bool,
    ) {
        self.from_size = self.to_size;
        self.from_pos = self.to_pos;
        self.click_time_offset = ui.input(|i| i.time);
        if self.zoomed {
            self.to_size = 0.6;
            self.to_pos = [0.0, 0.0];
            self.zoomed = false;
            #[cfg(target_arch = "wasm32")]
            if push_state {
                let mut states = self.states.borrow_mut();
                let state = states.len();
                states.push(State::Home);
                web_sys::window()
                    .unwrap()
                    .history()
                    .unwrap()
                    .push_state(&wasm_bindgen::JsValue::from(state), self.window.name)
                    .unwrap();
            }
        } else {
            self.to_size = 0.1;
            if let Some(transform) = transform {
                let point = transform.mul_vec2(glam::Vec2::new(2150.0, 0.0));
                self.to_pos = point.into();
            }
            self.zoomed = true;
        }
    }

    pub fn hover(&mut self, hovered: bool, ui: &egui::Ui) {
        if self.hovered != hovered {
            let time = ui.input(|i| i.time);
            let change_factor = HOVER_TIME - (time - self.hover_time_offset).clamp(0.0, HOVER_TIME);
            self.hover_time_offset = time - change_factor;
        }
        self.hovered = hovered;
    }
}

pub fn measure_buffer(buffer: &Buffer, vb: Vec2) -> Rect {
    let mut rtl = false;
    let (width, total_lines) =
        buffer
            .layout_runs()
            .fold((0.0, 0usize), |(width, total_lines), run| {
                if run.rtl {
                    rtl = true;
                }
                (run.line_w.max(width), total_lines + 1)
            });

    let (max_width, max_height) = buffer.size();

    let size = Vec2::new(
        if rtl { vb.x } else { width.min(max_width) },
        (total_lines as f32 * buffer.metrics().line_height).min(max_height),
    );
    match buffer.lines[0].align() {
        Some(Align::Right) | Some(Align::End) => {
            Align2::RIGHT_TOP.align_size_within_rect(size, Rect::from_min_size(Pos2::ZERO, vb))
        }
        Some(Align::Center) | Some(Align::Justified) => {
            Align2::CENTER_TOP.align_size_within_rect(size, Rect::from_min_size(Pos2::ZERO, vb))
        }
        Some(Align::Left) | None => Rect::from_min_size(Pos2::ZERO, size),
    }
}

impl Portfolio {
    fn update_customs(&mut self, ui: &mut egui::Ui) {
        let ppi = ui.ctx().pixels_per_point();
        self.custom.resolution =
            glam::Vec2::new(ui.max_rect().width() * ppi, ui.max_rect().height() * ppi);
        self.custom.time = ui.input(|i| i.time as f32);
        if let Some(pos) = ui.input(|i| i.pointer.latest_pos()) {
            let pos = pos * ppi;
            self.custom.cursor = glam::Vec2::new(pos.x, self.custom.resolution.y - pos.y);
        }
        self.custom.size = keyframe::ease_with_scaled_time(
            functions::EaseOutCubic,
            self.from_size,
            self.to_size,
            ui.input(|i| i.time) - self.click_time_offset,
            1.0,
        );
        self.custom.offset = glam::Vec2::from_array(keyframe::ease_with_scaled_time(
            functions::EaseOutCubic,
            self.from_pos,
            self.to_pos,
            ui.input(|i| i.time) - self.click_time_offset,
            1.0,
        ));
        self.custom.beam_width = keyframe::ease_with_scaled_time(
            functions::EaseOutCubic,
            self.from_beamwidth,
            self.to_beamwidth,
            if self.hovered {
                ui.input(|i| i.time) - self.hover_time_offset
            } else {
                HOVER_TIME - (ui.input(|i| i.time) - self.hover_time_offset)
            },
            HOVER_TIME,
        );
    }

    #[cfg(target_arch = "wasm32")]
    fn update_state(&mut self, ui: &mut egui::Ui) {
        if let Some(state) = self.next_state.load().deref() {
            match state.deref() {
                State::Home | State::Start => {
                    self.zoomed = true;
                    self.click(ui, None, false);
                }
                State::Context(transform, content) => {
                    self.zoomed = false;
                    self.click(ui, *transform, false);
                    self.window.set_content(
                        content,
                        *transform,
                        None,
                        self.font_system.lock().deref_mut(),
                        ui.max_rect().width(),
                        self.states.borrow_mut().deref_mut(),
                    );
                }
            }
            self.next_state.store(None);
        }
    }
}

impl eframe::App for Portfolio {
    /// Called each time the UI needs repainting, which may be many times per second.
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        // Put your widgets into a `SidePanel`, `TopBottomPanel`, `CentralPanel`, `Window` or `Area`.
        // For inspiration and more examples, go to https://emilk.github.io/egui

        egui::CentralPanel::default()
            .frame(Frame::default().fill(Color32::BLACK))
            .show(ctx, |ui| {
                self.update_customs(ui);
                #[cfg(target_arch = "wasm32")]
                self.update_state(ui);
                self.custom.custom_painting(ui);
                if ui.max_rect() != self.max_size {
                    self.max_size = ui.max_rect();
                    let scale = Vec2::from(self.custom.resolution.to_array())
                        / ui.ctx().pixels_per_point()
                        / Vec2::new(800.0, 800.0);
                    let (buffer_size, name_buffers) =
                        make_buffers(self.font_system.lock().deref_mut(), scale.x.min(scale.y));

                    self.buffer_size = buffer_size;
                    self.name_buffers = name_buffers;
                    self.window
                        .resize(self.max_size.width(), self.font_system.lock().deref_mut());
                }
                let time = ui.input(|i| i.time);
                let mut scroll_area = egui::ScrollArea::vertical().show_viewport(ui, |ui, rect| {
                    let max_rect = ui.max_rect();
                    ui.allocate_exact_size(
                        Vec2::new(
                            max_rect.width(),
                            self.window.size.y + CONTEXT_METRICS.line_height * 8.0,
                        ),
                        Sense::hover(),
                    );
                    rect.translate(Vec2::new(0.0, (-rect.min.y) * 2.0))
                });
                let name_rect =
                    Align2::CENTER_CENTER.align_size_within_rect(self.buffer_size, ui.max_rect());
                let name_opacity = keyframe::ease_with_scaled_time(
                    functions::EaseOutCubic,
                    if self.zoomed { 1.0 } else { 0.0 },
                    if self.zoomed { 0.0 } else { 1.0 },
                    time - self.click_time_offset - if self.zoomed { 0.0 } else { 0.5 },
                    1.0,
                );
                let zoom_view_opacity = keyframe::ease_with_scaled_time(
                    functions::EaseOutCubic,
                    if self.zoomed { 0.0 } else { 1.0 },
                    if self.zoomed { 1.0 } else { 0.0 },
                    time - self.click_time_offset,
                    1.0,
                );
                let mut buffer_offset = 0.0;
                let mut buffers: Vec<BufferWithTextArea> = self
                    .name_buffers
                    .iter()
                    .map(|b| {
                        let res = BufferWithTextArea::new(
                            b.0.clone(),
                            name_rect.translate(Vec2::new(0.0, buffer_offset)),
                            name_opacity,
                            egui_glyphon::glyphon::Color::rgb(255, 255, 255),
                            ui.ctx(),
                        );
                        buffer_offset += b.1.y;
                        res
                    })
                    .collect();
                let name_resp = ui.allocate_rect(name_rect, Sense::click());
                let center = Pos2::new(ui.max_rect().width() / 2.0, ui.max_rect().height() / 2.0);
                let reference_rect = if ui.max_rect().height() > ui.max_rect().width() {
                    let size = Vec2::new(ui.max_rect().width(), ui.max_rect().width())
                        * Vec2::new(0.85, 0.85);
                    Align2::CENTER_CENTER.align_size_within_rect(size, ui.max_rect())
                } else {
                    let size = Vec2::new(ui.max_rect().height(), ui.max_rect().height())
                        * Vec2::new(0.85, 0.85);
                    Align2::CENTER_CENTER.align_size_within_rect(size, ui.max_rect())
                };
                let mut icon_link = None;
                let rect = if ui.max_rect().height() > self.window.size.y {
                    Align2::CENTER_CENTER
                        .align_size_within_rect(self.window.size, scroll_area.inner)
                } else {
                    Align2::CENTER_TOP
                        .align_size_within_rect(self.window.size, scroll_area.inner)
                        .translate(Vec2::new(0.0, CONTEXT_METRICS.line_height * 4.0))
                };
                let mut link_clicked = false;
                let mut hovered = false;
                if zoom_view_opacity > 0.0 {
                    ui.painter().rect_filled(
                        Rect::from_min_size(rect.min, self.window.size).expand(25.0),
                        Rounding::same(25.0),
                        Color32::BLACK.gamma_multiply(lerp(0.0..=0.5, zoom_view_opacity)),
                    );
                    self.window.text.iter().for_each(|context_block| {
                        if let Some(image_urls) = &context_block.image_urls {
                            let image_rect =
                                context_block.relative_bounds.translate(rect.min.to_vec2());

                            let image_response = ui.allocate_rect(image_rect, Sense::click());
                            let image_response_clicked = image_response.clicked();

                            let time_offset = ui.memory_mut(|m| {
                                let time_offset =
                                    m.data.get_temp_mut_or_default::<f64>(image_response.id);

                                if image_response_clicked && !self.image_zoomed && self.zoomed {
                                    link_clicked = true;
                                    *time_offset = time;
                                    self.last_image_zoomed = image_response.id;
                                    self.image_zoomed = true;
                                }

                                *time_offset
                            });

                            if image_response.hovered() && !self.image_zoomed {
                                ui.ctx().set_cursor_icon(egui::CursorIcon::ZoomIn);
                            }

                            let fs_rect = Align2::CENTER_CENTER.align_size_within_rect(
                                {
                                    let max_size = self.max_size.shrink(16.0).size();
                                    ImageSize {
                                        max_size,
                                        ..Default::default()
                                    }
                                    .calc_size(max_size, image_rect.size())
                                },
                                self.max_size,
                            );

                            if self.last_image_zoomed == image_response.id {
                                let ui = egui::Ui::new(
                                    ui.ctx().clone(),
                                    LayerId::debug(),
                                    Id::new("image_zoom"),
                                    self.max_size,
                                    self.max_size,
                                );

                                let t = keyframe::ease_with_scaled_time(
                                    functions::EaseInOutCubic,
                                    if self.image_zoomed { 0.0 } else { 1.0 },
                                    if self.image_zoomed { 1.0 } else { 0.0 },
                                    time - time_offset,
                                    0.5,
                                );

                                ui.painter().rect_filled(
                                    ui.max_rect(),
                                    Rounding::default(),
                                    Color32::BLACK.gamma_multiply(keyframe::ease_with_scaled_time(
                                        functions::EaseInOutCubic,
                                        if self.image_zoomed { 0.0 } else { 0.6 },
                                        if self.image_zoomed { 0.6 } else { 0.0 },
                                        time - time_offset,
                                        0.5,
                                    )),
                                );

                                if t > 0.0 {
                                    Image::from_uri(&image_urls.1)
                                } else {
                                    Image::from_uri(&image_urls.0)
                                }
                                .tint(Color32::WHITE.gamma_multiply(zoom_view_opacity))
                                .paint_at(&ui, image_rect.lerp_towards(&fs_rect, t));
                            } else {
                                Image::from_uri(&image_urls.0)
                                    .tint(Color32::WHITE.gamma_multiply(zoom_view_opacity))
                                    .paint_at(
                                        ui,
                                        image_rect.lerp_towards(
                                            &fs_rect,
                                            keyframe::ease_with_scaled_time(
                                                functions::EaseInOutCubic,
                                                1.0,
                                                0.0,
                                                time - time_offset,
                                                0.5,
                                            ),
                                        ),
                                    );
                            }
                        } else {
                            let text_rect =
                                context_block.relative_bounds.translate(rect.min.to_vec2());
                            // ui.painter().debug_rect(text_rect, Color32::GREEN, "");
                            if let Some(url_map) = &context_block.url_map {
                                let text_response = ui.allocate_rect(text_rect, Sense::click());
                                let mut buffer = context_block.buffer.write();
                                let mut editor =
                                    Editor::new(BufferRef::Borrowed(buffer.deref_mut()));
                                if text_response.hovered()
                                    && ui.input(|i| i.raw_scroll_delta == Vec2::ZERO)
                                {
                                    let mouse_pos = ui
                                        .input(|i| i.pointer.latest_pos().unwrap_or_default())
                                        - text_rect.min.to_vec2();

                                    editor.action(
                                        self.font_system.lock().deref_mut(),
                                        glyphon::Action::Click {
                                            x: mouse_pos.x as i32,
                                            y: mouse_pos.y as i32 - 3,
                                        },
                                    );

                                    let mut location = editor.cursor();
                                    match location.affinity {
                                        glyphon::Affinity::After => location.index += 1,
                                        glyphon::Affinity::Before => {}
                                    }

                                    if url_map.get(&location.index).is_some() {
                                        ctx.set_cursor_icon(egui::CursorIcon::PointingHand);
                                    }
                                }
                                if text_response.clicked() && !self.image_zoomed {
                                    let mouse_click = ui
                                        .input(|i| i.pointer.interact_pos().unwrap_or_default())
                                        - text_rect.min.to_vec2();

                                    editor.action(
                                        self.font_system.lock().deref_mut(),
                                        glyphon::Action::Click {
                                            x: mouse_click.x as i32,
                                            y: mouse_click.y as i32,
                                        },
                                    );

                                    let mut location = editor.cursor();
                                    match location.affinity {
                                        glyphon::Affinity::After => location.index += 1,
                                        glyphon::Affinity::Before => {}
                                    }
                                    if let Some(url) = url_map.get(&location.index) {
                                        link_clicked = true;
                                        if url.starts_with('#') {
                                            if let Some(icon) =
                                                CONTEXT_ICONS.iter().find(|i| i.name == &url[1..])
                                            {
                                                icon_link = Some(icon);
                                            }
                                        } else {
                                            ui.ctx().open_url(OpenUrl::new_tab(url));
                                        }
                                        // clicked = false;
                                    }
                                }
                            }
                            buffers.push(BufferWithTextArea::new(
                                context_block.buffer.clone(),
                                text_rect,
                                zoom_view_opacity,
                                Color::rgb(255, 255, 255),
                                ui.ctx(),
                            ));
                        }
                    });
                }
                if self.zoomed {
                    if ui.input(|i| i.pointer.any_click()) && !link_clicked {
                        if self.image_zoomed {
                            ui.memory_mut(|m| {
                                let time_offset = m
                                    .data
                                    .get_temp_mut_or_default::<f64>(self.last_image_zoomed);

                                link_clicked = true;
                                *time_offset = time;
                                self.image_zoomed = false;
                            });
                        } else if icon_link.is_none() {
                            self.click(
                                ui,
                                None,
                                #[cfg(target_arch = "wasm32")]
                                true,
                            );
                        }
                    }
                } else {
                    if ui.input(|i| i.pointer.secondary_clicked()) {
                        let mouse = ui.input(|i| i.pointer.interact_pos().unwrap_or_default());
                        let center = glam::Vec2::new(
                            ui.max_rect().width() / 2.0,
                            ui.max_rect().height() / 2.0,
                        );
                        let angle = ((mouse.x - center.x).atan2(mouse.y - center.y) * 180.0
                            / std::f32::consts::PI)
                            - 90.0;
                        log::info!("{}", angle);
                    }

                    if name_resp.clicked() {
                        self.click(
                            ui,
                            None,
                            #[cfg(target_arch = "wasm32")]
                            true,
                        );
                        self.window.set_content(
                            &ABOUT_ME,
                            #[cfg(target_arch = "wasm32")]
                            None,
                            #[cfg(target_arch = "wasm32")]
                            Some("About Me"),
                            self.font_system.lock().deref_mut(),
                            ui.max_rect().width(),
                            #[cfg(target_arch = "wasm32")]
                            self.states.borrow_mut().deref_mut(),
                        );
                    }

                    hovered = name_resp.hovered() || hovered;
                    for i in CONTEXT_ICONS.iter() {
                        let transform = glam::Mat2::from_angle((-i.angle_at).to_radians());
                        let factor = reference_rect.size() / 775.0;
                        let point = center
                            + Vec2::from(
                                transform
                                    .mul_vec2(glam::Vec2::new(reference_rect.width() / 2.0, 0.0))
                                    .to_array(),
                            )
                            - (Vec2::splat(32.0) * factor);

                        let rect = Rect::from_min_size(point, Vec2::new(64.0, 64.0) * factor);

                        let response = ui.allocate_rect(rect, Sense::click());
                        let response_hovered = response.hovered();
                        let opacity = ui.memory_mut(|m| {
                            let icon_state =
                                m.data.get_temp_mut_or_insert_with(response.id, || {
                                    (0.0, response_hovered)
                                });

                            if response_hovered != icon_state.1 {
                                icon_state.0 = time;
                                icon_state.1 = response_hovered;
                            }

                            lerp(
                                0.0..=keyframe::ease_with_scaled_time(
                                    functions::EaseOutCubic,
                                    if response_hovered { 0.5 } else { 1.0 },
                                    if response_hovered { 1.0 } else { 0.5 },
                                    time - icon_state.0,
                                    0.5,
                                ),
                                name_opacity,
                            )
                        });

                        if response.clicked() {
                            self.window.set_content(
                                i.content,
                                #[cfg(target_arch = "wasm32")]
                                Some(transform),
                                #[cfg(target_arch = "wasm32")]
                                Some(i.name),
                                self.font_system.lock().deref_mut(),
                                ui.max_rect().width(),
                                #[cfg(target_arch = "wasm32")]
                                self.states.borrow_mut().deref_mut(),
                            );
                            self.click(
                                ui,
                                Some(transform),
                                #[cfg(target_arch = "wasm32")]
                                true,
                            );
                            scroll_area.state.offset = Vec2::ZERO;
                            scroll_area.state.store(ui.ctx(), scroll_area.id);
                        }

                        hovered = response_hovered || hovered;

                        egui::Image::new(i.icon.clone())
                            .tint(Color32::WHITE.gamma_multiply(opacity))
                            .paint_at(ui, rect);
                    }
                }

                if let Some(icon) = icon_link {
                    let transform = glam::Mat2::from_angle((-icon.angle_at).to_radians());
                    self.window.set_content(
                        icon.content,
                        #[cfg(target_arch = "wasm32")]
                        Some(transform),
                        #[cfg(target_arch = "wasm32")]
                        Some(icon.name),
                        self.font_system.lock().deref_mut(),
                        ui.max_rect().width(),
                        #[cfg(target_arch = "wasm32")]
                        self.states.borrow_mut().deref_mut(),
                    );

                    self.zoomed = false;
                    self.click(
                        ui,
                        Some(transform),
                        #[cfg(target_arch = "wasm32")]
                        true,
                    );
                    self.zoomed = true;

                    scroll_area.state.offset = Vec2::ZERO;
                    scroll_area.state.store(ui.ctx(), scroll_area.id);
                }
                self.hover(hovered, ui);
                if self.hovered {
                    ctx.set_cursor_icon(egui::CursorIcon::PointingHand);
                }
                ui.painter().add(egui_wgpu::Callback::new_paint_callback(
                    ui.max_rect(),
                    GlyphonRendererCallback { buffers },
                ));
            });

        ctx.request_repaint();
    }
}

#[derive(Copy, Clone, Default, ShaderType, Debug)]
#[allow(unused_attributes)]
#[repr(C)]
pub struct Custom3d {
    pub resolution: glam::Vec2,
    pub time: f32,
    pub cursor: glam::Vec2,
    pub size: f32,
    pub offset: glam::Vec2,
    pub beam_width: f32,
    paddingtwo: f32,
}

impl Custom3d {
    pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
        // Get the WGPU render state from the eframe creation context. This can also be retrieved
        // from `eframe::Frame` when you don't have a `CreationContext` available.
        let wgpu_render_state = cc.wgpu_render_state.as_ref()?;

        let device = &wgpu_render_state.device;

        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("3d"),
            source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
        });

        let constants = Self::default();

        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("Uniform Buffer"),
            contents: {
                let mut buffer = encase::UniformBuffer::new(Vec::new());
                buffer.write(&constants).unwrap();
                buffer.into_inner().as_slice()
            },
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
        });
        let uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::all(),
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
                label: Some("uniform_bind_group_layout"),
            });
        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &uniform_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: uniform_buffer.as_entire_binding(),
            }],
            label: Some("uniform_bind_group"),
        });

        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: None,
            bind_group_layouts: &[&uniform_bind_group_layout],
            push_constant_ranges: &[],
        });

        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: None,
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: "vs_main",
                buffers: &[],
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: "fs_main",
                targets: &[Some(wgpu::ColorTargetState {
                    format: wgpu_render_state.target_format,
                    blend: Some(wgpu::BlendState {
                        color: wgpu::BlendComponent::REPLACE,
                        alpha: wgpu::BlendComponent::REPLACE,
                    }),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
            }),
            primitive: wgpu::PrimitiveState::default(),
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
        });

        // Because the graphics pipeline must have the same lifetime as the egui render pass,
        // instead of storing the pipeline in our `Custom3D` struct, we insert it into the
        // `paint_callback_resources` type map, which is stored alongside the render pass.
        wgpu_render_state
            .renderer
            .write()
            .callback_resources
            .insert(TriangleRenderResources {
                pipeline,
                bind_group,
                uniform_buffer,
            });

        Some(constants)
    }
}

// Callbacks in egui_wgpu have 3 stages:
// * prepare (per callback impl)
// * finish_prepare (once)
// * paint (per callback impl)
//
// The prepare callback is called every frame before paint and is given access to the wgpu
// Device and Queue, which can be used, for instance, to update buffers and uniforms before
// rendering.
// If [`egui_wgpu::Renderer`] has [`egui_wgpu::FinishPrepareCallback`] registered,
// it will be called after all `prepare` callbacks have been called.
// You can use this to update any shared resources that need to be updated once per frame
// after all callbacks have been processed.
//
// On both prepare methods you can use the main `CommandEncoder` that is passed-in,
// return an arbitrary number of user-defined `CommandBuffer`s, or both.
// The main command buffer, as well as all user-defined ones, will be submitted together
// to the GPU in a single call.
//
// The paint callback is called after finish prepare and is given access to egui's main render pass,
// which can be used to issue draw commands.
struct CustomTriangleCallback {
    constants: Custom3d,
}

impl egui_wgpu::CallbackTrait for CustomTriangleCallback {
    fn prepare(
        &self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        _screen_descriptor: &egui_wgpu::ScreenDescriptor,
        _egui_encoder: &mut wgpu::CommandEncoder,
        resources: &mut egui_wgpu::CallbackResources,
    ) -> Vec<wgpu::CommandBuffer> {
        let resources: &TriangleRenderResources = resources.get().unwrap();
        resources.prepare(device, queue, &self.constants);
        Vec::new()
    }

    fn paint<'a>(
        &self,
        _info: egui::PaintCallbackInfo,
        render_pass: &mut wgpu::RenderPass<'a>,
        resources: &'a egui_wgpu::CallbackResources,
    ) {
        let resources: &TriangleRenderResources = resources.get().unwrap();
        resources.paint(render_pass);
    }
}

impl Custom3d {
    fn custom_painting(&mut self, ui: &mut egui::Ui) {
        ui.painter().add(egui_wgpu::Callback::new_paint_callback(
            ui.max_rect(),
            CustomTriangleCallback { constants: *self },
        ));
    }
}

struct TriangleRenderResources {
    pipeline: wgpu::RenderPipeline,
    bind_group: wgpu::BindGroup,
    uniform_buffer: wgpu::Buffer,
}

impl TriangleRenderResources {
    fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, constants: &Custom3d) {
        // Update our uniform buffer with the angle from the UI
        let mut buffer = encase::UniformBuffer::new(Vec::new());
        buffer.write(constants).unwrap();
        queue.write_buffer(&self.uniform_buffer, 0, buffer.into_inner().as_slice());
    }

    fn paint<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) {
        // Draw our triangle!
        render_pass.set_pipeline(&self.pipeline);
        render_pass.set_bind_group(0, &self.bind_group, &[]);
        render_pass.draw(0..3, 0..1);
    }
}
