use super::filetree_selection::{FileTreeSelectionReducerContract, FileTreeSelectionState};
use super::focus_state::PageFocusKeyboardReducerContract;
use super::picker_state::PickerStateReducerContract;
use serde::Serialize;
use std::collections::BTreeSet;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum TreeShellRendererMode {
    Page,
    FileTree,
    Picker,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TreeShellRendererInput {
    pub mode: TreeShellRendererMode,
    pub projection_item_ids: Vec<String>,
    pub expanded_ids: BTreeSet<String>,
    pub focused_id: Option<String>,
    pub page_focus_keyboard_reducer: Option<PageFocusKeyboardReducerContract>,
    pub filetree_selection: FileTreeSelectionState,
    pub filetree_selection_reducer: Option<FileTreeSelectionReducerContract>,
    pub active_picker_item: Option<String>,
    pub excluded_picker_ids: BTreeSet<String>,
    pub picker_state_reducer: Option<PickerStateReducerContract>,
    pub command_dispatcher: TreeShellCommandDispatcher,
    pub runtime_artifact: TreeShellRuntimeArtifactBoundary,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TreeShellCommandDispatcher {
    pub channel: String,
    pub command_names: BTreeSet<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TreeShellRuntimeArtifactBoundary {
    pub contract_name: &'static str,
    pub family: &'static str,
    pub version: u8,
    pub execution_strategy: &'static str,
    pub browser_bridge: &'static str,
    pub wasm_module_url: Option<&'static str>,
    pub js_glue_url: Option<&'static str>,
    pub input_fields: BTreeSet<&'static str>,
    pub output_channels: BTreeSet<&'static str>,
    pub event_kinds: BTreeSet<&'static str>,
    pub runtime_api: TreeShellRuntimeApiBoundary,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TreeShellRuntimeApiBoundary {
    pub request_contract: &'static str,
    pub result_contract: &'static str,
    pub reduce_endpoint: &'static str,
    pub state_snapshots: BTreeSet<&'static str>,
    pub dom_patch_kinds: BTreeSet<&'static str>,
    pub host_event_kinds: BTreeSet<&'static str>,
    pub command_event_kinds: BTreeSet<&'static str>,
}

impl Default for TreeShellRuntimeArtifactBoundary {
    fn default() -> Self {
        Self {
            contract_name: "rust_tree_shell_runtime_artifact_v1",
            family: "rust_family",
            version: 1,
            execution_strategy: "browser_bridge",
            browser_bridge: "iframe_srcdoc",
            wasm_module_url: Some("/api/tree-shell-runtime/mnote-tree-shell-runtime_bg.wasm"),
            js_glue_url: Some("/api/tree-shell-runtime/mnote-tree-shell-runtime.js"),
            input_fields: BTreeSet::from([
                "rendererInput",
                "projectionItems",
                "expandedIds",
                "selectedRowIds",
                "activePickerItem",
                "focusedId",
            ]),
            output_channels: BTreeSet::from(["domPatch", "intentEvent", "commandDispatchEvent"]),
            event_kinds: BTreeSet::from([
                "focus",
                "keyboard",
                "expandCollapse",
                "selection",
                "contextMenu",
                "dragDrop",
                "pick",
            ]),
            runtime_api: TreeShellRuntimeApiBoundary {
                request_contract: "TreeShellRuntimeRequest",
                result_contract: "TreeShellRuntimeResult",
                reduce_endpoint: "/api/tree/runtime/reduce",
                state_snapshots: BTreeSet::from(["page", "fileTree", "picker"]),
                dom_patch_kinds: BTreeSet::from(["pageState", "fileTreeState", "pickerState"]),
                host_event_kinds: BTreeSet::from([
                    "pageOpen",
                    "pageContextMenu",
                    "fileTreeOpen",
                    "fileTreeContextMenu",
                    "fileTreeInternalDrop",
                    "fileTreeExternalDrop",
                    "fileTreeKeyboardCommand",
                    "pickerPickRoot",
                    "pickerPickDocument",
                ]),
                command_event_kinds: BTreeSet::from([
                    "createNode",
                    "renameNode",
                    "moveSubtree",
                    "copyResource",
                    "moveResource",
                    "uploadResource",
                ]),
            },
        }
    }
}

impl TreeShellRendererInput {
    pub fn page(input: PageTreeRendererInput) -> Self {
        Self {
            mode: TreeShellRendererMode::Page,
            projection_item_ids: input.projection_item_ids,
            expanded_ids: input.expanded_ids,
            focused_id: input.focused_id,
            page_focus_keyboard_reducer: Some(PageFocusKeyboardReducerContract::default()),
            filetree_selection: FileTreeSelectionState::default(),
            filetree_selection_reducer: None,
            active_picker_item: None,
            excluded_picker_ids: BTreeSet::new(),
            picker_state_reducer: None,
            command_dispatcher: input.command_dispatcher,
            runtime_artifact: TreeShellRuntimeArtifactBoundary::default(),
        }
    }

    pub fn filetree(input: FileTreeRendererInput) -> Self {
        Self {
            mode: TreeShellRendererMode::FileTree,
            projection_item_ids: input.projection_item_ids,
            expanded_ids: input.expanded_ids,
            focused_id: input.filetree_selection.focused_row_id.clone(),
            page_focus_keyboard_reducer: None,
            filetree_selection: input.filetree_selection,
            filetree_selection_reducer: Some(FileTreeSelectionReducerContract::default()),
            active_picker_item: None,
            excluded_picker_ids: BTreeSet::new(),
            picker_state_reducer: None,
            command_dispatcher: input.command_dispatcher,
            runtime_artifact: TreeShellRuntimeArtifactBoundary::default(),
        }
    }

    pub fn picker(input: PickerRendererInput) -> Self {
        Self {
            mode: TreeShellRendererMode::Picker,
            projection_item_ids: input.projection_item_ids,
            expanded_ids: input.expanded_ids,
            focused_id: input.active_picker_item.clone(),
            page_focus_keyboard_reducer: None,
            filetree_selection: FileTreeSelectionState::default(),
            filetree_selection_reducer: None,
            active_picker_item: input.active_picker_item,
            excluded_picker_ids: input.excluded_picker_ids,
            picker_state_reducer: Some(PickerStateReducerContract::default()),
            command_dispatcher: input.command_dispatcher,
            runtime_artifact: TreeShellRuntimeArtifactBoundary::default(),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PageTreeRendererInput {
    pub projection_item_ids: Vec<String>,
    pub expanded_ids: BTreeSet<String>,
    pub focused_id: Option<String>,
    pub command_dispatcher: TreeShellCommandDispatcher,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileTreeRendererInput {
    pub projection_item_ids: Vec<String>,
    pub expanded_ids: BTreeSet<String>,
    pub filetree_selection: FileTreeSelectionState,
    pub command_dispatcher: TreeShellCommandDispatcher,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PickerRendererInput {
    pub projection_item_ids: Vec<String>,
    pub expanded_ids: BTreeSet<String>,
    pub active_picker_item: Option<String>,
    pub excluded_picker_ids: BTreeSet<String>,
    pub command_dispatcher: TreeShellCommandDispatcher,
}

#[cfg(test)]
mod tests {
    use super::{
        FileTreeRendererInput, PageTreeRendererInput, PickerRendererInput,
        TreeShellCommandDispatcher, TreeShellRendererInput, TreeShellRendererMode,
    };
    use crate::tree_shell::filetree_selection::FileTreeSelectionState;
    use std::collections::BTreeSet;

    fn set(values: &[&str]) -> BTreeSet<String> {
        values.iter().map(|value| (*value).to_string()).collect()
    }

    fn dispatcher() -> TreeShellCommandDispatcher {
        TreeShellCommandDispatcher {
            channel: "mnote.tree.shell".into(),
            command_names: set(&["tree.node.create", "tree.resource.move"]),
        }
    }

    #[test]
    fn tree_shell_renderer_input_contract_covers_page_filetree_and_picker_state() {
        let page = TreeShellRendererInput::page(PageTreeRendererInput {
            projection_item_ids: vec!["doc:root".into()],
            expanded_ids: set(&["doc:root"]),
            focused_id: Some("doc:root".into()),
            command_dispatcher: dispatcher(),
        });
        assert_eq!(page.mode, TreeShellRendererMode::Page);
        assert_eq!(page.focused_id.as_deref(), Some("doc:root"));
        assert_eq!(page.command_dispatcher.channel, "mnote.tree.shell");
        assert_eq!(
            page.page_focus_keyboard_reducer
                .as_ref()
                .map(|contract| contract.contract_name),
            Some("rust_page_focus_keyboard_reducer_v1")
        );

        let mut selection = FileTreeSelectionState::from_selected(&["asset:a".into()]);
        selection.focused_row_id = Some("asset:a".into());
        let filetree = TreeShellRendererInput::filetree(FileTreeRendererInput {
            projection_item_ids: vec!["doc:root".into(), "asset:a".into()],
            expanded_ids: set(&["doc:root"]),
            filetree_selection: selection,
            command_dispatcher: dispatcher(),
        });
        assert_eq!(filetree.mode, TreeShellRendererMode::FileTree);
        assert_eq!(filetree.focused_id.as_deref(), Some("asset:a"));
        assert!(filetree
            .filetree_selection
            .selected_row_ids
            .contains("asset:a"));
        assert!(filetree.page_focus_keyboard_reducer.is_none());
        assert_eq!(
            filetree
                .filetree_selection_reducer
                .as_ref()
                .map(|contract| contract.contract_name),
            Some("rust_filetree_selection_reducer_v1")
        );

        let picker = TreeShellRendererInput::picker(PickerRendererInput {
            projection_item_ids: vec!["doc:root".into(), "doc:child".into()],
            expanded_ids: set(&["doc:root"]),
            active_picker_item: Some("doc:child".into()),
            excluded_picker_ids: set(&["doc:archived"]),
            command_dispatcher: dispatcher(),
        });
        assert_eq!(picker.mode, TreeShellRendererMode::Picker);
        assert_eq!(picker.focused_id.as_deref(), Some("doc:child"));
        assert!(picker.excluded_picker_ids.contains("doc:archived"));
        assert!(picker.page_focus_keyboard_reducer.is_none());
        assert!(picker.filetree_selection_reducer.is_none());
        assert_eq!(
            picker
                .picker_state_reducer
                .as_ref()
                .map(|contract| contract.contract_name),
            Some("rust_picker_state_reducer_v1")
        );
    }

    #[test]
    fn tree_shell_renderer_input_exposes_runtime_artifact_boundary() {
        let page = TreeShellRendererInput::page(PageTreeRendererInput {
            projection_item_ids: vec!["doc:root".into()],
            expanded_ids: set(&["doc:root"]),
            focused_id: Some("doc:root".into()),
            command_dispatcher: dispatcher(),
        });

        let artifact = page.runtime_artifact;
        assert_eq!(
            artifact.contract_name,
            "rust_tree_shell_runtime_artifact_v1"
        );
        assert_eq!(artifact.family, "rust_family");
        assert_eq!(artifact.version, 1);
        assert_eq!(artifact.execution_strategy, "browser_bridge");
        assert_eq!(artifact.browser_bridge, "iframe_srcdoc");
        assert_eq!(
            artifact.wasm_module_url,
            Some("/api/tree-shell-runtime/mnote-tree-shell-runtime_bg.wasm")
        );
        assert_eq!(
            artifact.js_glue_url,
            Some("/api/tree-shell-runtime/mnote-tree-shell-runtime.js")
        );
        assert!(artifact.input_fields.contains("rendererInput"));
        assert!(artifact.input_fields.contains("projectionItems"));
        assert!(artifact.input_fields.contains("expandedIds"));
        assert!(artifact.input_fields.contains("selectedRowIds"));
        assert!(artifact.input_fields.contains("activePickerItem"));
        assert!(artifact.input_fields.contains("focusedId"));
        assert!(artifact.output_channels.contains("domPatch"));
        assert!(artifact.output_channels.contains("intentEvent"));
        assert!(artifact.output_channels.contains("commandDispatchEvent"));
        assert_eq!(
            artifact.runtime_api.request_contract,
            "TreeShellRuntimeRequest"
        );
        assert_eq!(
            artifact.runtime_api.result_contract,
            "TreeShellRuntimeResult"
        );
        assert_eq!(
            artifact.runtime_api.reduce_endpoint,
            "/api/tree/runtime/reduce"
        );
        assert!(artifact.runtime_api.state_snapshots.contains("page"));
        assert!(artifact.runtime_api.state_snapshots.contains("fileTree"));
        assert!(artifact.runtime_api.state_snapshots.contains("picker"));
        assert!(artifact.runtime_api.dom_patch_kinds.contains("pageState"));
        assert!(artifact
            .runtime_api
            .dom_patch_kinds
            .contains("fileTreeState"));
        assert!(artifact.runtime_api.dom_patch_kinds.contains("pickerState"));
        assert!(artifact.runtime_api.host_event_kinds.contains("pageOpen"));
        assert!(artifact
            .runtime_api
            .host_event_kinds
            .contains("fileTreeOpen"));
        assert!(artifact
            .runtime_api
            .host_event_kinds
            .contains("fileTreeInternalDrop"));
        assert!(artifact
            .runtime_api
            .host_event_kinds
            .contains("fileTreeExternalDrop"));
        assert!(artifact
            .runtime_api
            .host_event_kinds
            .contains("pickerPickDocument"));
        assert!(artifact
            .runtime_api
            .command_event_kinds
            .contains("createNode"));
        assert!(artifact
            .runtime_api
            .command_event_kinds
            .contains("renameNode"));
        assert!(artifact
            .runtime_api
            .command_event_kinds
            .contains("moveSubtree"));
        assert!(artifact
            .runtime_api
            .command_event_kinds
            .contains("copyResource"));
        assert!(artifact
            .runtime_api
            .command_event_kinds
            .contains("moveResource"));
        assert!(artifact
            .runtime_api
            .command_event_kinds
            .contains("uploadResource"));
        assert!(artifact.event_kinds.contains("focus"));
        assert!(artifact.event_kinds.contains("keyboard"));
        assert!(artifact.event_kinds.contains("expandCollapse"));
        assert!(artifact.event_kinds.contains("selection"));
        assert!(artifact.event_kinds.contains("contextMenu"));
        assert!(artifact.event_kinds.contains("dragDrop"));
        assert!(artifact.event_kinds.contains("pick"));
    }
}
