var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};

// server/wordProcessor.ts
var wordProcessor_exports = {};
__export(wordProcessor_exports, {
  WordProcessor: () => WordProcessor
});
import mammoth2 from "mammoth";
import { Document, Packer, Paragraph, TextRun, Header, AlignmentType, HeadingLevel } from "docx";
var WordProcessor;
var init_wordProcessor = __esm({
  "server/wordProcessor.ts"() {
    "use strict";
    WordProcessor = class {
      // Import Word document (.docx) to HTML/text
      static async importDocx(buffer) {
        try {
          const htmlResult = await mammoth2.convertToHtml(buffer, {
            convertImage: mammoth2.images.imgElement(function(image) {
              return image.read("base64").then(function(imageBuffer) {
                return {
                  src: "data:" + image.contentType + ";base64," + imageBuffer
                };
              });
            })
          });
          const textResult = await mammoth2.extractRawText(buffer);
          const titleMatch = htmlResult.value.match(/<h[1-6][^>]*>(.*?)<\/h[1-6]>/i);
          const title = titleMatch ? titleMatch[1].replace(/<[^>]*>/g, "").trim() : textResult.value.split("\n")[0].trim().substring(0, 50);
          return {
            html: htmlResult.value,
            text: textResult.value,
            title: title || "Imported Document"
          };
        } catch (error) {
          throw new Error(`Failed to import Word document: ${error.message}`);
        }
      }
      // Export HTML/text content to Word document (.docx)
      static async exportDocx(content, title = "Document", isHtml = true) {
        try {
          const children = [];
          if (isHtml) {
            children.push(...this.parseHtmlToDocx(content, title));
          } else {
            const paragraphs = content.split("\n\n");
            paragraphs.forEach((paragraph, index2) => {
              if (paragraph.trim()) {
                if (index2 === 0 && title) {
                  children.push(
                    new Paragraph({
                      children: [new TextRun({ text: title, bold: true, size: 32 })],
                      heading: HeadingLevel.TITLE,
                      alignment: AlignmentType.CENTER
                    })
                  );
                }
                children.push(
                  new Paragraph({
                    children: [new TextRun({ text: paragraph.trim() })]
                  })
                );
              }
            });
          }
          const doc = new Document({
            sections: [{
              properties: {},
              headers: {
                default: new Header({
                  children: [
                    new Paragraph({
                      children: [new TextRun({ text: title })],
                      alignment: AlignmentType.CENTER
                    })
                  ]
                })
              },
              children
            }]
          });
          return await Packer.toBuffer(doc);
        } catch (error) {
          throw new Error(`Failed to export Word document: ${error.message}`);
        }
      }
      // Parse HTML content to docx elements
      static parseHtmlToDocx(html, title) {
        const children = [];
        if (title) {
          children.push(
            new Paragraph({
              children: [new TextRun({ text: title, bold: true, size: 32 })],
              heading: HeadingLevel.TITLE,
              alignment: AlignmentType.CENTER
            })
          );
        }
        const content = html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<p[^>]*>/gi, "").replace(/<\/div>/gi, "\n").replace(/<div[^>]*>/gi, "");
        const headingRegex = /<h([1-6])[^>]*>(.*?)<\/h[1-6]>/gi;
        let processedContent = content;
        let match;
        while ((match = headingRegex.exec(content)) !== null) {
          const level = parseInt(match[1]);
          const text2 = match[2].replace(/<[^>]*>/g, "").trim();
          let headingLevel;
          switch (level) {
            case 1:
              headingLevel = HeadingLevel.HEADING_1;
              break;
            case 2:
              headingLevel = HeadingLevel.HEADING_2;
              break;
            case 3:
              headingLevel = HeadingLevel.HEADING_3;
              break;
            case 4:
              headingLevel = HeadingLevel.HEADING_4;
              break;
            case 5:
              headingLevel = HeadingLevel.HEADING_5;
              break;
            default:
              headingLevel = HeadingLevel.HEADING_6;
              break;
          }
          children.push(
            new Paragraph({
              children: [new TextRun({ text: text2, bold: true, size: 24 - level * 2 })],
              heading: headingLevel
            })
          );
          processedContent = processedContent.replace(match[0], "");
        }
        processedContent = processedContent.replace(/<strong[^>]*>(.*?)<\/strong>/gi, "**$1**").replace(/<b[^>]*>(.*?)<\/b>/gi, "**$1**").replace(/<em[^>]*>(.*?)<\/em>/gi, "*$1*").replace(/<i[^>]*>(.*?)<\/i>/gi, "*$1*");
        const listRegex = /<ul[^>]*>(.*?)<\/ul>/gis;
        while ((match = listRegex.exec(processedContent)) !== null) {
          const listItems = match[1].match(/<li[^>]*>(.*?)<\/li>/gi);
          if (listItems) {
            listItems.forEach((item) => {
              const text2 = item.replace(/<[^>]*>/g, "").trim();
              children.push(
                new Paragraph({
                  children: [new TextRun({ text: `\u2022 ${text2}` })]
                })
              );
            });
          }
          processedContent = processedContent.replace(match[0], "");
        }
        const paragraphs = processedContent.split("\n\n");
        paragraphs.forEach((paragraph) => {
          const text2 = paragraph.replace(/<[^>]*>/g, "").trim();
          if (text2) {
            const runs = [];
            const parts = text2.split(/(\*\*.*?\*\*|\*.*?\*)/);
            parts.forEach((part) => {
              if (part.startsWith("**") && part.endsWith("**")) {
                runs.push(new TextRun({ text: part.slice(2, -2), bold: true }));
              } else if (part.startsWith("*") && part.endsWith("*")) {
                runs.push(new TextRun({ text: part.slice(1, -1), italics: true }));
              } else if (part.trim()) {
                runs.push(new TextRun({ text: part }));
              }
            });
            if (runs.length > 0) {
              children.push(new Paragraph({ children: runs }));
            }
          }
        });
        return children;
      }
      // Convert LaTeX content to Word-compatible format
      static async exportLatexToDocx(latexContent, title = "Document") {
        const htmlContent = this.convertLatexToHtml(latexContent);
        return this.exportDocx(htmlContent, title, true);
      }
      static convertLatexToHtml(latex) {
        return latex.replace(/\\textbf\{(.*?)\}/g, "<strong>$1</strong>").replace(/\\textit\{(.*?)\}/g, "<em>$1</em>").replace(/\\underline\{(.*?)\}/g, "<u>$1</u>").replace(/\\section\{(.*?)\}/g, "<h1>$1</h1>").replace(/\\subsection\{(.*?)\}/g, "<h2>$1</h2>").replace(/\\subsubsection\{(.*?)\}/g, "<h3>$1</h3>").replace(/\\begin\{itemize\}(.*?)\\end\{itemize\}/gs, (match, content) => {
          const items = content.replace(/\\item\s*/g, "<li>").replace(/\n\s*(?=<li>)/g, "</li>\n");
          return `<ul>${items}</li></ul>`;
        }).replace(/\\begin\{enumerate\}(.*?)\\end\{enumerate\}/gs, (match, content) => {
          const items = content.replace(/\\item\s*/g, "<li>").replace(/\n\s*(?=<li>)/g, "</li>\n");
          return `<ol>${items}</li></ol>`;
        }).replace(/\\begin\{quote\}(.*?)\\end\{quote\}/gs, "<blockquote>$1</blockquote>").replace(/\\texttt\{(.*?)\}/g, "<code>$1</code>").replace(/\\par\s*/g, "</p><p>").replace(/\\\\(\s*)/g, "<br>").replace(/\\documentclass.*?\n/g, "").replace(/\\usepackage.*?\n/g, "").replace(/\\begin\{document\}/g, "<p>").replace(/\\end\{document\}/g, "</p>").replace(/\\maketitle/g, "").replace(/\\title\{(.*?)\}/g, "").replace(/\\author\{.*?\}/g, "").replace(/\\date\{.*?\}/g, "").trim();
      }
    };
  }
});

// server/index.ts
import express3 from "express";

// server/routes.ts
import { createServer } from "http";

// shared/schema.ts
var schema_exports = {};
__export(schema_exports, {
  aiSuggestions: () => aiSuggestions,
  aiSuggestionsRelations: () => aiSuggestionsRelations,
  citations: () => citations,
  citationsRelations: () => citationsRelations,
  documents: () => documents,
  documentsRelations: () => documentsRelations,
  insertAiSuggestionSchema: () => insertAiSuggestionSchema,
  insertCitationSchema: () => insertCitationSchema,
  insertDocumentSchema: () => insertDocumentSchema,
  insertPlagiarismCheckSchema: () => insertPlagiarismCheckSchema,
  plagiarismChecks: () => plagiarismChecks,
  plagiarismChecksRelations: () => plagiarismChecksRelations,
  sessions: () => sessions,
  users: () => users,
  usersRelations: () => usersRelations
});
import {
  pgTable,
  text,
  varchar,
  timestamp,
  jsonb,
  index,
  serial,
  integer,
  boolean
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { relations } from "drizzle-orm";
var sessions = pgTable(
  "sessions",
  {
    sid: varchar("sid").primaryKey(),
    sess: jsonb("sess").notNull(),
    expire: timestamp("expire").notNull()
  },
  (table) => [index("IDX_session_expire").on(table.expire)]
);
var users = pgTable("users", {
  id: serial("id").primaryKey(),
  email: varchar("email").unique(),
  firstName: varchar("first_name"),
  lastName: varchar("last_name"),
  profileImageUrl: varchar("profile_image_url"),
  themePreference: varchar("theme_preference").default("light"),
  languagePreference: varchar("language_preference").default("en"),
  socialLogins: jsonb("social_logins").default({}),
  // Store Google, Facebook IDs
  // Subscription fields
  subscriptionStatus: varchar("subscription_status").default("free"),
  // free, paid, expired
  subscriptionPeriodStart: timestamp("subscription_period_start"),
  subscriptionPeriodEnd: timestamp("subscription_period_end"),
  requestsUsed: integer("requests_used").default(0),
  requestsLimit: integer("requests_limit").default(5),
  // 5 for free, 500 for paid
  stripeCustomerId: varchar("stripe_customer_id"),
  stripeSubscriptionId: varchar("stripe_subscription_id"),
  googleId: text("google_id").unique(),
  facebookId: text("facebook_id").unique(),
  username: text("username").notNull().unique(),
  createdAt: timestamp("created_at").defaultNow(),
  updatedAt: timestamp("updated_at").defaultNow(),
  lastLogin: timestamp("last_login").defaultNow(),
  isEmailVerified: boolean("is_email_verified").default(false),
  password: text("password")
  // Store hashed password
});
var documents = pgTable("documents", {
  id: serial("id").primaryKey(),
  userId: integer("user_id").notNull(),
  title: varchar("title").notNull(),
  content: text("content"),
  wordCount: integer("word_count").default(0),
  documentType: varchar("document_type").default("article"),
  // thesis, article, draft
  createdAt: timestamp("created_at").defaultNow(),
  updatedAt: timestamp("updated_at").defaultNow()
});
var citations = pgTable("citations", {
  id: serial("id").primaryKey(),
  userId: integer("user_id").notNull(),
  documentId: integer("document_id"),
  title: varchar("title").notNull(),
  authors: text("authors").notNull(),
  publication: varchar("publication"),
  year: varchar("year"),
  pages: varchar("pages"),
  doi: varchar("doi"),
  url: varchar("url"),
  citationStyle: varchar("citation_style").default("APA"),
  createdAt: timestamp("created_at").defaultNow()
});
var aiSuggestions = pgTable("ai_suggestions", {
  id: serial("id").primaryKey(),
  documentId: integer("document_id").notNull(),
  userId: integer("user_id").notNull(),
  originalText: text("original_text").notNull(),
  suggestedText: text("suggested_text").notNull(),
  instruction: text("instruction"),
  accepted: boolean("accepted").default(false),
  createdAt: timestamp("created_at").defaultNow()
});
var plagiarismChecks = pgTable("plagiarism_checks", {
  id: serial("id").primaryKey(),
  userId: integer("user_id").notNull(),
  documentId: integer("document_id"),
  title: varchar("title").notNull(),
  content: text("content").notNull(),
  email: varchar("email").notNull(),
  submissionType: varchar("submission_type").notNull(),
  // upload, document
  status: varchar("status").notNull().default("pending"),
  // pending, processing, completed
  similarityScore: integer("similarity_score"),
  reportUrl: varchar("report_url"),
  submittedAt: timestamp("submitted_at").defaultNow(),
  completedAt: timestamp("completed_at")
});
var usersRelations = relations(users, ({ many }) => ({
  documents: many(documents),
  citations: many(citations),
  aiSuggestions: many(aiSuggestions),
  plagiarismChecks: many(plagiarismChecks)
}));
var documentsRelations = relations(documents, ({ one, many }) => ({
  user: one(users, {
    fields: [documents.userId],
    references: [users.id]
  }),
  citations: many(citations),
  aiSuggestions: many(aiSuggestions)
}));
var citationsRelations = relations(citations, ({ one }) => ({
  user: one(users, {
    fields: [citations.userId],
    references: [users.id]
  }),
  document: one(documents, {
    fields: [citations.documentId],
    references: [documents.id]
  })
}));
var aiSuggestionsRelations = relations(aiSuggestions, ({ one }) => ({
  user: one(users, {
    fields: [aiSuggestions.userId],
    references: [users.id]
  }),
  document: one(documents, {
    fields: [aiSuggestions.documentId],
    references: [documents.id]
  })
}));
var plagiarismChecksRelations = relations(plagiarismChecks, ({ one }) => ({
  user: one(users, {
    fields: [plagiarismChecks.userId],
    references: [users.id]
  }),
  document: one(documents, {
    fields: [plagiarismChecks.documentId],
    references: [documents.id]
  })
}));
var insertDocumentSchema = createInsertSchema(documents).omit({
  id: true,
  createdAt: true,
  updatedAt: true
});
var insertCitationSchema = createInsertSchema(citations).omit({
  id: true,
  createdAt: true
});
var insertAiSuggestionSchema = createInsertSchema(aiSuggestions).omit({
  id: true,
  createdAt: true
});
var insertPlagiarismCheckSchema = createInsertSchema(plagiarismChecks).omit({
  id: true,
  submittedAt: true,
  completedAt: true
});

// server/db.ts
import { Pool, neonConfig } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-serverless";
import ws from "ws";
import dotenv from "dotenv";
dotenv.config();
neonConfig.webSocketConstructor = ws;
if (!process.env.DATABASE_URL) {
  throw new Error(
    "DATABASE_URL must be set. Did you forget to provision a database?"
  );
}
var pool = new Pool({ connectionString: process.env.DATABASE_URL });
var db = drizzle({ client: pool, schema: schema_exports });

// server/storage.ts
import { eq, desc, and } from "drizzle-orm";
import bcrypt from "bcrypt";

// server/config/helper.ts
function sanitizeUser(user) {
  const { password, ...safeUser } = user;
  return safeUser;
}

// server/storage.ts
var DatabaseStorage = class {
  // User operations (IMPORTANT) these user operations are mandatory for Replit Auth.
  async getUser(id) {
    const [user] = await db.select().from(users).where(eq(users.id, id));
    return user;
  }
  async getUserByEmail(email) {
    const [user] = await db.select().from(users).where(eq(users.email, email));
    return user;
  }
  async upsertUser(userData) {
    const [user] = await db.insert(users).values(userData).onConflictDoUpdate({
      target: users.id,
      set: {
        ...userData,
        updatedAt: /* @__PURE__ */ new Date()
      }
    }).returning();
    return user;
  }
  async updateUserTheme(userId, theme) {
    await db.update(users).set({ themePreference: theme, updatedAt: /* @__PURE__ */ new Date() }).where(eq(users.id, userId));
  }
  async updateUserLanguage(userId, language) {
    await db.update(users).set({ languagePreference: language, updatedAt: /* @__PURE__ */ new Date() }).where(eq(users.id, userId));
  }
  // Document operations
  async getUserDocuments(userId) {
    return await db.select().from(documents).where(eq(documents.userId, userId)).orderBy(desc(documents.updatedAt));
  }
  async getDocument(id, userId) {
    const [document] = await db.select().from(documents).where(and(eq(documents.id, id), eq(documents.userId, userId)));
    return document;
  }
  async createDocument(document) {
    const sanitizedDocument = { ...document };
    if (typeof sanitizedDocument.content === "string") {
      sanitizedDocument.content = sanitizedDocument.content.replace(/\0/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").trim();
    }
    if (typeof sanitizedDocument.title === "string") {
      sanitizedDocument.title = sanitizedDocument.title.replace(/\0/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").trim();
    }
    const [newDocument] = await db.insert(documents).values(sanitizedDocument).returning();
    return newDocument;
  }
  async updateDocument(id, userId, updates) {
    const sanitizedUpdates = { ...updates };
    if (typeof sanitizedUpdates.content === "string") {
      sanitizedUpdates.content = sanitizedUpdates.content.replace(/\0/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").trim();
    }
    if (typeof sanitizedUpdates.title === "string") {
      sanitizedUpdates.title = sanitizedUpdates.title.replace(/\0/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").trim();
    }
    const [updatedDocument] = await db.update(documents).set({ ...sanitizedUpdates, updatedAt: /* @__PURE__ */ new Date() }).where(and(eq(documents.id, id), eq(documents.userId, userId))).returning();
    return updatedDocument;
  }
  async deleteDocument(id, userId) {
    const result = await db.delete(documents).where(and(eq(documents.id, id), eq(documents.userId, userId)));
    return (result.rowCount ?? 0) > 0;
  }
  // Citation operations
  async getUserCitations(userId, documentId) {
    const conditions = [eq(citations.userId, userId)];
    if (documentId) {
      conditions.push(eq(citations.documentId, documentId));
    }
    return await db.select().from(citations).where(and(...conditions)).orderBy(desc(citations.createdAt));
  }
  async createCitation(citation) {
    const [newCitation] = await db.insert(citations).values(citation).returning();
    return newCitation;
  }
  async updateCitation(id, userId, updates) {
    const [updatedCitation] = await db.update(citations).set(updates).where(and(eq(citations.id, id), eq(citations.userId, userId))).returning();
    return updatedCitation;
  }
  async deleteCitation(id, userId) {
    const result = await db.delete(citations).where(and(eq(citations.id, id), eq(citations.userId, userId)));
    return (result.rowCount ?? 0) > 0;
  }
  // AI Suggestion operations
  async createAiSuggestion(suggestion) {
    const [newSuggestion] = await db.insert(aiSuggestions).values(suggestion).returning();
    return newSuggestion;
  }
  async getDocumentSuggestions(documentId, userId) {
    return await db.select().from(aiSuggestions).where(
      and(
        eq(aiSuggestions.documentId, documentId),
        eq(aiSuggestions.userId, userId)
      )
    ).orderBy(desc(aiSuggestions.createdAt));
  }
  async acceptAiSuggestion(id, userId) {
    const result = await db.update(aiSuggestions).set({ accepted: true }).where(and(eq(aiSuggestions.id, id), eq(aiSuggestions.userId, userId)));
    return (result.rowCount ?? 0) > 0;
  }
  // Subscription operations
  async incrementRequestCount(userId) {
    try {
      const user = await this.getUser(userId);
      if (!user) {
        return { success: false, requestsUsed: 0, requestsLimit: 0 };
      }
      const currentRequestsUsed = user.requestsUsed ?? 0;
      const currentRequestsLimit = user.requestsLimit ?? 5;
      const newRequestsUsed = currentRequestsUsed + 1;
      if (newRequestsUsed > currentRequestsLimit && user.subscriptionStatus === "free") {
        return {
          success: false,
          requestsUsed: currentRequestsUsed,
          requestsLimit: currentRequestsLimit
        };
      }
      await db.update(users).set({ requestsUsed: newRequestsUsed }).where(eq(users.id, userId));
      return {
        success: true,
        requestsUsed: newRequestsUsed,
        requestsLimit: currentRequestsLimit
      };
    } catch (error) {
      console.error("Error incrementing request count:", error);
      return { success: false, requestsUsed: 0, requestsLimit: 0 };
    }
  }
  async resetMonthlyRequests(userId) {
    try {
      const now = /* @__PURE__ */ new Date();
      const nextMonth = new Date(
        now.getFullYear(),
        now.getMonth() + 1,
        now.getDate()
      );
      await db.update(users).set({
        requestsUsed: 0,
        subscriptionPeriodStart: now,
        subscriptionPeriodEnd: nextMonth
      }).where(eq(users.id, userId));
    } catch (error) {
      console.error("Error resetting monthly requests:", error);
    }
  }
  async upgradeToProPlan(userId, stripeCustomerId, stripeSubscriptionId) {
    try {
      const now = /* @__PURE__ */ new Date();
      const nextMonth = new Date(
        now.getFullYear(),
        now.getMonth() + 1,
        now.getDate()
      );
      const [updatedUser] = await db.update(users).set({
        subscriptionStatus: "paid",
        requestsLimit: 500,
        requestsUsed: 0,
        subscriptionPeriodStart: now,
        subscriptionPeriodEnd: nextMonth,
        stripeCustomerId,
        stripeSubscriptionId,
        updatedAt: now
      }).where(eq(users.id, userId)).returning();
      return updatedUser;
    } catch (error) {
      console.error("Error upgrading to pro plan:", error);
      throw error;
    }
  }
  async updateStripeInfo(userId, stripeCustomerId, stripeSubscriptionId) {
    try {
      await db.update(users).set({
        stripeCustomerId,
        stripeSubscriptionId,
        updatedAt: /* @__PURE__ */ new Date()
      }).where(eq(users.id, userId));
    } catch (error) {
      console.error("Error updating Stripe info:", error);
      throw error;
    }
  }
  // Plagiarism check operations
  async createPlagiarismCheck(check) {
    const [plagiarismCheck] = await db.insert(plagiarismChecks).values(check).returning();
    return plagiarismCheck;
  }
  async getUserPlagiarismChecks(userId) {
    return await db.select().from(plagiarismChecks).where(eq(plagiarismChecks.userId, userId)).orderBy(desc(plagiarismChecks.submittedAt));
  }
  async updatePlagiarismCheck(id, updates) {
    const [updated] = await db.update(plagiarismChecks).set({
      ...updates
    }).where(eq(plagiarismChecks.id, id)).returning();
    return updated;
  }
  // auth
  async createUser(user) {
    const existingUser = await this.getUserByEmail(user.email);
    if (existingUser) {
      throw new Error("User already exists");
    }
    const salt = await bcrypt.genSalt(11);
    const hashedPassword = await bcrypt.hash(user.password, salt);
    console.log("Creating user with email:", user);
    const [newUser] = await db.insert(users).values({
      ...user,
      password: hashedPassword
    }).returning();
    return newUser;
  }
  async authenticateUser(email, password) {
    const user = await this.getUserByEmail(email);
    if (!user || !user.password) return void 0;
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) return void 0;
    return sanitizeUser(user);
  }
  async updateLastLogin(userId) {
    try {
      await db.update(users).set({
        lastLogin: /* @__PURE__ */ new Date()
      }).where(eq(users.id, userId));
    } catch (error) {
      console.error("Error updating last login:", error);
    }
  }
};
var storage = new DatabaseStorage();

// server/simpleWordProcessor.ts
import mammoth from "mammoth";
var SimpleWordProcessor = class {
  // Import Word document (.docx) to HTML/text
  static async importDocx(buffer) {
    try {
      const arrayBuffer = new Uint8Array(buffer).buffer;
      const htmlResult = await mammoth.convertToHtml({ arrayBuffer });
      const textResult = await mammoth.extractRawText({ arrayBuffer });
      const titleMatch = htmlResult.value.match(/<h[1-6][^>]*>(.*?)<\/h[1-6]>/i);
      const title = titleMatch ? titleMatch[1].replace(/<[^>]*>/g, "").trim() : textResult.value.split("\n")[0].trim().substring(0, 50);
      return {
        html: htmlResult.value,
        text: textResult.value,
        title: title || "Imported Document"
      };
    } catch (error) {
      throw new Error(`Failed to import Word document: ${error.message}`);
    }
  }
  // Simple export to HTML format (for demo purposes)
  static async exportToHtml(content, title = "Document") {
    const htmlContent = `
<!DOCTYPE html>
<html>
<head>
    <title>${title}</title>
    <style>
        body { font-family: 'Times New Roman', serif; margin: 40px; line-height: 1.6; }
        h1, h2, h3 { color: #333; }
        .header { text-align: center; margin-bottom: 30px; }
        .content { max-width: 800px; margin: 0 auto; }
    </style>
</head>
<body>
    <div class="header">
        <h1>${title}</h1>
    </div>
    <div class="content">
        ${content}
    </div>
</body>
</html>`;
    return htmlContent;
  }
  // Convert LaTeX content to HTML for export
  static convertLatexToHtml(latex) {
    return latex.replace(/\\textbf\{(.*?)\}/g, "<strong>$1</strong>").replace(/\\textit\{(.*?)\}/g, "<em>$1</em>").replace(/\\underline\{(.*?)\}/g, "<u>$1</u>").replace(/\\section\{(.*?)\}/g, "<h1>$1</h1>").replace(/\\subsection\{(.*?)\}/g, "<h2>$1</h2>").replace(/\\subsubsection\{(.*?)\}/g, "<h3>$1</h3>").replace(/\\begin\{itemize\}(.*?)\\end\{itemize\}/g, (match, content) => {
      const items = content.replace(/\\item\s*/g, "<li>").replace(/\n\s*(?=<li>)/g, "</li>\n");
      return `<ul>${items}</li></ul>`;
    }).replace(/\\begin\{enumerate\}(.*?)\\end\{enumerate\}/g, (match, content) => {
      const items = content.replace(/\\item\s*/g, "<li>").replace(/\n\s*(?=<li>)/g, "</li>\n");
      return `<ol>${items}</li></ol>`;
    }).replace(/\\begin\{quote\}(.*?)\\end\{quote\}/g, "<blockquote>$1</blockquote>").replace(/\\texttt\{(.*?)\}/g, "<code>$1</code>").replace(/\\par\s*/g, "</p><p>").replace(/\\\\(\s*)/g, "<br>").replace(/\\documentclass.*?\n/g, "").replace(/\\usepackage.*?\n/g, "").replace(/\\begin\{document\}/g, "<p>").replace(/\\end\{document\}/g, "</p>").replace(/\\maketitle/g, "").replace(/\\title\{(.*?)\}/g, "").replace(/\\author\{.*?\}/g, "").replace(/\\date\{.*?\}/g, "").trim();
  }
};

// server/routes.ts
import multer from "multer";

// server/openai.ts
import OpenAI from "openai";
var openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY_ENV_VAR || "default_key"
});
async function generateAISuggestion(request) {
  try {
    let prompt = "";
    if (request.instruction) {
      prompt = `You are an expert academic writing assistant. The user has selected this text: "${request.text}"

Their instruction is: "${request.instruction}"

Please provide an improved version based on their instruction. Maintain academic tone and clarity.

Respond with JSON in this format: { "suggestion": "improved text here", "reasoning": "brief explanation of changes made" }`;
    } else {
      prompt = `You are an expert academic writing assistant. Please improve the following paragraph for academic writing:

"${request.text}"

Make it more:
- Clear and concise
- Academically appropriate
- Well-structured
- Engaging while maintaining scholarly tone

${request.context ? `Context: ${request.context}` : ""}

Respond with JSON in this format: { "suggestion": "improved paragraph here", "reasoning": "brief explanation of key improvements made" }`;
    }
    const response = await openai.chat.completions.create({
      model: "gpt-4o",
      messages: [
        {
          role: "system",
          content: "You are an expert academic writing assistant specializing in improving clarity, structure, and academic tone. Always respond with valid JSON."
        },
        {
          role: "user",
          content: prompt
        }
      ],
      response_format: { type: "json_object" },
      temperature: 0.7,
      max_tokens: 800
    });
    const result = JSON.parse(response.choices[0].message.content || "{}");
    return {
      suggestion: result.suggestion || "Unable to generate suggestion",
      reasoning: result.reasoning || "No reasoning provided"
    };
  } catch (error) {
    console.error("OpenAI API error:", error);
    throw new Error("Failed to generate AI suggestion: " + error.message);
  }
}
async function generateCitationSuggestion(query) {
  try {
    const prompt = `You are an academic research assistant. Based on this query: "${query}"

Suggest 3-5 relevant academic sources that would be appropriate for citation. Format each as a proper academic citation in APA style.

Respond with JSON in this format: { "citations": ["citation1", "citation2", "citation3"] }`;
    const response = await openai.chat.completions.create({
      model: "gpt-4o",
      messages: [
        {
          role: "system",
          content: "You are a research assistant that helps find and format academic citations."
        },
        {
          role: "user",
          content: prompt
        }
      ],
      response_format: { type: "json_object" },
      temperature: 0.3,
      max_tokens: 600
    });
    const result = JSON.parse(response.choices[0].message.content || "{}");
    return result.citations || [];
  } catch (error) {
    console.error("OpenAI citation error:", error);
    throw new Error("Failed to generate citation suggestions: " + error.message);
  }
}
async function summarizeDocument(content) {
  try {
    const prompt = `Please provide a concise summary of this academic document:

${content.substring(0, 3e3)}...

Respond with JSON in this format: { "summary": "concise summary here" }`;
    const response = await openai.chat.completions.create({
      model: "gpt-4o",
      messages: [
        {
          role: "system",
          content: "You are an academic writing assistant that creates clear, concise summaries."
        },
        {
          role: "user",
          content: prompt
        }
      ],
      response_format: { type: "json_object" },
      temperature: 0.3,
      max_tokens: 300
    });
    const result = JSON.parse(response.choices[0].message.content || "{}");
    return result.summary || "Unable to generate summary";
  } catch (error) {
    console.error("OpenAI summary error:", error);
    throw new Error("Failed to generate document summary: " + error.message);
  }
}

// server/routes.ts
import { z } from "zod";

// server/routes/auth.ts
import express from "express";
import passport from "passport";

// server/controllers/auth.ts
var FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:5002";
var googleCallback = (req, res) => {
  const user = req.user;
  res.redirect(`${FRONTEND_URL}/auth-success`);
};
var facebookCallback = (req, res) => {
  const user = req.user;
  res.redirect("/");
};

// server/routes/auth.ts
var router = express.Router();
router.get("/google", (req, res, next) => {
  passport.authenticate("google", { scope: ["profile", "email"] })(req, res, next);
});
router.get(
  "/google/callback",
  passport.authenticate("google", { failureRedirect: "/login-failure" }),
  googleCallback
);
router.get("/facebook", passport.authenticate("facebook", { scope: ["email"] }));
router.get(
  "/facebook/callback",
  passport.authenticate("facebook", { failureRedirect: "/login-failure" }),
  facebookCallback
);
var auth_default = router;

// server/guards/auth.ts
var authGuard = (req, res, next) => {
  console.log("Auth Guard:", req.session, req.user);
  if (req.isAuthenticated && req.isAuthenticated()) {
    return next();
  }
  return res.status(401).json({ message: "Unauthorized" });
};

// server/routes.ts
import passport2 from "passport";
var upload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: 10 * 1024 * 1024 }
  // 10MB limit
});
async function registerRoutes(app2) {
  app2.get("/api/auth/user", authGuard, async (req, res) => {
    console.log("Fetching user data for:", req.session);
    try {
      const userId = req.user.id || req.user.id;
      const user = await storage.getUser(userId);
      res.json(sanitizeUser(user));
    } catch (error) {
      console.error("Error fetching user:", error);
      res.status(500).json({ message: "Failed to fetch user" });
    }
  });
  app2.post("/api/auth/signup", async (req, res) => {
    try {
      const { email, password, firstName, lastName, defaultLanguage } = req.body;
      if (!email || !password || !firstName || !lastName || !defaultLanguage) {
        return res.status(400).json({ message: "Missing required fields" });
      }
      const user = await storage.createUser({
        email,
        password,
        // Password should be hashed in storage layer
        firstName,
        lastName,
        username: email.split("@")[0],
        // Simple username from email
        languagePreference: defaultLanguage
      });
      res.status(201).json(user);
    } catch (error) {
      console.error("Error signing up:", error);
      res.status(500).json({ message: "Failed to sign up" });
    }
  });
  app2.post(
    "/api/auth/login",
    passport2.authenticate("local", { failureRedirect: "/auth" }),
    async (req, res) => {
      try {
        const user = req.user;
        if (!user) {
          return res.status(401).json({ message: "Invalid credentials" });
        }
        await storage.updateLastLogin(user.id);
        res.json(sanitizeUser(user));
      } catch (error) {
        console.error("Error logging in:", error);
        return res.status(500).json({
          message: error?.message || "Failed to log in"
        });
      }
    }
  );
  app2.get("/api/auth/logout", authGuard, (req, res) => {
    req.logout(function(err) {
      if (err) {
        console.error("Error during logout:", err);
        return res.status(500).json({ message: "Logout failed" });
      }
      req.session.destroy((err2) => {
        if (err2) {
          console.error("Error destroying session:", err2);
          return res.status(500).json({ message: "Failed to destroy session" });
        }
        console.log("logging out");
        res.clearCookie("sessionId", {
          httpOnly: true,
          secure: false,
          path: "/"
        });
        return res.json({ success: true, message: "Logged out successfully" });
      });
    });
  });
  app2.use("/api/auth", auth_default);
  app2.patch("/api/auth/user/theme", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const { theme } = req.body;
      if (!["light", "dark"].includes(theme)) {
        return res.status(400).json({ message: "Invalid theme preference" });
      }
      await storage.updateUserTheme(userId, theme);
      res.json({ success: true });
    } catch (error) {
      console.error("Error updating theme:", error);
      res.status(500).json({ message: "Failed to update theme preference" });
    }
  });
  app2.post(
    "/api/subscription/increment-request",
    authGuard,
    async (req, res) => {
      try {
        const userId = req.user.id;
        if (userId === "demo-user-123") {
          res.json({
            success: true,
            requestsUsed: 3,
            requestsLimit: 5
          });
          return;
        }
        const result = await storage.incrementRequestCount(userId);
        if (result.success) {
          res.json({
            success: true,
            requestsUsed: result.requestsUsed,
            requestsLimit: result.requestsLimit
          });
        } else {
          res.status(429).json({
            success: false,
            message: "Request limit exceeded",
            requestsUsed: result.requestsUsed,
            requestsLimit: result.requestsLimit
          });
        }
      } catch (error) {
        console.error("Error incrementing request count:", error);
        res.status(500).json({ message: "Failed to increment request count" });
      }
    }
  );
  app2.post("/api/subscription/upgrade", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const { stripeCustomerId, stripeSubscriptionId } = req.body;
      const updatedUser = await storage.upgradeToProPlan(
        userId,
        stripeCustomerId,
        stripeSubscriptionId
      );
      res.json(updatedUser);
    } catch (error) {
      console.error("Error upgrading to pro plan:", error);
      res.status(500).json({ message: "Failed to upgrade subscription" });
    }
  });
  app2.post(
    "/api/subscription/reset-monthly",
    authGuard,
    async (req, res) => {
      try {
        const userId = req.user.id;
        await storage.resetMonthlyRequests(userId);
        res.json({ success: true });
      } catch (error) {
        console.error("Error resetting monthly requests:", error);
        res.status(500).json({ message: "Failed to reset monthly requests" });
      }
    }
  );
  app2.post("/api/plagiarism/submit", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const { documentId, title, content, email, submissionType } = req.body;
      if (userId === "demo-user-123") {
        res.json({
          id: Date.now(),
          status: "pending",
          message: "Plagiarism check submitted successfully (demo mode)"
        });
        return;
      }
      if (!title || !content || !email || !submissionType) {
        return res.status(400).json({ message: "Missing required fields" });
      }
      const plagiarismCheck = await storage.createPlagiarismCheck({
        userId,
        documentId: documentId || null,
        title,
        content,
        email,
        submissionType,
        status: "pending"
      });
      res.json({
        id: plagiarismCheck.id,
        status: plagiarismCheck.status,
        message: "Plagiarism check submitted successfully"
      });
    } catch (error) {
      console.error("Error submitting plagiarism check:", error);
      res.status(500).json({ message: "Failed to submit plagiarism check" });
    }
  });
  app2.get("/api/plagiarism/checks", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      if (userId === 1) {
        res.json([
          {
            id: 1,
            documentId: 42,
            title: "Sample Academic Paper",
            status: "completed",
            submittedAt: /* @__PURE__ */ new Date("2024-01-15"),
            completedAt: /* @__PURE__ */ new Date("2024-01-16"),
            similarityScore: 8,
            reportUrl: "demo-report.pdf"
          },
          {
            id: 2,
            documentId: null,
            title: "Research Draft",
            status: "processing",
            submittedAt: /* @__PURE__ */ new Date("2024-01-20"),
            completedAt: null,
            similarityScore: null,
            reportUrl: null
          }
        ]);
        return;
      }
      const checks = await storage.getUserPlagiarismChecks(userId);
      res.json(checks);
    } catch (error) {
      console.error("Error fetching plagiarism checks:", error);
      res.status(500).json({ message: "Failed to fetch plagiarism checks" });
    }
  });
  app2.patch(
    "/api/auth/user/language",
    authGuard,
    async (req, res) => {
      try {
        const userId = req.user.id;
        const { language } = req.body;
        const validLanguages = [
          "en",
          "es",
          "fr",
          "de",
          "it",
          "pt",
          "ru",
          "zh",
          "ja",
          "ko",
          "ar",
          "hi"
        ];
        if (!validLanguages.includes(language)) {
          return res.status(400).json({ message: "Invalid language preference" });
        }
        await storage.updateUserLanguage(userId, language);
        res.json({ success: true });
      } catch (error) {
        console.error("Error updating language:", error);
        res.status(500).json({ message: "Failed to update language preference" });
      }
    }
  );
  app2.post(
    "/api/documents/import",
    upload.single("file"),
    authGuard,
    async (req, res) => {
      try {
        if (!req.file) {
          return res.status(400).json({ message: "No file uploaded" });
        }
        const { html, text: text2, title } = await SimpleWordProcessor.importDocx(
          req.file.buffer
        );
        const userId = req.user.id;
        const document = await storage.createDocument({
          userId,
          title: title || "Imported Document",
          content: html
        });
        res.json({ document, message: "Document imported successfully" });
      } catch (error) {
        console.error("Import error:", error);
        res.status(500).json({ message: "Failed to import document: " + error.message });
      }
    }
  );
  app2.get(
    "/api/documents/:id/export/html",
    authGuard,
    async (req, res) => {
      try {
        const userId = req.user.id;
        const documentId = parseInt(req.params.id);
        const document = await storage.getDocument(documentId, userId);
        if (!document) {
          return res.status(404).json({ message: "Document not found" });
        }
        const htmlContent = await SimpleWordProcessor.exportToHtml(
          document.content,
          document.title
        );
        res.setHeader("Content-Type", "text/html");
        res.setHeader(
          "Content-Disposition",
          `attachment; filename="${document.title}.html"`
        );
        res.send(htmlContent);
      } catch (error) {
        console.error("Export error:", error);
        res.status(500).json({ message: "Failed to export document: " + error.message });
      }
    }
  );
  app2.get("/api/documents", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const documents2 = await storage.getUserDocuments(userId);
      res.json(documents2);
    } catch (error) {
      console.error("Error fetching documents:", error);
      res.status(500).json({ message: "Failed to fetch documents" });
    }
  });
  app2.get("/api/documents/:id", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const documentId = parseInt(req.params.id);
      if (isNaN(documentId)) {
        return res.status(400).json({ message: "Invalid document ID" });
      }
      const document = await storage.getDocument(documentId, userId);
      if (!document) {
        return res.status(404).json({ message: "Document not found" });
      }
      res.json(document);
    } catch (error) {
      console.error("Error fetching document:", error);
      res.status(500).json({ message: "Failed to fetch document" });
    }
  });
  app2.post("/api/documents", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const documentData = insertDocumentSchema.parse({
        ...req.body,
        userId
      });
      const document = await storage.createDocument(documentData);
      res.json(document);
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({ message: "Invalid document data", errors: error.errors });
      }
      console.error("Error creating document:", error);
      res.status(500).json({ message: "Failed to create document" });
    }
  });
  app2.patch("/api/documents/:id", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const documentId = parseInt(req.params.id);
      if (isNaN(documentId)) {
        return res.status(400).json({ message: "Invalid document ID" });
      }
      const updates = Object.keys(req.body).reduce((acc, key) => {
        if (["title", "content", "wordCount", "documentType"].includes(key)) {
          acc[key] = req.body[key];
        }
        return acc;
      }, {});
      const document = await storage.updateDocument(
        documentId,
        userId,
        updates
      );
      if (!document) {
        return res.status(404).json({ message: "Document not found" });
      }
      res.json(document);
    } catch (error) {
      console.error("Error updating document:", error);
      res.status(500).json({ message: "Failed to update document" });
    }
  });
  app2.delete("/api/documents/:id", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const documentId = parseInt(req.params.id);
      if (isNaN(documentId)) {
        return res.status(400).json({ message: "Invalid document ID" });
      }
      const success = await storage.deleteDocument(documentId, userId);
      if (!success) {
        return res.status(404).json({ message: "Document not found" });
      }
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting document:", error);
      res.status(500).json({ message: "Failed to delete document" });
    }
  });
  app2.post("/api/documents/export", authGuard, async (req, res) => {
    try {
      const { documentId, format, title, content } = req.body;
      if (!format || !content) {
        return res.status(400).json({ message: "Missing required fields" });
      }
      let buffer;
      let filename;
      let contentType;
      switch (format) {
        case "docx":
          const { WordProcessor: WordProcessor2 } = await Promise.resolve().then(() => (init_wordProcessor(), wordProcessor_exports));
          buffer = await WordProcessor2.exportDocx(
            content,
            title || "Document",
            false
          );
          filename = `${title || "document"}.docx`;
          contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
          break;
        case "latex":
          buffer = Buffer.from(content, "utf-8");
          filename = `${title || "document"}.tex`;
          contentType = "text/plain";
          break;
        case "pdf":
          return res.status(501).json({
            message: "PDF export is not yet implemented. Please use DOCX or LaTeX format."
          });
        default:
          return res.status(400).json({ message: "Unsupported export format" });
      }
      res.setHeader("Content-Type", contentType);
      res.setHeader(
        "Content-Disposition",
        `attachment; filename="${filename}"`
      );
      res.send(buffer);
    } catch (error) {
      console.error("Error exporting document:", error);
      res.status(500).json({ message: "Failed to export document" });
    }
  });
  app2.get("/api/citations", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const documentId = req.query.documentId ? parseInt(req.query.documentId) : void 0;
      const citations2 = await storage.getUserCitations(userId, documentId);
      res.json(citations2);
    } catch (error) {
      console.error("Error fetching citations:", error);
      res.status(500).json({ message: "Failed to fetch citations" });
    }
  });
  app2.post("/api/citations", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const citationData = insertCitationSchema.parse({
        ...req.body,
        userId
      });
      const citation = await storage.createCitation(citationData);
      res.json(citation);
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({ message: "Invalid citation data", errors: error.errors });
      }
      console.error("Error creating citation:", error);
      res.status(500).json({ message: "Failed to create citation" });
    }
  });
  app2.patch("/api/citations/:id", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const citationId = parseInt(req.params.id);
      if (isNaN(citationId)) {
        return res.status(400).json({ message: "Invalid citation ID" });
      }
      const updates = Object.keys(req.body).reduce((acc, key) => {
        if ([
          "title",
          "authors",
          "publication",
          "year",
          "pages",
          "doi",
          "url",
          "citationStyle",
          "documentId"
        ].includes(key)) {
          acc[key] = req.body[key];
        }
        return acc;
      }, {});
      const citation = await storage.updateCitation(
        citationId,
        userId,
        updates
      );
      if (!citation) {
        return res.status(404).json({ message: "Citation not found" });
      }
      res.json(citation);
    } catch (error) {
      console.error("Error updating citation:", error);
      res.status(500).json({ message: "Failed to update citation" });
    }
  });
  app2.delete("/api/citations/:id", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const citationId = parseInt(req.params.id);
      if (isNaN(citationId)) {
        return res.status(400).json({ message: "Invalid citation ID" });
      }
      const success = await storage.deleteCitation(citationId, userId);
      if (!success) {
        return res.status(404).json({ message: "Citation not found" });
      }
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting citation:", error);
      res.status(500).json({ message: "Failed to delete citation" });
    }
  });
  app2.post("/api/ai/suggest", authGuard, async (req, res) => {
    try {
      const userId = req.user.id;
      const { text: text2, instruction, documentId, context } = req.body;
      if (!text2 || !documentId) {
        return res.status(400).json({ message: "Text and document ID are required" });
      }
      const aiResponse = await generateAISuggestion({
        text: text2,
        instruction,
        context
      });
      const suggestionData = insertAiSuggestionSchema.parse({
        documentId: parseInt(documentId),
        userId,
        originalText: text2,
        suggestedText: aiResponse.suggestion,
        instruction: instruction || null
      });
      const storedSuggestion = await storage.createAiSuggestion(suggestionData);
      res.json({
        ...aiResponse,
        id: storedSuggestion.id
      });
    } catch (error) {
      console.error("Error generating AI suggestion:", error);
      res.status(500).json({ message: "Failed to generate AI suggestion" });
    }
  });
  app2.post(
    "/api/ai/accept-suggestion/:id",
    authGuard,
    async (req, res) => {
      try {
        const userId = req.user.id;
        const suggestionId = parseInt(req.params.id);
        if (isNaN(suggestionId)) {
          return res.status(400).json({ message: "Invalid suggestion ID" });
        }
        const success = await storage.acceptAiSuggestion(suggestionId, userId);
        if (!success) {
          return res.status(404).json({ message: "Suggestion not found" });
        }
        res.json({ success: true });
      } catch (error) {
        console.error("Error accepting suggestion:", error);
        res.status(500).json({ message: "Failed to accept suggestion" });
      }
    }
  );
  app2.get(
    "/api/ai/suggestions/:documentId",
    authGuard,
    async (req, res) => {
      try {
        const userId = req.user.id;
        const documentId = parseInt(req.params.documentId);
        if (isNaN(documentId)) {
          return res.status(400).json({ message: "Invalid document ID" });
        }
        const suggestions = await storage.getDocumentSuggestions(
          documentId,
          userId
        );
        res.json(suggestions);
      } catch (error) {
        console.error("Error fetching suggestions:", error);
        res.status(500).json({ message: "Failed to fetch suggestions" });
      }
    }
  );
  app2.post("/api/plagiarism/check", authGuard, async (req, res) => {
    try {
      const { content } = req.body;
      if (!content) {
        return res.status(400).json({ message: "Content is required" });
      }
      const sentences = content.split(/[.!?]+/).filter((s) => s.trim().length > 10);
      const totalSentences = sentences.length;
      const matches = [];
      const sources = [
        "Wikipedia - Academic Writing",
        "ResearchGate - Writing Methodology",
        "Journal of Academic Research",
        "University Writing Center",
        "Scholarly Articles Database",
        "Academic Publication Standards"
      ];
      const matchCount = Math.floor(totalSentences * 0.1);
      for (let i = 0; i < matchCount && i < 3; i++) {
        const randomSentence = sentences[Math.floor(Math.random() * sentences.length)];
        const similarity = Math.floor(Math.random() * 30) + 70;
        matches.push({
          source: sources[Math.floor(Math.random() * sources.length)],
          similarity,
          matchedText: randomSentence.trim(),
          url: `https://example.com/source-${i + 1}`
        });
      }
      const overallScore = matches.length > 0 ? Math.floor(
        matches.reduce((sum, match) => sum + match.similarity, 0) / matches.length * 0.3
      ) : Math.floor(Math.random() * 15);
      const suggestions = [
        "Consider paraphrasing highlighted sections to improve originality",
        "Add proper citations for referenced material",
        "Use quotation marks for direct quotes",
        "Expand on your original analysis and insights",
        "Review and rephrase similar content found in other sources"
      ];
      const result = {
        overallScore,
        matches,
        suggestions: suggestions.slice(0, Math.min(3, suggestions.length))
      };
      res.json(result);
    } catch (error) {
      console.error("Error checking plagiarism:", error);
      res.status(500).json({ message: "Failed to check plagiarism" });
    }
  });
  app2.post(
    "/api/ai/citation-suggestions",
    authGuard,
    async (req, res) => {
      try {
        const { query } = req.body;
        if (!query) {
          return res.status(400).json({ message: "Query is required" });
        }
        const suggestions = await generateCitationSuggestion(query);
        res.json({ suggestions });
      } catch (error) {
        console.error("Error generating citation suggestions:", error);
        res.status(500).json({ message: "Failed to generate citation suggestions" });
      }
    }
  );
  app2.post(
    "/api/ai/summarize/:documentId",
    authGuard,
    async (req, res) => {
      try {
        const userId = req.user.id;
        const documentId = parseInt(req.params.documentId);
        if (isNaN(documentId)) {
          return res.status(400).json({ message: "Invalid document ID" });
        }
        const document = await storage.getDocument(documentId, userId);
        if (!document) {
          return res.status(404).json({ message: "Document not found" });
        }
        const contentText = typeof document.content === "string" ? document.content : JSON.stringify(document.content);
        const summary = await summarizeDocument(contentText);
        res.json({ summary });
      } catch (error) {
        console.error("Error generating summary:", error);
        res.status(500).json({ message: "Failed to generate summary" });
      }
    }
  );
  app2.post("/api/documents/export", authGuard, async (req, res) => {
    try {
      const { documentId, format, title, content } = req.body;
      const userId = req.user?.claims?.sub;
      if (!documentId || !format || !title || !content) {
        return res.status(400).json({ message: "Missing required fields" });
      }
      if (!["docx", "pdf", "latex"].includes(format)) {
        return res.status(400).json({ message: "Invalid format. Must be docx, pdf, or latex" });
      }
      const document = await storage.getDocument(documentId, userId);
      if (!document) {
        return res.status(404).json({ message: "Document not found" });
      }
      let fileContent;
      let mimeType;
      let fileExtension;
      switch (format) {
        case "latex":
          fileContent = `\\documentclass{article}
\\usepackage[utf8]{inputenc}
\\usepackage{amsmath}
\\usepackage{amsfonts}
\\usepackage{amssymb}
\\usepackage{graphicx}

\\title{${title}}
\\author{Academic Writer}
\\date{\\today}

\\begin{document}

\\maketitle

${content.replace(/\*\*(.*?)\*\*/g, "\\textbf{$1}").replace(/\*(.*?)\*/g, "\\textit{$1}").replace(/^# (.*?)$/gm, "\\section{$1}").replace(/^## (.*?)$/gm, "\\subsection{$1}").replace(/^### (.*?)$/gm, "\\subsubsection{$1}").replace(/\n\n/g, "\n\n\\par\n")}

\\end{document}`;
          mimeType = "application/x-tex";
          fileExtension = "tex";
          break;
        case "docx":
          fileContent = `<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>${title}</title>
    <style>
        body { font-family: 'Times New Roman', serif; margin: 1in; }
        h1 { font-size: 24px; font-weight: bold; text-align: center; }
        p { font-size: 12px; line-height: 1.6; margin: 12px 0; }
        strong { font-weight: bold; }
        em { font-style: italic; }
    </style>
</head>
<body>
    <h1>${title}</h1>
    ${content.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>").replace(/\*(.*?)\*/g, "<em>$1</em>").replace(/^# (.*?)$/gm, "<h1>$1</h1>").replace(/^## (.*?)$/gm, "<h2>$1</h2>").replace(/^### (.*?)$/gm, "<h3>$1</h3>").replace(/\n\n/g, "</p><p>").replace(/\n/g, "<br>")}
</body>
</html>`;
          mimeType = "application/msword";
          fileExtension = "doc";
          break;
        case "pdf":
          fileContent = `${title}

${content}

Generated by Academic Writing Assistant
Date: ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`;
          mimeType = "text/plain";
          fileExtension = "txt";
          break;
        default:
          return res.status(400).json({ message: "Unsupported format" });
      }
      res.setHeader("Content-Type", mimeType);
      res.setHeader(
        "Content-Disposition",
        `attachment; filename="${title}.${fileExtension}"`
      );
      res.send(Buffer.from(fileContent, "utf8"));
    } catch (error) {
      console.error("Export error:", error);
      res.status(500).json({ message: "Failed to export document" });
    }
  });
  app2.post(
    "/api/ai/generate-suggestion",
    authGuard,
    async (req, res) => {
      try {
        const { prompt, text: text2, model, documentId } = req.body;
        const userId = req.user?.claims?.sub;
        if (!prompt || !text2) {
          return res.status(400).json({ message: "Prompt and text are required" });
        }
        if (documentId) {
          const document = await storage.getDocument(documentId, userId);
          if (!document) {
            return res.status(404).json({ message: "Document not found" });
          }
        }
        const suggestion = await generateAISuggestion({
          text: text2,
          instruction: prompt,
          context: `AI Model: ${model}`
        });
        res.json({
          suggestion: suggestion.suggestion,
          reasoning: suggestion.reasoning
        });
      } catch (error) {
        console.error("AI suggestion error:", error);
        res.status(500).json({ message: "Failed to generate AI suggestion" });
      }
    }
  );
  const httpServer = createServer(app2);
  return httpServer;
}

// server/vite.ts
import express2 from "express";
import fs from "fs";
import path2 from "path";
import { createServer as createViteServer, createLogger } from "vite";

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
import runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal";
var vite_config_default = defineConfig({
  plugins: [
    react(),
    runtimeErrorOverlay(),
    ...process.env.NODE_ENV !== "production" && process.env.REPL_ID !== void 0 ? [
      await import("@replit/vite-plugin-cartographer").then(
        (m) => m.cartographer()
      )
    ] : []
  ],
  resolve: {
    alias: {
      "@": path.resolve(import.meta.dirname, "client", "src"),
      "@shared": path.resolve(import.meta.dirname, "shared"),
      "@assets": path.resolve(import.meta.dirname, "attached_assets")
    }
  },
  root: path.resolve(import.meta.dirname, "client"),
  build: {
    outDir: path.resolve(import.meta.dirname, "dist/public"),
    emptyOutDir: true
  },
  server: {
    fs: {
      strict: true,
      deny: ["**/.*"]
    }
  }
});

// server/vite.ts
import { nanoid } from "nanoid";
var viteLogger = createLogger();
function log(message, source = "express") {
  const formattedTime = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
    hour: "numeric",
    minute: "2-digit",
    second: "2-digit",
    hour12: true
  });
  console.log(`${formattedTime} [${source}] ${message}`);
}
async function setupVite(app2, server) {
  const serverOptions = {
    middlewareMode: true,
    hmr: { server },
    allowedHosts: true
  };
  const vite = await createViteServer({
    ...vite_config_default,
    configFile: false,
    customLogger: {
      ...viteLogger,
      error: (msg, options) => {
        viteLogger.error(msg, options);
        process.exit(1);
      }
    },
    server: serverOptions,
    appType: "custom"
  });
  app2.use(vite.middlewares);
  app2.use("*", async (req, res, next) => {
    const url = req.originalUrl;
    try {
      const clientTemplate = path2.resolve(
        import.meta.dirname,
        "..",
        "client",
        "index.html"
      );
      let template = await fs.promises.readFile(clientTemplate, "utf-8");
      template = template.replace(
        `src="/src/main.tsx"`,
        `src="/src/main.tsx?v=${nanoid()}"`
      );
      const page = await vite.transformIndexHtml(url, template);
      res.status(200).set({ "Content-Type": "text/html" }).end(page);
    } catch (e) {
      vite.ssrFixStacktrace(e);
      next(e);
    }
  });
}
function serveStatic(app2) {
  const distPath = path2.resolve(import.meta.dirname, "public");
  if (!fs.existsSync(distPath)) {
    throw new Error(
      `Could not find the build directory: ${distPath}, make sure to build the client first`
    );
  }
  app2.use(express2.static(distPath));
  app2.use("*", (_req, res) => {
    res.sendFile(path2.resolve(distPath, "index.html"));
  });
}

// server/index.ts
import session from "express-session";

// server/config/passport.ts
import passport3 from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { Strategy as LocalStrategy } from "passport-local";
import dotenv2 from "dotenv";
import { eq as eq2 } from "drizzle-orm";
import bcrypt2 from "bcrypt";
dotenv2.config();
passport3.serializeUser((user, done) => done(null, user));
passport3.deserializeUser((user, done) => done(null, user));
passport3.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_SECRET_KEY,
      callbackURL: "/api/auth/google/callback",
      state: true
    },
    async (accessToken, refreshToken, profile, done) => {
      const email = profile.emails && profile.emails.length > 0 ? profile.emails[0].value : "";
      const picture = profile.photos && profile.photos.length > 0 ? profile.photos[0].value : "";
      const googleId = profile.id;
      const displayName = profile.displayName || profile.name?.givenName || profile.name?.familyName;
      if (!email) {
        return done(new Error("Email not found in Google profile"));
      }
      const existingUser = await getUserByGoogleId(googleId);
      if (!existingUser) {
        const userByEmail = await getUserByEmail(email);
        if (userByEmail) {
          if (!userByEmail?.googleId) {
            const updatedUser = await updateUserGoogleInfo(
              userByEmail.id,
              googleId,
              picture
            );
          }
          return done(null, sanitizeUser(userByEmail));
        }
        const usernameBase = email.split("@")[0];
        let username = usernameBase;
        let count = 1;
        while (await isUsernameTaken(username)) {
          username = `${usernameBase}${count}`;
          count++;
        }
        const newUser = await createUserFromGoogle({
          username,
          email,
          googleId,
          profileImageUrl: picture,
          displayName: displayName || username
        });
        done(null, newUser);
      }
      return done(null, sanitizeUser(existingUser));
    }
  )
);
passport3.use(
  new LocalStrategy(
    {
      usernameField: "email",
      passwordField: "password",
      session: true,
      passReqToCallback: true
    },
    function(req, email, password, done) {
      console.log("Local Strategy called with email:", email);
      db.query.users.findFirst({
        where: eq2(users.email, email)
      }).then((user) => {
        if (!user) {
          return done(null, false, { message: "Incorrect email." });
        }
        const comparePassword = bcrypt2.compareSync(password, user.password);
        if (!comparePassword) {
          return done(null, false, { message: "Incorrect password." });
        }
        console.log("User authenticated:", user);
        return done(null, sanitizeUser(user));
      }).catch((err) => {
        console.log("Error in Local Strategy:", err);
        return done(err);
      });
    }
  )
);
async function isUsernameTaken(username) {
  const existingUser = await db.query.users.findFirst({
    where: eq2(users.username, username)
  });
  return !!existingUser;
}
async function getUserByGoogleId(googleId) {
  return await db.query.users.findFirst({
    where: eq2(users.googleId, googleId)
  });
}
async function getUserByEmail(email) {
  return await db.query.users.findFirst({
    where: eq2(users.email, email)
  });
}
async function updateUserGoogleInfo(userId, googleId, profilePicture) {
  const [updatedUser] = await db.update(users).set({
    googleId,
    profileImageUrl: profilePicture || users.profileImageUrl,
    lastLogin: /* @__PURE__ */ new Date()
  }).where(eq2(users.id, userId)).returning();
  return updatedUser;
}
async function createUserFromGoogle(params) {
  const [newUser] = await db.insert(users).values({
    username: params.username,
    email: params.email,
    googleId: params.googleId,
    profileImageUrl: params.profileImageUrl,
    password: null,
    // No password needed for Google users
    lastLogin: /* @__PURE__ */ new Date(),
    createdAt: /* @__PURE__ */ new Date(),
    isEmailVerified: true
    // Assume email is verified for OAuth users
  }).returning();
  return newUser;
}
var passport_default = passport3;

// server/index.ts
var app = express3();
app.use(express3.json({ limit: "50mb" }));
app.use(express3.urlencoded({ extended: false, limit: "50mb" }));
app.use((req, res, next) => {
  const start = Date.now();
  const path3 = req.path;
  let capturedJsonResponse = void 0;
  const originalResJson = res.json;
  res.json = function(bodyJson, ...args) {
    capturedJsonResponse = bodyJson;
    return originalResJson.apply(res, [bodyJson, ...args]);
  };
  res.on("finish", () => {
    const duration = Date.now() - start;
    if (path3.startsWith("/api")) {
      let logLine = `${req.method} ${path3} ${res.statusCode} in ${duration}ms`;
      if (capturedJsonResponse) {
        logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
      }
      if (logLine.length > 80) {
        logLine = logLine.slice(0, 79) + "\u2026";
      }
      log(logLine);
    }
  });
  next();
});
app.use(session({
  secret: "your_secret_key",
  // replace this
  resave: false,
  saveUninitialized: false,
  cookie: { httpOnly: true, secure: false, maxAge: 24 * 60 * 60 * 1e3 },
  // 1 day
  name: "sessionId"
}));
app.use(passport_default.initialize());
app.use(passport_default.session());
(async () => {
  const server = await registerRoutes(app);
  app.use((err, _req, res, _next) => {
    const status = err.status || err.statusCode || 500;
    const message = err.message || "Internal Server Error";
    const timestamp2 = (/* @__PURE__ */ new Date()).toISOString();
    log(`Error: ${status} - ${message} at ${timestamp2}`);
    res.status(status).json({ message, timestamp: timestamp2 });
    throw err;
  });
  if (app.get("env") === "development") {
    await setupVite(app, server);
  } else {
    serveStatic(app);
  }
  const port = 5002;
  server.listen({
    port,
    host: "0.0.0.0",
    reusePort: true
  }, () => {
    log(`serving on port ${port}`);
  });
})();
