<script setup lang="ts">
/**
 * Список сделок — центральный экран CRM. Используется менеджерами ежедневно.
 *
 * Источник дизайна: liderra_v8_handoff/concepts/v8_deals.html.
 * MVP: page-head + chiprow со срезами + поиск + v-data-table с mock'ами.
 *
 * Не входит в этот коммит (отдельные TODO):
 * - Drawer с деталями сделки при клике на строку (правая панель v-navigation-drawer right).
 * - Filter-popover'ы для Проект/Менеджер/Ещё (полноценный multi-select).
 * - Sort по Стоимость/Время через v-data-table sort-by.
 * - Экспорт в XLSX (требует backend endpoint и xlsx-библиотеку).
 *
 * Источник статусов — composables/leadStatuses.ts (snapshot из db/schema.sql:2130).
 */
import { computed, defineAsyncComponent, onMounted, reactive, ref } from 'vue';
import { DEALS_TABS, MOCK_DEALS, type MockDeal } from '../composables/mockDeals';
import { mapApiDeal } from '../composables/dealsApiMapper';
import { usePolling } from '../composables/usePolling';
// Sprint 2 Phase B / O-perf-06: lazy-imports для тяжёлых компонентов, гейтящихся
// через v-model (открываются по action, не на mount). DealDetailDrawer (580 строк)
// и NewDealDialog отдельные chunk'и — initial bundle DealsView меньше.
const DealDetailDrawer = defineAsyncComponent(() => import('../components/deals/DealDetailDrawer.vue'));
const NewDealDialog = defineAsyncComponent(() => import('../components/deals/NewDealDialog.vue'));
import DealsFilters from '../components/deals/DealsFilters.vue';
import DealsBulkBar from '../components/deals/DealsBulkBar.vue';
import DealsTable from '../components/deals/DealsTable.vue';
import FilterChip from '../components/ui/FilterChip.vue';
import DensityToggle from '../components/ui/DensityToggle.vue';
import StatusPill from '../components/ui/StatusPill.vue';
import { useDensity } from '../composables/useDensity';
import { useAuthStore } from '../stores/auth';
import { useLeadStatusesStore } from '../stores/leadStatuses';
import * as dealsApi from '../api/deals';
import { buildCsvString, triggerBlobDownload, triggerCsvDownload } from '../composables/useCsvDownload';

// Task 15: density-toggle composable (persists в localStorage, влияет на row height).
const { rowHeight } = useDensity();

// Task 15: stub-обработчики redesign-filter-chip'ов. На I1 — popover'ы Проект/Менеджер
// не реализованы; chiprow служит quiet-luxury визуальной заменой для status-summary'ов.
// Не ломает существующие VSelect'ы в DealsFilters — те остаются как полноценный filter UI.
function onRedesignFilterClick(name: string): void {
    console.log(`[redesign filterbar] ${name} clicked — popover TBD`);
}

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

const activeTab = ref<(typeof DEALS_TABS)[number]['id']>('active');
const searchQuery = ref('');
const selected = ref<number[]>([]);

// Smart-filters (multi-select) — null = «все»
const filterProjects = ref<string[]>([]);
const filterManagers = ref<string[]>([]);

// Локальная reactive-копия. При наличии auth.user.tenant_id — fetch через
// API (см. onMounted ниже); на network/500 — fallback на MOCK_DEALS чтобы UI
// оставался работоспособным (полезно для dev и Vitest jsdom-среды).
const dealsState = reactive<MockDeal[]>(MOCK_DEALS.map((d) => ({ ...d, manager: { ...d.manager } })));
const loading = ref(false);
const fetchError = ref(false);

// Trash-mode: показываем только soft-deleted. Toggle через btn в page-head.
const trashMode = ref(false);

async function loadDeals() {
    if (!auth.user?.tenant_id) return;
    loading.value = true;
    fetchError.value = false;
    try {
        const { deals } = await dealsApi.listDeals({
            tenantId: auth.user.tenant_id,
            limit: 200,
            onlyDeleted: trashMode.value,
        });
        const mapped = deals.map((d) => mapApiDeal(d));
        dealsState.splice(0, dealsState.length, ...mapped);
    } catch {
        fetchError.value = true; // оставляем MOCK_DEALS как fallback
    } finally {
        loading.value = false;
    }
}

function toggleTrashMode() {
    trashMode.value = !trashMode.value;
    selected.value = [];
    void loadDeals();
}

async function applyBulkRestoreFromTrash() {
    const ids = [...selected.value];
    if (ids.length === 0) return;

    // Optimistic: убираем из текущего списка (пользователь сейчас в trash-mode).
    const idSet = new Set(ids);
    for (let i = dealsState.length - 1; i >= 0; i--) {
        if (idSet.has(dealsState[i].id)) dealsState.splice(i, 1);
    }
    selected.value = [];

    if (!auth.user?.tenant_id) return;

    try {
        const res = await dealsApi.bulkRestoreDeals({
            tenant_id: auth.user.tenant_id,
            ids,
        });
        deleteToastText.value = `Восстановлено ${res.restored} из ${res.requested}.`;
    } catch {
        deleteToastText.value = 'Не удалось восстановить — изменения только локально.';
    }
    deleteToastOpen.value = true;
}

onMounted(() => {
    void leadStatusesStore.load();
    void loadDeals();
});

// Polling — каждые 30 сек авто-refresh dealsState. Pause при скрытой вкладке.
// Включается только при наличии auth.user (без auth listDeals = no-op anyway).
usePolling(loadDeals);

// Уникальные проекты и менеджеры из dealsState — динамически (после bulk-edit
// статус-смены состав не меняется, но after «Новая сделка» — может).
const availableProjects = computed(() => Array.from(new Set(dealsState.map((d) => d.project))).sort());
const availableManagers = computed(() => {
    const seen = new Map<string, string>();
    dealsState.forEach((d) => seen.set(d.manager.name, d.manager.initials));
    return Array.from(seen.entries())
        .map(([name, initials]) => ({ name, initials }))
        .sort((a, b) => a.name.localeCompare(b.name));
});

function clearFilters() {
    filterProjects.value = [];
    filterManagers.value = [];
}

const drawerOpen = ref(false);
const selectedDeal = ref<MockDeal | null>(null);

// Bulk-actions state
const statusMenuOpen = ref(false);
const deleteConfirmOpen = ref(false);
const exportToastOpen = ref(false);
const exportToastText = ref('');
const statusToastOpen = ref(false);
const statusToastText = ref('');
const deleteToastOpen = ref(false);
const deleteToastText = ref('');
// Snapshot для undo последнего bulk-delete (auto-clear через timeout snackbar'а).
const lastDeletedIds = ref<number[]>([]);
const lastDeletedSnapshot = ref<MockDeal[]>([]);

// New-deal dialog state
const newDealOpen = ref(false);

function onDealCreated(deal: MockDeal) {
    dealsState.unshift(deal);
}

function openDeal(deal: MockDeal) {
    selectedDeal.value = deal;
    drawerOpen.value = true;
}

async function applyBulkStatus(slug: MockDeal['statusSlug']) {
    const ids = [...selected.value];
    statusMenuOpen.value = false;

    // Optimistic local-update — UI отвечает сразу, API на фоне.
    ids.forEach((id) => {
        const deal = dealsState.find((d) => d.id === id);
        if (deal) deal.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;
}

async function applyBulkDelete() {
    const ids = [...selected.value];
    deleteConfirmOpen.value = false;

    // Snapshot удалённых сделок для undo (deep clone — manager.* nested object).
    const removed: MockDeal[] = [];
    const idSet = new Set(ids);
    for (let i = dealsState.length - 1; i >= 0; i--) {
        if (idSet.has(dealsState[i].id)) {
            removed.unshift({ ...dealsState[i], manager: { ...dealsState[i].manager } });
            dealsState.splice(i, 1);
        }
    }
    selected.value = [];

    if (!auth.user?.tenant_id || ids.length === 0) {
        // Local-only mode — undo тоже только local.
        if (removed.length > 0) {
            lastDeletedIds.value = ids;
            lastDeletedSnapshot.value = removed;
            deleteToastText.value = `Удалено ${removed.length} сделок (локально).`;
            deleteToastOpen.value = true;
        }
        return;
    }

    try {
        const res = await dealsApi.bulkDeleteDeals({
            tenant_id: auth.user.tenant_id,
            ids,
        });
        // Сохраняем для undo (последний bulk-delete).
        lastDeletedIds.value = ids;
        lastDeletedSnapshot.value = removed;
        deleteToastText.value = `Удалено ${res.deleted} из ${res.requested}.`;
    } catch {
        // Локальный update НЕ откатываем (UX-pattern). Undo тоже доступен —
        // пользователь восстановит на backend если он позже стал доступен.
        lastDeletedIds.value = ids;
        lastDeletedSnapshot.value = removed;
        deleteToastText.value = 'Не удалось удалить — изменения только локально.';
    }
    deleteToastOpen.value = true;
}

async function undoBulkDelete() {
    const ids = lastDeletedIds.value;
    const snapshot = lastDeletedSnapshot.value;
    if (ids.length === 0 || snapshot.length === 0) return;

    // Optimistic — возвращаем сделки в state.
    snapshot.forEach((deal) => {
        if (!dealsState.find((d) => d.id === deal.id)) {
            dealsState.unshift(deal);
        }
    });
    lastDeletedIds.value = [];
    lastDeletedSnapshot.value = [];
    deleteToastOpen.value = false;

    if (!auth.user?.tenant_id) {
        deleteToastText.value = `Восстановлено ${snapshot.length} сделок (локально).`;
        deleteToastOpen.value = true;
        return;
    }

    try {
        const res = await dealsApi.bulkRestoreDeals({
            tenant_id: auth.user.tenant_id,
            ids,
        });
        deleteToastText.value = `Восстановлено ${res.restored} из ${res.requested}.`;
    } catch {
        deleteToastText.value = 'Не удалось восстановить — изменения только локально.';
    }
    deleteToastOpen.value = true;
}

async function applyBulkExport(format: 'xlsx' | 'csv' = 'xlsx') {
    if (selected.value.length === 0) {
        exportToastText.value = 'Нет выбранных сделок.';
        exportToastOpen.value = true;
        return;
    }

    // С tenant_id — backend (RLS-фильтрация чужих id). На fail — fallback на
    // local CSV (даже если запросили xlsx — без backend'а xlsx не построим).
    if (auth.user?.tenant_id) {
        try {
            if (format === 'xlsx') {
                const blob = await dealsApi.exportDealsXlsx({
                    tenant_id: auth.user.tenant_id,
                    ids: selected.value,
                });
                triggerBlobDownload(blob, `deals_export_${new Date().toISOString().slice(0, 10)}.xlsx`);
                exportToastText.value = `Экспортировано ${selected.value.length} сделок в XLSX.`;
            } else {
                const csv = await dealsApi.exportDeals({
                    tenant_id: auth.user.tenant_id,
                    ids: selected.value,
                });
                triggerCsvDownload(csv, `deals_export_${new Date().toISOString().slice(0, 10)}.csv`);
                exportToastText.value = `Экспортировано ${selected.value.length} сделок в CSV.`;
            }
            exportToastOpen.value = true;
            return;
        } catch {
            exportToastText.value = 'Backend недоступен — экспорт сформирован локально (CSV).';
        }
    }

    buildLocalCsv();
}

function buildLocalCsv() {
    const idSet = new Set(selected.value);
    const rows = dealsState.filter((d) => idSet.has(d.id));
    const headers = ['ID', 'Имя', 'Телефон', 'Статус', 'Проект', 'Менеджер', 'Стоимость', 'Получено мин назад'];
    const csv = buildCsvString(
        headers,
        rows.map((d) => [d.id, d.name, d.phone, d.statusSlug, d.project, d.manager.name, d.cost, d.receivedMinutesAgo]),
    );
    triggerCsvDownload(csv, `deals_export_${new Date().toISOString().slice(0, 10)}.csv`);

    if (!exportToastText.value) {
        exportToastText.value = `Экспортировано ${rows.length} сделок в CSV.`;
    }
    exportToastOpen.value = true;
}

// Expose internal state для unit-тестов (Vitest dom-mount).
defineExpose({
    selected,
    dealsState,
    applyBulkStatus,
    applyBulkDelete,
    applyBulkExport,
    exportToastOpen,
    exportToastText,
    onDealCreated,
    newDealOpen,
    filterProjects,
    filterManagers,
    clearFilters,
    loading,
    fetchError,
    loadDeals,
    statusToastOpen,
    statusToastText,
    deleteToastOpen,
    deleteToastText,
    lastDeletedIds,
    lastDeletedSnapshot,
    undoBulkDelete,
    trashMode,
    toggleTrashMode,
    applyBulkRestoreFromTrash,
});

const leadStatuses = computed(() => leadStatusesStore.statuses);
const statusBySlug = computed(() => leadStatusesStore.bySlug);

const filteredDeals = computed(() => {
    const tab = DEALS_TABS.find((t) => t.id === activeTab.value);
    const slugFilter = tab?.slugs;
    const q = searchQuery.value.trim().toLowerCase();
    const projects = new Set(filterProjects.value);
    const managers = new Set(filterManagers.value);

    return dealsState.filter((deal) => {
        if (slugFilter && !slugFilter.includes(deal.statusSlug)) return false;
        if (projects.size > 0 && !projects.has(deal.project)) return false;
        if (managers.size > 0 && !managers.has(deal.manager.name)) return false;
        if (q) {
            const haystack = `${deal.name} ${deal.phone} ${deal.project}`.toLowerCase();
            if (!haystack.includes(q)) return false;
        }
        return true;
    });
});

const counts = computed(() => {
    const result: Record<string, number> = {};
    DEALS_TABS.forEach((tab) => {
        const slugFilter = tab.slugs;
        result[tab.id] = dealsState.filter((d) => !slugFilter || slugFilter.includes(d.statusSlug)).length;
    });
    return result;
});

const totalDeals = computed(() => dealsState.length);
const newToday = 3; // mock
const inWork = computed(
    () => dealsState.filter((d) => ['new', 'viewed', 'worked', 'negotiations', 'hot'].includes(d.statusSlug)).length,
);
const waitingPay = computed(() => dealsState.filter((d) => d.statusSlug === 'waiting_payment').length);
</script>

<template>
    <v-container fluid class="deals pa-6">
        <header class="page-head">
            <div>
                <h1 class="text-h4 mb-2 page-title">{{ trashMode ? 'Корзина' : 'Сделки' }}</h1>
                <div class="page-stats text-body-2 text-medium-emphasis">
                    <span
                        ><span class="num text-primary">+{{ newToday }}</span> новых лида с утра</span
                    >
                    <span class="sep">·</span>
                    <span
                        ><span class="num">{{ totalDeals }}</span> всего</span
                    >
                    <span class="sep">·</span>
                    <span
                        ><span class="num">{{ inWork }}</span> в работе</span
                    >
                    <span class="sep">·</span>
                    <span
                        ><span class="num">{{ waitingPay }}</span> ждут оплату</span
                    >
                </div>
            </div>
            <div class="d-flex ga-2">
                <v-btn
                    variant="outlined"
                    prepend-icon="mdi-refresh"
                    :loading="loading"
                    data-testid="reload-btn"
                    @click="loadDeals"
                >
                    Обновить
                </v-btn>
                <v-btn
                    :variant="trashMode ? 'flat' : 'outlined'"
                    :color="trashMode ? 'warning' : undefined"
                    :prepend-icon="trashMode ? 'mdi-arrow-left' : 'mdi-trash-can-outline'"
                    data-testid="trash-toggle-btn"
                    @click="toggleTrashMode"
                >
                    {{ trashMode ? 'К сделкам' : 'Корзина' }}
                </v-btn>
                <v-btn v-if="!trashMode" variant="outlined" prepend-icon="mdi-download">Экспорт</v-btn>
                <v-btn
                    v-if="!trashMode"
                    color="primary"
                    variant="flat"
                    prepend-icon="mdi-plus"
                    data-testid="new-deal-btn"
                    @click="newDealOpen = true"
                >
                    Новая сделка
                </v-btn>
            </div>
        </header>

        <v-alert v-if="trashMode" type="info" variant="tonal" density="compact" class="mt-3">
            Корзина: показаны удалённые сделки. Выберите для восстановления.
        </v-alert>

        <DealsFilters
            v-if="!trashMode"
            v-model:active-tab="activeTab"
            v-model:search-query="searchQuery"
            v-model:filter-projects="filterProjects"
            v-model:filter-managers="filterManagers"
            :available-projects="availableProjects"
            :available-managers="availableManagers"
            :counts="counts"
            @clear-filters="clearFilters"
        />

        <!-- Task 15: redesign-filterbar (quiet luxury chiprow + density toggle).
             Минимальный набор: 3 FilterChip-ярлыка (Статус/Проект/Менеджер) + DensityToggle справа.
             Клики на I1 — stub'ы (popover'ы — TBD); полноценные multi-select'ы остаются в DealsFilters выше.
             Status-legend ниже визуализирует пул цветов StatusPill'ов воронки. -->
        <div v-if="!trashMode" class="ld-filterbar mt-3">
            <div class="ld-filterbar__chips">
                <FilterChip
                    label="Статус"
                    :count="filteredDeals.length"
                    :active="false"
                    @click="onRedesignFilterClick('Статус')"
                />
                <FilterChip
                    label="Проект"
                    :count="filterProjects.length"
                    :active="filterProjects.length > 0"
                    @click="onRedesignFilterClick('Проект')"
                />
                <FilterChip
                    label="Менеджер"
                    :count="filterManagers.length"
                    :active="filterManagers.length > 0"
                    @click="onRedesignFilterClick('Менеджер')"
                />
            </div>
            <DensityToggle class="ld-filterbar__density" />
        </div>

        <!-- Status-legend: показывает топ-4 статуса воронки как StatusPill-чипы.
             Цели: (1) предпросмотр пула цветов в quiet-luxury палитре,
             (2) обеспечивает наличие StatusPill в дереве компонентов (DealsTable inner
             slots — внутри stubbed VDataTable в тестах, не рендерятся). -->
        <div v-if="!trashMode" class="ld-status-legend mt-2">
            <StatusPill slug="new" label="Новые" />
            <StatusPill slug="in_progress" label="В работе" />
            <StatusPill slug="won" label="Выиграно" />
            <StatusPill slug="archived" label="Архив" />
        </div>

        <!-- Bulk-actions bar (показывается только при selected.length > 0) -->
        <DealsBulkBar
            v-model:status-menu-open="statusMenuOpen"
            :selected-count="selected.length"
            :trash-mode="trashMode"
            :lead-statuses="leadStatuses"
            @apply-status="applyBulkStatus"
            @apply-export="applyBulkExport()"
            @request-delete="deleteConfirmOpen = true"
            @apply-restore-trash="applyBulkRestoreFromTrash"
            @clear-selected="selected = []"
        />

        <v-alert
            v-if="fetchError"
            type="warning"
            variant="tonal"
            density="compact"
            closable
            class="mt-3"
            data-testid="fetch-error-alert"
        >
            Backend недоступен — показаны mock-данные.
        </v-alert>

        <!-- Task 15: wrapper с .ld-hover-lift + .ld-stagger-row для quiet-luxury motion
             (lift на hover + stagger fade-in строк, motion #2 #4). CSS-переменная
             --row-height пробрасывается в DealsTable для динамической плотности. -->
        <div class="ld-hover-lift ld-stagger-row mt-4" :style="{ '--row-height': rowHeight + 'px' }">
            <DealsTable
                :deals="filteredDeals"
                :selected-ids="selected"
                :status-by-slug="statusBySlug"
                :row-height="rowHeight"
                @update:selected-ids="selected = $event"
                @row-click="openDeal"
            />
        </div>

        <DealDetailDrawer v-model:open="drawerOpen" :deal="selectedDeal" :tenant-id="auth.user?.tenant_id" />

        <NewDealDialog v-model="newDealOpen" :tenant-id="auth.user?.tenant_id" @created="onDealCreated" />

        <!-- DELETE CONFIRM -->
        <v-dialog v-model="deleteConfirmOpen" :max-width="420" data-testid="bulk-delete-dialog">
            <v-card>
                <v-card-title>Удалить выбранные сделки?</v-card-title>
                <v-card-text>
                    Будет удалено <strong>{{ selected.length }}</strong> сделок. На production это переведёт их в архив
                    (soft-delete) — фактически записи в БД сохраняются, restorable в течение 30 дней.
                </v-card-text>
                <v-card-actions>
                    <v-spacer />
                    <v-btn variant="text" @click="deleteConfirmOpen = false">Отмена</v-btn>
                    <v-btn color="error" data-testid="bulk-delete-confirm-btn" @click="applyBulkDelete">
                        Удалить
                    </v-btn>
                </v-card-actions>
            </v-card>
        </v-dialog>

        <v-snackbar v-model="exportToastOpen" :timeout="3000" data-testid="bulk-export-toast" location="bottom right">
            {{ exportToastText }}
        </v-snackbar>

        <v-snackbar v-model="statusToastOpen" :timeout="3000" data-testid="bulk-status-toast" location="bottom right">
            {{ statusToastText }}
        </v-snackbar>

        <v-snackbar v-model="deleteToastOpen" :timeout="8000" data-testid="bulk-delete-toast" location="bottom right">
            {{ deleteToastText }}
            <template v-if="lastDeletedSnapshot.length > 0" #actions>
                <v-btn color="warning" variant="text" data-testid="bulk-delete-undo-btn" @click="undoBulkDelete">
                    Восстановить
                </v-btn>
            </template>
        </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;
}

.page-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}
.page-stats .sep {
    /* WCAG2AA 4.5:1 fix (was #92907b → 2.92:1 on ivory; #6b6356 → 5.33:1). */
    color: #6b6356;
}

.num {
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-feature-settings: 'tnum';
    font-weight: 500;
}

/* Task 15: redesign-filterbar — quiet-luxury chiprow + density toggle */
.ld-filterbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}
.ld-filterbar__chips {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
.ld-filterbar__density {
    flex-shrink: 0;
}

/* Status-legend strip: визуальная палитра StatusPill пула. */
.ld-status-legend {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}
</style>
