Documentation
//! 内部盘式抽象 (`Plate<T>`) 与三奇六仪/九星/八门/九神/暗干各盘的构造逻辑。
//!
//! 同时聚集所有按 tyme4rs 索引查表的常量,避免反复字符串匹配。

use tyme4rs::tyme::enums::YinYang;
use tyme4rs::tyme::sixtycycle::HeavenStem;
use tyme4rs::tyme::sixtycycle::SixtyCycle;

use crate::enums::QimenDoor;
use crate::enums::QimenGod;
use crate::enums::QimenStar;

/// 三奇六仪填充顺序:戊己庚辛壬癸丁丙乙 (对应天干索引 4..=8, 9, 3, 2, 1)。
pub(crate) const SAN_QI_LIU_YI: [u8; 9] = [4, 5, 6, 7, 8, 9, 3, 2, 1];

/// 九宫八方排列,坎宫起,顺八卦次序 (跳过中宫):坎坤震巽乾兑艮离。
pub(crate) const LUO_SHU_ORDER: [u8; 8] = [1, 8, 3, 4, 9, 2, 7, 6];

/// 由宫位号反查其在 [`LUO_SHU_ORDER`] 中的位置 (越界返回 0)。
pub(crate) const LUO_SHU_INDEX: [u8; 10] = [0, 0, 5, 2, 3, 0, 7, 6, 1, 4];

/// 阳遁九神排列。
pub(crate) const GOD_YANG_ORDER: [u8; 8] = [1, 8, 3, 4, 9, 2, 7, 6];

/// 阴遁九神排列。
pub(crate) const GOD_YIN_ORDER: [u8; 8] = [1, 6, 7, 2, 9, 4, 3, 8];

/// 九神排列顺序 (依次:值符、腾蛇、太阴、六合、白虎、玄武、九地、九天)。
pub(crate) const GODS_ORDER: [QimenGod; 8] = [
    QimenGod::ZhiFu,
    QimenGod::TengShe,
    QimenGod::TaiYin,
    QimenGod::LiuHe,
    QimenGod::BaiHu,
    QimenGod::XuanWu,
    QimenGod::JiuDi,
    QimenGod::JiuTian,
];

/// 三行三列九宫展示:`[巽离坤; 震中兑; 艮坎乾]` (按行从左到右)。
pub(crate) const GRID: [[u8; 3]; 3] = [[4, 9, 2], [3, 5, 7], [8, 1, 6]];

/// 宫名 (索引 0 占位为空,1..=9 对应实际宫位)。
pub(crate) const PALACE_NAMES: [&str; 10] = ["", "", "", "", "", "", "", "", "", ""];

/// 节气索引 (0..24) → [上元局, 中元局, 下元局]。
///
/// 索引对应 tyme4rs `SOLAR_TERM_NAMES`:0冬至, 1小寒, ..., 23大雪。
pub(crate) const TERM_JU: [[u8; 3]; 24] = [
    [1, 7, 4], // 0  冬至
    [2, 8, 5], // 1  小寒
    [3, 9, 6], // 2  大寒
    [8, 5, 2], // 3  立春
    [9, 6, 3], // 4  雨水
    [1, 7, 4], // 5  惊蛰
    [3, 9, 6], // 6  春分
    [4, 1, 7], // 7  清明
    [5, 2, 8], // 8  谷雨
    [4, 1, 7], // 9  立夏
    [5, 2, 8], // 10 小满
    [6, 3, 9], // 11 芒种
    [9, 3, 6], // 12 夏至
    [8, 2, 5], // 13 小暑
    [7, 1, 4], // 14 大暑
    [2, 5, 8], // 15 立秋
    [1, 4, 7], // 16 处暑
    [9, 3, 6], // 17 白露
    [7, 1, 4], // 18 秋分
    [6, 9, 3], // 19 寒露
    [5, 8, 2], // 20 霜降
    [6, 9, 3], // 21 立冬
    [5, 8, 2], // 22 小雪
    [4, 7, 1], // 23 大雪
];

/// 旬首 (按 [`tyme4rs::tyme::culture::TEN_NAMES`] 索引):甲子→戊, 甲戌→己, 甲申→庚, 甲午→辛, 甲辰→壬, 甲寅→癸。
pub(crate) const TEN_XUN_SHOU: [u8; 6] = [4, 5, 6, 7, 8, 9];

/// 旬首起始地支 (按旬索引):甲子→子, 甲戌→戌, 甲申→申, 甲午→午, 甲辰→辰, 甲寅→寅。
pub(crate) const TEN_XUN_START_BRANCH: [u8; 6] = [0, 10, 8, 6, 4, 2];

/// 旬空亡地支对 (按旬索引):甲子旬空戌亥, 甲戌空申酉, 甲申空午未, 甲午空辰巳, 甲辰空寅卯, 甲寅空子丑。
pub(crate) const TEN_KONG_BRANCHES: [[u8; 2]; 6] = [
    [10, 11], // 甲子 → 戌亥
    [8, 9],   // 甲戌 → 申酉
    [6, 7],   // 甲申 → 午未
    [4, 5],   // 甲午 → 辰巳
    [2, 3],   // 甲辰 → 寅卯
    [0, 1],   // 甲寅 → 子丑
];

/// 地支 (0..12) → 落宫:子1, 丑寅8, 卯3, 辰巳4, 午9, 未申2, 酉7, 戌亥6。
pub(crate) const BRANCH_TO_PALACE: [u8; 12] = [1, 8, 8, 3, 4, 4, 9, 2, 2, 7, 6, 6];

/// 天干 (0..10) → 入墓宫位:甲癸→2, 乙丙戊→6, 丁己庚→8, 辛壬→4。
pub(crate) const STEM_TOMB_PALACE: [u8; 10] = [
    2, //    6, //    6, //    8, //    6, //    8, //    8, //    4, //    4, //    2, //];

/// 九宫盘:索引 0..=8 对应宫位 1..=9 (`palace - 1`)。
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Plate<T> {
    cells: [Option<T>; 9],
}

impl<T> Plate<T> {
    pub(crate) fn empty() -> Self
    where
        T: Clone,
    {
        Self { cells: std::array::from_fn(|_| None) }
    }

    pub(crate) fn set(&mut self, palace: u8, value: T) {
        debug_assert!((1..=9).contains(&palace), "palace must be 1..=9");
        self.cells[palace as usize - 1] = Some(value);
    }

    pub(crate) fn get(&self, palace: u8) -> Option<&T> {
        if !(1..=9).contains(&palace) {
            return None;
        }
        self.cells[palace as usize - 1].as_ref()
    }
}

impl<T: Clone> Plate<T> {
    pub(crate) fn cloned(&self, palace: u8) -> Option<T> { self.get(palace).cloned() }
}

/// 在盘中查找指定天干所在的宫位 (含/不含中宫)。
pub(crate) fn find_stem_palace(plate: &Plate<HeavenStem>, stem: &HeavenStem, exclude_center: bool) -> Option<u8> {
    for palace in 1..=9u8 {
        if exclude_center && palace == 5 {
            continue;
        }
        if let Some(value) = plate.get(palace)
            && value == stem
        {
            return Some(palace);
        }
    }
    None
}

/// 求当前时辰天干所在地盘宫位。甲干寄旬首符使所在宫;否则按地盘排找;若中宫含则寄 2 宫。
pub(crate) fn find_hour_stem_palace(plate: &Plate<HeavenStem>, stem: &HeavenStem, zhi_fu_palace: u8) -> u8 {
    if stem.get_index() == 0 {
        // 甲 — 旬首起,寄符使
        return zhi_fu_palace;
    }
    if let Some(palace) = find_stem_palace(plate, stem, true) {
        return palace;
    }
    if plate.get(5) == Some(stem) { 2 } else { zhi_fu_palace }
}

/// 在门盘中查找指定门所在宫位。
pub(crate) fn find_door_palace(plate: &Plate<QimenDoor>, door: QimenDoor) -> Option<u8> {
    (1..=9u8).find(|&palace| plate.get(palace) == Some(&door))
}

/// 构造地盘天干 (三奇六仪)。
pub(crate) fn build_earth_plate(yin_yang: YinYang, ju: u8) -> Plate<HeavenStem> {
    let mut plate = Plate::empty();
    let mut palace = ju;
    for stem_index in SAN_QI_LIU_YI {
        plate.set(palace, HeavenStem::from_index(stem_index as isize));
        palace = step_palace(palace, yin_yang);
    }
    plate
}

/// 构造天盘天干。需提前算出 `zhi_fu_palace` (旬首落宫) 与 `hour_palace` (时干落宫) 复用。
pub(crate) fn build_heaven_plate(
    earth: &Plate<HeavenStem>, yin_yang: YinYang, zhi_fu_palace: u8, hour_palace: u8,
) -> Plate<HeavenStem> {
    let mut plate = Plate::empty();
    let zhi_fu_index = LUO_SHU_INDEX[zhi_fu_palace as usize] as usize;
    let hour_index = LUO_SHU_INDEX[hour_palace as usize] as usize;
    let steps = match yin_yang {
        YinYang::YANG => (hour_index + 8 - zhi_fu_index) % 8,
        YinYang::YIN => (zhi_fu_index + 8 - hour_index) % 8,
    };
    for (i, palace) in LUO_SHU_ORDER.iter().enumerate() {
        if let Some(stem) = earth.cloned(*palace) {
            let target_index = match yin_yang {
                YinYang::YANG => (i + steps) % 8,
                YinYang::YIN => (i + 8 - steps) % 8,
            };
            plate.set(LUO_SHU_ORDER[target_index], stem);
        }
    }
    if let Some(center) = earth.cloned(5) {
        plate.set(5, center);
    }
    plate
}

/// 构造九星盘。需提前算出 `zhi_fu_palace` 与 `hour_palace`。
pub(crate) fn build_star_plate(zhi_fu_palace: u8, hour_palace: u8) -> Plate<QimenStar> {
    let mut plate = Plate::empty();
    let zhi_fu_index = LUO_SHU_INDEX[zhi_fu_palace as usize] as usize;
    let hour_index = LUO_SHU_INDEX[hour_palace as usize] as usize;
    let steps = (hour_index + 8 - zhi_fu_index) % 8;
    for (i, original_palace) in LUO_SHU_ORDER.iter().enumerate() {
        let target_palace = LUO_SHU_ORDER[(i + steps) % 8];
        let star = if *original_palace == 2 {
            // 天禽与天芮共落 2 宫,合并标记为禽芮
            QimenStar::QinRui
        } else {
            QimenStar::from_palace(*original_palace).unwrap_or(QimenStar::TianRui)
        };
        plate.set(target_palace, star);
    }
    plate
}

/// 构造八门盘。
pub(crate) fn build_door_plate(yin_yang: YinYang, zhi_fu_palace: u8, hour: &SixtyCycle) -> Plate<QimenDoor> {
    let mut plate = Plate::empty();
    let xun_start_branch_index = ten_xun_start_branch_index(hour);
    let hour_branch_index = hour.get_earth_branch().get_index();
    let branch_steps = (hour_branch_index + 12 - xun_start_branch_index) % 12;
    let zhi_shi_palace = move_palace_by_steps(zhi_fu_palace, branch_steps, yin_yang);
    let zhi_fu_index = LUO_SHU_INDEX[zhi_fu_palace as usize] as usize;
    let zhi_shi_index = LUO_SHU_INDEX[zhi_shi_palace as usize] as usize;
    let steps = match yin_yang {
        YinYang::YANG => (zhi_shi_index + 8 - zhi_fu_index) % 8,
        YinYang::YIN => (zhi_fu_index + 8 - zhi_shi_index) % 8,
    };
    for (i, original_palace) in LUO_SHU_ORDER.iter().enumerate() {
        let target_index = match yin_yang {
            YinYang::YANG => (i + steps) % 8,
            YinYang::YIN => (i + 8 - steps) % 8,
        };
        if let Some(door) = QimenDoor::from_palace(*original_palace) {
            plate.set(LUO_SHU_ORDER[target_index], door);
        }
    }
    plate
}

/// 构造九神盘。
pub(crate) fn build_god_plate(yin_yang: YinYang, zhi_fu_palace: u8) -> Plate<QimenGod> {
    let mut plate = Plate::empty();
    let order = match yin_yang {
        YinYang::YANG => GOD_YANG_ORDER,
        YinYang::YIN => GOD_YIN_ORDER,
    };
    let start_palace = if zhi_fu_palace == 5 { 2 } else { zhi_fu_palace };
    let start_index = order.iter().position(|p| *p == start_palace).unwrap_or(0);
    for (i, god) in GODS_ORDER.iter().enumerate() {
        plate.set(order[(start_index + i) % 8], *god);
    }
    plate
}

/// 构造暗干盘 (从 8 宫起)。
pub(crate) fn build_hidden_plate(yin_yang: YinYang) -> Plate<HeavenStem> {
    let mut plate = Plate::empty();
    let mut palace = 8u8;
    for stem_index in SAN_QI_LIU_YI {
        plate.set(palace, HeavenStem::from_index(stem_index as isize));
        palace = step_palace(palace, yin_yang);
    }
    plate
}

/// 按阴阳遁次序前进一步 (跳过 5 中宫)。
fn step_palace(palace: u8, yin_yang: YinYang) -> u8 {
    match yin_yang {
        YinYang::YANG => {
            if palace == 9 {
                1
            } else {
                palace + 1
            }
        }
        YinYang::YIN => {
            if palace == 1 {
                9
            } else {
                palace - 1
            }
        }
    }
}

/// 按阴阳遁连续前进 `steps` 步,最终若落 5 宫则改寄 2 宫。
pub(crate) fn move_palace_by_steps(palace: u8, steps: usize, yin_yang: YinYang) -> u8 {
    let mut target = palace;
    for _ in 0..steps {
        target = step_palace(target, yin_yang);
    }
    if target == 5 { 2 } else { target }
}

/// 由旬索引取旬首起始地支索引。
pub(crate) fn ten_xun_start_branch_index(sixty_cycle: &SixtyCycle) -> usize {
    let ten_index = sixty_cycle.get_ten().get_index();
    TEN_XUN_START_BRANCH[ten_index.min(5)] as usize
}

/// 宫位 → 该宫所统辖的地支索引列表 (按子丑..亥的索引值)。
pub(crate) const fn palace_branch_indices(palace: u8) -> &'static [u8] {
    match palace {
        1 => &[0],      //        2 => &[7, 8],   // 未申
        3 => &[3],      //        4 => &[4, 5],   // 辰巳
        6 => &[10, 11], // 戌亥
        7 => &[9],      //        8 => &[1, 2],   // 丑寅
        9 => &[6],      //        _ => &[],
    }
}

/// 判断两个宫位是否对冲 (1↔9, 2↔8, 3↔7, 4↔6)。
pub(crate) const fn are_opposite_palaces(a: u8, b: u8) -> bool {
    matches!((a, b), (1, 9) | (9, 1) | (2, 8) | (8, 2) | (3, 7) | (7, 3) | (4, 6) | (6, 4))
}

/// 判断天干是否落入自己的墓库宫。
pub(crate) fn is_stem_in_tomb(stem: &HeavenStem, palace: u8) -> bool {
    let idx = stem.get_index();
    if idx >= 10 {
        return false;
    }
    STEM_TOMB_PALACE[idx] == palace
}