from __future__ import annotations

from copy import copy
import hashlib
import tkinter as tk
from datetime import datetime
from pathlib import Path
from tkinter import filedialog, messagebox, ttk
from urllib.parse import quote, urlparse, urlsplit, urlunsplit
from urllib.request import Request

from openpyxl import Workbook, load_workbook
from openpyxl.drawing.image import Image as XLImage
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side

from app_core.paths import DATA_DIR, DB_PATH
from app_core.http_client import open_url
from app_core.server_api import fetch_app_setting, save_app_setting
from app_core.ui_helpers import configure_striped_treeview, create_contained_photo, resolve_image_source
from database import get_app_setting, normalize_style_identifier, normalize_workflow_status, parse_active_flag, set_app_setting


COSTING_SORT_KEY_OPTIONS = [
    ("collection", "costing_collection_column"),
    ("season", "costing_season_column"),
    ("dept_number", "costing_dept_column"),
    ("mj_class", "costing_mj_class_column"),
    ("sub_class", "costing_sub_class_column"),
    ("buyer", "buyer_name"),
    ("style_code", "costing_style_column"),
    ("manf_color", "costing_manf_color_column"),
    ("color_code", "costing_color_code_column"),
    ("product_type", "costing_product_type_column"),
    ("ticket_type", "costing_ticket_type_column"),
    ("packing_type", "costing_packing_type_column"),
    ("fabrication", "costing_fabrication_column"),
    ("size_range", "costing_size_range_column"),
    ("size_scale", "costing_size_scale_column"),
    ("allocation_multiple", "costing_allocation_multiple_column"),
    ("min_qty", "costing_min_column"),
    ("max_qty", "costing_max_column"),
    ("inner_pack", "costing_inner_column"),
    ("outer_pack", "costing_master_pack_column"),
    ("hanger_supplier", "costing_hanger_supplier_column"),
    ("hanger_type", "costing_hanger_type_column"),
    ("item_retail", "costing_item_retail_column"),
    ("po_number", "costing_po_number_column"),
    ("total_units", "costing_total_units_column"),
    ("description", "costing_description_column"),
    ("is_active", "active_status"),
    ("status", "status"),
]

COSTING_FILTER_FIELD_OPTIONS = [
    ("__all__", "samples_filter_all_option"),
    ("collection", "costing_collection_column"),
    ("season", "costing_season_column"),
    ("dept_number", "costing_dept_column"),
    ("mj_class", "costing_mj_class_column"),
    ("sub_class", "costing_sub_class_column"),
    ("buyer", "buyer_name"),
    ("style_code", "costing_style_column"),
    ("description", "costing_description_column"),
    ("fabrication", "costing_fabrication_column"),
    ("manf_color", "costing_manf_color_column"),
    ("color_code", "costing_color_code_column"),
    ("product_type", "costing_product_type_column"),
    ("ticket_type", "costing_ticket_type_column"),
    ("packing_type", "costing_packing_type_column"),
    ("total_units", "costing_total_units_column"),
    ("allocation_multiple", "costing_allocation_multiple_column"),
    ("min_qty", "costing_min_column"),
    ("max_qty", "costing_max_column"),
    ("inner_pack", "costing_inner_column"),
    ("outer_pack", "costing_master_pack_column"),
    ("size_range", "costing_size_range_column"),
    ("size_scale", "costing_size_scale_column"),
    ("hanger_supplier", "costing_hanger_supplier_column"),
    ("hanger_type", "costing_hanger_type_column"),
    ("item_retail", "costing_item_retail_column"),
    ("po_number", "costing_po_number_column"),
    ("is_active", "active_status"),
    ("status", "status"),
    ("created_at", "create_time_column"),
    ("updated_at", "revised_time_column"),
]

COSTING_DATE_FILTER_FIELDS = {"created_at", "updated_at"}

COSTING_FILTER_DEBOUNCE_MS = 180
COSTING_FORM_RECORDS_KEY = "costing_form_records"
COSTING_FORM_ASSETS_KEY = "costing_form_assets"
COSTING_FORM_TEMPLATE_BUCKET = "costing-form-assets"
COSTING_FORM_TEMPLATE_FILENAME = "SUNNY_COSTING_FORM_TEMPLATE.xlsx"
COSTING_FORM_LOGISTICS_DEFAULTS = ("Duty Rate", "Exim", "LA warehouse")
COSTING_FORM_LABOUR_DEFAULTS = ("Sewing Labor", "Printing", "Washing", "Transfer")
COSTING_FORM_COST_UNIT_OPTIONS = ("%", "USD", "VND")
COSTING_FORM_CURRENCY_OPTIONS = ("USD", "VND")


def _costing_form_now_text() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def _costing_safe_float(value: object) -> float:
    text = str(value or "").strip().replace(",", "").replace("$", "")
    text = text.replace("%", "")
    if not text or text in {"-", "$-"}:
        return 0.0
    try:
        return float(text)
    except Exception:
        return 0.0


def _costing_percent_float(value: object) -> float:
    text = str(value or "").strip()
    if not text:
        return 0.0
    numeric = _costing_safe_float(text)
    if "%" in text or abs(numeric) > 1:
        return numeric / 100
    return numeric


def _costing_format_number(value: float) -> str:
    numeric = round(float(value or 0.0), 4)
    if abs(numeric - round(numeric)) < 1e-9:
        return str(int(round(numeric)))
    return f"{numeric:.4f}".rstrip("0").rstrip(".")


def _costing_line_cost(line: dict) -> float:
    consumption = _costing_safe_float(line.get("consumption"))
    loss_percent = _costing_percent_float(line.get("loss_percent"))
    price = _costing_safe_float(line.get("price"))
    return consumption * (1 + loss_percent) * price


def _costing_charge_value(line: dict, base_value: float = 0.0, value_key: str = "costing_value") -> float:
    value = _costing_safe_float(line.get(value_key))
    unit = str(line.get("unit", "")).strip().upper()
    if unit == "%":
        return base_value * value / 100
    return value


def default_costing_form_assets() -> dict[str, object]:
    return {"template_url": ""}


def default_costing_form_record(style_code: object = "") -> dict[str, object]:
    return {
        "style_code": normalize_style_identifier(style_code),
        "qty": "",
        "offer_date": "",
        "confirm_date": "",
        "buyer_target": "",
        "buyer_confirm": "",
        "approved_by": "",
        "approved_date": "",
        "image_path": "",
        "material_lines": [],
        "actual_lines": [],
        "logistics_lines": [
            {"name": name, "costing_value": "", "actual_value": "", "unit": "", "note": ""}
            for name in COSTING_FORM_LOGISTICS_DEFAULTS
        ],
        "labour_lines": [
            {"name": name, "costing_value": "", "actual_value": "", "unit": "USD", "note": ""}
            for name in COSTING_FORM_LABOUR_DEFAULTS
        ],
        "created_at": "",
        "updated_at": "",
    }


def normalize_costing_form_line(payload: object) -> dict:
    row = dict(payload) if isinstance(payload, dict) else {}
    return {
        "item_code": str(row.get("item_code", "")).strip(),
        "name": str(row.get("name") or row.get("item_name") or row.get("bom_item_name") or "").strip(),
        "type": str(row.get("type") or row.get("fabric_type") or "").strip(),
        "color": str(row.get("color", "")).strip(),
        "consumption": str(row.get("consumption", "")).strip(),
        "uom": str(row.get("uom", "")).strip(),
        "loss_percent": str(row.get("loss_percent") or row.get("waste_percent") or "").strip(),
        "price": str(row.get("price") or row.get("standard_price") or "").strip(),
        "currency": str(row.get("currency", "")).strip().upper(),
        "cost": str(row.get("cost", "")).strip(),
        "note": str(row.get("note", "")).strip(),
    }


def normalize_costing_form_record(payload: object, style_code: object = "") -> dict[str, object]:
    normalized = default_costing_form_record(style_code)
    if isinstance(payload, dict):
        for key in normalized:
            if key in {"material_lines", "actual_lines", "logistics_lines", "labour_lines"}:
                continue
            normalized[key] = str(payload.get(key, normalized[key]) or "").strip()
        normalized["material_lines"] = [
            normalize_costing_form_line(line)
            for line in list(payload.get("material_lines", []))
            if isinstance(line, dict)
        ]
        normalized["actual_lines"] = [
            normalize_costing_form_line(line)
            for line in list(payload.get("actual_lines", []))
            if isinstance(line, dict)
        ]
        logistics_lines = []
        for line in list(payload.get("logistics_lines", [])):
            if not isinstance(line, dict):
                continue
            logistics_lines.append(
                {
                    "name": str(line.get("name", "")).strip(),
                    "costing_value": str(line.get("costing_value", "")).strip(),
                    "actual_value": str(line.get("actual_value", "")).strip(),
                    "unit": str(line.get("unit", "")).strip().upper(),
                    "note": str(line.get("note", "")).strip(),
                }
            )
        if logistics_lines:
            normalized["logistics_lines"] = logistics_lines
        labour_lines = []
        for line in list(payload.get("labour_lines", [])):
            if not isinstance(line, dict):
                continue
            labour_lines.append(
                {
                    "name": str(line.get("name", "")).strip(),
                    "costing_value": str(line.get("costing_value", "")).strip(),
                    "actual_value": str(line.get("actual_value", "")).strip(),
                    "unit": str(line.get("unit", "")).strip().upper() or "USD",
                    "note": str(line.get("note", "")).strip(),
                }
            )
        if labour_lines:
            normalized["labour_lines"] = labour_lines
    normalized["style_code"] = normalize_style_identifier(normalized.get("style_code") or style_code)
    return normalized


def _costing_form_image_path_from_source(source: dict) -> str:
    for key in ("image_path", "cover_image", "thumbnail_path"):
        value = str(source.get(key, "") or "").strip()
        if value:
            return value
    preview_images = source.get("preview_images", [])
    if isinstance(preview_images, list):
        for value in preview_images:
            text = str(value or "").strip()
            if text:
                return text
    return ""


def _build_costing_module(self, parent: ttk.Frame) -> None:
    parent.columnconfigure(0, weight=1)
    parent.rowconfigure(2, weight=1)

    toolbar = ttk.Frame(parent)
    toolbar.grid(row=0, column=0, sticky="ew")
    self.widget_text["costing_import_button"] = ttk.Button(toolbar, command=self.import_costing_pdf)
    self.widget_text["costing_import_button"].pack(side="left")
    self.register_permission_widget("action.costing.import_pdf", self.widget_text["costing_import_button"])
    self.widget_text["costing_import_ws_button"] = ttk.Button(toolbar, command=self.import_costing_worksheet)
    self.widget_text["costing_import_ws_button"].pack(side="left", padx=(8, 0))
    self.register_permission_widget("action.costing.import_ws", self.widget_text["costing_import_ws_button"])
    self.widget_text["costing_import_folder_button"] = ttk.Button(toolbar, command=self.import_costing_folder)
    self.widget_text["costing_import_folder_button"].pack(side="left", padx=(8, 0))
    self.register_permission_widget("action.costing.import_folder", self.widget_text["costing_import_folder_button"])
    self.widget_text["costing_export_report_button"] = ttk.Button(
        toolbar,
        command=lambda: self.export_report("costing"),
    )
    self.widget_text["costing_export_report_button"].pack(side="left", padx=(8, 0))
    self.register_permission_widget("action.costing.export_report", self.widget_text["costing_export_report_button"])
    self.widget_text["costing_adjust_button"] = ttk.Button(toolbar, command=self.open_costing_crop_adjuster)
    self.widget_text["costing_adjust_button"].pack(side="left", padx=(8, 0))
    self.register_permission_widget("action.costing.adjust_crop", self.widget_text["costing_adjust_button"])
    self.widget_text["costing_delete_button"] = ttk.Button(toolbar, command=self.delete_selected_costing_styles)
    self.widget_text["costing_delete_button"].pack(side="left", padx=(8, 0))
    self.register_permission_widget("action.costing.delete_style", self.widget_text["costing_delete_button"])
    self.widget_text["costing_edit_button"] = ttk.Button(toolbar, command=self.edit_selected_costing_row)
    self.widget_text["costing_edit_button"].pack(side="left", padx=(8, 0))
    self.register_permission_widget("action.costing.edit_row", self.widget_text["costing_edit_button"])
    self.widget_text["costing_collection_list_button"] = ttk.Button(toolbar, command=self.open_collection_list_dialog)
    self.widget_text["costing_collection_list_button"].pack(side="left", padx=(8, 0))
    self.register_permission_widget("action.costing.collection_list", self.widget_text["costing_collection_list_button"])
    self.widget_text["costing_form_button"] = ttk.Button(toolbar, text="Costing form", command=self.export_selected_costing_form)
    self.widget_text["costing_form_button"].pack(side="left", padx=(8, 0))
    self.register_permission_widget("action.costing.export_report", self.widget_text["costing_form_button"])

    meta = ttk.Frame(parent)
    meta.grid(row=1, column=0, sticky="ew", pady=(12, 14))
    meta.columnconfigure(8, weight=1)
    self.widget_text["costing_filter_field_label"] = ttk.Label(meta)
    self.widget_text["costing_filter_field_label"].grid(row=0, column=0, sticky="w", padx=(0, 6))
    self.costing_filter_field_combo = ttk.Combobox(meta, textvariable=self.costing_filter_field_var, state="readonly", width=22)
    self.costing_filter_field_combo.grid(row=0, column=1, sticky="w")
    self.costing_filter_field_combo.bind("<<ComboboxSelected>>", self.on_costing_filter_field_changed)
    self.widget_text["costing_filter_value_label"] = ttk.Label(meta)
    self.widget_text["costing_filter_value_label"].grid(row=0, column=2, sticky="w", padx=(12, 6))
    self.costing_search_entry = ttk.Entry(meta, textvariable=self.costing_search_var, width=36)
    self.costing_search_entry.grid(row=0, column=3, sticky="w", padx=(0, 12))
    self.costing_search_entry.bind("<Return>", self.on_costing_search_submit)
    self.costing_search_entry.bind("<KP_Enter>", self.on_costing_search_submit)
    self.widget_text["costing_date_from_label"] = ttk.Label(meta)
    self.widget_text["costing_date_from_label"].grid(row=0, column=4, sticky="w", padx=(0, 6))
    self.costing_date_from_entry = ttk.Entry(meta, textvariable=self.costing_date_from_var, width=12)
    self.costing_date_from_entry.grid(row=0, column=5, sticky="w", padx=(0, 12))
    self.bind_date_entry_picker(self.costing_date_from_entry, self.costing_date_from_var)
    self.costing_date_from_var.trace_add("write", lambda *_args: self.apply_costing_filter())
    self.widget_text["costing_date_to_label"] = ttk.Label(meta)
    self.widget_text["costing_date_to_label"].grid(row=0, column=6, sticky="w", padx=(0, 6))
    self.costing_date_to_entry = ttk.Entry(meta, textvariable=self.costing_date_to_var, width=12)
    self.costing_date_to_entry.grid(row=0, column=7, sticky="w")
    self.bind_date_entry_picker(self.costing_date_to_entry, self.costing_date_to_var)
    self.costing_date_to_var.trace_add("write", lambda *_args: self.apply_costing_filter())
    self.widget_text["costing_sort_by_label"] = ttk.Label(meta)
    self.widget_text["costing_sort_by_label"].grid(row=1, column=0, sticky="w", pady=(8, 0), padx=(0, 6))
    self.costing_sort_key_combo = ttk.Combobox(
        meta,
        textvariable=self.costing_sort_key_var,
        state="readonly",
        width=18,
    )
    self.costing_sort_key_combo.grid(row=1, column=1, sticky="w", pady=(8, 0))
    self.costing_sort_key_combo.bind("<<ComboboxSelected>>", self.on_costing_sort_key_changed)
    self.widget_text["costing_sort_order_label"] = ttk.Label(meta)
    self.widget_text["costing_sort_order_label"].grid(row=1, column=2, sticky="w", pady=(8, 0), padx=(12, 6))
    self.costing_sort_order_combo = ttk.Combobox(
        meta,
        textvariable=self.costing_sort_order_var,
        state="readonly",
        width=12,
    )
    self.costing_sort_order_combo.grid(row=1, column=3, sticky="w", pady=(8, 0))
    self.costing_sort_order_combo.bind("<<ComboboxSelected>>", self.on_costing_sort_order_changed)
    self.update_costing_filter_input_state()
    self.widget_text["costing_source_file_label"] = ttk.Label(meta, style="FieldLabel.TLabel")
    self.widget_text["costing_source_file_label"].grid(row=2, column=0, sticky="nw", padx=(0, 8), pady=(10, 0))
    self.costing_source_file_var = tk.StringVar(value="-")
    self.costing_source_file_value = ttk.Label(meta, textvariable=self.costing_source_file_var, wraplength=1020, justify="left")
    self.costing_source_file_value.grid(row=2, column=1, columnspan=3, sticky="w", pady=(10, 0))

    body = ttk.Panedwindow(parent, orient=tk.HORIZONTAL)
    body.grid(row=2, column=0, sticky="nsew")
    self.costing_pane = body

    left = ttk.Frame(body, padding=(0, 0, 12, 0))
    right = ttk.Frame(body)
    body.add(left, weight=3)
    body.add(right, weight=4)

    self.widget_text["costing_styles_section"] = ttk.Label(left, style="Section.TLabel")
    self.widget_text["costing_styles_section"].pack(anchor="w", pady=(0, 8))

    costing_table = ttk.Frame(left)
    costing_table.pack(fill="both", expand=True)
    costing_table.columnconfigure(0, weight=1)
    costing_table.rowconfigure(0, weight=1)
    self.costing_tree = ttk.Treeview(
        costing_table,
        columns=(
            "created_at",
            "select_flag",
            "collection",
            "season",
            "dept_number",
            "mj_class",
            "sub_class",
            "buyer",
            "style_code",
            "description",
            "fabrication",
            "manf_color",
            "color_code",
            "product_type",
            "ticket_type",
            "packing_type",
            "size_range",
            "size_scale",
            "allocation_multiple",
            "min_qty",
            "max_qty",
            "inner_pack",
            "outer_pack",
            "hanger_supplier",
            "hanger_type",
            "item_retail",
            "po_number",
            "total_units",
            "is_active",
            "status",
            "updated_at",
            "latest_user",
        ),
        show="tree headings",
        selectmode="browse",
        style="Costing.Treeview",
    )
    self.costing_tree.column("#0", width=96, anchor="center", stretch=False)
    self.costing_tree.column("select_flag", width=60, anchor="center", stretch=False)
    self.costing_tree.column("created_at", width=145, anchor="center", stretch=False)
    self.costing_tree.column("collection", width=120, anchor="w", stretch=False)
    self.costing_tree.column("season", width=110, anchor="center", stretch=False)
    self.costing_tree.column("dept_number", width=70, anchor="center", stretch=False)
    self.costing_tree.column("mj_class", width=80, anchor="center", stretch=False)
    self.costing_tree.column("sub_class", width=80, anchor="center", stretch=False)
    self.costing_tree.column("buyer", width=90, anchor="w", stretch=False)
    self.costing_tree.column("style_code", width=100, anchor="w", stretch=False)
    self.costing_tree.column("description", width=220, anchor="w", stretch=False)
    self.costing_tree.column("fabrication", width=140, anchor="w", stretch=False)
    self.costing_tree.column("manf_color", width=120, anchor="w", stretch=False)
    self.costing_tree.column("color_code", width=90, anchor="center", stretch=False)
    self.costing_tree.column("product_type", width=110, anchor="w", stretch=False)
    self.costing_tree.column("ticket_type", width=110, anchor="w", stretch=False)
    self.costing_tree.column("packing_type", width=90, anchor="center", stretch=False)
    self.costing_tree.column("size_range", width=90, anchor="center", stretch=False)
    self.costing_tree.column("size_scale", width=90, anchor="center", stretch=False)
    self.costing_tree.column("allocation_multiple", width=120, anchor="center", stretch=False)
    self.costing_tree.column("min_qty", width=70, anchor="e", stretch=False)
    self.costing_tree.column("max_qty", width=70, anchor="e", stretch=False)
    self.costing_tree.column("inner_pack", width=90, anchor="center", stretch=False)
    self.costing_tree.column("outer_pack", width=90, anchor="center", stretch=False)
    self.costing_tree.column("hanger_supplier", width=130, anchor="w", stretch=False)
    self.costing_tree.column("hanger_type", width=110, anchor="w", stretch=False)
    self.costing_tree.column("item_retail", width=90, anchor="e", stretch=False)
    self.costing_tree.column("po_number", width=110, anchor="w", stretch=False)
    self.costing_tree.column("total_units", width=90, anchor="e", stretch=False)
    self.costing_tree.column("is_active", width=90, anchor="center", stretch=False)
    self.costing_tree.column("status", width=90, anchor="center", stretch=False)
    self.costing_tree.column("updated_at", width=145, anchor="center", stretch=False)
    self.costing_tree.column("latest_user", width=140, anchor="w", stretch=False)
    self.costing_tree.heading("#0", command=lambda: None)
    self.costing_tree.heading("select_flag", command=lambda: self.on_costing_heading_click("select_flag"))
    self.costing_tree.heading("created_at", command=lambda: self.on_costing_heading_click("created_at"))
    self.costing_tree.heading("collection", command=lambda: self.on_costing_heading_click("collection"))
    self.costing_tree.heading("season", command=lambda: self.on_costing_heading_click("season"))
    self.costing_tree.heading("dept_number", command=lambda: self.on_costing_heading_click("dept_number"))
    self.costing_tree.heading("mj_class", command=lambda: self.on_costing_heading_click("mj_class"))
    self.costing_tree.heading("sub_class", command=lambda: self.on_costing_heading_click("sub_class"))
    self.costing_tree.heading("buyer", command=lambda: self.on_costing_heading_click("buyer"))
    self.costing_tree.heading("style_code", command=lambda: self.on_costing_heading_click("style_code"))
    self.costing_tree.heading("description", command=lambda: self.on_costing_heading_click("description"))
    self.costing_tree.heading("fabrication", command=lambda: self.on_costing_heading_click("fabrication"))
    self.costing_tree.heading("manf_color", command=lambda: self.on_costing_heading_click("manf_color"))
    self.costing_tree.heading("color_code", command=lambda: self.on_costing_heading_click("color_code"))
    self.costing_tree.heading("product_type", command=lambda: self.on_costing_heading_click("product_type"))
    self.costing_tree.heading("ticket_type", command=lambda: self.on_costing_heading_click("ticket_type"))
    self.costing_tree.heading("packing_type", command=lambda: self.on_costing_heading_click("packing_type"))
    self.costing_tree.heading("size_range", command=lambda: self.on_costing_heading_click("size_range"))
    self.costing_tree.heading("size_scale", command=lambda: self.on_costing_heading_click("size_scale"))
    self.costing_tree.heading("allocation_multiple", command=lambda: self.on_costing_heading_click("allocation_multiple"))
    self.costing_tree.heading("min_qty", command=lambda: self.on_costing_heading_click("min_qty"))
    self.costing_tree.heading("max_qty", command=lambda: self.on_costing_heading_click("max_qty"))
    self.costing_tree.heading("inner_pack", command=lambda: self.on_costing_heading_click("inner_pack"))
    self.costing_tree.heading("outer_pack", command=lambda: self.on_costing_heading_click("outer_pack"))
    self.costing_tree.heading("hanger_supplier", command=lambda: self.on_costing_heading_click("hanger_supplier"))
    self.costing_tree.heading("hanger_type", command=lambda: self.on_costing_heading_click("hanger_type"))
    self.costing_tree.heading("item_retail", command=lambda: self.on_costing_heading_click("item_retail"))
    self.costing_tree.heading("po_number", command=lambda: self.on_costing_heading_click("po_number"))
    self.costing_tree.heading("total_units", command=lambda: self.on_costing_heading_click("total_units"))
    self.costing_tree.heading("is_active", command=lambda: self.on_costing_heading_click("is_active"))
    self.costing_tree.heading("status", command=lambda: self.on_costing_heading_click("status"))
    self.costing_tree.heading("updated_at", command=lambda: self.on_costing_heading_click("updated_at"))
    self.costing_tree.heading("latest_user", command=lambda: self.on_costing_heading_click("latest_user"))
    self.costing_tree.heading("latest_user", text=self.t("latest_user_column"), command=lambda: self.on_costing_heading_click("latest_user"))
    costing_scroll = ttk.Scrollbar(costing_table, orient="vertical", command=self.costing_tree.yview)
    costing_scroll_x = ttk.Scrollbar(costing_table, orient="horizontal", command=self.costing_tree.xview)
    self.costing_tree.configure(yscrollcommand=costing_scroll.set, xscrollcommand=costing_scroll_x.set)
    configure_striped_treeview(self.costing_tree)
    self.costing_tree.grid(row=0, column=0, sticky="nsew")
    costing_scroll.grid(row=0, column=1, sticky="ns")
    costing_scroll_x.grid(row=1, column=0, sticky="ew")
    self.costing_tree.bind("<<TreeviewSelect>>", self.on_costing_item_selected)
    self.costing_tree.bind("<Button-1>", self.on_costing_tree_click)
    self.costing_tree.bind("<Double-1>", self.on_costing_tree_double_click)

    self.widget_text["costing_preview_section"] = ttk.Label(right, style="Section.TLabel")
    self.widget_text["costing_preview_section"].pack(anchor="w", pady=(0, 8))

    colors_row = ttk.Frame(right)
    colors_row.pack(fill="x", pady=(0, 8))
    self.widget_text["costing_colors_label"] = ttk.Label(colors_row, style="FieldLabel.TLabel")
    self.widget_text["costing_colors_label"].pack(side="left")
    self.costing_variant_buttons_frame = ttk.Frame(colors_row)
    self.costing_variant_buttons_frame.pack(side="left", padx=(8, 0))

    self.costing_image_label = ttk.Label(right, anchor="center")
    self.costing_image_label.pack(fill="both", expand=False)

    self.widget_text["costing_description_label"] = ttk.Label(right, style="FieldLabel.TLabel")
    self.widget_text["costing_description_label"].pack(anchor="w", pady=(12, 4))
    self.costing_description_text = tk.Text(right, wrap="word", height=10)
    self.costing_description_text.pack(fill="both", expand=True)

    self.costing_status_label = ttk.Label(
        parent,
        textvariable=self.status_var,
        relief="sunken",
        anchor="w",
        justify="left",
        padding=8,
        wraplength=1180,
    )
    self.costing_status_label.grid(row=3, column=0, sticky="ew", pady=(12, 0))


def create_costing_thumbnail(self, item: dict):
    image_path = self.resolve_costing_item_image_path(item)
    if not image_path:
        return None
    return create_contained_photo(image_path, target_width=84, target_height=84)


def resolve_costing_item_image_path(self, item: dict) -> str | None:
    variants = item.get("variants", [])
    for variant in variants:
        image_path = str(variant.get("image_path", "")).strip()
        if image_path:
            return image_path

    style_code = str(item.get("style_code", "")).strip()
    tech_pack_row = self.get_tech_pack_row_by_style_code(style_code)
    if tech_pack_row is None:
        return None

    tech_pack_image_path = str(tech_pack_row.get("image_path", "")).strip()
    if tech_pack_image_path:
        return tech_pack_image_path
    return None


def get_costing_check_display(self, item: dict) -> str:
    group_key = tuple(item.get("group_key", ("", "", "")))
    return "[x]" if group_key in self.checked_costing_keys else "[ ]"


def populate_costing_module(self) -> None:
    data = self.costing_data or {}
    self.costing_source_file_var.set(self.display_value(data.get("source_pdf")))
    self.apply_costing_filter()
    self.update_report_target_summary()
    self.update_costing_module_status()


def refresh_costing_tree(self, selected_id: str | None = None) -> None:
    current_selection = self.costing_tree.selection()
    if selected_id is None and current_selection:
        selected_id = current_selection[0]

    for item_id in self.costing_tree.get_children():
        self.costing_tree.delete(item_id)
    self.costing_tree.tag_configure("costing_status_draft", background="#ffffff")
    self.costing_tree.tag_configure("costing_status_checking", background="#fff7cc")
    self.costing_tree.tag_configure("costing_status_approved", background="#dff5e3")
    self.costing_thumbnail_photos = []
    thumbnail_limit = self.get_startup_thumbnail_render_limit("costing") if hasattr(self, "get_startup_thumbnail_render_limit") else None
    rendered_thumbnails = 0

    for index, item in enumerate(self.filtered_costing_items, start=1):
        thumbnail_photo = None
        if thumbnail_limit is None or index <= thumbnail_limit:
            thumbnail_photo = self.create_costing_thumbnail(item)
            if thumbnail_photo is not None:
                self.costing_thumbnail_photos.append(thumbnail_photo)
                rendered_thumbnails += 1
        row_values = (
            str(self.display_value(item.get("created_at"))),
            str(self.get_costing_check_display(item)),
            str(self.display_value(item.get("collection"))),
            str(self.display_value(item.get("season"))),
            str(self.display_value(item.get("dept_number"))),
            str(self.display_value(item.get("mj_class"))),
            str(self.display_value(item.get("sub_class"))),
            str(self.display_value(item.get("buyer"))),
            str(self.display_value(item.get("style_code"))),
            str(self.display_value(item.get("description"))).replace("\n", " | "),
            str(self.display_value(item.get("fabrication"))),
            str(self.display_value(item.get("manf_color"))),
            str(self.display_value(item.get("color_code"))),
            str(self.display_value(item.get("product_type"))),
            str(self.display_value(item.get("ticket_type"))),
            str(self.display_value(item.get("packing_type"))),
            str(self.display_value(item.get("size_range"))),
            str(self.display_value(item.get("size_scale"))),
            str(self.display_value(item.get("allocation_multiple"))),
            str(self.display_value(item.get("min_qty"))),
            str(self.display_value(item.get("max_qty"))),
            str(self.display_value(item.get("inner_pack"))),
            str(self.display_value(item.get("outer_pack"))),
            str(self.display_value(item.get("hanger_supplier"))),
            str(self.display_value(item.get("hanger_type"))),
            str(self.display_value(item.get("item_retail"))),
            str(self.display_value(item.get("po_number"))),
            str(self.display_value(item.get("total_units"))),
            "Active" if bool(item.get("is_active", True)) else "Inactive",
            str(self.display_value(item.get("status"))),
            str(self.display_value(item.get("updated_at"))),
            str(self.display_value(item.get("latest_user_display"))),
        )
        insert_kwargs: dict[str, object] = {
            "text": "",
            "values": row_values,
            "tags": (
                {
                    "checking": "costing_status_checking",
                    "approved": "costing_status_approved",
                }.get(str(item.get("status", "")).strip().lower(), "costing_status_draft"),
            ),
        }
        if thumbnail_photo is not None:
            insert_kwargs["image"] = thumbnail_photo
        self.costing_tree.insert("", "end", str(index), **insert_kwargs)

    valid_keys = {tuple(item.get("group_key", ("", "", ""))) for item in self.costing_items}
    self.checked_costing_keys.intersection_update(valid_keys)

    if selected_id and self.costing_tree.exists(selected_id):
        self.costing_tree.selection_set(selected_id)
        self.costing_tree.focus(selected_id)
        self.on_costing_item_selected(None)
    elif self.filtered_costing_items:
        self.costing_tree.selection_set("1")
        self.costing_tree.focus("1")
        self.load_costing_item(0)
    else:
        self.costing_selected_index = None
        for child in self.costing_variant_buttons_frame.winfo_children():
            child.destroy()
        self.costing_image_label.configure(image="", text=self.t("costing_no_preview"))
        self.costing_description_text.delete("1.0", tk.END)
        self.costing_description_text.insert("1.0", self.t("costing_empty"))
    if hasattr(self, "note_startup_thumbnail_render_state"):
        self.note_startup_thumbnail_render_state(
            "costing",
            total_rows=len(self.filtered_costing_items),
            rendered_rows=min(len(self.filtered_costing_items), thumbnail_limit) if thumbnail_limit is not None else len(self.filtered_costing_items),
        )
    self.update_costing_module_status()


def update_costing_tree_check_state(self, row_id: str | None = None) -> None:
    if not hasattr(self, "costing_tree"):
        return
    target_ids = [row_id] if row_id else list(self.costing_tree.get_children())
    for item_id in target_ids:
        if not self.costing_tree.exists(item_id):
            continue
        try:
            index = int(item_id) - 1
        except (TypeError, ValueError):
            continue
        if index < 0 or index >= len(self.filtered_costing_items):
            continue
        item = self.filtered_costing_items[index]
        values = list(self.costing_tree.item(item_id, "values"))
        if len(values) < 2:
            continue
        values[1] = self.get_costing_check_display(item)
        self.costing_tree.item(item_id, values=tuple(values))
    self.update_costing_module_status()


def apply_costing_filter(self) -> None:
    if getattr(self, "_costing_filter_update_suspended", False):
        return
    query = self.costing_search_var.get().strip().lower()
    from_text = self.costing_date_from_var.get().strip()
    to_text = self.costing_date_to_var.get().strip()
    filter_field = getattr(self, "costing_filter_field_internal", "__all__")
    is_date_field = filter_field in COSTING_DATE_FILTER_FIELDS
    self.filtered_costing_items = list(self.costing_items)
    if is_date_field:
        self.filtered_costing_items = [
            item
            for item in self.filtered_costing_items
            if self.is_within_date_range(item.get(filter_field), from_text, to_text)
        ]
    elif filter_field == "__all__" and query:
        filtered: list[dict] = []
        for item in self.filtered_costing_items:
            haystack = " ".join(
                [
                    self.display_value(item.get("collection")),
                    self.display_value(item.get("season")),
                    self.display_value(item.get("dept_number")),
                    self.display_value(item.get("mj_class")),
                    self.display_value(item.get("sub_class")),
                    self.display_value(item.get("buyer")),
                    self.display_value(item.get("style_code")),
                    self.display_value(item.get("manf_color")),
                    self.display_value(item.get("color_code")),
                    self.display_value(item.get("product_type")),
                    self.display_value(item.get("ticket_type")),
                    self.display_value(item.get("packing_type")),
                    self.display_value(item.get("fabrication")),
                    self.display_value(item.get("allocation_multiple")),
                    self.display_value(item.get("min_qty")),
                    self.display_value(item.get("max_qty")),
                    self.display_value(item.get("inner_pack")),
                    self.display_value(item.get("outer_pack")),
                    self.display_value(item.get("size_range")),
                    self.display_value(item.get("size_scale")),
                    self.display_value(item.get("total_units")),
                    self.display_value(item.get("hanger_supplier")),
                    self.display_value(item.get("hanger_type")),
                    self.display_value(item.get("item_retail")),
                    self.display_value(item.get("po_number")),
                    "active" if bool(item.get("is_active", True)) else "inactive",
                    self.display_value(item.get("status")),
                    self.display_value(item.get("created_at")),
                    self.display_value(item.get("updated_at")),
                    self.display_value(item.get("description")),
                    " ".join(self.display_value(variant.get("description")) for variant in item.get("variants", [])),
                ]
            ).lower()
            if query in haystack:
                filtered.append(item)
        self.filtered_costing_items = filtered
    elif query:
        if filter_field == "is_active":
            self.filtered_costing_items = [
                item
                for item in self.filtered_costing_items
                if ("active" if bool(item.get("is_active", True)) else "inactive") == query
            ]
            if hasattr(self, "costing_tree"):
                self.refresh_costing_tree()
            self.update_report_target_summary()
            self.update_costing_module_status()
            return
        self.filtered_costing_items = [
            item
            for item in self.filtered_costing_items
            if self.display_value(item.get(filter_field)).strip().lower() == query
        ]

    sort_key = self.costing_sort_key_internal
    reverse = self.costing_sort_order_internal == "desc"

    def sort_value(item: dict) -> object:
        value = item.get(sort_key)
        if value is None:
            return ""
        return self.parse_costing_sort_value(sort_key, value)

    self.filtered_costing_items.sort(key=sort_value, reverse=reverse)
    if hasattr(self, "costing_tree"):
        self.refresh_costing_tree()
    self.update_report_target_summary()
    self.update_costing_module_status()


def commit_costing_search_query(self, value: object | None = None, *, apply_filter: bool = True) -> None:
    if value is not None:
        self.costing_search_var.set(str(value or "").strip())
    if apply_filter:
        self.apply_costing_filter()


def on_costing_search_submit(self, _event: object | None = None) -> str:
    self.commit_costing_search_query()
    return "break"


def sync_costing_search_query(self, value: object) -> None:
    normalized_value = str(value or "").strip()
    self.costing_search_var.set(normalized_value)


def schedule_costing_filter_apply(self) -> None:
    self.commit_costing_search_query()


def run_costing_filter_apply(self) -> None:
    self.apply_costing_filter()


def update_costing_filter_input_state(self, *, clear_irrelevant: bool = False) -> None:
    filter_field = getattr(self, "costing_filter_field_internal", "__all__")
    is_date_field = filter_field in COSTING_DATE_FILTER_FIELDS
    if hasattr(self, "costing_search_entry"):
        self.costing_search_entry.configure(state="disabled" if is_date_field else "normal")
    if hasattr(self, "costing_date_from_entry"):
        self.costing_date_from_entry.configure(state="normal" if is_date_field else "disabled")
    if hasattr(self, "costing_date_to_entry"):
        self.costing_date_to_entry.configure(state="normal" if is_date_field else "disabled")
    if clear_irrelevant:
        self._costing_filter_update_suspended = True
        try:
            if is_date_field:
                self.sync_costing_search_query("")
            else:
                self.costing_date_from_var.set("")
                self.costing_date_to_var.set("")
        finally:
            self._costing_filter_update_suspended = False


def on_costing_item_selected(self, _event: object) -> None:
    selection = self.costing_tree.selection()
    if not selection:
        return
    index = int(selection[0]) - 1
    self.load_costing_item(index)
    self.update_costing_module_status()


def load_costing_item(self, index: int) -> None:
    if index < 0 or index >= len(self.filtered_costing_items):
        return

    self.costing_selected_index = index
    self.costing_selected_variant_index = 0
    item = self.filtered_costing_items[index]
    self.render_costing_variant_buttons(item)
    self.load_costing_variant(item, self.costing_selected_variant_index)


def on_costing_tree_click(self, event: tk.Event) -> str | None:
    region = self.costing_tree.identify_region(event.x, event.y)
    if region != "cell":
        return None
    column = self.costing_tree.identify_column(event.x)
    if column != "#2":
        return None
    row_id = self.costing_tree.identify_row(event.y)
    if not row_id:
        return None

    index = int(row_id) - 1
    if index < 0 or index >= len(self.filtered_costing_items):
        return "break"

    item = self.filtered_costing_items[index]
    group_key = tuple(item.get("group_key", ("", "", "")))
    if group_key in self.checked_costing_keys:
        self.checked_costing_keys.remove(group_key)
    else:
        self.checked_costing_keys.add(group_key)
    self.update_costing_tree_check_state(row_id)
    self.costing_tree.selection_set(row_id)
    self.costing_tree.focus(row_id)
    return "break"


def on_costing_tree_double_click(self, _event: object | None = None) -> str:
    self.edit_selected_costing_row()
    return "break"


def render_costing_variant_buttons(self, item: dict) -> None:
    for child in self.costing_variant_buttons_frame.winfo_children():
        child.destroy()

    variants = item.get("variants", [])
    for variant_index, variant in enumerate(variants):
        button = ttk.Button(
            self.costing_variant_buttons_frame,
            text=self.display_value(variant.get("color_label")),
            command=lambda idx=variant_index: self.load_costing_variant(item, idx),
        )
        button.pack(side="left", padx=(0, 6))


def load_costing_variant(self, item: dict, variant_index: int) -> None:
    variants = item.get("variants", [])
    if variant_index < 0 or variant_index >= len(variants):
        return
    self.costing_selected_variant_index = variant_index

    variant = variants[variant_index]
    preview_image = None
    image_path = str(variant.get("image_path", "")).strip()
    if image_path:
        preview_image = create_contained_photo(image_path, target_width=420, target_height=420)
    elif variant_index == 0:
        fallback_image_path = self.resolve_costing_item_image_path(item)
        if fallback_image_path:
            preview_image = create_contained_photo(fallback_image_path, target_width=420, target_height=420)
    if preview_image is not None:
        self.costing_preview_photo = preview_image
        self.costing_image_label.configure(image=preview_image, text="")
    else:
        self.costing_preview_photo = None
        self.costing_image_label.configure(image="", text=self.t("tech_pack_no_image"))

    self.costing_description_text.configure(state="normal")
    self.costing_description_text.delete("1.0", tk.END)
    source_kind = str(variant.get("source_kind", "")).strip().lower()
    if source_kind == "worksheet":
        detail_lines = [
            f"Source: {self.t('costing_source_kind_worksheet')}",
            f"Style: {self.display_value(variant.get('style_code'))}",
            f"Color: {self.display_value(variant.get('manf_color') or variant.get('color_label'))}",
            f"Color Code: {self.display_value(variant.get('color_code'))}",
            f"Product Type: {self.display_value(variant.get('product_type'))}",
            f"Ticket Type: {self.display_value(variant.get('ticket_type'))}",
            f"Packing Type: {self.display_value(variant.get('packing_type'))}",
            f"Description: {self.display_value(variant.get('description'))}",
            f"Fabrication: {self.display_value(variant.get('fabrication'))}",
            f"Brand: {self.display_value(variant.get('brand'))}",
            f"Dept #: {self.display_value(variant.get('dept_number'))}",
            f"Mj Class: {self.display_value(variant.get('mj_class'))}",
            f"Sub Class: {self.display_value(variant.get('sub_class'))}",
            f"Size Range: {self.display_value(variant.get('size_range'))}",
            f"Size Scale: {self.display_value(variant.get('size_scale'))}",
            f"Allocation Multiple: {self.display_value(variant.get('allocation_multiple'))}",
            f"Min: {self.display_value(variant.get('min_qty'))}",
            f"Max: {self.display_value(variant.get('max_qty'))}",
            f"Total Units: {self.display_value(variant.get('total_units'))}",
            f"Inner Pack: {self.display_value(variant.get('inner_pack'))}",
            f"Master Pack: {self.display_value(variant.get('outer_pack'))}",
            f"Hanger Supplier: {self.display_value(variant.get('hanger_supplier'))}",
            f"Hanger Type: {self.display_value(variant.get('hanger_type'))}",
            f"Item Retail: {self.display_value(variant.get('item_retail'))}",
            f"PO #: {self.display_value(variant.get('po_number'))}",
            f"Est. CBM: {self.display_value(variant.get('est_cbm'))}",
            f"Item Type for Freight: {self.display_value(variant.get('item_type_for_freight'))}",
            f"Workbook: {self.display_value(variant.get('source_workbook') or variant.get('source_pdf'))}",
            f"Sheet: {self.display_value(variant.get('source_sheet'))}",
            f"Row: {self.display_value(variant.get('source_row_number'))}",
        ]
        self.costing_description_text.insert("1.0", "\n".join(detail_lines))
    else:
        description = self.display_value(variant.get("description"))
        self.costing_description_text.insert("1.0", description)


def delete_selected_costing_styles(self) -> None:
    if not self.is_action_allowed("action.costing.delete_style"):
        return
    selected_keys = sorted(self.checked_costing_keys)
    if not selected_keys:
        return
    selected_items = [
        item
        for item in self.costing_items
        if tuple(item.get("group_key", ("", "", ""))) in selected_keys
    ]
    if not messagebox.askyesno(
        self.t("costing_delete_style"),
        self.t("costing_delete_confirm", count=len(selected_keys)),
    ):
        return

    delete_specs: list[object] = []
    for item in selected_items:
        variants = list(item.get("variants", []))
        first_variant = variants[0] if variants else {}
        if str(first_variant.get("source_kind", "")).strip().lower() == "worksheet":
            delete_specs.append(
                {
                    "source_kind": "worksheet",
                    "collection": item.get("collection"),
                    "season": item.get("source_season", item.get("season")),
                    "style_code": item.get("style_code"),
                    "source_workbook": first_variant.get("source_workbook") or first_variant.get("source_pdf"),
                    "source_pdf": first_variant.get("source_pdf"),
                    "source_sheet": first_variant.get("source_sheet"),
                    "source_row_number": first_variant.get("source_row_number"),
                }
            )
        else:
            delete_specs.append(
                (
                    item.get("collection"),
                    item.get("source_season", item.get("season")),
                    item.get("style_code"),
                )
            )

    self.delete_costing_styles_from_source(delete_specs)
    self.checked_costing_keys.clear()
    self.load_saved_costing_imports()
    self.set_status(self.t("costing_delete_style"))


def get_selected_costing_item_and_variant(self) -> tuple[dict | None, dict | None]:
    if self.costing_selected_index is None or self.costing_selected_index >= len(self.filtered_costing_items):
        return None, None
    item = self.filtered_costing_items[self.costing_selected_index]
    variants = list(item.get("variants", []))
    variant = variants[self.costing_selected_variant_index] if 0 <= self.costing_selected_variant_index < len(variants) else (variants[0] if variants else None)
    return dict(item), dict(variant) if isinstance(variant, dict) else None


def load_costing_form_records_from_source(self) -> list[dict]:
    default_rows: list[dict] = []
    if self.should_use_server_data_source():
        if self.is_local_data_mode():
            self.ensure_local_server_running()
        try:
            payload = fetch_app_setting(self.server_settings, COSTING_FORM_RECORDS_KEY, default_rows)
            return [normalize_costing_form_record(item) for item in list(payload or []) if isinstance(item, dict)]
        except Exception:
            pass
    payload = get_app_setting(DB_PATH, COSTING_FORM_RECORDS_KEY, default_rows)
    return [normalize_costing_form_record(item) for item in list(payload or []) if isinstance(item, dict)]


def save_costing_form_record_to_source(self, record: dict) -> dict:
    normalized = normalize_costing_form_record(record, record.get("style_code"))
    style_code = str(normalized.get("style_code", "")).strip()
    if not style_code:
        return {}
    existing_rows = self.load_costing_form_records_from_source()
    merged_rows: list[dict] = []
    replaced = False
    for row in existing_rows:
        if normalize_style_identifier(row.get("style_code")) == style_code:
            merged_rows.append(normalized)
            replaced = True
        else:
            merged_rows.append(row)
    if not replaced:
        merged_rows.append(normalized)
    if self.should_use_server_data_source():
        if self.is_local_data_mode():
            self.ensure_local_server_running()
        saved = save_app_setting(self.server_settings, COSTING_FORM_RECORDS_KEY, merged_rows)
        rows = [normalize_costing_form_record(item) for item in list(saved or []) if isinstance(item, dict)]
    else:
        saved = set_app_setting(DB_PATH, COSTING_FORM_RECORDS_KEY, merged_rows)
        rows = [normalize_costing_form_record(item) for item in list(saved or []) if isinstance(item, dict)]
    return next((row for row in rows if normalize_style_identifier(row.get("style_code")) == style_code), normalized)


def get_costing_form_record_for_style(self, style_code: object) -> dict:
    normalized_style = normalize_style_identifier(style_code)
    for record in self.load_costing_form_records_from_source():
        if normalize_style_identifier(record.get("style_code")) == normalized_style:
            return record
    return default_costing_form_record(normalized_style)


def get_costing_form_consumption_lines(self, style_code: object, version_type: str) -> list[dict]:
    normalized_style = normalize_style_identifier(style_code)
    if not normalized_style:
        return []
    if not getattr(self, "tech_pack_library", []):
        try:
            self.refresh_tech_pack_module(silent=True)
        except Exception:
            pass
    row = self.get_tech_pack_row_by_style_code(normalized_style) if hasattr(self, "get_tech_pack_row_by_style_code") else None
    if row is None:
        for item in list(getattr(self, "tech_pack_library", []) or []):
            if normalize_style_identifier(item.get("style_code")) == normalized_style:
                row = dict(item)
                break
    if not isinstance(row, dict):
        return []
    versions = self.get_tech_pack_consumption_versions(row) if hasattr(self, "get_tech_pack_consumption_versions") else []
    target = next((item for item in versions if str(item.get("version_type", "")).strip().lower() == version_type), {})
    bom_by_code = {
        str(item.get("item_code", "")).strip(): dict(item)
        for item in list(getattr(self, "bom_items", []) or [])
        if isinstance(item, dict) and str(item.get("item_code", "")).strip()
    }
    lines: list[dict] = []
    for line in list(target.get("lines", [])) if isinstance(target, dict) else []:
        if not isinstance(line, dict):
            continue
        item_code = str(line.get("bom_item_code") or line.get("item_code") or "").strip()
        bom_row = bom_by_code.get(item_code, {})
        lines.append(
            normalize_costing_form_line(
                {
                    "item_code": item_code,
                    "name": line.get("bom_item_name") or bom_row.get("item_name"),
                    "type": line.get("fabric_type") or bom_row.get("item_type"),
                    "color": line.get("color") or bom_row.get("color"),
                    "consumption": line.get("consumption"),
                    "uom": line.get("uom") or bom_row.get("uom") or bom_row.get("purchase_uom"),
                    "loss_percent": bom_row.get("waste_percent"),
                    "price": bom_row.get("standard_price"),
                    "currency": bom_row.get("currency"),
                }
            )
        )
    return lines


def build_costing_form_record_context(self, item: dict, variant: dict | None) -> dict:
    source = dict(variant or item or {})
    style_code = normalize_style_identifier(source.get("style_code") or source.get("mfg_style") or item.get("style_code"))
    record = self.get_costing_form_record_for_style(style_code)
    if not record.get("material_lines"):
        record["material_lines"] = self.get_costing_form_consumption_lines(style_code, "costing")
    if not record.get("actual_lines"):
        record["actual_lines"] = self.get_costing_form_consumption_lines(style_code, "production")
    if not str(record.get("image_path", "")).strip():
        tech_pack_row = {}
        for source_row in list(getattr(self, "tech_pack_library", []) or []):
            if isinstance(source_row, dict) and normalize_style_identifier(source_row.get("style_code")) == style_code:
                tech_pack_row = dict(source_row)
                break
        record["image_path"] = _costing_form_image_path_from_source(tech_pack_row) or _costing_form_image_path_from_source(source)
    return normalize_costing_form_record(record, style_code)


def edit_selected_costing_row(self) -> None:
    if not self.is_action_allowed("action.costing.edit_row"):
        return
    item, variant = self.get_selected_costing_item_and_variant()
    if item is None:
        messagebox.showwarning(self.t("costing_edit_title"), self.t("costing_empty"))
        return
    record = self.build_costing_form_record_context(item, variant)
    style_code = str(record.get("style_code", "")).strip()

    dialog = tk.Toplevel(self.root)
    dialog.title(f"Costing Form - {style_code or '-'}")
    dialog.transient(self.root)
    dialog.grab_set()
    dialog.geometry("1180x760")
    dialog.minsize(980, 640)

    frame = ttk.Frame(dialog, padding=14)
    frame.pack(fill="both", expand=True)
    frame.columnconfigure(0, weight=1)
    frame.rowconfigure(1, weight=1)

    ttk.Label(frame, text=f"Costing Form - {style_code or '-'}", style="Section.TLabel").grid(row=0, column=0, sticky="w")
    notebook = ttk.Notebook(frame)
    notebook.grid(row=1, column=0, sticky="nsew", pady=(12, 0))

    row_tab = ttk.Frame(notebook, padding=14)
    header_tab = ttk.Frame(notebook, padding=14)
    material_tab = ttk.Frame(notebook, padding=14)
    logistics_tab = ttk.Frame(notebook, padding=14)
    labour_tab = ttk.Frame(notebook, padding=14)
    summary_tab = ttk.Frame(notebook, padding=14)
    notebook.add(row_tab, text="Costing Row")
    notebook.add(header_tab, text="Header")
    notebook.add(material_tab, text="Material Cost")
    notebook.add(logistics_tab, text="Import / Logistics")
    notebook.add(labour_tab, text="Labour Cost")
    notebook.add(summary_tab, text="Summary")

    row_fields = [
        ("collection", self.t("costing_collection_column")),
        ("dept_number", self.t("costing_dept_column")),
        ("mj_class", self.t("costing_mj_class_column")),
        ("sub_class", self.t("costing_sub_class_column")),
        ("mfg_style", self.t("costing_style_column")),
        ("description", self.t("costing_description_column")),
        ("fabrication", self.t("costing_fabrication_column")),
        ("mfg_color", self.t("costing_manf_color_column")),
        ("color_code", self.t("costing_color_code_column")),
        ("product_type", self.t("costing_product_type_column")),
        ("ticket_type", self.t("costing_ticket_type_column")),
        ("packing_type", self.t("costing_packing_type_column")),
        ("size_range", self.t("costing_size_range_column")),
        ("size_scale", self.t("costing_size_scale_column")),
        ("allocation_multiple", self.t("costing_allocation_multiple_column")),
        ("min_qty", self.t("costing_min_column")),
        ("max_qty", self.t("costing_max_column")),
        ("inner_pack", self.t("costing_inner_column")),
        ("outer_pack", self.t("costing_master_pack_column")),
        ("hanger_supplier", self.t("costing_hanger_supplier_column")),
        ("hanger_type", self.t("costing_hanger_type_column")),
        ("item_retail", self.t("costing_item_retail_column")),
        ("po_number", self.t("costing_po_number_column")),
        ("total_units", self.t("costing_total_units_column")),
    ]
    row_tab.columnconfigure(1, weight=1)
    row_vars: dict[str, tk.StringVar] = {}
    source_variant = dict(variant or {})
    for row_index, (key, label) in enumerate(row_fields):
        ttk.Label(row_tab, text=label).grid(row=row_index, column=0, sticky="w", padx=(0, 10), pady=4)
        raw_value = source_variant.get(key)
        if key == "mfg_style":
            raw_value = source_variant.get("mfg_style") or source_variant.get("style_code") or item.get("style_code")
        elif key == "mfg_color":
            raw_value = source_variant.get("mfg_color") or source_variant.get("manf_color") or source_variant.get("color_label")
        text_value = "" if self.is_missing_value(raw_value) else str(raw_value or "")
        row_vars[key] = tk.StringVar(value=text_value)
        ttk.Entry(row_tab, textvariable=row_vars[key]).grid(row=row_index, column=1, sticky="ew", pady=4)

    status_row_index = len(row_fields)
    ttk.Label(row_tab, text="Status").grid(row=status_row_index, column=0, sticky="w", padx=(0, 10), pady=4)
    status_var = tk.StringVar(value=normalize_workflow_status((variant or {}).get("status") or item.get("status"), "draft"))
    ttk.Combobox(row_tab, textvariable=status_var, values=("draft", "checking", "approved"), state="readonly").grid(
        row=status_row_index,
        column=1,
        sticky="ew",
        pady=4,
    )
    active_var = tk.BooleanVar(value=parse_active_flag((variant or {}).get("is_active", item.get("is_active", True)), True))
    ttk.Checkbutton(row_tab, text="Active", variable=active_var).grid(
        row=status_row_index + 1,
        column=1,
        sticky="w",
        pady=(6, 4),
    )

    header_vars = {
        "style_code": tk.StringVar(value=row_vars["mfg_style"].get().strip() or style_code),
        "qty": tk.StringVar(value=str(record.get("qty", "")).strip()),
        "offer_date": tk.StringVar(value=str(record.get("offer_date", "")).strip()),
        "confirm_date": tk.StringVar(value=str(record.get("confirm_date", "")).strip()),
        "buyer_target": tk.StringVar(value=str(record.get("buyer_target", "")).strip()),
        "approved_by": tk.StringVar(value=str(record.get("approved_by", "")).strip()),
        "approved_date": tk.StringVar(value=str(record.get("approved_date", "")).strip()),
        "buyer_confirm": tk.StringVar(value=str(record.get("buyer_confirm", "")).strip()),
    }

    def sync_header_from_row(*_args: object) -> None:
        header_vars["style_code"].set(row_vars["mfg_style"].get().strip())

    row_vars["mfg_style"].trace_add("write", sync_header_from_row)

    def add_header_entry(row_index: int, col_index: int, label: str, key: str, *, readonly: bool = False) -> ttk.Entry:
        host = ttk.Frame(header_tab)
        host.grid(row=row_index, column=col_index, sticky="ew", padx=8, pady=(0, 12))
        host.columnconfigure(0, weight=1)
        ttk.Label(host, text=label).grid(row=0, column=0, sticky="w", pady=(0, 4))
        entry = ttk.Entry(host, textvariable=header_vars[key])
        if readonly:
            entry.configure(state="readonly")
        entry.grid(row=1, column=0, sticky="ew")
        return entry

    for col in range(3):
        header_tab.columnconfigure(col, weight=1)
    add_header_entry(0, 0, "Style", "style_code", readonly=True)
    add_header_entry(0, 1, "Qty", "qty")
    offer_entry = add_header_entry(0, 2, "Offer date", "offer_date")
    confirm_entry = add_header_entry(1, 0, "Confirm date", "confirm_date")
    add_header_entry(1, 1, "Buyer target", "buyer_target")
    add_header_entry(1, 2, "Approved by", "approved_by")
    approved_date_entry = add_header_entry(2, 0, "Approved date", "approved_date")
    if hasattr(self, "bind_date_entry_picker"):
        self.bind_date_entry_picker(offer_entry, header_vars["offer_date"])
        self.bind_date_entry_picker(confirm_entry, header_vars["confirm_date"])
        self.bind_date_entry_picker(approved_date_entry, header_vars["approved_date"])

    line_headers = ("Item Code", "Name", "Type", "Color", "Cons", "UOM", "Loss %", "Price", "Currency", "Note")

    def build_line_editor(parent: ttk.Frame, lines: list[dict]) -> list[dict[str, tk.StringVar]]:
        parent.columnconfigure(0, weight=1)
        canvas = tk.Canvas(parent, highlightthickness=0)
        canvas.grid(row=0, column=0, sticky="nsew")
        scroll = ttk.Scrollbar(parent, orient="vertical", command=canvas.yview)
        scroll.grid(row=0, column=1, sticky="ns")
        parent.rowconfigure(0, weight=1)
        canvas.configure(yscrollcommand=scroll.set)
        table = ttk.Frame(canvas)
        window_id = canvas.create_window((0, 0), window=table, anchor="nw")
        for col, header in enumerate(line_headers):
            table.columnconfigure(col, weight=1)
            ttk.Label(table, text=header, style="FieldLabel.TLabel").grid(row=0, column=col, sticky="ew", padx=3, pady=(0, 6))
        row_vars: list[dict[str, tk.StringVar]] = []

        def add_row(seed: dict | None = None) -> None:
            values = normalize_costing_form_line(seed or {})
            row_index = len(row_vars) + 1
            vars_map = {key: tk.StringVar(value=str(values.get(key, "")).strip()) for key in ("item_code", "name", "type", "color", "consumption", "uom", "loss_percent", "price", "currency", "note")}
            row_vars.append(vars_map)
            for col, key in enumerate(("item_code", "name", "type", "color", "consumption", "uom", "loss_percent", "price", "currency", "note")):
                ttk.Entry(table, textvariable=vars_map[key], width=12 if key not in {"name", "note"} else 20).grid(row=row_index, column=col, sticky="ew", padx=3, pady=3)

        for line in lines:
            add_row(line)
        if not row_vars:
            add_row({})
        actions = ttk.Frame(parent)
        actions.grid(row=1, column=0, sticky="w", pady=(8, 0))
        ttk.Button(actions, text="+", width=4, command=lambda: add_row({})).pack(side="left")

        def sync_canvas(_event: object | None = None) -> None:
            canvas.configure(scrollregion=canvas.bbox("all"))
            canvas.itemconfigure(window_id, width=canvas.winfo_width())

        table.bind("<Configure>", sync_canvas)
        canvas.bind("<Configure>", sync_canvas)
        return row_vars

    material_tab.columnconfigure(0, weight=1)
    material_tab.rowconfigure(0, weight=1)
    material_tab.rowconfigure(1, weight=1)
    costing_material_box = ttk.LabelFrame(material_tab, text="Costing", padding=8)
    costing_material_box.grid(row=0, column=0, sticky="nsew", pady=(0, 10))
    production_material_box = ttk.LabelFrame(material_tab, text="Production (Actual)", padding=8)
    production_material_box.grid(row=1, column=0, sticky="nsew")
    material_vars = build_line_editor(costing_material_box, list(record.get("material_lines", [])))
    actual_vars = build_line_editor(production_material_box, list(record.get("actual_lines", [])))

    def build_charge_editor(parent: ttk.Frame, lines: list[dict], *, unit_values: tuple[str, ...]) -> list[dict[str, tk.StringVar]]:
        parent.columnconfigure(0, weight=1)
        table = ttk.Frame(parent)
        table.grid(row=0, column=0, sticky="nsew")
        parent.rowconfigure(0, weight=1)
        headers = ("Name", "Costing", "Actual", "Unit", "Note")
        for col, header in enumerate(headers):
            table.columnconfigure(col, weight=1)
            ttk.Label(table, text=header, style="FieldLabel.TLabel").grid(row=0, column=col, sticky="ew", padx=4)
        rows: list[dict[str, tk.StringVar]] = []

        def add_row(seed: dict | None = None) -> None:
            row = dict(seed or {})
            row_index = len(rows) + 1
            values = {
                "name": tk.StringVar(value=str(row.get("name", "")).strip()),
                "costing_value": tk.StringVar(value=str(row.get("costing_value", "")).strip()),
                "actual_value": tk.StringVar(value=str(row.get("actual_value", "")).strip()),
                "unit": tk.StringVar(value=str(row.get("unit", "")).strip().upper() or unit_values[0]),
                "note": tk.StringVar(value=str(row.get("note", "")).strip()),
            }
            rows.append(values)
            ttk.Entry(table, textvariable=values["name"]).grid(row=row_index, column=0, sticky="ew", padx=4, pady=4)
            ttk.Entry(table, textvariable=values["costing_value"]).grid(row=row_index, column=1, sticky="ew", padx=4, pady=4)
            ttk.Entry(table, textvariable=values["actual_value"]).grid(row=row_index, column=2, sticky="ew", padx=4, pady=4)
            ttk.Combobox(table, textvariable=values["unit"], values=unit_values, state="readonly", width=8).grid(row=row_index, column=3, sticky="ew", padx=4, pady=4)
            ttk.Entry(table, textvariable=values["note"]).grid(row=row_index, column=4, sticky="ew", padx=4, pady=4)

        for line in lines:
            add_row(line)
        if not rows:
            add_row({})
        actions_row = ttk.Frame(parent)
        actions_row.grid(row=1, column=0, sticky="w", pady=(8, 0))
        ttk.Button(actions_row, text="+", width=4, command=lambda: add_row({})).pack(side="left")
        return rows

    logistics_vars = build_charge_editor(
        logistics_tab,
        list(record.get("logistics_lines", [])),
        unit_values=COSTING_FORM_COST_UNIT_OPTIONS,
    )
    labour_vars = build_charge_editor(
        labour_tab,
        list(record.get("labour_lines", [])),
        unit_values=COSTING_FORM_CURRENCY_OPTIONS,
    )

    summary_tab.columnconfigure(1, weight=1)
    summary_values = {
        "FOB": tk.StringVar(value=""),
        "DDP": tk.StringVar(value=""),
        "Included 5%": tk.StringVar(value=""),
        "Included 10%": tk.StringVar(value=""),
        "Included 15%": tk.StringVar(value=""),
        "Included 20%": tk.StringVar(value=""),
    }
    ttk.Label(summary_tab, text="Buyer confirm").grid(row=0, column=0, sticky="w", padx=(0, 10), pady=6)
    ttk.Entry(summary_tab, textvariable=header_vars["buyer_confirm"]).grid(row=0, column=1, sticky="ew", pady=6)
    for idx, (label, var) in enumerate(summary_values.items(), start=1):
        ttk.Label(summary_tab, text=label).grid(row=idx, column=0, sticky="w", padx=(0, 10), pady=6)
        ttk.Label(summary_tab, textvariable=var).grid(row=idx, column=1, sticky="w", pady=6)

    def collect_line_vars(rows: list[dict[str, tk.StringVar]]) -> list[dict]:
        collected = []
        for vars_map in rows:
            line = normalize_costing_form_line({key: variable.get().strip() for key, variable in vars_map.items()})
            if any(str(line.get(key, "")).strip() for key in ("item_code", "name", "consumption", "price", "note")):
                collected.append(line)
        return collected

    def collect_charge_vars(rows: list[dict[str, tk.StringVar]]) -> list[dict]:
        collected = []
        for vars_map in rows:
            line = {key: variable.get().strip() for key, variable in vars_map.items()}
            if any(str(line.get(key, "")).strip() for key in ("name", "costing_value", "actual_value", "note")):
                line["unit"] = str(line.get("unit", "")).strip().upper()
                collected.append(line)
        return collected

    def refresh_summary() -> None:
        material_total = sum(_costing_line_cost(line) for line in collect_line_vars(material_vars))
        labour_total = sum(_costing_safe_float(row["costing_value"].get()) for row in labour_vars)
        logistics_total = sum(
            _costing_charge_value(
                {key: variable.get().strip() for key, variable in row.items()},
                material_total + labour_total,
                "costing_value",
            )
            for row in logistics_vars
        )
        fob = material_total + labour_total
        ddp = (fob + logistics_total) * 1.025
        summary_values["FOB"].set(_costing_format_number(fob))
        summary_values["DDP"].set(_costing_format_number(ddp))
        for pct in (5, 10, 15, 20):
            summary_values[f"Included {pct}%"].set(_costing_format_number(ddp * (1 + pct / 100)))

    refresh_summary()
    ttk.Button(summary_tab, text="Refresh summary", command=refresh_summary).grid(row=7, column=1, sticky="w", pady=(10, 0))

    actions = ttk.Frame(frame)
    actions.grid(row=2, column=0, sticky="e", pady=(12, 0))

    def save_action() -> None:
        style_value = row_vars["mfg_style"].get().strip()
        color_value = row_vars["mfg_color"].get().strip()
        row_updates = {
            "collection": row_vars["collection"].get().strip(),
            "dept_number": row_vars["dept_number"].get().strip(),
            "mfg_style": style_value,
            "style_code": style_value,
            "description": row_vars["description"].get().strip(),
            "fabrication": row_vars["fabrication"].get().strip(),
            "mfg_color": color_value,
            "manf_color": color_value,
            "mj_class": row_vars["mj_class"].get().strip(),
            "sub_class": row_vars["sub_class"].get().strip(),
            "color_code": row_vars["color_code"].get().strip(),
            "product_type": row_vars["product_type"].get().strip(),
            "ticket_type": row_vars["ticket_type"].get().strip(),
            "packing_type": row_vars["packing_type"].get().strip(),
            "total_units": row_vars["total_units"].get().strip(),
            "allocation_multiple": row_vars["allocation_multiple"].get().strip(),
            "min_qty": row_vars["min_qty"].get().strip(),
            "max_qty": row_vars["max_qty"].get().strip(),
            "inner_pack": row_vars["inner_pack"].get().strip(),
            "outer_pack": row_vars["outer_pack"].get().strip(),
            "size_range": row_vars["size_range"].get().strip(),
            "size_scale": row_vars["size_scale"].get().strip(),
            "hanger_supplier": row_vars["hanger_supplier"].get().strip(),
            "hanger_type": row_vars["hanger_type"].get().strip(),
            "item_retail": row_vars["item_retail"].get().strip(),
            "po_number": row_vars["po_number"].get().strip(),
        }
        current_style_code = normalize_style_identifier(style_value or style_code)
        refreshed = normalize_costing_form_record(
            {
                "style_code": current_style_code,
                "qty": header_vars["qty"].get().strip(),
                "offer_date": header_vars["offer_date"].get().strip(),
                "confirm_date": header_vars["confirm_date"].get().strip(),
                "buyer_target": header_vars["buyer_target"].get().strip(),
                "buyer_confirm": header_vars["buyer_confirm"].get().strip(),
                "approved_by": header_vars["approved_by"].get().strip(),
                "approved_date": header_vars["approved_date"].get().strip(),
                "image_path": str(record.get("image_path", "")).strip(),
                "material_lines": collect_line_vars(material_vars),
                "actual_lines": collect_line_vars(actual_vars),
                "logistics_lines": collect_charge_vars(logistics_vars),
                "labour_lines": collect_charge_vars(labour_vars),
                "created_at": str(record.get("created_at", "")).strip() or _costing_form_now_text(),
                "updated_at": _costing_form_now_text(),
            },
            current_style_code,
        )
        target_status = normalize_workflow_status(status_var.get(), "draft")
        if target_status == "approved" and not self.is_action_allowed("action.costing.approve"):
            self.deny_permission_action("action.costing.approve")
            return
        self.save_costing_form_record_to_source(refreshed)
        if variant and str(variant.get("source_kind", "")).strip().lower() == "worksheet":
            try:
                self.update_costing_worksheet_row_in_source(
                    source_workbook=str(variant.get("source_workbook") or variant.get("source_pdf") or "").strip(),
                    source_sheet=str(variant.get("source_sheet") or "").strip(),
                    source_row_number=int(variant.get("source_row_number") or 0),
                    updates={
                        **row_updates,
                        "status": target_status,
                        "is_active": bool(active_var.get()),
                    },
                )
            except Exception as exc:
                messagebox.showwarning(self.t("warning_title"), f"Costing form saved, but worksheet row was not updated.\n\n{exc}")
        self.record_entity_history(
            scope="costing",
            entity_key=current_style_code,
            entity_label=current_style_code,
            action="update",
            before={"row": source_variant, "form": record},
            after={"row": row_updates, "form": refreshed},
            summary=f"Updated costing row {current_style_code}",
        )
        dialog.destroy()
        self.load_saved_costing_imports()
        self.set_status(self.t("costing_edit_saved"))

    ttk.Button(actions, text=self.t("collection_list_save"), command=save_action).pack(side="right")
    ttk.Button(actions, text=self.t("close"), command=dialog.destroy).pack(side="right", padx=(0, 8))
    dialog.wait_window()


def load_costing_form_assets_from_source(self) -> dict[str, object]:
    default_value = default_costing_form_assets()
    if self.should_use_server_data_source():
        if self.is_local_data_mode():
            self.ensure_local_server_running()
        try:
            payload = fetch_app_setting(self.server_settings, COSTING_FORM_ASSETS_KEY, default_value)
            merged = dict(default_value)
            if isinstance(payload, dict):
                merged.update(payload)
            return merged
        except Exception:
            pass
    payload = get_app_setting(DB_PATH, COSTING_FORM_ASSETS_KEY, default_value)
    merged = dict(default_value)
    if isinstance(payload, dict):
        merged.update(payload)
    return merged


def save_costing_form_assets_to_source(self, payload: dict[str, object]) -> dict[str, object]:
    normalized = dict(default_costing_form_assets())
    normalized.update(payload if isinstance(payload, dict) else {})
    if self.should_use_server_data_source():
        if self.is_local_data_mode():
            self.ensure_local_server_running()
        saved = save_app_setting(self.server_settings, COSTING_FORM_ASSETS_KEY, normalized)
        return dict(saved) if isinstance(saved, dict) else normalized
    saved = set_app_setting(DB_PATH, COSTING_FORM_ASSETS_KEY, normalized)
    return dict(saved) if isinstance(saved, dict) else normalized


def resolve_costing_form_template_asset(self, value: str, *, fallback_suffix: str = ".xlsx") -> Path | None:
    raw_value = str(value or "").strip()
    if not raw_value:
        return None
    candidate = Path(raw_value)
    if candidate.exists() and candidate.is_file():
        return candidate
    if raw_value.lower().startswith(("http://", "https://")):
        try:
            split = urlsplit(raw_value)
            normalized_url = urlunsplit(
                (
                    split.scheme,
                    split.netloc,
                    quote(split.path, safe="/:@"),
                    quote(split.query, safe="=&?/:@"),
                    quote(split.fragment, safe="=&?/:@"),
                )
            )
            parsed = urlparse(normalized_url)
            suffix = Path(parsed.path).suffix or fallback_suffix
            cache_dir = DATA_DIR / "costing_form_assets"
            cache_dir.mkdir(parents=True, exist_ok=True)
            target_path = cache_dir / f"{hashlib.sha1(raw_value.encode('utf-8')).hexdigest()}{suffix}"
            if target_path.exists() and target_path.is_file():
                return target_path
            request = Request(normalized_url, headers={"User-Agent": "CitiFoxERPApp/desktop"})
            with open_url(request, timeout=20) as response:
                target_path.write_bytes(response.read())
            return target_path if target_path.exists() else None
        except Exception:
            return None
    return None


def build_clean_costing_form_template(self, target_path: Path) -> Path:
    target_path.parent.mkdir(parents=True, exist_ok=True)
    wb = Workbook()
    ws = wb.active
    ws.title = "Costing Form"
    widths = {
        "A": 20.625,
        "B": 9.625,
        "C": 9.625,
        "D": 9.625,
        "E": 13.0,
        "F": 0.875,
        "G": 20.625,
        "H": 9.625,
        "I": 9.625,
        "J": 9.625,
        "K": 13.0,
        "L": 0.875,
        "M": 41.25,
        "N": 4.625,
    }
    for col, width in widths.items():
        ws.column_dimensions[col].width = width
    default_heights = {
        1: 12.95,
        15: 20.1,
        16: 6.0,
        17: 36.0,
        20: 4.5,
        29: 4.5,
        40: 6.0,
        46: 6.0,
        51: 5.1,
    }
    for row in range(1, 55):
        ws.row_dimensions[row].height = default_heights.get(row, 15.95)
    section_fill = PatternFill("solid", fgColor="D9D9D9")
    thin = Side(style="thin", color="808080")
    border = Border(left=thin, right=thin, top=thin, bottom=thin)
    for cell_range in (
        "A1:E1",
        "G1:K1",
        "M2:M19",
        "C4:E7",
        "C8:E8",
        "B11:E14",
        "J4:K4",
        "J5:K5",
        "J6:K6",
        "J7:K7",
        "J8:K8",
        "J9:K9",
        "J10:K10",
        "J11:K11",
        "J12:K12",
        "J13:K13",
        "G14:K14",
        "B17:E17",
        "B18:E18",
        "H17:K17",
        "H18:K18",
        "A15:E15",
        "G15:K15",
    ):
        ws.merge_cells(cell_range)
    ws["A1"] = "COSTING"
    ws["G1"] = "ACTUAL"
    ws["M2"] = "PHOTO"
    for cell in ("A1", "G1"):
        ws[cell].alignment = Alignment(horizontal="center")
    ws["M2"].font = Font(bold=True)
    ws["M2"].alignment = Alignment(horizontal="center", vertical="center")
    labels = [("A2", "STYLE NO:"), ("A3", "Q'TY:"), ("C3", "Offer Date:"), ("G2", "STYLE NO:"), ("G3", "Q'TY:"), ("I3", "ConfirmDate:"), ("J4", "Remark")]
    for coord, label in labels:
        ws[coord] = label
        ws[coord].font = Font(bold=True)
    ws["A4"] = "Included 5%"
    ws["A5"] = "Included 10%"
    ws["A6"] = "Included 15%"
    ws["A7"] = "Included 20%"
    ws["A8"] = "Buyer confirm"
    ws["A9"] = "Buyer target (calculated)"
    ws["A11"] = "Approved by:"
    ws["A13"] = "Approved date:"
    ws["A15"] = "COSTING"
    ws["G15"] = "ACTUAL"
    ws["A17"] = "Fabric"
    ws["A18"] = "Supplier"
    ws["A19"] = "Width"
    ws["C19"] = "Weight"
    ws["G17"] = "Fabric"
    ws["G18"] = "Supplier"
    ws["G19"] = "Width"
    ws["I19"] = "Weight"
    for row, headers in (
        (21, ("Fabric", "Cons", "Lost %", "Price", "Cost")),
        (30, ("Trim", "Cons", "Lost %", "Price", "Cost")),
    ):
        for offset, value in enumerate(headers):
            ws.cell(row=row, column=1 + offset, value=value)
            ws.cell(row=row, column=7 + offset, value=value)
    for row, label in ((28, "Total"), (39, "Total"), (45, "Total"), (50, "Total")):
        ws.cell(row=row, column=1, value=label)
        ws.cell(row=row, column=7, value=label)
    for row, label in (
        (41, "Sewing Labor"),
        (42, "Printing"),
        (43, "Washing"),
        (44, "Transfer"),
        (47, "Duty Rate"),
        (48, "Exim"),
        (49, "LA warehouse"),
    ):
        ws.cell(row=row, column=1, value=label)
        ws.cell(row=row, column=7, value=label)
    ws["M21"] = "Balance"
    ws["M30"] = "Trim"
    ws["M41"] = "Labor cost"
    ws["M47"] = "EXIM"
    ws["D52"] = "FOB"
    ws["D53"] = "DDP"
    ws["J52"] = "FOB"
    ws["J53"] = "DDP"
    for row in range(1, 55):
        for col in range(1, 15):
            cell = ws.cell(row=row, column=col)
            cell.border = border
            cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
            if row in {1, 15}:
                cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
            if row in {21, 30}:
                cell.fill = section_fill
            if row in {15}:
                cell.font = Font(bold=True)
            if col in {5, 11, 13} and row >= 21:
                cell.number_format = '$#,##0.00'
            elif col in {3, 9} and row >= 21:
                cell.number_format = '0.00%'
    wb.save(target_path)
    return target_path


def ensure_costing_form_template_asset(self) -> Path:
    cache_dir = DATA_DIR / "costing_form_assets"
    cache_dir.mkdir(parents=True, exist_ok=True)
    local_template = cache_dir / COSTING_FORM_TEMPLATE_FILENAME
    assets = self.load_costing_form_assets_from_source()
    template_url = str(assets.get("template_url", "")).strip()
    template_path = self.resolve_costing_form_template_asset(template_url)
    if template_path is not None:
        return template_path
    if not local_template.exists():
        self.build_clean_costing_form_template(local_template)
    if self.should_use_server_data_source() and not template_url and hasattr(self, "upload_shared_file_reference"):
        uploaded_url = self.upload_shared_file_reference(local_template, bucket=COSTING_FORM_TEMPLATE_BUCKET)
        if uploaded_url and str(uploaded_url).lower().startswith(("http://", "https://")):
            assets["template_url"] = uploaded_url
            self.save_costing_form_assets_to_source(assets)
    return local_template


def build_costing_form_file(self, record: dict, save_path: Path) -> Path:
    template_path = self.ensure_costing_form_template_asset()
    wb = load_workbook(template_path)
    ws = wb.active
    style_code = str(record.get("style_code", "")).strip()
    qty = str(record.get("qty", "")).strip()
    ws["B2"] = style_code
    ws["H2"] = style_code
    ws["B3"] = _costing_safe_float(qty) if qty else ""
    ws["H3"] = _costing_safe_float(qty) if qty else ""
    ws["D3"] = str(record.get("offer_date", "")).strip()
    ws["J3"] = str(record.get("confirm_date", "")).strip()
    ws["B8"] = _costing_safe_float(record.get("buyer_confirm")) if str(record.get("buyer_confirm", "")).strip() else ""
    ws["B9"] = _costing_safe_float(record.get("buyer_target")) if str(record.get("buyer_target", "")).strip() else ""
    ws["A12"] = str(record.get("approved_by", "")).strip()
    ws["A14"] = str(record.get("approved_date", "")).strip()
    ws["C4"] = str(record.get("lead_time_note", "")).strip()

    image_path = str(record.get("image_path", "")).strip()
    resolved_image_path = resolve_image_source(image_path) if image_path else None
    if resolved_image_path:
        try:
            xl_image = XLImage(str(resolved_image_path))
            xl_image.width = 294
            xl_image.height = 400
            ws.add_image(xl_image, "M2")
        except Exception:
            ws["M2"] = "PHOTO"

    def copy_row_style(source_row: int, target_row: int) -> None:
        ws.row_dimensions[target_row].height = ws.row_dimensions[source_row].height
        for col in range(1, 15):
            source_cell = ws.cell(row=source_row, column=col)
            target_cell = ws.cell(row=target_row, column=col)
            target_cell._style = copy(source_cell._style)
            target_cell.font = copy(source_cell.font)
            target_cell.fill = copy(source_cell.fill)
            target_cell.border = copy(source_cell.border)
            target_cell.alignment = copy(source_cell.alignment)
            target_cell.number_format = source_cell.number_format

    def write_lines(lines: list[dict], start_row: int, end_row: int, start_col: int) -> None:
        for row in range(start_row, end_row + 1):
            for col in range(start_col, start_col + 5):
                ws.cell(row=row, column=col).value = ""
        for offset, line in enumerate(lines[: end_row - start_row + 1]):
            row = start_row + offset
            ws.cell(row=row, column=start_col, value=str(line.get("name") or line.get("item_code") or "").strip())
            ws.cell(row=row, column=start_col + 1, value=_costing_safe_float(line.get("consumption")) if str(line.get("consumption", "")).strip() else "")
            ws.cell(row=row, column=start_col + 2, value=_costing_percent_float(line.get("loss_percent")) if str(line.get("loss_percent", "")).strip() else "")
            ws.cell(row=row, column=start_col + 3, value=_costing_safe_float(line.get("price")) if str(line.get("price", "")).strip() else "")
            ws.cell(row=row, column=start_col + 4, value=f"={ws.cell(row=row, column=start_col+1).coordinate}*(1+{ws.cell(row=row, column=start_col+2).coordinate})*{ws.cell(row=row, column=start_col+3).coordinate}")
            ws.cell(row=row, column=start_col + 2).number_format = "0.00%"
            ws.cell(row=row, column=start_col + 3).number_format = '$#,##0.00'
            ws.cell(row=row, column=start_col + 4).number_format = '$#,##0.00'

    def write_charge_lines(lines: list[dict], start_row: int, end_row: int, start_col: int, base_cell: str) -> None:
        for row in range(start_row, end_row + 1):
            for col in range(start_col, start_col + 5):
                ws.cell(row=row, column=col).value = ""
        for offset, line in enumerate(lines[: end_row - start_row + 1]):
            row = start_row + offset
            value_cell = ws.cell(row=row, column=start_col + 1).coordinate
            unit_cell = ws.cell(row=row, column=start_col + 2).coordinate
            ws.cell(row=row, column=start_col, value=str(line.get("name", "")).strip())
            ws.cell(row=row, column=start_col + 1, value=_costing_safe_float(line.get("costing_value")) if str(line.get("costing_value", "")).strip() else "")
            ws.cell(row=row, column=start_col + 2, value=str(line.get("unit", "")).strip().upper())
            ws.cell(row=row, column=start_col + 3, value=str(line.get("note", "")).strip())
            ws.cell(row=row, column=start_col + 4, value=f'=IF({unit_cell}="%",{base_cell}*{value_cell}/100,{value_cell})')

    def write_actual_charge_lines(lines: list[dict], start_row: int, end_row: int, start_col: int, base_cell: str) -> None:
        for row in range(start_row, end_row + 1):
            for col in range(start_col, start_col + 5):
                ws.cell(row=row, column=col).value = ""
        for offset, line in enumerate(lines[: end_row - start_row + 1]):
            row = start_row + offset
            value_cell = ws.cell(row=row, column=start_col + 1).coordinate
            unit_cell = ws.cell(row=row, column=start_col + 2).coordinate
            ws.cell(row=row, column=start_col, value=str(line.get("name", "")).strip())
            ws.cell(row=row, column=start_col + 1, value=_costing_safe_float(line.get("actual_value")) if str(line.get("actual_value", "")).strip() else "")
            ws.cell(row=row, column=start_col + 2, value=str(line.get("unit", "")).strip().upper())
            ws.cell(row=row, column=start_col + 3, value=str(line.get("note", "")).strip())
            ws.cell(row=row, column=start_col + 4, value=f'=IF({unit_cell}="%",{base_cell}*{value_cell}/100,{value_cell})')

    material_lines = list(record.get("material_lines", []))
    actual_lines = list(record.get("actual_lines", []))
    fabric_lines = [line for line in material_lines if str(line.get("type", "")).strip().lower() == "fabric"]
    trim_lines = [line for line in material_lines if str(line.get("type", "")).strip().lower() != "fabric"]
    actual_fabric = [line for line in actual_lines if str(line.get("type", "")).strip().lower() == "fabric"]
    actual_trim = [line for line in actual_lines if str(line.get("type", "")).strip().lower() != "fabric"]
    write_lines(fabric_lines, 22, 27, 1)
    write_lines(trim_lines, 31, 38, 1)
    write_lines(actual_fabric, 22, 27, 7)
    write_lines(actual_trim, 31, 38, 7)
    ws["E28"] = "=SUM(E22:E27)"
    ws["K28"] = "=SUM(K22:K27)"
    ws["E39"] = "=SUM(E31:E38)"
    ws["K39"] = "=SUM(K31:K38)"

    labour_lines = list(record.get("labour_lines", []))
    extra_labour_rows = max(0, len(labour_lines) - 4)
    if extra_labour_rows:
        ws.insert_rows(45, extra_labour_rows)
        for row in range(45, 45 + extra_labour_rows):
            copy_row_style(44, row)
    labour_total_row = 45 + extra_labour_rows
    logistics_start_row = 47 + extra_labour_rows
    write_charge_lines(labour_lines, 41, labour_total_row - 1, 1, "E39")
    write_actual_charge_lines(labour_lines, 41, labour_total_row - 1, 7, "K39")
    ws.cell(row=labour_total_row, column=1, value="Total")
    ws.cell(row=labour_total_row, column=5, value=f"=SUM(E41:E{labour_total_row - 1})")
    ws.cell(row=labour_total_row, column=7, value="Total")
    ws.cell(row=labour_total_row, column=11, value=f"=SUM(K41:K{labour_total_row - 1})")

    logistics_lines = list(record.get("logistics_lines", []))
    extra_logistics_rows = max(0, len(logistics_lines) - 3)
    if extra_logistics_rows:
        ws.insert_rows(logistics_start_row + 3, extra_logistics_rows)
        for row in range(logistics_start_row + 3, logistics_start_row + 3 + extra_logistics_rows):
            copy_row_style(logistics_start_row + 2, row)
    logistics_total_row = logistics_start_row + 3 + extra_logistics_rows
    fob_row = logistics_total_row + 2
    ddp_row = fob_row + 1
    write_charge_lines(logistics_lines, logistics_start_row, logistics_total_row - 1, 1, f"E{fob_row}")
    write_actual_charge_lines(logistics_lines, logistics_start_row, logistics_total_row - 1, 7, f"K{fob_row}")
    ws.cell(row=logistics_total_row, column=1, value="Total")
    ws.cell(row=logistics_total_row, column=5, value=f"=SUM(E{logistics_start_row}:E{logistics_total_row - 1})")
    ws.cell(row=logistics_total_row, column=7, value="Total")
    ws.cell(row=logistics_total_row, column=11, value=f"=SUM(K{logistics_start_row}:K{logistics_total_row - 1})")
    ws.cell(row=fob_row, column=4, value="FOB")
    ws.cell(row=fob_row, column=5, value=f"=E28+E39+E{labour_total_row}")
    ws.cell(row=fob_row, column=10, value="FOB")
    ws.cell(row=fob_row, column=11, value=f"=K28+K39+K{labour_total_row}")
    ws.cell(row=ddp_row, column=4, value="DDP")
    ws.cell(row=ddp_row, column=5, value=f"=(E{fob_row}+E{logistics_total_row})*1.025")
    ws.cell(row=ddp_row, column=10, value="DDP")
    ws.cell(row=ddp_row, column=11, value=f"=(K{fob_row}+K{logistics_total_row})*1.025")
    ws["B4"] = f"=E{ddp_row}*1.05"
    ws["B5"] = f"=E{ddp_row}*1.1"
    ws["B6"] = f"=E{ddp_row}*1.15"
    ws["B7"] = f"=E{ddp_row}*1.2"
    ws["C8"] = f"=(B8/E{ddp_row})-1"
    for cell_ref in ("B4", "B5", "B6", "B7"):
        ws[cell_ref].number_format = '$#,##0.00'
    for row in range(22, ddp_row + 2):
        ws.cell(row=row, column=13, value=f"=K{row}-E{row}")
        ws.cell(row=row, column=13).number_format = '$#,##0.00'
    save_path.parent.mkdir(parents=True, exist_ok=True)
    wb.save(save_path)
    return save_path


def export_selected_costing_form(self) -> None:
    if not self.is_action_allowed("action.costing.export_report"):
        return
    item, variant = self.get_selected_costing_item_and_variant()
    if item is None:
        messagebox.showwarning("Costing form", self.t("costing_empty"))
        return
    record = self.build_costing_form_record_context(item, variant)
    style_code = str(record.get("style_code", "")).strip() or "costing"
    save_path = filedialog.asksaveasfilename(
        title="Save Costing Form",
        defaultextension=".xlsx",
        initialfile=f"{style_code}_costing_form.xlsx",
        filetypes=[("Excel workbook", "*.xlsx")],
    )
    if not save_path:
        return
    try:
        self.build_costing_form_file(record, Path(save_path))
    except Exception as exc:
        messagebox.showerror("Costing form", f"Could not export costing form.\n\n{exc}")
        return
    self.set_status(f"Saved costing form for {style_code}: {Path(save_path).name}")
    messagebox.showinfo("Costing form", f"Saved costing form for {style_code}.")


def bind_costing_list_module(app_cls: type) -> None:
    method_map = {
        "_build_costing_module": _build_costing_module,
        "create_costing_thumbnail": create_costing_thumbnail,
        "resolve_costing_item_image_path": resolve_costing_item_image_path,
        "get_costing_check_display": get_costing_check_display,
        "populate_costing_module": populate_costing_module,
        "refresh_costing_tree": refresh_costing_tree,
        "update_costing_tree_check_state": update_costing_tree_check_state,
        "apply_costing_filter": apply_costing_filter,
        "commit_costing_search_query": commit_costing_search_query,
        "on_costing_search_submit": on_costing_search_submit,
        "schedule_costing_filter_apply": schedule_costing_filter_apply,
        "run_costing_filter_apply": run_costing_filter_apply,
        "sync_costing_search_query": sync_costing_search_query,
        "update_costing_filter_input_state": update_costing_filter_input_state,
        "on_costing_item_selected": on_costing_item_selected,
        "load_costing_item": load_costing_item,
        "on_costing_tree_click": on_costing_tree_click,
        "on_costing_tree_double_click": on_costing_tree_double_click,
        "render_costing_variant_buttons": render_costing_variant_buttons,
        "load_costing_variant": load_costing_variant,
        "delete_selected_costing_styles": delete_selected_costing_styles,
        "get_selected_costing_item_and_variant": get_selected_costing_item_and_variant,
        "load_costing_form_records_from_source": load_costing_form_records_from_source,
        "save_costing_form_record_to_source": save_costing_form_record_to_source,
        "get_costing_form_record_for_style": get_costing_form_record_for_style,
        "get_costing_form_consumption_lines": get_costing_form_consumption_lines,
        "build_costing_form_record_context": build_costing_form_record_context,
        "edit_selected_costing_row": edit_selected_costing_row,
        "load_costing_form_assets_from_source": load_costing_form_assets_from_source,
        "save_costing_form_assets_to_source": save_costing_form_assets_to_source,
        "resolve_costing_form_template_asset": resolve_costing_form_template_asset,
        "build_clean_costing_form_template": build_clean_costing_form_template,
        "ensure_costing_form_template_asset": ensure_costing_form_template_asset,
        "build_costing_form_file": build_costing_form_file,
        "export_selected_costing_form": export_selected_costing_form,
    }
    for method_name, function in method_map.items():
        setattr(app_cls, method_name, function)
