use super::protocol;
use std::collections::{BTreeMap, BTreeSet};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PickerRenderRow {
    pub node_id: String,
    pub parent_node_id: Option<String>,
    pub title: String,
    pub depth: u32,
    pub expandable: bool,
    pub expanded: bool,
    pub active: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PickerRenderResult {
    pub root_test_id: &'static str,
    pub item_count: usize,
    pub allow_root_pick: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PickerInitialRenderInput {
    pub rows: Vec<PickerRenderRow>,
    pub allow_root_pick: bool,
    pub root_active: bool,
}

pub fn build_picker_render_result(
    rows: &[PickerRenderRow],
    allow_root_pick: bool,
) -> PickerRenderResult {
    PickerRenderResult {
        root_test_id: protocol::TEST_ID_TREE_PICKER_ROOT,
        item_count: rows.len(),
        allow_root_pick,
    }
}

fn escape_html(input: &str) -> String {
    input
        .replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
        .replace('\'', "&#39;")
}

fn render_picker_row(
    html: &mut String,
    row: &PickerRenderRow,
    children_by_parent: &BTreeMap<Option<String>, Vec<PickerRenderRow>>,
) {
    html.push_str(&format!(
        r#"<li class="tree-node" data-node-id="{node_id}"><button type="button" class="tree-row" role="treeitem" aria-level="{aria_level}" aria-expanded="{expanded_attr}" data-rust-rendered-row="picker" data-testid="tree-picker-row" data-node-id="{node_id}" data-shell-mode="picker" data-focused="{active}" data-rust-action="pick" tabindex="{tabindex}"><span class="tree-spacer" aria-hidden="true"></span><span class="tree-kind-badge" data-kind="page" aria-hidden="true"></span><span class="tree-link"><span class="tree-link-title">{title}</span></span></button>"#,
        node_id = escape_html(&row.node_id),
        aria_level = row.depth + 1,
        expanded_attr = if row.expandable && row.expanded { "true" } else { "false" },
        active = row.active,
        tabindex = if row.active { "0" } else { "-1" },
        title = escape_html(&row.title),
    ));
    if row.expandable && row.expanded {
        if let Some(children) = children_by_parent.get(&Some(row.node_id.clone())) {
            html.push_str(r#"<ul class="tree-children">"#);
            for child in children {
                render_picker_row(html, child, children_by_parent);
            }
            html.push_str("</ul>");
        }
    }
    html.push_str("</li>");
}

pub fn render_initial_picker_html(input: &PickerInitialRenderInput) -> String {
    let mut html = String::from(
        r#"<ul class="tree-root" role="tree" data-rust-picker-renderer="initial_v1">"#,
    );
    if input.allow_root_pick {
        html.push_str(&format!(
            r#"<li class="tree-node" data-node-id="__root__"><button type="button" class="tree-row" data-testid="tree-picker-root" data-rust-rendered-row="picker-root" data-rust-action="pick-root" data-focused="{focused}" tabindex="{tabindex}"><span class="tree-spacer" aria-hidden="true"></span><span class="tree-link"><span class="tree-link-title">根目录</span></span></button></li>"#,
            focused = input.root_active,
            tabindex = if input.root_active { "0" } else { "-1" },
        ));
    }

    let ids = input
        .rows
        .iter()
        .map(|row| row.node_id.clone())
        .collect::<BTreeSet<_>>();
    let mut children_by_parent = BTreeMap::<Option<String>, Vec<PickerRenderRow>>::new();
    for row in &input.rows {
        let parent_id = row
            .parent_node_id
            .as_ref()
            .filter(|parent_id| ids.contains(*parent_id))
            .cloned();
        children_by_parent
            .entry(parent_id)
            .or_default()
            .push(row.clone());
    }
    if let Some(roots) = children_by_parent.get(&None).cloned() {
        for root in &roots {
            render_picker_row(&mut html, root, &children_by_parent);
        }
    }
    html.push_str("</ul>");
    html
}

#[cfg(test)]
mod tests {
    use super::super::filetree_renderer::{build_filetree_testids, FileTreeRenderRow};
    use super::{
        build_picker_render_result, render_initial_picker_html, PickerInitialRenderInput,
        PickerRenderRow,
    };

    #[test]
    fn tree_shell_filetree_picker_builds_file_rows_and_picker_mode() {
        let file_rows = build_filetree_testids(&[
            FileTreeRenderRow {
                row_id: "doc:page_root".into(),
                row_kind: "document".into(),
                node_id: "page_root".into(),
                parent_node_id: None,
                title: "首页".into(),
                depth: 0,
                expandable: false,
                expanded: false,
                icon_kind: "page".into(),
                document_id: Some("page_root".into()),
                asset_id: None,
                relative_path: None,
                object_identity: None,
                index_status: None,
                selected: false,
            },
            FileTreeRenderRow {
                row_id: "asset:asset_1".into(),
                row_kind: "asset".into(),
                node_id: "asset:asset_1".into(),
                parent_node_id: Some("page_root".into()),
                title: "附件".into(),
                depth: 1,
                expandable: false,
                expanded: false,
                icon_kind: "file".into(),
                document_id: Some("page_root".into()),
                asset_id: Some("asset_1".into()),
                relative_path: Some("asset_1".into()),
                object_identity: None,
                index_status: None,
                selected: false,
            },
        ]);
        let picker = build_picker_render_result(
            &[PickerRenderRow {
                node_id: "page_root".into(),
                parent_node_id: None,
                title: "首页".into(),
                depth: 0,
                expandable: false,
                expanded: false,
                active: false,
            }],
            true,
        );

        assert_eq!(file_rows[0].0, "filetree-doc-row");
        assert_eq!(file_rows[1].0, "filetree-asset-row");
        assert_eq!(picker.root_test_id, "tree-picker-root");
        assert!(picker.allow_root_pick);
    }

    #[test]
    fn tree_shell_picker_renderer_outputs_initial_html_contract() {
        let html = render_initial_picker_html(&PickerInitialRenderInput {
            allow_root_pick: true,
            root_active: false,
            rows: vec![
                PickerRenderRow {
                    node_id: "page_root".into(),
                    parent_node_id: None,
                    title: "首页 <安全>".into(),
                    depth: 0,
                    expandable: false,
                    expanded: false,
                    active: true,
                },
                PickerRenderRow {
                    node_id: "page_other".into(),
                    parent_node_id: None,
                    title: "其他页面".into(),
                    depth: 0,
                    expandable: false,
                    expanded: false,
                    active: false,
                },
            ],
        });

        assert!(html.contains("data-rust-picker-renderer=\"initial_v1\""));
        assert!(html.contains("data-rust-rendered-row=\"picker-root\""));
        assert!(html.contains("data-rust-rendered-row=\"picker\""));
        assert!(html.contains("data-testid=\"tree-picker-root\""));
        assert!(html.contains("data-testid=\"tree-picker-row\""));
        assert!(html.contains("首页 &lt;安全&gt;"));
        assert!(html.contains("data-focused=\"true\""));
        assert!(html.contains("tabindex=\"0\""));
        assert!(html.contains("tabindex=\"-1\""));
    }
}
