mod app;
mod highlight;
#[allow(dead_code)]
mod operations;
mod viewport;

use app::App;

fn theme(_: &App) -> iced::Theme {
    iced::Theme::Dark
}

fn title(app: &App) -> String {
    app.title()
}

#[cfg(target_os = "macos")]
mod apple_events {
    use std::path::PathBuf;
    use objc2::rc::Retained;
    use objc2::runtime::{AnyObject, NSObject};
    use objc2::{define_class, msg_send, sel, AnyThread};

    // kCoreEventClass = 'aevt', kAEOpenDocuments = 'odoc', keyDirectObject = '----'
    const KAEVT: u32 = u32::from_be_bytes(*b"aevt");
    const KODOC: u32 = u32::from_be_bytes(*b"odoc");
    const KEY_DIRECT_OBJECT: u32 = u32::from_be_bytes(*b"----");

    define_class!(
        #[unsafe(super(NSObject))]
        #[name = "CordOpenHandler"]
        struct OpenHandler;

        impl OpenHandler {
            #[unsafe(method(handleOpenEvent:withReplyEvent:))]
            fn handle_open_event(&self, event: &AnyObject, _reply: &AnyObject) {
                handle_odoc(event);
            }
        }
    );

    impl OpenHandler {
        fn new() -> Retained<Self> {
            let this = Self::alloc().set_ivars(());
            unsafe { msg_send![super(this), init] }
        }
    }

    fn handle_odoc(event: &AnyObject) {
        let file_list: Option<Retained<AnyObject>> = unsafe {
            msg_send![event, paramDescriptorForKeyword: KEY_DIRECT_OBJECT]
        };
        let file_list = match file_list {
            Some(fl) => fl,
            None => return,
        };

        let count: isize = unsafe { msg_send![&*file_list, numberOfItems] };
        let queue = crate::app::open_queue();

        for i in 1..=count {
            let desc: Option<Retained<AnyObject>> = unsafe {
                msg_send![&*file_list, descriptorAtIndex: i]
            };
            let desc = match desc {
                Some(d) => d,
                None => continue,
            };
            let url_str: Option<Retained<AnyObject>> = unsafe {
                msg_send![&*desc, stringValue]
            };
            let url_str = match url_str {
                Some(s) => s,
                None => continue,
            };
            let raw: *const std::ffi::c_char = unsafe { msg_send![&*url_str, UTF8String] };
            if raw.is_null() {
                continue;
            }
            let cstr = unsafe { std::ffi::CStr::from_ptr(raw) };
            let s = match cstr.to_str() {
                Ok(s) => s,
                Err(_) => continue,
            };
            let path_str = if let Some(stripped) = s.strip_prefix("file://") {
                percent_decode(stripped)
            } else {
                s.to_string()
            };
            let path = PathBuf::from(&path_str);
            if path.exists() {
                if let Ok(mut q) = queue.lock() {
                    q.push(path);
                }
            }
        }
    }

    fn percent_decode(input: &str) -> String {
        let mut out = Vec::with_capacity(input.len());
        let bytes = input.as_bytes();
        let mut i = 0;
        while i < bytes.len() {
            if bytes[i] == b'%' && i + 2 < bytes.len() {
                if let Ok(byte) = u8::from_str_radix(
                    &input[i + 1..i + 3],
                    16,
                ) {
                    out.push(byte);
                    i += 3;
                    continue;
                }
            }
            out.push(bytes[i]);
            i += 1;
        }
        String::from_utf8(out).unwrap_or_else(|_| input.to_string())
    }

    pub fn register_open_handler() {
        use objc2_foundation::NSAppleEventManager;

        let handler = OpenHandler::new();
        let mgr = NSAppleEventManager::sharedAppleEventManager();
        let sel = sel!(handleOpenEvent:withReplyEvent:);
        unsafe {
            let _: () = msg_send![
                &*mgr,
                setEventHandler: &*handler,
                andSelector: sel,
                forEventClass: KAEVT,
                andEventID: KODOC
            ];
        }
        // Leak the handler so it lives for the process lifetime
        std::mem::forget(handler);
    }
}

fn main() -> iced::Result {
    #[cfg(target_os = "macos")]
    apple_events::register_open_handler();

    iced::application(App::new, App::update, App::view)
        .title(title)
        .theme(theme)
        .subscription(App::subscription)
        .antialiasing(true)
        .run()
}
