<template>
    <v-dialog :model-value="modelValue" max-width="720" @update:model-value="$emit('update:modelValue', $event)">
        <v-card style="position: relative">
            <DevIndexBadge
                :index="mode === 'edit' ? 19 : 18"
                :label="mode === 'edit' ? 'EditProjectDialog' : 'NewProjectDialog'"
                :dialog-mode="true"
                style="top: 12px; right: 12px"
            />
            <v-card-title>{{ mode === 'edit' ? 'Редактирование проекта' : 'Новый проект' }}</v-card-title>

            <v-card-text>
                <!-- Косяк 04 / вариант C: для нового клиента без реквизитов — шаг 1
                     (короткие реквизиты) прямо в визарде, без ухода на /settings. -->
                <div v-if="step === 'requisites'" data-testid="req-step">
                    <div class="steps-head mb-3">
                        <span class="step-on">1. Реквизиты</span>
                        <span class="step-sep"></span>
                        <span class="step-off">2. Проект</span>
                    </div>
                    <v-alert type="info" variant="tonal" density="comfortable" class="mb-4">
                        Сначала короткие реквизиты компании — без них нельзя создать первый проект. Это пара минут.
                    </v-alert>
                    <v-select
                        v-model="reqForm.subject_type"
                        :items="subjectTypeItems"
                        item-title="title"
                        item-value="value"
                        label="Тип лица"
                        density="comfortable"
                        class="ld-input-quiet"
                        data-testid="req-subject-type"
                        :error-messages="reqErrors.subject_type"
                    />
                    <v-text-field
                        v-model="reqForm.contact_name"
                        label="Контактное имя"
                        density="comfortable"
                        class="ld-input-quiet"
                        data-testid="req-contact-name"
                        :error-messages="reqErrors.contact_name"
                    />
                    <v-text-field
                        v-model="reqForm.contact_phone"
                        label="Контактный телефон"
                        hint="Примем в любом формате — приведём сами"
                        persistent-hint
                        density="comfortable"
                        class="ld-input-quiet"
                        data-testid="req-contact-phone"
                        :error-messages="reqErrors.contact_phone"
                    />
                    <v-text-field
                        v-if="reqForm.subject_type === 'legal_entity' || reqForm.subject_type === 'sole_proprietor'"
                        v-model="reqForm.inn"
                        label="ИНН"
                        density="comfortable"
                        class="ld-input-quiet mt-2"
                        data-testid="req-inn"
                        :error-messages="reqErrors.inn"
                    />
                    <v-alert
                        v-if="reqGeneralError"
                        type="error"
                        variant="tonal"
                        density="compact"
                        class="mt-2"
                    >
                        {{ reqGeneralError }}
                    </v-alert>
                </div>

                <template v-else>
                <!-- Баннер-объявление: когда пойдут лиды (спека 2026-06-22-project-source-edit-lock-ux). -->
                <v-alert
                    v-if="mode !== 'edit'"
                    type="info"
                    variant="tonal"
                    density="comfortable"
                    class="mb-4"
                    data-testid="np-lead-banner"
                >
                    📣 Лидерра поставит проект в сбор сразу после создания. Первые лиды пойдут с {{ leadStart }}.
                </v-alert>

                <div class="d-flex align-center mb-3 text-body-2 text-medium-emphasis" data-testid="np-boost-hint">
                    <span>Как увеличить количество сделок</span>
                    <v-tooltip
                        text="Ваш лимит распределяется на нескольких поставщиков равномерно. Даже если лимит не выбирается полностью, просто увеличьте лимит — и сделок придёт больше."
                        location="top"
                        max-width="280"
                    >
                        <template #activator="{ props: tip }">
                            <v-icon
                                v-bind="tip"
                                size="14"
                                class="src-hint ml-1"
                                icon="mdi-help-circle-outline"
                                aria-label="Как увеличить количество сделок"
                                tabindex="0"
                            />
                        </template>
                    </v-tooltip>
                </div>

                <div class="d-flex align-center mb-1 text-body-2 text-medium-emphasis">
                    <span>Откуда собирать заявки</span>
                    <v-tooltip
                        text="Сайт — заявки с сайтов вашей ниши. Звонок — звонки на номер. СМС — заявки по СМС. Выберите подходящий источник."
                        location="top"
                        max-width="280"
                    >
                        <template #activator="{ props: tip }">
                            <v-icon
                                v-bind="tip"
                                size="14"
                                class="src-hint ml-1"
                                icon="mdi-help-circle-outline"
                                aria-label="Чем отличаются источники"
                                tabindex="0"
                            />
                        </template>
                    </v-tooltip>
                </div>
                <v-tabs v-model="form.signal_type" :disabled="mode === 'edit'" color="primary">
                    <v-tab value="site"><v-icon start>mdi-web</v-icon>Сайт</v-tab>
                    <v-tab value="call"><v-icon start>mdi-phone</v-icon>Звонок</v-tab>
                    <v-tab value="sms"><v-icon start>mdi-message-text</v-icon>СМС</v-tab>
                </v-tabs>

                <v-tabs-window v-model="form.signal_type" class="mt-4">
                    <v-tabs-window-item value="site">
                        <div class="source-hint text-caption text-medium-emphasis mb-2">
                            Укажите сайт в вашей нише, по теме которого нужны заявки — приведём вам таких же клиентов
                        </div>
                        <v-text-field
                            v-model="form.signal_identifier"
                            label="Сайт, с которого нужны заявки"
                            placeholder="okna-moskva.ru"
                            class="ld-input-quiet"
                            data-testid="np-source-identifier"
                            :error-messages="errors.signal_identifier"
                        />
                    </v-tabs-window-item>
                    <v-tabs-window-item value="call">
                        <div class="source-hint text-caption text-medium-emphasis mb-2">
                            Укажите телефон в вашей нише, по звонкам на который соберём вам заявки
                        </div>
                        <v-text-field
                            v-model="form.signal_identifier"
                            label="Телефон, по которому идут клиенты"
                            placeholder="79161234567"
                            hint="Можно вводить с +7, 8, скобками и пробелами — приведём к виду 79161234567"
                            persistent-hint
                            class="ld-input-quiet"
                            data-testid="np-source-identifier"
                            :error-messages="errors.signal_identifier"
                        />
                    </v-tabs-window-item>
                    <v-tabs-window-item value="sms">
                        <div class="source-hint text-caption text-medium-emphasis mb-2">
                            Источник — отправитель SMS и (опционально) ключевое слово в тексте
                        </div>
                        <v-combobox
                            v-model="form.sms_senders"
                            label="Отправители (до 11 символов каждый)"
                            multiple
                            chips
                            clearable
                            :error-messages="errors.sms_senders"
                        />
                        <v-text-field
                            v-model="form.sms_keyword"
                            label="Ключевое слово (опционально)"
                            hint="Если пусто — проект подключится только к B3"
                            class="ld-input-quiet"
                            :error-messages="errors.sms_keyword"
                        />
                    </v-tabs-window-item>
                </v-tabs-window>

                <!-- Эпик 3.1: объявление о вступлении правок количества/региона/дней (без блокировки). -->
                <v-alert
                    v-if="showAppliesFromBanner"
                    type="info"
                    variant="tonal"
                    density="comfortable"
                    class="mt-3"
                    data-testid="np-applies-from-banner"
                >
                    ⏳ Ближайший сбор уже идёт по текущим настройкам. Новые количество, регионы
                    и дни приёма вступят в силу с {{ appliesFromDate }}.
                </v-alert>

                <v-divider class="my-4" />

                <v-text-field
                    v-model="form.name"
                    label="Название проекта"
                    class="ld-input-quiet"
                    :error-messages="errors.name"
                />

                <v-text-field
                    v-model.number="form.daily_limit_target"
                    label="Лимит лидов в день"
                    type="number"
                    min="1"
                    max="10000"
                    class="ld-input-quiet"
                    data-testid="np-limit"
                    hint="Сколько заявок в день вы готовы принимать и оплачивать. Можно менять в любой момент."
                    persistent-hint
                    :error-messages="errors.daily_limit_target"
                />

                <v-autocomplete
                    v-model:search="regionSearch"
                    :model-value="form.regions"
                    :items="selectableRegions"
                    item-title="name"
                    item-value="code"
                    label="Регионы"
                    :disabled="vsyaRfConfirmed"
                    multiple
                    chips
                    closable-chips
                    clearable
                    density="comfortable"
                    class="ld-input-quiet"
                    data-testid="regions-autocomplete"
                    :error-messages="errors.regions"
                    @update:model-value="onRegionsChange"
                >
                    <template #item="{ props: itemProps, item }">
                        <v-list-item v-bind="itemProps">
                            <template #subtitle>
                                {{ FEDERAL_DISTRICT_NAMES[item.raw.federalDistrict] || '' }}
                            </template>
                        </v-list-item>
                    </template>
                </v-autocomplete>

                <v-checkbox
                    :model-value="vsyaRf"
                    label="Вся РФ (все регионы)"
                    density="comfortable"
                    hide-details
                    data-testid="vsya-rf-checkbox"
                    @update:model-value="(v: boolean | null) => (v ? chooseVsyaRf() : cancelVsyaRf())"
                />

                <v-alert
                    v-if="vsyaRf"
                    type="info"
                    variant="tonal"
                    density="compact"
                    class="mt-2"
                    data-testid="vsya-rf-confirmed"
                >
                    Проект будет получать лиды по всей России — по всем субъектам страны.
                </v-alert>


                <v-alert
                    v-if="generalError"
                    type="error"
                    variant="tonal"
                    density="compact"
                    class="mt-3"
                    closable
                    @click:close="generalError = null"
                >
                    {{ generalError }}
                </v-alert>

                <div class="mt-3">
                    <span class="text-caption">Дни недели приёма</span>
                    <v-btn-toggle v-model="selectedDays" multiple density="comfortable" class="mt-1">
                        <v-btn v-for="(day, i) in dayLabels" :key="i" :value="i">{{ day }}</v-btn>
                    </v-btn-toggle>
                    <div class="mt-1">
                        <v-btn size="small" variant="text" @click="setWorkdays('weekdays')">Будни</v-btn>
                        <v-btn size="small" variant="text" @click="setWorkdays('all')">Все дни</v-btn>
                    </div>
                </div>
                </template>
            </v-card-text>

            <v-card-actions>
                <v-spacer />
                <v-btn variant="text" @click="close">Отмена</v-btn>
                <v-btn
                    v-if="step === 'requisites'"
                    color="primary"
                    :loading="reqSaving"
                    data-testid="req-next-btn"
                    @click="saveRequisites"
                >
                    Далее: к проекту
                </v-btn>
                <v-btn v-else color="primary" :loading="saving" data-testid="submit-btn" @click="submit">
                    {{ mode === 'edit' ? 'Сохранить' : 'Создать' }}
                </v-btn>
            </v-card-actions>
        </v-card>
    </v-dialog>

    <ProjectLimitOverloadDialog
        v-model="overloadOpen"
        :payload="overloadPayload"
        @save-blocked="onOverloadSaveBlocked"
        @set-zero="onOverloadSetZero"
    />

    <!-- Эпик 3.2: подтверждение смены источника на залоченном проекте. -->
    <v-dialog v-model="sourceConfirmOpen" max-width="430">
        <v-card data-testid="np-source-change-confirm">
            <v-card-title>Сменить источник?</v-card-title>
            <v-card-text>{{ sourceConfirmText }}</v-card-text>
            <v-card-actions>
                <v-spacer />
                <v-btn variant="text" data-testid="np-source-confirm-no" @click="cancelSourceChange">Отмена</v-btn>
                <v-btn color="primary" data-testid="np-source-confirm-yes" @click="confirmSourceChange">
                    Сменить источник
                </v-btn>
            </v-card-actions>
        </v-card>
    </v-dialog>
</template>

<script setup lang="ts">
import { ref, reactive, watch, computed } from 'vue';
import { apiClient, ensureCsrfCookie, extractErrorMessage } from '../../api/client';
import { getRequisites, updateRequisites, type Requisites } from '../../api/requisites';
import { firstLeadDate, formatLeadDate } from '../../utils/leadDate';
import { REGIONS, FEDERAL_DISTRICT_NAMES } from '../../constants/regions';
import type { Project } from '../../stores/projectsStore';
import DevIndexBadge from '../../components/DevIndexBadge.vue';
import ProjectLimitOverloadDialog from '../../components/projects/ProjectLimitOverloadDialog.vue';

const selectableRegions = REGIONS.filter((r) => r.code !== 0);

// Дата старта лидов для баннера нового проекта (правило слепка 18:00 МСК).
const leadStart = computed(() => firstLeadDate());

// Эпик 3 (редизайн 2026-06-25): в edit-режиме источник редактируем (не readonly).
// Замок заменён объявлением о вступлении + подтверждением смены источника —
// раздача доводит хвост по старому источнику через слепок.
const sourceLocked = computed(() => props.mode === 'edit' && props.project?.source_locked === true);

const slepokFieldsDirty = computed(() => {
    const p = props.project;
    if (!p) return false;
    const regionsChanged = JSON.stringify([...form.regions].sort()) !== JSON.stringify([...(p.regions ?? [])].sort());
    return (
        form.daily_limit_target !== p.daily_limit_target ||
        form.delivery_days_mask !== (p.delivery_days_mask ?? 127) ||
        regionsChanged
    );
});
const showAppliesFromBanner = computed(() => sourceLocked.value && slepokFieldsDirty.value);
// Time-aware дата вступления (правило слепка 18:00 МСК) — объявление актуально по времени суток.
const appliesFromDate = computed(() => firstLeadDate());

const sourceDirty = computed(() => {
    const p = props.project;
    if (!p) return false;
    if (form.signal_type === 'sms') {
        return (
            JSON.stringify(form.sms_senders) !== JSON.stringify(p.sms_senders ?? []) ||
            form.sms_keyword !== (p.sms_keyword ?? '')
        );
    }
    return form.signal_identifier !== (p.signal_identifier ?? '');
});

const sourceConfirmOpen = ref(false);
const sourceConfirmText = computed(() => {
    // Эпик 6.3: единый текст правила из API (ProjectRuleMessages). Fallback — локальный.
    const fromApi = props.project?.source_change_message;
    if (fromApi) {
        return `Мы уже ведём сбор на завтра. ${fromApi} Подтвердите смену источника.`;
    }
    const d = formatLeadDate(props.project?.source_unlock_at);
    return (
        'Мы уже ведём сбор на завтра. Лиды по старому источнику придут завтра в любом случае. ' +
        (d ? `Послезавтра (после ${d}) — уже по новому. ` : '') +
        'Подтвердите смену источника.'
    );
});

function cancelSourceChange(): void {
    sourceConfirmOpen.value = false;
}

async function confirmSourceChange(): Promise<void> {
    sourceConfirmOpen.value = false;
    await persist();
}

const props = defineProps<{
    modelValue: boolean;
    mode?: 'create' | 'edit';
    project?: Project | null;
}>();
const emit = defineEmits<{
    'update:modelValue': [value: boolean];
    saved: [appliesFrom: string | null];
}>();

// Plan 6: regions = subject codes (1..89) — backend dual-writes region_mask/region_mode.
// Пустой массив = вся РФ.
const form = reactive({
    name: '',
    signal_type: 'site' as 'site' | 'call' | 'sms',
    signal_identifier: '',
    sms_senders: [] as string[],
    sms_keyword: '',
    daily_limit_target: 50,
    regions: [] as number[],
    delivery_days_mask: 127,
});
const errors = reactive<Record<string, string[]>>({});
const saving = ref(false);
const generalError = ref<string | null>(null);

// Косяк 04 / вариант C: визард создания. step='requisites' — новый клиент без
// реквизитов заполняет их прямо здесь (шаг 1), затем step='project' (шаг 2).
// Реквизиты неполны ⟺ проектов ещё нет (гейт G1/SP2 не пускал без них), поэтому
// решение по шагу принимаем по light-complete, без отдельного счётчика проектов.
const step = ref<'requisites' | 'project'>('project');
const reqForm = reactive({ subject_type: '', contact_name: '', contact_phone: '', inn: '' });
const reqErrors = reactive<Record<string, string[]>>({});
const reqSaving = ref(false);
const reqGeneralError = ref<string | null>(null);

const subjectTypeItems = [
    { value: 'individual', title: 'Физлицо' },
    { value: 'sole_proprietor', title: 'ИП' },
    { value: 'legal_entity', title: 'Юрлицо' },
];

// Зеркало RequisitesService::isLightComplete — тип лица + имя + телефон (+ ИНН для юр/ИП).
function reqLightComplete(r: Requisites | null): boolean {
    if (!r || !r.subject_type || !r.contact_name?.trim() || !r.contact_phone?.trim()) {
        return false;
    }
    if ((r.subject_type === 'legal_entity' || r.subject_type === 'sole_proprietor') && !r.inn?.trim()) {
        return false;
    }
    return true;
}

async function initCreateStep(): Promise<void> {
    step.value = 'project';
    try {
        const r = await getRequisites();
        if (!reqLightComplete(r)) {
            step.value = 'requisites';
            if (r) {
                reqForm.subject_type = r.subject_type ?? '';
                reqForm.contact_name = r.contact_name ?? '';
                reqForm.contact_phone = r.contact_phone ?? '';
                reqForm.inn = r.inn ?? '';
            }
        }
    } catch {
        // fail-open: при ошибке чтения не блокируем — бэкенд-гейт защитит на submit (422 → шаг 1).
        step.value = 'project';
    }
}

async function saveRequisites(): Promise<void> {
    Object.keys(reqErrors).forEach((k) => delete reqErrors[k]);
    reqGeneralError.value = null;
    reqSaving.value = true;
    try {
        await updateRequisites({
            subject_type: reqForm.subject_type as Requisites['subject_type'],
            contact_name: reqForm.contact_name,
            contact_phone: reqForm.contact_phone,
            inn: reqForm.inn || null,
        });
        step.value = 'project';
    } catch (e: unknown) {
        const err = e as { response?: { status?: number; data?: { errors?: Record<string, string[]> } } };
        if (err.response?.status === 422 && err.response.data?.errors) {
            Object.assign(reqErrors, err.response.data.errors);
        } else {
            reqGeneralError.value = extractErrorMessage(e);
        }
    } finally {
        reqSaving.value = false;
    }
}

// Spec C §6.2: префлайт баланса — диалог перегрузки лимита по 409.
interface OverloadPayloadShape {
    current_balance_rub: string;
    current_capacity_leads: number;
    would_be_required_leads: number;
    deficit_leads: number;
}
const overloadOpen = ref(false);
const overloadPayload = ref<OverloadPayloadShape | null>(null);

// Plan 4 Task 4 + косяк 03: явная «Вся РФ» одной галочкой (защита от случайной
// «всей РФ» сохранена — нужен осознанный клик), без отдельной кнопки подтверждения.
// vsyaRf — чекбокс выбран; vsyaRfConfirmed — true сразу при установке галочки.
// На бэке regions=[] (Вся РФ) и «забыл» неотличимы → гейт намеренно UI-only.
const vsyaRf = ref(false);
const vsyaRfConfirmed = ref(false);

function chooseVsyaRf(): void {
    vsyaRf.value = true;
    confirmVsyaRf();
}

function confirmVsyaRf(): void {
    vsyaRfConfirmed.value = true;
    form.regions = []; // Вся РФ → пустой массив субъектов
    delete errors.regions; // косяк 03: галочка сразу снимает зависшую ошибку
}

function cancelVsyaRf(): void {
    vsyaRf.value = false;
    vsyaRfConfirmed.value = false;
}

function onRegionsChange(codes: number[]): void {
    form.regions = Array.isArray(codes) ? codes : [];
    // После выбора субъекта очищаем строку поиска (иначе набранное «красно»
    // остаётся в поле рядом с чипом).
    regionSearch.value = '';
    if (form.regions.length > 0) {
        // Взаимоисключение: выбор конкретных субъектов снимает «Вся РФ».
        vsyaRf.value = false;
        vsyaRfConfirmed.value = false;
        delete errors.regions;
    }
}

const regionSearch = ref('');

const dayLabels = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
const selectedDays = ref<number[]>([0, 1, 2, 3, 4, 5, 6]);
watch(selectedDays, (days) => {
    form.delivery_days_mask = days.reduce((acc, d) => acc | (1 << d), 0);
});

function setWorkdays(preset: 'weekdays' | 'all') {
    if (preset === 'weekdays') selectedDays.value = [0, 1, 2, 3, 4];
    else selectedDays.value = [0, 1, 2, 3, 4, 5, 6];
}

watch(
    () => props.modelValue,
    (open) => {
        if (open) generalError.value = null;
        if (open) {
            delete errors.regions;
        }
        if (open && props.mode === 'edit' && props.project) {
            step.value = 'project'; // редактирование — всегда форма проекта, без шага реквизитов
            Object.assign(form, props.project);
            form.regions = Array.isArray(props.project.regions) ? [...props.project.regions] : [];
            const days: number[] = [];
            for (let i = 0; i < 7; i++) if (form.delivery_days_mask & (1 << i)) days.push(i);
            selectedDays.value = days;
            // Существующий проект с пустыми регионами = «Вся РФ» (предзаполняем подтверждённым).
            vsyaRf.value = form.regions.length === 0;
            vsyaRfConfirmed.value = form.regions.length === 0;
        } else if (open) {
            Object.assign(form, {
                name: '',
                signal_type: 'site',
                signal_identifier: '',
                sms_senders: [],
                sms_keyword: '',
                daily_limit_target: 50,
                regions: [],
                delivery_days_mask: 127,
            });
            selectedDays.value = [0, 1, 2, 3, 4, 5, 6];
            vsyaRf.value = false;
            vsyaRfConfirmed.value = false;
            // Косяк 04 / вариант C: решаем, нужен ли шаг реквизитов (новый клиент без них).
            Object.assign(reqForm, { subject_type: '', contact_name: '', contact_phone: '', inn: '' });
            Object.keys(reqErrors).forEach((k) => delete reqErrors[k]);
            reqGeneralError.value = null;
            void initCreateStep();
        }
    },
    { immediate: true },
);

async function persist(extra: Record<string, unknown> = {}): Promise<void> {
    saving.value = true;
    try {
        await ensureCsrfCookie();
        const body: Record<string, unknown> = { ...form, ...extra };
        // M (балансовый блок findings): для site/call не отправляем sms-поля. Форма
        // держит sms_senders=null/[] и sms_keyword, а UpdateProjectRequest валидирует
        // sms_senders как array|min:1 → null давал молчаливый 422 (поле sms на форме
        // site/call не отрисовано, ошибка не видна). Заодно не триггерим зря snapshot-guard.
        if (form.signal_type !== 'sms') {
            delete body.sms_senders;
            delete body.sms_keyword;
        }
        let appliesFrom: string | null = null;
        if (props.mode === 'edit' && props.project) {
            const { data } = await apiClient.patch(`/api/projects/${props.project.id}`, body);
            // Backend кладёт applies_from только когда правка задела slepok-чувствительные поля.
            appliesFrom = data?.data?.applies_from ?? null;
        } else {
            await apiClient.post('/api/projects', body);
            // Create НЕ генерирует applies_from (новый проект сразу попадает в snapshot).
        }
        overloadOpen.value = false;
        emit('saved', appliesFrom);
        close();
    } catch (e: unknown) {
        const err = e as {
            response?: { status?: number; data?: { error?: string; errors?: Record<string, string[]> } };
        };
        // G1/SP3b + косяк 04: гейт первого проекта — показываем шаг реквизитов
        // прямо в этом окне (запасной путь, обычно шаг 1 показан ещё при открытии).
        if (err.response?.status === 422 && err.response.data?.error === 'requisites_required') {
            step.value = 'requisites';
            void initCreateStep();
        }
        // Spec C §6.2: лимит превышает баланс — открываем диалог перегрузки.
        else if (err.response?.status === 409 && err.response.data?.error === 'balance_insufficient') {
            overloadPayload.value = err.response.data as OverloadPayloadShape;
            overloadOpen.value = true;
        } else if (err.response?.status === 422 && err.response.data?.errors) {
            Object.assign(errors, err.response.data.errors);
        } else {
            generalError.value = extractErrorMessage(e);
        }
    } finally {
        saving.value = false;
    }
}

async function submit() {
    generalError.value = null;
    Object.keys(errors).forEach((k) => delete errors[k]);

    // F-NEWPROJECT-1: клиентская валидация обязательных полей — показываем ошибку
    // сразу, не дожидаясь ответа сервера (сервер всё равно валидирует повторно).
    const clientErrors: Record<string, string[]> = {};
    if (!form.name.trim()) {
        clientErrors.name = ['Введите название проекта'];
    }
    if (form.signal_type === 'sms') {
        if (form.sms_senders.length === 0) {
            clientErrors.sms_senders = ['Укажите хотя бы одного отправителя'];
        }
    } else if (!form.signal_identifier.trim()) {
        clientErrors.signal_identifier = [
            form.signal_type === 'call' ? 'Введите номер в вашей нише' : 'Введите домен сайта в вашей нише',
        ];
    }
    // Гейт обязательного региона: нужны либо субъекты, либо подтверждённая «Вся РФ».
    if (form.regions.length === 0 && !vsyaRfConfirmed.value) {
        clientErrors.regions = ['Выберите регион или подтвердите «Вся РФ»'];
    }

    if (Object.keys(clientErrors).length > 0) {
        Object.assign(errors, clientErrors);
        return;
    }

    // Эпик 3.2: смена источника на залоченном проекте — через подтверждение.
    if (sourceLocked.value && sourceDirty.value) {
        sourceConfirmOpen.value = true;
        return;
    }

    await persist();
}

// Spec C §6.2 — исходы диалога перегрузки лимита.
async function onOverloadSaveBlocked(): Promise<void> {
    await persist({ force_save_blocked: true });
}

async function onOverloadSetZero(): Promise<void> {
    form.daily_limit_target = 0;
    overloadOpen.value = false;
    await persist();
}

function close() {
    emit('update:modelValue', false);
}

defineExpose({
    chooseVsyaRf,
    confirmVsyaRf,
    cancelVsyaRf,
    onRegionsChange,
    vsyaRf,
    vsyaRfConfirmed,
    form,
    submit,
    step,
    reqForm,
    saveRequisites,
});
</script>

<style scoped>
.source-hint {
    line-height: 1.4;
    padding: 4px 2px;
}
.steps-head {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
}
.steps-head .step-on {
    color: var(--liderra-teal, #0f6e56);
    font-weight: 650;
}
.steps-head .step-off {
    color: #6b6f72;
}
.steps-head .step-sep {
    flex: 1;
    height: 1px;
    background: var(--liderra-line, #e6e2d6);
}
.ld-input-quiet :deep(.v-field) {
    border-radius: var(--radius-8);
}
.ld-input-quiet :deep(.v-field__outline__start),
.ld-input-quiet :deep(.v-field__outline__end),
.ld-input-quiet :deep(.v-field__outline__notch::before),
.ld-input-quiet :deep(.v-field__outline__notch::after) {
    border-color: var(--liderra-line);
    opacity: 1;
    transition: border-color 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
.ld-input-quiet :deep(.v-field:hover .v-field__outline__start),
.ld-input-quiet :deep(.v-field:hover .v-field__outline__end),
.ld-input-quiet :deep(.v-field:hover .v-field__outline__notch::before),
.ld-input-quiet :deep(.v-field:hover .v-field__outline__notch::after) {
    border-color: var(--liderra-line-strong);
    opacity: 1;
}
.ld-input-quiet :deep(.v-field--focused .v-field__outline__start),
.ld-input-quiet :deep(.v-field--focused .v-field__outline__end),
.ld-input-quiet :deep(.v-field--focused .v-field__outline__notch::before),
.ld-input-quiet :deep(.v-field--focused .v-field__outline__notch::after) {
    border-color: var(--liderra-teal);
    opacity: 1;
}
.ld-input-quiet :deep(.v-field--error:not(.v-field--disabled) .v-field__outline__start),
.ld-input-quiet :deep(.v-field--error:not(.v-field--disabled) .v-field__outline__end),
.ld-input-quiet :deep(.v-field--error:not(.v-field--disabled) .v-field__outline__notch::before),
.ld-input-quiet :deep(.v-field--error:not(.v-field--disabled) .v-field__outline__notch::after) {
    border-color: currentColor;
    opacity: 1;
}
</style>
