package com.fieldlabel.collector.ui.viewmodel

import android.annotation.SuppressLint
import android.bluetooth.BluetoothManager
import android.content.Context
import android.os.HandlerThread
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.brother.sdk.lmprinter.BLESearchOption
import com.brother.sdk.lmprinter.Channel
import com.brother.sdk.lmprinter.PrinterModel
import com.brother.sdk.lmprinter.PrinterSearcher
import com.fieldlabel.collector.data.CollectionRepository
import com.fieldlabel.collector.domain.model.CustomFieldDef
import com.fieldlabel.collector.service.PrintResult
import com.fieldlabel.collector.service.PrinterService
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.util.UUID
import java.util.Collections
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import javax.inject.Inject

/** アプリで選択できる PT-BT プリンターモデル一覧（lmprinter v4 API 対応） */
enum class SupportedPrinterModel(
    val displayName: String,
    val maxTapeMm: Int,
    val lmModel: PrinterModel
) {
    PT_P300BT("PT-P300BT (最大12mm)", 12, PrinterModel.PT_P300BT),
    PT_P710BT("PT-P710BT (最大18mm)", 18, PrinterModel.PT_P710BT),
    PT_P910BT("PT-P910BT (最大36mm)", 36, PrinterModel.PT_P910BT),
    PT_E310BT("PT-E310BT (最大12mm)", 12, PrinterModel.PT_E310BT),
    PT_E560BT("PT-E560BT (最大24mm)", 24, PrinterModel.PT_E560BT);

    companion object {
        fun fromKey(key: String): SupportedPrinterModel =
            entries.firstOrNull { it.name == key } ?: PT_P910BT
    }
}

/** テスト印刷の状態 */
sealed class TestPrintState {
    data object Idle     : TestPrintState()
    data object Printing : TestPrintState()
    data class  Success(val message: String) : TestPrintState()
    data class  Error  (val message: String) : TestPrintState()
}

/** BLE スキャンで発見されたプリンター */
data class BleDiscoveredPrinter(
    val localName: String,   // Channel.newBluetoothLowEnergyChannel に渡すBLEローカルネーム
    val modelName: String    // 表示用モデル名
)

/** ペアリング済み（クラシックBT）プリンター情報 */
data class BondedPrinterInfo(
    val name: String,        // デバイス名（Android 表示名）
    val address: String      // Bluetooth MAC アドレス
)

/** BLE スキャン状態 */
sealed class BleScanState {
    data object Idle     : BleScanState()
    data object Scanning : BleScanState()
    data class  Results(val printers: List<BleDiscoveredPrinter>) : BleScanState()
    data class  Error  (val message: String) : BleScanState()
}

@HiltViewModel
class SettingsViewModel @Inject constructor(
    @ApplicationContext private val context: Context,
    private val repository: CollectionRepository,
    private val printerService: PrinterService
) : ViewModel() {

    val collectorName = repository.collectorName.stateIn(
        viewModelScope, SharingStarted.Eagerly, ""
    )
    val tapeWidthMm = repository.tapeWidthMm.stateIn(
        viewModelScope, SharingStarted.Eagerly, 36
    )
    val printModeImmediate = repository.printModeImmediate.stateIn(
        viewModelScope, SharingStarted.Eagerly, false
    )
    val labelLayout = repository.labelLayout.stateIn(
        viewModelScope, SharingStarted.Eagerly, "auto_h"
    )
    val labelLengthMm = repository.labelLengthMm.stateIn(
        viewModelScope, SharingStarted.Eagerly, 70
    )
    val labelFontScale = repository.labelFontScale.stateIn(
        viewModelScope, SharingStarted.Eagerly, 1.0f
    )
    val labelFontFamily = repository.labelFontFamily.stateIn(
        viewModelScope, SharingStarted.Eagerly, ""
    )
    val labelFontBold = repository.labelFontBold.stateIn(
        viewModelScope, SharingStarted.Eagerly, false
    )
    val labelShowQr = repository.labelShowQr.stateIn(
        viewModelScope, SharingStarted.Eagerly, true
    )
    val labelShowDate = repository.labelShowDate.stateIn(
        viewModelScope, SharingStarted.Eagerly, true
    )
    val labelShowLocation = repository.labelShowLocation.stateIn(
        viewModelScope, SharingStarted.Eagerly, true
    )
    val labelShowNotes = repository.labelShowNotes.stateIn(
        viewModelScope, SharingStarted.Eagerly, true
    )
    val cutMode = repository.cutMode.stateIn(
        viewModelScope, SharingStarted.Eagerly, "half"
    )
    val labelShowCollector = repository.labelShowCollector.stateIn(
        viewModelScope, SharingStarted.Eagerly, true
    )
    val labelShowCoordinates = repository.labelShowCoordinates.stateIn(
        viewModelScope, SharingStarted.Eagerly, true
    )
    // マップ設定
    val mapTileSource  = repository.mapTileSource .stateIn(viewModelScope, SharingStarted.Eagerly, "MAPNIK" )
    val mapPinStyle    = repository.mapPinStyle   .stateIn(viewModelScope, SharingStarted.Eagerly, "default")
    // 表示設定
    val showThumbnails = repository.showThumbnails.stateIn(viewModelScope, SharingStarted.Eagerly, true     )
    // ハンズフリー設定
    val hfEnabled          = repository.hfEnabled         .stateIn(viewModelScope, SharingStarted.Eagerly, false        )
    val hfScreenLock       = repository.hfScreenLock      .stateIn(viewModelScope, SharingStarted.Eagerly, true         )
    val hfVoiceResponse    = repository.hfVoiceResponse   .stateIn(viewModelScope, SharingStarted.Eagerly, true         )
    val hfWakeWord         = repository.hfWakeWord        .stateIn(viewModelScope, SharingStarted.Eagerly, "れこちゃん")
    val hfLanguage         = repository.hfLanguage        .stateIn(viewModelScope, SharingStarted.Eagerly, "ja"         )
    val hfRequireWakeWord  = repository.hfRequireWakeWord .stateIn(viewModelScope, SharingStarted.Eagerly, true         )
    // 要素ごとの文字スケール・占有比
    val elemIdScale         = repository.elemIdScale        .stateIn(viewModelScope, SharingStarted.Eagerly, 1.2f  )
    val elemIdWeight        = repository.elemIdWeight       .stateIn(viewModelScope, SharingStarted.Eagerly, 1.5f  )
    val elemDateScale       = repository.elemDateScale      .stateIn(viewModelScope, SharingStarted.Eagerly, 0.85f )
    val elemDateWeight      = repository.elemDateWeight     .stateIn(viewModelScope, SharingStarted.Eagerly, 1.0f  )
    val elemLocationScale   = repository.elemLocationScale  .stateIn(viewModelScope, SharingStarted.Eagerly, 0.85f )
    val elemLocationWeight  = repository.elemLocationWeight .stateIn(viewModelScope, SharingStarted.Eagerly, 1.2f  )
    val elemCollectorScale  = repository.elemCollectorScale .stateIn(viewModelScope, SharingStarted.Eagerly, 0.85f )
    val elemCollectorWeight = repository.elemCollectorWeight.stateIn(viewModelScope, SharingStarted.Eagerly, 1.0f  )
    val elemNotesScale      = repository.elemNotesScale     .stateIn(viewModelScope, SharingStarted.Eagerly, 0.8f  )
    val elemNotesWeight     = repository.elemNotesWeight    .stateIn(viewModelScope, SharingStarted.Eagerly, 1.0f  )
    val elemQrWeight        = repository.elemQrWeight       .stateIn(viewModelScope, SharingStarted.Eagerly, 1.5f  )

    // 地点ID命名規則
    val locIdPrefix             = repository.locIdPrefix            .stateIn(viewModelScope, SharingStarted.Eagerly, "LOC" )
    val locIdSeparator          = repository.locIdSeparator         .stateIn(viewModelScope, SharingStarted.Eagerly, "-"   )
    val locIdIncludeYear        = repository.locIdIncludeYear       .stateIn(viewModelScope, SharingStarted.Eagerly, true  )
    val locIdYearDigits         = repository.locIdYearDigits        .stateIn(viewModelScope, SharingStarted.Eagerly, "YYYY")
    val locIdCounterDigits      = repository.locIdCounterDigits     .stateIn(viewModelScope, SharingStarted.Eagerly, 4     )
    val locIdCounterResetYearly = repository.locIdCounterResetYearly.stateIn(viewModelScope, SharingStarted.Eagerly, true  )

    /** BLE ローカルネーム（例: "PT-P710BT"）*/
    val printerLocalName: StateFlow<String> = repository.printerLocalName.stateIn(
        viewModelScope, SharingStarted.Eagerly, ""
    )

    val printerModelKey = repository.printerModel.stateIn(
        viewModelScope, SharingStarted.Eagerly, "PT_P910BT"
    )

    private val _testPrintState = MutableStateFlow<TestPrintState>(TestPrintState.Idle)
    val testPrintState: StateFlow<TestPrintState> = _testPrintState.asStateFlow()

    private val _bleScanState = MutableStateFlow<BleScanState>(BleScanState.Idle)
    val bleScanState: StateFlow<BleScanState> = _bleScanState.asStateFlow()

    // ラベルプレビュー用ビットマップ
    private val _labelPreviewBitmap = MutableStateFlow<android.graphics.Bitmap?>(null)
    val labelPreviewBitmap: StateFlow<android.graphics.Bitmap?> = _labelPreviewBitmap.asStateFlow()

    /** プレビュー用チューブ径 (0f = フラット面補正なし) */
    val tubeDiameterDefault: StateFlow<Float> = repository.tubeDiameterDefault
        .stateIn(viewModelScope, SharingStarted.Eagerly, 0f)
    val tubePreset1: StateFlow<Float> = repository.tubePreset1
        .stateIn(viewModelScope, SharingStarted.Eagerly, 10f)
    val tubePreset2: StateFlow<Float> = repository.tubePreset2
        .stateIn(viewModelScope, SharingStarted.Eagerly, 16f)
    val tubePreset3: StateFlow<Float> = repository.tubePreset3
        .stateIn(viewModelScope, SharingStarted.Eagerly, 25f)

    init {
        viewModelScope.launch {
            repository.printerLocalName.collect { name ->
                if (name.isNotBlank()) printerService.printerLocalName = name
            }
        }
        viewModelScope.launch {
            repository.printerModel.collect { key ->
                printerService.printerLmModel = SupportedPrinterModel.fromKey(key).lmModel
            }
        }
        viewModelScope.launch {
            repository.labelLayout.collect { printerService.labelLayout = it }
        }
        viewModelScope.launch {
            repository.labelLengthMm.collect { printerService.labelLengthMm = it }
        }
        viewModelScope.launch {
            repository.labelFontScale.collect { printerService.labelFontScale = it }
        }
        viewModelScope.launch { repository.labelShowQr       .collect { printerService.showQr        = it } }
        viewModelScope.launch { repository.labelShowDate     .collect { printerService.showDate      = it } }
        viewModelScope.launch { repository.labelShowLocation .collect { printerService.showLocation  = it } }
        viewModelScope.launch { repository.labelShowNotes    .collect { printerService.showNotes     = it } }
        viewModelScope.launch { repository.labelShowCollector   .collect { printerService.showCollector   = it } }
        viewModelScope.launch { repository.labelShowCoordinates.collect { printerService.showCoordinates = it } }
        viewModelScope.launch { repository.cutMode             .collect { printerService.cutMode         = it } }
        viewModelScope.launch { repository.labelFontFamily   .collect { printerService.labelFontFamily = it } }
        viewModelScope.launch { repository.labelFontBold     .collect { printerService.labelFontBold   = it } }
        // 要素スケール・占有比
        viewModelScope.launch { repository.elemIdScale        .collect { printerService.elemIdScale         = it } }
        viewModelScope.launch { repository.elemIdWeight       .collect { printerService.elemIdWeight        = it } }
        viewModelScope.launch { repository.elemDateScale      .collect { printerService.elemDateScale       = it } }
        viewModelScope.launch { repository.elemDateWeight     .collect { printerService.elemDateWeight      = it } }
        viewModelScope.launch { repository.elemLocationScale  .collect { printerService.elemLocationScale   = it } }
        viewModelScope.launch { repository.elemLocationWeight .collect { printerService.elemLocationWeight  = it } }
        viewModelScope.launch { repository.elemCollectorScale .collect { printerService.elemCollectorScale  = it } }
        viewModelScope.launch { repository.elemCollectorWeight.collect { printerService.elemCollectorWeight = it } }
        viewModelScope.launch { repository.elemNotesScale     .collect { printerService.elemNotesScale      = it } }
        viewModelScope.launch { repository.elemNotesWeight    .collect { printerService.elemNotesWeight     = it } }
        viewModelScope.launch { repository.elemQrWeight       .collect { printerService.elemQrWeight        = it } }
    }

    fun saveCollectorName(name: String) {
        viewModelScope.launch { repository.saveCollectorName(name) }
    }

    fun saveTapeWidth(mm: Int) {
        viewModelScope.launch { repository.saveTapeWidth(mm) }
    }

    fun savePrinterLocalName(name: String) {
        viewModelScope.launch {
            repository.savePrinterLocalName(name)
            printerService.printerLocalName = name
        }
    }

    fun savePrinterModel(model: SupportedPrinterModel) {
        viewModelScope.launch {
            repository.savePrinterModel(model.name)
            printerService.printerLmModel = model.lmModel
        }
    }

    fun savePrintModeImmediate(immediate: Boolean) {
        viewModelScope.launch { repository.savePrintModeImmediate(immediate) }
    }

    fun saveLabelPortrait(portrait: Boolean) {
        // 後方互換：縦/横の選択を labelLayout にマップする
        val layout = if (portrait) "portrait" else "compact"
        saveLabelLayout(layout)
    }

    fun saveLabelLayout(layout: String) {
        viewModelScope.launch {
            repository.saveLabelLayout(layout)
            printerService.labelLayout = layout
            refreshLabelPreview()
        }
    }

    fun saveLabelLengthMm(mm: Int) {
        viewModelScope.launch {
            repository.saveLabelLengthMm(mm)
            printerService.labelLengthMm = mm
            refreshLabelPreview()
        }
    }

    fun saveLabelFontScale(scale: Float) {
        viewModelScope.launch {
            repository.saveLabelFontScale(scale)
            printerService.labelFontScale = scale
            refreshLabelPreview()
        }
    }

    fun saveLabelShowQr(show: Boolean) {
        viewModelScope.launch {
            repository.saveLabelShowQr(show)
            printerService.showQr = show
            refreshLabelPreview()
        }
    }

    fun saveLabelShowDate(show: Boolean) {
        viewModelScope.launch {
            repository.saveLabelShowDate(show)
            printerService.showDate = show
            refreshLabelPreview()
        }
    }

    fun saveLabelShowLocation(show: Boolean) {
        viewModelScope.launch {
            repository.saveLabelShowLocation(show)
            printerService.showLocation = show
            refreshLabelPreview()
        }
    }

    fun saveLabelShowNotes(show: Boolean) {
        viewModelScope.launch {
            repository.saveLabelShowNotes(show)
            printerService.showNotes = show
            refreshLabelPreview()
        }
    }

    fun saveCutMode(mode: String) {
        viewModelScope.launch {
            repository.saveCutMode(mode)
            printerService.cutMode = mode
        }
    }

    fun saveLabelShowCollector(show: Boolean) {
        viewModelScope.launch {
            repository.saveLabelShowCollector(show)
            printerService.showCollector = show
            refreshLabelPreview()
        }
    }

    fun saveLabelShowCoordinates(show: Boolean) {
        viewModelScope.launch {
            repository.saveLabelShowCoordinates(show)
            printerService.showCoordinates = show
            refreshLabelPreview()
        }
    }

    fun saveLabelFontFamily(family: String) {
        viewModelScope.launch {
            repository.saveLabelFontFamily(family)
            printerService.labelFontFamily = family
            refreshLabelPreview()
        }
    }

    fun saveLabelFontBold(bold: Boolean) {
        viewModelScope.launch {
            repository.saveLabelFontBold(bold)
            printerService.labelFontBold = bold
            refreshLabelPreview()
        }
    }

    // ---- QR 曲面補正 チューブ径 ----
    fun saveTubeDiameterDefault(mm: Float) {
        viewModelScope.launch {
            repository.saveTubeDiameterDefault(mm)
            // tubeDiameterDefault StateFlow が更新されると refreshLabelPreview が LaunchedEffect で呼ばれる
            refreshLabelPreview()
        }
    }

    // ---- 要素ごとの文字スケール・占有比 ----
    private fun saveElemAndRefresh(
        save: suspend () -> Unit,
        apply: () -> Unit
    ) { viewModelScope.launch { save(); apply(); refreshLabelPreview() } }

    fun saveElemIdScale(v: Float)         = saveElemAndRefresh({ repository.saveElemIdScale(v) })         { printerService.elemIdScale         = v }
    fun saveElemIdWeight(v: Float)        = saveElemAndRefresh({ repository.saveElemIdWeight(v) })        { printerService.elemIdWeight        = v }
    fun saveElemDateScale(v: Float)       = saveElemAndRefresh({ repository.saveElemDateScale(v) })       { printerService.elemDateScale       = v }
    fun saveElemDateWeight(v: Float)      = saveElemAndRefresh({ repository.saveElemDateWeight(v) })      { printerService.elemDateWeight      = v }
    fun saveElemLocationScale(v: Float)   = saveElemAndRefresh({ repository.saveElemLocationScale(v) })   { printerService.elemLocationScale   = v }
    fun saveElemLocationWeight(v: Float)  = saveElemAndRefresh({ repository.saveElemLocationWeight(v) })  { printerService.elemLocationWeight  = v }
    fun saveElemCollectorScale(v: Float)  = saveElemAndRefresh({ repository.saveElemCollectorScale(v) })  { printerService.elemCollectorScale  = v }
    fun saveElemCollectorWeight(v: Float) = saveElemAndRefresh({ repository.saveElemCollectorWeight(v) }) { printerService.elemCollectorWeight = v }
    fun saveElemNotesScale(v: Float)      = saveElemAndRefresh({ repository.saveElemNotesScale(v) })      { printerService.elemNotesScale      = v }
    fun saveElemNotesWeight(v: Float)     = saveElemAndRefresh({ repository.saveElemNotesWeight(v) })     { printerService.elemNotesWeight     = v }
    fun saveElemQrWeight(v: Float)        = saveElemAndRefresh({ repository.saveElemQrWeight(v) })        { printerService.elemQrWeight        = v }

    fun refreshLabelPreview() {
        viewModelScope.launch(kotlinx.coroutines.Dispatchers.Default) {
            try {
                val widthMm = tapeWidthMm.value
                val previewTubeMm = tubeDiameterDefault.value.let { if (it > 0f) it else null }
                val sample  = com.fieldlabel.collector.domain.model.CollectionRecord(
                    id             = "preview",
                    locationId     = "LOC-2026-0001",
                    specimenNumber = 1,
                    specimenId     = "LOC-2026-0001-01",
                    collectionDate = java.time.LocalDate.now(),
                    latitude       = 35.6789,
                    longitude      = 139.7654,
                    altitude       = 123.0,
                    address        = "東京都千代田区",
                    collectorName  = collectorName.value.ifBlank { "採集者名" },
                    notes          = "備考サンプル",
                    createdAt      = java.time.LocalDateTime.now(),
                    tubeDiameterMm = previewTubeMm
                )
                _labelPreviewBitmap.value = printerService.buildLabelBitmap(sample, widthMm)
            } catch (e: Throwable) {
                android.util.Log.e("SettingsViewModel", "refreshLabelPreview failed: ${e.javaClass.simpleName}: ${e.message}", e)
                // ビットマップ生成に失敗してもクラッシュしない（プレビューは null のまま）
            }
        }
    }

    /**
     * BLE スキャンを実行してプリンターを検索する。
     * 発見された BLE ローカルネームを [bleScanState] に格納する。
     * スキャン時間は 10 秒。
     */
    @SuppressLint("MissingPermission")
    fun startBleScan() {
        if (_bleScanState.value is BleScanState.Scanning) return
        viewModelScope.launch(Dispatchers.IO) {
            _bleScanState.value = BleScanState.Scanning
            // BLE コールバックには Looper が必要。HandlerThread でLooperを用意する
            val discovered   = Collections.synchronizedList(mutableListOf<BleDiscoveredPrinter>())
            val handlerThread = HandlerThread("BrotherBLEScan")
            handlerThread.start()
            val latch = CountDownLatch(1)
            android.os.Handler(handlerThread.looper).post {
                try {
                    val option = BLESearchOption(10.0)
                    PrinterSearcher.startBLESearch(context, option) { channel ->
                        val localName = channel.channelInfo ?: return@startBLESearch
                        val modelName = channel.extraInfo[Channel.ExtraInfoKey.ModelName] ?: ""
                        if (localName.isNotBlank() && discovered.none { it.localName == localName }) {
                            discovered.add(BleDiscoveredPrinter(localName, modelName))
                        }
                    }
                } catch (e: Exception) {
                    // エラーは latch 後に bleScanState へ反映
                    discovered.clear()
                } finally {
                    latch.countDown()
                    handlerThread.quitSafely()
                }
            }
            // 最大15秒待ってから結果を反映（SDK内部タイムアウト10秒＋マージン）
            latch.await(15, TimeUnit.SECONDS)
            _bleScanState.value = if (discovered.isEmpty()) {
                BleScanState.Error(
                    "プリンターが見つかりませんでした\n" +
                    "プリンターの電源を入れて近くに置き、再スキャンしてください"
                )
            } else {
                BleScanState.Results(discovered.toList())
            }
        }
    }

    fun resetBleScan() {
        _bleScanState.value = BleScanState.Idle
    }

    /**
     * Bluetooth 設定でペアリング済みの Brother プリンター一覧を返す。
     * BLE スキャン不要で即時に利用可能。
     * 名前が "PT-" で始まるデバイスを対象とする。
     */
    @SuppressLint("MissingPermission")
    fun getBondedBrotherPrinters(): List<BondedPrinterInfo> {
        return try {
            val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
            manager?.adapter?.bondedDevices
                ?.filter { it.name?.startsWith("PT-") == true }
                ?.map { BondedPrinterInfo(it.name ?: "", it.address) }
                ?: emptyList()
        } catch (_: Exception) { emptyList() }
    }

    /** テスト印刷を実行する */
    fun testPrint() {
        if (_testPrintState.value is TestPrintState.Printing) return
        viewModelScope.launch {
            _testPrintState.value = TestPrintState.Printing
            val result = printerService.printTest(tapeWidthMm.value)
            _testPrintState.value = when (result) {
                is PrintResult.Success -> TestPrintState.Success(result.message)
                is PrintResult.Error   -> TestPrintState.Error(result.message)
            }
        }
    }

    fun clearTestPrintState() {
        _testPrintState.value = TestPrintState.Idle
    }

    // ---- 地点ID命名規則 ----
    fun saveLocIdPrefix(v: String)              { viewModelScope.launch { repository.saveLocIdPrefix(v.trim().ifBlank { "LOC" }) } }
    fun saveLocIdSeparator(v: String)           { viewModelScope.launch { repository.saveLocIdSeparator(v) } }
    fun saveLocIdIncludeYear(v: Boolean)        { viewModelScope.launch { repository.saveLocIdIncludeYear(v) } }
    fun saveLocIdYearDigits(v: String)          { viewModelScope.launch { repository.saveLocIdYearDigits(v) } }
    fun saveLocIdCounterDigits(v: Int)          { viewModelScope.launch { repository.saveLocIdCounterDigits(v) } }
    fun saveLocIdCounterResetYearly(v: Boolean) { viewModelScope.launch { repository.saveLocIdCounterResetYearly(v) } }

    // ---- マップ・表示設定 ----
    fun saveMapTileSource(source: String) { viewModelScope.launch { repository.saveMapTileSource(source) } }
    fun saveMapPinStyle(style: String)    { viewModelScope.launch { repository.saveMapPinStyle(style) } }
    fun saveShowThumbnails(show: Boolean) { viewModelScope.launch { repository.saveShowThumbnails(show) } }

    // ---- ハンズフリー設定 ----
    fun saveHfEnabled(v: Boolean)       { viewModelScope.launch { repository.saveHfEnabled(v) } }
    fun saveHfScreenLock(v: Boolean)    { viewModelScope.launch { repository.saveHfScreenLock(v) } }
    fun saveHfVoiceResponse(v: Boolean)   { viewModelScope.launch { repository.saveHfVoiceResponse(v)   } }
    fun saveHfWakeWord(v: String)         { viewModelScope.launch { repository.saveHfWakeWord(v)         } }
    fun saveHfLanguage(v: String)         { viewModelScope.launch { repository.saveHfLanguage(v)         } }
    fun saveHfRequireWakeWord(v: Boolean) { viewModelScope.launch { repository.saveHfRequireWakeWord(v)  } }

    // ---- カスタムフィールド定義管理 ----
    val allCustomFieldDefs = repository.allCustomFieldDefs.stateIn(
        viewModelScope, SharingStarted.Eagerly, emptyList()
    )

    fun addCustomFieldDef(label: String, unit: String, valueType: String, appliesTo: String) {
        val maxOrder = allCustomFieldDefs.value.maxOfOrNull { it.sortOrder } ?: -1
        val def = CustomFieldDef(
            id        = UUID.randomUUID().toString(),
            label     = label.trim(),
            unit      = unit.trim(),
            valueType = valueType,
            appliesTo = appliesTo,
            sortOrder = maxOrder + 1
        )
        viewModelScope.launch { repository.saveCustomFieldDef(def) }
    }

    fun deleteCustomFieldDef(def: CustomFieldDef) {
        viewModelScope.launch { repository.deleteCustomFieldDef(def) }
    }

    fun updateCustomFieldDef(def: CustomFieldDef) {
        viewModelScope.launch { repository.saveCustomFieldDef(def) }
    }
}
