/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/

use super::error::MyResult;
use crate::{request_info::RequestInfo, ui_responder::UiResponse};
use anyhow::Result;
use jellycommon::{
    jellyobject::{Object, ObjectBuffer, ObjectBufferBuilder, Path},
    *,
};
use jellydb::{Filter, MultiBehaviour, Query, Sort, SortOrder, Transaction, ValueSort};
use rocket::get;
use std::collections::BTreeMap;

#[get("/n/<slug>")]
pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult<UiResponse> {
    ri.require_user()?;

    let mut page_out = ObjectBuffer::empty();
    ri.state.database.transaction(&mut |txn| {
        if let Some(row) = txn.query_single(Query {
            filter: Filter::Match(Path(vec![NO_SLUG.0]), slug.into()),
            sort: Sort::None,
        })? {
            let n = txn.get(row)?.unwrap();
            let nku = Object::EMPTY.insert(NKU_NODE, n.as_object());
            let nku = nku.as_object();

            let mut page = ObjectBufferBuilder::default();
            let title = nku
                .get(NKU_NODE)
                .unwrap_or_default()
                .get(NO_TITLE)
                .unwrap_or_default();

            page.push(VIEW_TITLE, title);
            page.push(VIEW_NODE_PAGE, nku);
            c_children(&mut page, txn, row, &nku)?;
            c_credits(&mut page, txn, &nku)?;
            c_credited(&mut page, txn, row)?;

            page_out = page.finish();
        }
        Ok(())
    })?;

    Ok(ri.respond_ui(page_out))
}

fn c_children(
    page: &mut ObjectBufferBuilder,
    txn: &mut dyn Transaction,
    row: u64,
    nku: &Object,
) -> Result<()> {
    let kind = nku
        .get(NKU_NODE)
        .unwrap_or_default()
        .get(NO_KIND)
        .unwrap_or(KIND_COLLECTION);

    let (order, path) = match kind {
        KIND_CHANNEL => (SortOrder::Descending, Path(vec![NO_RELEASEDATE.0])),
        KIND_SEASON | KIND_SHOW => (SortOrder::Ascending, Path(vec![NO_INDEX.0])),
        _ => (SortOrder::Ascending, Path(vec![NO_TITLE.0])),
    };

    let children_rows = txn
        .query(Query {
            sort: Sort::Value(ValueSort {
                multi: MultiBehaviour::First,
                offset: None,
                order,
                path,
            }),
            filter: Filter::All(vec![
                Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()),
                Filter::Match(Path(vec![NO_PARENT.0]), row.into()),
            ]),
        })?
        .collect::<Result<Vec<_>>>()?;

    if children_rows.is_empty() {
        return Ok(());
    }

    let mut list = ObjectBufferBuilder::default();

    list.push(
        NODELIST_DISPLAYSTYLE,
        match kind {
            KIND_SEASON | KIND_SHOW => NLSTYLE_LIST,
            _ => NLSTYLE_GRID,
        },
    );

    for (row, _) in children_rows {
        list.push(
            NODELIST_ITEM,
            Object::EMPTY
                .insert(NKU_NODE, txn.get(row)?.unwrap().as_object())
                .as_object(),
        );
    }

    page.push(VIEW_NODE_LIST, list.finish().as_object());
    Ok(())
}

fn c_credits(
    page: &mut ObjectBufferBuilder,
    txn: &mut dyn Transaction,
    nku: &Object,
) -> Result<()> {
    if !nku.get(NKU_NODE).unwrap_or_default().has(NO_CREDIT.0) {
        return Ok(());
    }

    let mut cats = BTreeMap::<_, Vec<_>>::new();
    for cred in nku.get(NKU_NODE).unwrap_or_default().iter(NO_CREDIT) {
        let mut o = ObjectBuffer::empty();
        if let Some(row) = cred.get(CR_NODE) {
            let node = txn.get(row)?.unwrap();
            o = o.as_object().insert(NKU_NODE, node.as_object());
        }
        if let Some(role) = cred.get(CR_ROLE) {
            o = o.as_object().insert(NKU_ROLE, role)
        }
        cats.entry(cred.get(CR_KIND).unwrap_or(CRCAT_CREW))
            .or_default()
            .push(o);
    }
    let mut cats = cats.into_iter().collect::<Vec<_>>();
    cats.sort_by_key(|(c, _)| match *c {
        CRCAT_CAST => 0,
        CRCAT_CREW => 1,
        _ => 100,
    });
    for (cat, elems) in cats {
        let mut list = ObjectBufferBuilder::default();
        list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_INLINE);
        list.push(NODELIST_TITLE, &format!("tag.cred.kind.{cat}"));
        for item in elems {
            list.push(NODELIST_ITEM, item.as_object());
        }
        page.push(VIEW_NODE_LIST, list.finish().as_object());
    }

    Ok(())
}

fn c_credited(page: &mut ObjectBufferBuilder, txn: &mut dyn Transaction, row: u64) -> Result<()> {
    let children_rows = txn
        .query(Query {
            sort: Sort::Value(ValueSort {
                multi: MultiBehaviour::First,
                offset: None,
                order: SortOrder::Ascending,
                path: Path(vec![NO_TITLE.0]),
            }),
            filter: Filter::All(vec![
                Filter::Match(Path(vec![NO_VISIBILITY.0]), VISI_VISIBLE.into()),
                Filter::Match(Path(vec![NO_CREDIT.0, CR_NODE.0]), row.into()),
            ]),
        })?
        .collect::<Result<Vec<_>>>()?;

    if children_rows.is_empty() {
        return Ok(());
    }

    let mut list = ObjectBufferBuilder::default();
    list.push(NODELIST_DISPLAYSTYLE, NLSTYLE_GRID);
    list.push(NODELIST_TITLE, "node.credited");

    for (row, _) in children_rows {
        list.push(
            NODELIST_ITEM,
            Object::EMPTY
                .insert(NKU_NODE, txn.get(row)?.unwrap().as_object())
                .as_object(),
        );
    }

    page.push(VIEW_NODE_LIST, list.finish().as_object());
    Ok(())
}
