import { uuidv4 } from '@firebase/util';
import { makeAutoObservable } from 'mobx';
import { getColorFromColorString } from './utils/color.utils';

import { Color } from './Color';
import colorSort from 'color-sorter';
import { ColorType } from './utils/color.interface';
import {
  Colors,
  DEFAULT_THEME_MODE,
  PaletteType,
  ThemeModeType
} from './palette.interface';

export class Palette {
  title: string;
  id: string;
  description?: string;
  mode: ThemeModeType;
  colors: Colors;
  sortColorsBy: 'key' | 'color';
  /**
   * The theme id for which this palette is for
   */
  themeId: string;
  /**
   * When palette is copied or imported from another theme it will have a
   * reference to the original theme id
   */
  referenceThemeId: string;

  constructor(palette?: PaletteType) {
    this.title = palette?.title || 'Untitled Palette';
    this.id = palette?.id || Palette.autoGenId(this.title);
    this.description = palette?.description;
    this.mode = palette?.mode || DEFAULT_THEME_MODE;
    this.sortColorsBy = 'key';
    this.colors = Palette.colorify(palette?.colors);
    this.referenceThemeId = palette?.referenceThemeId || '';
    this.themeId = palette?.themeId || '';

    makeAutoObservable(this);
  }
  get objectify(): PaletteType {
    const colors: ColorType[] = [];
    Object.keys(this.colors).forEach((key) => {
      colors.push(this.colors[key].objectify);
    });

    return {
      title: this.title || 'Untitled Palette',
      id: this.id,
      description: this.description || '',
      mode: this.mode,
      colors: colors,
      themeId: this.themeId || '',
      referenceThemeId: this.referenceThemeId || ''
    };
  }

  static autoGenId = (title: string) => {
    if (!title) {
      return uuidv4();
    }
    return title?.split(' ').join('-').toLocaleLowerCase();
  };

  get sortedColors() {
    const colors = this.colorsList;
    const colorsBykey: Colors = {};
    colors.forEach((color) => {
      if (color.rgbString) {
        colorsBykey[color.rgbString] = color;
      } else {
        console.warn('Attempted to sort colors that do not exist');
      }
    });
    const rgbaColors = Object.keys(colorsBykey);
    const sortedColorKeys: string[] = colorSort(rgbaColors);

    const sortedColors: Color[] = [];
    sortedColorKeys.forEach((colorKey) => {
      sortedColors.push(colorsBykey[colorKey]);
    });
    return sortedColors;
  }
  setTitle = (value: string) => {
    this.title = value;
    this.setId(Palette.autoGenId(value));
  };

  setId = (id: string) => {
    this.id = id;
  };

  addColors = (colors: ColorType[]) => {
    colors.forEach((color) => {
      this.colors[color.id] = new Color(color);
    });
  };

  setColors = (colors: Record<string, ColorType>) => {
    Object.keys(colors).forEach((key) => {
      const color = colors[key];
      if (typeof color === 'string') {
        // user may be attempting to add a raw color
        // let's attempt to normalize it
        const nColor = getColorFromColorString(color, key);
        if (nColor) {
          this.colors[key] = nColor;
        }
      } else {
        this.colors[key] = new Color(color);
      }
    });
  };

  appendColors = (colors: Colors) => {
    Object.keys(colors).forEach((key) => {
      let color = colors[key];

      this.setColor(color);
    });
  };

  setColor = (color: Color) => {
    this.colors[color.id] = color;
  };

  setMode = (mode: ThemeModeType) => {
    this.mode = mode;
  };

  setDescription = (value: string) => {
    this.description = value;
  };

  setReferenceThemeId = (id: string) => {
    this.referenceThemeId = id;
  };

  get colorsList() {
    return Object.keys(this.colors).map((key) => this.colors[key]);
  }

  addColor = (key: string, value: ColorType) => {
    const color = new Color(value);
    this.colors[key] = color;
  };

  removeColor = (key: string) => {
    if (this.colors[key]) {
      delete this.colors[key];
    }
  };

  deleteAllColors = () => {
    this.colors = {};
  };

  getColorFromKey = (key: string): string | undefined => {
    const color = this.colors[key];
    return color?.rgbString;
  };

  /**
   * ColorType[] --> Record<string, Color>
   * @param colors colors list
   * @returns the colors list in Color
   */
  static colorify = (colors?: ColorType[]): Colors => {
    if (!colors) {
      return {};
    }
    const _colors: Colors = {};
    colors.forEach((color: ColorType) => {
      _colors[color.id] = new Color(color);
    });

    return _colors;
  };
}
