<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import { PocketBaseAuth } from '$lib/auth/frontend';
  import type { AuthState } from '$lib/auth/types';

  interface JobRecord {
    id: string;
    Job_Number: string;
    Job_Name: string;
    Job_Full_Name: string;
    Company_Client: string;
    Contact_Person: string;
    Job_Division: string;
    Active: boolean | string;
    Estimator: string;
    Job_Status: string;
    Notes?: string;
    Job_Folder_Link?: string;
    [key: string]: any;
  }

  let auth: PocketBaseAuth | undefined;
  let authState: AuthState = {
    isAuthenticated: false,
    user: null,
    token: null,
  };

  let jobsCache: JobRecord[] = [];
  let visibleJobs: JobRecord[] = [];
  let loading = false;
  let error = '';
  let selectedJob: JobRecord | null = null;
  let showDetailModal = false;
  let showNotesTab = false;
  let showFolderContents = false;
  let folderSearchFilter = '';

  // File categorization keywords (from Mode6Test)
  const categoryKeywords = {
    managerInfo: ['manager info', 'manager_info', 'managerinfo'],
    contracts: ['contract', 'estimate', 'proposal', 'bid', 'award', 'agreement', 'sow'],
    plans: ['plan', 'drawing', 'dwg', 'pdf', 'sheet', 'layout'],
    submittals: ['submittal', 'submittals', 'submit'],
  };

  const categorizeFile = (name: string): 'managerInfo' | 'contracts' | 'plans' | 'submittals' | 'other' => {
    const n = name.toLowerCase();
    if (categoryKeywords.managerInfo.some(w => n.includes(w))) return 'managerInfo';
    if (categoryKeywords.contracts.some(w => n.includes(w))) return 'contracts';
    if (categoryKeywords.submittals.some(w => n.includes(w))) return 'submittals';
    if (categoryKeywords.plans.some(w => n.includes(w))) return 'plans';
    return 'other';
  };

  const getFileIcon = (name: string): string => {
    const n = name.toLowerCase();
    if (n.endsWith('.pdf')) return 'pdf';
    if (n.endsWith('.doc') || n.endsWith('.docx')) return 'doc';
    if (n.endsWith('.xls') || n.endsWith('.xlsx')) return 'sheet';
    if (n.endsWith('.ppt') || n.endsWith('.pptx')) return 'presentation';
    if (n.match(/\.(jpg|jpeg|png|gif|bmp|tiff|webp)$/i)) return 'image';
    return 'file';
  };

  const getSvgIcon = (iconType: string): string => {
    const icons: {[key: string]: string} = {
      pdf: `<svg viewBox="0 0 24 24" fill="none" style="width:16px;height:16px;"><rect fill="#D32F2F" width="24" height="24" rx="2"/><text x="12" y="16" font-size="10" font-weight="bold" fill="white" text-anchor="middle">PDF</text></svg>`,
      doc: `<svg viewBox="0 0 24 24" fill="none" style="width:16px;height:16px;"><rect fill="#2B579A" width="24" height="24" rx="2"/><text x="12" y="16" font-size="10" font-weight="bold" fill="white" text-anchor="middle">W</text></svg>`,
      sheet: `<svg viewBox="0 0 24 24" fill="none" style="width:16px;height:16px;"><rect fill="#217346" width="24" height="24" rx="2"/><text x="12" y="16" font-size="10" font-weight="bold" fill="white" text-anchor="middle">X</text></svg>`,
      presentation: `<svg viewBox="0 0 24 24" fill="none" style="width:16px;height:16px;"><rect fill="#D35230" width="24" height="24" rx="2"/><text x="12" y="16" font-size="10" font-weight="bold" fill="white" text-anchor="middle">P</text></svg>`,
      image: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;color:#6b7280;"><rect x="2" y="2" width="20" height="20" rx="2"/><circle cx="8" cy="8" r="2"/><path d="M21 15l-5-5L5 21"/></svg>`,
      zip: `<svg viewBox="0 0 24 24" fill="currentColor" style="width:16px;height:16px;color:#6b7280;"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zm-5-9h-3v4h3v-4zm0-3h-3v2h3V7z"/></svg>`,
      text: `<svg viewBox="0 0 24 24" fill="currentColor" style="width:16px;height:16px;color:#6b7280;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/><path d="M16 13H8M16 17H8M10 9H8"/></svg>`,
      file: `<svg viewBox="0 0 24 24" fill="currentColor" style="width:16px;height:16px;color:#6b7280;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/></svg>`,
    };
    return icons[iconType] || icons.file;
  };

  const formatFileSize = (bytes: number | undefined): string => {
    if (!bytes) return '—';
    if (bytes < 1024) return `${bytes}B`;
    if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
    return `${Math.round(bytes / (1024 * 1024))}MB`;
  };

  const formatDate = (dateStr: string | undefined): string => {
    if (!dateStr) return '';
    try {
      return new Date(dateStr).toLocaleDateString();
    } catch {
      return '';
    }
  };

  const canPreviewInline = (name: string, contentType: string = ''): boolean => {
    const ext = (name || '').toLowerCase().split('.').pop();
    const type = (contentType || '').toLowerCase();
    return ext === 'pdf' || type.includes('pdf') || 
           ['doc', 'docx'].includes(ext as string) || type.includes('word') ||
           ['jpg', 'jpeg', 'png', 'gif', 'svg', 'bmp'].includes(ext as string) || type.includes('image');
  };

  const renderPdfPage = async () => {
    if (!currentPdf || !currentPdf.doc) return;
    
    const pageObj = await currentPdf.doc.getPage(currentPdf.page);
    const viewport = pageObj.getViewport({ scale: currentPdf.scale });
    const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;
    if (!canvas) return;
    
    const context = canvas.getContext('2d');
    if (!context) return;
    
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    
    await pageObj.render({
      canvasContext: context,
      viewport: viewport,
    }).promise;
  };

  const renderImageOnCanvas = () => {
    if (!currentPdf?.imageElement) return;
    const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;
    if (!canvas) return;
    
    const ctx = canvas.getContext('2d');
    if (!ctx) return;
    
    const scale = currentPdf.scale;
    canvas.width = (currentPdf.imageWidth || 0) * scale;
    canvas.height = (currentPdf.imageHeight || 0) * scale;
    
    ctx.drawImage(currentPdf.imageElement, 0, 0, canvas.width, canvas.height);
  };

  const updateZoomLabel = () => {
    const label = document.getElementById('pdfZoomLabel');
    if (label && currentPdf) {
      label.textContent = `${Math.round(currentPdf.scale * 100)}%`;
    }
  };

  const setupCanvasEventHandlers = () => {
    const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;
    if (!canvas) return;

    // Zoom buttons
    const zoomOutBtn = document.getElementById('pdfZoomOut');
    const zoomResetBtn = document.getElementById('pdfZoomReset');
    const zoomInBtn = document.getElementById('pdfZoomIn');

    if (zoomOutBtn) {
      zoomOutBtn.addEventListener('click', async () => {
        if (!currentPdf) return;
        currentPdf.scale = Math.max(MIN_PDF_SCALE, currentPdf.scale - 0.1);
        pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
        canvas.style.transform = 'translate(0px, 0px)';
        if (currentPdf.isImage) {
          renderImageOnCanvas();
        } else {
          await renderPdfPage();
        }
        updateZoomLabel();
      });
    }

    if (zoomResetBtn) {
      zoomResetBtn.addEventListener('click', async () => {
        if (!currentPdf) return;
        currentPdf.scale = DEFAULT_PDF_SCALE;
        pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
        canvas.style.transform = 'translate(0px, 0px)';
        if (currentPdf.isImage) {
          renderImageOnCanvas();
        } else {
          await renderPdfPage();
        }
        updateZoomLabel();
      });
    }

    if (zoomInBtn) {
      zoomInBtn.addEventListener('click', async () => {
        if (!currentPdf) return;
        currentPdf.scale = Math.min(MAX_PDF_SCALE, currentPdf.scale + 0.1);
        pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
        canvas.style.transform = 'translate(0px, 0px)';
        if (currentPdf.isImage) {
          renderImageOnCanvas();
        } else {
          await renderPdfPage();
        }
        updateZoomLabel();
      });
    }

    // Page navigation buttons (if multi-page)
    const prevBtn = document.getElementById('pdfPrev');
    const nextBtn = document.getElementById('pdfNext');

    if (prevBtn) {
      prevBtn.addEventListener('click', async () => {
        if (!currentPdf || currentPdf.page <= 1) return;
        currentPdf.page--;
        pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
        canvas.style.transform = 'translate(0px, 0px)';
        await renderPdfPage();
        document.getElementById('pdfPageNum')!.textContent = String(currentPdf.page);
      });
    }

    if (nextBtn) {
      nextBtn.addEventListener('click', async () => {
        if (!currentPdf || currentPdf.page >= currentPdf.pages) return;
        currentPdf.page++;
        pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
        canvas.style.transform = 'translate(0px, 0px)';
        await renderPdfPage();
        document.getElementById('pdfPageNum')!.textContent = String(currentPdf.page);
      });
    }

    // Canvas pan (mouse drag)
    canvas.addEventListener('mousedown', (e) => {
      pdfPan.dragging = true;
      pdfPan.startX = e.clientX;
      pdfPan.startY = e.clientY;
      canvas.style.cursor = 'grabbing';
    });

    canvas.addEventListener('mousemove', (e) => {
      if (!pdfPan.dragging) return;
      const dx = e.clientX - pdfPan.startX;
      const dy = e.clientY - pdfPan.startY;
      pdfPan.x = pdfPan.baseX + dx;
      pdfPan.y = pdfPan.baseY + dy;
      canvas.style.transform = `translate(${pdfPan.x}px, ${pdfPan.y}px)`;
    });

    canvas.addEventListener('mouseup', () => {
      if (pdfPan.dragging) {
        pdfPan.baseX = pdfPan.x;
        pdfPan.baseY = pdfPan.y;
        pdfPan.dragging = false;
        canvas.style.cursor = 'grab';
      }
    });

    canvas.addEventListener('mouseleave', () => {
      if (pdfPan.dragging) {
        pdfPan.baseX = pdfPan.x;
        pdfPan.baseY = pdfPan.y;
        pdfPan.dragging = false;
        canvas.style.cursor = 'grab';
      }
    });

    // Pinch zoom (touch)
    let lastDistance = 0;
    canvas.addEventListener('touchstart', (e) => {
      if (e.touches.length === 2) {
        const dx = e.touches[0].clientX - e.touches[1].clientX;
        const dy = e.touches[0].clientY - e.touches[1].clientY;
        lastDistance = Math.sqrt(dx * dx + dy * dy);
      } else if (e.touches.length === 1) {
        pdfPan.dragging = true;
        pdfPan.startX = e.touches[0].clientX;
        pdfPan.startY = e.touches[0].clientY;
      }
    });

    canvas.addEventListener('touchmove', async (e) => {
      if (e.touches.length === 2) {
        e.preventDefault();
        const dx = e.touches[0].clientX - e.touches[1].clientX;
        const dy = e.touches[0].clientY - e.touches[1].clientY;
        const distance = Math.sqrt(dx * dx + dy * dy);
        if (lastDistance > 0) {
          const scaleDelta = (distance - lastDistance) / 100;
          const newScale = currentPdf ? currentPdf.scale + scaleDelta : DEFAULT_PDF_SCALE;
          if (currentPdf) {
            currentPdf.scale = Math.max(MIN_PDF_SCALE, Math.min(MAX_PDF_SCALE, newScale));
            if (currentPdf.isImage) {
              renderImageOnCanvas();
            } else {
              await renderPdfPage();
            }
            updateZoomLabel();
          }
        }
        lastDistance = distance;
      } else if (e.touches.length === 1 && pdfPan.dragging) {
        const dx = e.touches[0].clientX - pdfPan.startX;
        const dy = e.touches[0].clientY - pdfPan.startY;
        pdfPan.x = pdfPan.baseX + dx;
        pdfPan.y = pdfPan.baseY + dy;
        const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;
        if (canvas) {
          canvas.style.transform = `translate(${pdfPan.x}px, ${pdfPan.y}px)`;
        }
      }
    });

    canvas.addEventListener('touchend', () => {
      pdfPan.baseX = pdfPan.x;
      pdfPan.baseY = pdfPan.y;
      pdfPan.dragging = false;
      lastDistance = 0;
    });

    // Wheel zoom (mouse scroll)
    canvas.addEventListener('wheel', async (e) => {
      e.preventDefault();
      if (!currentPdf) return;
      const zoomDelta = e.deltaY > 0 ? -0.1 : 0.1;
      currentPdf.scale = Math.max(MIN_PDF_SCALE, Math.min(MAX_PDF_SCALE, currentPdf.scale + zoomDelta));
      if (currentPdf.isImage) {
        renderImageOnCanvas();
      } else {
        await renderPdfPage();
      }
      updateZoomLabel();
    }, { passive: false });
  };


  // Setup canvas event handlers for zoom, pan, and pinch
  const setupPdfControls = () => {
    const zoomInBtn = document.getElementById('pdfZoomInBtn') as HTMLButtonElement;
    const zoomOutBtn = document.getElementById('pdfZoomOutBtn') as HTMLButtonElement;
    const zoomResetBtn = document.getElementById('pdfZoomResetBtn') as HTMLButtonElement;
    const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;

    if (!canvas) return;

    const applyPan = () => {
      canvas.style.transform = `translate(${pdfPan.x}px, ${pdfPan.y}px)`;
    };

    const activePointers = new Map<number, {x: number; y: number}>();
    let pinchStartDistance: number | null = null;
    let pinchStartScale: number | null = null;

    const distanceBetween = () => {
      if (activePointers.size < 2) return null;
      const pts = Array.from(activePointers.values());
      const a = pts[0];
      const b = pts[1];
      const dx = a.x - b.x;
      const dy = a.y - b.y;
      return Math.hypot(dx, dy);
    };

    // Zoom button handlers
    if (zoomInBtn) {
      zoomInBtn.addEventListener('click', async () => {
        if (!currentPdf) return;
        currentPdf.scale = Math.min(currentPdf.scale + 0.1, MAX_PDF_SCALE);
        if (currentPdf.isImage) {
          renderImageOnCanvas();
        } else {
          await renderPdfPage();
        }
      });
    }

    if (zoomOutBtn) {
      zoomOutBtn.addEventListener('click', async () => {
        if (!currentPdf) return;
        currentPdf.scale = Math.max(currentPdf.scale - 0.1, MIN_PDF_SCALE);
        if (currentPdf.isImage) {
          renderImageOnCanvas();
        } else {
          await renderPdfPage();
        }
      });
    }

    if (zoomResetBtn) {
      zoomResetBtn.addEventListener('click', async () => {
        if (!currentPdf) return;
        currentPdf.scale = currentPdf.isImage ? 1 : DEFAULT_PDF_SCALE;
        pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
        canvas.style.transform = 'translate(0px, 0px)';
        if (currentPdf.isImage) {
          renderImageOnCanvas();
        } else {
          await renderPdfPage();
        }
      });
    }

    // Canvas event handlers
    canvas.style.touchAction = 'none';

    canvas.addEventListener('wheel', async (e) => {
      if (!currentPdf) return;
      e.preventDefault();
      const delta = e.deltaY;
      const step = 0.1;
      if (delta < 0) {
        currentPdf.scale = Math.min(currentPdf.scale + step, MAX_PDF_SCALE);
      } else {
        currentPdf.scale = Math.max(currentPdf.scale - step, MIN_PDF_SCALE);
      }
      if (currentPdf.isImage) {
        renderImageOnCanvas();
      } else {
        await renderPdfPage();
      }
    }, { passive: false });

    canvas.addEventListener('pointerdown', (e) => {
      if (!currentPdf) return;
      activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });

      if (activePointers.size === 2) {
        pinchStartDistance = distanceBetween();
        pinchStartScale = currentPdf.scale;
        pdfPan.dragging = false;
      } else if (activePointers.size === 1) {
        pdfPan.dragging = true;
        pdfPan.startX = e.clientX;
        pdfPan.startY = e.clientY;
        pdfPan.baseX = pdfPan.x;
        pdfPan.baseY = pdfPan.y;
        canvas.style.cursor = 'grabbing';
      }

      canvas.setPointerCapture(e.pointerId);
    });

    canvas.addEventListener('pointermove', (e) => {
      if (!currentPdf) return;

      if (activePointers.has(e.pointerId)) {
        activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
      }

      if (activePointers.size >= 2 && pinchStartDistance) {
        const dist = distanceBetween();
        if (dist) {
          const scale = pinchStartScale! * (dist / pinchStartDistance);
          currentPdf.scale = Math.min(Math.max(scale, MIN_PDF_SCALE), MAX_PDF_SCALE);
          if (currentPdf.isImage) {
            renderImageOnCanvas();
          } else {
            renderPdfPage();
          }
        }
        return;
      }

      if (!pdfPan.dragging) return;
      const dx = e.clientX - pdfPan.startX;
      const dy = e.clientY - pdfPan.startY;
      pdfPan.x = pdfPan.baseX + dx;
      pdfPan.y = pdfPan.baseY + dy;
      applyPan();
    });

    const endPan = (e: PointerEvent) => {
      if (activePointers.has(e.pointerId)) {
        activePointers.delete(e.pointerId);
      }

      if (activePointers.size < 2) {
        pinchStartDistance = null;
        pinchStartScale = null;
      }

      if (activePointers.size === 0) {
        pdfPan.dragging = false;
        canvas.style.cursor = 'grab';
      }

      try {
        canvas.releasePointerCapture(e.pointerId);
      } catch {}
    };

    canvas.addEventListener('pointerup', endPan);
    canvas.addEventListener('pointercancel', endPan);
  };

  // Search and filters
  let searchQuery = '';
  let showFiltersPanel = false;
  let filterDivisions: string[] = [];
  let filterActive: string[] = [];
  let filterEstimators: string[] = [];
  let filterStatuses: string[] = [];

  // Voice search
  let isListening = false;
  let recognition: any = null;
  let hasVoicePermission = false;

  // Files
  let jobFiles: any[] = [];
  let loadingFiles = false;
  let filesError = '';
  let showFileViewer = false;
  let currentViewerFile: any = null;
  let graphToken = '';
  
  // PDF/Image viewer state
  let currentPreviewUrl: string | null = null;
  let currentPdf: {doc: any; page: number; pages: number; scale: number; isImage?: boolean; imageElement?: HTMLImageElement; imageWidth?: number; imageHeight?: number} | null = null;
  let pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
  let showPdfViewer = false;
  const DEFAULT_PDF_SCALE = 0.45;
  const MIN_PDF_SCALE = 0.3;
  const MAX_PDF_SCALE = 3.0;
  let graphTokenExpiry: number | null = null; // Expiration timestamp in ms
  let graphTokenRefreshTimer: NodeJS.Timeout | null = null; // Background refresh timer

  // New note
  let newNoteText = '';

  const divisions = ['C#', 'R#', 'S#'];
  const activeOptions = ['Yes', 'No'];
  const estimators = ['Steve Brewer', 'Beth Cardoza', 'Timothy Cardoza'];
  const statuses = ['Estimating', 'In Progress', 'Awarded', 'Billed (In Progress)', 'Not Bidding', 'Est Sent', 'Not Awarded'];

  const safeLower = (v: any) => (v || '').toString().trim().toLowerCase();
  const toBool = (v: any) => {
    if (typeof v === 'boolean') return v;
    const s = String(v || '').trim().toLowerCase();
    if (s === 'true' || s === 'yes' || s === 'y' || s === '1') return true;
    if (s === 'false' || s === 'no' || s === 'n' || s === '0') return false;
    return null;
  };

  onMount(async () => {
    try {
      const response = await fetch('/api/auth/config');
      if (!response.ok) throw new Error('Failed to get config');
      const config = await response.json();

      auth = new PocketBaseAuth(config, {
        onAuthSuccess: (state: AuthState) => {
          console.log('📍 onAuthSuccess called with state:', state);
          authState = state;
          console.log('📍 authState updated, isAuthenticated:', authState.isAuthenticated);
          // Delay loading to ensure auth state updates
          setTimeout(() => {
            console.log('📍 loadInitialJobs starting, auth check:', authState.isAuthenticated);
            loadInitialJobs();
          }, 100);
        },
        onAuthFailure: (err: unknown) => {
          error = 'Authentication failed: ' + (err instanceof Error ? err.message : 'Unknown error');
        },
        onTokenUpdate: (state: AuthState) => {
          console.log('📍 onTokenUpdate called with state:', state);
          authState = state;
          console.log('📍 authState updated from token refresh');
          // Also load jobs when token is refreshed (e.g., on page reload after login)
          setTimeout(() => {
            console.log('📍 loadInitialJobs starting from token update');
            loadInitialJobs();
          }, 100);
        },
        onUiUpdate: (state: AuthState) => {
          authState = state;
        },
      });

      // Setup voice search
      const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
      if (SpeechRecognition) {
        recognition = new SpeechRecognition();
        recognition.continuous = false;
        recognition.interimResults = false;
        recognition.language = 'en-US';
        recognition.onstart = () => {
          isListening = true;
        };
        recognition.onend = () => {
          isListening = false;
        };
        recognition.onresult = (event: any) => {
          let transcript = Array.from(event.results)
            .map((result: any) => result[0].transcript)
            .join('')
            .trim();
          // Remove trailing period that speech recognition often adds
          transcript = transcript.replace(/\.$/, '');
          searchQuery = transcript;
          applyFiltersAndRender();
        };
        hasVoicePermission = true;
      }
    } catch (err) {
      error = err instanceof Error ? err.message : 'Unknown error';
    }
  });

  // Clean up token refresh timer when component is destroyed
  onDestroy(() => {
    if (graphTokenRefreshTimer) {
      clearTimeout(graphTokenRefreshTimer);
      console.log('📁 Cleaned up token refresh timer');
    }
  });

  async function loadInitialJobs() {
    console.log('📊 loadInitialJobs called');
    console.log('📊 authState.isAuthenticated:', authState.isAuthenticated);
    
    if (!authState.isAuthenticated) {
      error = 'Not authenticated';
      console.error('📊 Load failed: Not authenticated');
      return;
    }

    loading = true;
    error = '';

    try {
      // Load first batch of 100 jobs immediately
      console.log('📊 Fetching first batch: /api/jobs/list?page=1&perPage=100');
      const response = await fetch('/api/jobs/list?page=1&perPage=100');
      console.log('📊 Fetch response status:', response.status);
      
      if (!response.ok) throw new Error('Failed to load jobs');

      const data = await response.json();
      console.log('📊 Data received, items count:', data.items?.length || 0);
      console.log('📊 Total pages:', data.totalPages || 'unknown');
      
      jobsCache = data.items || [];
      console.log('📊 jobsCache set, length:', jobsCache.length);
      
      if (jobsCache.length === 0) {
        error = 'No jobs found';
      }
      
      console.log('📊 Calling applyFiltersAndRender');
      applyFiltersAndRender();
      console.log('📊 visibleJobs after render:', visibleJobs.length);
      
      // Load remaining pages in background if there are more
      if (data.totalPages && data.totalPages > 1) {
        console.log('📊 Loading remaining pages in background...');
        loadRemainingPages(data.totalPages, 2);
      } else {
        console.log('📊 All jobs loaded');
      }
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Failed to load jobs';
      error = msg;
      console.error('📊 Load error:', msg);
      jobsCache = [];
      visibleJobs = [];
    } finally {
      loading = false;
      console.log('📊 loadInitialJobs complete');
    }
  }

  async function loadRemainingPages(totalPages: number, startPage: number) {
    try {
      for (let page = startPage; page <= totalPages; page++) {
        console.log(`📊 Loading page ${page}/${totalPages}`);
        const response = await fetch(`/api/jobs/list?page=${page}&perPage=100`);
        if (!response.ok) {
          console.warn(`📊 Failed to load page ${page}, stopping background load`);
          break;
        }
        const data = await response.json();
        const items = data.items || [];
        jobsCache.push(...items);
        console.log(`📊 Page ${page} loaded, total jobs now: ${jobsCache.length}`);
        
        // Re-render with updated cache
        applyFiltersAndRender();
      }
      console.log('📊 All pages loaded, total jobs:', jobsCache.length);
    } catch (err) {
      console.error('📊 Background page load error:', err);
    }
  }

  function applyFiltersAndRender() {
    console.log('🔍 applyFiltersAndRender called');
    console.log('🔍 jobsCache length:', jobsCache.length);
    console.log('🔍 searchQuery:', searchQuery);
    
    let q = safeLower(searchQuery);

    const divisionVals = filterDivisions;
    const activeVals = filterActive;
    const activeWanted = activeVals.map(v => toBool(v)).filter(v => v !== null);
    const statusVals = filterStatuses;
    const estimatorVals = filterEstimators;
    const estimatorValsLower = estimatorVals.map(s => safeLower(s));

    console.log('🔍 Filter criteria - divisions:', divisionVals, 'active:', activeWanted, 'status:', statusVals, 'estimators:', estimatorValsLower);
    console.log('🔍 Starting filter with', jobsCache.length, 'jobs');
    
    visibleJobs = jobsCache.filter(job => {
      const searchable = [
        job.Job_Number,
        job.Job_Full_Name,
        job.Job_Address,
        job.Contact_Person,
        job.Company_Client,
        job.Notes
      ]
        .join(' ')
        .toLowerCase();

      // Partial match for search
      if (q && !searchable.includes(q)) return false;
      if (divisionVals.length && !divisionVals.includes(job.Job_Division)) return false;

      if (activeWanted.length) {
        const b = toBool(job.Active);
        if (b === null || !activeWanted.includes(b)) return false;
      }

      if (statusVals.length && !statusVals.includes(job.Job_Status || '')) return false;

      if (estimatorVals.length) {
        const je = safeLower(job.Estimator || '');
        const ok = estimatorValsLower.some(v => je.includes(v) || v.includes(je));
        if (!ok) return false;
      }

      return true;
    });

    // Sort by job number descending
    visibleJobs.sort((a, b) => {
      const numA = parseInt(a.Job_Number || '0', 10);
      const numB = parseInt(b.Job_Number || '0', 10);
      return numB - numA;
    });
    
    console.log('🔍 After filter, visibleJobs length:', visibleJobs.length);
    if (visibleJobs.length > 0) {
      console.log('🔍 First few jobs:', visibleJobs.slice(0, 3).map(j => `${j.Job_Number}: ${j.Job_Full_Name}`));
    }
  }

  async function selectJob(job: JobRecord) {
    try {
      const response = await fetch(`/api/jobs/${job.id}`);
      if (!response.ok) throw new Error('Failed to load job details');
      selectedJob = await response.json();
      showDetailModal = true;
      showNotesTab = false;
      showFolderContents = false;
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to load job details';
    }
  }

  /**
   * Ensure Graph token is valid and fresh
   * Returns token or empty string if fetch fails
   * Automatically refreshes if expired (60s buffer like backend)
   */
  async function ensureGraphToken() {
    const now = Date.now();
    const bufferMs = 60000; // 60 second buffer (same as backend)
    
    // Token is valid if it exists and has 60+ seconds remaining
    if (graphToken && graphTokenExpiry && (graphTokenExpiry - bufferMs) > now) {
      console.log('📁 Using cached Graph token (expires in', Math.floor((graphTokenExpiry - now) / 1000), 'seconds)');
      return graphToken;
    }
    
    if (graphToken) {
      console.log('📁 Graph token expired or expiring soon, refreshing...');
    }
    
    try {
      const response = await fetch('/api/auth/graph/token');
      if (!response.ok) {
        console.warn('📁 Failed to get Graph token:', response.statusText);
        return '';
      }
      const data = await response.json();
      graphToken = data.fullToken || data.token || '';
      
      // Parse expiration from response
      if (data.expiresOn) {
        graphTokenExpiry = new Date(data.expiresOn).getTime();
        const expiresIn = Math.floor((graphTokenExpiry - now) / 1000);
        console.log('📁 Graph token obtained, expires in', expiresIn, 'seconds');
        
        // Schedule refresh 5 minutes before expiration (so we refresh before the 60s buffer hits)
        scheduleTokenRefresh();
      } else {
        console.log('📁 Graph token obtained (no expiry info)');
        graphTokenExpiry = null;
      }
      
      return graphToken;
    } catch (err) {
      console.error('📁 Error getting Graph token:', err);
      return '';
    }
  }
  
  /**
   * Schedule background refresh of Graph token before expiration
   * Refreshes 5 minutes before expiration to maintain a buffer
   */
  function scheduleTokenRefresh() {
    // Clear any existing timer
    if (graphTokenRefreshTimer) {
      clearTimeout(graphTokenRefreshTimer);
    }
    
    if (!graphTokenExpiry) return;
    
    const now = Date.now();
    const expiresIn = graphTokenExpiry - now;
    const refreshAtMs = expiresIn - (5 * 60 * 1000); // Refresh 5 minutes before expiry
    
    if (refreshAtMs > 0) {
      console.log('📁 Scheduled token refresh in', Math.floor(refreshAtMs / 1000), 'seconds');
      graphTokenRefreshTimer = setTimeout(() => {
        console.log('📁 Background token refresh triggered');
        ensureGraphToken(); // This will fetch a new token
      }, refreshAtMs);
    } else {
      // Token expires in less than 5 minutes, refresh immediately
      console.log('📁 Token expiring very soon, refreshing immediately');
      ensureGraphToken();
    }
  }

  async function loadJobFiles() {
    if (!selectedJob?.Job_Folder_Link) {
      filesError = 'No folder link available for this job';
      return;
    }

    loadingFiles = true;
    filesError = '';
    jobFiles = [];    folderSearchFilter = '';  // Reset search when loading new folder
    try {
      // Ensure we have a Graph token
      await ensureGraphToken();
      if (!graphToken) {
        throw new Error('Unable to obtain Graph token for file access');
      }

      console.log('📁 Loading files for folder:', selectedJob.Job_Folder_Link);
      const response = await fetch(`/api/job-files?link=${encodeURIComponent(selectedJob.Job_Folder_Link)}`, {
        headers: {
          'x-graph-token': graphToken
        }
      });
      
      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || `Failed to load files: ${response.statusText}`);
      }

      const data = await response.json();
      jobFiles = data.items || [];
      console.log('📁 Loaded', jobFiles.length, 'files');

      if (jobFiles.length === 0) {
        filesError = 'No files found in folder';
      }
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Failed to load files';
      filesError = msg;
      console.error('📁 Error:', msg);
    } finally {
      loadingFiles = false;
    }
  }

  function openFileInViewer(file: any) {
    if (!file.url) {
      console.error('No URL for file:', file);
      return;
    }
    console.log('📂 Opening file in viewer:', file.name, file.url);
    currentViewerFile = file;
    showFileViewer = true;
    showDetailModal = false;
    
    // Try to preview in canvas if supported
    const ext = (file.name || '').toLowerCase().split('.').pop();
    if (canPreviewInline(file.name, file.contentType || '')) {
      loadFilePreview(file);
    }
  }

  async function loadFilePreview(file: any) {
    if (!file.url) return;
    
    try {
      let fetchUrl = '';
      
      // Use backend proxy endpoints instead of direct SharePoint URLs
      const ext = (file.name || '').toLowerCase().split('.').pop();
      const isWordDoc = ['doc', 'docx'].includes(ext as string) || file.contentType?.includes('word');
      const isPdf = ext === 'pdf' || file.contentType?.includes('pdf');
      
      if (file.driveId && file.id) {
        // Use proxy endpoints with Graph API
        if (isWordDoc) {
          fetchUrl = `/api/file-pdf?driveId=${encodeURIComponent(file.driveId)}&itemId=${encodeURIComponent(file.id)}`;
        } else {
          fetchUrl = `/api/file-content?driveId=${encodeURIComponent(file.driveId)}&itemId=${encodeURIComponent(file.id)}`;
        }
      } else {
        // Fallback to direct URL (for non-SharePoint files)
        fetchUrl = file.url;
      }
      
      const response = await fetch(fetchUrl, {
        headers: graphToken ? { 'x-graph-token': graphToken } : {},
      });
      
      if (!response.ok) throw new Error(`Failed to fetch file: ${response.status}`);
      
      const blob = await response.blob();
      if (currentPreviewUrl) {
        URL.revokeObjectURL(currentPreviewUrl);
      }
      currentPreviewUrl = URL.createObjectURL(blob);
      
      const isImage = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'bmp'].includes(ext as string) || file.contentType?.includes('image');
      
      if ((isPdf || isWordDoc) && (window as any).pdfjsLib) {
        const data = await blob.arrayBuffer();
        const pdfjsLib = (window as any).pdfjsLib;
        pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
        
        const doc = await pdfjsLib.getDocument({ data }).promise;
        currentPdf = {
          doc,
          page: 1,
          pages: doc.numPages,
          scale: DEFAULT_PDF_SCALE,
        };
        pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
        showPdfViewer = true;
        
        // Render first page and setup controls
        await new Promise(resolve => setTimeout(resolve, 100));
        await renderPdfPage();
        updateZoomLabel();
        setupCanvasEventHandlers();
      } else if (isImage) {
        const img = new Image();
        img.onload = async () => {
          currentPdf = {
            doc: null,
            page: 1,
            pages: 1,
            scale: 1,
            isImage: true,
            imageElement: img,
            imageWidth: img.width,
            imageHeight: img.height,
          };
          pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
          showPdfViewer = true;
          
          await new Promise(resolve => setTimeout(resolve, 100));
          renderImageOnCanvas();
          updateZoomLabel();
          setupCanvasEventHandlers();
        };
        img.src = currentPreviewUrl;
      }
    } catch (err) {
      console.error('Preview load error:', err);
    }
  }

  function closeFileViewer() {
    showFileViewer = false;
    showPdfViewer = false;
    currentViewerFile = null;
    currentPdf = null;
    if (currentPreviewUrl) {
      URL.revokeObjectURL(currentPreviewUrl);
      currentPreviewUrl = null;
    }
    showDetailModal = true;
  }

  function handleSearch() {
    applyFiltersAndRender();
  }

  function handleFilterChange() {
    applyFiltersAndRender();
  }

  function clearFilters() {
    searchQuery = '';
    filterDivisions = [];
    filterActive = [];
    filterEstimators = [];
    filterStatuses = [];
    applyFiltersAndRender();
  }

  function toggleFiltersPanel() {
    showFiltersPanel = !showFiltersPanel;
  }

  function startVoiceSearch() {
    if (!recognition || isListening) return;
    recognition.start();
  }

  function handleLogout() {
    if (auth) {
      auth.logout();
      authState = {
        isAuthenticated: false,
        user: null,
        token: null,
      };
      jobsCache = [];
      visibleJobs = [];
      error = '';
    }
  }

  function toggleFilterCheckbox(filterArray: string[], value: string) {
    const idx = filterArray.indexOf(value);
    if (idx >= 0) {
      filterArray.splice(idx, 1);
    } else {
      filterArray.push(value);
    }
    filterArray = filterArray; // Trigger reactivity
    handleFilterChange();
  }

  async function saveNote() {
    if (!selectedJob || !newNoteText.trim()) return;

    try {
      const timestamp = new Date().toLocaleString();
      const formatted = `[${timestamp}]\n${newNoteText}`;
      const updated = formatted + '\n\n' + (selectedJob.Notes || '');

      const response = await fetch(`/api/jobs/${selectedJob.id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ Notes: updated }),
      });

      if (!response.ok) throw new Error('Failed to save note');
      const updatedJob = await response.json();

      // Update selectedJob and cache
      selectedJob = updatedJob;
      const idx = jobsCache.findIndex(j => j.id === selectedJob.id);
      if (idx >= 0) jobsCache[idx] = updatedJob;

      newNoteText = '';
    } catch (err) {
      error = err instanceof Error ? err.message : 'Failed to save note';
    }
  }
</script>

<div style="min-height: 100vh; background: #f3f4f6; color: #1f2937; display: flex; flex-direction: column; font-family: system-ui, -apple-system, sans-serif;">
  {#if !authState.isAuthenticated}
    <div style="flex: 1; display: flex; align-items: center; justify-content: center; padding: 24px;">
      <div style="background: white; border: 1px solid #e5e7eb; padding: 40px; border-radius: 8px; text-align: center; max-width: 400px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
        <h1 style="margin: 0 0 16px 0; font-size: 28px; color: #2563eb; font-weight: bold;">Job Info</h1>
        <p style="margin: 0 0 24px 0; color: #6b7280; font-size: 16px;">Please authenticate to access job information</p>
        <button
          id="loginBtn"
          style="background: #2563eb; color: white; border: none; padding: 14px 28px; border-radius: 6px; font-weight: bold; font-size: 16px; cursor: pointer; width: 100%;"
        >
          Login with Microsoft
        </button>
      </div>
    </div>
  {:else}
    <div style="max-width: 480px; margin: 0 auto; width: 100%; padding: 12px; display: flex; flex-direction: column; height: 100vh; overflow: hidden; gap: 8px;">
      <!-- Header -->
      <div style="text-align: center; margin-bottom: 8px;">
        <h1 style="margin: 0; font-size: 28px; font-weight: bold; color: #2563eb;">Job Info</h1>
      </div>

      <!-- Search Box -->
      <div style="position: relative;">
        <input
          bind:value={searchQuery}
          on:keyup={handleSearch}
          placeholder="Search jobs (start typing or use voice)"
          style="width: 100%; padding: 10px 40px 10px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; box-sizing: border-box;"
        />
        {#if hasVoicePermission}
          <button
            on:click={startVoiceSearch}
            disabled={isListening}
            style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; cursor: pointer; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; opacity: {isListening ? 0.6 : 1};"
            title="Voice search"
          >
            {#if isListening}
              <span style="color: #dc2626; font-size: 12px; animation: pulse 1s infinite;">●</span>
            {:else}
              <svg style="width: 20px; height: 20px; color: #6b7280;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"></path>
              </svg>
            {/if}
          </button>
        {/if}
      </div>

      <!-- Filters Toggle & Panel -->
      <div style="display: flex; gap: 8px;">
        <button
          on:click={toggleFiltersPanel}
          style="background: #2563eb; color: white; padding: 10px 16px; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; font-size: 14px;"
        >
          {showFiltersPanel ? 'Hide Filters' : 'Show Filters'}
        </button>
      </div>

      {#if showFiltersPanel}
        <div style="background: white; border: 1px solid #e5e7eb; border-radius: 6px; padding: 12px; max-height: 200px; overflow-y: auto;">
          <div style="margin-bottom: 12px;">
            <strong style="font-size: 13px; color: #374151;">Division</strong>
            <div style="display: flex; flex-direction: column; gap: 6px; margin-top: 6px;">
              {#each divisions as div}
                <label style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; color: #6b7280;">
                  <input
                    type="checkbox"
                    checked={filterDivisions.includes(div)}
                    on:change={() => toggleFilterCheckbox(filterDivisions, div)}
                    style="cursor: pointer;"
                  />
                  {div}
                </label>
              {/each}
            </div>
          </div>

          <div style="margin-bottom: 12px;">
            <strong style="font-size: 13px; color: #374151;">Active</strong>
            <div style="display: flex; flex-direction: column; gap: 6px; margin-top: 6px;">
              {#each activeOptions as opt}
                <label style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; color: #6b7280;">
                  <input
                    type="checkbox"
                    checked={filterActive.includes(opt)}
                    on:change={() => toggleFilterCheckbox(filterActive, opt)}
                    style="cursor: pointer;"
                  />
                  {opt}
                </label>
              {/each}
            </div>
          </div>

          <div style="margin-bottom: 12px;">
            <strong style="font-size: 13px; color: #374151;">Estimator</strong>
            <div style="display: flex; flex-direction: column; gap: 6px; margin-top: 6px;">
              {#each estimators as est}
                <label style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; color: #6b7280;">
                  <input
                    type="checkbox"
                    checked={filterEstimators.includes(est)}
                    on:change={() => toggleFilterCheckbox(filterEstimators, est)}
                    style="cursor: pointer;"
                  />
                  {est}
                </label>
              {/each}
            </div>
          </div>

          <div style="margin-bottom: 12px;">
            <strong style="font-size: 13px; color: #374151;">Status</strong>
            <div style="display: flex; flex-direction: column; gap: 6px; margin-top: 6px;">
              {#each statuses as stat}
                <label style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; color: #6b7280;">
                  <input
                    type="checkbox"
                    checked={filterStatuses.includes(stat)}
                    on:change={() => toggleFilterCheckbox(filterStatuses, stat)}
                    style="cursor: pointer;"
                  />
                  {stat}
                </label>
              {/each}
            </div>
          </div>

          <button
            on:click={clearFilters}
            style="background: #6b7280; color: white; border: none; padding: 8px 12px; border-radius: 4px; font-size: 13px; cursor: pointer; width: 100%;"
          >
            Clear Filters
          </button>
        </div>
      {/if}

      <!-- Error Display -->
      {#if error}
        <div style="background: #fee2e2; border: 1px solid #fca5a5; color: #991b1b; padding: 12px; border-radius: 6px; font-size: 13px;">
          {error}
        </div>
      {/if}

      <!-- Job Results -->
      <div style="flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; padding: 0;">
        {#if loading}
          <div style="text-align: center; padding: 20px; color: #9ca3af;">Loading jobs...</div>
        {:else if visibleJobs.length === 0}
          <div style="text-align: center; padding: 20px; color: #9ca3af;">No jobs found</div>
        {:else}
          {#each visibleJobs as job (job.id)}
            <button
              on:click={() => selectJob(job)}
              style="background: white; border: 1px solid #dbeafe; border-radius: 6px; padding: 12px; text-align: left; cursor: pointer; box-shadow: 0 1px 2px rgba(0,0,0,0.05); position: relative; transition: background 0.2s;"
            >
              <div style="display: flex; justify-content: space-between; gap: 8px; margin-bottom: 6px;">
                <strong style="font-size: 16px; color: #1f2937;">{job.Job_Number}</strong>
                <div
                  style="width: 12px; height: 12px; border-radius: 50%; border: 2px solid white; box-shadow: 0 0 2px rgba(0,0,0,0.1); background: {toBool(job.Active) === true ? '#22c55e' : toBool(job.Active) === false ? '#ef4444' : '#cbd5e1'};"
                ></div>
              </div>
              <div style="font-size: 14px; color: #374151; margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
                {job.Job_Full_Name}
              </div>
              <div style="font-size: 12px; color: #6b7280; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
                {job.Contact_Person}
              </div>
            </button>
          {/each}
        {/if}
      </div>

      <!-- Footer -->
      <div style="border-top: 1px solid #e5e7eb; padding-top: 8px; font-size: 11px; color: #9ca3af; text-align: center;">
        <div style="margin-bottom: 4px;">{authState.user?.email}</div>
        <button
          on:click={handleLogout}
          style="background: #f3f4f6; border: 1px solid #d1d5db; color: #374151; padding: 4px 8px; border-radius: 4px; font-size: 10px; cursor: pointer;"
        >
          Sign out
        </button>
      </div>
    </div>
  {/if}
</div>

<!-- Detail Modal -->
{#if showDetailModal && selectedJob}
  <div style="position: fixed; inset: 0; background: rgba(0, 0, 0, 0.4); display: flex; align-items: flex-start; justify-content: center; padding-top: 20px; z-index: 999; overflow-y: auto;">
    <div style="background: white; border-radius: 8px; width: 90%; max-width: 380px; max-height: 80vh; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 20px 25px rgba(0,0,0,0.15);">
      <!-- Tab Buttons -->
      <div style="display: flex; border-bottom: 1px solid #e5e7eb; background: #f9fafb;">
        <button
          on:click={() => { showNotesTab = false; showFolderContents = false; }}
          style="flex: 1; padding: 12px; border: none; background: {!showNotesTab && !showFolderContents ? '#2563eb' : '#f9fafb'}; color: {!showNotesTab && !showFolderContents ? 'white' : '#6b7280'}; font-weight: bold; cursor: pointer; font-size: 13px;"
        >
          INFO
        </button>
        <button
          on:click={() => { showFolderContents = false; showNotesTab = true; }}
          style="flex: 1; padding: 12px; border: none; background: {showNotesTab && !showFolderContents ? '#ea580c' : '#f9fafb'}; color: {showNotesTab && !showFolderContents ? 'white' : '#6b7280'}; font-weight: bold; cursor: pointer; font-size: 13px;"
        >
          NOTES
        </button>
      </div>

      {#if !showNotesTab && !showFolderContents}
        <!-- INFO Tab -->
        <div style="flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column;">
          <div style="flex: 1; overflow-y: auto; margin-bottom: 12px;">
            {#each Object.entries(selectedJob) as [key, value]}
              {#if key !== 'id' && value !== null && value !== undefined && !key.startsWith('collectionId') && key !== 'collectionName' && key !== 'Job_Folder_Link'}
                <div style="margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid #f3f4f6;">
                  <div style="font-size: 11px; font-weight: bold; color: #9ca3af; text-transform: uppercase; margin-bottom: 4px;">
                    {key}
                  </div>
                  <div style="font-size: 13px; color: #1f2937; word-break: break-word;">
                    {typeof value === 'object' ? JSON.stringify(value) : String(value)}
                  </div>
                </div>
              {/if}
            {/each}
          </div>

          {#if selectedJob.Job_Folder_Link}
            <button
              on:click={() => { showFolderContents = true; loadJobFiles(); }}
              style="width: 100%; padding: 12px; background: #059669; color: white; border: none; border-radius: 6px; font-weight: bold; text-align: center; cursor: pointer; font-size: 13px;"
            >
              📁 Show Folder
            </button>
          {/if}
        </div>
      {:else if showFolderContents}
        <!-- Folder Contents with Grouping -->
        <div style="flex: 1; overflow-y: auto; display: flex; flex-direction: column; padding: 12px;">
          {#if loadingFiles}
            <div style="text-align: center; padding: 20px; color: #9ca3af;">
              <div style="margin-bottom: 12px;">Loading files...</div>
            </div>
          {:else if filesError}
            <div style="background: #fee2e2; border: 1px solid #fca5a5; color: #991b1b; padding: 12px; border-radius: 6px; font-size: 13px; margin-bottom: 12px;">
              {filesError}
            </div>
            <button
              on:click={() => showFolderContents = false}
              style="width: 100%; padding: 10px; background: #6b7280; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; font-size: 12px;"
            >
              Back to Info
            </button>
          {:else if jobFiles.length === 0}
            <div style="text-align: center; padding: 20px; color: #9ca3af; margin-bottom: 12px;">No files found in folder</div>
            <button
              on:click={() => showFolderContents = false}
              style="width: 100%; padding: 10px; background: #6b7280; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; font-size: 12px;"
            >
              Back to Info
            </button>
          {:else}
            <!-- Search Filter -->
            <input
              bind:value={folderSearchFilter}
              placeholder="Search files..."
              style="width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 12px; margin-bottom: 12px; box-sizing: border-box;"
            />
            
            <!-- Grouped Files -->
            <div style="display: flex; flex-direction: column; gap: 12px; flex: 1; overflow-y: auto; margin-bottom: 12px;">
              {#each ['managerInfo', 'contracts', 'plans', 'submittals', 'other'] as category}
                {@const categoryFiles = jobFiles
                  .filter(f => !f.isFolder && categorizeFile(f.name) === category)
                  .filter(f => folderSearchFilter === '' || f.name.toLowerCase().includes(folderSearchFilter.toLowerCase()))
                }
                {@const categoryTitles = {
                  managerInfo: 'Manager Info',
                  contracts: 'Contracts / Estimates',
                  plans: 'Plans',
                  submittals: 'Submittals',
                  other: 'Other'
                }}
                {#if categoryFiles.length > 0}
                  <div>
                    <div style="font-weight: bold; color: #1f2937; font-size: 13px; margin-bottom: 6px;">
                      {categoryTitles[category]} ({categoryFiles.length})
                    </div>
                    <div style="border: 1px solid #e5e7eb; border-radius: 6px; overflow: hidden; background: white;">
                      {#each categoryFiles as file, idx (file.id || file.name)}
                        <button
                          on:click={() => openFileInViewer(file)}
                          style="width: 100%; padding: 10px 12px; text-align: left; border: none; background: white; cursor: pointer; border-bottom: {idx < categoryFiles.length - 1 ? '1px solid #e5e7eb' : 'none'}; transition: background 0.2s;"
                          on:mouseenter={(e) => e.target.style.background = '#f9fafb'}
                          on:mouseleave={(e) => e.target.style.background = 'white'}
                        >
                          <div style="display: flex; align-items: center; gap: 8px;">
                            <div style="flex-shrink: 0; font-size: 16px; color: #6b7280; display: flex; align-items: center; justify-content: center;">
                              {@html getSvgIcon(getFileIcon(file.name))}
                            </div>
                            <div style="flex: 1; min-width: 0;">
                              <div style="font-weight: 600; color: #1f2937; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
                                {file.name}
                              </div>
                              <div style="font-size: 11px; color: #6b7280; margin-top: 2px;">
                                {formatFileSize(file.size)}
                                {#if file.modified}
                                  • {formatDate(file.modified)}
                                {/if}
                              </div>
                            </div>
                          </div>
                        </button>
                      {/each}
                    </div>
                  </div>
                {/if}
              {/each}
            </div>
            
            <button
              on:click={() => showFolderContents = false}
              style="width: 100%; padding: 10px; background: #6b7280; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; font-size: 12px;"
            >
              Back to Info
            </button>
          {/if}
        </div>
      {:else}
        <!-- NOTES Tab -->
        <div style="flex: 1; overflow-y: auto; display: flex; flex-direction: column; padding: 12px;">
          <div
            style="min-height: 120px; max-height: 200px; overflow-y: auto; padding: 8px; border: 1px solid #d1d5db; border-radius: 4px; margin-bottom: 12px; font-size: 12px; line-height: 1.5; background: white; white-space: pre-wrap; word-break: break-word; color: #6b7280;"
          >
            {selectedJob.Notes || '(No notes)'}
          </div>

          <div style="margin-bottom: 12px;">
            <textarea
              bind:value={newNoteText}
              placeholder="Add a new note..."
              style="width: 100%; min-height: 80px; padding: 8px; border: 1px solid #d1d5db; border-radius: 4px; font-size: 12px; font-family: inherit; resize: vertical;"
            ></textarea>
          </div>

          <button
            on:click={saveNote}
            disabled={!newNoteText.trim()}
            style="background: #2563eb; color: white; border: none; padding: 10px; border-radius: 4px; font-weight: bold; cursor: pointer; opacity: {!newNoteText.trim() ? 0.5 : 1};"
          >
            Save Note
          </button>
        </div>
      {/if}

      <!-- Close Button -->
      <div style="padding: 12px; border-top: 1px solid #e5e7eb; background: #f9fafb;">
        <button
          on:click={() => (showDetailModal = false)}
          style="width: 100%; background: #ef4444; color: white; border: none; padding: 10px; border-radius: 4px; font-weight: bold; cursor: pointer;"
        >
          Close
        </button>
      </div>
    </div>
  </div>
{/if}

<!-- File Viewer Modal -->
{#if showFileViewer && currentViewerFile}
  <div style="position: fixed; inset: 0; background: #000; z-index: 1000; display: flex; flex-direction: column;">
    <!-- Header -->
    <div style="background: #1f2937; color: white; padding: 12px; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">
      <button
        on:click={closeFileViewer}
        style="background: #2563eb; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 12px; display: flex; align-items: center; gap: 6px;"
      >
        <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
        Back
      </button>
      <div style="flex: 1; text-center; padding: 0 12px; min-width: 0;">
        <div style="font-size: 13px; font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
          {currentViewerFile.name || 'File Preview'}
        </div>
      </div>
      <button
        on:click={closeFileViewer}
        style="background: #ef4444; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 12px; display: flex; align-items: center; gap: 6px;"
      >
        <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
        Close
      </button>
    </div>

    <!-- Viewer Content -->
    <div style="flex: 1; overflow: auto; background: #000; display: flex; align-items: center; justify-content: center; flex-direction: column;">
      {#if showPdfViewer && currentPdf}
        <!-- PDF/Image Controls -->
        <div style="background: #1f2937; color: white; width: 100%; padding: 8px 12px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid #374151; flex-shrink: 0;">
          <div style="display: flex; align-items: center; gap: 6px;">
            <button
              id="pdfZoomOutBtn"
              style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold;"
            >
              −
            </button>
            <button
              id="pdfZoomResetBtn"
              style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold;"
            >
              {Math.round(currentPdf.scale * 100)}%
            </button>
            <button
              id="pdfZoomInBtn"
              style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold;"
            >
              +
            </button>
            <span style="font-size: 11px; color: #d1d5db; margin-left: 6px;">
              {Math.round(currentPdf.scale * 100)}%
            </span>
          </div>
          {#if !currentPdf.isImage && currentPdf.pages > 1}
            <div style="display: flex; align-items: center; gap: 6px; margin-left: 12px;">
              <button style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">◀</button>
              <button style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">▶</button>
              <span style="font-size: 11px; color: #d1d5db;">Page <span>{currentPdf.page}</span> / <span>{currentPdf.pages}</span></span>
            </div>
          {/if}
        </div>
        
        <!-- Canvas Viewer -->
        <div style="flex: 1; overflow: hidden; background: #111; display: flex; align-items: center; justify-content: center; width: 100%;">
          <canvas
            id="pdfCanvas"
            style="max-width: 100%; max-height: 100%; object-fit: contain; background: white; cursor: grab; touch-action: none;"
          ></canvas>
        </div>
      {:else if currentViewerFile.url}
        <iframe
          src={currentViewerFile.url}
          style="width: 100%; height: 100%; border: none;"
          title={currentViewerFile.name}
          sandbox="allow-same-origin allow-scripts allow-popups allow-downloads"
        ></iframe>
      {:else}
        <div style="color: white; text-align: center;">
          <div style="font-size: 16px; margin-bottom: 12px;">Unable to preview file</div>
          <div style="font-size: 12px; color: #aaa;">No URL available</div>
        </div>
      {/if}
    </div>
  </div>
{/if}

<style>
  :global(body) {
    margin: 0;
    padding: 0;
  }
</style>

