<script setup lang="ts">
/**
 * Страница «Сделки» — реестр лидов, поставленных crm.bp (редизайн 2026-05-17).
 *
 * Лиды поступают ТОЛЬКО от поставщика — ручного создания и корзины нет.
 * Фильтрация (телефон/Статус/Проект + диапазон дат поставки) и пагинация —
 * server-side через GET /api/deals. Клик по строке открывает master-detail
 * панель справа (список сжимается, панель не перекрывает таблицу).
 *
 * Спека: docs/superpowers/specs/2026-05-17-deals-page-redesign-design.md
 */
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import type { MockDeal } from '../composables/mockDeals';
import { mapApiDeal } from '../composables/dealsApiMapper';
import { stripChannelPrefix } from '../composables/projectName';
import { usePolling } from '../composables/usePolling';
import RuDateField from '../components/common/RuDateField.vue';
import DealsFilters from '../components/deals/DealsFilters.vue';
import DealsBulkBar from '../components/deals/DealsBulkBar.vue';
import DealsTable from '../components/deals/DealsTable.vue';
import DealDetailDrawer from '../components/deals/DealDetailDrawer.vue';
import { useAuthStore } from '../stores/auth';
import { useLeadStatusesStore } from '../stores/leadStatuses';
import * as dealsApi from '../api/deals';
import { triggerBlobDownload, triggerCsvDownload } from '../composables/useCsvDownload';

const route = useRoute();
const auth = useAuthStore();
const leadStatusesStore = useLeadStatusesStore();

// --- Фильтры (single-select) + диапазон дат поставки ---
const searchPhone = ref('');
const filterStatus = ref<string | null>(null);
const filterProject = ref<number | null>(null);
const filterCity = ref<string | null>(null);
const receivedFrom = ref(''); // 'YYYY-MM-DD'
const receivedTo = ref('');

// --- Пагинация ---
const PER_PAGE_OPTIONS = [10, 20, 50];
const perPage = ref(20);
const page = ref(1);

// --- Данные текущей страницы ---
const dealsState = reactive<MockDeal[]>([]);
const total = ref(0);
const loading = ref(false);
const fetchError = ref(false);
const availableProjects = ref<dealsApi.ApiProject[]>([]);
// Список для фильтра «Проект» — без префикса B1_/B2_/B3_ (display-only;
// id сохраняем, фильтрация идёт по id, не по name).
const availableProjectsForFilter = computed(() =>
    availableProjects.value.map((p) => ({ ...p, name: stripChannelPrefix(p.name) })),
);

const leadStatuses = computed(() => leadStatusesStore.statuses);
const statusBySlug = computed(() => leadStatusesStore.bySlug);
const availableCities = computed(() =>
    Array.from(new Set(dealsState.map((d) => d.city).filter((c): c is string => !!c))).sort(),
);
const pageCount = computed(() => Math.max(1, Math.ceil(total.value / perPage.value)));

// --- Master-detail панель ---
const selectedDeal = ref<MockDeal | null>(null);
const panelOpen = ref(false);

// --- Bulk-смена статуса ---
const selected = ref<number[]>([]);
const statusMenuOpen = ref(false);

// --- Тосты ---
const exportToastOpen = ref(false);
const exportToastText = ref('');
const statusToastOpen = ref(false);
const statusToastText = ref('');

async function loadDeals() {
    if (!auth.user?.tenant_id) return;
    loading.value = true;
    fetchError.value = false;
    try {
        const res = await dealsApi.listDeals({
            tenantId: auth.user.tenant_id,
            statusIn: filterStatus.value ? [filterStatus.value] : undefined,
            projectId: filterProject.value ?? undefined,
            search: searchPhone.value.trim() || undefined,
            receivedFrom: receivedFrom.value || undefined,
            receivedTo: receivedTo.value || undefined,
            limit: perPage.value,
            offset: (page.value - 1) * perPage.value,
        });
        total.value = res.total;
        dealsState.splice(0, dealsState.length, ...res.deals.map((d) => mapApiDeal(d)));
    } catch {
        fetchError.value = true;
        dealsState.splice(0, dealsState.length);
        total.value = 0;
    } finally {
        loading.value = false;
    }
}

async function loadProjects() {
    if (!auth.user?.tenant_id) return;
    try {
        availableProjects.value = await dealsApi.listProjects(auth.user.tenant_id);
    } catch {
        availableProjects.value = [];
    }
}

// Фильтры (кроме поиска) и perPage → стр.1 + ОДНА перезагрузка.
// page!==1 → меняем page, перезагрузку делает watch(page); page===1 → грузим напрямую.
watch([filterStatus, filterProject, receivedFrom, receivedTo, perPage], () => {
    if (page.value !== 1) {
        page.value = 1;
    } else {
        void loadDeals();
    }
});
watch(page, () => void loadDeals());

// Selected-driven drawer visibility (18.05.2026 ux-request):
// 0 selected → drawer по row-click; 1 selected → авто-открыт для этой сделки;
// ≥2 selected → закрыт (показывается bulk-полоса).
watch(selected, (ids) => {
    if (ids.length === 1) {
        const deal = dealsState.find((d) => d.id === ids[0]);
        if (deal) {
            selectedDeal.value = deal;
            panelOpen.value = true;
        }
    } else if (ids.length >= 2) {
        panelOpen.value = false;
    }
});

// Поиск по телефону — debounce 350 мс.
let searchTimer: ReturnType<typeof setTimeout> | undefined;
watch(searchPhone, () => {
    if (searchTimer) clearTimeout(searchTimer);
    searchTimer = setTimeout(() => {
        if (page.value !== 1) {
            page.value = 1;
        } else {
            void loadDeals();
        }
    }, 350);
});

onBeforeUnmount(() => {
    if (searchTimer) clearTimeout(searchTimer);
});

function clearFilters() {
    searchPhone.value = '';
    filterStatus.value = null;
    filterProject.value = null;
    filterCity.value = null;
}

/**
 * 18.05.2026 ux — inline status picker в drawer (DealDetailHero).
 * Optimistic UI: меняем statusSlug в dealsState ДО API, rollback при ошибке.
 */
async function onDrawerStatusChanged(slug: string): Promise<void> {
    if (!auth.user?.tenant_id || !selectedDeal.value) return;
    const id = selectedDeal.value.id;
    const target = dealsState.find((d) => d.id === id);
    if (!target) return;
    const prev = target.statusSlug;
    if (prev === slug) return;
    target.statusSlug = slug as MockDeal['statusSlug'];
    try {
        await dealsApi.updateDeal(id, { tenant_id: auth.user.tenant_id, status: slug });
        statusToastText.value = 'Статус обновлён.';
    } catch {
        target.statusSlug = prev;
        statusToastText.value = 'Не удалось сохранить статус.';
    }
    statusToastOpen.value = true;
}

async function applyBulkStatus(slug: MockDeal['statusSlug']) {
    const ids = [...selected.value];
    statusMenuOpen.value = false;
    ids.forEach((id) => {
        const d = dealsState.find((x) => x.id === id);
        if (d) d.statusSlug = slug;
    });
    if (!auth.user?.tenant_id || ids.length === 0) return;
    try {
        const res = await dealsApi.transitionDeals({ tenant_id: auth.user.tenant_id, ids, status: slug });
        statusToastText.value = `Обновлено ${res.updated} из ${res.requested}.`;
    } catch {
        statusToastText.value = 'Не удалось сохранить статус — изменения только локально.';
    }
    statusToastOpen.value = true;
    selected.value = [];
}

async function exportByRange(format: 'xlsx' | 'csv') {
    if (!auth.user?.tenant_id) {
        exportToastText.value = 'Нет данных для экспорта.';
        exportToastOpen.value = true;
        return;
    }
    try {
        const result = await dealsApi.exportDealsByRange({
            tenant_id: auth.user.tenant_id,
            received_from: receivedFrom.value || undefined,
            received_to: receivedTo.value || undefined,
            format,
        });
        const stamp = new Date().toISOString().slice(0, 10);
        if (format === 'xlsx') {
            triggerBlobDownload(result as Blob, `deals_export_${stamp}.xlsx`);
            exportToastText.value = 'Экспорт XLSX сформирован.';
        } else {
            triggerCsvDownload(result as string, `deals_export_${stamp}.csv`);
            exportToastText.value = 'Экспорт CSV сформирован.';
        }
    } catch {
        exportToastText.value = 'Не удалось сформировать экспорт.';
    }
    exportToastOpen.value = true;
}

function openPanel(deal: MockDeal) {
    if (panelOpen.value && selectedDeal.value?.id === deal.id) {
        closePanel();
        return;
    }
    selectedDeal.value = deal;
    panelOpen.value = true;
}

function closePanel() {
    panelOpen.value = false;
}

/** Deep-link /deals?openId= — открыть панель сделки с текущей страницы (audit C8/F3). */
function openDealFromQuery(): void {
    const raw = route.query.openId;
    const id = Number(Array.isArray(raw) ? raw[0] : raw);
    if (!Number.isInteger(id) || id <= 0) return;
    const deal = dealsState.find((d) => d.id === id);
    if (deal) openPanel(deal);
}

onMounted(async () => {
    void leadStatusesStore.load();
    void loadProjects();
    await loadDeals();
    openDealFromQuery();
});

watch(
    () => route.query.openId,
    () => openDealFromQuery(),
);

// Polling — авто-refresh текущей страницы (pause при скрытой вкладке).
usePolling(loadDeals);

defineExpose({
    searchPhone,
    filterStatus,
    filterProject,
    filterCity,
    receivedFrom,
    receivedTo,
    perPage,
    page,
    pageCount,
    dealsState,
    total,
    loading,
    fetchError,
    availableProjects,
    selected,
    selectedDeal,
    panelOpen,
    statusMenuOpen,
    loadDeals,
    clearFilters,
    applyBulkStatus,
    exportByRange,
    openPanel,
    closePanel,
    exportToastOpen,
    exportToastText,
    statusToastOpen,
    statusToastText,
});
</script>

<template>
    <v-container fluid class="deals pa-6">
        <header class="page-head">
            <div>
                <h1 class="text-h4 mb-2 page-title">Сделки</h1>
                <div class="page-stats text-body-2 text-medium-emphasis">
                    Ваши заявки ·
                    <span class="num">{{ total }}</span> в выборке
                </div>
            </div>
            <v-btn
                variant="outlined"
                prepend-icon="mdi-refresh"
                :loading="loading"
                data-testid="reload-btn"
                @click="loadDeals"
            >
                Обновить
            </v-btn>
        </header>

        <!-- Панель экспорта: диапазон дат поставки + Excel/CSV -->
        <v-card variant="outlined" class="export-panel mt-4">
            <div class="export-panel-inner">
                <span class="export-label text-body-2">Диапазон поставки:</span>
                <RuDateField v-model="receivedFrom" label="от" class="date-input" data-testid="export-from" />
                <RuDateField v-model="receivedTo" label="до" class="date-input" data-testid="export-to" />
                <v-spacer />
                <v-btn
                    variant="outlined"
                    prepend-icon="mdi-file-excel-outline"
                    data-testid="export-xlsx-btn"
                    @click="exportByRange('xlsx')"
                >
                    Экспорт в Excel
                </v-btn>
                <v-btn
                    variant="outlined"
                    prepend-icon="mdi-file-delimited-outline"
                    data-testid="export-csv-btn"
                    @click="exportByRange('csv')"
                >
                    Экспорт в CSV
                </v-btn>
            </div>
        </v-card>

        <DealsFilters
            v-model:search-phone="searchPhone"
            v-model:filter-status="filterStatus"
            v-model:filter-project="filterProject"
            v-model:filter-city="filterCity"
            :lead-statuses="leadStatuses"
            :available-projects="availableProjectsForFilter"
            :available-cities="availableCities"
            class="mt-4"
            @clear-filters="clearFilters"
        />

        <!-- Селектор числа строк — между фильтрами и таблицей -->
        <div class="perpage mt-3">
            <span class="text-body-2 text-medium-emphasis">Показывать по:</span>
            <v-btn-toggle
                v-model="perPage"
                mandatory
                density="comfortable"
                variant="outlined"
                color="primary"
                data-testid="perpage-toggle"
            >
                <v-btn v-for="n in PER_PAGE_OPTIONS" :key="n" :value="n" size="small">{{ n }}</v-btn>
            </v-btn-toggle>
        </div>

        <DealsBulkBar
            v-if="selected.length >= 2"
            v-model:status-menu-open="statusMenuOpen"
            :selected-count="selected.length"
            :lead-statuses="leadStatuses"
            @apply-status="applyBulkStatus"
            @clear-selected="selected = []"
        />

        <v-alert
            v-if="fetchError"
            type="warning"
            variant="tonal"
            density="compact"
            closable
            class="mt-3"
            data-testid="fetch-error-alert"
        >
            Не удалось загрузить сделки. Попробуйте обновить.
        </v-alert>

        <!-- master-detail: список сжимается, панель встаёт рядом (не overlay) -->
        <div class="deals-body mt-4">
            <div class="deals-list">
                <DealsTable
                    :deals="dealsState"
                    :selected-ids="selected"
                    :status-by-slug="statusBySlug"
                    :active-deal-id="panelOpen ? (selectedDeal?.id ?? null) : null"
                    @update:selected-ids="selected = $event"
                    @row-click="openPanel"
                />
            </div>
            <DealDetailDrawer
                inline
                :open="panelOpen"
                :deal="selectedDeal"
                :tenant-id="auth.user?.tenant_id"
                @update:open="(v: boolean) => (panelOpen = v)"
                @status-changed="onDrawerStatusChanged"
            />
        </div>

        <!-- Футер: пагинация -->
        <div v-if="pageCount > 1" class="tfoot mt-4">
            <v-pagination
                v-model="page"
                :length="pageCount"
                :total-visible="7"
                density="comfortable"
                data-testid="deals-pagination"
            />
        </div>

        <v-snackbar v-model="exportToastOpen" :timeout="3000" data-testid="export-toast" location="bottom right">
            {{ exportToastText }}
        </v-snackbar>
        <v-snackbar v-model="statusToastOpen" :timeout="3000" data-testid="status-toast" location="bottom right">
            {{ statusToastText }}
        </v-snackbar>
    </v-container>
</template>

<style scoped>
.deals {
    max-width: 1440px;
}
.page-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 16px;
}
.page-title {
    font-variation-settings: 'opsz' 28;
    letter-spacing: -0.018em;
}
.num {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-feature-settings: 'tnum';
    font-weight: 500;
}
.export-panel {
    background: #fff;
}
.export-panel-inner {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 12px 16px;
}
.export-label {
    color: #6b6356;
    flex-shrink: 0;
}
.date-input {
    max-width: 170px;
}
.perpage {
    display: flex;
    align-items: center;
    gap: 12px;
}
.deals-body {
    display: flex;
    align-items: flex-start;
    gap: 16px;
}
.deals-list {
    flex: 1 1 auto;
    min-width: 0;
}
.tfoot {
    display: flex;
    justify-content: center;
}
</style>
