/* SPDX-FileCopyrightText: © 2024-2026 decompals */
/* SPDX-License-Identifier: MIT */

use serde::Deserialize;
use std::{collections::HashMap, path::PathBuf};

use crate::{
    absent_nullable::AbsentNullable, linker_symbols_style::LinkerSymbolsStyle, EscapedPath,
    RuntimeSettings, SlinkyError,
};

#[derive(PartialEq, Debug)]
pub struct Settings {
    pub base_path: PathBuf,
    pub linker_symbols_style: LinkerSymbolsStyle,

    pub hardcoded_gp_value: Option<u32>,

    pub d_path: Option<PathBuf>,
    pub target_path: Option<PathBuf>,

    pub symbols_header_path: Option<PathBuf>,
    pub symbols_header_type: String,
    pub symbols_header_as_array: bool,

    pub paths_list_path: Option<PathBuf>,

    pub sections_allowlist: Vec<String>,
    pub sections_allowlist_extra: Vec<String>,
    pub sections_denylist: Vec<String>,
    pub discard_wildcard_section: bool,

    pub single_segment_mode: bool,

    pub partial_scripts_folder: Option<PathBuf>,
    pub partial_build_segments_folder: Option<PathBuf>,

    // Options passed down to each segment
    pub alloc_sections: Option<Vec<String>>,
    pub noload_sections: Option<Vec<String>>,

    pub subalign: Option<u32>,
    pub segment_start_align: Option<u32>,
    pub segment_end_align: Option<u32>,
    pub section_start_align: Option<u32>,
    pub section_end_align: Option<u32>,
    pub sections_start_alignment: HashMap<String, u32>,
    pub sections_end_alignment: HashMap<String, u32>,

    pub wildcard_sections: bool,

    pub fill_value: Option<u32>,

    pub sections_subgroups: HashMap<String, Vec<String>>,
}

fn settings_default_base_path() -> PathBuf {
    PathBuf::new()
}

const fn settings_default_linker_symbols_style() -> LinkerSymbolsStyle {
    LinkerSymbolsStyle::Splat
}

const fn settings_default_d_path() -> Option<PathBuf> {
    None
}

const fn settings_default_target_path() -> Option<PathBuf> {
    None
}

const fn settings_default_symbols_header_path() -> Option<PathBuf> {
    None
}

fn settings_default_symbols_header_type() -> String {
    "char".to_string()
}

const fn settings_default_symbols_header_as_array() -> bool {
    true
}

const fn settings_default_paths_list_path() -> Option<PathBuf> {
    None
}

const fn settings_default_hardcoded_gp_value() -> Option<u32> {
    None
}

const fn settings_default_sections_allowlist() -> Vec<String> {
    vec![]
}

fn settings_default_sections_allowlist_extra() -> Vec<String> {
    vec![".symtab".into(), ".strtab".into(), ".shstrtab".into()]
}

fn settings_default_sections_denylist() -> Vec<String> {
    vec![
        ".reginfo".into(),
        ".MIPS.abiflags".into(),
        ".MIPS.options".into(),
        ".note.gnu.build-id".into(),
        ".interp".into(),
        ".eh_frame".into(),
        ".got".into(),
    ]
}

const fn settings_default_discard_wildcard_section() -> bool {
    true
}

const fn settings_default_single_segment_mode() -> bool {
    false
}

const fn settings_default_partial_scripts_folder() -> Option<PathBuf> {
    None
}

const fn settings_default_partial_build_segments_folder() -> Option<PathBuf> {
    None
}

fn settings_default_alloc_sections() -> Option<Vec<String>> {
    Some(vec![
        ".text".into(),
        ".data".into(),
        ".rodata".into(),
        ".sdata".into(),
    ])
}

fn settings_default_noload_sections() -> Option<Vec<String>> {
    Some(vec![
        ".sbss".into(),
        ".scommon".into(),
        ".bss".into(),
        "COMMON".into(),
    ])
}

const fn settings_default_subalign() -> Option<u32> {
    None
}

const fn settings_default_segment_start_align() -> Option<u32> {
    None
}

const fn settings_default_segment_end_align() -> Option<u32> {
    None
}

const fn settings_default_section_start_align() -> Option<u32> {
    None
}

const fn settings_default_section_end_align() -> Option<u32> {
    None
}

fn settings_default_sections_start_alignment() -> HashMap<String, u32> {
    HashMap::new()
}

fn settings_default_sections_end_alignment() -> HashMap<String, u32> {
    HashMap::new()
}

const fn settings_default_wildcard_sections() -> bool {
    true
}

const fn settings_default_fill_value() -> Option<u32> {
    Some(0)
}

fn settings_default_subsections_groups() -> HashMap<String, Vec<String>> {
    HashMap::new()
}

impl Default for Settings {
    fn default() -> Self {
        Self {
            base_path: settings_default_base_path(),
            linker_symbols_style: settings_default_linker_symbols_style(),

            hardcoded_gp_value: settings_default_hardcoded_gp_value(),

            d_path: settings_default_d_path(),
            target_path: settings_default_target_path(),

            symbols_header_path: settings_default_symbols_header_path(),
            symbols_header_type: settings_default_symbols_header_type(),
            symbols_header_as_array: settings_default_symbols_header_as_array(),

            paths_list_path: settings_default_paths_list_path(),

            sections_allowlist: settings_default_sections_allowlist(),
            sections_allowlist_extra: settings_default_sections_allowlist_extra(),
            sections_denylist: settings_default_sections_denylist(),
            discard_wildcard_section: settings_default_discard_wildcard_section(),

            single_segment_mode: settings_default_single_segment_mode(),

            partial_scripts_folder: settings_default_partial_scripts_folder(),
            partial_build_segments_folder: settings_default_partial_build_segments_folder(),

            alloc_sections: settings_default_alloc_sections(),
            noload_sections: settings_default_noload_sections(),

            subalign: settings_default_subalign(),
            segment_start_align: settings_default_segment_start_align(),
            segment_end_align: settings_default_segment_end_align(),
            section_start_align: settings_default_section_start_align(),
            section_end_align: settings_default_section_end_align(),
            sections_start_alignment: settings_default_sections_start_alignment(),
            sections_end_alignment: settings_default_sections_end_alignment(),

            wildcard_sections: settings_default_wildcard_sections(),

            fill_value: settings_default_fill_value(),

            sections_subgroups: settings_default_subsections_groups(),
        }
    }
}

impl Settings {
    pub fn base_path_escaped(&self, rs: &RuntimeSettings) -> Result<EscapedPath, SlinkyError> {
        rs.escape_path(&self.base_path)
    }

    pub fn d_path_escaped(&self, rs: &RuntimeSettings) -> Result<Option<EscapedPath>, SlinkyError> {
        match &self.d_path {
            Some(p) => Ok(Some(rs.escape_path(p)?)),
            None => Ok(None),
        }
    }

    pub fn target_path_escaped(
        &self,
        rs: &RuntimeSettings,
    ) -> Result<Option<EscapedPath>, SlinkyError> {
        match &self.target_path {
            Some(p) => Ok(Some(rs.escape_path(p)?)),
            None => Ok(None),
        }
    }

    pub fn symbols_header_path_escaped(
        &self,
        rs: &RuntimeSettings,
    ) -> Result<Option<EscapedPath>, SlinkyError> {
        match &self.symbols_header_path {
            Some(p) => Ok(Some(rs.escape_path(p)?)),
            None => Ok(None),
        }
    }

    pub fn paths_list_path_escaped(
        &self,
        rs: &RuntimeSettings,
    ) -> Result<Option<EscapedPath>, SlinkyError> {
        self.paths_list_path
            .as_ref()
            .map(|p| rs.escape_path(p))
            .transpose()
    }

    pub fn partial_scripts_folder_escaped(
        &self,
        rs: &RuntimeSettings,
    ) -> Result<Option<EscapedPath>, SlinkyError> {
        match &self.partial_scripts_folder {
            Some(p) => Ok(Some(rs.escape_path(p)?)),
            None => Ok(None),
        }
    }

    pub fn partial_build_segments_folder_escaped(
        &self,
        rs: &RuntimeSettings,
    ) -> Result<Option<EscapedPath>, SlinkyError> {
        match &self.partial_build_segments_folder {
            Some(p) => Ok(Some(rs.escape_path(p)?)),
            None => Ok(None),
        }
    }
}

#[derive(Deserialize, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
pub(crate) struct SettingsSerial {
    #[serde(default)]
    pub base_path: AbsentNullable<PathBuf>,
    #[serde(default)]
    pub linker_symbols_style: AbsentNullable<LinkerSymbolsStyle>,

    #[serde(default)]
    pub hardcoded_gp_value: AbsentNullable<u32>,

    #[serde(default)]
    pub d_path: AbsentNullable<PathBuf>,
    #[serde(default)]
    pub target_path: AbsentNullable<PathBuf>,

    #[serde(default)]
    pub symbols_header_path: AbsentNullable<PathBuf>,
    #[serde(default)]
    pub symbols_header_type: AbsentNullable<String>,
    #[serde(default)]
    pub symbols_header_as_array: AbsentNullable<bool>,

    #[serde(default)]
    paths_list_path: AbsentNullable<PathBuf>,

    #[serde(default)]
    pub sections_allowlist: AbsentNullable<Vec<String>>,
    #[serde(default)]
    pub sections_allowlist_extra: AbsentNullable<Vec<String>>,
    #[serde(default)]
    pub sections_denylist: AbsentNullable<Vec<String>>,
    #[serde(default)]
    pub discard_wildcard_section: AbsentNullable<bool>,

    #[serde(default)]
    pub single_segment_mode: AbsentNullable<bool>,

    #[serde(default)]
    pub partial_scripts_folder: AbsentNullable<PathBuf>,
    #[serde(default)]
    pub partial_build_segments_folder: AbsentNullable<PathBuf>,

    // Options passed down to each Segment
    #[serde(default)]
    pub alloc_sections: AbsentNullable<Vec<String>>,
    #[serde(default)]
    pub noload_sections: AbsentNullable<Vec<String>>,

    #[serde(default)]
    pub subalign: AbsentNullable<u32>,
    #[serde(default)]
    pub segment_start_align: AbsentNullable<u32>,
    #[serde(default)]
    pub segment_end_align: AbsentNullable<u32>,
    #[serde(default)]
    pub section_start_align: AbsentNullable<u32>,
    #[serde(default)]
    pub section_end_align: AbsentNullable<u32>,
    #[serde(default)]
    pub sections_start_alignment: AbsentNullable<HashMap<String, u32>>,
    #[serde(default)]
    pub sections_end_alignment: AbsentNullable<HashMap<String, u32>>,

    #[serde(default)]
    pub wildcard_sections: AbsentNullable<bool>,

    #[serde(default)]
    pub fill_value: AbsentNullable<u32>,

    #[serde(default)]
    pub sections_subgroups: AbsentNullable<HashMap<String, Vec<String>>>,
}

impl SettingsSerial {
    pub fn unserialize(self) -> Result<Settings, SlinkyError> {
        let base_path = self
            .base_path
            .get_non_null("base_path", settings_default_base_path)?;
        let linker_symbols_style = self.linker_symbols_style.get_non_null(
            "linker_symbols_style",
            settings_default_linker_symbols_style,
        )?;

        let hardcoded_gp_value = self
            .hardcoded_gp_value
            .get_optional_nullable("hardcoded_gp_value", settings_default_hardcoded_gp_value)?;

        let d_path = self
            .d_path
            .get_optional_nullable("d_path", settings_default_d_path)?;
        let target_path = self
            .target_path
            .get_optional_nullable("target_path", settings_default_target_path)?;

        let symbols_header_path = self
            .symbols_header_path
            .get_optional_nullable("symbols_header_path", settings_default_symbols_header_path)?;
        let symbols_header_type = self
            .symbols_header_type
            .get_non_null("symbols_header_type", settings_default_symbols_header_type)?;
        let symbols_header_as_array = self.symbols_header_as_array.get_non_null(
            "symbols_header_as_array",
            settings_default_symbols_header_as_array,
        )?;

        let paths_list_path = self
            .paths_list_path
            .get_optional_nullable("paths_list_path", settings_default_paths_list_path)?;

        let sections_allowlist = self
            .sections_allowlist
            .get_non_null("sections_allowlist", settings_default_sections_allowlist)?;
        let sections_allowlist_extra = self.sections_allowlist_extra.get_non_null(
            "sections_allowlist_extra",
            settings_default_sections_allowlist_extra,
        )?;
        let sections_denylist = self
            .sections_denylist
            .get_non_null("sections_denylist", settings_default_sections_denylist)?;
        let discard_wildcard_section = self.discard_wildcard_section.get_non_null(
            "discard_wildcard_section",
            settings_default_discard_wildcard_section,
        )?;

        let single_segment_mode = self
            .single_segment_mode
            .get_non_null("single_segment_mode", settings_default_single_segment_mode)?;

        let partial_scripts_folder = self.partial_scripts_folder.get_optional_nullable(
            "partial_scripts_folder",
            settings_default_partial_scripts_folder,
        )?;
        let partial_build_segments_folder =
            self.partial_build_segments_folder.get_optional_nullable(
                "partial_build_segments_folder",
                settings_default_partial_build_segments_folder,
            )?;

        if d_path.is_some() && target_path.is_none() {
            return Err(SlinkyError::MissingRequiredFieldCombo {
                required: "target_path".to_string(),
                other: "d_path".to_string(),
            });
        }

        let alloc_sections = self
            .alloc_sections
            .get_optional_nullable("alloc_sections", settings_default_alloc_sections)?;
        let noload_sections = self
            .noload_sections
            .get_optional_nullable("noload_sections", settings_default_noload_sections)?;

        let subalign = self
            .subalign
            .get_optional_nullable("subalign", settings_default_subalign)?;

        let segment_start_align = self
            .segment_start_align
            .get_optional_nullable("segment_start_align", settings_default_segment_start_align)?;

        let segment_end_align = self
            .segment_end_align
            .get_optional_nullable("segment_end_align", settings_default_segment_end_align)?;

        let section_start_align = self
            .section_start_align
            .get_optional_nullable("section_start_align", settings_default_section_start_align)?;

        let section_end_align = self
            .section_end_align
            .get_optional_nullable("section_end_align", settings_default_section_end_align)?;

        let sections_start_alignment = self.sections_start_alignment.get_non_null(
            "sections_start_alignment",
            settings_default_sections_start_alignment,
        )?;

        let sections_end_alignment = self.sections_end_alignment.get_non_null(
            "sections_end_alignment",
            settings_default_sections_end_alignment,
        )?;

        let wildcard_sections = self
            .wildcard_sections
            .get_non_null("wildcard_sections", settings_default_wildcard_sections)?;

        let fill_value = self
            .fill_value
            .get_optional_nullable("fill_value", settings_default_fill_value)?;

        let sections_subgroups = self
            .sections_subgroups
            .get_non_null("sections_subgroups", settings_default_subsections_groups)?;

        Ok(Settings {
            base_path,
            linker_symbols_style,
            hardcoded_gp_value,

            d_path,
            target_path,

            symbols_header_path,
            symbols_header_type,
            symbols_header_as_array,

            paths_list_path,

            sections_allowlist,
            sections_allowlist_extra,
            sections_denylist,
            discard_wildcard_section,

            single_segment_mode,

            partial_scripts_folder,
            partial_build_segments_folder,

            alloc_sections,
            noload_sections,

            subalign,
            segment_start_align,
            segment_end_align,
            section_start_align,
            section_end_align,
            sections_start_alignment,
            sections_end_alignment,
            wildcard_sections,
            fill_value,
            sections_subgroups,
        })
    }
}
