import { DiscordImage, MJConfig } from "./interfaces";

export const Commands = [
  "ask",
  "blend",
  "describe",
  "fast",
  "turbo",
  "help",
  "imagine",
  "info",
  "prefer",
  "private",
  "public",
  "relax",
  "settings",
  "show",
  "stealth",
  "shorten",
  "subscribe",
] as const;
export type CommandName = (typeof Commands)[number];
function getCommandName(name: string): CommandName | undefined {
  for (const command of Commands) {
    if (command === name) {
      return command;
    }
  }
}

export class Command {
  constructor(public config: MJConfig) {}
  cache: Partial<Record<CommandName, any>> = {};

  async cacheCommand(name: CommandName) {
    if (this.cache[name] !== undefined) {
      return this.cache[name];
    }
    if (this.config.ChannelId) {
      const command = await this.getCommand(name);
      this.cache[name] = command;
    }
    if (this.cache[name] !== undefined) {
      return this.cache[name];
    }
    return await this.getCommand(name);
  }
  async userCommand() {
    let url = `${this.config.DiscordBaseUrl}/api/v9/users/@me/application-command-index`;
    const response = await this.config.fetch(url, {
      headers: { authorization: this.config.SalaiToken },
    });
    if (response.status > 300) {
      console.error(response);
      throw new Error(`Failed to get application_commands of user for command`);
    }
    const data = await response.json();

    if (data?.application_commands) {
      data.application_commands.forEach((command: any) => {
        const name = getCommandName(command.name);
        if (name) {
          this.cache[name] = command;
        }
      });
    }
  }
  async allCommand() {
    try {
      let url;
      if (!this.config.ServerId) {
        url = `${this.config.DiscordBaseUrl}/api/v9/channels/${this.config.ChannelId}/application-command-index`;
      } else {
        url = `${this.config.DiscordBaseUrl}/api/v9/guilds/${this.config.ServerId}/application-command-index`;
      }
      const response = await this.config.fetch(url, {
        headers: { authorization: this.config.SalaiToken },
      });
      if (response.status > 300) {
        console.error(response);
        throw new Error(`Failed to get application_commands for command`);
      }
      const data = await response.json();

      if (data?.application_commands) {
        data.application_commands.forEach((command: any) => {
          const name = getCommandName(command.name);
          if (name) {
            this.cache[name] = command;
          }
        });
      } 
    } catch (error) {
      console.error('Fail to get command from channel or server');
    }
    if (!this.cache['info']) {
      await this.userCommand();
    }
  }

  async getCommand(name: CommandName) {
    if (this.cache[name]) {
      return this.cache[name];
    }
    await this.allCommand();

    if (this.cache[name]) {
      return this.cache[name];
    }
    throw new Error(`Missing permission, please make sure you have added MJ Bot.`);
  }
  async imaginePayload(prompt: string, nonce?: string) {
    const data = await this.commandData("imagine", [
      {
        type: 3,
        name: "prompt",
        value: prompt,
      },
    ]);
    return this.data2Paylod(data, nonce);
  }
  async PreferPayload(nonce?: string) {
    const data = await this.commandData("prefer", [
      {
        type: 1,
        name: "remix",
        options: [],
      },
    ]);
    return this.data2Paylod(data, nonce);
  }

  async shortenPayload(prompt: string, nonce?: string) {
    const data = await this.commandData("shorten", [
      {
        type: 3,
        name: "prompt",
        value: prompt,
      },
    ]);
    return this.data2Paylod(data, nonce);
  }

  async describePayload(image: DiscordImage, nonce?: string) {
    const data = await this.commandData(
      "describe",
      [
        {
          type: 11,
          name: "image",
          value: image.id,
        },
      ],
      [
        {
          id: <string>image.id,
          filename: image.filename,
          uploaded_filename: image.upload_filename,
        },
      ]
    );
    return this.data2Paylod(data, nonce);
  }

  async commandPayload(name: CommandName, nonce?:string) {
    const data = await this.commandData(name);
    return this.data2Paylod(data, nonce);
  }

  protected async commandData(
    name: CommandName,
    options: any[] = [],
    attachments: any[] = []
  ) {
    const command = await this.getCommand(name);
    const data = {
      version: command.version,
      id: command.id,
      name: command.name,
      type: command.type,
      options,
      application_command: command,
      attachments,
    };
    return data;
  }
  //TODO data type
  protected data2Paylod(data: any, nonce?: string) {
    const payload = {
      type: 2,
      application_id: data.application_command.application_id,
      guild_id: this.config.ServerId,
      channel_id: this.config.ChannelId,
      session_id: this.config.SessionId,
      nonce,
      data,
    };
    return payload;
  }
}
