mod draft;

pub use draft::*;

use crate::emoji::*;
use crate::nostru;
use crate::routes::route_prelude::*;
use gnostr_types::{ContentSegment, ShatteredContent};
use subslay::EmojiStylist;

#[derive(Template)]
#[template(path = "note.gmi", escape = "txt")]
pub struct NoteThreadTemplate {
    title: Option<String>,
    event: Event,
    note_blocks: Vec<Block>,
    replies_blocks: Option<Vec<Block>>,
}
#[derive(Template)]
#[template(path = "note_reaction_select.gmi", escape = "txt")]
pub struct NoteReactionSelect {
    event_id: EventId,
}

pub struct NoteContext {
    event: Event,
}

/// Get the event ids of "e" tags with a specific [`Marker`]
fn event_ids_with_marker(
    event: &Event,
    with_marker: Marker,
) -> impl Iterator<Item = &EventId> {
    event.tags.filter_standardized(TagKind::e()).filter_map(
        move |tag| match tag {
            TagStandard::Event {
                event_id, marker, ..
            } => {
                if *marker == Some(with_marker) {
                    Some(event_id)
                } else {
                    None
                }
            }
            _ => None,
        },
    )
}

/// Return the event id of the last "e" tag with the given [`Marker`]
fn last_etag_with_marker(
    event: &Event,
    with_marker: Marker,
) -> Option<&EventId> {
    event
        .tags
        .filter_standardized(TagKind::e())
        .filter_map(move |tag| match tag {
            TagStandard::Event {
                event_id, marker, ..
            } => {
                if *marker == Some(with_marker) {
                    Some(event_id)
                } else {
                    None
                }
            }
            _ => None,
        })
        .last()
}

async fn note_context(
    params: Parameters,
    client: &Client,
) -> Option<NoteContext> {
    let Some(evid) = params.get("event_id") else {
        return None;
    };

    let Ok(event_id) = EventId::parse(evid) else {
        return None;
    };

    return match client.database().event_by_id(&event_id).await {
        Ok(ev) => {
            return if ev.is_some() {
                Some(NoteContext { event: ev.unwrap() })
            } else {
                None
            };
        }
        Err(_err) => None,
    };
}

pub async fn post_note(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let mode = ctx.parameters.get("mode").unwrap_or("raw");

    let pow: u8 = match ctx.parameters.get("pow") {
        Some(param) => match param.to_string().parse::<u8>() {
            Ok(val) => match val {
                5..=50 => val,
                _ => 0,
            },
            Err(_error) => 0,
        },
        None => 0,
    };

    let url = ctx.url.clone();

    if let Some(text) = dec_urlq(&url) {
        let mut tags = Vec::new();
        let shattered_content = ShatteredContent::new(text.clone());
        for segment in shattered_content.segments.iter() {
            if let ContentSegment::NostrUrl(nostr_url) = segment
                && let Ok(n21) = Nip21::parse(&format!("{nostr_url}"))
                && let Nip21::Pubkey(pubk) = n21
            {
                tags.push(Tag::public_key(pubk));
            }
        }

        if let Some(url_hashtag) = ctx.parameters.get("hashtag") {
            // Hashtag in the URL => add a hashtag Tag to the list of tags
            tags.push(Tag::hashtag(url_hashtag));
        }

        tags.extend(nostru::body_hashtags(&text));

        let event_builder = match mode {
            "emojize" => {
                // Emojize with subslay
                match EmojiStylist::new() {
                    Ok(stylist) => {
                        EventBuilder::text_note(stylist.slay(&text).join(""))
                            .tags(tags)
                            .pow(pow)
                    }
                    Err(_) => {
                        EventBuilder::text_note(&text).tags(tags).pow(pow)
                    }
                }
            }
            _ => EventBuilder::text_note(&text).tags(tags),
        };

        if let Ok(output) = user.send_builder(event_builder).await {
            Response::temporary_redirect(format!(
                "/note/{}/thread",
                output.id()
            ))
        } else {
            resp_error_send_note()
        }
    } else {
        resp_note_prompt_text()
    }
}

pub async fn note_thread(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let mut doc = vec![];
    let mut rblocks = vec![];
    let evid = ctx.parameters.get("event_id").unwrap();

    if let Ok(note_event_id) = EventId::parse(evid) {
        let e_filter = Filter::new()
            .kind(Kind::TextNote)
            .kind(Kind::LongFormTextNote)
            .id(note_event_id);
        let Ok(events) = user
            .client
            .fetch_combined_events(e_filter, Duration::from_secs(3))
            .await
        else {
            return Response::temporary_failure(t!("fetch_events_failed"));
        };

        let Some(event) = events.first() else {
            return Response::temporary_failure(t!("no_such_event"));
        };

        let rf_filter = Filter::new().kind(Kind::TextNote).event(note_event_id);
        let Ok(replies_events) = user
            .client
            .fetch_combined_events(rf_filter, Duration::from_secs(3))
            .await
        else {
            return Response::temporary_failure(t!(
                "fetch_note_replies_failed"
            ));
        };

        let usermeta =
            nostru::get_user_metadata(&user.client, event.pubkey).await;

        let title = extract_first_words(event, 16);

        let roptions = NoteRenderOptions::default()
            | NoteRenderOptions::PUBLIC_BOOKMARK
            | NoteRenderOptions::SHOW_HASHTAGS;

        match render_note(&user.client, event, usermeta.clone(), roptions).await
        {
            Ok(mut blocks) => {
                doc.append(&mut blocks);
            }
            Err(_err) => {}
        }

        for revent in replies_events {
            let root_tag = last_etag_with_marker(&revent, Marker::Root);
            let reply_tag = last_etag_with_marker(&revent, Marker::Reply);

            if root_tag.is_some()
                && let Some(reply_id) = reply_tag
                && *reply_id != note_event_id
            {
                // This event is in the reply chain and has a
                // tag with root marker and a tag with reply marker.
                // But this is a reply to another note. Ignore it.
                continue;
            } else if root_tag.is_none() && reply_tag.is_none() {
                // No root or reply marker (just a mention)
                continue;
            }

            let rusermeta =
                nostru::get_user_metadata(&user.client, revent.pubkey).await;

            if let Ok(mut blks) =
                render_note(&user.client, &revent, rusermeta.clone(), roptions)
                    .await
            {
                rblocks.append(&mut blks);
            }
        }

        let tmpl = NoteThreadTemplate {
            title,
            event: event.clone(),
            note_blocks: doc,
            replies_blocks: Some(rblocks),
        };

        Response::success(WindTemplate::render(tmpl))
    } else {
        Response::temporary_failure(t!("invalid_event_id"))
    }
}

pub async fn note_reply(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    let url = ctx.url.clone();

    if let Some(text) = dec_urlq(&url) {
        let builder =
            EventBuilder::text_note_reply(&text, &note_ctx.event, None, None)
                .tags(nostru::body_hashtags(&text));

        match user.send_builder(builder).await {
            Ok(output) => Response::temporary_redirect(format!(
                "/note/{}/thread",
                output.id()
            )),
            Err(err) => Response::temporary_failure(format!("{err}")),
        }
    } else {
        resp_note_prompt_text()
    }
}

pub async fn note_repost(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    let builder = EventBuilder::repost(&note_ctx.event, None);

    match user.send_builder(builder).await {
        Ok(_) => Response::temporary_redirect(format!(
            "/note/{}/thread",
            note_ctx.event.id
        )),
        Err(_) => resp_error_send_note(),
    }
}

/// Republish a note
pub async fn note_republish(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Result<Response, UserError> {
    let note_ctx = note_context(ctx.parameters, &user.client)
        .await
        .ok_or(UserError::InvalidParams)?;

    let output = user.client.send_event(&note_ctx.event).await?;

    Ok(Response::temporary_redirect(format!(
        "/note/{}/thread",
        output.id()
    )))
}

/// React to a note (custom input from user)
pub async fn note_react_custom(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(emoji) = decode_query(&ctx) else {
        return Response::input(t!("input_reaction_emoji"));
    };

    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    let builder = EventBuilder::reaction(&note_ctx.event, emoji);

    match user.send_builder(builder).await {
        Ok(_) => Response::temporary_redirect(format!(
            "/note/{}/thread",
            note_ctx.event.id
        )),
        Err(err) => {
            Response::temporary_failure(format!("Send event failed: {err}"))
        }
    }
}

/// React to a note, using a template that lets the user pick an emoji
pub async fn note_react_select(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    Response::success(WindTemplate::render(NoteReactionSelect {
        event_id: note_ctx.event.id,
    }))
}

/// Sent an event deletion request for a note
pub async fn note_delete(
    ctx: RouteContext,
    user: &'static mut CaracalUser,
) -> Response {
    let Some(note_ctx) = note_context(ctx.parameters, &user.client).await
    else {
        return resp_invalid_params();
    };

    // Ask for confirmation
    if let Some(input_text) = dec_urlq(&ctx.url) {
        let input_text = input_text.to_lowercase();
        let yes = t!("yes").to_lowercase();

        if input_text != yes {
            return Response::input(t!(
                "delete_note_confirm",
                confirm = t!("yes_positive")
            ));
        }
    } else {
        return Response::input(t!(
            "delete_note_confirm",
            confirm = t!("yes_positive")
        ));
    };

    let builder =
        EventBuilder::delete(EventDeletionRequest::new().id(note_ctx.event.id));

    match user.send_builder(builder).await {
        Ok(_) => Response::temporary_redirect("/"),
        Err(err) => {
            Response::temporary_failure(format!("Send event failed: {err}"))
        }
    }
}
