use super::*;

pub(super) fn begin_new_folder(world: &mut World, parent: String) {
    world
        .resource_mut::<AssetBrowserUiState>()
        .pending_path_edit = Some(ContentPathEdit {
        kind: ContentPathEditKind::NewFolder { parent },
        name: "New Folder".into(),
    });
}

pub(super) fn create_material_here(world: &mut World, folder: &str) {
    if let Err(error) =
        crate::ui::material_library::create_material_asset_in(world, Path::new(folder))
    {
        world.resource_mut::<SceneIo>().status = error;
    }
}

pub(super) fn begin_rename(world: &mut World) {
    if world.resource::<EditorAssets>().selections.len() > 1 {
        world.resource_mut::<SceneIo>().status = "Rename requires one selected item".into();
        return;
    }
    let Some(source) = selected_content_path(world) else {
        world.resource_mut::<SceneIo>().status = "Select an asset or folder to rename".into();
        return;
    };
    let name = Path::new(&source)
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("Asset")
        .to_string();
    world
        .resource_mut::<AssetBrowserUiState>()
        .pending_path_edit = Some(ContentPathEdit {
        kind: ContentPathEditKind::Rename { source },
        name,
    });
}

pub(super) fn set_content_clipboard(world: &mut World, cut: bool) {
    let sources = selected_content_paths(world);
    if sources.is_empty() {
        world.resource_mut::<SceneIo>().status = "Select an asset or folder first".into();
        return;
    }
    world.resource_mut::<AssetBrowserUiState>().clipboard = Some(ContentClipboard {
        sources: sources.clone(),
        cut,
    });
    world.resource_mut::<SceneIo>().status = format!(
        "{} {} item(s)",
        if cut { "Cut" } else { "Copied" },
        sources.len()
    );
}

pub(super) fn duplicate_selection(world: &mut World) {
    let sources = selected_content_paths(world);
    if sources.is_empty() {
        world.resource_mut::<SceneIo>().status = "Select an asset or folder to duplicate".into();
        return;
    }
    let operations = sources
        .into_iter()
        .map(|source| {
            let source_path = Path::new(&source);
            let parent = source_path
                .parent()
                .unwrap_or_else(|| Path::new(ASSETS_ROOT));
            content_pipeline::ContentOperation::Copy {
                source: source.clone().into(),
                destination: unique_copy_destination(parent, source_path),
            }
        })
        .collect();
    queue_content_operations(world, operations, false, false);
}

pub(super) fn paste_content_clipboard(world: &mut World) {
    let destination_folder = world.resource::<EditorAssets>().current_folder.clone();
    paste_content_clipboard_into(world, &destination_folder);
}

pub(super) fn paste_content_clipboard_into(world: &mut World, destination_folder: &str) {
    let Some(clipboard) = world.resource::<AssetBrowserUiState>().clipboard.clone() else {
        return;
    };
    let mut reserved = HashSet::new();
    let operations: Vec<_> = clipboard
        .sources
        .iter()
        .filter_map(|source| {
            let source_path = Path::new(source);
            if clipboard.cut && source_path.parent() == Some(Path::new(destination_folder)) {
                return None;
            }
            let destination = paste_destination(
                Path::new(destination_folder),
                source_path,
                clipboard.cut,
                &mut reserved,
            );
            Some(if clipboard.cut {
                content_pipeline::ContentOperation::Move {
                    source: source.clone().into(),
                    destination,
                }
            } else {
                content_pipeline::ContentOperation::Copy {
                    source: source.clone().into(),
                    destination,
                }
            })
        })
        .collect();
    if operations.is_empty() {
        world.resource_mut::<SceneIo>().status = "Content is already in this folder".into();
        return;
    }
    queue_content_operations(world, operations, clipboard.cut, false);
}

pub(super) fn paste_destination(
    parent: &Path,
    source: &Path,
    cut: bool,
    reserved: &mut HashSet<PathBuf>,
) -> PathBuf {
    let direct = parent.join(source.file_name().unwrap_or_default());
    if cut {
        // Moves keep their intended filename. Existing or duplicate targets must remain visible as
        // review collisions instead of silently turning a move into a renamed copy.
        return direct;
    }
    if !direct.exists() && reserved.insert(direct.clone()) {
        return direct;
    }
    let mut candidate = unique_copy_destination(parent, source);
    while !reserved.insert(candidate.clone()) {
        candidate = unique_copy_destination(parent, &candidate);
    }
    candidate
}

pub(super) fn unique_copy_destination(parent: &Path, source: &Path) -> PathBuf {
    let name = source
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("Asset");
    let (stem, suffix) = name
        .split_once('.')
        .map_or((name, ""), |(stem, suffix)| (stem, suffix));
    for index in 1.. {
        let marker = if index == 1 {
            "_copy".into()
        } else {
            format!("_copy_{index}")
        };
        let candidate_name = if suffix.is_empty() {
            format!("{stem}{marker}")
        } else {
            format!("{stem}{marker}.{suffix}")
        };
        let candidate = parent.join(candidate_name);
        if !candidate.exists() {
            return candidate;
        }
    }
    unreachable!()
}

pub(super) fn draw_path_edit_modal(world: &mut World, ctx: &egui::Context) {
    let Some(mut edit) = world
        .resource::<AssetBrowserUiState>()
        .pending_path_edit
        .clone()
    else {
        return;
    };
    let title = match edit.kind {
        ContentPathEditKind::Rename { .. } => "Rename Content",
        ContentPathEditKind::NewFolder { .. } => "Create Folder",
    };
    let mut apply = false;
    let mut cancel = false;
    egui::Window::new(title)
        .collapsible(false)
        .resizable(false)
        .show(ctx, |ui| {
            ui.label("Name");
            let response = ui.add_sized([300.0, 24.0], egui::TextEdit::singleline(&mut edit.name));
            let valid = valid_content_name(&edit.name);
            if !valid {
                ui.colored_label(
                    crate::ui::theme::ERROR,
                    "Use a normal file/folder name without slashes.",
                );
            }
            ui.horizontal(|ui| {
                apply = ui.add_enabled(valid, egui::Button::new("Apply")).clicked()
                    || (valid
                        && response.lost_focus()
                        && ui.input(|input| input.key_pressed(egui::Key::Enter)));
                cancel = ui.button("Cancel").clicked();
            });
        });
    if cancel {
        world
            .resource_mut::<AssetBrowserUiState>()
            .pending_path_edit = None;
    } else if apply {
        let operation = match &edit.kind {
            ContentPathEditKind::Rename { source } => {
                let source_path = Path::new(source);
                let destination = source_path
                    .parent()
                    .unwrap_or_else(|| Path::new(ASSETS_ROOT))
                    .join(&edit.name);
                content_pipeline::ContentOperation::Move {
                    source: source.into(),
                    destination,
                }
            }
            ContentPathEditKind::NewFolder { parent } => {
                content_pipeline::ContentOperation::CreateFolder {
                    path: Path::new(parent).join(&edit.name),
                }
            }
        };
        if queue_content_operations(world, vec![operation], false, false) {
            world
                .resource_mut::<AssetBrowserUiState>()
                .pending_path_edit = None;
        }
    } else {
        world
            .resource_mut::<AssetBrowserUiState>()
            .pending_path_edit = Some(edit);
    }
}

pub(super) fn valid_content_name(name: &str) -> bool {
    let trimmed = name.trim();
    !trimmed.is_empty()
        && !matches!(trimmed, "." | "..")
        && !trimmed.contains(['/', '\\'])
        && !trimmed.starts_with('.')
}

pub(super) fn queue_content_operations(
    world: &mut World,
    operations: Vec<content_pipeline::ContentOperation>,
    clear_cut_clipboard: bool,
    clear_drag: bool,
) -> bool {
    if operations.is_empty() {
        return false;
    }
    let document = world
        .resource::<crate::asset_db::AssetRegistry>()
        .document();
    let preview = match content_pipeline::preview_transaction(Path::new("."), operations, &document)
    {
        Ok(preview) => preview,
        Err(error) => {
            world.resource_mut::<SceneIo>().status =
                format!("Content operation preview failed: {error}");
            return false;
        }
    };
    let operation_count = preview.operations.len();
    let affected_count = preview.affected_asset_ids.len();
    let rewrite_count = preview.reference_rewrites.len();
    let mut guarded_paths = HashSet::new();
    for operation in &preview.operations {
        if let content_pipeline::ContentOperation::Move { source, .. }
        | content_pipeline::ContentOperation::Copy { source, .. } = operation
        {
            guarded_paths.insert(source.clone());
        }
    }
    guarded_paths.extend(
        preview
            .reference_rewrites
            .iter()
            .map(|rewrite| rewrite.document.clone()),
    );
    let mut guarded_paths = guarded_paths
        .into_iter()
        .map(|path| {
            content_pipeline::tree_fingerprint(&path).map(|fingerprint| (path, fingerprint))
        })
        .collect::<Result<Vec<_>, _>>()
        .map_err(|error| {
            world.resource_mut::<SceneIo>().status =
                format!("Content operation guard failed: {error}");
        });
    let Ok(ref mut guarded_paths) = guarded_paths else {
        return false;
    };
    guarded_paths.sort_by(|left, right| left.0.cmp(&right.0));
    let browser_snapshot = capture_content_browser_snapshot(world);
    world
        .resource_mut::<AssetBrowserUiState>()
        .pending_content_transaction = Some(PendingContentTransaction {
        preview,
        guarded_paths: std::mem::take(guarded_paths),
        clear_cut_clipboard,
        clear_drag,
        browser_snapshot,
    });
    world.resource_mut::<SceneIo>().status = format!(
        "Review {operation_count} content operation(s): {affected_count} asset ID(s), {rewrite_count} cached reference rewrite(s)"
    );
    true
}

pub(super) fn draw_content_transaction_modal(world: &mut World, ctx: &egui::Context) {
    let Some(pending) = world
        .resource::<AssetBrowserUiState>()
        .pending_content_transaction
        .clone()
    else {
        return;
    };
    let mut cancel = false;
    let mut commit = false;
    let collaboration_statuses =
        content_transaction_collaboration_statuses(world, &pending.preview);
    let has_collaboration_blocker = collaboration_statuses
        .iter()
        .any(|(_, _, _, blocked)| *blocked);
    let externally_changed = pending
        .guarded_paths
        .iter()
        .filter(|(path, expected)| {
            content_pipeline::tree_fingerprint(path).as_deref() != Ok(expected.as_str())
        })
        .map(|(path, _)| path.clone())
        .collect::<Vec<_>>();
    egui::Window::new("Review Content Operation")
        .collapsible(false)
        .resizable(true)
        .default_width(660.0)
        .min_width(520.0)
        .show(ctx, |ui| {
            ui.small(
                egui::RichText::new(
                    "Filesystem paths, registry identity, generated catalog entries, and cached project references commit together or roll back together.",
                )
                .color(TEXT_DIM),
            );
            ui.separator();
            egui::ScrollArea::vertical()
                .max_height(260.0)
                .show(ui, |ui| {
                    for operation in &pending.preview.operations {
                        ui.label(content_operation_summary(operation));
                    }
                });
            ui.separator();
            ui.label(format!(
                "Affected registered assets: {}",
                pending.preview.affected_asset_ids.len()
            ));
            ui.label(format!(
                "Cached reference documents to rewrite: {}",
                pending.preview.reference_rewrites.len()
            ));
            for rewrite in pending.preview.reference_rewrites.iter().take(8) {
                ui.small(format!(
                    "{}: {} → {}",
                    rewrite.document.display(),
                    rewrite.from,
                    rewrite.to
                ));
            }
            if pending.preview.reference_rewrites.len() > 8 {
                ui.small(format!(
                    "…and {} more",
                    pending.preview.reference_rewrites.len() - 8
                ));
            }
            if !pending.preview.collisions.is_empty() {
                ui.separator();
                ui.colored_label(ERROR, "Resolve these path collisions before committing:");
                for collision in &pending.preview.collisions {
                    ui.small(collision.display().to_string());
                }
            }
            if !collaboration_statuses.is_empty() {
                ui.separator();
                ui.label("Source-control / ownership state");
                for (path, label, tooltip, blocked) in &collaboration_statuses {
                    ui.horizontal(|ui| {
                        ui.small(path.display().to_string());
                        ui.colored_label(if *blocked { ERROR } else { WARNING }, label)
                            .on_hover_text(tooltip);
                    });
                }
            }
            if !externally_changed.is_empty() {
                ui.separator();
                ui.colored_label(
                    ERROR,
                    "Content changed after this preview. Cancel and review the operation again:",
                );
                for path in &externally_changed {
                    ui.small(path.display().to_string());
                }
            }
            ui.separator();
            ui.horizontal(|ui| {
                cancel = ui.button("Cancel").clicked();
                commit = ui
                    .add_enabled(
                        pending.preview.is_committable()
                            && !has_collaboration_blocker
                            && externally_changed.is_empty(),
                        egui::Button::new("Commit Transaction"),
                    )
                    .clicked();
            });
        });

    if cancel {
        restore_cancelled_content_transaction(world, &pending);
        world
            .resource_mut::<AssetBrowserUiState>()
            .pending_content_transaction = None;
    } else if commit {
        let operations = pending.preview.operations.clone();
        if commit_content_operations_internal(world, operations, true) {
            if pending.clear_cut_clipboard {
                world.resource_mut::<AssetBrowserUiState>().clipboard = None;
            }
            if pending.clear_drag {
                world.resource_mut::<EditorAssets>().clear_drag();
            }
            world
                .resource_mut::<AssetBrowserUiState>()
                .pending_content_transaction = None;
        }
    }
}

pub(super) fn capture_content_browser_snapshot(world: &World) -> ContentBrowserSnapshot {
    let assets = world.resource::<EditorAssets>();
    ContentBrowserSnapshot {
        current_folder: assets.current_folder.clone(),
        selected: assets.selected.clone(),
        selections: assets.selections.clone(),
        selection_anchor: assets.selection_anchor.clone(),
        dragging: assets.dragging.clone(),
        asset_status: assets.status.clone(),
        scene_status: world.resource::<SceneIo>().status.clone(),
    }
}

pub(super) fn restore_cancelled_content_transaction(
    world: &mut World,
    pending: &PendingContentTransaction,
) {
    let snapshot = &pending.browser_snapshot;
    {
        let mut assets = world.resource_mut::<EditorAssets>();
        assets.current_folder.clone_from(&snapshot.current_folder);
        assets.selected.clone_from(&snapshot.selected);
        assets.selections.clone_from(&snapshot.selections);
        assets
            .selection_anchor
            .clone_from(&snapshot.selection_anchor);
        assets.dragging = if pending.clear_drag {
            None
        } else {
            snapshot.dragging.clone()
        };
        assets.status.clone_from(&snapshot.asset_status);
    }
    world
        .resource_mut::<SceneIo>()
        .status
        .clone_from(&snapshot.scene_status);
}

pub(super) fn content_transaction_collaboration_statuses(
    world: &World,
    preview: &content_pipeline::ContentTransactionPreview,
) -> Vec<(PathBuf, String, String, bool)> {
    let Some(collaboration) = world.get_resource::<CollaborationState>() else {
        return Vec::new();
    };
    let mut paths = HashSet::new();
    for operation in &preview.operations {
        if let content_pipeline::ContentOperation::Move { source, .. }
        | content_pipeline::ContentOperation::Copy { source, .. } = operation
        {
            paths.insert(source.clone());
        }
    }
    paths.extend(
        preview
            .reference_rewrites
            .iter()
            .map(|rewrite| rewrite.document.clone()),
    );
    let mut statuses = paths
        .into_iter()
        .filter_map(|path| {
            let status = collaboration.file_status(&path);
            let indicator = status.indicator()?;
            let label = indicator.label.to_string();
            let blocked = status.readonly || status.locked_by_other || label == "CONFLICT";
            let tooltip = status.tooltip(&path);
            Some((path, label, tooltip, blocked))
        })
        .collect::<Vec<_>>();
    statuses.sort_by(|left, right| left.0.cmp(&right.0));
    statuses
}

pub(super) fn content_operation_summary(operation: &content_pipeline::ContentOperation) -> String {
    match operation {
        content_pipeline::ContentOperation::CreateFolder { path } => {
            format!("Create folder  {}", path.display())
        }
        content_pipeline::ContentOperation::Move {
            source,
            destination,
        } => format!("Move  {}  →  {}", source.display(), destination.display()),
        content_pipeline::ContentOperation::Copy {
            source,
            destination,
        } => format!("Copy  {}  →  {}", source.display(), destination.display()),
        content_pipeline::ContentOperation::WriteFile { path, bytes } => {
            format!("Create asset  {}  ({} bytes)", path.display(), bytes.len())
        }
        content_pipeline::ContentOperation::ReplaceFile { path, bytes, .. } => {
            format!("Replace asset  {}  ({} bytes)", path.display(), bytes.len())
        }
    }
}
