//! Inspector extensions for authoring components.

use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};

use bevy::prelude::*;
use bevy_egui::egui;
use egui_phosphor_icons::{icons, Icon};
use shared::{
    authoring_component_active,
    brush_math::{validate_brush, BrushDiagnosticSeverity},
    infer_actor_kind, standard_lit_input_schema, ActorId, ActorKind, AnimationControllerDesc,
    AudioListenerDesc, AudioSourceDesc, AuthoringComponentStates, AuthoringLightKind,
    AuthoringRigidBody, BrushDesc, BrushKind, ColliderDesc, ColliderShapeDesc, ColorDesc,
    ComponentInstanceId, EditorAssetRef, InspectorOrder, LevelObject, LightDesc, MaterialAsset,
    MaterialDesc, MaterialInstanceAsset, MaterialParameter, MaterialParameterValue,
    MaterialPropertyBlock, MaterialPropertyBlocks, MaterialRef, MaterialShaderKind, NavigationArea,
    NavigationBounds, NavigationLink, NavigationObstacle, ObjectiveMarker, PhysicsBody,
    PlayerSpawn, PostProcessVolumeDesc, PrefabInstance, Primitive, PrimitiveShape, ProjectSun,
    RigidBodyDesc, SkinnedMeshRenderer, StaticMeshRenderer, StaticMeshRendererEntry, TeamSpawn,
    TerrainDesc, TriggerVolume, WeaponSpawn, AUTHORING_DIRECTIONAL_LUX_MAX,
    AUTHORING_POINT_SPOT_LUMENS_MAX, COMPONENT_ANIMATION_CONTROLLER_DESC,
    COMPONENT_AUDIO_LISTENER_DESC, COMPONENT_AUDIO_SOURCE_DESC, COMPONENT_BRUSH_DESC,
    COMPONENT_COLLIDER_DESC, COMPONENT_LIGHT_DESC, COMPONENT_MATERIAL_DESC,
    COMPONENT_NAVIGATION_AREA, COMPONENT_NAVIGATION_BOUNDS, COMPONENT_NAVIGATION_LINK,
    COMPONENT_NAVIGATION_OBSTACLE, COMPONENT_OBJECTIVE_MARKER, COMPONENT_PHYSICS_BODY,
    COMPONENT_PLAYER_SPAWN, COMPONENT_POST_PROCESS_VOLUME, COMPONENT_PREFAB_INSTANCE,
    COMPONENT_PRIMITIVE, COMPONENT_PROJECT_SUN, COMPONENT_RIGID_BODY_DESC,
    COMPONENT_SKINNED_MESH_RENDERER, COMPONENT_STATIC_MESH_RENDERER, COMPONENT_TEAM_SPAWN,
    COMPONENT_TERRAIN_DESC, COMPONENT_TRIGGER_VOLUME, COMPONENT_WEAPON_SPAWN,
};

use crate::history::{
    material_eq, reflected_component_transaction, set_animation_controller_with_history,
    set_audio_listener_with_history, set_audio_source_with_history, set_brush_with_history,
    set_collider_with_history, set_inspector_order_with_history, set_light_with_history,
    set_material_with_history, set_physics_with_history, set_post_process_volume_with_history,
    set_primitive_with_history, set_rigid_body_with_history, set_static_mesh_renderer_with_history,
    EditorEntitySnapshot,
};
use crate::ui::materials::material_input_schema_editor;
use crate::ui::theme::{
    panel_heading, BORDER, PANEL_BG_DARK, SELECTION_BG_MUTED, TEXT_DIM, TEXT_MUTED, WIDGET_BG,
};
use crate::ui::widgets::{icon_button_small, phosphor_icon, phosphor_icon_text};
use crate::viewport::brush_edit::{BrushElementKey, BrushElementSelection};
use crate::viewport::collider_diagnostics::{
    diagnose_collider, ColliderDiagnosticSeverity, ColliderOverlayStatus,
};

use super::component_registry::{
    EditorComponentCategory, EditorComponentDescriptor, EditorComponentRegistry,
};
use super::helpers::create_scene_sun_override_from_project_settings;
use super::EditorTab;
use crate::assets::asset_db::{
    ensure_asset_record_at, find_asset_by_path, AssetRegistry, ModelMaterialSelection,
};
use crate::assets::static_mesh::{
    load_static_mesh_manifest, material_id_from_label, part_id_from_label,
};
use crate::assets::thumbnails::ThumbnailStudio;
use crate::assets::{
    asset_cache_key, invalidate_on_catalog_refresh, prefetch_asset_thumbnails, AssetSelection,
    AssetSubAssetKind, AssetThumbnailCache, EditorAssetKind, EditorAssets, ASSETS_ROOT,
    BUILTINS_FOLDER,
};
use crate::project::collaboration::{publish_authored_file, FileSnapshot, FileWriteIntent};
use crate::project_io::ProjectWorkspace;

const COMPACT_INSPECTOR_WIDTH: f32 = 360.0;
const ASSET_SELECTOR_LABEL_WIDTH: f32 = 72.0;
const ASSET_SELECTOR_HEIGHT: f32 = 48.0;
const MIN_INLINE_CONTROL_WIDTH: f32 = 120.0;
const TEXT_FIELD_MAX_WIDTH: f32 = 320.0;
const PROPERTY_LABEL_WIDTH: f32 = 136.0;
pub(crate) const COMPONENT_TRANSFORM: &str = "bevy_transform::components::Transform";

fn fit_width(ui: &egui::Ui, min: f32, max: f32) -> f32 {
    let available = ui.available_width().max(1.0);
    available.min(max).max(min.min(available))
}

fn exact_region<R>(
    ui: &mut egui::Ui,
    size: egui::Vec2,
    layout: egui::Layout,
    add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> R {
    let (rect, _response) = ui.allocate_exact_size(size, egui::Sense::hover());
    let mut child = ui.new_child(egui::UiBuilder::new().max_rect(rect).layout(layout));
    add_contents(&mut child)
}

#[derive(Debug, Clone)]
struct AssetRefCandidate {
    reference: EditorAssetRef,
    label: String,
    detail: String,
    selection: AssetSelection,
    folder_path: String,
    texture_id: Option<egui::TextureId>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AssetRefCandidateKind {
    Mesh,
    Material,
    Texture,
}

#[derive(Default)]
struct AssetSelectorResponse {
    selected: Option<EditorAssetRef>,
    clear: bool,
    locate: bool,
    accepted_drop: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AssetSelectorActions {
    Full,
}

#[derive(Debug, Clone)]
struct TextureAssetCandidate {
    label: String,
    path: String,
    folder_path: String,
    selection: AssetSelection,
    texture_id: Option<egui::TextureId>,
}

#[derive(Resource, Default, Debug, Clone)]
pub(crate) struct InspectorClipboard {
    component: Option<CopiedComponent>,
}

#[derive(Resource, Default, Debug, Clone)]
pub(crate) struct InspectorPanelState {
    add_component_search: String,
    add_component_open: bool,
    add_component_focus_search: bool,
    add_component_scroll_selected: bool,
    add_component_selected_index: usize,
    add_component_target: Option<Entity>,
    collapsed_components: HashSet<String>,
}

#[derive(Resource, Default, Debug, Clone)]
pub(crate) struct PropertyBlockPromotionReview {
    pending: Option<PendingPropertyBlockPromotion>,
    error: Option<String>,
}

#[derive(Debug, Clone)]
struct PromotionSourceSnapshot {
    path: PathBuf,
    snapshot: FileSnapshot,
}

#[derive(Debug, Clone)]
struct PendingPropertyBlockPromotion {
    entity: Entity,
    slot_id: ComponentInstanceId,
    slot_name: String,
    base: MaterialRef,
    /// Actor-owned assignment captured when the review opened. `None` means the selected base
    /// was inherited from the model or project defaults and must remain inherited until commit.
    actor_assignment: Option<MaterialRef>,
    block: MaterialPropertyBlock,
    project_root: PathBuf,
    destination_path: PathBuf,
    destination_absolute: PathBuf,
    target_snapshot: FileSnapshot,
    source_snapshots: Vec<PromotionSourceSnapshot>,
    instance: MaterialInstanceAsset,
    bytes: Vec<u8>,
    clear_property_block: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AddComponentShelfDirection {
    Up,
    Down,
}

#[derive(Debug, Clone)]
enum CopiedComponent {
    Reflected(crate::history::ReflectedComponentValue),
    AnimationControllerDesc(AnimationControllerDesc),
    Primitive(Primitive),
    BrushDesc(BrushDesc),
    StaticMeshRenderer(StaticMeshRenderer),
    MaterialDesc(MaterialDesc),
    LightDesc(LightDesc),
    AudioSourceDesc(AudioSourceDesc),
    AudioListenerDesc(AudioListenerDesc),
    RigidBodyDesc(RigidBodyDesc),
    ColliderDesc(ColliderDesc),
    PhysicsBody(PhysicsBody),
    WeaponSpawn(WeaponSpawn),
    TriggerVolume(TriggerVolume),
    TeamSpawn(TeamSpawn),
    ObjectiveMarker(ObjectiveMarker),
    PostProcessVolume(PostProcessVolumeDesc),
    PrefabInstance(PrefabInstance),
    NavigationBounds(NavigationBounds),
    NavigationObstacle(NavigationObstacle),
    NavigationArea(NavigationArea),
    NavigationLink(NavigationLink),
}

impl CopiedComponent {
    fn type_name(&self) -> &str {
        match self {
            Self::Reflected(value) => &value.type_path,
            Self::AnimationControllerDesc(_) => COMPONENT_ANIMATION_CONTROLLER_DESC,
            Self::Primitive(_) => COMPONENT_PRIMITIVE,
            Self::BrushDesc(_) => COMPONENT_BRUSH_DESC,
            Self::StaticMeshRenderer(_) => COMPONENT_STATIC_MESH_RENDERER,
            Self::MaterialDesc(_) => COMPONENT_MATERIAL_DESC,
            Self::LightDesc(_) => COMPONENT_LIGHT_DESC,
            Self::AudioSourceDesc(_) => COMPONENT_AUDIO_SOURCE_DESC,
            Self::AudioListenerDesc(_) => COMPONENT_AUDIO_LISTENER_DESC,
            Self::RigidBodyDesc(_) => COMPONENT_RIGID_BODY_DESC,
            Self::ColliderDesc(_) => COMPONENT_COLLIDER_DESC,
            Self::PhysicsBody(_) => COMPONENT_PHYSICS_BODY,
            Self::WeaponSpawn(_) => COMPONENT_WEAPON_SPAWN,
            Self::TriggerVolume(_) => COMPONENT_TRIGGER_VOLUME,
            Self::TeamSpawn(_) => COMPONENT_TEAM_SPAWN,
            Self::ObjectiveMarker(_) => COMPONENT_OBJECTIVE_MARKER,
            Self::PostProcessVolume(_) => COMPONENT_POST_PROCESS_VOLUME,
            Self::PrefabInstance(_) => COMPONENT_PREFAB_INSTANCE,
            Self::NavigationBounds(_) => COMPONENT_NAVIGATION_BOUNDS,
            Self::NavigationObstacle(_) => COMPONENT_NAVIGATION_OBSTACLE,
            Self::NavigationArea(_) => COMPONENT_NAVIGATION_AREA,
            Self::NavigationLink(_) => COMPONENT_NAVIGATION_LINK,
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct ComponentCardOptions {
    pub type_name: &'static str,
    pub title: &'static str,
    pub icon: Icon,
    pub active_toggle: bool,
    pub removable: bool,
    pub reorderable: bool,
    pub resettable: bool,
    pub copyable: bool,
    pub summary: &'static str,
    pub body_margin: i8,
}

impl ComponentCardOptions {
    pub(crate) fn removable(type_name: &'static str, title: &'static str, icon: Icon) -> Self {
        Self {
            type_name,
            title,
            icon,
            active_toggle: true,
            removable: true,
            reorderable: true,
            resettable: true,
            copyable: true,
            summary: "Authoring component",
            body_margin: 12,
        }
    }

    pub(crate) fn fixed(type_name: &'static str, title: &'static str, icon: Icon) -> Self {
        Self {
            type_name,
            title,
            icon,
            active_toggle: false,
            removable: false,
            reorderable: false,
            resettable: true,
            copyable: false,
            summary: "Authoring component",
            body_margin: 12,
        }
    }
}

#[derive(Debug, Clone)]
pub(crate) struct ComponentCardContext {
    options: ComponentCardOptions,
    active: bool,
    collapsed: bool,
    pasteable: bool,
    can_move_up: bool,
    can_move_down: bool,
}

#[derive(Debug, Clone, Default)]
pub(crate) struct ComponentCardResponse {
    pub(crate) type_name: &'static str,
    pub(crate) collapsed: Option<bool>,
    pub(crate) active: Option<bool>,
    pub(crate) reset: bool,
    pub(crate) copy: bool,
    pub(crate) paste: bool,
    pub(crate) move_up: bool,
    pub(crate) move_down: bool,
    pub(crate) remove: bool,
}

pub(crate) fn component_card_context(
    world: &World,
    entity: Entity,
    options: ComponentCardOptions,
) -> ComponentCardContext {
    let present = present_component_type_names(world, entity);
    let order = world.get::<InspectorOrder>(entity);
    let registry = world.resource::<EditorComponentRegistry>();
    let state_key = registry.stable_id_for_type(options.type_name);
    let active = authoring_component_active(
        world.get::<AuthoringComponentStates>(entity),
        order,
        state_key,
    );
    let collapsed = world
        .resource::<InspectorPanelState>()
        .collapsed_components
        .contains(&component_card_key(world, entity, options.type_name));
    let pasteable = options.copyable
        && world
            .get_resource::<InspectorClipboard>()
            .and_then(|clipboard| clipboard.component.as_ref())
            .is_some_and(|component| component.type_name() == options.type_name);
    let ordered = order
        .map(|order| registry.ordered_components(order, &present))
        .unwrap_or_else(|| present.clone());
    let order_index = ordered
        .iter()
        .position(|type_name| *type_name == options.type_name);

    ComponentCardContext {
        options,
        active,
        collapsed,
        pasteable,
        can_move_up: options.reorderable && order_index.is_some_and(|index| index > 0),
        can_move_down: options.reorderable
            && order_index.is_some_and(|index| index + 1 < ordered.len()),
    }
}

#[path = "inspector/asset_selectors.rs"]
mod asset_selectors;
#[path = "inspector/brush_terrain.rs"]
mod brush_terrain;
#[path = "inspector/component_add.rs"]
mod component_add;
#[path = "inspector/component_chrome.rs"]
mod component_chrome;
#[path = "inspector/component_lifecycle.rs"]
mod component_lifecycle;
#[path = "inspector/dispatch.rs"]
mod dispatch;
#[path = "inspector/imported_material_preview.rs"]
mod imported_material_preview;
#[path = "inspector/material_resolution.rs"]
mod material_resolution;
#[path = "inspector/material_slots.rs"]
mod material_slots;
#[path = "inspector/mesh_renderers.rs"]
mod mesh_renderers;
#[path = "inspector/primitive_domains.rs"]
mod primitive_domains;
#[path = "inspector/property_blocks.rs"]
mod property_blocks;
#[path = "inspector/renderer_panel.rs"]
mod renderer_panel;
#[path = "inspector/sun_textures.rs"]
mod sun_textures;

use asset_selectors::*;
use brush_terrain::*;
use component_add::*;
use component_chrome::*;
pub(crate) use component_chrome::{component_card, property_row, text_field_width};
pub(crate) use component_lifecycle::apply_component_card_response;
use component_lifecycle::*;
pub use dispatch::authoring_inspector_ui;
pub(crate) use dispatch::{add_component_footer, register_builtin_component_inspectors};
use material_resolution::*;
use material_slots::*;
use mesh_renderers::*;
use primitive_domains::*;
pub(crate) use property_blocks::property_block_promotion_review_ui;
use property_blocks::*;
use renderer_panel::*;
use sun_textures::*;

#[cfg(test)]
mod tests {
    use super::*;
    include!("inspector/tests/a.rs");
    include!("inspector/tests/b.rs");
}
