# -*- coding: utf-8 -*-
# [SKRIP I3.4] Memasang domain nilai (Value Map, Value Relation, Range), aturan isian, nilai bawaan, widget foto,
# dan tata letak formulir pada layer Titik_Survei, lalu menyimpannya sebagai gaya bawaan di dalam GeoPackage.
# Penulis: Badar Mubarok Yogaswara
# Jalankan setelah skrip I3.2. Dijalankan di Python Console QGIS atau python-qgis.bat.
from qgis.core import (QgsVectorLayer, QgsEditorWidgetSetup, QgsFieldConstraints, QgsDefaultValue, QgsEditFormConfig,
                       QgsAttributeEditorContainer, QgsAttributeEditorField, Qgis, QgsProject, QgsFeature, QgsGeometry,
                       QgsPointXY, QgsVectorLayerUtils)

GPKG = r"D:/KPH_Contoh/Survei_KPH.gpkg"          # sama dengan KELUAR pada skrip I3.2

lyr = QgsVectorLayer(GPKG + "|layername=Titik_Survei", "Titik_Survei", "ogr")
assert lyr.isValid()
idx = lyr.fields().indexOf


def widget(nama_field, jenis, konfig):
    lyr.setEditorWidgetSetup(idx(nama_field), QgsEditorWidgetSetup(jenis, konfig))


# 1. Domain nilai ------------------------------------------------------------------------------
# a) Value Map: daftar pendek yang tetap. Isi peta: {teks yang tampil: nilai yang disimpan}
pilihan = ["Sehat", "Terserang Hama", "Tebangan Liar"]
widget("Kondisi", "ValueMap", {"map": [{p: p} for p in pilihan]})
widget("KPH", "ValueMap", {"map": [{"KPH Alpha": "KPH Alpha"}, {"KPH Beta": "KPH Beta"}, {"KPH Gamma": "KPH Gamma"}]})

# b) Value Relation: pilihan diambil dari tabel acuan, jadi mengubah daftar cukup mengubah tabel
ref_jenis = QgsVectorLayer(GPKG + "|layername=Ref_Jenis", "Ref_Jenis", "ogr")
QgsProject.instance().addMapLayer(ref_jenis)
# LayerName dan LayerSource ikut disimpan agar pilihan tetap ketemu saat gaya dibaca di proyek lain
widget("Jenis", "ValueRelation", {"Layer": ref_jenis.id(), "LayerName": "Ref_Jenis", "LayerSource": ref_jenis.source(),
                                  "LayerProviderName": "ogr", "Key": "Jenis", "Value": "Jenis", "AllowNull": False,
                                  "OrderByValue": True, "AllowMulti": False, "NofColumns": 1, "FilterExpression": ""})

# c) Range: angka dibatasi rentang
widget("Tinggi_Phn", "Range", {"Min": 0.0, "Max": 60.0, "Step": 0.1, "Style": "SpinBox", "Precision": 1, "AllowNull": False})
widget("Diameter_cm", "Range", {"Min": 0.0, "Max": 200.0, "Step": 0.5, "Style": "SpinBox", "Precision": 1, "AllowNull": False})

# d) Tanggal dan foto
widget("Tgl_Survei", "DateTime", {"display_format": "yyyy-MM-dd", "field_format": "yyyy-MM-dd", "calendar_popup": True,
                                  "allow_null": False})
widget("Foto", "ExternalResource", {"DocumentViewer": 1, "DocumentViewerHeight": 0, "DocumentViewerWidth": 0,
                                    "FileWidget": True, "FileWidgetButton": True, "RelativeStorage": 1,
                                    "StorageMode": 0})

# 2. Aturan isian (constraint) --------------------------------------------------------------------
K = QgsFieldConstraints
lyr.setFieldConstraint(idx("ID_Survei"), K.ConstraintNotNull, K.ConstraintStrengthHard)
lyr.setFieldConstraint(idx("ID_Survei"), K.ConstraintUnique, K.ConstraintStrengthHard)
lyr.setFieldConstraint(idx("Kondisi"), K.ConstraintNotNull, K.ConstraintStrengthHard)
lyr.setConstraintExpression(idx("Tinggi_Phn"), '"Tinggi_Phn" BETWEEN 0 AND 60', "Tinggi pohon harus 0 sampai 60 m")
lyr.setFieldConstraint(idx("Tinggi_Phn"), K.ConstraintExpression, K.ConstraintStrengthHard)
lyr.setConstraintExpression(idx("Akurasi_m"), '"Akurasi_m" <= 5', "Akurasi lebih dari 5 m: ulangi pengukuran bila bisa")
lyr.setFieldConstraint(idx("Akurasi_m"), K.ConstraintExpression, K.ConstraintStrengthSoft)      # soft = hanya peringatan

# 3. Nilai bawaan ---------------------------------------------------------------------------------
lyr.setDefaultValueDefinition(idx("Tgl_Survei"), QgsDefaultValue("to_date(now())"))
lyr.setDefaultValueDefinition(idx("Surveyor"), QgsDefaultValue("@user_account_name"))
lyr.setDefaultValueDefinition(idx("ID_Survei"), QgsDefaultValue(
    "'SV-' || lpad(to_string(coalesce(maximum(to_int(substr(\"ID_Survei\", 4))), 0) + 1), 3, '0')"))

# Label yang ramah dibaca (alias) lengkap dengan satuan
ALIAS = {"ID_Survei": "ID survei", "KPH": "Petak KPH", "Jenis": "Jenis pohon", "Kondisi": "Kondisi pohon",
         "Tinggi_Phn": "Tinggi pohon (m)", "Diameter_cm": "Diameter batang (cm)", "Tgl_Survei": "Tanggal survei",
         "Surveyor": "Nama surveyor", "Akurasi_m": "Akurasi GNSS (m)", "Foto": "Foto"}
for nm, al in ALIAS.items():
    lyr.setFieldAlias(idx(nm), al)

# 4. Tata letak formulir (Drag and drop designer) -----------------------------------------------------
cfg = lyr.editFormConfig()
cfg.setLayout(Qgis.AttributeFormLayout.DragAndDrop)
cfg.clearTabs()
akar = cfg.invisibleRootContainer()
akar.clear()


def kelompok(judul, field_field, kolom=1):
    c = QgsAttributeEditorContainer(judul, akar)
    c.setType(Qgis.AttributeEditorContainerType.GroupBox)
    c.setColumnCount(kolom)
    for nm in field_field:
        c.addChildElement(QgsAttributeEditorField(nm, idx(nm), c))
    akar.addChildElement(c)


kelompok("Identitas", ["ID_Survei", "KPH", "Surveyor", "Tgl_Survei"])
kelompok("Pohon", ["Jenis", "Kondisi", "Tinggi_Phn", "Diameter_cm"])
kelompok("Bukti", ["Foto", "Akurasi_m"])
lyr.setEditFormConfig(cfg)

# 5. Simpan sebagai gaya bawaan di dalam GeoPackage (tabel layer_styles) -----------------------------------
hasil_simpan, galat = lyr.saveStyleToDatabaseV2("Formulir_Survei", "Formulir survei pohon", True, "")
print("Simpan gaya ke GeoPackage:", hasil_simpan, galat or "(tanpa pesan galat)")

# 6. Uji: layer baru yang membaca ulang berkas langsung memakai formulir ini
baru = QgsVectorLayer(GPKG + "|layername=Titik_Survei", "uji_baca", "ogr")
pesan, ok = baru.loadDefaultStyle()
print("Gaya bawaan terbaca:", ok)
print("Widget Kondisi:", baru.editorWidgetSetup(baru.fields().indexOf("Kondisi")).type())
print("Widget Tinggi_Phn:", baru.editorWidgetSetup(baru.fields().indexOf("Tinggi_Phn")).type())
print("Tata letak:", baru.editFormConfig().layout(), "| alias Tinggi_Phn:", baru.attributeAlias(baru.fields().indexOf("Tinggi_Phn")))

# 7. Uji aturan: satu fitur salah (tinggi 250 m, kondisi kosong) dan satu fitur benar
def periksa(atribut):
    f = QgsFeature(baru.fields())
    f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(312100, 9996100)))
    for k, v in atribut.items():
        f[k] = v
    out = []
    for nm in ["ID_Survei", "Kondisi", "Tinggi_Phn", "Akurasi_m"]:
        ok, galat = QgsVectorLayerUtils.validateAttribute(baru, f, baru.fields().indexOf(nm))
        if not ok:
            out.append("%s: %s" % (nm, "; ".join(galat)))
    return out


print("Fitur salah :", periksa({"ID_Survei": "SV-001", "Kondisi": None, "Tinggi_Phn": 250.0, "Akurasi_m": 8.4}))
print("Fitur benar :", periksa({"ID_Survei": "SV-001", "Kondisi": "Sehat", "Tinggi_Phn": 12.0, "Akurasi_m": 2.0}))

# 8. Uji: di proyek baru, tabel acuan dicari lewat nama layer, dan nilai bawaan terisi
from qgis.core import QgsValueRelationFieldFormatter
proyek = QgsProject()                                   # proyek baru; proyek milik pengguna tidak dikosongkan
acuan = QgsVectorLayer(GPKG + "|layername=Ref_Jenis", "Ref_Jenis", "ogr")
proyek.addMapLayer(acuan)
uji = QgsVectorLayer(GPKG + "|layername=Titik_Survei", "uji_proyek_baru", "ogr")
uji.loadDefaultStyle()
konfig = uji.editorWidgetSetup(uji.fields().indexOf("Jenis")).config()
print("Tabel acuan ditemukan:", QgsValueRelationFieldFormatter.resolveLayer(konfig, proyek))
baris = QgsVectorLayerUtils.createFeature(uji)
print("Nilai bawaan -> ID_Survei:", baris["ID_Survei"], "| Tgl_Survei:", baris["Tgl_Survei"].toString("yyyy-MM-dd"), "| Surveyor:", baris["Surveyor"])

# 9. Uji: tata letak formulir terbaca ulang dari gaya yang tersimpan (kelompok dan isinya)
print("Susunan formulir:")
cfg_uji = uji.editFormConfig()                          # simpan dulu di variabel agar tidak hilang saat dibaca
for anak in cfg_uji.invisibleRootContainer().children():
    print("- [%s]" % anak.name(), ", ".join(c.name() for c in anak.children()))
print("Widget Foto:", uji.editorWidgetSetup(uji.fields().indexOf("Foto")).type(),
      "| widget Tgl_Survei:", uji.editorWidgetSetup(uji.fields().indexOf("Tgl_Survei")).type())
