import { AndroidConfig, ConfigPlugin, withDangerousMod } from '@expo/config-plugins';
import {
  buildResourceItem,
  readResourcesXMLAsync,
} from '@expo/config-plugins/build/android/Resources';
import { createAndroidManifestPlugin } from '@expo/config-plugins/build/plugins/android-plugins';
import { writeXMLAsync } from '@expo/config-plugins/build/utils/XML';
import { ExpoConfig } from '@expo/config-types';
import { generateImageAsync } from '@expo/image-utils';
import fs from 'fs-extra';
import path from 'path';

const { Colors } = AndroidConfig;
const { ANDROID_RES_PATH, dpiValues } = AndroidConfig.Icon;
const {
  addMetaDataItemToMainApplication,
  getMainApplicationOrThrow,
  removeMetaDataItemFromMainApplication,
} = AndroidConfig.Manifest;
const BASELINE_PIXEL_SIZE = 24;
export const META_DATA_NOTIFICATION_ICON = 'expo.modules.notifications.default_notification_icon';
export const META_DATA_NOTIFICATION_ICON_COLOR =
  'expo.modules.notifications.default_notification_color';
export const NOTIFICATION_ICON = 'notification_icon';
export const NOTIFICATION_ICON_RESOURCE = `@drawable/${NOTIFICATION_ICON}`;
export const NOTIFICATION_ICON_COLOR = 'notification_icon_color';
export const NOTIFICATION_ICON_COLOR_RESOURCE = `@color/${NOTIFICATION_ICON_COLOR}`;

export const withNotificationIcons: ConfigPlugin = config => {
  return withDangerousMod(config, [
    'android',
    async config => {
      await setNotificationIconAsync(config, config.modRequest.projectRoot);
      return config;
    },
  ]);
};

export const withNotificationIconColor: ConfigPlugin = config => {
  return withDangerousMod(config, [
    'android',
    async config => {
      await setNotificationIconColorAsync(config, config.modRequest.projectRoot);
      return config;
    },
  ]);
};

export const withNotificationManifest = createAndroidManifestPlugin(
  setNotificationConfigAsync,
  'withNotificationManifest'
);

export function getNotificationIcon(config: ExpoConfig) {
  return config.notification?.icon || null;
}

export function getNotificationColor(config: ExpoConfig) {
  return config.notification?.color || null;
}

/**
 * Applies configuration for expo-notifications, including
 * the notification icon and notification color.
 */
export async function setNotificationIconAsync(config: ExpoConfig, projectRoot: string) {
  const icon = getNotificationIcon(config);
  if (icon) {
    await writeNotificationIconImageFilesAsync(icon, projectRoot);
  } else {
    await removeNotificationIconImageFilesAsync(projectRoot);
  }
}

export async function setNotificationConfigAsync(
  config: ExpoConfig,
  manifest: AndroidConfig.Manifest.AndroidManifest
) {
  const icon = getNotificationIcon(config);
  const color = getNotificationColor(config);
  const mainApplication = getMainApplicationOrThrow(manifest);
  if (icon) {
    addMetaDataItemToMainApplication(
      mainApplication,
      META_DATA_NOTIFICATION_ICON,
      NOTIFICATION_ICON_RESOURCE,
      'resource'
    );
  } else {
    removeMetaDataItemFromMainApplication(mainApplication, META_DATA_NOTIFICATION_ICON);
  }
  if (color) {
    addMetaDataItemToMainApplication(
      mainApplication,
      META_DATA_NOTIFICATION_ICON_COLOR,
      NOTIFICATION_ICON_COLOR_RESOURCE,
      'resource'
    );
  } else {
    removeMetaDataItemFromMainApplication(mainApplication, META_DATA_NOTIFICATION_ICON_COLOR);
  }
  return manifest;
}

export async function setNotificationIconColorAsync(config: ExpoConfig, projectRoot: string) {
  const color = getNotificationColor(config);
  const colorsXmlPath = await Colors.getProjectColorsXMLPathAsync(projectRoot);
  let colorsJson = await readResourcesXMLAsync({ path: colorsXmlPath });
  if (color) {
    const colorItemToAdd = buildResourceItem({ name: NOTIFICATION_ICON_COLOR, value: color });
    colorsJson = Colors.setColorItem(colorItemToAdd, colorsJson);
  } else {
    colorsJson = Colors.removeColorItem(NOTIFICATION_ICON_COLOR, colorsJson);
  }
  await writeXMLAsync({ path: colorsXmlPath, xml: colorsJson });
}

async function writeNotificationIconImageFilesAsync(icon: string, projectRoot: string) {
  await Promise.all(
    Object.values(dpiValues).map(async ({ folderName, scale }) => {
      const drawableFolderName = folderName.replace('mipmap', 'drawable');
      const dpiFolderPath = path.resolve(projectRoot, ANDROID_RES_PATH, drawableFolderName);
      await fs.ensureDir(dpiFolderPath);
      const iconSizePx = BASELINE_PIXEL_SIZE * scale;

      try {
        const resizedIcon = (
          await generateImageAsync(
            { projectRoot, cacheType: 'android-notification' },
            {
              src: icon,
              width: iconSizePx,
              height: iconSizePx,
              resizeMode: 'cover',
              backgroundColor: 'transparent',
            }
          )
        ).source;
        await fs.writeFile(path.resolve(dpiFolderPath, NOTIFICATION_ICON + '.png'), resizedIcon);
      } catch (e) {
        throw new Error('Encountered an issue resizing Android notification icon: ' + e);
      }
    })
  );
}

async function removeNotificationIconImageFilesAsync(projectRoot: string) {
  await Promise.all(
    Object.values(dpiValues).map(async ({ folderName }) => {
      const drawableFolderName = folderName.replace('mipmap', 'drawable');
      const dpiFolderPath = path.resolve(projectRoot, ANDROID_RES_PATH, drawableFolderName);
      await fs.remove(path.resolve(dpiFolderPath, NOTIFICATION_ICON + '.png'));
    })
  );
}

export const withNotificationsAndroid: ConfigPlugin = config => {
  config = withNotificationIconColor(config);
  config = withNotificationIcons(config);
  config = withNotificationManifest(config);
  return config;
};
