import { promises as fs } from 'fs';
import path from 'path';
import pdf from 'pdf-parse';
import { fromPath } from 'pdf2pic';
import { v4 as uuidv4 } from 'uuid';
import { prisma } from '../shared/database/prisma.client';
import { logger } from '../shared/utils/logger';

export interface PDFProcessJob {
  filePath: string;
  fileName: string;
  type: 'TEXTBOOK' | 'PRACTICE' | 'PRACTICE_ANSWERS' | 'EXAM' | 'ADDITIONAL_MATERIAL';
  topicType?: 'VECTORES' | 'MATRICES' | 'SISTEMAS' | 'ESPACIOS_VECTORIALES' | 'PROGRAMACION_LINEAL';
}

export interface PDFProcessJobResult {
  success: boolean;
  fileName: string;
  processedPdfId?: string;
  pagesProcessed?: number;
  exercisesDetected?: number;
  formulasExtracted?: number;
  processingTime: number;
  error?: string;
}

export interface PDFProcessEvent {
  event: 'started' | 'progress' | 'completed' | 'error';
  fileName: string;
  message: string;
  data?: any;
}

export interface ParsedFormula {
  id: string;
  type: 'inline' | 'display' | 'numbered' | 'matrix' | 'determinant' | 'vector';
  latex: string;
  content: string;
  position: {
    page: number;
    line?: number;
    startIndex: number;
    endIndex: number;
  };
  confidence: number;
  metadata?: {
    label?: string;
    number?: number;
    dependencies?: string[];
  };
}

export interface DetectedExercise {
  id: string;
  number: string;
  type: 'problem' | 'example' | 'question' | 'proof' | 'application';
  category: string | undefined;
  title: string | undefined;
  statement: string;
  formulas: ParsedFormula[];
  hints: string[] | undefined;
  answer: string | undefined;
  difficulty: 'basic' | 'intermediate' | 'advanced' | undefined;
  tags: string[];
  position: {
    page: number;
    startIndex: number;
    endIndex: number;
  };
}

const PDF_BASE_PATH = process.env.PDF_BASE_PATH || '/app/pdfs';
const THUMBNAIL_PATH = process.env.THUMBNAIL_PATH || '/app/uploads/thumbnails';
const PROCESSING_VERSION = '1.0.0';

export class PDFProcessorWorker {
  private isProcessing = false;
  private currentJob: PDFProcessJob | null = null;

  constructor() {
    this.ensureDirectories();
  }

  private async ensureDirectories(): Promise<void> {
    try {
      await fs.mkdir(PDF_BASE_PATH, { recursive: true });
      await fs.mkdir(THUMBNAIL_PATH, { recursive: true });
    } catch (error) {
      logger.error({ error }, 'Failed to create necessary directories');
    }
  }

  async processJob(job: PDFProcessJob): Promise<PDFProcessJobResult> {
    const startTime = Date.now();
    this.currentJob = job;
    this.isProcessing = true;

    logger.info({ fileName: job.fileName }, 'Starting PDF processing job');

    try {
      const pdfPath = path.join(PDF_BASE_PATH, job.filePath);
      const fileBuffer = await fs.readFile(pdfPath);
      const pdfDoc = await pdf(fileBuffer);

      await this.updateProcessingStatus(job.fileName, true, null);

      const textByPage = await this.extractTextByPage(pdfDoc);
      const fullText = textByPage.join('\n\n--- Page Break ---\n\n');

      const formulas = this.extractFormulas(fullText, textByPage);
      const exercises = this.detectExercises(fullText, textByPage, formulas);

      const thumbnailPaths = await this.generateThumbnails(job.fileName, fileBuffer, pdfDoc.numpages);

      const checksum = await this.calculateChecksum(fileBuffer);

      const extractedContent = {
        pages: textByPage.map((text, idx) => ({
          page: idx + 1,
          text,
          tables: [],
          images: []
        }))
      };

      const processedPdf = await prisma.processed_pdfs.upsert({
        where: { file_name: job.fileName },
        update: {
          is_processed: true,
          processing_completed_at: new Date(),
          errorMessage: null,
          extractedText: extractedContent,
          exercisesDetected: exercises.map(ex => ({
            page: ex.position.page,
            exerciseNumber: ex.number,
            content: ex.statement,
            type: ex.type,
            difficulty: ex.difficulty,
            tags: ex.tags
          })),
          formulasExtracted: formulas.map(f => ({
            latex: f.latex,
            position: f.position,
            context: f.content,
            formulaType: f.type
          })),
          metadata: {
            author: pdfDoc.info?.Author,
            pages: pdfDoc.numpages,
            title: pdfDoc.info?.Title,
            creationDate: pdfDoc.info?.CreationDate,
            thumbnails: thumbnailPaths
          },
          totalPages: pdfDoc.numpages,
          processingVersion: PROCESSING_VERSION,
          checksum
        },
        create: {
          id: crypto.randomUUID(),
          file_name: job.fileName,
          original_path: job.filePath,
          type: job.type,
          topicType: job.topicType,
          is_processed: true,
          processing_started_at: new Date(),
          processing_completed_at: new Date(),
          extractedText: extractedContent,
          exercisesDetected: exercises.map(ex => ({
            page: ex.position.page,
            exerciseNumber: ex.number,
            content: ex.statement,
            type: ex.type,
            difficulty: ex.difficulty,
            tags: ex.tags
          })),
          formulasExtracted: formulas.map(f => ({
            latex: f.latex,
            position: f.position,
            context: f.content,
            formulaType: f.type
          })),
          metadata: {
            author: pdfDoc.info?.Author,
            pages: pdfDoc.numpages,
            title: pdfDoc.info?.Title,
            creationDate: pdfDoc.info?.CreationDate,
            thumbnails: thumbnailPaths
          },
          totalPages: pdfDoc.numpages,
          processingVersion: PROCESSING_VERSION,
          checksum
        }
      });

      const processingTime = Date.now() - startTime;

      logger.info({
        fileName: job.fileName,
        pagesProcessed: pdfDoc.numpages,
        exercisesDetected: exercises.length,
        formulasExtracted: formulas.length,
        processingTime
      }, 'PDF processing completed successfully');

      this.isProcessing = false;
      this.currentJob = null;

      return {
        success: true,
        fileName: job.fileName,
        processedPdfId: processedPdf.id,
        pagesProcessed: pdfDoc.numpages,
        exercisesDetected: exercises.length,
        formulasExtracted: formulas.length,
        processingTime
      };

    } catch (error) {
      const processingTime = Date.now() - startTime;
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';

      logger.error({ fileName: job.fileName, error: errorMessage }, 'PDF processing failed');

      await this.updateProcessingStatus(job.fileName, false, errorMessage);

      this.isProcessing = false;
      this.currentJob = null;

      return {
        success: false,
        fileName: job.fileName,
        processingTime,
        error: errorMessage
      };
    }
  }

  private async extractTextByPage(pdfDoc: any): Promise<string[]> {
    const pages: string[] = [];

    // Use pdf-parse's pagerender callback for real page-by-page extraction
    // This properly extracts text from each page instead of dividing by character count
    const renderPage = (pageData: any) => {
      const renderOptions = {
        // Normalize whitespace for cleaner text
        normalizeWhitespace: true,
        // Remove duplicate whitespace
        disableCombineTextItems: false,
      };

      return pageData.getTextContent(renderOptions)
        .then((textContent: any) => {
          const textItems = textContent.items;
          let pageText = '';

          // Concatenate all text items for this page
          for (const item of textItems) {
            if (item.str) {
              pageText += item.str;
              // Add space or line break based on transform
              if (item.hasEOL) {
                pageText += '\n';
              } else {
                pageText += ' ';
              }
            }
          }

          pageText = this.normalizeWhitespace(pageText);
          pages.push(pageText.trim());
          return '';
        })
        .catch(() => {
          // Fallback: push empty string for failed pages
          pages.push('');
          return '';
        });
    };

    try {
      // Re-parse the PDF with the pagerender option
      // pdf-parse accepts pagerender in its options
      const pdfBuffer = pdfDoc._pdfBuffer || Buffer.from('');
      if (pdfBuffer.length > 0) {
        await pdf(pdfBuffer, { pagerender: renderPage });
      }
    } catch (error) {
      // If pagerender fails, fall back to character-based division (legacy behavior)
      logger.warn({ error }, 'Pagerender extraction failed, falling back to character division');

      const totalPages = pdfDoc.numpages;
      const totalText = pdfDoc.text || '';

      if (!totalText || totalText.length === 0) {
        return pages;
      }

      const avgCharsPerPage = Math.floor(totalText.length / totalPages);

      for (let i = 0; i < totalPages; i++) {
        const startPos = i * avgCharsPerPage;
        const endPos = (i + 1) * avgCharsPerPage;
        let pageText = totalText.substring(startPos, endPos);
        pageText = this.normalizeWhitespace(pageText);
        pages.push(pageText.trim());
      }
    }

    return pages.filter(page => page.length > 0);
  }

  private normalizeWhitespace(text: string): string {
    return text
      .replace(/[ \t]+/g, ' ')
      .replace(/\n{3,}/g, '\n\n')
      .split('\n')
      .map(line => line.trim())
      .filter(line => line.length > 0)
      .join('\n');
  }

  private extractFormulas(_text: string, textByPage: string[]): ParsedFormula[] {
    const formulas: ParsedFormula[] = [];

    const mathPatterns = [
      { regex: /\(([0-9]+)\)\s*(.+?)(?=\n|$)/g, type: 'numbered' as const },
      { regex: /\[([0-9]+\.[0-9]+)\]\s*(.+?)(?=\n|$)/g, type: 'numbered' as const },
      { regex: /\\begin\{bmatrix\}([\s\S]*?)\\end\{bmatrix\}/g, type: 'matrix' as const },
      { regex: /\\begin\{pmatrix\}([\s\S]*?)\\end\{pmatrix\}/g, type: 'matrix' as const },
      { regex: /\\vec\{([^}]+)\}/g, type: 'vector' as const },
      { regex: /\\mathbf\{([^}]+)\}/g, type: 'vector' as const },
      { regex: /\|[^|]+\|/g, type: 'determinant' as const },
      { regex: /[a-z]\s*=\s*[^,.;]+/gi, type: 'inline' as const },
      { regex: /\([^)]*\)\s*[=<>]\s*[^,.;]+/g, type: 'inline' as const },
      { regex: /[0-9]+\s*[\^_]\s*[a-z0-9]+/gi, type: 'inline' as const },
      { regex: /\\[a-z]+\{[^}]*\}/g, type: 'inline' as const }
    ];

    const greekLetters: Record<string, string> = {
      'α': '\\alpha', 'β': '\\beta', 'γ': '\\gamma', 'δ': '\\delta',
      'ε': '\\epsilon', 'θ': '\\theta', 'λ': '\\lambda', 'μ': '\\mu',
      'π': '\\pi', 'σ': '\\sigma', 'φ': '\\phi', 'ω': '\\omega',
      'Δ': '\\Delta', 'Σ': '\\Sigma', 'Π': '\\Pi', 'Ω': '\\Omega'
    };

    for (let pageIdx = 0; pageIdx < textByPage.length; pageIdx++) {
      const pageText = textByPage[pageIdx];
      if (!pageText) continue;
      const lines = pageText.split('\n');

      for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
        const line = lines[lineIdx];
        if (!line) continue;

        for (const pattern of mathPatterns) {
          let match;
          const regex = new RegExp(pattern.regex.source, pattern.regex.flags);

          while ((match = regex.exec(line)) !== null) {
            let latex = match[0];

            for (const [greek, replacement] of Object.entries(greekLetters)) {
              latex = latex.replace(new RegExp(greek, 'g'), replacement);
            }

            latex = this.convertToLaTeX(latex);

            formulas.push({
              id: uuidv4(),
              type: pattern.type,
              latex,
              content: match[0],
              position: {
                page: pageIdx + 1,
                line: lineIdx + 1,
                startIndex: match.index,
                endIndex: match.index + match[0].length
              },
              confidence: pattern.type === 'numbered' ? 0.95 : pattern.type === 'matrix' ? 0.9 : 0.75
            });
          }
        }
      }
    }

    return this.deduplicateFormulas(formulas);
  }

  private convertToLaTeX(text: string): string {
    let latex = text;

    const replacements: [RegExp, string][] = [
      [/≤/g, '\\leq'],
      [/≥/g, '\\geq'],
      [/≠/g, '\\neq'],
      [/≈/g, '\\approx'],
      [/±/g, '\\pm'],
      [/×/g, '\\times'],
      [/÷/g, '\\div'],
      [/·/g, '\\cdot'],
      [/→/g, '\\rightarrow'],
      [/←/g, '\\leftarrow'],
      [/∞/g, '\\infty'],
      [/√/g, '\\sqrt'],
      [/∑/g, '\\sum'],
      [/∫/g, '\\int'],
      [/∈/g, '\\in'],
      [/∉/g, '\\notin'],
      [/⊂/g, '\\subset'],
      [/⊃/g, '\\supset'],
      [/∪/g, '\\cup'],
      [/∩/g, '\\cap'],
      [/∅/g, '\\emptyset'],
      [/∂/g, '\\partial'],
      [/∇/g, '\\nabla']
    ];

    for (const [pattern, replacement] of replacements) {
      latex = latex.replace(pattern, replacement);
    }

    latex = latex.replace(/_(\w+)/g, '_{$1}');
    latex = latex.replace(/\^(\w+)/g, '^{$1}');
    latex = latex.replace(/(\d+)\s*\/\s*(\d+)/g, '\\frac{$1}{$2}');
    latex = latex.replace(/√\s*(\w+)/g, '\\sqrt{$1}');

    return latex.trim();
  }

  private deduplicateFormulas(formulas: ParsedFormula[]): ParsedFormula[] {
    const seen = new Map<string, ParsedFormula>();

    for (const formula of formulas) {
      const key = `${formula.position.page}-${formula.content}`;
      const existing = seen.get(key);

      if (!existing || formula.confidence > existing.confidence) {
        seen.set(key, formula);
      }
    }

    return Array.from(seen.values()).sort((a, b) => {
      if (a.position.page !== b.position.page) {
        return a.position.page - b.position.page;
      }
      return a.position.startIndex - b.position.startIndex;
    });
  }

  private detectExercises(text: string, textByPage: string[], formulas: ParsedFormula[]): DetectedExercise[] {
    const exercises: DetectedExercise[] = [];

    const exercisePatterns = [
      { regex: /^(?:Ejercicio|Ejer)\s+(\d+)[.:]\s*(.+?)(?=\nEjercicio|\n$)/gim, type: 'problem' as const, language: 'es' },
      { regex: /^(?:Problema|Prob)\s+(\d+)[.:]\s*(.+?)(?=\nProblema|\n$)/gim, type: 'problem' as const, language: 'es' },
      { regex: /^(?:Ejemplo)\s+(\d+)[.:]\s*(.+?)(?=\nEjemplo|\n$)/gim, type: 'example' as const, language: 'es' },
      { regex: /^(?:Práctica)\s+(\d+)[.:]\s*(.+?)(?=\nPráctica|\n$)/gim, type: 'problem' as const, language: 'es' },
      { regex: /^(?:Exercise|Ex)\s+(\d+)[.:]\s*(.+?)(?=\nExercise|\n$)/gim, type: 'problem' as const, language: 'en' },
      { regex: /^(?:Problem)\s+(\d+)[.:]\s*(.+?)(?=\nProblem|\n$)/gim, type: 'problem' as const, language: 'en' },
      { regex: /^(\d+)[.)]\s+(.+?)(?=\n\d+[.)]|\n$)/g, type: 'problem' as const, language: 'mixed' },
      { regex: /^([a-z])[.)]\s+(.+?)(?=\n[a-z][).]|\n$)/g, type: 'problem' as const, language: 'mixed' }
    ];

    const solutionPatterns = [
      /^(?:Solución|Solution|SOLUCIÓN)\s*[:\.-]/gim,
      /^(?:Respuesta|Answer|RESPUESTA)\s*[:\.-]/gim,
      /^(?:Resolución|Resolution)\s*[:\.-]/gim
    ];

    const difficultyKeywords = {
      basic: /\b(básico|elemental|introductorio|fácil|basic|elementary|introductory|easy)\b/gi,
      intermediate: /\b(intermedio|medio|moderado|intermediate|medium|moderate)\b/gi,
      advanced: /\b(avanzado|complejo|difícil|desafiante|advanced|complex|difficult|challenging)\b/gi
    };

    const topicKeywords = {
      vectores: /\b(vector|vectores|producto\s+(?:escalar|cruz|punto)|norma|magnitud)\b/gi,
      matrices: /\b(matriz|matrices|determinante|inversa|transpuesta|traza|rank)\b/gi,
      sistemas: /\b(sistema|ecuaciones|incógnitas|gauss|jordan|compatible)\b/gi,
      espacios: /\b(espacio\s+vectorial|subespacio|base|dimensión|span)\b/gi,
      programacionLineal: /\b(programación\s+lineal|optimización|restricción|simplex)\b/gi
    };

    for (let pageIdx = 0; pageIdx < textByPage.length; pageIdx++) {
      const pageText = textByPage[pageIdx];
      if (!pageText) continue;
      const lines = pageText.split('\n');

      let currentExercise: Partial<DetectedExercise> | null = null;
      let currentStatement: string[] = [];
      let inSolution = false;
      let lineIndex = 0;

      while (lineIndex < lines.length) {
        const line = lines[lineIndex]?.trim();
        if (line === undefined) break;

        let matchedPattern = null;
        for (const pattern of exercisePatterns) {
          const match = line.match(pattern.regex);
          if (match) {
            if (currentExercise && currentStatement.length > 0) {
              exercises.push(this.finalizeExercise(currentExercise, currentStatement, pageIdx + 1, lineIndex, formulas));
            }

            currentExercise = {
              id: uuidv4(),
              number: match[1] ?? '?',
              type: pattern.type,
              category: undefined,
              title: undefined,
              statement: '',
              formulas: [],
              hints: undefined,
              answer: undefined,
              difficulty: undefined,
              tags: [],
              position: {
                page: pageIdx + 1,
                startIndex: lineIndex,
                endIndex: lineIndex
              }
            };
            currentStatement = [match[2] || line];
            inSolution = false;
            matchedPattern = true;
            break;
          }
        }

        if (!matchedPattern && currentExercise) {
          let isSolution = false;
          for (const solPattern of solutionPatterns) {
            if (solPattern.test(line)) {
              isSolution = true;
              inSolution = true;
              if (!currentExercise.answer) {
                currentExercise.answer = '';
              }
              break;
            }
          }

          if (isSolution) {
            currentExercise.answer = (currentExercise.answer || '') + '\n' + line;
          } else if (inSolution) {
            currentExercise.answer = (currentExercise.answer || '') + '\n' + line;
          } else {
            currentStatement.push(line);
            if (currentExercise.position) {
              currentExercise.position.endIndex = lineIndex;
            }
          }
        }

        lineIndex++;
      }

      if (currentExercise && currentStatement.length > 0) {
        exercises.push(this.finalizeExercise(currentExercise, currentStatement, pageIdx + 1, lineIndex, formulas));
      }
    }

    return this.postProcessExercises(exercises, text, difficultyKeywords, topicKeywords);
  }

  private finalizeExercise(
    exercise: Partial<DetectedExercise>,
    statementLines: string[],
    pageNumber: number,
    lineIndex: number,
    allFormulas: ParsedFormula[]
  ): DetectedExercise {
    const statement = statementLines.join('\n').trim();

    const exerciseFormulas = allFormulas.filter(f =>
      f.position.page === pageNumber &&
      f.position.line !== undefined &&
      f.position.line >= exercise.position!.startIndex &&
      f.position.line <= lineIndex
    );

    return {
      id: exercise.id || uuidv4(),
      number: exercise.number || '?',
      type: exercise.type || 'problem',
      category: exercise.category,
      title: exercise.title,
      statement,
      formulas: exerciseFormulas,
      hints: exercise.hints,
      answer: exercise.answer,
      difficulty: exercise.difficulty,
      tags: [],
      position: exercise.position!
    };
  }

  private postProcessExercises(
    exercises: DetectedExercise[],
    _fullText: string,
    difficultyKeywords: Record<string, RegExp>,
    topicKeywords: Record<string, RegExp>
  ): DetectedExercise[] {
    const uniqueMap = new Map<string, DetectedExercise>();

    for (const exercise of exercises) {
      const key = `${exercise.position.page}-${exercise.number}`;
      const existing = uniqueMap.get(key);

      if (!existing || exercise.statement.length > existing.statement.length) {
        const difficulty = this.detectDifficulty(exercise.statement, difficultyKeywords);
        const tags = this.detectTags(exercise.statement, topicKeywords);

        uniqueMap.set(key, { ...exercise, difficulty, tags });
      }
    }

    return Array.from(uniqueMap.values()).sort((a, b) => {
      if (a.position.page !== b.position.page) {
        return a.position.page - b.position.page;
      }
      return a.position.startIndex - b.position.startIndex;
    });
  }

  private detectDifficulty(statement: string, keywords: Record<string, RegExp>): 'basic' | 'intermediate' | 'advanced' | undefined {
    for (const [level, pattern] of Object.entries(keywords)) {
      if (pattern.test(statement)) {
        return level as 'basic' | 'intermediate' | 'advanced';
      }
    }
    return undefined;
  }

  private detectTags(statement: string, topicKeywords: Record<string, RegExp>): string[] {
    const tags: string[] = [];

    for (const [topic, pattern] of Object.entries(topicKeywords)) {
      if (pattern.test(statement)) {
        tags.push(topic);
      }
    }

    return tags;
  }

  private async generateThumbnails(fileName: string, _pdfBuffer: Buffer, pageCount: number): Promise<string[]> {
    const thumbnailPaths: string[] = [];

    try {
      const thumbnailDir = path.join(THUMBNAIL_PATH, fileName.replace('.pdf', ''));
      await fs.mkdir(thumbnailDir, { recursive: true });

      const convert = fromPath(path.join(PDF_BASE_PATH, fileName), {
        density: 100,
        saveFilename: 'page',
        savePath: thumbnailDir,
        format: 'png',
        width: 400,
        height: 600
      });

      for (let i = 1; i <= Math.min(pageCount, 10); i++) {
        try {
          const result = await convert(i);
          if (result && result.path) {
            thumbnailPaths.push(result.path);
          }
        } catch (pageError) {
          logger.warn({ page: i, fileName }, 'Failed to generate thumbnail for page');
        }
      }

      logger.info({ fileName, thumbnailCount: thumbnailPaths.length }, 'Thumbnail generation completed');
    } catch (error) {
      logger.warn({ error, fileName }, 'Thumbnail generation failed');
    }

    return thumbnailPaths;
  }

  private async calculateChecksum(buffer: Buffer): Promise<string> {
    const crypto = await import('crypto');
    return crypto.createHash('sha256').update(buffer).digest('hex');
  }

  private async updateProcessingStatus(fileName: string, isProcessed: boolean, errorMessage: string | null): Promise<void> {
    try {
      await prisma.processed_pdfs.upsert({
        where: { file_name: fileName },
        update: {
          is_processed: isProcessed,
          processing_started_at: new Date(),
          processing_completed_at: isProcessed ? new Date() : null,
          errorMessage
        },
        create: {
          id: crypto.randomUUID(),
          file_name: fileName,
          original_path: path.join(PDF_BASE_PATH, fileName),
          type: 'TEXTBOOK',
          is_processed: isProcessed,
          processing_started_at: new Date(),
          processing_completed_at: isProcessed ? new Date() : null,
          errorMessage
        }
      });
    } catch (error) {
      logger.error({ error, fileName }, 'Failed to update processing status');
    }
  }

  async processAll(): Promise<PDFProcessJobResult[]> {
    const results: PDFProcessJobResult[] = [];

    try {
      const files = await fs.readdir(PDF_BASE_PATH);
      const pdfFiles = files.filter(f => f.toLowerCase().endsWith('.pdf'));

      logger.info({ count: pdfFiles.length }, 'Found PDFs to process');

      for (const file of pdfFiles) {
        const job: PDFProcessJob = {
          filePath: file,
          fileName: file,
          type: 'TEXTBOOK'
        };

        const result = await this.processJob(job);
        results.push(result);

        await new Promise(resolve => setTimeout(resolve, 100));
      }
    } catch (error) {
      logger.error({ error }, 'Failed to process PDFs directory');
    }

    return results;
  }

  async getStatus(): Promise<{ isProcessing: boolean; currentJob: PDFProcessJob | null }> {
    return {
      isProcessing: this.isProcessing,
      currentJob: this.currentJob
    };
  }

  async getProcessedPdfs(): Promise<any[]> {
    return await prisma.processed_pdfs.findMany({
      orderBy: { updatedAt: 'desc' }
    });
  }
}

export default PDFProcessorWorker;
