// Copyright © 2025 The µcad authors <info@ucad.xyz>
// SPDX-License-Identifier: AGPL-3.0-or-later

use crate::{eval::*, model::*};

/// Frame in [Stack] for *local variables*, *aliases* (*use statements*) and *calls*.
///
/// A *stack frame* can have different types and some provide a storage for *local variables*
/// like [`StackFrame::Source`] and [`StackFrame::Body`]) and some do not, some have a *id*
/// like [`StackFrame::Source`] amd [`StackFrame::Module`]) and some do not and
/// [`Call`] is used for procedural calls.
///
/// Each frame store some of these information:
///   - an [`Identifier`]
///   - local variables in a [`SymbolMap`] (e.g. `i = 5;`)
///   - local aliases in a [`SymbolMap`] (e.g. `use std::print;`)
///   - argument value list (e.g. `f(x = 0, y = 1);`
pub enum StackFrame {
    /// Source file with locals.
    Source(Identifier, SymbolMap),
    /// Module scope with locals.
    Module(Identifier, SymbolMap),
    /// initializer scope with locals.
    Init(SymbolMap),
    /// Part scope with locals.
    Workbench(Model, Identifier, SymbolMap),
    /// Body (scope)  with locals.
    Body(SymbolMap),
    /// Function body
    Function(Identifier, SymbolMap),
    /// A call (e.g. og function or  part).
    Call {
        /// Symbol that was called.
        symbol: Symbol,
        /// Evaluated arguments.
        args: ArgumentValueList,
        /// Source code reference.
        src_ref: SrcRef,
    },
}

impl StackFrame {
    /// Get identifier if available or panic.
    pub fn id(&self) -> Option<Identifier> {
        match self {
            StackFrame::Source(id, _) | StackFrame::Module(id, _) => Some(id.clone()),
            _ => None,
        }
    }

    /// Return symbol of the stack frame, if there is any.
    pub fn symbol(&self) -> Option<Symbol> {
        match &self {
            StackFrame::Call { symbol, .. } => Some(symbol.clone()),
            _ => None,
        }
    }

    /// Return stack frame kind as str
    pub fn kind_str(&self) -> &'static str {
        match self {
            StackFrame::Source(..) => "source",
            StackFrame::Module(..) => "module",
            StackFrame::Init(..) => "init",
            StackFrame::Workbench(..) => "workbench",
            StackFrame::Body(..) => "body",
            StackFrame::Function(..) => "function",
            StackFrame::Call { .. } => "call",
        }
    }

    /// Print stack frame.
    pub fn locals(&self) -> Option<&SymbolMap> {
        match self {
            StackFrame::Source(_, locals)
            | StackFrame::Module(_, locals)
            | StackFrame::Init(locals)
            | StackFrame::Workbench(.., locals)
            | StackFrame::Body(locals)
            | StackFrame::Function(_, locals) => Some(locals),
            StackFrame::Call { .. } => None,
        }
    }
    /// Print stack frame.
    pub fn print_locals(
        &self,
        f: &mut std::fmt::Formatter<'_>,
        idx: usize,
        mut depth: usize,
    ) -> std::fmt::Result {
        let locals = match self {
            StackFrame::Source(id, locals) => {
                writeln!(f, "{:depth$}[{idx}] Source: {id:?}", "")?;
                locals
            }
            StackFrame::Module(id, locals) => {
                writeln!(f, "{:depth$}[{idx}] Module: {id:?}", "")?;
                locals
            }
            StackFrame::Init(locals) => {
                writeln!(f, "{:depth$}[{idx}] Init", "")?;
                locals
            }
            StackFrame::Workbench(_, id, locals) => {
                writeln!(f, "{:depth$}[{idx}] Workbench: {id:?}", "")?;
                locals
            }
            StackFrame::Body(locals) => {
                writeln!(f, "{:depth$}[{idx}] Body:", "")?;
                locals
            }
            StackFrame::Function(id, locals) => {
                writeln!(f, "{:depth$}[{idx}] Function: {id:?}", "")?;
                locals
            }
            StackFrame::Call {
                symbol,
                args,
                src_ref: _,
            } => {
                return writeln!(
                    f,
                    "{:depth$}[{idx}] Call: {name:?}({args:?})",
                    "",
                    args = args,
                    name = symbol.full_name()
                );
            }
        };

        depth += 4;

        for (id, symbol) in locals.iter() {
            let full_name = symbol.full_name();
            let full_name = if full_name != id.into() {
                format!(" [{full_name:?}]")
            } else {
                String::new()
            };
            let entry = symbol.with_def(|def| match def {
                SymbolDef::Constant(visibility, id, value) => {
                    format!("{visibility}{id:?} = {value:?}{full_name} (constant)",)
                }
                SymbolDef::Assignment(a) => {
                    format!("{a}{full_name} (assignment)")
                }
                SymbolDef::Argument(id, value) => {
                    format!("{id:?} = {value:?}{full_name} (argument)")
                }
                SymbolDef::SourceFile(source) => {
                    format!("{:?} (source)", source.filename())
                }
                SymbolDef::Module(def) => {
                    format!("{:?}{full_name} (module)", def.id)
                }
                SymbolDef::Workbench(def) => {
                    format!("{:?}{full_name} (workbench)", def.id)
                }
                SymbolDef::Function(def) => {
                    format!("{:?}{full_name} (function)", def.id)
                }
                SymbolDef::Builtin(builtin) => {
                    format!("{:?}{full_name} (builtin)", builtin.id)
                }
                SymbolDef::Alias(visibility, id, name) => {
                    format!("{visibility}{id:?}{full_name} -> {name:?} (alias)")
                }
                SymbolDef::UseAll(visibility, name) => {
                    format!("{visibility}{name:?}{full_name} (use all)",)
                }
                #[cfg(test)]
                SymbolDef::Tester(id) => format!("{id:?} (tester)"),
            });
            if cfg!(feature = "ansi-color") && symbol.is_used() {
                writeln!(f, "{:depth$}- {entry}", "")?;
            } else {
                color_print::cwriteln!(f, "{:depth$}- <#606060>{entry}</>", "",)?;
            }
        }

        Ok(())
    }

    /// Pretty print single call stack frame.
    pub fn print_stack(
        &self,
        f: &mut dyn std::fmt::Write,
        source_by_hash: &impl GetSourceByHash,
        idx: usize,
    ) -> std::fmt::Result {
        match self {
            StackFrame::Call {
                symbol,
                args,
                src_ref,
            } => {
                writeln!(
                    f,
                    "{:>4}: {name:?}({args:?})",
                    idx,
                    name = symbol.full_name()
                )?;

                if let Some(line_col) = src_ref.at() {
                    let source_file = source_by_hash.get_by_hash(src_ref.source_hash());
                    writeln!(
                        f,
                        "            at {filename}:{line_col}",
                        filename = source_file
                            .as_ref()
                            .map(|sf| sf.filename_as_str())
                            .unwrap_or(crate::invalid!(FILE)),
                    )?;
                }
            }
            _ => unreachable!(),
        }

        Ok(())
    }
}
