use std as alloc;
use std::borrow::Cow;
use std::ops::DerefMut;
use std::sync::Arc;

use cosmic_jotdown::jotdown::{self, Event, ListKind};
use cosmic_jotdown::{Indent, INDENT_AMOUNT};
use eframe::egui::mutex::{Mutex, RwLock};
use eframe::egui::{
    self, lerp, Align2, Id, Image, ImageSize, ImageSource, LayerId, OpenUrl, Pos2, Rect, Rounding,
    Sense, Stroke, Vec2,
};
use eframe::egui_wgpu::{self, wgpu};
use egui::{Color32, Frame};
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 range_map::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,
}

#[derive(Default)]
pub struct ContextWindow {
    pub size: Vec2,
    pub text: Vec<(Rect, Indent, ContextBlock)>,
}

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

#[derive(Clone)]
pub enum ContextBlock {
    Buffer(Arc<RwLock<Buffer>>, Option<RangeMap<usize, &'static str>>),
    Image {
        alt_text: Arc<RwLock<Buffer>>,
        image: Image<'static>,
    },
}

const CONTEXT_METRICS: Metrics = Metrics::new(16.0, 18.0);

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

const CONTEXT_ICONS: [ContextIcon; 11] = [
    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",
    },
];

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

impl ContextWindow {
    pub fn set_content(
        &mut self,
        content: &Cow<'static, [Event<'static>]>,
        font_system: &mut FontSystem,
        mut max_width: f32,
    ) {
        self.size = Vec2::new(max_width / 1.5, 0.0);
        let mut last_indent = None;
        let mut last_image_size: Option<Vec2> = None;
        max_width /= 1.5;
        self.text = cosmic_jotdown::jotdown_into_buffers(
            content.iter().cloned(),
            font_system,
            CONTEXT_METRICS,
            max_width,
        )
        .map(|buffer| {
            let measurement = measure_buffer(&buffer.buffer, self.size);
            let paragraph_height = if last_indent.is_none() || buffer.indent.modifier.is_none() {
                CONTEXT_METRICS.line_height * 1.5
            } else {
                8.0
            };
            last_indent = buffer.indent.modifier;
            let buffer = if let Some(url) = buffer.image_url {
                let image;
                let url = url.split_once('#').unwrap();
                let size = url.1.split_once('x').unwrap();
                let size = Vec2::new(size.0.parse().unwrap(), size.1.parse().unwrap());
                #[cfg(target_arch = "wasm32")]
                {
                    image = Image::from_uri(format!(concat!(env!("PHOST"), "/{}"), url.0));
                }
                #[cfg(not(target_arch = "wasm32"))]
                {
                    image = Image::from_uri(format!("file://assets/{}", url.0));
                }
                let mut res = (
                    Rect::from_min_size(
                        Pos2::new(buffer.indent.indent, self.size.y + paragraph_height),
                        size,
                    ),
                    buffer.indent,
                    ContextBlock::Image {
                        alt_text: Arc::new(RwLock::new(buffer.buffer)),
                        image,
                    },
                );
                const IMAGE_PADDING: f32 = 8.0;
                if let Some(last_size) = last_image_size.as_mut() {
                    let ls = *last_size;
                    last_size.x += size.x + IMAGE_PADDING;

                    if last_size.x > max_width {
                        self.size.y += last_size.y + paragraph_height;
                        last_size.x = size.x + IMAGE_PADDING;
                        last_size.y = size.y;
                        res.0 = Rect::from_min_size(
                            Pos2::new(buffer.indent.indent, self.size.y + paragraph_height),
                            size,
                        );
                    } else {
                        last_size.y = last_size.y.max(size.y);
                        res.0 = res.0.translate(Vec2::new(ls.x, 0.0));
                    }
                } else {
                    if size.x > max_width {
                        let max_size = Vec2::new(max_width, size.y);
                        let new_size = ImageSize {
                            max_size,
                            ..Default::default()
                        }
                        .calc_size(max_size, size);
                        res.0 = Rect::from_min_size(
                            Pos2::new(buffer.indent.indent, self.size.y + paragraph_height),
                            new_size,
                        );
                        self.size.y += new_size.y + paragraph_height;
                    } else {
                        last_image_size = Some(size + Vec2::new(IMAGE_PADDING, 0.0));
                    }
                }
                res
            } else {
                if let Some(size) = last_image_size {
                    self.size.y += size.y + paragraph_height;
                }
                let res = (
                    Rect::from_min_size(
                        Pos2::new(buffer.indent.indent, self.size.y + paragraph_height),
                        measurement.size(),
                    ),
                    buffer.indent,
                    ContextBlock::Buffer(Arc::new(RwLock::new(buffer.buffer)), buffer.url_map),
                );
                last_image_size = None;
                self.size.y += measurement.height() + paragraph_height;
                res
            };
            self.size.x = self.size.x.max(measurement.width());
            buffer
        })
        .collect();

        if let Some(size) = last_image_size {
            self.size.y += size.y;
        }
    }
}

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.

        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: 2.0,
            font_system,
            name_buffers,
            buffer_size,
            window: ContextWindow::default(),
            max_size: Rect::ZERO,
        }
    }

    pub fn click(&mut self, ui: &egui::Ui, transform: Option<Mat2>) {
        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;
        } 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 {
            self.hover_time_offset = ui.input(|i| i.time);
        }
        self.hovered = hovered;
        if hovered {
            self.to_beamwidth = 6.0;
            self.from_beamwidth = 2.0;
        } else {
            self.to_beamwidth = 2.0;
            self.from_beamwidth = 6.0;
        }
    }
}

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,
            ui.input(|i| i.time) - self.hover_time_offset,
            0.5,
        );
    }
}

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);
                self.custom.custom_painting(ui);
                if ui.max_rect() != self.max_size {
                    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.max_size = ui.max_rect();
                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;
                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| match &context_block.2 {
                            ContextBlock::Buffer(buffer, url_map) => {
                                let text_rect = context_block.0.translate(rect.min.to_vec2());
                                // ui.painter().debug_rect(text_rect, Color32::GREEN, "");
                                if let Some(url_map) = url_map {
                                    let text_response = ui.allocate_rect(text_rect, Sense::click());
                                    if text_response.clicked() {
                                        let mut buffer = buffer.write();
                                        let mut editor =
                                            Editor::new(BufferRef::Borrowed(buffer.deref_mut()));

                                        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(
                                    buffer.clone(),
                                    text_rect,
                                    zoom_view_opacity,
                                    Color::rgb(255, 255, 255),
                                    ui.ctx(),
                                ));
                                match context_block.1.modifier {
                                    Some(ListKind::Unordered) => {
                                        ui.painter().circle(
                                            text_rect.min
                                                + Vec2::new(
                                                    -INDENT_AMOUNT,
                                                    CONTEXT_METRICS.line_height / 2.0,
                                                ),
                                            2.5,
                                            Color32::WHITE.gamma_multiply(zoom_view_opacity),
                                            Stroke::NONE,
                                        );
                                    }
                                    _ => {}
                                }
                            }
                            ContextBlock::Image { image, .. } => {
                                let image_rect = context_block.0.translate(rect.min.to_vec2());

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

                                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 {
                                        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,
                                            ),
                                        ),
                                    );

                                    image
                                        .clone()
                                        .tint(Color32::WHITE.gamma_multiply(zoom_view_opacity))
                                        .paint_at(&ui, image_rect.lerp_towards(&fs_rect, t));
                                } else {
                                    image
                                        .clone()
                                        .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,
                                                ),
                                            ),
                                        );
                                }
                            }
                        });
                }
                let mut hovered = false;
                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);
                        }
                    }
                } 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);
                        self.window.set_content(
                            &ABOUT_ME,
                            self.font_system.lock().deref_mut(),
                            ui.max_rect().width(),
                        );
                    }

                    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,
                                self.font_system.lock().deref_mut(),
                                ui.max_rect().width(),
                            );
                            self.click(ui, Some(transform));
                            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 {
                    self.window.set_content(
                        icon.content,
                        self.font_system.lock().deref_mut(),
                        ui.max_rect().width(),
                    );

                    self.zoomed = false;
                    self.click(
                        ui,
                        Some(glam::Mat2::from_angle((-icon.angle_at).to_radians())),
                    );
                    self.zoomed = true;

                    scroll_area.state.offset = Vec2::ZERO;
                    scroll_area.state.store(ui.ctx(), scroll_area.id);
                }
                self.hover(hovered, ui);
                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);
    }
}
