use alloc::string::String;
use alloc::vec::Vec;

use super::preprocessor::DylibSpec;

/// One `#pragma export(<name>)` resolved against the
/// compiled program: the externally visible name plus the
/// function's `ent_pc` identifier. Shared-object writers
/// translate the identifier to a code-segment offset via
/// `Build::pc_to_native[ent_pc]` and then to a runtime
/// address by adding the code segment's vmaddr base.
#[derive(Debug, Clone)]
pub struct ExportedFunction {
    /// External name as written in `#pragma export(...)`.
    /// Per-format writers may decorate it with a leading
    /// underscore (Mach-O `_foo`) at emit time; the c5-side
    /// name stays as the user wrote it.
    pub name: String,
    /// `ent_pc` identifier of the function's entry. Indexes
    /// into `Build::pc_to_native` to recover the native
    /// code-segment offset.
    pub ent_pc: usize,
}

/// A pointer-to-extern-data initializer. The slot at `data_offset` in
/// [`Program::data`] must hold the runtime address of the data symbol
/// `symbol_name`, which is defined in another translation unit and
/// resolved by name at link time.
#[derive(Debug, Clone)]
pub struct ExternDataReloc {
    pub data_offset: u64,
    pub symbol_name: alloc::string::String,
    /// Byte offset added to the symbol's address -- non-zero for
    /// `&extern_arr[N]` and `&extern_g + K`.
    pub addend: i64,
}

/// A pointer-to-global initializer that needs run-time
/// relocation. `data_offset` is the byte offset within
/// [`Program::data`] where the absolute address of the target
/// must end up at runtime; `target_offset` is the byte offset
/// of the target within the same data segment.
#[derive(Debug, Clone, Copy)]
pub struct DataReloc {
    /// Byte offset in `Program::data` of the 8-byte slot that
    /// holds the runtime address of `target_offset`. Always
    /// 8-byte aligned (c5 lays globals out in 8-byte slots).
    pub data_offset: u64,
    /// Byte offset in `Program::data` of the global being
    /// pointed at. Same data segment; cross-segment
    /// relocations (e.g., into `tls_data`) are future work.
    pub target_offset: u64,
}

/// A function-pointer initializer that needs run-time relocation.
/// `data_offset` is the byte offset within [`Program::data`] where
/// the function's runtime code address must end up;
/// `target_ent_pc` is the function's `ent_pc` identifier (the
/// codegen maps it through `Build::pc_to_native` to a native
/// code-segment offset, then to a runtime address by adding the
/// text segment's vmaddr).
///
/// Generated by initializers like
/// `static const VTable v = { .xClose = my_close };`. The VM
/// reads the data slot directly because its function pointers
/// carry the small `CODE_BASE + ent_pc` bias and the indirect-
/// call lowering recognises that range. Native writers replace
/// the slot's
/// contents at write time (ELF) or via a dynamic relocation
/// (Mach-O rebase, PE `.reloc DIR64`).
#[derive(Debug, Clone, Copy)]
pub struct CodeReloc {
    /// Byte offset in `Program::data` of the 8-byte slot that
    /// receives the function's runtime code address. Always
    /// 8-byte aligned.
    pub data_offset: u64,
    /// `ent_pc` of the function. The codegen translates this to a
    /// native code-segment offset via `pc_to_native`.
    pub target_ent_pc: u64,
}

/// Compiled program ready for the VM.
///
/// `data` is the static data segment (string literals plus zero-
/// initialised globals), `entry_pc` is an integer identifier for
/// the program's entry function (codegen and the VM look it up
/// against `FunctionSsa::ent_pc`), and `warnings` carries any
/// non-fatal diagnostics the compiler emitted (type mismatches,
/// arity issues). The compiler never fails on a warning -- callers
/// decide whether to print, ignore, or treat them as errors.
#[derive(Debug, Clone)]
pub struct Program {
    pub data: Vec<u8>,
    /// Start offsets of anonymous data objects (string literals and the
    /// implicit `__func__` arrays of C99 6.4.2.2) within `data`. Named
    /// globals already carry their offset in `symbols[..].val`; these are
    /// the objects with no symbol. Static DCE treats the sorted union of
    /// these and the named-global offsets as object boundaries: an
    /// `[start, next_start)` interval is one object. Only true object
    /// starts are recorded, so a missing entry merely glues an object to
    /// its predecessor (kept conservatively), never splits a live one.
    pub data_object_starts: Vec<i64>,
    pub entry_pc: usize,
    pub warnings: Vec<String>,
    /// Initialised + zero-init thread-local data. Layout matches
    /// the way `data` does for ordinary globals: a flat byte array
    /// indexed by `Inst::TlsAddr`'s operand. The image writers copy
    /// this into `.tdata` (initialised slice = `tls_data[..tls_init_size]`)
    /// and `.tbss` (zero-fill remainder = `tls_data[tls_init_size..]`).
    /// Today everything starts zero (no `_Thread_local int x = 5;`
    /// initialiser syntax), so `tls_init_size == 0` and the whole
    /// block lives in .tbss; the layout is structured for future
    /// expansion.
    pub tls_data: Vec<u8>,
    /// Number of bytes of `tls_data` that are statically initialised
    /// (i.e., emitted into `.tdata`). The remainder
    /// (`tls_data.len() - tls_init_size` bytes) is zero-init and
    /// goes into `.tbss`. Invariant: `tls_init_size <= tls_data.len()`.
    pub tls_init_size: usize,
    /// Address-of-global initializers. Each entry says "byte
    /// `data_offset` of the data segment must hold the runtime
    /// address of byte `target_offset` of the data segment".
    /// Generated by initializers like
    /// `int *p = &x;` where both `p` and `x` live in `.data`.
    /// The VM ignores this -- it materializes pointers
    /// dynamically by adding `data_base` to the offset immediate.
    /// The native writers translate each entry to the per-format
    /// dynamic relocation:
    ///
    /// * **ELF** (`ET_EXEC`, no slide): write the absolute VA
    ///   into `build.data` at write time. No runtime fixup.
    /// * **Mach-O** (`MH_PIE`, slid): emit a rebase opcode for
    ///   the slot. Initial bytes hold the preferred VA; the
    ///   loader adds the slide.
    /// * **PE** (DYNAMIC_BASE + `.reloc`): emit a
    ///   `IMAGE_REL_BASED_DIR64` entry. Initial bytes hold the
    ///   preferred VA; the loader adds the slide delta.
    pub data_relocs: Vec<DataReloc>,
    /// Pointer-to-extern-data initializers (`int *p = &extern_g;` and the
    /// equivalent extern-array decay). The target is defined in another
    /// translation unit, so the slot at `data_offset` must hold that
    /// symbol's runtime address, resolved by name at link time -- unlike
    /// [`DataReloc`], whose target is a known offset in this unit's data.
    pub extern_data_relocs: Vec<ExternDataReloc>,
    /// Function-pointer initializers in the data segment. Each
    /// entry says "byte `data_offset` of the data segment must
    /// hold the runtime code address of the function whose first
    /// instruction is at ent_pc `target_ent_pc`". Per-format
    /// writer handling mirrors `data_relocs` -- see [`CodeReloc`]
    /// for the per-format strategy.
    pub code_relocs: Vec<CodeReloc>,
    /// Functions the program asked to expose externally via
    /// `#pragma export(<name>)`. Each entry pairs the source
    /// name with the function's ent_pc -- the
    /// shared-object writers (Mach-O dylib, ELF .so, PE DLL)
    /// promote each entry to a real export-table record so
    /// callers in another image can resolve the symbol via
    /// `dlsym` / `GetProcAddress`.
    ///
    /// Empty for executable output (the user-supplied
    /// program never reached for `#pragma export`, or the
    /// build asked for an executable rather than a shared
    /// library). The VM and JIT ignore this field; only the
    /// shared-object writer paths consume it.
    pub exports: Vec<ExportedFunction>,
    /// Per-target dylib + binding map produced by the preprocessor
    /// from the `#pragma comment(dylib, ...)` and
    /// `#pragma binding(...)` directives in `headers/badc-{target}.h`.
    /// The native codegen uses this to:
    /// * pick the per-target real-symbol name for each interp op
    ///   (`printf` -> `_printf` on macOS, `_printf` on Windows
    ///   msvcrt, `printf` on Linux),
    /// * conditionally include only those dylibs whose bindings the
    ///   program actually references (so a c5 source that never
    ///   calls `mprotect` doesn't drag `kernel32.dll` into the
    ///   import table on Windows), and
    /// * emit a hard error when a referenced op has no binding for
    ///   the chosen target, or when a declared dylib path doesn't
    ///   exist on disk.
    ///
    /// The VM ignores this field; only `emit_native` reaches for it.
    pub(crate) dylibs: Vec<DylibSpec>,
    /// Bytecode PC of a user-defined `DllMain` function, if the
    /// source declared one. Used by the PE writer for
    /// `--shared` output: when present, `AddressOfEntryPoint`
    /// targets the user's body and the boilerplate
    /// `mov eax, 1; ret` stub is suppressed; when `None`, the
    /// writer falls back to the stub. The signature isn't
    /// validated -- the user is trusted to honor the Win64
    /// `BOOL DllMain(HINSTANCE, DWORD, LPVOID)` shape, same as
    /// `main` is trusted today. The Mach-O / ELF / VM / JIT
    /// paths ignore this field.
    pub dllmain_pc: Option<usize>,
    /// Source-file table. Populated by the lexer's GNU line-
    /// marker handling: each `#include` boundary or
    /// `#line N "file"` directive interns a fresh filename.
    /// Index 0 is the user's translation-unit source (the
    /// `--source-path` CLI arg, falling back to the lexer's
    /// initial `"<source>"` placeholder). The DWARF emitter
    /// reads this list and emits one file entry per name; the
    /// per-Inst `inst_src` records the file index inline.
    pub source_files: Vec<String>,
    /// Filesystem path of the input `.c` file, when known. Set by
    /// the CLI shim from the user's argv before
    /// [`crate::emit_native`] runs; the c5 frontend itself doesn't
    /// know the path, so it leaves this empty for the
    /// `compile_str` / stdin / fixture paths.
    ///
    /// The DWARF emitter uses this as the CU's
    /// `DW_AT_name` and the line program's only file entry, so
    /// `lldb image lookup -n foo` reports `foo at /path/to/src.c:N`
    /// instead of `<unknown>:N`. Empty falls back to `<unknown>`.
    /// The VM / JIT / interpreter ignore it.
    pub source_path: String,
    /// Per-function local + formal-parameter records, captured at
    /// function-body close (just before the c5 `Token::Loc`
    /// shadowing is unwound). Indexed by lookup -- consumers
    /// (DWARF emitter) filter by `function_bc_pc` to gather
    /// the variables that belong to each subprogram.
    ///
    /// `fp_slot` is the c5 frame-relative slot the symbol resolves
    /// to: positive values are arguments (`fp + slot*8`, which is
    /// `fp+0x10` for the first arg etc.), negative values are
    /// locals (`fp + slot*8`, e.g. `-1` -> `fp - 8`). DWARF turns
    /// this into a `DW_AT_location` with `DW_OP_fbreg` so lldb /
    /// gdb can resolve a variable name to its current stack
    /// address.
    ///
    /// Empty when c5 is run as a library (no driver-side capture)
    /// or the program contains no functions. The VM / JIT /
    /// interpreter ignore this field.
    pub variables: Vec<VariableInfo>,
    /// Struct / union registry, indexed by the struct id encoded
    /// in c5 type tags (`STRUCT_BASE + id * STRUCT_STRIDE`).
    /// Cloned out of `Compiler::structs` at `compile()` time so
    /// the DWARF emitter can walk member offsets and
    /// bitfield layouts to produce `DW_TAG_structure_type` /
    /// `DW_TAG_union_type` DIEs. The VM / JIT / interpreter
    /// ignore this field.
    pub structs: Vec<crate::c5::compiler::StructDef>,
    /// Captured enum definitions. Sourced from
    /// `Compiler::enums` at `compile()`; the DWARF emitter walks
    /// this to produce DW_TAG_enumeration_type + DW_TAG_enumerator
    /// DIEs so `(gdb) ptype enum Tag` shows the named constants.
    /// Empty for archive-reloaded units (enum tags don't round-trip
    /// through the `.o` format yet).
    pub enums: Vec<crate::c5::compiler::EnumDef>,
    /// Resolved entry-function name: `#pragma entrypoint(<name>)`
    /// when present, otherwise whichever of `main` / `wmain` /
    /// `WinMain` / `wWinMain` the source defines. `None` for
    /// entry-less images (DLL with exports). The PE writer
    /// reads this to pick the stub flavour (`wmain` swaps
    /// `__getmainargs` for `__wgetmainargs`); other writers
    /// ignore it.
    pub entry_name: Option<String>,
    /// Source-declared Windows subsystem. Set by
    /// `#pragma subsystem(<kind>)`; `None` falls back to the
    /// PE writer's default (`Console`). Read only by the PE
    /// writers; non-PE targets (Mach-O / ELF) ignore this
    /// field.
    pub subsystem: Option<crate::c5::preprocessor::Subsystem>,
    /// Per-function AST snapshots paired with their function-
    /// shaped metadata (ent_pc, n_params, is_variadic, n_locals,
    /// name). Populated by the parser's dual-emit; the SSA walker
    /// reads from here to lower each function. Empty for builds
    /// that didn't go through the parser path (linker reload from
    /// a `.o` / `.a`).
    pub(crate) finished_functions: alloc::vec::Vec<crate::c5::ast::FinishedFunction>,
    /// Symbol-table snapshot taken at `compile()` close. The AST
    /// walker reads `array_size` (for the C99 6.3.2.1p3
    /// array-to-pointer decay detection) and `type_` (for
    /// `Decl::Local` width selection) off this slice. Empty for
    /// `Program` shapes built outside the parser pipeline.
    pub(crate) symbols: alloc::vec::Vec<crate::c5::symbol::Symbol>,
    /// Synthesised `FunctionSsa` entries the parser produces
    /// outside the AST walker (sys-trampolines + the synthetic
    /// CRT entry). The codegen reads these directly through
    /// `produce_ssa_funcs`.
    pub(crate) synthetic_ssa_funcs: alloc::vec::Vec<crate::c5::ir::FunctionSsa>,
    /// User-function `FunctionSsa` entries produced by the AST
    /// walker; the native linker concatenates and rebases them to
    /// merged PCs. Carries the body for every parser-declared
    /// function. The codegen reads these directly through
    /// `produce_ssa_funcs`. Empty only for `Program` shapes built
    /// outside the parser pipeline.
    pub(crate) user_ssa_funcs: alloc::vec::Vec<crate::c5::ir::FunctionSsa>,
    /// Cross-TU user-function imports surfaced by the parser
    /// for the `-c` (`OutputKind::Relocatable`) path. Each
    /// entry is `(placeholder_pc, symbol_name)`:
    /// the parser assigned `placeholder_pc` as a unique
    /// out-of-range `ent_pc` to every extern-declared function
    /// with no body in this TU. The walker forwards it
    /// through `live_fun_val`, so the matching `Inst::Call`
    /// carries that PC. The native codegen detects it (PC
    /// past every real function's `end_pc`) and surfaces a
    /// `RelocCallSite` against the symbol's name instead of
    /// patching the BL/CALL placeholder against
    /// `pc_to_native`. Empty for builds without
    /// `CompileOptions::no_entry_point`; the regular single-
    /// TU compile errors out on undefined externs earlier.
    pub(crate) extern_function_imports: alloc::vec::Vec<(usize, alloc::string::String)>,
}

/// A single local variable or formal parameter belonging to a
/// specific c5-emitted function, captured for DWARF emission.
/// The function is identified by `function_bc_pc` -- the
/// function's `ent_pc`, which the DWARF emitter already uses to
/// find the matching subprogram DIE.
#[derive(Debug, Clone)]
pub struct VariableInfo {
    /// PC of the owning function's entry. Same value
    /// the subprogram-discovery walk in `dwarf::collect_subprograms`
    /// produces, so the DWARF emitter can group variables by
    /// subprogram with a simple equality check.
    pub function_bc_pc: u64,
    /// Source-level identifier (parameter or local name).
    pub name: String,
    /// c5 type tag (`Ty` enum encoded as `i64`). Pointer levels
    /// follow the `+= 2` convention; struct types live in the
    /// `STRUCT_BASE +` band.
    pub type_tag: i64,
    /// Frame-relative slot. `>=2` is an argument (caller pushed
    /// it at `fp + slot*8`); `<0` is a local (the function
    /// allocated it at `fp + slot*8`, with the value negative).
    /// Slots `0..2` are the saved-x29 / saved-x30 area and don't
    /// hold user-visible variables.
    pub fp_slot: i64,
    /// True for arguments (`Token::Loc` symbols introduced by the
    /// parameter list) so the DWARF emitter picks
    /// `DW_TAG_formal_parameter`; locals get `DW_TAG_variable`.
    pub is_parameter: bool,
    /// Source line of the declaration. Surfaces as `DW_AT_decl_line`
    /// on the matching DIE so `(gdb) info args` / `info locals` can
    /// point at the declaration. Zero when the parser didn't record
    /// a position (archive-reloaded units default here).
    pub decl_line: u32,
    /// Declared element count for true local arrays (`int xs[N]`).
    /// Drives DW_TAG_array_type / DW_TAG_subrange_type emission so
    /// `ptype xs` shows `int [N]` instead of just `int`. Zero for
    /// scalars and for parameters (the latter decay to pointers per
    /// C99 6.7.5.3p7 and keep the pointer-type DIE).
    pub array_size: u32,
    /// `Compiler::source_files` index of the declaration's source
    /// file. Surfaces as `DW_AT_decl_file` after mapping to the
    /// DWARF file_names index. Zero is the primary source.
    pub decl_file: u32,
}
