<script setup lang="ts">
/**
 * Канбан — альтернативный вид сделок (по статусам). 5 колонок (lead_statuses).
 *
 * Источник дизайна: liderra_v8_handoff/concepts/v8_kanban.html.
 * DnD реализован через vuedraggable@4 (обёртка SortableJS) — карточки можно
 * перетаскивать между колонками. При drop:
 *   - событие 'added' в целевой колонке → optimistic update statusSlug +
 *     POST /api/deals/transition (через dealsApi). На failure — revert:
 *     карточка возвращается в исходную колонку + toast «Не удалось переместить».
 *     Без auth.user.tenant_id — local-only mode (API не зовётся).
 *   - событие 'removed' в исходной колонке → ничего не делаем (обработано в added).
 *   - событие 'moved' внутри одной колонки → только смена порядка (statusSlug
 *     не меняется; на API будущем — PATCH /api/deals/{id} {sort_order}).
 *
 * Не входит в этот коммит:
 * - PATCH /api/deals/{id} {sort_order} при moved (intra-column reorder) — backend.
 * - Filters (Проект/Менеджер) — общий filter-bar с DealsView.
 * - DealDetailDrawer на click по карточке (event @open-deal).
 */
import { computed, onMounted, reactive, ref } from 'vue';
import { LEAD_STATUSES } from '../composables/leadStatuses';
import { type MockDeal } from '../composables/mockDeals';
import { mapApiDeal } from '../composables/dealsApiMapper';
import { usePolling } from '../composables/usePolling';
import * as dealsApi from '../api/deals';
import KanbanColumn from '../components/kanban/KanbanColumn.vue';
// NB: DealDetailDrawer и NewDealDialog оставлены sync — Vitest падает с
// EnvironmentTeardownError при async-загрузке chunk'ов после teardown jsdom
// (KanbanView.spec.ts). Sprint 2 Phase B / O-perf-06 split применён к DealsView.
import DealDetailDrawer from '../components/deals/DealDetailDrawer.vue';
import NewDealDialog from '../components/deals/NewDealDialog.vue';
import { useAuthStore } from '../stores/auth';
import { useLeadStatusesStore } from '../stores/leadStatuses';

const auth = useAuthStore();
const leadStatusesStore = useLeadStatusesStore();
const leadStatuses = computed(() => leadStatusesStore.statuses);

interface DraggableChangeEvent {
    added?: { element: MockDeal; newIndex: number };
    removed?: { element: MockDeal; oldIndex: number };
    moved?: { element: MockDeal; oldIndex: number; newIndex: number };
}

// Reactive Record<slug, MockDeal[]> — отдельный массив для каждой колонки
// (vuedraggable v-model требует независимые arrays).
const dealsByStatus = reactive<Record<string, MockDeal[]>>(
    LEAD_STATUSES.reduce<Record<string, MockDeal[]>>((acc, s) => {
        acc[s.slug] = [];
        return acc;
    }, {}),
);

/**
 * 18.05.2026 ux — inline status picker в drawer (DealDetailHero).
 * При смене статуса через drawer — переносим карточку между колонками
 * Канбана (vuedraggable arrays) + API call + rollback.
 */
async function onDrawerStatusChanged(slug: string): Promise<void> {
    if (!selectedDeal.value) return;
    const deal = selectedDeal.value;
    const prev = deal.statusSlug;
    if (prev === slug) return;
    const next = slug as MockDeal['statusSlug'];

    // Optimistic: переносим карточку между колонками.
    const fromCol = dealsByStatus[prev];
    const toCol = dealsByStatus[next];
    if (fromCol && toCol) {
        const idx = fromCol.findIndex((d) => d.id === deal.id);
        if (idx >= 0) fromCol.splice(idx, 1);
        deal.statusSlug = next;
        toCol.unshift(deal);
    } else {
        deal.statusSlug = next;
    }

    if (!auth.user?.tenant_id) return;
    try {
        await dealsApi.transitionDeals({ tenant_id: auth.user.tenant_id, ids: [deal.id], status: next });
        transitionToastColor.value = 'success';
        transitionToastText.value = 'Статус обновлён.';
        transitionToastOpen.value = true;
    } catch {
        // Rollback: вернуть карточку обратно.
        deal.statusSlug = prev;
        if (fromCol && toCol) {
            const idx = toCol.findIndex((d) => d.id === deal.id);
            if (idx >= 0) toCol.splice(idx, 1);
            if (!fromCol.find((d) => d.id === deal.id)) fromCol.push(deal);
        }
        transitionToastColor.value = 'warning';
        transitionToastText.value = 'Не удалось изменить статус — возвращено как было.';
        transitionToastOpen.value = true;
    }
}

async function onColumnChange(targetSlug: MockDeal['statusSlug'], event: DraggableChangeEvent) {
    if (!event.added) {
        // 'removed' и 'moved' — vuedraggable мутирует array; reactive triggers re-render.
        return;
    }

    const dealItem = event.added.element;
    const previousSlug = dealItem.statusSlug;

    // Optimistic: меняем статус в local state сразу (UX отвечает мгновенно).
    dealItem.statusSlug = targetSlug;

    // Без auth — local-only mode (dev/demo без tenant context). API не зовём.
    if (!auth.user?.tenant_id) return;

    try {
        await dealsApi.transitionDeals({
            tenant_id: auth.user.tenant_id,
            ids: [dealItem.id],
            status: targetSlug,
        });
        // success — статус уже применён, тостить не нужно.
    } catch {
        // Revert на исходный статус + переместить карточку обратно в исходную колонку.
        dealItem.statusSlug = previousSlug;
        // Найдём целевую колонку и удалим из неё карточку, потом вернём в исходную.
        const targetCol = dealsByStatus[targetSlug];
        if (targetCol) {
            const idx = targetCol.findIndex((d) => d.id === dealItem.id);
            if (idx >= 0) targetCol.splice(idx, 1);
        }
        const sourceCol = dealsByStatus[previousSlug];
        if (sourceCol && !sourceCol.find((d) => d.id === dealItem.id)) {
            sourceCol.push(dealItem);
        }
        transitionToastColor.value = 'warning';
        transitionToastText.value = `Не удалось переместить сделку #${dealItem.id} — восстановлен исходный статус.`;
        transitionToastOpen.value = true;
    }
}

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

function onOpenDeal(id: number) {
    // Найти deal среди всех reactive-массивов dealsByStatus.
    for (const slug of Object.keys(dealsByStatus)) {
        const found = dealsByStatus[slug].find((d) => d.id === id);
        if (found) {
            selectedDeal.value = found;
            drawerOpen.value = true;
            return;
        }
    }
}

const totalDeals = ref(0);
const fetchError = ref(false);

const newDealOpen = ref(false);

// Sprint 1 C4: revert-on-fail toast при DnD-fail + success-toast смены статуса (UI-аудит).
const transitionToastOpen = ref(false);
const transitionToastText = ref('');
const transitionToastColor = ref<'warning' | 'success'>('warning');

function onDealCreated(deal: MockDeal) {
    if (!dealsByStatus[deal.statusSlug]) dealsByStatus[deal.statusSlug] = [];
    dealsByStatus[deal.statusSlug].unshift(deal);
    totalDeals.value++;
}

async function loadDeals() {
    if (!auth.user?.tenant_id) return;
    fetchError.value = false;
    try {
        const { deals, total } = await dealsApi.listDeals({ tenantId: auth.user.tenant_id, limit: 500 });
        const mapped = deals.map((d) => mapApiDeal(d));
        // Очищаем все колонки и распределяем заново.
        for (const slug of Object.keys(dealsByStatus)) {
            dealsByStatus[slug].splice(0, dealsByStatus[slug].length);
        }
        for (const deal of mapped) {
            if (!dealsByStatus[deal.statusSlug]) dealsByStatus[deal.statusSlug] = [];
            dealsByStatus[deal.statusSlug].push(deal);
        }
        totalDeals.value = total;
    } catch {
        fetchError.value = true; // state остаётся пустым — показываем error-alert
    }
}

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

usePolling(loadDeals);

defineExpose({
    dealsByStatus,
    totalDeals,
    newDealOpen,
    onDealCreated,
    fetchError,
    loadDeals,
    onColumnChange,
    transitionToastOpen,
    transitionToastText,
});
</script>

<template>
    <v-container fluid class="kanban 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
                        ><span class="num">{{ leadStatuses.length }}</span> статусов</span
                    >
                    <span class="sep">·</span>
                    <span
                        ><span class="num">{{ totalDeals }}</span> сделок</span
                    >
                    <span class="sep">·</span>
                    <span class="text-caption text-medium-emphasis"> Перетаскивание между колонками </span>
                </div>
            </div>
            <div class="d-flex ga-2">
                <v-btn variant="outlined" prepend-icon="mdi-refresh" data-testid="reload-btn" @click="loadDeals">
                    Обновить
                </v-btn>
            </div>
        </header>

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

        <div class="kanban-board mt-4" tabindex="0" role="region" aria-label="Канбан-доска воронки продаж">
            <KanbanColumn
                v-for="status in leadStatuses"
                :key="status.slug"
                :status="status"
                :deals="dealsByStatus[status.slug] || []"
                @update:deals="(list) => (dealsByStatus[status.slug] = list)"
                @change="(e) => onColumnChange(status.slug, e)"
                @open-deal="onOpenDeal"
            />
        </div>

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

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

        <v-snackbar
            v-model="transitionToastOpen"
            :timeout="4000"
            :color="transitionToastColor"
            location="bottom right"
            data-testid="kanban-transition-toast"
        >
            {{ transitionToastText }}
        </v-snackbar>
    </v-container>
</template>

<style scoped>
.kanban {
    max-width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
}

.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;
}

.kanban-board {
    display: flex;
    gap: 12px;
    overflow-x: auto;
    overflow-y: hidden;
    flex: 1 1 auto;
    min-height: 600px;
    padding-bottom: 12px;
}

.kanban-board::-webkit-scrollbar {
    height: 8px;
}
.kanban-board::-webkit-scrollbar-track {
    background: transparent;
}
.kanban-board::-webkit-scrollbar-thumb {
    background: #d9d5cd;
    border-radius: 4px;
}
.kanban-board::-webkit-scrollbar-thumb:hover {
    background: #92907b;
}
</style>
