import { COOKIE_NAME } from "../shared/const.js";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { getSessionCookieOptions } from "./_core/cookies";
import { systemRouter } from "./_core/systemRouter";
import { protectedProcedure, publicProcedure, router } from "./_core/trpc";
import { requestPhoneOtp, verifyPhoneOtp } from "./otp";
import { createPhoneSession, verifyPhoneSession } from "./phone-session";
import { createJordanianSupportReply } from "./support-assistant";
import * as db from "./db";

async function getPhoneFromSession(sessionToken: string) {
  try {
    return await verifyPhoneSession(sessionToken);
  } catch {
    throw new TRPCError({ code: "UNAUTHORIZED", message: "انتهت جلسة الهاتف. يرجى تسجيل الدخول مجدداً." });
  }
}

export const appRouter = router({
  // if you need to use socket.io, read and register route in server/_core/index.ts, all api should start with '/api/' so that the gateway can route correctly
  system: systemRouter,
  auth: router({
    me: publicProcedure.query((opts) => opts.ctx.user),
    logout: publicProcedure.mutation(({ ctx }) => {
      const cookieOptions = getSessionCookieOptions(ctx.req);
      ctx.res.clearCookie(COOKIE_NAME, { ...cookieOptions, maxAge: -1 });
      return {
        success: true,
      } as const;
    }),
  }),
  otp: router({
    send: publicProcedure.input(z.object({ phone: z.string().min(9).max(24) })).mutation(async ({ input }) => {
      try {
        return await requestPhoneOtp(input.phone);
      } catch (error) {
        throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر إرسال رمز التحقق" });
      }
    }),
    verify: publicProcedure.input(z.object({ phone: z.string().min(9).max(24), code: z.string().regex(/^\d{4}$/) })).mutation(async ({ input }) => {
      try {
        const result = await verifyPhoneOtp(input.phone, input.code);
        if (!result.verified) {
          const messages = { expired: "انتهت صلاحية الرمز. اطلب رمزاً جديداً.", locked: "تم تجاوز عدد المحاولات المسموح. اطلب رمزاً جديداً.", invalid: "رمز التحقق غير صحيح." };
          throw new TRPCError({ code: "BAD_REQUEST", message: messages[result.reason] });
        }
        const sessionToken = await createPhoneSession(result.phone);
        await db.ensureCustomer(result.phone);
        return { ...result, sessionToken };
      } catch (error) {
        if (error instanceof TRPCError) throw error;
        throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر التحقق من الرمز" });
      }
    }),
  }),
  customer: router({
    profile: publicProcedure.input(z.object({ sessionToken: z.string().min(20) })).query(async ({ input }) => {
      const phone = await getPhoneFromSession(input.sessionToken);
      return db.ensureCustomer(phone);
    }),
    saveProfile: publicProcedure.input(z.object({ sessionToken: z.string().min(20), fullName: z.string().trim().max(160).optional(), email: z.string().email().max(320).optional() })).mutation(async ({ input }) => {
      const phone = await getPhoneFromSession(input.sessionToken);
      return db.updateCustomerProfile(phone, { fullName: input.fullName, email: input.email });
    }),
    uploadAvatar: publicProcedure.input(z.object({ sessionToken: z.string().min(20), base64: z.string().min(16).max(2_000_000), mimeType: z.enum(["image/jpeg", "image/png", "image/webp"]) })).mutation(async ({ input }) => {
      try {
        const phone = await getPhoneFromSession(input.sessionToken);
        return await db.uploadCustomerAvatar(phone, { base64: input.base64, mimeType: input.mimeType });
      } catch (error) {
        throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر حفظ الصورة الشخصية." });
      }
    }),
  }),
  locations: router({
    list: publicProcedure.input(z.object({ sessionToken: z.string().min(20) })).query(async ({ input }) => db.listCustomerLocations(await getPhoneFromSession(input.sessionToken))),
    create: publicProcedure.input(z.object({ sessionToken: z.string().min(20), label: z.string().trim().min(1).max(80), kind: z.enum(["home", "business", "other"]), address: z.string().trim().min(3).max(2000), details: z.string().max(2000).optional() })).mutation(async ({ input }) => {
      const phone = await getPhoneFromSession(input.sessionToken);
      return db.createCustomerLocation(phone, { label: input.label, kind: input.kind, address: input.address, details: input.details });
    }),
  }),
  orders: router({
    list: publicProcedure.input(z.object({ sessionToken: z.string().min(20) })).query(async ({ input }) => db.listCustomerOrders(await getPhoneFromSession(input.sessionToken))),
    create: publicProcedure.input(z.object({ sessionToken: z.string().min(20), locationId: z.number().int().positive(), serviceCode: z.string().trim().min(1).max(80), serviceTitle: z.string().trim().min(1).max(180), preferredDate: z.string().max(80).optional(), note: z.string().max(4000).optional() })).mutation(async ({ input }) => {
      const phone = await getPhoneFromSession(input.sessionToken);
      return db.createCustomerOrder(phone, { locationId: input.locationId, serviceCode: input.serviceCode, serviceTitle: input.serviceTitle, preferredDate: input.preferredDate, note: input.note });
    }),
    financials: publicProcedure.input(z.object({ sessionToken: z.string().min(20), orderId: z.number().int().positive() })).query(async ({ input }) => db.getCustomerOrderFinancials(await getPhoneFromSession(input.sessionToken), input.orderId)),
    acceptQuote: publicProcedure.input(z.object({ sessionToken: z.string().min(20), orderId: z.number().int().positive(), quoteId: z.number().int().positive() })).mutation(async ({ input }) => {
      try { return await db.acceptCustomerQuote(await getPhoneFromSession(input.sessionToken), { orderId: input.orderId, quoteId: input.quoteId }); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر قبول عرض السعر." }); }
    }),
    rate: publicProcedure.input(z.object({ sessionToken: z.string().min(20), orderId: z.number().int().positive(), stars: z.number().int().min(1).max(5), comment: z.string().trim().max(2000).optional() })).mutation(async ({ input }) => {
      try {
        return await db.saveOrderRating(await getPhoneFromSession(input.sessionToken), { orderId: input.orderId, stars: input.stars, comment: input.comment });
      } catch (error) {
        throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر حفظ التقييم." });
      }
    }),
  }),
  support: router({
    conversation: publicProcedure.input(z.object({ sessionToken: z.string().min(20), conversationId: z.number().int().positive().optional() })).query(async ({ input }) => db.getSupportConversation(await getPhoneFromSession(input.sessionToken), input.conversationId)),
    quickPrompts: publicProcedure.query(() => db.listSupportQuickPrompts()),
    send: publicProcedure.input(z.object({ sessionToken: z.string().min(20), body: z.string().trim().min(1).max(4000), language: z.enum(["ar", "en"]).default("ar") })).mutation(async ({ input }) => {
      const phone = await getPhoneFromSession(input.sessionToken);
      const created = await db.createSupportMessage(phone, input.body);
      if (!created) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "تعذر حفظ رسالتك حالياً." });
      const agentContext = created.conversation.status === "ai_active" ? await db.getSupportAgentContext(phone, created.conversation.id) : null;
      const assistantMessage = created.conversation.status === "ai_active"
        ? await db.createAssistantSupportMessage(created.conversation.id, await createJordanianSupportReply(input.body, input.language, agentContext))
        : null;
      return { ...created, assistantMessage };
    }),
    takeOver: protectedProcedure.input(z.object({ conversationId: z.number().int().positive() })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة لإدارة بوابة الإتقان فقط." });
      return db.takeOverSupportConversation(input.conversationId, ctx.user.id);
    }),
  }),
  catalog: router({
    content: publicProcedure.input(z.object({ section: z.string().min(1).max(80).optional() }).optional()).query(async ({ input }) => db.getAppContent(input?.section)),
    categories: publicProcedure.query(async () => db.getActiveCategories()),
    services: publicProcedure.input(z.object({ featuredOnly: z.boolean().optional() }).optional()).query(async ({ input }) => db.getActiveServices(input?.featuredOnly ?? false)),
    directory: publicProcedure.query(async () => {
      const [categories, services] = await Promise.all([
        db.getActiveCategories(),
        db.getActiveServices(false),
      ]);
      return { categories, services };
    }),
    promotions: publicProcedure.query(async () => db.getActivePromotions()),
  }),
  notifications: router({
    list: publicProcedure.input(z.object({ sessionToken: z.string().min(20) })).query(async ({ input }) => db.getCustomerNotifications(await getPhoneFromSession(input.sessionToken))),
    unreadCount: publicProcedure.input(z.object({ sessionToken: z.string().min(20) })).query(async ({ input }) => ({ count: await db.getCustomerUnreadNotificationCount(await getPhoneFromSession(input.sessionToken)) })),
    registerDevice: publicProcedure.input(z.object({ sessionToken: z.string().min(20), expoPushToken: z.string().trim().min(10).max(255), platform: z.enum(["ios", "android"]), preferredLanguage: z.enum(["ar", "en"]), permissionStatus: z.enum(["granted", "provisional"]) })).mutation(async ({ input }) => db.registerCustomerDeviceToken(await getPhoneFromSession(input.sessionToken), input)),
    listDevices: publicProcedure.input(z.object({ sessionToken: z.string().min(20) })).query(async ({ input }) => db.getCustomerDevices(await getPhoneFromSession(input.sessionToken))),
    revokeDevice: publicProcedure.input(z.object({ sessionToken: z.string().min(20), deviceId: z.number().int().positive() })).mutation(async ({ input }) => ({ ok: await db.revokeCustomerDevice(await getPhoneFromSession(input.sessionToken), input.deviceId) })),
    byId: publicProcedure.input(z.object({ sessionToken: z.string().min(20), notificationId: z.number().int().positive() })).query(async ({ input }) => db.getCustomerNotification(await getPhoneFromSession(input.sessionToken), input.notificationId)),
    markRead: publicProcedure.input(z.object({ sessionToken: z.string().min(20), notificationId: z.number().int().positive() })).mutation(async ({ input }) => ({ ok: await db.markCustomerNotificationRead(await getPhoneFromSession(input.sessionToken), input.notificationId) })),
    markAllRead: publicProcedure.input(z.object({ sessionToken: z.string().min(20) })).mutation(async ({ input }) => { await db.markCustomerNotificationsRead(await getPhoneFromSession(input.sessionToken)); return { ok: true }; }),
  }),
  adminNotifications: router({
    sendToCustomer: protectedProcedure.input(z.object({
      customerId: z.number().int().positive(),
      category: z.enum(["orders", "payments", "offers", "support", "general"]),
      titleAr: z.string().trim().min(1).max(240),
      titleEn: z.string().trim().min(1).max(240),
      bodyAr: z.string().trim().min(1).max(4000),
      bodyEn: z.string().trim().min(1).max(4000),
      icon: z.string().trim().max(80).optional(),
      imageUrl: z.string().url().max(2048).optional(),
      deepLink: z.string().startsWith("/").max(255).optional(),
    })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      return db.createCustomerNotification(input);
    }),
  }),
  adminTasks: router({
    orders: protectedProcedure.input(z.object({ status: z.enum(["received", "confirmed", "on_the_way", "in_progress", "completed", "cancelled"]).optional() }).optional()).query(({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      return db.listAdminOrders(input?.status);
    }),
    updateOrderStatus: protectedProcedure.input(z.object({ orderId: z.number().int().positive(), status: z.enum(["received", "confirmed", "on_the_way", "in_progress", "completed", "cancelled"]) })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      try { return await db.updateAdminOrderStatus({ ...input, adminId: ctx.user.id }); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر تحديث حالة الطلب." }); }
    }),
    technicians: protectedProcedure.query(({ ctx }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      return db.listActiveTechnicians();
    }),
    teams: protectedProcedure.query(({ ctx }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      return db.listTechnicianTeams();
    }),
    list: protectedProcedure.input(z.object({ status: z.string().optional() }).optional()).query(({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      return db.listAdminTasks(input?.status);
    }),
    assignableOrders: protectedProcedure.query(({ ctx }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      return db.listAssignableOrders();
    }),
    byId: protectedProcedure.input(z.object({ taskId: z.number().int().positive() })).query(({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      return db.getAdminTaskDetail(input.taskId);
    }),
    assign: protectedProcedure.input(z.object({
      orderId: z.number().int().positive(),
      titleAr: z.string().trim().min(2).max(240),
      titleEn: z.string().trim().min(2).max(240),
      instructionsAr: z.string().trim().max(4000).optional(),
      instructionsEn: z.string().trim().max(4000).optional(),
      priority: z.enum(["low", "normal", "high", "urgent"]),
      scheduledStartAt: z.coerce.date().optional(),
      scheduledEndAt: z.coerce.date().optional(),
      technicianIds: z.array(z.number().int().positive()).min(1).max(12),
      primaryTechnicianId: z.number().int().positive().optional(),
    })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      try { return await db.createAdminTask({ ...input, adminId: ctx.user.id }); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر تعيين المهمة." }); }
    }),
    reviewCompletion: protectedProcedure.input(z.object({ taskId: z.number().int().positive(), approve: z.boolean(), rejectionNoteAr: z.string().trim().max(2000).optional(), rejectionNoteEn: z.string().trim().max(2000).optional() })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      try { return await db.reviewTaskCompletion(ctx.user.id, input); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر مراجعة المهمة." }); }
    }),
  }),
  adminQuotes: router({
    orders: protectedProcedure.query(({ ctx }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      return db.listAdminOrders();
    }),
    byOrder: protectedProcedure.input(z.object({ orderId: z.number().int().positive() })).query(({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      return db.getAdminOrderQuote(input.orderId);
    }),
    create: protectedProcedure.input(z.object({
      orderId: z.number().int().positive(),
      totalAmountFils: z.number().int().positive(),
      depositAmountFils: z.number().int().min(0),
      notes: z.string().trim().max(4000).optional(),
      expiresAt: z.coerce.date().optional(),
      contractDocumentUrl: z.string().url().max(2048).optional(),
      items: z.array(z.object({ titleAr: z.string().trim().min(1).max(240), titleEn: z.string().trim().min(1).max(240), description: z.string().trim().max(2000).optional(), quantity: z.number().int().positive().max(999), unitAmountFils: z.number().int().positive() })).min(1).max(30),
    })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للإدارة فقط." });
      try { return await db.createAdminQuote({ ...input, adminId: ctx.user.id }); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر إرسال عرض السعر." }); }
    }),
  }),
  technicianTasks: router({
    list: protectedProcedure.query(({ ctx }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه الصفحة متاحة للفنيين فقط." });
      return db.listTechnicianTasks(ctx.user.id);
    }),
    byId: protectedProcedure.input(z.object({ taskId: z.number().int().positive() })).query(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه المهمة متاحة للفني المعيّن فقط." });
      try { return await db.getTechnicianTaskDetail(ctx.user.id, input.taskId); }
      catch (error) { throw new TRPCError({ code: "FORBIDDEN", message: error instanceof Error ? error.message : "تعذر الوصول إلى المهمة." }); }
    }),
    respond: protectedProcedure.input(z.object({ taskId: z.number().int().positive(), accept: z.boolean() })).mutation(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للفني المعيّن فقط." });
      try { return await db.respondToTaskAssignment(ctx.user.id, input); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر معالجة التعيين." }); }
    }),
    updateStatus: protectedProcedure.input(z.object({ taskId: z.number().int().positive(), status: z.enum(["en_route", "on_site", "in_progress"]) })).mutation(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للفني المعيّن فقط." });
      try { return await db.updateTechnicianTaskStatus(ctx.user.id, input); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر تحديث حالة المهمة." }); }
    }),
    toggleChecklist: protectedProcedure.input(z.object({ taskId: z.number().int().positive(), itemId: z.number().int().positive(), completed: z.boolean() })).mutation(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للفني المعيّن فقط." });
      try { return await db.toggleTaskChecklistItem(ctx.user.id, input); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر تحديث قائمة التحقق." }); }
    }),
    startTimer: protectedProcedure.input(z.object({ taskId: z.number().int().positive() })).mutation(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للفني المعيّن فقط." });
      try { return await db.startTaskTimer(ctx.user.id, input.taskId); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر بدء مؤقت المهمة." }); }
    }),
    stopTimer: protectedProcedure.input(z.object({ taskId: z.number().int().positive(), noteAr: z.string().trim().max(1000).optional(), noteEn: z.string().trim().max(1000).optional() })).mutation(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للفني المعيّن فقط." });
      try { return await db.stopTaskTimer(ctx.user.id, input); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر إيقاف مؤقت المهمة." }); }
    }),
    addEvidence: protectedProcedure.input(z.object({ taskId: z.number().int().positive(), kind: z.enum(["before", "during", "after", "issue", "receipt", "signature"]), url: z.string().trim().min(1).max(2048), captionAr: z.string().trim().max(240).optional(), captionEn: z.string().trim().max(240).optional() })).mutation(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للفني المعيّن فقط." });
      try { return await db.createTaskEvidence(ctx.user.id, input); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر حفظ توثيق المهمة." }); }
    }),
    requestPart: protectedProcedure.input(z.object({ taskId: z.number().int().positive(), titleAr: z.string().trim().min(2).max(200), titleEn: z.string().trim().min(2).max(200), quantity: z.number().int().min(1).max(999), reasonAr: z.string().trim().max(2000).optional(), reasonEn: z.string().trim().max(2000).optional(), inventoryItemId: z.number().int().positive().optional() })).mutation(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للفني المعيّن فقط." });
      try { return await db.createTaskPartRequest(ctx.user.id, input); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر إرسال طلب القطعة." }); }
    }),
    sendMessage: protectedProcedure.input(z.object({ taskId: z.number().int().positive(), bodyAr: z.string().trim().min(1).max(4000), bodyEn: z.string().trim().min(1).max(4000), attachmentUrl: z.string().trim().max(2048).optional() })).mutation(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للفني المعيّن فقط." });
      try { return await db.createTaskMessage(ctx.user.id, input); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر إرسال الرسالة." }); }
    }),
    submitCompletion: protectedProcedure.input(z.object({ taskId: z.number().int().positive(), summaryAr: z.string().trim().min(2).max(4000), summaryEn: z.string().trim().min(2).max(4000), technicianSignatureUrl: z.string().trim().max(2048).optional(), customerSignatureUrl: z.string().trim().max(2048).optional(), customerSignedName: z.string().trim().max(160).optional() })).mutation(async ({ ctx, input }) => {
      if (String(ctx.user.role) !== "technician") throw new TRPCError({ code: "FORBIDDEN", message: "هذه العملية متاحة للفني المعيّن فقط." });
      try { return await db.submitTaskCompletion(ctx.user.id, input); }
      catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "تعذر إرسال المهمة للمراجعة." }); }
    }),
  }),

  // TODO: add feature routers here, e.g.
  // todo: router({
  //   list: protectedProcedure.query(({ ctx }) =>
  //     db.getUserTodos(ctx.user.id)
  //   ),
  // }),
});

export type AppRouter = typeof appRouter;
