import React, { useState, useMemo, useEffect } from 'react';
import * as XLSX from 'xlsx';
import { X } from 'lucide-react';
import defaultWorkbookUrl from '../data (3).xlsx?url';
import { cn } from './lib/utils';
import { AppState, ExcelRow, resolveColumnIndices, COLUMNS } from './types';
import { ColumnsProvider } from './contexts/ColumnsContext';
import { FilterProvider } from './contexts/FilterContext';
import { Sidebar } from './components/Sidebar';
import { TopBar } from './components/TopBar';
import { ProductDescriptions } from './components/ProductDescriptions';
import { MatrixView } from './components/MatrixView';
import { EditPanel } from './components/EditPanel';
import { getAllSyncedRows, saveRowToSupabase, resetAllPendingRows, saveHistoryEntry, deleteHistoryEntry, getHistoryDataForMerge } from './lib/supabase';
import { getStoredSession, signOut, type AuthSession } from './lib/auth';
import { LoginPage } from './components/LoginPage';
import { DimensionsView } from './components/DimensionsView';
import { PricingView } from './components/PricingView';
import { ArticleDetails } from './components/ArticleDetails';
import { HistoryView } from './components/HistoryView';
import { UndoToast } from './components/UndoToast';
import { PendingValidationView } from './components/PendingValidationView';
import { MissingDataView } from './components/MissingDataView';
import { CosmeticItemsView } from './components/CosmeticItemsView';
import { ControlDashboardView } from './components/ControlDashboardView';
import { UserManagementView } from './components/UserManagementView';
import { downloadBusinessCentralItemsExcel, previewBusinessCentralSync, applyBusinessCentralSync, isPreviewTokenMismatchError } from './services/businessCentral';
import { ControlTabId, type DashboardDrilldownRequest } from './lib/controlDashboard';

const FORCED_ZERO_STOCK_SKUS = new Set([
  '11631VC', '1237VC', '1238VC', '1652VC', '1653VC', '1684VC', '1688VC', '1717VC',
  '1718VC', '180VC', '1832VC', '2025VC', '2027VC', '2180VC', '2181VC', '2210VC',
  '2260VC', '2355VC', '2356VC', '2360VC', '2400VC', '2401VC', '2402VC', '2405VC',
  '2406VC', '2414VC', '2415VC', '2425VC', '2447VC', '2450VC', '2510VC', '2517VC',
  '2522VC', '2611VC', '2648VC', '2660VC', '2665VC', '2857VC', '2858VC', '2990VC',
  '3011VC', '3012VC', '3013VC', '3015VC', '3018VC', '3019VC', '3020VC', '3021VC',
  '3027VC', '3030VC', '3035VC', '3038VC', '3041VC', '3043VC', '3044VC', '3045VC',
  '3046VC', '3048VC', '3052VC', '3053VC', '3054VC', '3055VC', '3062VC', '3063VC',
  '3064VC', '3069VC', '3070VC', '3071VC', '3072VC', '3074VC', '3089VC', '3116VC',
  '3117VC', '3118VC', '3119VC', '3120VC', '3121VC', '3123VC', '3125VC', '3126VC',
  '3127VC', '3130VC', '3133VC', '3134VC', '3135VC', '3200VC', '3201VC', '3230VC',
  '3311VC', '3314VC', '3315VC', '3354VC', '3372VC', '3380VC', '3391VC', '3392VC',
  '3423VC', '3432VC', '3437VC', '3484VC', '3487VC', '3555VC', '3557VC', '3558VC',
  '3559VC', '3712VC', '3824VC', '4107VC', '4108VC', '4110VC', '4115VC', '4125VC',
  '4130VC', '4134VC', '4140VC', '4155VC', '4158VC', '4165VC', '4231VC', '4232VC',
  '4255VC', '4259VC', '4261VC', '4263VC', '4264VC', '4265VC', '4285VC', '4288VC',
  '4330VC', '4361VC', '4442VC', '4445VC', '4462VC', '4528VC', '4531VC', '4534VC',
  '4558VC', '4657VC', '4721VC', '4722VC', '4727VC', '4735VC', '4737VC', '4745VC',
  '4752VC', '4758VC', '4765VC', '4785VC', '4790VC', '4801VC', '4815VC', '4874VC',
  '4882VC', '5032VC', '5113VC', '5382VC', '5383VC', '5440VC', '5442VC', '5456VC',
  '5540VC', '5571VC', '5572VC', '5574VC', '5575VC', '5588VC', '5592VC', '5685VC',
  '5712VC', '5721VC', '5807VC', '5823VC', '5825VC', '5835VC', '5852VC', '5871VC',
  '5872VC', '5873VC', '5874VC', '5882VC', '5883VC', '5885VC', '5987VC', '5991VC',
  '5995VC', '6001VC', '6007VC', '6040VC', '6151VC', '6252VC', '6255VC', '6270VC',
  '6471VC', '5658VCI', '2915244CLM', '30512912CLM', '2861262CLM', '41683MDRG', '41891MDRG'
]);

const BC_SYNC_QUEUE_STORAGE_KEY = 'craze_bc_sync_queue';
const LEGACY_CPNP_INDEX = 77;

type BcSyncStatus = 'queued' | 'previewed' | 'preview_only' | 'syncing' | 'synced' | 'failed';

interface BcSyncQueueEntry {
  rowIndex: number;
  originalData: ExcelRow;
  newData: ExcelRow;
  articleName: string;
  selected: boolean;
  status: BcSyncStatus;
  previewToken?: string;
  error?: string;
  warning?: string;
  source: 'save_all' | 'cosmetic_items' | 'manual';
}

type BcSyncQueue = Record<string, BcSyncQueueEntry>;

function readStoredBcSyncQueue(): BcSyncQueue {
  try {
    const raw = localStorage.getItem(BC_SYNC_QUEUE_STORAGE_KEY);
    if (!raw) return {};
    const parsed = JSON.parse(raw) as unknown;
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {};
    return parsed as BcSyncQueue;
  } catch {
    return {};
  }
}

async function loadWorkbookFromUrl(url: string) {
  const response = await fetch(url, { cache: 'no-store' });
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  const arrayBuffer = await response.arrayBuffer();
  if (arrayBuffer.byteLength < 100) {
    throw new Error('File too small');
  }

  const wb = XLSX.read(arrayBuffer, { type: 'array' });
  const wsname = wb.SheetNames[0];
  const ws = wb.Sheets[wsname];
  const allData = XLSX.utils.sheet_to_json<any[]>(ws, { header: 1 });
  const rows = allData.slice(1);
  const headers = allData.slice(0, 1)[0] || [];

  return {
    headers,
    rows,
    fileMeta: { rev: url, size: arrayBuffer.byteLength },
  };
}

export default function App() {
  const [session, setSession] = useState<AuthSession | null>(() => getStoredSession());

  const [appState, setAppState] = useState<AppState>({
    headers: [],
    data: [],
    fileName: '',
    fileDate: null,
    hasUnsavedChanges: false,
    asinColumnIndex: null
  });
  const [activeModule, setActiveModule] = useState<ControlTabId>('control_dashboard');
  const [undoHistory, setUndoHistory] = useState<{ data: ExcelRow[], message: string }[]>([]);
  const [editingRowIndex, setEditingRowIndex] = useState<number | null>(null);
  const [isLoadingDefault, setIsLoadingDefault] = useState(true);
  const [defaultLoadError, setDefaultLoadError] = useState<string | null>(null);
  const [rowStatuses, setRowStatuses] = useState<Record<string, string>>({});
  const [pendingRows, setPendingRows] = useState<Record<string, { rowIndex: number; originalData: ExcelRow; newData: ExcelRow; articleName: string }>>({});
  const [bcSyncQueue, setBcSyncQueue] = useState<BcSyncQueue>(() => readStoredBcSyncQueue());
  const [isSavingAll, setIsSavingAll] = useState(false);
  const [isSyncingBC, setIsSyncingBC] = useState(false);
  const [isDownloadingBCExcel, setIsDownloadingBCExcel] = useState(false);
  const [refreshTrigger, setRefreshTrigger] = useState(0);
  const [dashboardDrilldown, setDashboardDrilldown] = useState<DashboardDrilldownRequest | null>(null);

  useEffect(() => {
    console.log('[App] session changed:', session ? 'logged in' : 'logged out');
  }, [session]);

  // Background user validation status check
  useEffect(() => {
    if (!session) return;

    let isMounted = true;

    const checkUserStatus = async () => {
      try {
        const res = await fetch('/api/users-admin', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${session.access_token}`
          },
          body: JSON.stringify({ action: 'check-status' })
        });

        if (!isMounted) return;

        if (!res.ok) {
          console.warn('[App] Validation check failed, logging out.');
          handleSignOut();
          return;
        }

        const data = await res.json();
        if (!data.validated) {
          alert('Tu usuario ya no está autorizado o está pendiente de validación.');
          handleSignOut();
        }
      } catch (err) {
        console.error('[App] Background validation check error:', err);
      }
    };

    // Check immediately on mount/session change
    checkUserStatus();

    // Check periodically every 5 minutes
    const interval = setInterval(checkUserStatus, 5 * 60 * 1000);

    return () => {
      isMounted = false;
      clearInterval(interval);
    };
  }, [session]);

  useEffect(() => {
    try {
      localStorage.setItem(BC_SYNC_QUEUE_STORAGE_KEY, JSON.stringify(bcSyncQueue));
    } catch {
      // Ignore storage quota or serialization errors.
    }
  }, [bcSyncQueue]);

  const handleSignOut = () => {
    signOut();
    setSession(null);
  };

  // Sync session state when localStorage is updated (e.g. by token refresh)
  useEffect(() => {
    const handleStorage = (e: StorageEvent) => {
      if (e.key === 'craze_auth_session') {
        setSession(getStoredSession());
      }
    };
    window.addEventListener('storage', handleStorage);
    return () => window.removeEventListener('storage', handleStorage);
  }, []);

  useEffect(() => {
    const loadDefaultData = async () => {
      setIsLoadingDefault(true);
      setDefaultLoadError(null);

      try {
        const isDev = import.meta.env.DEV;
        
        let rows: any[][];
        let allData: any[][];
        let fileMeta = { rev: '', size: 0 };
        
        if (isDev) {
          const cacheBuster = `t_${Date.now()}`;
          const fileUrl = `/dropbox-file/scl/fi/usa8me7ywgylrij2bt6hj/Data-Matrix.xlsx?rlkey=tsec8csrhye54u1fdvk15ped1&dl=1&${cacheBuster}=${Date.now()}`;
          console.log('Fetching Data-Matrix.xlsx from Dropbox (hard refresh)...');
          try {
            const response = await fetch(fileUrl, { cache: 'no-store' });
            if (!response.ok) throw new Error(`HTTP ${response.status}`);

            const contentType = response.headers.get('content-type');
            if (contentType && contentType.includes('text/html')) {
              throw new Error('Dropbox returned an HTML page instead of the Excel file.');
            }

            const arrayBuffer = await response.arrayBuffer();
            if (arrayBuffer.byteLength < 100) throw new Error('File too small');

            const wb = XLSX.read(arrayBuffer, { type: 'array' });
            const wsname = wb.SheetNames[0];
            const ws = wb.Sheets[wsname];
            allData = XLSX.utils.sheet_to_json<any[]>(ws, { header: 1 });
            rows = allData.slice(1);
            fileMeta = { rev: 'dev', size: arrayBuffer.byteLength };
          } catch (devErr) {
            console.warn('Dropbox dev load failed, using bundled workbook fallback:', devErr);
            const fallback = await loadWorkbookFromUrl(defaultWorkbookUrl);
            allData = [fallback.headers, ...fallback.rows];
            rows = fallback.rows;
            fileMeta = fallback.fileMeta;
          }
        } else {
          console.log('Fetching file info from Dropbox...');
          const infoRes = await fetch('/api/dropbox-proxy?info=1');
          if (infoRes.ok) {
            fileMeta = await infoRes.json();
            console.log('File meta:', fileMeta);
          }
          
          console.log('Fetching Data-Matrix.xlsx from proxy (hard refresh)...');
          try {
            const response = await fetch('/api/dropbox-proxy', { cache: 'no-store' });
            if (!response.ok) throw new Error(`HTTP ${response.status}`);

            const contentType = response.headers.get('content-type');
            if (contentType && contentType.includes('text/html')) {
              throw new Error('Dropbox returned an HTML page instead of the Excel file.');
            }

            const arrayBuffer = await response.arrayBuffer();
            if (arrayBuffer.byteLength < 100) throw new Error('File too small');

            const wb = XLSX.read(arrayBuffer, { type: 'array' });
            const wsname = wb.SheetNames[0];
            const ws = wb.Sheets[wsname];
            allData = XLSX.utils.sheet_to_json<any[]>(ws, { header: 1 });
            rows = allData.slice(1);
          } catch (prodErr) {
            console.warn('Dropbox prod load failed, using bundled workbook fallback:', prodErr);
            const fallback = await loadWorkbookFromUrl(defaultWorkbookUrl);
            allData = [fallback.headers, ...fallback.rows];
            rows = fallback.rows;
            fileMeta = fallback.fileMeta;
          }
        }

        if (rows.length > 0) {
          const headers = allData.slice(0, 1)[0];
          
          console.log('Syncing Excel data to Supabase...');
          const syncRes = await fetch('/api/dropbox-sync', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ rows, headers, fileMeta })
          });
          
          if (syncRes.ok) {
            const syncResult = await syncRes.json();
            console.log('Supabase sync result:', syncResult);
          } else {
            const errorText = await syncRes.text();
            console.warn('Supabase sync failed:', syncRes.status, errorText);
          }
          
          console.log('Fetching synced data from Supabase...');
          const syncedData = await getAllSyncedRows();
          console.log('Fetching history data from Supabase for merge...');
          const historyData = await getHistoryDataForMerge();
          console.log('Supabase synced rows:', Object.keys(syncedData).length, '| History rows:', Object.keys(historyData).length);
          
          // Extend headers with virtual columns if the Excel is shorter than the saved data.
          // COLUMNS hardcoded indices (PRODUCT_TYPE=103, ITEM_TO_LOGISTIC=104, etc.) are used
          // by older saved rows — ensure headers covers them so the merge and render work.
          const extendedHeaders = [...headers];
          const VIRTUAL_COLS: Record<number, string> = {
            [COLUMNS.VERIFIED_DIMS]: 'Verified Dims',
            [COLUMNS.VALIDATED_CHECK]: 'Validated',
            [COLUMNS.VALIDATED_NOTE]: 'Note',
            [COLUMNS.PRODUCT_TYPE]: 'TYPE',
            [COLUMNS.ITEM_TO_LOGISTIC]: 'Item to Logistic',
            [COLUMNS.ANNA_CHECK]: 'Anna Check',
            [COLUMNS.ANNA_NOTE]: 'Anna Note',
            [COLUMNS.CPNP_NO]: 'CPNP No.',
          };
          Object.entries(VIRTUAL_COLS).forEach(([idxStr, name]) => {
            const idx = Number(idxStr);
            if (extendedHeaders[idx] === undefined) {
              extendedHeaders[idx] = name;
            }
          });
          // Fill any sparse holes with '' to avoid undefined.includes() errors
          for (let i = 0; i < extendedHeaders.length; i++) {
            if (extendedHeaders[i] === undefined) extendedHeaders[i] = '';
          }

          const resolvedCols = resolveColumnIndices(extendedHeaders);
          const articleNoIdx = resolvedCols.ARTICLE_NO;
          const processedRows = rows.map(row => {
            const articleNo = String(row[articleNoIdx]);
            const synced = syncedData[articleNo];
            const hist = historyData[articleNo];
            
            // Start with fresh Dropbox values (prices from Excel)
            let finalRow = [...row];
            
            // Pad row to match extendedHeaders length so internal cols (index >= 100) are readable
            while (finalRow.length < extendedHeaders.length) {
              finalRow.push(null);
            }
            
            const syncedHasInternalCols = Boolean(
              synced?.data && (
                synced.data.length > 100 ||
                Object.values(COLUMNS).some(idx =>
                  idx >= 100 &&
                  synced.data?.[idx] !== undefined &&
                  synced.data?.[idx] !== null &&
                  synced.data?.[idx] !== ''
                )
              )
            );
            const sourceForInternal = syncedHasInternalCols ? synced!.data : (hist || synced?.data);

            if (sourceForInternal) {
              Object.values(COLUMNS).forEach(idx => {
                if (idx < 100) return;

                const historyValue = hist?.[idx];
                const syncedValue = synced?.data?.[idx];
                const value =
                  historyValue !== undefined && historyValue !== null && historyValue !== ''
                    ? historyValue
                    : syncedValue !== undefined && syncedValue !== null && syncedValue !== ''
                      ? syncedValue
                      : undefined;

                if (value !== undefined) {
                  finalRow[idx] = value;
                }
              });

              // Older cosmetic edits stored CPNP in index 77 before the dedicated
              // internal CPNP column was stabilized at index 107.
              const legacyCpnp =
                hist?.[COLUMNS.CPNP_NO] ??
                synced?.data?.[COLUMNS.CPNP_NO] ??
                hist?.[LEGACY_CPNP_INDEX] ??
                synced?.data?.[LEGACY_CPNP_INDEX];
              if (legacyCpnp !== undefined && legacyCpnp !== null && String(legacyCpnp).trim() !== '') {
                finalRow[COLUMNS.CPNP_NO] = legacyCpnp;
              }

              // Update row status in UI if it's not the default 'excel'
              if (synced?.status && synced.status !== 'excel') {
                setRowStatuses(prev => ({ ...prev, [articleNo]: synced.status! }));
              }
            }
            
            // Force stock 0 for specific SKUs (User request)
            if (FORCED_ZERO_STOCK_SKUS.has(articleNo)) {
              finalRow[COLUMNS.ITEM_AVAILABLE] = 0;
            }

            return finalRow.map((val: any, idx: number) => {
              if (val === undefined || val === null || val === '') return val;
              const header = (extendedHeaders[idx] || '').toLowerCase();
               
               if ((header.includes('id') || header.includes('no') || header.includes('code') ||
                 header.includes('art.') || header.includes('barcode') || header.includes('article')) &&
                 !(header.includes('nw') || header.includes('gw') || header.includes('weight') || header.includes('kg'))) {
                 return val;
               }

               // Handle date columns - Excel serial dates are numbers >= 25569 (Jan 1, 1970)
               if (header.includes('date') || header.includes('launch') || header.includes('ready')) {
                 if (typeof val === 'number' && val >= 25569 && val <= 60000) {
                   const excelEpoch = new Date(1899, 11, 30);
                   const date = new Date(excelEpoch.getTime() + val * 86400000);
                   return date.toISOString().split('T')[0]; // Returns YYYY-MM-DD
                 }
                 return val;
               }

               const formatKeywords = ['price', 'eur', 'cost', 'msrp', 'net', 'gross', 'netto', 'brutto', 'pp', 'pph', 'uvp', 'vpe', 'stk', 'nw', 'gw', 'weight', 'kg'];
               const shouldFormat = formatKeywords.some(kw => header.includes(kw));
               
               if (typeof val === 'number') {
                 return Number(val.toFixed(2));
               }
               
               if (typeof val === 'string') {
                 const normalized = val.trim().replace(',', '.');
                const num = parseFloat(normalized);
                if (!isNaN(num) && (shouldFormat || val.includes('.') || val.includes(','))) {
                  return num.toFixed(2);
                }
              }
              return val;
            });
          });
          
          const asinIdx = (extendedHeaders as string[]).findIndex((h: string) =>
            String(h).toLowerCase().trim() === 'asin'
          );

          setAppState({
            headers: extendedHeaders,
            data: processedRows,
            fileName: 'Data-Matrix.xlsx (Cloud Sync)',
            fileDate: new Date(),
            hasUnsavedChanges: false,
            asinColumnIndex: asinIdx !== -1 ? asinIdx : null
          });
          setActiveModule('control_dashboard');
        }
      } catch (err) {
        console.error('Failed to load from Dropbox:', err);
        setDefaultLoadError(err instanceof Error ? err.message : 'Connection failed');
      } finally {
        setIsLoadingDefault(false);
      }
    };

    if (session) loadDefaultData();
  }, [session, refreshTrigger]);

  const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    if (appState.hasUnsavedChanges) {
      if (!window.confirm('You have unsaved changes. Are you sure you want to load a new file and discard them?')) {
        e.target.value = '';
        return;
      }
    }

    const reader = new FileReader();
    reader.onload = (evt) => {
      const bstr = evt.target?.result;
      const wb = XLSX.read(bstr, { type: 'binary' });
      const wsname = wb.SheetNames[0];
      const ws = wb.Sheets[wsname];
      const data = XLSX.utils.sheet_to_json<any[]>(ws, { header: 1 });

      if (data.length > 0) {
        const headers = data[0];
        const rawRows = data.slice(1);

        const resolvedCols = resolveColumnIndices(headers);
        const processedRows = rawRows.map(row => {
          const finalRow = row.map((val, idx) => {
            if (val === undefined || val === null || val === '') return val;
            const header = (headers[idx] || '').toLowerCase();

            if (header.includes('id') || header.includes('no') || header.includes('code') ||
              header.includes('art.') || header.includes('barcode') || header.includes('article')) {
              // But allow if it's a weight/measure column (e.g. Article NW (kg))
              if (!(header.includes('nw') || header.includes('gw') || header.includes('weight') || header.includes('kg'))) {
                return val;
              }
            }

            // Handle date columns - Excel serial dates are numbers >= 25569 (Jan 1, 1970)
            if (header.includes('date') || header.includes('launch') || header.includes('ready')) {
              if (typeof val === 'number' && val >= 25569 && val <= 60000) {
                const excelEpoch = new Date(1899, 11, 30);
                const date = new Date(excelEpoch.getTime() + val * 86400000);
                return date.toISOString().split('T')[0]; // Returns YYYY-MM-DD
              }
              return val;
            }

            const formatKeywords = ['price', 'eur', 'cost', 'msrp', 'net', 'gross', 'netto', 'brutto', 'pp', 'pph', 'uvp', 'vpe', 'stk', 'nw', 'gw', 'weight', 'kg'];
            const shouldFormat = formatKeywords.some(kw => header.includes(kw));

            if (typeof val === 'number') {
              return Number(val.toFixed(2));
            }

            if (typeof val === 'string') {
              const normalized = val.trim().replace(',', '.');
              const num = parseFloat(normalized);
              if (!isNaN(num) && (shouldFormat || val.includes('.') || val.includes(','))) {
                return num.toFixed(2);
              }
            }
            return val;
          });

          // Force stock 0 for specific SKUs (User request)
          const articleNo = String(finalRow[resolvedCols.ARTICLE_NO] || '');
          if (FORCED_ZERO_STOCK_SKUS.has(articleNo)) {
            finalRow[resolvedCols.ITEM_AVAILABLE] = 0;
          }
          return finalRow;
        });

        setAppState({
          headers: headers,
          data: processedRows,
          fileName: file.name,
          fileDate: new Date(),
          hasUnsavedChanges: false,
          asinColumnIndex: null
        });
        setActiveModule('control_dashboard');
      }
    };
    reader.readAsBinaryString(file);
  };

  const handleExport = () => {
    if (appState.data.length === 0) return;

    const wsData = [
      appState.headers, 
      ...appState.data.map(row => {
        const copy = [...row];
        delete copy[COLUMNS.VERIFIED_DIMS];
        return copy.slice(0, appState.headers.length);
      })
    ];
    const ws = XLSX.utils.aoa_to_sheet(wsData);
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, 'Products');

    const dateStr = new Date().toISOString().split('T')[0];
    XLSX.writeFile(wb, `CRAZE_Products_Updated_${dateStr}.xlsx`);

    // 3. Post-export: Reset pending statuses in Supabase (pending → edited to preserve on reload)
    console.log('Resetting pending statuses in Supabase...');
    resetAllPendingRows().then(success => {
      if (success) {
        console.log('Successfully reset all pending statuses');
        // Only clear 'pending' statuses locally; keep 'edited'/'saved' so they remain visible
        setRowStatuses((prev: Record<string, string>) => {
          const next: Record<string, string> = {};
          for (const [id, status] of Object.entries(prev)) {
            if (status !== 'pending') next[id] = status as string;
          }
          return next;
        });
      }
    });

    setAppState(prev => ({ ...prev, hasUnsavedChanges: false }));
  };

  const handleSaveRow = (rowIndex: number, updatedRow: ExcelRow) => {
    const articleNo = String(updatedRow[COLUMNS.ARTICLE_NO]);
    const currentPending = pendingRows[articleNo];
    const originalData = currentPending?.originalData ?? appState.data[rowIndex];

    const isRowDifferent = (rowA: any[], rowB: any[]) => {
      if (!rowA || !rowB) return rowA !== rowB;
      const length = Math.max(rowA.length, rowB.length);
      for (let i = 0; i < length; i++) {
        const a = rowA[i];
        const b = rowB[i];
        if (a === b) continue;
        const normA = (a === null || a === undefined || a === '' || a === false) ? null : a;
        const normB = (b === null || b === undefined || b === '' || b === false) ? null : b;
        if (normA === normB) continue;
        if (String(a) === String(b)) continue;
        return true;
      }
      return false;
    };

    const isActuallyModified = isRowDifferent(updatedRow, originalData);

    if (isActuallyModified) {
      setAppState(prev => {
        const newData = [...prev.data];
        newData[rowIndex] = updatedRow;
        return { ...prev, data: newData, hasUnsavedChanges: true };
      });
      setPendingRows(prev => ({
        ...prev,
        [articleNo]: {
          rowIndex,
          originalData,
          newData: updatedRow,
          articleName: String(updatedRow[COLUMNS.ARTICLE_NAME] || articleNo),
        }
      }));
      setRowStatuses(prev => ({ ...prev, [articleNo]: 'pending' }));
    } else {
      // Changed back to original state - remove from pending
      setPendingRows(prev => {
        const n = { ...prev };
        delete n[articleNo];
        return n;
      });
      setRowStatuses(prev => {
        const n = { ...prev };
        delete n[articleNo];
        return n;
      });
      setAppState(prev => {
        const newData = [...prev.data];
        newData[rowIndex] = updatedRow;
        
        // Use a more reliable way to check if there are still other pending rows
        const otherPendingCount = Object.keys(pendingRows).filter(k => k !== articleNo).length;
        const stillHasChanges = otherPendingCount > 0;
        
        return { ...prev, data: newData, hasUnsavedChanges: stillHasChanges };
      });
    }
    setEditingRowIndex(null);
  };

  const handleRevertRow = (articleNo: string) => {
    const pending = pendingRows[articleNo];
    if (!pending) return;
    setAppState(prev => {
      const newData = [...prev.data];
      newData[pending.rowIndex] = pending.originalData;
      const stillPending = Object.keys(pendingRows).length > 0;
      return { ...prev, data: newData, hasUnsavedChanges: stillPending };
    });
    setPendingRows(prev => { const n = { ...prev }; delete n[articleNo]; return n; });
    setRowStatuses(prev => { const n = { ...prev }; delete n[articleNo]; return n; });
    setBcSyncQueue(prev => {
      if (!prev[articleNo]) return prev;
      const n = { ...prev };
      delete n[articleNo];
      return n;
    });
  };

  const handleSaveAll = async () => {
    console.log('[handleSaveAll] Starting save, pendingRows:', pendingRows);
    const entries = Object.entries(pendingRows) as [string, { rowIndex: number; originalData: ExcelRow; newData: ExcelRow; articleName: string }][];
    if (entries.length === 0) {
      console.log('[handleSaveAll] No entries to save, returning');
      return;
    }
    
    setIsSavingAll(true);
    let failedArticles: string[] = [];
    let sessionIssue = false;
    
    try {
      await Promise.all(entries.map(async ([articleNo, { rowIndex, newData, originalData, articleName }]) => {
        console.log('[handleSaveAll] Saving article:', articleNo);
        const result = await saveRowToSupabase(articleNo, newData);
        console.log('[handleSaveAll] Save result for', articleNo, ':', result);

        if (result.success) {
          const histRes = await saveHistoryEntry(articleNo, articleName, originalData, newData, session?.user?.email || 'unknown');

          if (!histRes.success) {
            console.warn(`[handleSaveAll] History save failed for ${articleNo}:`, histRes.error);
          }

          setBcSyncQueue(prev => ({
            ...prev,
            [articleNo]: {
              rowIndex,
              originalData,
              newData,
              articleName,
              selected: true,
              status: 'queued',
              source: 'save_all',
            },
          }));

          setRowStatuses(prev => ({ ...prev, [articleNo]: 'bc_pending' }));
          setPendingRows(prev => {
            const n = { ...prev };
            delete n[articleNo];
            return n;
          });
        } else {
          setRowStatuses(prev => ({ ...prev, [articleNo]: 'error' }));
          failedArticles.push(`${articleNo} [${result.error || 'Unknown error'}]`);
          if (result.error?.includes('401') || result.error?.includes('expired') || result.error?.includes('Session expired')) {
            sessionIssue = true;
          }
        }
      }));

      console.log('[handleSaveAll] Finished loop. Failed:', failedArticles.length);
      
      if (sessionIssue) {
        alert("Tu sesión ha caducado definitivamente. Por favor, cierra sesión e inicia sesión de nuevo para continuar. No refresques la página para no perder tus cambios pendientes en pantalla.");
      } else if (failedArticles.length > 0) {
        alert(`Failed to save items:\n\n${failedArticles.join('\n')}\n\nPlease try again.`);
      } else {
        setAppState(prev => ({ ...prev, hasUnsavedChanges: false }));
      }
    } catch (err: any) {
      console.error('[handleSaveAll] Critical error:', err);
      alert(`A critical error occurred while saving: ${err.message || err}\nCheck console for details.`);
    } finally {
      setIsSavingAll(false);
      console.log('[handleSaveAll] isSavingAll set to false');
    }
  };

  const handleQueueBcSync = (articleNo: string, rowIndex: number, originalData: ExcelRow, newData: ExcelRow, articleName: string) => {
    setBcSyncQueue(prev => ({
      ...prev,
      [articleNo]: {
        rowIndex,
        originalData,
        newData,
        articleName,
        selected: true,
        status: 'queued',
        source: 'cosmetic_items',
      },
    }));
    setRowStatuses(prev => ({ ...prev, [articleNo]: 'bc_pending' }));
  };

  const handleToggleBcQueueSelection = (articleNo: string) => {
    setBcSyncQueue(prev => {
      const item = prev[articleNo];
      if (!item) return prev;
      return {
        ...prev,
        [articleNo]: {
          ...item,
          selected: !item.selected,
        },
      };
    });
  };

  const handleDiscardSelectedBcQueue = () => {
    const selectedCount = (Object.values(bcSyncQueue) as BcSyncQueueEntry[]).filter(e => e.selected).length;
    if (selectedCount === 0) return;
    if (!window.confirm(`Discard ${selectedCount} pending BC sync(s)? They will be removed from the queue.`)) return;
    setBcSyncQueue(prev => {
      const next: BcSyncQueue = { ...prev };
      (Object.keys(next) as string[]).forEach(articleNo => {
        if (next[articleNo].selected) delete next[articleNo];
      });
      return next;
    });
  };

  const handleSelectAllBcQueue = (selected: boolean) => {
    setBcSyncQueue(prev => {
      const next: BcSyncQueue = { ...prev };
      (Object.keys(next) as string[]).forEach(articleNo => {
        const item = next[articleNo];
        next[articleNo] = { ...item, selected };
      });
      return next;
    });
  };

  const bcQueueEntries = useMemo<Array<[string, BcSyncQueueEntry]>>(
    () => Object.entries(bcSyncQueue) as Array<[string, BcSyncQueueEntry]>,
    [bcSyncQueue]
  );

  const selectedBcQueueEntries = useMemo<Array<[string, BcSyncQueueEntry]>>(
    () => bcQueueEntries.filter(([, item]) => item.selected),
    [bcQueueEntries]
  );

  useEffect(() => {
    if (appState.data.length === 0) return;
    const validArticles = new Set(appState.data.map(row => String(row[COLUMNS.ARTICLE_NO] ?? '').trim()).filter(Boolean));
    setBcSyncQueue(prev => {
      let changed = false;
      const next: BcSyncQueue = {};
      for (const [articleNo, item] of Object.entries(prev) as Array<[string, BcSyncQueueEntry]>) {
        if (!validArticles.has(articleNo)) {
          changed = true;
          continue;
        }
        next[articleNo] = item;
      }
      return changed ? next : prev;
    });
  }, [appState.data]);

  const handlePreviewSelectedBcSync = async () => {
    if (selectedBcQueueEntries.length === 0) return;
    setIsSyncingBC(true);
    try {
      const results = await Promise.all(selectedBcQueueEntries.map(async ([articleNo, item]) => {
        const preview = await previewBusinessCentralSync(appState.headers, item.newData);
        return [articleNo, preview] as const;
      }));

      setBcSyncQueue(prev => {
        const next: BcSyncQueue = { ...prev };
        results.forEach(([articleNo, preview]) => {
          const item = next[articleNo];
          if (!item || !preview.success) {
            if (item) {
              next[articleNo] = {
                ...item,
                status: 'failed',
                error: preview.error || 'Preview failed',
                warning: undefined,
              };
            }
            return;
          }

          next[articleNo] = {
            ...item,
            status: 'previewed',
            previewToken: preview.previewToken,
            error: undefined,
            selected: item.selected,
            warning: undefined,
          };
        });
        return next;
      });
    } finally {
      setIsSyncingBC(false);
    }
  };

  const handleSyncSelectedBcSync = async () => {
    if (selectedBcQueueEntries.length === 0) return;
    setIsSyncingBC(true);
    try {
      const selectedArticles = new Set(selectedBcQueueEntries.map(([articleNo]) => articleNo));

      setBcSyncQueue(prev => {
        const next: BcSyncQueue = { ...prev };
        (Object.entries(next) as Array<[string, BcSyncQueueEntry]>).forEach(([articleNo, item]) => {
          if (selectedArticles.has(articleNo)) {
            next[articleNo] = { ...item, status: 'syncing', error: undefined };
          }
        });
        return next;
      });

      for (const [articleNo, item] of selectedBcQueueEntries) {
        const preview = await previewBusinessCentralSync(appState.headers, item.newData);
        if (!preview.success) {
          setBcSyncQueue(prev => ({
            ...prev,
            [articleNo]: {
              ...prev[articleNo],
              status: 'failed',
              error: preview.error || 'Preview failed',
              warning: undefined,
            },
          }));
          continue;
        }

        const result = await applyBusinessCentralSync(appState.headers, item.newData, preview.previewToken);
        if (!result.success) {
          if (isPreviewTokenMismatchError(result.error)) {
            const refreshedPreview = await previewBusinessCentralSync(appState.headers, item.newData);
            if (refreshedPreview.success) {
              setBcSyncQueue(prev => ({
                ...prev,
                [articleNo]: {
                  ...prev[articleNo],
                  status: 'previewed',
                  previewToken: refreshedPreview.previewToken,
                  error: undefined,
                  warning: undefined,
                },
              }));

              const retryResult = await applyBusinessCentralSync(appState.headers, item.newData, refreshedPreview.previewToken);
              if (retryResult.success) {
                setBcSyncQueue(prev => {
                  const next = { ...prev };
                  delete next[articleNo];
                  return next;
                });
                setRowStatuses(prev => ({ ...prev, [articleNo]: 'synced' }));
                continue;
              }

              setBcSyncQueue(prev => ({
                ...prev,
                [articleNo]: {
                  ...prev[articleNo],
                  status: 'failed',
                  error: retryResult.error || 'BC sync failed',
                  warning: retryResult.warning,
                },
              }));
              continue;
            }
          }
          setBcSyncQueue(prev => ({
            ...prev,
            [articleNo]: {
              ...prev[articleNo],
              status: 'failed',
              error: result.error || 'BC sync failed',
              warning: result.warning,
            },
          }));
          continue;
        }

        setBcSyncQueue(prev => {
          const next = { ...prev };
          delete next[articleNo];
          return next;
        });
        setRowStatuses(prev => ({ ...prev, [articleNo]: 'synced' }));
      }
    } finally {
      setIsSyncingBC(false);
    }
  };

  const handleDownloadBCExcel = async () => {
    setIsDownloadingBCExcel(true);
    try {
      const result = await downloadBusinessCentralItemsExcel();
      if (!result.success) {
        alert(`Failed to download Business Central Excel:\n\n${result.error || 'Unknown error'}`);
      }
    } finally {
      setIsDownloadingBCExcel(false);
    }
  };

  const captureState = (message: string) => {
    setUndoHistory(prev => {
      const newState = {
        data: JSON.parse(JSON.stringify(appState.data)), // Deep copy
        rowStatuses: { ...rowStatuses },
        pendingRows: { ...pendingRows },
        bcSyncQueue: JSON.parse(JSON.stringify(bcSyncQueue)) as BcSyncQueue,
        message
      };
      // Keep last 50 steps
      const newHistory = [newState, ...prev].slice(0, 50);
      return newHistory;
    });
  };

  const handleUndo = () => {
    if (undoHistory.length === 0) return;

    const [lastAction, ...remainingHistory] = undoHistory;
    
    // Restore data
    setAppState(prev => ({
      ...prev,
      data: lastAction.data,
      hasUnsavedChanges: Object.keys(lastAction.pendingRows || {}).length > 0
    }));

    // Restore statuses and pending state
    if (lastAction.rowStatuses) setRowStatuses(lastAction.rowStatuses);
    if (lastAction.pendingRows) setPendingRows(lastAction.pendingRows);
    if (lastAction.bcSyncQueue) setBcSyncQueue(lastAction.bcSyncQueue);
    
    setUndoHistory(remainingHistory);
  };

  const stats = useMemo(() => {
    if (appState.data.length === 0) return null;
    let missingDeLong = 0;
    let missingEnLong = 0;
    let missingDeShort = 0;
    let missingEnShort = 0;
    let fullyComplete = 0;

    appState.data.forEach(row => {
      const deLong = row[COLUMNS.LONG_DE];
      const enLong = row[COLUMNS.LONG_EN];
      const deShort = row[COLUMNS.SHORT_DE];
      const enShort = row[COLUMNS.SHORT_EN];

      if (!deLong) missingDeLong++;
      if (!enLong) missingEnLong++;
      if (!deShort) missingDeShort++;
      if (!enShort) missingEnShort++;

      if (deLong && enLong && deShort && enShort) fullyComplete++;
    });

    return {
      total: appState.data.length,
      missingDeLong,
      missingEnLong,
      missingDeShort,
      missingEnShort,
      fullyComplete
    };
  }, [appState.data]);

  const [isMaximized, setIsMaximized] = useState(false);

  if (!session) {
    return <LoginPage onLogin={() => {
      const stored = getStoredSession();
      console.log('[App] onLogin, stored session:', stored ? 'found' : 'null');
      setSession(stored);
    }} />;
  }

  return (
    <ColumnsProvider headers={appState.headers}>
      <FilterProvider>
        <div className="h-screen bg-[#040d1a] text-slate-200 flex flex-col font-sans overflow-hidden">
      {!isMaximized && (
        <TopBar
          stats={stats}
          activeModule={activeModule}
          onExport={handleExport}
          onDownloadBCExcel={handleDownloadBCExcel}
          onRefresh={() => { setRefreshTrigger(t => t + 1); }}
          hasData={appState.data.length > 0}
          hasUnsavedChanges={appState.hasUnsavedChanges}
          userEmail={session.user.email}
          onSignOut={handleSignOut}
          canUndo={undoHistory.length > 0}
          onUndo={handleUndo}
          undoMessage={undoHistory[0]?.message}
          undoSteps={undoHistory.length}
          pendingCount={Object.keys(pendingRows).length}
          pendingChanges={Object.fromEntries(Object.entries(pendingRows).map(([k, v]) => [k, { articleName: (v as any).articleName }]))}
          onSaveAll={handleSaveAll}
          onRevertRow={handleRevertRow}
          isSavingAll={isSavingAll}
          bcQueueCount={Object.keys(bcSyncQueue).length}
          bcQueueEntries={Object.fromEntries((Object.entries(bcSyncQueue) as Array<[string, BcSyncQueueEntry]>).map(([k, v]) => [k, { articleName: v.articleName, selected: v.selected, status: v.status, error: v.error, warning: v.warning }]))}
          onToggleBcQueueSelection={handleToggleBcQueueSelection}
          onSelectAllBcQueue={handleSelectAllBcQueue}
          onPreviewSelectedBcSync={handlePreviewSelectedBcSync}
          onSyncSelectedBcSync={handleSyncSelectedBcSync}
          onDiscardSelectedBcQueue={handleDiscardSelectedBcQueue}
          isSyncingBC={isSyncingBC}
          isDownloadingBCExcel={isDownloadingBCExcel}
          isMaximized={isMaximized}
          onToggleMaximize={() => setIsMaximized(true)}
        />
      )}
      <div className="flex flex-1 overflow-hidden">
        {!isMaximized && (
          <Sidebar activeModule={activeModule} setActiveModule={setActiveModule} userEmail={session.user.email} />
        )}
        <main className={cn(
          "flex-1 overflow-auto relative bg-[#041021] transition-all duration-300",
          isMaximized ? "p-0" : "p-6"
        )}>
          {isMaximized && (
            <button
              onClick={() => setIsMaximized(false)}
              className="fixed top-4 right-4 z-[100] p-3 bg-slate-800 hover:bg-slate-700 text-slate-400 hover:text-white rounded-full shadow-2xl border border-slate-700 transition-all active:scale-95 group"
              title="Exit Fullscreen"
            >
              <X className="w-6 h-6" />
              <span className="absolute right-full mr-2 top-1/2 -translate-y-1/2 px-2 py-1 bg-slate-900 text-xs text-white rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
                Exit Fullscreen
              </span>
            </button>
          )}
          {isLoadingDefault ? (
            <div className="flex flex-col items-center justify-center h-full text-slate-400">
              <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mb-4"></div>
              <p className="text-lg">Loading latest data...</p>
            </div>
          ) : defaultLoadError && appState.data.length === 0 ? (
            <div className="flex flex-col items-center justify-center h-full text-red-400">
              <p className="mb-4 text-lg">Failed to auto-load data: {defaultLoadError}</p>
              <label className="cursor-pointer bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg shadow-lg transition-colors">
                Load Excel File Manually
                <input type="file" accept=".xlsx, .xls" className="hidden" onChange={handleFileUpload} />
              </label>
            </div>
          ) : appState.data.length === 0 && activeModule !== 'upload' ? (
            <div className="flex flex-col items-center justify-center h-full text-slate-400">
              <p className="mb-4 text-lg">No data loaded.</p>
              <label className="cursor-pointer bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg shadow-lg transition-colors">
                Load Excel File
                <input type="file" accept=".xlsx, .xls" className="hidden" onChange={handleFileUpload} />
              </label>
            </div>
          ) : (
            <>
              {activeModule === 'descriptions' && (
                <ProductDescriptions
                  data={appState.data}
                  headers={appState.headers}
                  asinColumnIndex={appState.asinColumnIndex}
                  onEdit={(index) => setEditingRowIndex(index)}
                  rowStatuses={rowStatuses}
                  dashboardDrilldown={dashboardDrilldown}
                  onDashboardDrilldownApplied={() => setDashboardDrilldown(null)}
                />
              )}
              {activeModule === 'control_dashboard' && (
                <ControlDashboardView
                  headers={appState.headers}
                  data={appState.data}
                  pendingRows={pendingRows}
                  rowStatuses={rowStatuses}
                  activeModule={activeModule}
                  onDrillDown={(request) => {
                    setDashboardDrilldown(request);
                    setActiveModule(request.tabId);
                  }}
                />
              )}
              {activeModule === 'matrix' && (
                <MatrixView data={appState.data} headers={appState.headers} rowStatuses={rowStatuses} />
              )}

              {activeModule === 'dimensions' && (
                <DimensionsView
                  data={appState.data}
                  headers={appState.headers}
                  onEdit={(index) => setEditingRowIndex(index)}
                  onSaveRow={handleSaveRow}
                  onCaptureState={captureState}
                  rowStatuses={rowStatuses}
                  onRevertRow={handleRevertRow}
                />
              )}

              {activeModule === 'pricing' && (
                <PricingView
                  data={appState.data}
                  headers={appState.headers}
                  onSaveRow={handleSaveRow}
                  onCaptureState={captureState}
                  onEdit={(index) => setEditingRowIndex(index)}
                  rowStatuses={rowStatuses}
                  dashboardDrilldown={dashboardDrilldown}
                  onDashboardDrilldownApplied={() => setDashboardDrilldown(null)}
                />
              )}
              {activeModule === 'article_details' && (
                <ArticleDetails
                  data={appState.data}
                  onEdit={(index) => setEditingRowIndex(index)}
                  rowStatuses={rowStatuses}
                  dashboardDrilldown={dashboardDrilldown}
                  onDashboardDrilldownApplied={() => setDashboardDrilldown(null)}
                />
              )}
              {activeModule === 'pending_validation' && (
                <PendingValidationView
                  data={appState.data}
                  pendingRows={pendingRows}
                  rowStatuses={rowStatuses}
                  onRevertRow={handleRevertRow}
                  onEdit={(index) => setEditingRowIndex(index)}
                />
              )}
              {activeModule === 'missing_data' && (
                <MissingDataView
                  data={appState.data}
                  headers={appState.headers}
                  onSaveRow={handleSaveRow}
                  onCaptureState={captureState}
                />
              )}
              {activeModule === 'cosmetic_items' && (
                <CosmeticItemsView
                  data={appState.data}
                  headers={appState.headers}
                  onSaveRow={handleSaveRow}
                  onCaptureState={captureState}
                  rowStatuses={rowStatuses}
                  onQueueBcSync={handleQueueBcSync}
                  dashboardDrilldown={dashboardDrilldown}
                  onDashboardDrilldownApplied={() => setDashboardDrilldown(null)}
                />
              )}
              {activeModule === 'history' && (
                <HistoryView 
                  headers={appState.headers}
                  data={appState.data}
                  onEdit={(index) => setEditingRowIndex(index)}
                  onRevert={async (articleNo, revertedData, historyId) => {
                    // Find the row in appState.data and update it
                    const rowIndex = appState.data.findIndex(r => String(r[COLUMNS.ARTICLE_NO]) === articleNo);
                    if (rowIndex !== -1) {
                      setAppState(prev => {
                        const newData = [...prev.data];
                        newData[rowIndex] = revertedData;
                        return { ...prev, data: newData, hasUnsavedChanges: true };
                      });
                      // Mark as pending for sync
                      setRowStatuses(prev => ({ ...prev, [articleNo]: 'pending' }));
                      setPendingRows(prev => ({
                        ...prev,
                        [articleNo]: {
                          rowIndex,
                          originalData: appState.data[rowIndex],
                          newData: revertedData,
                          articleName: String(revertedData[COLUMNS.ARTICLE_NAME] || articleNo),
                        }
                      }));
                      // Delete the history entry after revert
                      if (historyId) {
                        await deleteHistoryEntry(String(historyId));
                      }
                    }
                  }}
                />
              )}
              {activeModule === 'user_management' && session && (
                <UserManagementView session={session} />
              )}
            </>
          )}
        </main>
      </div>

      <UndoToast
        undoState={undoHistory[0] || null}
        onUndo={handleUndo}
        onClose={() => {
          // Instead of clearing history, we can just hide the toast
          // But since UndoToast is driven by undoHistory[0], 
          // we might want a way to "acknowledge" the current top of history
          // For now, let's just not clear the history.
        }}
      />

      {editingRowIndex !== null && (
        <EditPanel
          row={appState.data[editingRowIndex]}
          rowIndex={editingRowIndex}
          onSave={handleSaveRow}
          onClose={() => setEditingRowIndex(null)}
          onCaptureState={captureState}
        />
      )}
    </div>
      </FilterProvider>
    </ColumnsProvider>
  );
}
