use anyhow::Result;
use mdbook::book::{Book, BookItem};
use mdbook::errors::Error;
use mdbook::preprocess::{Preprocessor, PreprocessorContext as MdBookContext};
use settings::KeymapFile;
use std::sync::Arc;
use util::asset_str;

mod templates;

use templates::{ActionTemplate, KeybindingTemplate, Template};

pub struct PreprocessorContext {
    macos_keymap: Arc<KeymapFile>,
    linux_keymap: Arc<KeymapFile>,
}

impl PreprocessorContext {
    pub fn new() -> Result<Self> {
        let macos_keymap = Arc::new(load_keymap("keymaps/default-macos.json")?);
        let linux_keymap = Arc::new(load_keymap("keymaps/default-linux.json")?);
        Ok(Self {
            macos_keymap,
            linux_keymap,
        })
    }

    pub fn find_binding(&self, os: &str, action: &str) -> Option<String> {
        let keymap = match os {
            "macos" => &self.macos_keymap,
            "linux" => &self.linux_keymap,
            _ => return None,
        };

        // Find the binding in reverse order, as the last binding takes precedence.
        keymap.sections().rev().find_map(|section| {
            section.bindings().rev().find_map(|(keystroke, a)| {
                if a.to_string() == action {
                    Some(keystroke.to_string())
                } else {
                    None
                }
            })
        })
    }
}

fn load_keymap(asset_path: &str) -> Result<KeymapFile> {
    let content = asset_str::<settings::SettingsAssets>(asset_path);
    KeymapFile::parse(content.as_ref())
}

pub struct ZedDocsPreprocessor {
    context: PreprocessorContext,
    templates: Vec<Box<dyn Template>>,
}

impl ZedDocsPreprocessor {
    pub fn new() -> Result<Self> {
        let context = PreprocessorContext::new()?;
        let templates: Vec<Box<dyn Template>> = vec![
            Box::new(KeybindingTemplate::new()),
            Box::new(ActionTemplate::new()),
        ];
        Ok(Self { context, templates })
    }

    fn process_content(&self, content: &str) -> String {
        let mut processed = content.to_string();
        for template in &self.templates {
            processed = template.process(&self.context, &processed);
        }
        processed
    }
}

impl Preprocessor for ZedDocsPreprocessor {
    fn name(&self) -> &str {
        "zed-docs-preprocessor"
    }

    fn run(&self, _ctx: &MdBookContext, mut book: Book) -> Result<Book, Error> {
        book.for_each_mut(|item| {
            if let BookItem::Chapter(chapter) = item {
                chapter.content = self.process_content(&chapter.content);
            }
        });
        Ok(book)
    }

    fn supports_renderer(&self, renderer: &str) -> bool {
        renderer != "not-supported"
    }
}
