import os
import re
import json
import threading
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import pythoncom
import win32com.client
import time
from win32com.client import pythoncom as pycom

PROGRESS_LOG = 'export_progress.json'

def safe_open_doc(docs, dwg_path, retries=5, delay=3):
    for attempt in range(retries):
        try:
            return docs.Open(dwg_path)
        except Exception as e:
            if 'Call was rejected by callee' in str(e) and attempt < retries - 1:
                time.sleep(delay)
            else:
                raise

# --- Backend Logic ---
def set_block_attribute(doc, block_name, attr_tag, value):
    # Search PaperSpace for block references
    for entity in doc.PaperSpace:
        if entity.ObjectName == 'AcDbBlockReference' and entity.Name == block_name:
            try:
                for attrib in entity.GetAttributes():
                    if attrib.TagString.upper() == attr_tag.upper():
                        attrib.TextString = value
                        return True
            except Exception:
                continue
    return False

def set_wyncorp_block(doc, chunk, log_operation=None):
    # First try to find UPS.AE 11X17 (V0) block and set DWGDESC1
    for entity in doc.PaperSpace:
        if entity.ObjectName == 'AcDbBlockReference' and entity.Name == "UPS.AE 11X17 (V0)":
            try:
                value = "IO LAYOUT " + ", ".join(chunk)
                for attrib in entity.GetAttributes():
                    if attrib.TagString.upper() == "DWGDESC1":
                        attrib.TextString = value
                        if log_operation:
                            log_operation(f"Inputting block DWGDESC1: {value}")
                        return True
            except Exception:
                continue
    
    # If UPS.AE block not found, try WYNCORP-DSIZE_AS block and set TITLE2 and TITLE3
    for entity in doc.PaperSpace:
        if entity.ObjectName == 'AcDbBlockReference' and entity.Name == "WYNCORP-DSIZE_AS":
            try:
                title2_val = "/".join(chunk[:2]) if chunk[:2] else ""
                title3_val = "/".join(chunk[2:4]) if chunk[2:4] else ""
                if title3_val:
                    title3_val += " IO DRAWINGS"
                else:
                    title3_val = "IO DRAWINGS"
                for attrib in entity.GetAttributes():
                    if attrib.TagString.upper() == "TITLE2":
                        attrib.TextString = title2_val
                        if log_operation:
                            log_operation(f"Inputting block TITLE2: {title2_val}")
                    if attrib.TagString.upper() == "TITLE3":
                        attrib.TextString = title3_val
                        if log_operation:
                            log_operation(f"Inputting block TITLE3: {title3_val}")
                return True
            except Exception:
                continue
    return False

def transform_dpm_name_for_network(raw_name):
    try:
        import re as _re
        m = _re.search(r"^(.*)_(DPM\d+)$", raw_name, _re.IGNORECASE)
        if m:
            rest = m.group(1)
            dpm = m.group(2).upper()
            return f"{dpm}_{rest}_ENET"
        return f"{raw_name}_ENET"
    except Exception:
        return f"{raw_name}_ENET"

def set_network_block(doc, raw_name, log_operation=None):
    value_title3 = transform_dpm_name_for_network(raw_name)
    # Prefer WYNCORP-DSIZE_AS: TITLE2 = NETWORK DETAILS, TITLE3 = transformed
    for entity in doc.PaperSpace:
        if entity.ObjectName == 'AcDbBlockReference' and entity.Name == "WYNCORP-DSIZE_AS":
            try:
                for attrib in entity.GetAttributes():
                    if attrib.TagString.upper() == "TITLE2":
                        attrib.TextString = "NETWORK DETAILS"
                        if log_operation:
                            log_operation("Inputting block TITLE2: NETWORK DETAILS")
                    if attrib.TagString.upper() == "TITLE3":
                        attrib.TextString = value_title3
                        if log_operation:
                            log_operation(f"Inputting block TITLE3: {value_title3}")
                return True
            except Exception:
                continue
    # Fallback to UPS.AE 11X17 (V0) -> DWGDESC1 (use raw DPM name, no transform)
    for entity in doc.PaperSpace:
        if entity.ObjectName == 'AcDbBlockReference' and entity.Name == "UPS.AE 11X17 (V0)":
            try:
                for attrib in entity.GetAttributes():
                    if attrib.TagString.upper() == "DWGDESC1":
                        fallback_val = f"NETWORK DETAILS {raw_name}"
                        attrib.TextString = fallback_val
                        if log_operation:
                            log_operation(f"Inputting block DWGDESC1: {fallback_val}")
                        return True
            except Exception:
                continue
    return False

def process_single_file(dwg_path, base_dwg_path, layout_template, template_layout_name, index, total, acad, docs, block_names, mode="IO", log_operation=None, error_callback=None):
    new_doc = None
    # Auto-close if already open
    for doc in list(docs):
        try:
            if os.path.abspath(doc.FullName).lower() == os.path.abspath(dwg_path).lower():
                doc.Close(False)
                # time.sleep(2)   
        except Exception:
            continue
    try:
        if log_operation:
            log_operation(f"Opening file: {os.path.basename(dwg_path)}")
        new_doc = safe_open_doc(docs, dwg_path)
        # time.sleep(2)
        # Check if Xref already exists (skip attaching when processing the master file itself)
        is_master_file = os.path.abspath(dwg_path).lower() == os.path.abspath(base_dwg_path).lower()
        xref_exists = False
        if not is_master_file:
            for item in new_doc.ModelSpace:
                if item.ObjectName == "AcDbBlockReference":
                    try:
                        if item.Name == "XREF1":
                            xref_exists = True
                            break
                    except:
                        continue
            if not xref_exists:
                if log_operation:
                    log_operation("Attaching Xref...")
                # Use relative path - just the filename if in same folder
                master_filename = os.path.basename(base_dwg_path)
                insertion_point = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [0, 0, 0])
                new_doc.ModelSpace.AttachExternalReference(
                    master_filename, "XREF1", insertion_point, 1, 1, 1, 0, False)
        # Check if layout exists
        layout_exists = False
        for layout in new_doc.Layouts:
            if layout.Name.upper() == template_layout_name.upper():
                layout_exists = True
                break
        if not layout_exists:
            if log_operation:
                log_operation("Adding template layout...")
            filedia_original = new_doc.GetVariable("FILEDIA")
            new_doc.SetVariable("FILEDIA", 0)
            command_sequence = (
                "_-LAYOUT\n"
                "T\n"
                f'"{layout_template}"\n'
                f"{template_layout_name}\n"
                "\n"
            )
            acad.ActiveDocument.SendCommand(command_sequence)
            # time.sleep(2)
            new_doc.SetVariable("FILEDIA", filedia_original)
        # Activate layout
        new_doc.ActiveLayout = new_doc.Layouts.Item(template_layout_name)
        # time.sleep(0.5)
        # Pan/zoom viewport
        if log_operation:
            log_operation("Zooming to layout area...")
        
        # Set viewport values based on mode
        if mode == "IO":
            base_x = 19.13
            base_y = 0.7881
            block_width = 38.5
            height = 22.2955
        else:  # NETWORK mode
            # NETWORK mode needs different values - working backwards from your feedback
            # For 19 files with total width 826.5: block_width = 826.5/19 = 43.5
            # For 10th file (index 9) to be 369: 369 = base_x + (9 * 43.5)
            # So base_x = 369 - 391.5 = -22.5
            base_x = -22
            base_y = 13.25
            block_width = 43.5
            height = 26.1748
            
        acad.ActiveDocument.SendCommand("MSPACE\n")
        # time.sleep(0.5)
        # For first file (index 0), use base position. For subsequent files, add block_width * index
        center_x = base_x + (index * block_width)
        center_y = base_y
        zoom_command = f'ZOOM C {center_x},{center_y} {height}\n'
        acad.ActiveDocument.SendCommand(zoom_command)
        # time.sleep(1)
        acad.ActiveDocument.SendCommand("PSPACE\n")
        # time.sleep(0.5)
        # --- Set block attribute for this layout ---
        if mode == "IO":
            # IO mode: use chunks of 4 names
            start_idx = index * 4
            chunk = block_names[start_idx:start_idx+4]
            block_set = False
            if chunk:
                value = "IO LAYOUT " + "/".join(chunk)
                if log_operation:
                    log_operation(f"Inputting block names: {value}")
                block_set = set_block_attribute(new_doc, "UPS.AE 11X17 (V0)", "DWGDESC1", value)
            # If not found, try WYNCORP-DSIZE_AS logic
            if not block_set and chunk:
                if log_operation:
                    log_operation(f"Trying WYNCORP-DSIZE_AS block for chunk: {chunk}")
                set_wyncorp_block(new_doc, chunk, log_operation=log_operation)
        else:  # NETWORK mode
            # NETWORK mode: use single name transformed: DPMx_<rest>_ENET,
            # and set TITLE2/TITLE3 on WYNCORP when present; fallback to UPS.AE
            if block_names and index < len(block_names):
                raw_name = block_names[index]
                if log_operation:
                    log_operation(f"Preparing NETWORK labels for: {raw_name}")
                set_network_block(new_doc, raw_name, log_operation=log_operation)
        # Delete default layouts
        if log_operation:
            log_operation("Deleting default layouts (Layout1, Layout2)...")
        for layout_name in ["Layout1", "Layout2"]:
            delete_command = f"_-LAYOUT\nD\n{layout_name}\n"
            acad.ActiveDocument.SendCommand(delete_command)
            time.sleep(1)
        # Save and restore FILEDIA, then close
        if log_operation:
            log_operation("Saving file and restoring FILEDIA...")
        new_doc.Save()
        try:
            new_doc.SetVariable("FILEDIA", 1)
        except Exception:
            pass
    except Exception as e:
        if error_callback:
            error_callback(str(e))
        raise
    finally:
        try:
            if new_doc is not None:
                new_doc.Close(False)
                # time.sleep(2)
        except Exception:
            pass
    return True

# --- Progress Log ---
def load_progress():
    if os.path.exists(PROGRESS_LOG):
        with open(PROGRESS_LOG, 'r') as f:
            return set(json.load(f))
    return set()

def save_progress(done_files):
    with open(PROGRESS_LOG, 'w') as f:
        json.dump(list(done_files), f)

def is_autocad_running():
    try:
        win32com.client.GetActiveObject("AutoCAD.Application")
        return True
    except Exception:
        return False

def read_block_names(names_file):
    if not names_file or not os.path.exists(names_file):
        return []
    with open(names_file, 'r', encoding='utf-8') as f:
        return [line.strip() for line in f if line.strip()]

def delete_bak_for_dwg(dwg_path, log_operation=None, error_callback=None):
    bak_path = os.path.splitext(dwg_path)[0] + '.bak'
    try:
        if os.path.exists(bak_path):
            os.remove(bak_path)
            if log_operation:
                log_operation(f"Deleted backup file: {os.path.basename(bak_path)}")
    except Exception as e:
        if error_callback:
            error_callback(f"Failed to delete backup file {os.path.basename(bak_path)}: {e}")

def delete_all_bak_files(root_dir, log_operation=None, error_callback=None):
    deleted_count = 0
    try:
        for current_root, _, files in os.walk(root_dir):
            for file_name in files:
                if file_name.lower().endswith('.bak'):
                    bak_path = os.path.join(current_root, file_name)
                    try:
                        os.remove(bak_path)
                        deleted_count += 1
                    except Exception as e:
                        if error_callback:
                            error_callback(f"Failed to delete backup file {file_name}: {e}")
        if log_operation:
            log_operation(f"Deleted {deleted_count} remaining .bak file(s) after full completion.")
    except Exception as e:
        if error_callback:
            error_callback(f"Failed during final backup cleanup: {e}")

# --- UI ---
def main():
    root = tk.Tk()
    root.title("Export Layouts Batch Processor")
    root.geometry("750x750")
    root.configure(bg="#181818")
    root.resizable(False, False)

    # Modern font
    FONT = ("Segoe UI", 11)
    FONT_BOLD = ("Segoe UI", 12, "bold")
    GOLD = "#FFD700"
    BLACK = "#181818"
    WHITE = "#FFFFFF"

    style = ttk.Style()
    style.theme_use('clam')
    style.configure("TButton", font=FONT, background=GOLD, foreground=BLACK, borderwidth=0, focusthickness=3, focuscolor=GOLD, padding=8)
    style.map("TButton", background=[('active', GOLD), ('pressed', GOLD)])
    style.configure("TEntry", fieldbackground=BLACK, foreground=GOLD, borderwidth=1)
    style.configure("TLabel", background=BLACK, foreground=GOLD, font=FONT)
    style.configure("TProgressbar", troughcolor=BLACK, bordercolor=BLACK, background=GOLD, lightcolor=GOLD, darkcolor=GOLD, thickness=20)

    master_path = tk.StringVar()
    template_path = tk.StringVar()
    drawings_files = []  # List of selected DWG files
    drawings_files_var = tk.StringVar(value="No drawings selected.")
    progress_var = tk.IntVar(value=0)
    status_var = tk.StringVar(value="Idle.")
    current_file_var = tk.StringVar(value="No file processing yet.")
    error_log_var = tk.StringVar(value="")
    names_file = tk.StringVar()
    block_names = []

    # --- Logging variables ---
    operations_log_var = tk.StringVar(value="")
    error_log_var = tk.StringVar(value="")

    def log_operation(msg):
        prev = operations_log_var.get()
        operations_log_var.set((prev + "\n" if prev else "") + msg)

    def append_error(msg):
        prev = error_log_var.get()
        error_log_var.set((prev + "\n" if prev else "") + msg)

    def select_master():
        path = filedialog.askopenfilename(title="Select Master DWG", filetypes=[("DWG Files", "*.dwg")])
        if path:
            master_path.set(path)

    def select_template():
        path = filedialog.askopenfilename(title="Select Template DWG", filetypes=[("DWG Files", "*.dwg")])
        if path:
            template_path.set(path)

    def select_drawings_files():
        nonlocal drawings_files
        files = filedialog.askopenfilenames(title="Select Drawing Files", filetypes=[("DWG Files", "*.dwg")])
        if files:
            drawings_files = list(files)
            drawings_files_var.set(f"{len(drawings_files)} files selected.")
        else:
            drawings_files = []
            drawings_files_var.set("No drawings selected.")

    def select_names_file():
        path = filedialog.askopenfilename(title="Select Block Names File", filetypes=[("Text Files", "*.txt")])
        if path:
            names_file.set(path)

    cancel_requested = tk.BooleanVar(value=False)

    def set_buttons_state(state):
        # IO tab buttons
        try:
            run_btn.config(state=state)
            start_over_btn.config(state=state)
            cancel_btn.config(state="normal" if state == "disabled" else "disabled")
        except Exception:
            pass
        # NETWORK tab buttons
        try:
            n_run_btn.config(state=state)
            n_start_over_btn.config(state=state)
            n_cancel_btn.config(state="normal" if state == "disabled" else "disabled")
        except Exception:
            pass

    def start_batch():
        nonlocal block_names
        if not master_path.get() or not template_path.get() or not drawings_files or not names_file.get():
            messagebox.showerror("Error", "Please select master, template DWG files, drawing files, and block names file.")
            return
        block_names = read_block_names(names_file.get())
        if not block_names:
            messagebox.showerror("Error", "Block names file is empty or not found.")
            return
        
        # Determine which tab is active
        current_tab = main_notebook.select()
        if "IO" in main_notebook.tab(current_tab, "text"):
            mode = "IO"
        else:
            mode = "NETWORK"
            
        progress_var.set(0)
        status_var.set("Processing...")
        current_file_var.set("")
        operations_log_var.set("")
        error_log_var.set("")
        cancel_requested.set(False)
        set_buttons_state("disabled")
        threading.Thread(target=lambda: run_batch(mode), daemon=True).start()

    def start_over():
        if os.path.exists(PROGRESS_LOG):
            os.remove(PROGRESS_LOG)
        progress_var.set(0)
        status_var.set("Progress reset. Ready.")
        current_file_var.set("")
        error_log_var.set("")
        operations_log_var.set("")

    def cancel_batch():
        cancel_requested.set(True)
        status_var.set("Cancelling... (will stop after current file)")

    def run_batch(mode="IO"):
        try:
            # Build processing order: master first, then sorted drawings (excluding master if present)
            master = master_path.get()
            drawings = [f for f in list(drawings_files) if os.path.abspath(f).lower() != os.path.abspath(master).lower()]
            def sort_key(file_name):
                match = re.search(r'LAYOUT\s*(\d+)', os.path.basename(file_name), re.IGNORECASE)
                if match:
                    return int(match.group(1))
                return float('inf')
            drawings.sort(key=sort_key)
            files = [master] + drawings
            total = len(files)
            done_files = load_progress()
            pythoncom.CoInitialize()
            acad = win32com.client.Dispatch("AutoCAD.Application")
            docs = acad.Documents
            
            template_doc = docs.Open(template_path.get())
            time.sleep(2)  # Give AutoCAD time to fully load the template
            template_layout_name = None
            try:
                for layout in template_doc.Layouts:
                    if layout.Name.upper() != "MODEL":
                        template_layout_name = layout.Name
                        break
            except Exception as e:
                append_error(f"Error reading template layouts: {e}")
                template_layout_name = "TITLE PAGE"  # Fallback to default name
            finally:
                try:
                    template_doc.Close(False)
                except Exception:
                    pass
            if not template_layout_name:
                status_var.set("No layout found in template file!")
                pythoncom.CoUninitialize()
                set_buttons_state("normal")
                return
            idx = 0
            while idx < total:
                if cancel_requested.get():
                    status_var.set("Batch cancelled by user.")
                    break
                fname = files[idx]
                base_fname = os.path.basename(fname)
                if base_fname in done_files:
                    progress_var.set(int(100 * (idx + 1) / total))
                    idx += 1
                    continue
                dwg_path = fname
                current_file_var.set(f"Processing: {base_fname} ({idx+1}/{total})")
                try:
                    ok = process_single_file(
                        dwg_path, master_path.get(), template_path.get(), template_layout_name, idx, total, acad, docs, block_names, mode, log_operation=log_operation, error_callback=append_error
                    )
                    if ok:
                        done_files.add(base_fname)
                        save_progress(done_files)
                        delete_bak_for_dwg(dwg_path, log_operation=log_operation, error_callback=append_error)
                        progress_var.set(int(100 * (idx + 1) / total))
                        idx += 1
                    else:
                        append_error(f"Failed: {base_fname}")
                        idx += 1  # Continue to next file
                except Exception as e:
                    append_error(f"Error processing {base_fname}: {e}")
                    idx += 1  # Continue to next file
            if not cancel_requested.get() and len(done_files) >= total:
                delete_all_bak_files(os.getcwd(), log_operation=log_operation, error_callback=append_error)

            if not cancel_requested.get():
                status_var.set("All files processed (or resumed to last file).")
            current_file_var.set("")
        except Exception as e:
            append_error(f"Batch processing error: {e}")
            status_var.set("Batch processing failed.")
        finally:
            set_buttons_state("normal")
            try:
                pythoncom.CoUninitialize()
            except Exception:
                pass

    # --- Modern UI Layout ---
    pad = {'padx': 16, 'pady': 8}
    frame = tk.Frame(root, bg=BLACK)
    frame.pack(fill="both", expand=True)

    tk.Label(frame, text="Export Layouts Batch Processor", font=FONT_BOLD, fg=GOLD, bg=BLACK).pack(pady=(18, 10))

    # Main tabbed interface
    main_notebook = ttk.Notebook(frame)
    main_notebook.pack(fill="both", expand=True, padx=16, pady=(0, 10))
    
    # IO Tab
    io_frame = tk.Frame(main_notebook, bg=BLACK)
    network_frame = tk.Frame(main_notebook, bg=BLACK)
    main_notebook.add(io_frame, text="IO")
    main_notebook.add(network_frame, text="NETWORK")

    # IO Tab Content
    # Master DWG
    row1 = tk.Frame(io_frame, bg=BLACK)
    row1.pack(fill="x", **pad)
    row1.grid_columnconfigure(1, weight=1)
    tk.Label(row1, text="Master DWG (Xref):", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
    master_entry = tk.Entry(row1, textvariable=master_path, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD)
    master_entry.grid(row=0, column=1, sticky="ew", padx=(0,8))
    ttk.Button(row1, text="Browse...", command=select_master).grid(row=0, column=2, sticky="e")

    # Template DWG
    row2 = tk.Frame(io_frame, bg=BLACK)
    row2.pack(fill="x", **pad)
    row2.grid_columnconfigure(1, weight=1)
    tk.Label(row2, text="Template DWG:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
    template_entry = tk.Entry(row2, textvariable=template_path, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD)
    template_entry.grid(row=0, column=1, sticky="ew", padx=(0,8))
    ttk.Button(row2, text="Browse...", command=select_template).grid(row=0, column=2, sticky="e")

    # Drawings files (multi-select)
    row3 = tk.Frame(io_frame, bg=BLACK)
    row3.pack(fill="x", **pad)
    row3.grid_columnconfigure(1, weight=1)
    tk.Label(row3, text="Drawings:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
    tk.Label(row3, textvariable=drawings_files_var, font=FONT, fg=GOLD, bg=BLACK, anchor="w").grid(row=0, column=1, sticky="ew", padx=(0,8))
    ttk.Button(row3, text="Browse...", command=select_drawings_files).grid(row=0, column=2, sticky="e")

    # Block names file
    row4 = tk.Frame(io_frame, bg=BLACK)
    row4.pack(fill="x", **pad)
    row4.grid_columnconfigure(1, weight=1)
    tk.Label(row4, text="Block Names File:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
    tk.Entry(row4, textvariable=names_file, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD).grid(row=0, column=1, sticky="ew", padx=(0,8))
    ttk.Button(row4, text="Browse...", command=select_names_file).grid(row=0, column=2, sticky="e")

    # Progress bar with white border
    tk.Label(io_frame, text="Progress:", font=FONT, fg=GOLD, bg=BLACK, anchor="w").pack(fill="x", **pad)
    pb_border = tk.Frame(io_frame, bg=WHITE, padx=2, pady=2)
    pb_border.pack(pady=(0,10), padx=32, fill="x")
    ttk.Progressbar(pb_border, variable=progress_var, maximum=100, length=500, style="TProgressbar").pack(fill="x")

    # Status and current file
    tk.Label(io_frame, textvariable=status_var, font=FONT, fg=WHITE, bg=BLACK).pack(fill="x", **pad)
    tk.Label(io_frame, textvariable=current_file_var, font=FONT, fg=GOLD, bg=BLACK).pack(fill="x", **pad)

    # Tabbed log area
    log_notebook = ttk.Notebook(io_frame)
    log_notebook.pack(padx=32, pady=(10,10), fill="both", expand=False)
    op_frame = tk.Frame(log_notebook, bg=BLACK)
    err_frame = tk.Frame(log_notebook, bg=BLACK)
    log_notebook.add(op_frame, text="Operations Log")
    log_notebook.add(err_frame, text="Error Log")
    # Operations log with auto-hiding scrollbar
    op_scroll = tk.Scrollbar(op_frame)
    op_text = tk.Text(op_frame, height=7, width=80, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD, borderwidth=2, relief="groove", state="disabled", yscrollcommand=op_scroll.set, wrap="word")
    op_scroll.config(command=op_text.yview)
    op_text.pack(side="left", fill="both", expand=True)
    # Error log with auto-hiding scrollbar
    err_scroll = tk.Scrollbar(err_frame)
    err_text = tk.Text(err_frame, height=7, width=80, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD, borderwidth=2, relief="groove", state="disabled", yscrollcommand=err_scroll.set, wrap="word")
    err_scroll.config(command=err_text.yview)
    err_text.pack(side="left", fill="both", expand=True)

    def show_scrollbar_if_needed(text_widget, scrollbar):
        text_widget.update_idletasks()
        if text_widget.yview()[0] > 0 or text_widget.yview()[1] < 1:
            scrollbar.pack(side="right", fill="y")
        else:
            scrollbar.pack_forget()

    def update_op_log(*args):
        op_text.config(state="normal")
        op_text.delete(1.0, tk.END)
        op_text.insert(tk.END, operations_log_var.get())
        op_text.see(tk.END)
        op_text.config(state="disabled")
        show_scrollbar_if_needed(op_text, op_scroll)
    def update_err_log(*args):
        err_text.config(state="normal")
        err_text.delete(1.0, tk.END)
        err_text.insert(tk.END, error_log_var.get())
        err_text.see(tk.END)
        err_text.config(state="disabled")
        show_scrollbar_if_needed(err_text, err_scroll)
    operations_log_var.trace_add('write', update_op_log)
    error_log_var.trace_add('write', update_err_log)
    op_text.bind('<Configure>', lambda e: show_scrollbar_if_needed(op_text, op_scroll))
    err_text.bind('<Configure>', lambda e: show_scrollbar_if_needed(err_text, err_scroll))

    # Style the selected tab
    style.layout("TNotebook.Tab", [
        ('Notebook.tab', {'sticky': 'nswe', 'children': [
            ('Notebook.padding', {'side': 'top', 'sticky': 'nswe', 'children': [
                ('Notebook.focus', {'side': 'top', 'sticky': 'nswe', 'children': [
                    ('Notebook.label', {'side': 'top', 'sticky': ''})
                ]})
            ]})
        ]})
    ])
    style.map("TNotebook.Tab",
        background=[('selected', GOLD), ('!selected', BLACK)],
        foreground=[('selected', BLACK), ('!selected', GOLD)])

    # Buttons
    btn_frame = tk.Frame(io_frame, bg=BLACK)
    btn_frame.pack(pady=(10, 18))
    run_btn = ttk.Button(btn_frame, text="Run Batch", command=start_batch)
    run_btn.pack(side="left", padx=12)
    start_over_btn = ttk.Button(btn_frame, text="Start Over", command=start_over)
    start_over_btn.pack(side="left", padx=12)
    cancel_btn = ttk.Button(btn_frame, text="Cancel", command=cancel_batch, state="disabled")
    cancel_btn.pack(side="left", padx=12)

    # NETWORK Tab Content
    # Master DWG
    n_row1 = tk.Frame(network_frame, bg=BLACK)
    n_row1.pack(fill="x", **pad)
    n_row1.grid_columnconfigure(1, weight=1)
    tk.Label(n_row1, text="Master DWG (Xref):", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
    n_master_entry = tk.Entry(n_row1, textvariable=master_path, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD)
    n_master_entry.grid(row=0, column=1, sticky="ew", padx=(0,8))
    ttk.Button(n_row1, text="Browse...", command=select_master).grid(row=0, column=2, sticky="e")

    # Template DWG
    n_row2 = tk.Frame(network_frame, bg=BLACK)
    n_row2.pack(fill="x", **pad)
    n_row2.grid_columnconfigure(1, weight=1)
    tk.Label(n_row2, text="Template DWG:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
    n_template_entry = tk.Entry(n_row2, textvariable=template_path, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD)
    n_template_entry.grid(row=0, column=1, sticky="ew", padx=(0,8))
    ttk.Button(n_row2, text="Browse...", command=select_template).grid(row=0, column=2, sticky="e")

    # Drawings files (multi-select)
    n_row3 = tk.Frame(network_frame, bg=BLACK)
    n_row3.pack(fill="x", **pad)
    n_row3.grid_columnconfigure(1, weight=1)
    tk.Label(n_row3, text="Drawings:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
    tk.Label(n_row3, textvariable=drawings_files_var, font=FONT, fg=GOLD, bg=BLACK, anchor="w").grid(row=0, column=1, sticky="ew", padx=(0,8))
    ttk.Button(n_row3, text="Browse...", command=select_drawings_files).grid(row=0, column=2, sticky="e")

    # Block names file
    n_row4 = tk.Frame(network_frame, bg=BLACK)
    n_row4.pack(fill="x", **pad)
    n_row4.grid_columnconfigure(1, weight=1)
    tk.Label(n_row4, text="Block Names File:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
    tk.Entry(n_row4, textvariable=names_file, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD).grid(row=0, column=1, sticky="ew", padx=(0,8))
    ttk.Button(n_row4, text="Browse...", command=select_names_file).grid(row=0, column=2, sticky="e")

    # Progress bar with white border
    tk.Label(network_frame, text="Progress:", font=FONT, fg=GOLD, bg=BLACK, anchor="w").pack(fill="x", **pad)
    n_pb_border = tk.Frame(network_frame, bg=WHITE, padx=2, pady=2)
    n_pb_border.pack(pady=(0,10), padx=32, fill="x")
    ttk.Progressbar(n_pb_border, variable=progress_var, maximum=100, length=500, style="TProgressbar").pack(fill="x")

    # Status and current file
    tk.Label(network_frame, textvariable=status_var, font=FONT, fg=WHITE, bg=BLACK).pack(fill="x", **pad)
    tk.Label(network_frame, textvariable=current_file_var, font=FONT, fg=GOLD, bg=BLACK).pack(fill="x", **pad)

    # Tabbed log area
    n_log_notebook = ttk.Notebook(network_frame)
    n_log_notebook.pack(padx=32, pady=(10,10), fill="both", expand=False)
    n_op_frame = tk.Frame(n_log_notebook, bg=BLACK)
    n_err_frame = tk.Frame(n_log_notebook, bg=BLACK)
    n_log_notebook.add(n_op_frame, text="Operations Log")
    n_log_notebook.add(n_err_frame, text="Error Log")
    # Operations log with auto-hiding scrollbar
    n_op_scroll = tk.Scrollbar(n_op_frame)
    n_op_text = tk.Text(n_op_frame, height=7, width=80, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD, borderwidth=2, relief="groove", state="disabled", yscrollcommand=n_op_scroll.set, wrap="word")
    n_op_scroll.config(command=n_op_text.yview)
    n_op_text.pack(side="left", fill="both", expand=True)
    # Error log with auto-hiding scrollbar
    n_err_scroll = tk.Scrollbar(n_err_frame)
    n_err_text = tk.Text(n_err_frame, height=7, width=80, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD, borderwidth=2, relief="groove", state="disabled", yscrollcommand=n_err_scroll.set, wrap="word")
    n_err_scroll.config(command=n_err_text.yview)
    n_err_text.pack(side="left", fill="both", expand=True)

    def n_show_scrollbar_if_needed(text_widget, scrollbar):
        text_widget.update_idletasks()
        if text_widget.yview()[0] > 0 or text_widget.yview()[1] < 1:
            scrollbar.pack(side="right", fill="y")
        else:
            scrollbar.pack_forget()

    def n_update_op_log(*args):
        n_op_text.config(state="normal")
        n_op_text.delete(1.0, tk.END)
        n_op_text.insert(tk.END, operations_log_var.get())
        n_op_text.see(tk.END)
        n_op_text.config(state="disabled")
        n_show_scrollbar_if_needed(n_op_text, n_op_scroll)
    def n_update_err_log(*args):
        n_err_text.config(state="normal")
        n_err_text.delete(1.0, tk.END)
        n_err_text.insert(tk.END, error_log_var.get())
        n_err_text.see(tk.END)
        n_err_text.config(state="disabled")
        n_show_scrollbar_if_needed(n_err_text, n_err_scroll)
    operations_log_var.trace_add('write', n_update_op_log)
    error_log_var.trace_add('write', n_update_err_log)
    n_op_text.bind('<Configure>', lambda e: n_show_scrollbar_if_needed(n_op_text, n_op_scroll))
    n_err_text.bind('<Configure>', lambda e: n_show_scrollbar_if_needed(n_err_text, n_err_scroll))

    # Buttons
    n_btn_frame = tk.Frame(network_frame, bg=BLACK)
    n_btn_frame.pack(pady=(10, 18))
    n_run_btn = ttk.Button(n_btn_frame, text="Run Batch", command=start_batch)
    n_run_btn.pack(side="left", padx=12)
    n_start_over_btn = ttk.Button(n_btn_frame, text="Start Over", command=start_over)
    n_start_over_btn.pack(side="left", padx=12)
    n_cancel_btn = ttk.Button(n_btn_frame, text="Cancel", command=cancel_batch, state="disabled")
    n_cancel_btn.pack(side="left", padx=12)

    root.mainloop()

if __name__ == "__main__":
    main() 