﻿# app.spec
# letakkan file ini di samping app.py

block_cipher = None

import os
import json
import sys
from PyInstaller.utils.hooks import collect_submodules, collect_data_files, collect_dynamic_libs


def _collect_dir_data(src_dir, target_root):
    data_pairs = []
    if not os.path.isdir(src_dir):
        return data_pairs
    for root, _, files in os.walk(src_dir):
        rel = os.path.relpath(root, src_dir)
        for name in files:
            if name.endswith((".pyc", ".pyo")):
                continue
            src_path = os.path.join(root, name)
            if "__pycache__" in src_path:
                continue
            if rel == ".":
                dst_path = target_root
            else:
                dst_path = os.path.join(target_root, rel)
            data_pairs.append((src_path, dst_path.replace("\\", "/")))
    return data_pairs


PROJECT_ROOT = os.path.abspath(globals().get("SPECPATH", os.getcwd()))


LOCAL_ESCPOS_PATH = os.path.join(PROJECT_ROOT, "libs", "python-escpos")
if os.path.isdir(LOCAL_ESCPOS_PATH) and LOCAL_ESCPOS_PATH not in sys.path:
    sys.path.insert(0, LOCAL_ESCPOS_PATH)


def _collect_dynamic_libs_safe(packages):
    collected = []
    for package_name in packages:
        try:
            collected.extend(collect_dynamic_libs(package_name))
        except Exception:
            continue
    # dedupe agar tidak ada binary ganda saat paket overlap.
    return list(dict.fromkeys(collected))


def _collect_submodules_safe(package_name):
    try:
        return collect_submodules(package_name)
    except Exception:
        return []


def _load_build_profile_from_settings():
    settings_path = os.path.join(PROJECT_ROOT, "app_settings.json")
    if not os.path.exists(settings_path):
        return ""
    try:
        with open(settings_path, "r", encoding="utf-8") as fh:
            data = json.load(fh)
        if not isinstance(data, dict):
            return ""
        raw = data.get("build_profile") or data.get("pyinstaller_build_profile") or ""
        return str(raw).strip().lower()
    except Exception:
        return ""


def _load_build_mode_from_settings():
    settings_path = os.path.join(PROJECT_ROOT, "app_settings.json")
    if not os.path.exists(settings_path):
        return ""
    try:
        with open(settings_path, "r", encoding="utf-8") as fh:
            data = json.load(fh)
        if not isinstance(data, dict):
            return ""
        raw = data.get("build_mode") or data.get("pyinstaller_build_mode") or ""
        return str(raw).strip().lower()
    except Exception:
        return ""


def _normalize_build_profile(raw_value):
    raw = str(raw_value or "").strip().lower()
    if raw in {"", "minimal"}:
        return "minimal"
    if raw in {"full", "all"}:
        return "full"
    print(f"[WARN] build_profile tidak valid: {raw}. Fallback ke 'minimal'.")
    return "minimal"


def _normalize_build_mode(raw_value):
    raw = str(raw_value or "").strip().lower()
    if raw in {"", "prod", "production"}:
        return "prod"
    if raw in {"dev", "development"}:
        return "dev"
    print(f"[WARN] build_mode tidak valid: {raw}. Fallback ke 'prod'.")
    return "prod"


BUILD_PROFILE = _normalize_build_profile(
    os.environ.get("POSAPP_BUILD_PROFILE", "").strip().lower()
    or _load_build_profile_from_settings()
)
IS_FULL_PROFILE = BUILD_PROFILE == "full"

BUILD_MODE = _normalize_build_mode(
    os.environ.get("POSAPP_BUILD_MODE", "").strip().lower()
    or _load_build_mode_from_settings()
)
IS_DEV_MODE = BUILD_MODE == "dev"

print(f"[INFO] Build profile: {BUILD_PROFILE}, build mode: {BUILD_MODE}")

hidden_imports = []
# edited by glg
# Namespace package di bawah `pypos.modules` (tanpa __init__.py)
# tidak selalu ikut saat hanya collect dari root `pypos`.
# Tambahkan collect eksplisit untuk area yang memakai lazy import (__import__).
hidden_import_roots = [
    "pypos",
    "pypos.modules",
    "pypos.modules.penjualan",
    "pypos.modules.penjualan.models",
    "pypos.modules.penjualan.services",
    "pypos.modules.sinkronisasi",
]
for package_name in hidden_import_roots:
    hidden_imports += _collect_submodules_safe(package_name)
hidden_imports += [
    "pypos.modules.penjualan.views.index",
    "pypos.modules.penjualan.views.transaksi_penjualan_view",
    "pypos.modules.printer.services.settlement_print_service",
    # edited by glg
    # Guard explicit untuk modul lazy import pada TransaksiModel.
    "pypos.modules.penjualan.services.transaksi_counter_service",
    "pypos.modules.penjualan.services.transaksi_lookup_harga_service",
    "pypos.modules.penjualan.services.transaksi_payment_payload_service",
    "pypos.modules.penjualan.services.transaksi_simpan_use_case_service",
]
if IS_FULL_PROFILE:
    hidden_imports += _collect_submodules_safe("escpos")
else:
    hidden_imports += [
        "escpos",
        "escpos.capabilities",
        "escpos.constants",
        "escpos.exceptions",
        "escpos.image",
        "escpos.escpos",
        "escpos.printer",
        "escpos.printer.win32raw",
        "escpos.printer.network",
        "escpos.printer.usb",
    ]
hidden_imports = list(dict.fromkeys(hidden_imports))

excludes = []
if not IS_FULL_PROFILE:
    excludes += [
        "cups",
        "serial",
        "argcomplete",
        "pyodide",
        "pyodide.ffi",
        "js",
        "h2",
        "h2.connection",
        "h2.events",
        "OpenSSL",
        "OpenSSL.crypto",
        "cryptography",
        "cryptography.x509",
        "brotli",
        "brotlicffi",
        "socks",
    ]

datas = []
datas += _collect_dir_data(os.path.join(PROJECT_ROOT, "pypos", "assets"), "pypos/assets")
datas += _collect_dir_data(os.path.join(PROJECT_ROOT, "resources"), "resources")

default_endpoint_cfg = os.path.join(PROJECT_ROOT, "config.json")
if os.path.exists(default_endpoint_cfg):
    datas.append((default_endpoint_cfg, "."))

default_app_settings = os.path.join(PROJECT_ROOT, "app_settings.json")
if os.path.exists(default_app_settings):
    datas.append((default_app_settings, "."))

default_db = os.path.join(PROJECT_ROOT, "db", "beta_sb_pos_sqlite.db")
if os.path.exists(default_db):
    datas.append((default_db, "db"))

datas += collect_data_files("escpos", include_py_files=False)

binary_packages = ["escpos"]
binaries = _collect_dynamic_libs_safe(binary_packages)

analysis_pathex = []
if os.path.isdir(LOCAL_ESCPOS_PATH):
    analysis_pathex.append(LOCAL_ESCPOS_PATH)

a = Analysis(
    ["run.py"],
    pathex=analysis_pathex,
    binaries=binaries,
    datas=datas,
    hiddenimports=hidden_imports,
    hookspath=[],
    runtime_hooks=[],
    excludes=excludes,
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name="POSApp",
    debug=IS_DEV_MODE,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=IS_DEV_MODE,
    icon=None,
)

coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name="POSApp",
)
