import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { GenericMutationCtx } from "convex/server";
import { DataModel, Id } from "./_generated/dataModel";
import { internal } from "./_generated/api";

type TableWithChannelIndex =
  | "channelKeys"
  | "dmParticipants"
  | "typingIndicators"
  | "voiceStates"
  | "channelReadState";

async function deleteByChannel(
  ctx: GenericMutationCtx<DataModel>,
  table: TableWithChannelIndex,
  channelId: Id<"channels">
) {
  const docs = await (ctx.db.query(table) as any)
    .withIndex("by_channel", (q: any) => q.eq("channelId", channelId))
    .collect();
  for (const doc of docs) {
    await ctx.db.delete(doc._id);
  }
}

// List all non-DM channels
export const list = query({
  args: {},
  returns: v.array(
    v.object({
      _id: v.id("channels"),
      _creationTime: v.number(),
      name: v.string(),
      type: v.string(),
      categoryId: v.optional(v.id("categories")),
      topic: v.optional(v.string()),
      position: v.optional(v.number()),
    })
  ),
  handler: async (ctx) => {
    const channels = await ctx.db.query("channels").collect();
    return channels
      .filter((c) => c.type !== "dm")
      .sort((a, b) => (a.position ?? 0) - (b.position ?? 0) || a.name.localeCompare(b.name));
  },
});

// Get single channel by ID
export const get = query({
  args: { id: v.id("channels") },
  returns: v.union(
    v.object({
      _id: v.id("channels"),
      _creationTime: v.number(),
      name: v.string(),
      type: v.string(),
      categoryId: v.optional(v.id("categories")),
      topic: v.optional(v.string()),
      position: v.optional(v.number()),
    }),
    v.null()
  ),
  handler: async (ctx, args) => {
    return await ctx.db.get(args.id);
  },
});

// Create new channel
export const create = mutation({
  args: {
    name: v.string(),
    type: v.optional(v.string()),
    categoryId: v.optional(v.id("categories")),
    topic: v.optional(v.string()),
    position: v.optional(v.number()),
  },
  returns: v.object({ id: v.id("channels") }),
  handler: async (ctx, args) => {
    if (!args.name.trim()) {
      throw new Error("Channel name required");
    }

    const existing = await ctx.db
      .query("channels")
      .withIndex("by_name", (q) => q.eq("name", args.name))
      .unique();

    if (existing) {
      throw new Error("Channel already exists");
    }

    // Auto-calculate position if not provided
    let position = args.position;
    if (position === undefined) {
      const allChannels = await ctx.db.query("channels").collect();
      const sameCategory = allChannels.filter(
        (c) => c.categoryId === args.categoryId && c.type !== "dm"
      );
      const maxPos = sameCategory.reduce(
        (max, c) => Math.max(max, c.position ?? 0),
        -1000
      );
      position = maxPos + 1000;
    }

    const id = await ctx.db.insert("channels", {
      name: args.name,
      type: args.type || "text",
      categoryId: args.categoryId,
      topic: args.topic,
      position,
    });

    return { id };
  },
});

// Update channel topic
export const updateTopic = mutation({
  args: {
    id: v.id("channels"),
    topic: v.string(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const channel = await ctx.db.get(args.id);
    if (!channel) throw new Error("Channel not found");
    await ctx.db.patch(args.id, { topic: args.topic });
    return null;
  },
});

// Rename channel
export const rename = mutation({
  args: {
    id: v.id("channels"),
    name: v.string(),
  },
  returns: v.object({
    _id: v.id("channels"),
    _creationTime: v.number(),
    name: v.string(),
    type: v.string(),
    categoryId: v.optional(v.id("categories")),
    topic: v.optional(v.string()),
    position: v.optional(v.number()),
  }),
  handler: async (ctx, args) => {
    if (!args.name.trim()) {
      throw new Error("Name required");
    }

    const channel = await ctx.db.get(args.id);
    if (!channel) {
      throw new Error("Channel not found");
    }

    await ctx.db.patch(args.id, { name: args.name });
    return { ...channel, name: args.name };
  },
});

// Move a channel to a different category with a new position
export const moveChannel = mutation({
  args: {
    id: v.id("channels"),
    categoryId: v.optional(v.id("categories")),
    position: v.number(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const channel = await ctx.db.get(args.id);
    if (!channel) throw new Error("Channel not found");
    await ctx.db.patch(args.id, {
      categoryId: args.categoryId,
      position: args.position,
    });
    return null;
  },
});

// Batch reorder channels
export const reorderChannels = mutation({
  args: {
    updates: v.array(
      v.object({
        id: v.id("channels"),
        categoryId: v.optional(v.id("categories")),
        position: v.number(),
      })
    ),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    for (const u of args.updates) {
      await ctx.db.patch(u.id, {
        categoryId: u.categoryId,
        position: u.position,
      });
    }
    return null;
  },
});

// Delete channel + cascade messages and keys
export const remove = mutation({
  args: { id: v.id("channels") },
  returns: v.object({ success: v.boolean() }),
  handler: async (ctx, args) => {
    const channel = await ctx.db.get(args.id);
    if (!channel) {
      throw new Error("Channel not found");
    }

    // Delete reactions for all messages in this channel
    const messages = await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channelId", args.id))
      .collect();
    for (const msg of messages) {
      const reactions = await ctx.db
        .query("messageReactions")
        .withIndex("by_message", (q) => q.eq("messageId", msg._id))
        .collect();
      for (const r of reactions) {
        await ctx.db.delete(r._id);
      }
      await ctx.db.delete(msg._id);
    }

    await deleteByChannel(ctx, "channelKeys", args.id);
    await deleteByChannel(ctx, "dmParticipants", args.id);
    await deleteByChannel(ctx, "typingIndicators", args.id);
    await deleteByChannel(ctx, "voiceStates", args.id);
    await deleteByChannel(ctx, "channelReadState", args.id);

    // Clear AFK setting if this channel was the AFK channel
    await ctx.runMutation(internal.serverSettings.clearAfkChannel, { channelId: args.id });

    await ctx.db.delete(args.id);

    return { success: true };
  },
});
