//! High-level format functions for each q value shape.
//!
//! Each function produces a multi-line ASCII repr string. Rendering is driven
//! by the active [`FormattingOptions`] (read from the process-wide global).

use qroissant_core::Atom as CoreAtom;
use qroissant_core::Dictionary as CoreDictionary;
use qroissant_core::List as CoreList;
use qroissant_core::Table as CoreTable;
use qroissant_core::Value as CoreValue;
use qroissant_core::Vector as CoreVector;
use qroissant_core::VectorData;

use super::cell::atom_primitive_label;
use super::cell::format_atom_cell;
use super::cell::format_atom_raw;
use super::cell::format_char_vector;
use super::cell::format_vector_item;
use super::cell::primitive_label;
use super::cell::truncate;
use super::options::active_options;
use super::render::PreviewSlot;
use super::render::preview_slots;
use super::render::render_preview;

// ---------------------------------------------------------------------------
// Attribute helper
// ---------------------------------------------------------------------------

fn attribute_label(attribute: qroissant_core::Attribute) -> &'static str {
    match attribute {
        qroissant_core::Attribute::None => "none",
        qroissant_core::Attribute::Sorted => "sorted",
        qroissant_core::Attribute::Unique => "unique",
        qroissant_core::Attribute::Parted => "parted",
        qroissant_core::Attribute::Grouped => "grouped",
    }
}

// ---------------------------------------------------------------------------
// Atom
// ---------------------------------------------------------------------------

pub fn format_atom(atom: &CoreAtom) -> String {
    let label = atom_primitive_label(atom);
    render_preview(
        vec![format!("Atom [{label}]")],
        vec!["value".to_string()],
        vec![vec![format_atom_cell(atom)]],
        vec!["shape: (1,)".to_string()],
    )
}

// ---------------------------------------------------------------------------
// Vector
// ---------------------------------------------------------------------------

pub fn format_vector(vector: &CoreVector) -> String {
    let len = vector.len();
    let data = vector.data();
    let label = primitive_label(data);
    let attr = vector.attribute();

    let rows = match data {
        VectorData::Char(chars) => {
            vec![vec![format_char_vector(chars)]]
        }
        _ => {
            let opts = active_options();
            preview_slots(len, opts.max_rows, opts.row_display)
                .into_iter()
                .map(|slot| match slot {
                    PreviewSlot::Index(i) => vec![format_vector_item(data, i)],
                    PreviewSlot::Ellipsis => vec!["...".to_string()],
                })
                .collect()
        }
    };

    render_preview(
        vec![format!("Vector [{label}, attr={}]", attribute_label(attr))],
        vec!["value".to_string()],
        rows,
        vec![format!("shape: ({len},)")],
    )
}

// ---------------------------------------------------------------------------
// List
// ---------------------------------------------------------------------------

/// Compact single-line summary of any `CoreValue` (used for list/dict cells).
fn inline_value_summary(value: &CoreValue) -> String {
    match value {
        CoreValue::Atom(atom) => truncate(format!(
            "{} [{}]",
            format_atom_raw(atom),
            atom_primitive_label(atom)
        )),
        CoreValue::Vector(vector) => {
            let label = primitive_label(vector.data());
            let len = vector.len();
            match vector.data() {
                VectorData::Char(chars) => truncate(format_char_vector(chars)),
                _ => truncate(format!("vector<{label}>[{len}]")),
            }
        }
        CoreValue::List(list) => truncate(format!("list[{}]", list.len())),
        CoreValue::Dictionary(dict) => truncate(format!("dict[{}]", dict.len())),
        CoreValue::Table(table) => {
            truncate(format!("table[{}x{}]", table.len(), table.num_columns()))
        }
        CoreValue::UnaryPrimitive { opcode } => truncate(format!("unary(0x{opcode:02x})")),
    }
}

pub fn format_list(list: &CoreList) -> String {
    let len = list.len();
    let opts = active_options();
    let attr = list.attribute();

    let rows = preview_slots(len, opts.max_rows, opts.row_display)
        .into_iter()
        .map(|slot| match slot {
            PreviewSlot::Index(i) => vec![inline_value_summary(&list.values()[i])],
            PreviewSlot::Ellipsis => vec!["...".to_string()],
        })
        .collect();

    render_preview(
        vec![format!("List [list, attr={}]", attribute_label(attr))],
        vec!["value".to_string()],
        rows,
        vec![format!("shape: ({len},)")],
    )
}

// ---------------------------------------------------------------------------
// Dictionary
// ---------------------------------------------------------------------------

pub fn format_dictionary(dict: &CoreDictionary) -> String {
    let size = dict.len();
    let sorted = dict.sorted();

    let all_rows = vec![
        vec!["keys".to_string(), inline_value_summary(dict.keys())],
        vec!["values".to_string(), inline_value_summary(dict.values())],
    ];

    let opts = active_options();
    let rows = preview_slots(all_rows.len(), opts.max_rows, opts.row_display)
        .into_iter()
        .map(|slot| match slot {
            PreviewSlot::Index(i) => all_rows[i].clone(),
            PreviewSlot::Ellipsis => vec!["...".to_string(), "...".to_string()],
        })
        .collect();

    render_preview(
        vec![format!("Dictionary [dict, sorted={sorted}]")],
        vec!["part".to_string(), "value".to_string()],
        rows,
        vec![format!("shape: ({size},)")],
    )
}

// ---------------------------------------------------------------------------
// Table
// ---------------------------------------------------------------------------

fn column_primitive_label(col: &CoreValue) -> &'static str {
    match col {
        CoreValue::Vector(v) => primitive_label(v.data()),
        CoreValue::List(_) => "list",
        CoreValue::Atom(_) => "atom",
        _ => "?",
    }
}

fn table_cell(col: &CoreValue, row_index: usize) -> String {
    match col {
        CoreValue::Vector(v) => match v.data() {
            VectorData::Char(chars) => {
                // Show a single char per cell
                if row_index < chars.len() {
                    (chars[row_index] as char).to_string()
                } else {
                    "?".to_string()
                }
            }
            data => format_vector_item(data, row_index),
        },
        CoreValue::Atom(atom) => format_atom_cell(atom),
        CoreValue::List(list) => {
            if row_index < list.len() {
                inline_value_summary(&list.values()[row_index])
            } else {
                "?".to_string()
            }
        }
        _ => inline_value_summary(col),
    }
}

fn column_name(raw: &[u8]) -> String {
    String::from_utf8_lossy(raw).into_owned()
}

pub fn format_table(table: &CoreTable) -> String {
    let num_rows = table.len();
    let num_cols = table.num_columns();
    let opts = active_options();
    let visible_cols = num_cols.min(opts.max_columns);

    // Build headers: "name\ntype" for each visible column
    let mut headers: Vec<String> = table
        .column_names()
        .iter()
        .zip(table.columns().iter())
        .take(visible_cols)
        .map(|(name, col)| {
            let col_name = truncate(column_name(name));
            let type_label = column_primitive_label(col);
            format!("{col_name}\n{type_label}")
        })
        .collect();

    if num_cols > visible_cols {
        headers.push("...\n...".to_string());
    } else if headers.is_empty() {
        headers.push("value".to_string());
    }

    // Build rows
    let row_slots = preview_slots(num_rows, opts.max_rows, opts.row_display);
    let columns = table.columns();

    let body_rows: Vec<Vec<String>> = row_slots
        .into_iter()
        .map(|slot| {
            let mut row: Vec<String> = match slot {
                PreviewSlot::Index(row_i) => (0..visible_cols)
                    .map(|col_i| table_cell(&columns[col_i], row_i))
                    .collect(),
                PreviewSlot::Ellipsis => vec!["...".to_string(); visible_cols.max(1)],
            };
            if num_cols > visible_cols {
                row.push("...".to_string());
            }
            row
        })
        .collect();

    render_preview(
        vec![format!(
            "Table [table, attr={}]",
            attribute_label(table.attribute())
        )],
        headers,
        body_rows,
        vec![format!("shape: ({num_rows}, {num_cols})")],
    )
}

// ---------------------------------------------------------------------------
// UnaryPrimitive
// ---------------------------------------------------------------------------

#[allow(dead_code)]
pub fn format_unary_primitive(opcode: i8) -> String {
    render_preview(
        vec!["UnaryPrimitive [unary_primitive]".to_string()],
        vec!["opcode".to_string()],
        vec![vec![format!("0x{opcode:02x}")]],
        vec!["shape: (1,)".to_string()],
    )
}
