# radar_data_reader/core/app_controller.py

import multiprocessing as mp
import csv
import json
import os
import subprocess
import sys
import re
from pathlib import Path
from typing import List, Any, Dict, Tuple, Optional, Callable
from tkinter import filedialog, messagebox
import tkinter as tk
import ctypes
import threading
import shutil
import time
from datetime import datetime
import queue
from collections import OrderedDict

try:
    import pandas as pd
except ImportError:
    pd = None

from ..utils.config_manager import ConfigManager
from .file_reader import run_worker_process
from .cpp_runner import run_cpp_converter
from .data_structures import DataBatch, CtypesStructureBase
from .data_enums import ENUM_REGISTRY, get_enum_name
from .export_profiles import ExportProfile, ExportField
from .export_manager import ExportManager, ExportJob
from .flight_analyzer import FlightAnalyzer
from .out_processor import OutProcessor
from .segment_processor import SegmentProcessor
from .file_analyzer import FileStructureAnalyzer

from ..utils import logger
from ..gui.profile_editor_window import ProfileEditorWindow
from ..gui.rec_config_window import RecConfigWindow
from ..gui.segment_export_config_window import SegmentExportConfigWindow
from ..gui.segment_processor_tab import SegmentProcessorTab
from .struct_utils import convert_ctypes_to_dict

log = logger.get_logger(__name__)


def _get_value_from_path(batch: DataBatch, field: ExportField) -> Any:
    """Helper function to safely retrieve a nested value from a DataBatch object."""
    try:
        path = field.data_path
        if path == "batch_id": return batch.batch_id
        parts = re.split(r'[.[]', path)
        current_obj = batch
        for part in parts:
            if current_obj is None: return "N/A"
            if not part: continue
            if part.endswith(']'):
                index_str = part[:-1]
                if not index_str.isdigit(): return "N/A"
                try:
                    current_obj = current_obj[int(index_str)]
                except (IndexError, TypeError): return "N/A"
            else:
                current_obj = getattr(current_obj, part, None)
        value = current_obj if current_obj is not None else "N/A"
        log.debug(f"Path: '{field.data_path}', Extracted value: {value}")
        if field.translate_with_enum and isinstance(value, int):
            enum_path = re.sub(r'[\[\]]', '', path)
            enum_class = ENUM_REGISTRY.get(enum_path)
            if enum_class: return get_enum_name(enum_class, value)
        return value
    except Exception as e:
        log.warning(f"Could not resolve path '{field.data_path}': {e}")
        return "N/A"

def run_analysis_worker(filepath: Path, config: Dict[str, Any], result_queue: mp.Queue):
    """
    Worker function to run the FileStructureAnalyzer in a separate process.
    """
    try:
        analyzer = FileStructureAnalyzer(filepath, config)
        report = analyzer.analyze(result_queue)
        stats = analyzer.stats
        result_queue.put({"type": "analysis_report", "report": report, "stats": stats})
    except Exception as e:
        log.error(f"Analysis worker failed: {e}", exc_info=True)
        result_queue.put({"type": "error", "message": f"Analysis worker failed: {e}"})


class AppController:
    """The main controller of the application."""

    def __init__(self, config_manager: ConfigManager):
        self.config_manager = config_manager
        self.view: Optional[tk.Tk] = None
        self.is_processing = False
        self.worker_process: Optional[mp.Process] = None
        self.command_queue = mp.Queue()
        self.result_queue = mp.Queue()
        
        self.export_manager = ExportManager(self.result_queue)
        self.flight_analyzer = FlightAnalyzer(config_manager, self.result_queue)
        self.segment_processor: Optional[SegmentProcessor] = None
        
        self.active_export_profiles: Dict[str, ExportProfile] = {}
        self.output_file_handles: Dict[str, Any] = {}
        self.csv_writers: Dict[str, Any] = {}
        self.json_data_buffer: List[Dict[str, Any]] = []

        self.batch_buffer = OrderedDict()
        self.last_written_batch_counter = -1
        self.total_batches_found_count = 0
        
        self.last_generated_out_file: Optional[Path] = None
        self.last_flight_summary_df: Optional["pd.DataFrame"] = None
        
        self.total_files_for_analysis: int = 0
        self.total_segments_for_export: int = 0

    def bind_view(self, view: Any):
        self.view = view
        self._load_initial_config()
        self.export_manager.start()
        self.segment_processor = SegmentProcessor(self)


    def shutdown(self):
        log.info("Controller shutting down.")
        self.export_manager.stop()
        if self.is_processing:
            self.stop_processing()
        
        if self.worker_process and self.worker_process.is_alive():
            log.info(f"Shutting down worker process PID: {self.worker_process.pid}")
            self.worker_process.join(timeout=1.0)
            if self.worker_process.is_alive():
                self.worker_process.terminate()
                
        self._close_all_files()
        logger.shutdown_logging_system()

    def _load_initial_config(self):
        if last_file := self.config_manager.get("last_opened_out_file"):
            if Path(last_file).is_file(): self.view.out_filepath_var.set(last_file)
        self.on_out_config_changed()
        if last_dir := self.config_manager.get("last_out_output_dir"):
            self.view.out_output_dir_var.set(last_dir)
        
        self.view.out_debug_dump_var.set(self.config_manager.get("enable_structure_debugging", False))

        if last_file := self.config_manager.get("last_opened_rec_file"):
            if Path(last_file).is_file(): self.view.rec_filepath_var.set(last_file)
        self.on_rec_config_changed()
        if last_dir := self.config_manager.get("last_rec_output_dir"):
            self.view.rec_output_dir_var.set(last_dir)
            
        if last_workspace_parent := self.config_manager.get("last_flight_workspace_parent_dir"):
            self.view.analyzer_workspace_dir_var.set(last_workspace_parent)
            
        profiles = self.config_manager.get_export_profiles()
        self.view.update_export_profiles(
            profiles=profiles, 
            active_out_profile=self.config_manager.get("active_out_export_profile_name")
        )
        
        sp_config = self.config_manager.get("segment_processor_config", {})
        self.view.segment_processor_tab.output_dir_var.set(sp_config.get("last_output_dir", ""))
        self.view.segment_processor_tab.create_separate_folders_var.set(sp_config.get("create_separate_folders", True))
        
        if last_analysis_dir := sp_config.get("last_flight_analysis_dir"):
            self.view.segment_processor_tab.flight_analysis_dir_var.set(last_analysis_dir)
        
        fa_options = self.config_manager.get("flight_analysis_options", {})
        self.view.aggregate_by_scale_var.set(fa_options.get("aggregate_by_scale", True))
        self.view.aggregate_by_waveform_var.set(fa_options.get("aggregate_by_waveform", True))

    def on_out_config_changed(self, *args):
        filepath_str = self.view.out_filepath_var.get()
        if not filepath_str: return
        p = Path(filepath_str)
        if not self.view.out_output_dir_var.get(): self.view.out_output_dir_var.set(str(p.parent))
        self.view.out_basename_var.set(p.stem)

    def on_rec_config_changed(self, *args):
        try:
            file_count = self.view.rec_file_count_var.get()
        except (tk.TclError, ValueError): return
        filepath_str = self.view.rec_filepath_var.get()
        if not filepath_str: return
        p = Path(filepath_str)
        if not self.view.rec_output_dir_var.get(): self.view.rec_output_dir_var.set(str(p.parent))
        base_stem = Path(p.stem).stem
        new_stem = f"{base_stem}-n{file_count}"
        self.view.rec_basename_var.set(new_stem)

    def select_output_dir(self, dir_var: tk.StringVar):
        initial_dir = dir_var.get() if dir_var.get() else None
        if new_dir := filedialog.askdirectory(initialdir=initial_dir, title="Select Output Directory"):
            dir_var.set(new_dir)

    def _select_file(self, path_var: tk.StringVar, file_types: List[Tuple[str, str]]):
        initial_dir = (Path(path_var.get()).parent if path_var.get() and Path(path_var.get()).exists() else None)
        if filepath := filedialog.askopenfilename(initialdir=initial_dir, filetypes=file_types):
            path_var.set(filepath)

    def select_out_file(self):
        self._select_file(self.view.out_filepath_var, [("Radar Output", "*.out"), ("All files", "*.*")])

    def select_rec_file(self):
        self._select_file(self.view.rec_filepath_var, [("Recorder Data", "*.rec"), ("All files", "*.*")])

    def _prepare_out_processor_files(self, output_dir: Path, basename: str, options: Dict[str, Any]) -> bool:
        self.output_file_handles.clear()
        self.csv_writers.clear()
        self.active_export_profiles.clear()
        self.json_data_buffer.clear()
        self.batch_buffer.clear()
        self.last_written_batch_counter = -1
        self.total_batches_found_count = 0
        
        try:
            profiles = self.config_manager.get_export_profiles()
            use_full_path = options.get("use_full_path_headers", False)
            
            if options.get("generate_csv", False):
                profile_name = options.get("csv_profile_name")
                profile = next((p for p in profiles if p.name == profile_name), None)
                if not profile: raise ValueError(f"CSV profile '{profile_name}' not found.")
                self.active_export_profiles["csv"] = profile
                
                csv_filename = f"{basename}_{profile.name}.csv"
                path = output_dir / csv_filename
                delimiter = "\t" if options.get("csv_use_tab", False) else ","
                
                fh = open(path, "w", encoding="utf-8", newline="")
                header = [field.data_path if use_full_path else field.column_name for field in profile.fields]
                csv_writer = csv.writer(fh, delimiter=delimiter)
                csv_writer.writerow(header)
                self.output_file_handles["csv"] = fh
                self.csv_writers["csv"] = csv_writer

            if options.get("generate_json", False):
                profile_name = options.get("json_profile_name")
                profile = next((p for p in profiles if p.name == profile_name), None)
                if not profile: raise ValueError(f"JSON profile '{profile_name}' not found.")
                self.active_export_profiles["json"] = profile
            
            return True
        except (IOError, ValueError) as e:
            log.error(f"Failed to prepare output files: {e}")
            self._close_all_files()
            return False

    def start_out_processing(self):
        if self.is_processing:
            log.warning("Processing already in progress.")
            return

        filepath_str = self.view.out_filepath_var.get()
        if not filepath_str:
            messagebox.showerror("Input Error", "Please select an input .out file.", parent=self.view)
            return

        thread = threading.Thread(target=self._run_single_out_processing_flow, daemon=True)
        thread.start()

    def _run_single_out_processing_flow(self):
        """Orchestrates the processing for the 'Single OUT Processor' tab."""
        self.is_processing = True
        self.view.start_processing_ui()

        filepath = Path(self.view.out_filepath_var.get())
        output_dir = Path(self.view.out_output_dir_var.get())
        basename = self.view.out_basename_var.get()
        
        options = {
            "generate_csv": self.view.out_output_csv_var.get(),
            "csv_profile_name": self.view.out_csv_profile_var.get(),
            "csv_use_tab": self.view.out_csv_use_tab_var.get(),
            "generate_json": self.view.out_output_json_var.get(),
            "json_profile_name": self.view.out_json_profile_var.get(),
            "use_full_path_headers": self.view.out_use_full_path_var.get(),
            "analysis_only": self.view.out_analysis_only_var.get(),
            "debug_json_dump": self.view.out_debug_dump_var.get()
        }
        
        self.config_manager.set("last_opened_out_file", str(filepath))
        self.config_manager.set("last_out_output_dir", str(output_dir))
        self.config_manager.set("active_out_export_profile_name", options["csv_profile_name"])
        self.config_manager.set("enable_structure_debugging", options["debug_json_dump"])
        self.config_manager.save_config()

        success = self.process_single_out_file(filepath, output_dir, basename, options)
        
        if success:
            log.info(f"Successfully processed {filepath.name}.")
        else:
            log.error(f"Failed to process {filepath.name}.")
            
        self.is_processing = False

    def process_single_out_file(self, filepath: Path, output_dir: Path, basename: str, options: Dict[str, Any]) -> bool:
        """
        Core blocking function to process a single .out file.
        This is now the central point for OUT processing logic.
        """
        if options.get("analysis_only", False):
            log.info(f"Starting file structure analysis for {filepath.name}...")
            analysis_config = self.config_manager.get("file_analyzer_config", {})
            worker_args = (filepath, analysis_config, self.result_queue)
            self._launch_worker(run_analysis_worker, worker_args)
        
        else:
            if not options.get("generate_csv") and not options.get("generate_json"):
                log.error("No output format selected. Aborting.")
                return False
                
            if not self._prepare_out_processor_files(output_dir, basename, options):
                return False

            active_profile = self.active_export_profiles.get("csv") or self.active_export_profiles.get("json")
            if not active_profile:
                log.error("No active export profile found for processing.")
                return False
            
            cpp_config = self.config_manager.get_cpp_converter_config()
            enable_profiling = cpp_config.get("enable_python_worker_profiling", False)
            debug_json_dump = options.get("debug_json_dump", False)
            
            worker_args = (filepath, self.command_queue, self.result_queue, active_profile, enable_profiling, debug_json_dump)
            self._launch_worker(run_worker_process, worker_args)
        
        while self.is_processing:
            time.sleep(0.1) 

        return True

    def _build_cpp_command_list(self) -> List[str]:
        config = self.config_manager.get_cpp_converter_config()
        exe_path = config.get("cpp_executable_path")
        if not exe_path or not Path(exe_path).is_file(): raise ValueError("g_reconvert.exe path is not set or is invalid.")
        rec_file, output_dir, out_basename = self.view.rec_filepath_var.get(), self.view.rec_output_dir_var.get(), self.view.rec_basename_var.get()
        if not all([rec_file, output_dir, out_basename]): raise ValueError("Missing required paths for C++ converter (REC file or Output).")
        output_file_path = Path(output_dir) / f"{out_basename}.out"
        self.last_generated_out_file = output_file_path
        command = [exe_path, rec_file, f"/o={str(output_file_path)}", f"/n={self.view.rec_file_count_var.get()}"]
        if config.get("post_process"): command.append(f"/p={config.get('post_process_level', '1')}")
        if config.get("video_show"): command.append("/vshow")
        if config.get("video_save"): command.append("/vsave")
        if config.get("gps_save_track"): command.append("/gps")
        if config.get("silent_overwrite"): command.append("//o")
        log.info(f"Assembled C++ command: {' '.join(command)}")
        return command

    def start_rec_conversion(self):
        if self.is_processing: log.warning("A process is already running."); return
        try:
            command_list, output_dir = self._build_cpp_command_list(), self.view.rec_output_dir_var.get()
        except ValueError as e:
            log.error(f"Configuration error: {e}"); messagebox.showerror("Configuration Error", str(e), parent=self.view); return
        self.is_processing = True
        self.view.start_processing_ui()
        worker_args = (command_list, self.result_queue, output_dir)
        self._launch_worker(run_cpp_converter, worker_args)

    def _launch_worker(self, target_func, args_tuple: Tuple):
        while not self.command_queue.empty(): self.command_queue.get_nowait()
        while not self.result_queue.empty(): self.result_queue.get_nowait()
        
        self.is_processing = True 
        
        self.worker_process = mp.Process(target=target_func, args=args_tuple, daemon=True)
        self.worker_process.start()
        
        if self.view:
            self.view.after(100, self.view.poll_result_queue)
        
    def stop_processing(self):
        if not self.is_processing: return
        log.info("Stop requested by user.")
        if self.worker_process and self.worker_process.is_alive():
            self.command_queue.put("STOP")
        if self.segment_processor:
            self.segment_processor.stop()

    def _close_all_files(self):
        self._flush_batch_buffer(force_write_all=True)
        for fh in self.output_file_handles.values():
            try: fh.close() 
            except Exception as e: log.error(f"Error closing file handle {fh.name}: {e}")
        self.output_file_handles.clear()
        self.csv_writers.clear()

    def _write_single_batch_to_files(self, batch: DataBatch):
        if not batch: return

        self.total_batches_found_count += 1
        use_full_path = self.view.out_use_full_path_var.get()
        
        batch.batch_id = self.total_batches_found_count

        if "csv" in self.csv_writers:
            profile = self.active_export_profiles["csv"]
            row_values = []
            for field in profile.fields:
                value = _get_value_from_path(batch, field)
                if isinstance(value, (ctypes.Structure, ctypes.Array)):
                    value = f"[{type(value).__name__}]"
                row_values.append(value)
            self.csv_writers["csv"].writerow(row_values)

        if "json" in self.active_export_profiles:
            profile = self.active_export_profiles["json"]
            row_dict = {}
            for field in profile.fields:
                raw_value = _get_value_from_path(batch, field)
                serializable_value = convert_ctypes_to_dict(raw_value)
                key = field.data_path if use_full_path else field.column_name
                row_dict[key] = serializable_value
            self.json_data_buffer.append(row_dict)

        if self.total_batches_found_count % 20 == 0 and "csv" in self.output_file_handles:
            self.output_file_handles["csv"].flush()

    def _flush_batch_buffer(self, new_batch_counter: Optional[int] = None, force_write_all: bool = False):
        if force_write_all:
            for counter, batch_to_write in self.batch_buffer.items():
                self._write_single_batch_to_files(batch_to_write)
            self.batch_buffer.clear()
            return

        if new_batch_counter is None: return

        completed_batches = []
        for counter, batch_in_buffer in self.batch_buffer.items():
            if counter < new_batch_counter:
                self._write_single_batch_to_files(batch_in_buffer)
                completed_batches.append(counter)
        
        for counter in completed_batches:
            del self.batch_buffer[counter]

    def handle_data_batch_fragment(self, fragment: DataBatch):
        header = fragment.main_header
        if not header or not header.ge_header:
            log.warning("Received a batch fragment without a valid header, skipping.")
            return

        current_counter = header.ge_header.signal.bacth_counter
        
        if current_counter < self.last_written_batch_counter:
             self._flush_batch_buffer(force_write_all=True)

        self.last_written_batch_counter = current_counter
        self._flush_batch_buffer(new_batch_counter=current_counter)

        if current_counter not in self.batch_buffer:
            self.batch_buffer[current_counter] = fragment
        else:
            existing_batch = self.batch_buffer[current_counter]
            existing_batch.blocks.extend(fragment.blocks)
            if fragment.cdp_sts_results: existing_batch.cdp_sts_results = fragment.cdp_sts_results
            if fragment.timer_data: existing_batch.timer_data = fragment.timer_data
            if fragment.aesa_data: existing_batch.aesa_data = fragment.aesa_data
            if fragment.d1553_data: existing_batch.d1553_data = fragment.d1553_data
            if fragment.stt_data: existing_batch.stt_data = fragment.stt_data

    def _write_json_buffer_to_file(self):
        if not self.json_data_buffer:
            if "json" in self.active_export_profiles: log.info("JSON export enabled, but no data. Skipping file creation.")
            return
        try:
            profile = self.active_export_profiles["json"]
            output_dir, basename = Path(self.view.out_output_dir_var.get()), self.view.out_basename_var.get()
            json_filename = f"{basename}_{profile.name}.json"
            path = output_dir / json_filename
            log.info(f"Writing {len(self.json_data_buffer)} records to JSON file: {path}")
            with open(path, "w", encoding="utf-8") as f: json.dump(self.json_data_buffer, f, indent=4)
            log.info("JSON file written successfully.")
        except (IOError, TypeError) as e: log.error(f"Failed to write JSON output file: {e}")
        finally: self.json_data_buffer.clear()

    def open_folder_from_path(self, folder_path_str: str):
        if not folder_path_str: return
        folder_path = Path(folder_path_str)
        if not folder_path.is_dir(): log.error(f"Directory '{folder_path}' does not exist."); return
        log.info(f"Opening folder: {folder_path}")
        try:
            if sys.platform == "win32": os.startfile(folder_path)
            elif sys.platform == "darwin": subprocess.run(["open", folder_path])
            else: subprocess.run(["xdg-open", folder_path])
        except Exception as e: log.error(f"Failed to open folder: {e}")

    def handle_worker_completion(self, msg: Dict[str, Any]):
        status = "Interrupted" if msg.get("interrupted") else "Complete"
        log.info(f"--- Process {status}. ---")
        
        self._flush_batch_buffer(force_write_all=True)
        
        if self.view.out_output_json_var.get(): self._write_json_buffer_to_file()
        self._close_all_files()
        
        self.is_processing = False
        self.worker_process = None
        self.view.update_ui_for_processing_state(False)
        is_cpp_success = "Conversion process completed successfully" in msg.get("message", "")
        self.view.update_rec_tab_buttons_state(conversion_successful=is_cpp_success)
        if is_cpp_success: log.info(f"C++ converter successfully generated: {self.last_generated_out_file}")
        
        if stats := msg.get("stats"):
            stats['total_batches_aggregated'] = self.total_batches_found_count
            self._log_summary(stats)

    def process_last_generated_out(self):
        if self.last_generated_out_file and self.last_generated_out_file.exists():
            log.info(f"Loading generated file '{self.last_generated_out_file.name}' into OUT Processor.")
            self.view.out_filepath_var.set(str(self.last_generated_out_file))
            self.view.notebook.select(self.view.out_processor_tab)
        else:
            log.warning("No recently generated .out file to process.")

    def _log_summary(self, stats: Dict[str, int]):
        log.info("--- .out File Processing Summary ---")
        log.info(f"Total Blocks Scanned: {stats.get('total_blocks_scanned', 0)}")
        log.info(f"Total Batches Aggregated: {stats.get('total_batches_aggregated', 0)}")
        block_types = sorted([k for k in stats if k.startswith("found_")])
        if block_types:
            log.info("Block Types Found:")
            for key in block_types: log.info(f"  - {key.replace('found_', '')}: {stats[key]}")
        if failed := stats.get("failed_to_parse_blocks"): log.error(f"Failed to Parse Blocks: {failed}")
        log.info("------------------------------------")

    def open_profile_editor(self):
        if self.view.profile_editor_window and self.view.profile_editor_window.winfo_exists():
            self.view.profile_editor_window.lift(); return
        profiles = self.config_manager.get_export_profiles()
        self.view.profile_editor_window = ProfileEditorWindow(master=self.view.master, controller=self, profiles=profiles)
        self.view.profile_editor_window.wait_window()
        self._load_initial_config()
        
    def open_rec_config_editor(self):
        if self.view.rec_config_window and self.view.rec_config_window.winfo_exists():
            self.view.rec_config_window.lift(); return
        current_config = self.config_manager.get_cpp_converter_config()
        self.view.rec_config_window = RecConfigWindow(
            master=self.view.master, controller=self, current_config=current_config,
            title="g_reconverter Config (REC to OUT)",
            save_command=self.save_rec_config
        )
        self.view.rec_config_window.wait_window()
        
    def open_export_config_editor(self):
        if self.view.export_config_window and self.view.export_config_window.winfo_exists():
            self.view.export_config_window.lift(); return
        current_config = self.config_manager.get("segment_export_config", {})
        self.view.export_config_window = SegmentExportConfigWindow(
            master=self.view.master, controller=self, current_config=current_config
        )
        self.view.export_config_window.wait_window()

    def save_rec_config(self, new_config: Dict[str, Any]):
        self.config_manager.save_cpp_converter_config(new_config)
        log.info("g_reconverter configuration saved.")
        
    def save_segment_export_config(self, new_config: Dict[str, Any]):
        full_config = self.config_manager.get("segment_export_config", {})
        full_config.update(new_config)
        main_cpp_config = self.config_manager.get_cpp_converter_config()
        if "g_reconverter_options" in full_config:
            full_config["g_reconverter_options"]["cpp_executable_path"] = main_cpp_config.get("cpp_executable_path")
        self.config_manager.set("segment_export_config", full_config)
        self.config_manager.save_config()
        log.info("Segment export configuration saved.")

    def save_export_profiles(self, profiles: List[ExportProfile]):
        self.config_manager.save_export_profiles(profiles)
        
    def open_current_flight_folder(self):
        if self.flight_analyzer.current_flight_folder_path and self.flight_analyzer.current_flight_folder_path.is_dir():
            self.open_folder_from_path(str(self.flight_analyzer.current_flight_folder_path))
        else:
            log.warning("No flight folder available. Please run an analysis first.")
            messagebox.showinfo("No Folder", "No flight folder has been created yet.", parent=self.view)

    def select_and_analyze_flight_folder(self):
        initial_dir = self.config_manager.get("last_flight_folder")
        new_dir_str = filedialog.askdirectory(initialdir=initial_dir, title="Select Folder with Flight Recordings")
        if not new_dir_str:
            return

        new_dir = Path(new_dir_str)
        self.config_manager.set("last_flight_folder", new_dir_str)
        self.view.analyzer_rec_folder_var.set(new_dir_str)

        flight_name = self._generate_flight_name(new_dir)
        self.view.analyzer_flight_name_var.set(flight_name)
        
        if not self.view.analyzer_workspace_dir_var.get():
            self.view.analyzer_workspace_dir_var.set(str(Path.cwd() / "flight_workspace"))

        workspace_parent_dir = Path(self.view.analyzer_workspace_dir_var.get())
        flight_dir = workspace_parent_dir / flight_name
        
        summary_csv = flight_dir / "flight_summary.csv"

        if summary_csv.is_file():
            response = messagebox.askyesno(
                "Previous Analysis Found",
                f"An analysis for flight '{flight_name}' already exists in the specified workspace.\n\nDo you want to load the previous results?",
                parent=self.view
            )
            if response:
                log.info(f"Loading previous analysis from {flight_dir}")
                self.flight_analyzer.current_flight_folder_path = flight_dir
                self._load_previous_analysis(summary_csv)
                return

        self.view.analyzer_info_var.set("Scanning folder, please wait...")
        self.view.start_analysis_button.config(state=tk.DISABLED)
        self.view.update_idletasks()
        
        try:
            rec_files = sorted(list(new_dir.glob("*.rec")))
            if not rec_files:
                self.view.analyzer_info_var.set("No .rec files found."); return
            
            self.total_files_for_analysis = len(rec_files)
            total_size_mb = sum(f.stat().st_size for f in rec_files) / (1024 * 1024)
            info_text = (f"Found {self.total_files_for_analysis} .rec files, Total size: {total_size_mb:.2f} MB. Ready for analysis.")
            
            self.view.analyzer_info_var.set(info_text)
            self.view.start_analysis_button.config(state=tk.NORMAL)
        except Exception as e:
            log.error(f"Error in folder pre-analysis: {e}", exc_info=True)
            self.view.analyzer_info_var.set(f"Error: {e}")

    def _generate_flight_name(self, folder_path: Path) -> str:
        rec_files = sorted([f for f in folder_path.glob("*.rec")])
        if not rec_files: return f"{folder_path.name}_Flight"
        first_file_name = rec_files[0].stem
        match = re.search(r"(\d{{2}})-(\d{{2}})-(\d{{2}})-(\d{{2}})-(\d{{2}})-(\d{{2}})", first_file_name)
        if match:
            yy, mo, dd, hh, mi, ss = match.groups()
            return f"{yy}{mo}{dd}_{hh}{mi}{ss}_Flight"
        return f"{folder_path.name}_Flight"

    def _load_previous_analysis(self, summary_csv_path: Path):
        if pd is None:
            log.error("Cannot load previous analysis: Pandas is not installed."); return
        try:
            summary_df = pd.read_csv(summary_csv_path)
            self.last_flight_summary_df = summary_df
            self.view.populate_timeline_from_dataframe(summary_df)
            self.view.analyzer_info_var.set("Successfully loaded previous analysis.")
            self.view.open_flight_folder_button.config(state=tk.NORMAL)
            self.view.export_segment_button.config(state=tk.NORMAL)
            self.view.start_analysis_button.config(state=tk.NORMAL)
        except Exception as e:
            log.error(f"Failed to load previous analysis from {summary_csv_path}: {e}")
            messagebox.showerror("Load Error", f"Could not load the summary file:\n{e}", parent=self.view)

    def start_flight_analysis(self):
        if self.is_processing:
            log.warning("Analysis already in progress.")
            return
            
        flight_name = self.view.analyzer_flight_name_var.get().strip()
        rec_folder = self.view.analyzer_rec_folder_var.get()
        workspace_parent_dir_str = self.view.analyzer_workspace_dir_var.get().strip()

        if not all([flight_name, rec_folder, workspace_parent_dir_str]):
            messagebox.showerror("Setup Incomplete", "Select a recordings folder, a workspace directory, and provide a flight name.", parent=self.view)
            return

        self.config_manager.set("last_flight_workspace_parent_dir", workspace_parent_dir_str)
        analysis_options = {
            "aggregate_by_scale": self.view.aggregate_by_scale_var.get(),
            "aggregate_by_waveform": self.view.aggregate_by_waveform_var.get()
        }
        self.config_manager.set("flight_analysis_options", analysis_options)
        self.config_manager.save_config()
            
        self.is_processing = True
        self.view.start_processing_ui()
        
        full_workspace_path = Path(workspace_parent_dir_str) / flight_name
        self.view.analyzer_info_var.set(f"Starting analysis for flight: {flight_name}...")
        
        self.flight_analyzer.start_analysis(rec_folder, flight_name, full_workspace_path, analysis_options)
        self.worker_process = self.flight_analyzer.worker_process
        self.view.poll_result_queue()

    def handle_final_analysis_steps(self):
        self.flight_analyzer.handle_final_analysis_steps()

    def handle_analysis_summary_data(self, msg: Dict[str, Any]):
        summary_df = msg.get("data")
        flight_folder_path_str = str(msg.get("flight_folder_path"))

        if self.view and summary_df is not None:
            self.last_flight_summary_df = summary_df
            self.view.populate_timeline_from_dataframe(summary_df)

        if flight_folder_path_str:
            log.info(f"Analysis summary received for flight folder: {flight_folder_path_str}")
            log.info("Automatically setting flight analysis path in Segment Processor tab.")
            self.view.segment_processor_tab.flight_analysis_dir_var.set(flight_folder_path_str)

    def start_segment_export(self):
        if self.is_processing:
            messagebox.showwarning("Process Busy", "Cannot start export while another process is running.", parent=self.view); return
        
        selected_item_ids = self.view.flight_timeline_tree.selection()
        if not selected_item_ids:
            messagebox.showinfo("No Selection", "Please select one or more segments to export.", parent=self.view); return
            
        if self.last_flight_summary_df is None:
            messagebox.showerror("Error", "Flight summary data is not available. Cannot proceed with export.")
            return

        self.total_segments_for_export = len(selected_item_ids)
        log.info(f"Queuing {self.total_segments_for_export} segment(s) for export.")
        
        while not self.export_manager.job_queue.empty():
            try: self.export_manager.job_queue.get_nowait()
            except queue.Empty: break

        rec_folder = Path(self.view.analyzer_rec_folder_var.get())
        export_config = self.config_manager.get("segment_export_config")
        folder_template = export_config.get("naming_options", {}).get("folder_name_template", "{Segment}_{StartBatch}-{EndBatch}")
        current_flight_path = self.flight_analyzer.current_flight_folder_path
        if not current_flight_path:
            log.error("Cannot start export, flight analysis folder path is unknown.")
            messagebox.showerror("Error", "Could not determine the flight workspace folder.", parent=self.view)
            return

        for iid in selected_item_ids:
            try:
                row_index = self.view.flight_timeline_tree.index(iid)
                segment_info_dict = self.last_flight_summary_df.iloc[row_index].to_dict()
                
                placeholders = {
                    "{Segment}": str(segment_info_dict.get('Segment (Mode | Scale | WF)', '')),
                    "{StartBatch}": str(segment_info_dict.get('Start Batch', '')),
                    "{EndBatch}": str(segment_info_dict.get('End Batch', '')),
                    "{BatchCount}": str(segment_info_dict.get('Batch Count', '')),
                }
                folder_name = folder_template
                for key, value in placeholders.items():
                    folder_name = folder_name.replace(key, value)
                safe_folder_name = re.sub(r'[\\/*?:"<>|]', "-", folder_name)

                segment_folder = current_flight_path / safe_folder_name
                
                job = ExportJob(segment_info_dict, segment_folder, rec_folder, export_config)
                self.export_manager.submit_job(job)

            except (ValueError, IndexError) as e:
                log.error(f"Could not process selection '{iid}': {e}", exc_info=True)
                continue
            
        self.export_manager.submit_job(None)

        self.is_processing = True
        self.view.start_processing_ui()
        self.view.analyzer_info_var.set(f"Exporting {self.total_segments_for_export} segment(s)...")
        
        self.export_manager.start() 
        
        self.view.poll_result_queue()
    
    def load_segments_for_processing(self):
        if self.segment_processor:
            self.segment_processor.load_segments_for_processing()
        
    def start_segment_batch_processing(self):
        if self.segment_processor:
            self.segment_processor.start_batch_processing()
        
    def _save_analysis_report(self, report_data: Dict[str, Any]) -> Optional[Path]:
        """Saves the detailed analysis report and returns its path."""
        report_list = report_data.get("report", [])
        stats = report_data.get("stats", {})
        block_counts = stats.get("block_type_counts", {})
        
        try:
            output_dir = Path(self.view.out_output_dir_var.get())
            basename = self.view.out_basename_var.get()
            report_path = output_dir / f"{basename}_analysis_report.txt"
        except Exception:
            log.error("Could not determine output path for analysis report.")
            return None

        log.info(f"Saving analysis report to: {report_path}")
        try:
            output_dir.mkdir(parents=True, exist_ok=True)
            with open(report_path, "w", encoding="utf-8") as f:
                f.write(f"--- Analysis Report for file: {self.view.out_filepath_var.get()} ---\n\n")
                
                f.write("--- Summary ---")
                for key, value in stats.items():
                    if key != "block_type_counts":
                        f.write(f"{key.replace('_', ' ').title()}: {value}\n")
                f.write("\n")

                if block_counts:
                    f.write("--- Block Type Summary ---")
                    sorted_blocks = sorted(block_counts.items(), key=lambda item: item[1], reverse=True)
                    for name, count in sorted_blocks:
                        f.write(f"{name:<15}: {count}\n")
                    f.write("\n")

                f.write("--- Block Sequence Log ---")
                for entry in report_list:
                    offset = entry.get('offset', 'N/A')
                    msg_type = entry.get('type', 'INFO').upper()
                    
                    if msg_type == "BLOCK":
                        name = entry.get('name', 'N/A')
                        size = entry.get('size_bytes', 'N/A') 
                        line = f"[{offset}] {msg_type:<8} | Name: {name:<15} | Declared Payload: {size} bytes"
                        
                        if (batch_counter := entry.get('batch_counter')) is not None:
                            line += f" | Batch Counter: {batch_counter}"

                        mode_info = []
                        if (master_mode := entry.get('master_mode')) is not None:
                            mode_info.append(f"Master: {get_enum_name(ENUM_REGISTRY.get('main_header.ge_header.mode.master_mode'), master_mode)}")
                        if (op_mode := entry.get('operation_mode')) is not None:
                            mode_info.append(f"Op: {get_enum_name(ENUM_REGISTRY.get('main_header.ge_header.mode.operation_mode'), op_mode)}")
                        if (waveform := entry.get('waveform')) is not None:
                            mode_info.append(f"WF: {waveform}")
                        if (rng_scale := entry.get('range_scale')) is not None:
                            mode_info.append(f"Scale: {get_enum_name(ENUM_REGISTRY.get('main_header.ge_header.mode.range_scale'), rng_scale)}")
                        
                        if mode_info:
                            line += f" | {', '.join(mode_info)}"

                        f.write(line + "\n")
                    else:
                        message = entry.get('message', '')
                        f.write(f"[{offset}] {msg_type:<8} | {message}\n")
            log.info("Analysis report saved successfully.")
            return report_path
        except IOError as e: log.error(f"Failed to save analysis report: {e}")
        return None

    def handle_analysis_report(self, msg: Dict[str, Any]):
        """Handles the completion of the analysis worker."""
        log.info("--- File Structure Analysis Complete. ---")
        
        report_path = self._save_analysis_report(msg)
        
        self.is_processing = False
        self.worker_process = None
        self.view.update_ui_for_processing_state(False)

        if self.view and report_path:
            response = messagebox.showinfo(
                "Analysis Complete",
                f"Structure analysis report has been saved to:\n\n{report_path}\n\nClick OK to open the output folder.",
                parent=self.view
            )
            if response == "ok":
                self.open_folder_from_path(str(report_path.parent))
        
    def select_flight_analysis_dir_for_segment_processor(self):
        initial_dir = self.view.segment_processor_tab.flight_analysis_dir_var.get()
        if not initial_dir:
            initial_dir = self.flight_analyzer.current_flight_folder_path

        new_dir_str = filedialog.askdirectory(
            initialdir=initial_dir,
            title="Select Flight Analysis Directory"
        )
        if new_dir_str:
            self.view.segment_processor_tab.flight_analysis_dir_var.set(new_dir_str)
