use camino::Utf8PathBuf;
use icu_locale::locale;

use crate::{ExtensionState, vscode_sys};

pub mod hover;
pub mod line_annotation;

pub fn create_decoration_type<'env>(
    env: &'env napi::Env,
) -> Result<vscode_sys::TextEditorDecorationType<'env>, napi::Error> {
    let theme_color =
        vscode_sys::ThemeColor::new(env, "pijul.decorations.inlineCredit.foreground")?;

    let after = vscode_sys::ThemableDecorationAttachmentRenderOptions::new(env)?
        // Margin of roughly 10 characters to the right
        // https://developer.mozilla.org/en-US/docs/Web/CSS/length#ch
        .margin("0 0 0 10ch")?
        .color(&theme_color)?;
    let render_options = vscode_sys::DecorationRenderOptions::new(env)?.after(after)?;
    let decoration_type =
        vscode_sys::window::create_text_editor_decoration_type(env, render_options)?;

    Ok(decoration_type)
}

// TODO: different rendering modes? e.g. render only at boundaries between lines, render only on cursors, etc
pub fn render(
    env: &napi::Env,
    program_state: &ExtensionState,
    editor: &vscode_sys::TextEditor,
) -> Result<(), napi::Error> {
    let editor_document = editor.get_document()?;
    let document_uri = editor_document.get_uri()?;

    let Some((repository_path, open_repository)) = program_state
        .repositories
        .get_open_repository(env, &document_uri)?
    else {
        tracing::warn!(
            message = "No repository for URI",
            uri = document_uri.to_string()?
        );

        return Ok(());
    };

    let absolute_file_path = Utf8PathBuf::from(document_uri.get_fs_path()?);
    let relative_file_path =
        absolute_file_path
            .strip_prefix(&repository_path)
            .map_err(|error| {
                napi::Error::from_reason(format!(
                    "Failed to strip prefix {repository_path} from {absolute_file_path}: {error}"
                ))
            })?;

    let open_file = open_repository
        .repository
        .get_open_file(relative_file_path)
        .ok_or_else(|| {
            napi::Error::from_reason(format!("unable to get open file for {relative_file_path}"))
        })?;

    // TODO: don't initialize here
    let localization_context = l10n_embed::Context::new(locale!("en-US"), false);

    let mut decoration_options_list = Vec::new();
    for editor_selection in editor.get_selections()? {
        let start_line = editor_selection.get_start()?.get_line()?;
        let end_line: u32 = editor_selection.get_end()?.get_line()?;

        let selected_range = (start_line as usize)..=(end_line as usize);
        let credits = open_file.credit(selected_range);

        for credit_span in credits {
            for line in credit_span.lines {
                let annotation_range = line_annotation::range(env, open_file, line)?;
                let line_annotation_text = line_annotation::render(
                    &localization_context,
                    &open_repository.repository,
                    &editor_document,
                    open_file,
                    &credit_span.value,
                )?;

                let hover_message = hover::render(
                    env,
                    &localization_context,
                    &open_repository.repository,
                    open_file,
                    credit_span.value,
                )?;

                let after = vscode_sys::ThemableDecorationAttachmentRenderOptions::new(env)?
                    .content_text(&line_annotation_text)?;
                let render_options =
                    vscode_sys::DecorationInstanceRenderOptions::new(env)?.after(after)?;
                let decoration_options =
                    vscode_sys::DecorationOptions::new(env, &annotation_range)?
                        .hover_message(&hover_message)?
                        .render_options(render_options)?;

                decoration_options_list.push(decoration_options);
            }
        }
    }

    let decoration_type = program_state.decoration_type.get_inner(env)?;
    editor.set_decorations(decoration_type, decoration_options_list)?;

    Ok(())
}