//! Pose 处理：根据 `.pose3.json` 管理互斥 Part 的显示状态。
//!
//! Haru 这类模型会把“手臂伸开”和“手臂抱胸”做成不同 Part。
//! 如果不执行 Pose，多个 Part 会同时可见，画面上就会出现四只手臂。

use super::core_ffi::CubismCore;
use super::model::CubismModel;
use anyhow::{Context, Result};
use serde::Deserialize;
use std::path::Path;

const DEFAULT_FADE_IN_SECONDS: f32 = 0.5;
const EPSILON: f32 = 0.001;
const PHI: f32 = 0.5;
const BACK_OPACITY_THRESHOLD: f32 = 0.15;

#[derive(Deserialize)]
struct PoseJson {
    #[serde(rename = "FadeInTime")]
    fade_in_time: Option<f32>,
    #[serde(rename = "Groups", default)]
    groups: Vec<Vec<PosePartJson>>,
}

#[derive(Deserialize)]
struct PosePartJson {
    #[serde(rename = "Id")]
    id: String,
    #[serde(rename = "Link", default)]
    link: Vec<String>,
}

#[derive(Clone, Debug)]
struct PosePart {
    part_index: Option<usize>,
    param_index: Option<usize>,
    link_part_indices: Vec<usize>,
    virtual_param_value: f32,
}

/// Live2D Pose 状态。
pub struct PoseState {
    fade_time_seconds: f32,
    groups: Vec<Vec<PosePart>>,
    initialized: bool,
}

impl PoseState {
    pub fn from_path(path: &Path, model: &CubismModel) -> Result<Self> {
        let text = std::fs::read_to_string(path)
            .with_context(|| format!("读取 pose3.json 失败: {}", path.display()))?;
        let json: PoseJson = serde_json::from_str(&text)
            .with_context(|| format!("解析 pose3.json 失败: {}", path.display()))?;

        let fade_time_seconds = json
            .fade_in_time
            .filter(|v| *v >= 0.0)
            .unwrap_or(DEFAULT_FADE_IN_SECONDS);

        let groups: Vec<Vec<PosePart>> = json
            .groups
            .into_iter()
            .map(|group| {
                group
                    .into_iter()
                    .map(|part| PosePart {
                        param_index: model.param_index.get(&part.id).copied(),
                        part_index: model.part_index(&part.id),
                        link_part_indices: part
                            .link
                            .iter()
                            .filter_map(|id| model.part_index(id))
                            .collect(),
                        virtual_param_value: 0.0,
                    })
                    .collect()
            })
            .collect();

        println!(
            "[Live2D] Pose 加载: {} groups={} fade={:.3}s",
            path.display(),
            groups.len(),
            fade_time_seconds
        );

        Ok(Self {
            fade_time_seconds,
            groups,
            initialized: false,
        })
    }

    /// 初始化 Pose：每个互斥组保留当前最可见的 Part，隐藏其余 Part。
    pub fn reset(&mut self, core: &CubismCore, model: &CubismModel) {
        for group in &mut self.groups {
            let mut visible_index = 0;
            let mut max_opacity = -1.0;
            for (index, part) in group.iter().enumerate() {
                let opacity = part
                    .part_index
                    .and_then(|idx| model.get_part_opacity_by_index(core, idx))
                    .unwrap_or(0.0);
                if opacity >= max_opacity {
                    max_opacity = opacity;
                    visible_index = index;
                }
            }

            for (index, part) in group.iter_mut().enumerate() {
                let opacity = if index == visible_index { 1.0 } else { 0.0 };
                part.virtual_param_value = opacity;
                if let Some(param_index) = part.param_index {
                    model.set_param_by_index(core, param_index, opacity);
                }
                if let Some(part_index) = part.part_index {
                    model.set_part_opacity_by_index(core, part_index, opacity);
                }
            }
        }
        self.copy_part_opacities(core, model);
        self.initialized = true;
    }

    /// 按官方 CubismPose 的互斥淡入规则更新 PartOpacity。
    pub fn update_parameters(&mut self, core: &CubismCore, model: &CubismModel, dt: f32) {
        if !self.initialized {
            self.reset(core, model);
        }

        let dt = dt.max(0.0);
        let fade_time_seconds = self.fade_time_seconds;
        for group in &mut self.groups {
            Self::do_fade(core, model, fade_time_seconds, dt, group);
        }
        self.copy_part_opacities(core, model);
    }

    fn do_fade(
        core: &CubismCore,
        model: &CubismModel,
        fade_time_seconds: f32,
        dt: f32,
        group: &mut [PosePart],
    ) {
        if group.is_empty() {
            return;
        }

        let mut visible_part_index = None;
        let mut new_opacity = 1.0;

        for (index, part) in group.iter().enumerate() {
            if part.pose_value(core, model) <= EPSILON {
                continue;
            }

            visible_part_index = Some(index);
            if fade_time_seconds == 0.0 {
                new_opacity = 1.0;
            } else {
                new_opacity = part
                    .part_index
                    .and_then(|idx| model.get_part_opacity_by_index(core, idx))
                    .unwrap_or(0.0);
                new_opacity = (new_opacity + dt / fade_time_seconds).min(1.0);
            }
            break;
        }

        let visible_part_index = visible_part_index.unwrap_or(0);
        if visible_part_index == 0 && group[0].pose_value(core, model) <= EPSILON {
            group[0].virtual_param_value = 1.0;
            if let Some(param_index) = group[0].param_index {
                model.set_param_by_index(core, param_index, 1.0);
            }
        }

        for (index, part) in group.iter().enumerate() {
            let Some(part_index) = part.part_index else {
                continue;
            };

            if index == visible_part_index {
                model.set_part_opacity_by_index(core, part_index, new_opacity);
                continue;
            }

            let mut opacity = model
                .get_part_opacity_by_index(core, part_index)
                .unwrap_or(0.0);
            let mut target_opacity = if new_opacity < PHI {
                new_opacity * (PHI - 1.0) / PHI + 1.0
            } else {
                (1.0 - new_opacity) * PHI / (1.0 - PHI)
            };

            let back_opacity = (1.0 - target_opacity) * (1.0 - new_opacity);
            if back_opacity > BACK_OPACITY_THRESHOLD {
                target_opacity = 1.0 - BACK_OPACITY_THRESHOLD / (1.0 - new_opacity);
            }

            if opacity > target_opacity {
                opacity = target_opacity;
            }
            model.set_part_opacity_by_index(core, part_index, opacity);
        }
    }

    fn copy_part_opacities(&self, core: &CubismCore, model: &CubismModel) {
        for group in &self.groups {
            for part in group {
                let Some(part_index) = part.part_index else {
                    continue;
                };
                let Some(opacity) = model.get_part_opacity_by_index(core, part_index) else {
                    continue;
                };
                for &link_part_index in &part.link_part_indices {
                    model.set_part_opacity_by_index(core, link_part_index, opacity);
                }
            }
        }
    }
}

impl PosePart {
    fn pose_value(&self, core: &CubismCore, model: &CubismModel) -> f32 {
        self.param_index
            .and_then(|idx| model.get_param_by_index(core, idx))
            .unwrap_or(self.virtual_param_value)
    }
}
