import dotenv from "dotenv";
import {
  Client,
  GatewayIntentBits,
  Collection,
  Events,
  BaseInteraction, // Use a base type for now, refine later if needed
  SlashCommandBuilder, // Assuming commands use this
} from "discord.js";
import { Shoukaku, Connectors, NodeOption, ShoukakuOptions } from "shoukaku";
import logger from "./utils/logger"; // Assuming logger uses export default or similar
import fs from "fs";
import path from "path";
// import { fileURLToPath } from 'url'; // Needed for __dirname in ES Modules if module is not CommonJS

// Define Command structure
interface Command {
  data: Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">; // Or appropriate type for your command data
  execute: (_interaction: BaseInteraction, _client: BotClient) => Promise<void>; // Adjust _interaction type if needed
}

// Define Event structure
interface BotEvent {
  name: string; // Should match discord.js event names or custom names
  once?: boolean;
  execute: (..._args: any[]) => void; // Use specific types later if possible
}

// Extend the discord.js Client class to include custom properties
export interface BotClient extends Client {
  // Add export keyword
  commands: Collection<string, Command>;
  shoukaku: Shoukaku;
}

// --- Setup ---
dotenv.config();
// __dirname is available in CommonJS modules, which is set in tsconfig.json

// Validate essential environment variables
if (!process.env.DISCORD_TOKEN) {
  logger.error("DISCORD_TOKEN is missing in the .env file!");
  process.exit(1);
}
if (!process.env.LAVALINK_HOST || !process.env.LAVALINK_PORT || !process.env.LAVALINK_PASSWORD) {
  logger.warn(
    "Lavalink connection details (HOST, PORT, PASSWORD) are missing or incomplete in .env. Music functionality will be limited.",
  );
  // Decide if the bot should exit or continue without music
  // process.exit(1); // Uncomment to exit if Lavalink is mandatory
}

// Create a new Discord _client instance with necessary intents
const _client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildVoiceStates,
    GatewayIntentBits.GuildMessages, // Add if needed for prefix commands or message content
    GatewayIntentBits.MessageContent, // Add if needed for message content
  ],
}) as BotClient; // Assert the type here

// Define Shoukaku nodes
const Nodes: NodeOption[] = [
  {
    name: process.env.LAVALINK_NAME || "lavalink-node-1", // Use an env var or default name
    url: `${process.env.LAVALINK_HOST || "localhost"}:${process.env.LAVALINK_PORT || 2333}`, // Use || 2333 for default port number
    auth: process.env.LAVALINK_PASSWORD || "youshallnotpass", // Password from your Lavalink server config
    secure: process.env.LAVALINK_SECURE === "true", // Set to true if using HTTPS/WSS
  },
];

// Shoukaku _options
const shoukakuOptions: ShoukakuOptions = {
  moveOnDisconnect: false, // Whether to move players to another node when a node disconnects
  resume: true, // Whether to resume players session after Lavalink restarts
  reconnectTries: 3, // Number of attempts to reconnect to Lavalink
  reconnectInterval: 5000, // Interval between reconnect attempts in milliseconds
  // Add other _options as needed
};

// Initialize Shoukaku
_client.shoukaku = new Shoukaku(new Connectors.DiscordJS(_client), Nodes, shoukakuOptions);

// Show the actual Lavalink connection details (without exposing the actual password)
logger.info(
  `Lavalink connection configured to: ${process.env.LAVALINK_HOST}:${process.env.LAVALINK_PORT} (Password: ${process.env.LAVALINK_PASSWORD ? "[SET]" : "[NOT SET]"})`,
);

// Collections for commands
_client.commands = new Collection<string, Command>();

// --- Command Loading ---
const commandsPath = path.join(__dirname, "commands");
// Read .ts files now
const commandFiles = fs.readdirSync(commandsPath).filter((file: string) => file.endsWith(".ts"));

const loadCommands = async () => {
  for (const file of commandFiles) {
    const filePath = path.join(commandsPath, file);
    try {
      // Use dynamic import for ES Modules/CommonJS interop
      const commandModule = await import(filePath);
      const command: Command = commandModule.default || commandModule; // Handle default exports

      if (command && typeof command === "object" && "data" in command && "execute" in command) {
        _client.commands.set(command.data.name, command);
        logger.info(`Loaded command: ${command.data.name}`);
      } else {
        logger.warn(
          `[WARNING] The command at ${filePath} is missing a required "data" or "execute" property or is not structured correctly.`,
        );
      }
    } catch (error: unknown) {
      // Type the error as unknown
      const errorMessage = error instanceof Error ? error.message : String(error);
      logger.error(`Error loading command at ${filePath}: ${errorMessage}`, error);
    }
  }
};

// --- Event Handling ---
const eventsPath = path.join(__dirname, "events");
// Read .ts files now
const eventFiles = fs.readdirSync(eventsPath).filter((file: string) => file.endsWith(".ts"));

const loadEvents = async () => {
  for (const file of eventFiles) {
    const filePath = path.join(eventsPath, file);
    try {
      const eventModule = await import(filePath);
      const event: BotEvent = eventModule.default || eventModule; // Handle default exports

      if (event && typeof event === "object" && "name" in event && "execute" in event) {
        if (event.once) {
          _client.once(event.name, (..._args: any[]) => event.execute(..._args, _client)); // Pass _client
          logger.info(`Loaded event ${event.name} (once)`);
        } else {
          _client.on(event.name, (..._args: any[]) => event.execute(..._args, _client)); // Pass _client
          logger.info(`Loaded event ${event.name}`);
        }
      } else {
        logger.warn(
          `[WARNING] The event at ${filePath} is missing a required "name" or "execute" property or is not structured correctly.`,
        );
      }
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      logger.error(`Error loading event at ${filePath}: ${errorMessage}`, error);
    }
  }
};

// --- Shoukaku Event Handling ---
_client.shoukaku.on("ready", (name: string) =>
  logger.info(`Lavalink Node: ${name} is now connected`),
);
_client.shoukaku.on("error", (name: string, error: Error) =>
  logger.error(`Lavalink Node: ${name} emitted an error: ${error.message}`),
);
_client.shoukaku.on("close", (name: string, code: number, reason: string | undefined) =>
  logger.warn(`Lavalink Node: ${name} closed with code ${code}. Reason: ${reason || "No reason"}`),
);
// Corrected disconnect event signature based on common usage and error TS148
_client.shoukaku.on("disconnect", (name: string, count: number) => {
  logger.warn(
    `Lavalink Node: ${name} disconnected. ${count} players were disconnected from this node.`,
  );
});

// --- Main Execution ---
async function main() {
  await loadCommands();
  await loadEvents();

  // Log in to Discord with your _client's token
  try {
    await _client.login(process.env.DISCORD_TOKEN);
    logger.info("Successfully logged in to Discord.");
  } catch (error: unknown) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    logger.error(`Failed to log in: ${errorMessage}`);
    process.exit(1); // Exit if login fails
  }
}

main().catch((error) => {
  const errorMessage = error instanceof Error ? error.message : String(error);
  logger.error(`Error during bot initialization: ${errorMessage}`, error);
  process.exit(1);
});

// Basic error handling
process.on("unhandledRejection", (reason: unknown, promise: Promise<any>) => {
  const reasonMessage = reason instanceof Error ? reason.message : String(reason);
  logger.error("Unhandled promise rejection:", { reason: reasonMessage, promise });
});
process.on("uncaughtException", (error: Error, origin: NodeJS.UncaughtExceptionOrigin) => {
  logger.error(`Uncaught exception: ${error.message}`, { error, origin });
  // Optional: exit process on critical uncaught exceptions
  // process.exit(1);
});
