import { memoize } from 'lodash-es';
import parse from 'parse-css-color';
import { v4 as uuidv4 } from 'uuid';

import { AlignedLyric, SongSection } from './types';

export const songSectionColors = {
  chorus: '#FF6A00',
  preChorus: '#02AF4A',
  verse: '#DE1677',
  bridge: '#D4BB03',
  outro: '#02AF4A',
  intro: '#02AF4A',
  hook: '#7251F7',
  instrumental: '#208BFF',
  song: '#02AF4A',
};

const darken = memoize((color: string, amount: number) => {
  const parsed = parse(color);
  if (!parsed) return '#000000';
  const darker = parsed.values.map((c) =>
    Math.round(Math.max(c * (1 - amount), 0))
  );
  const hex = darker.map((c) => c.toString(16).padStart(2, '0')).join('');
  return `#${hex}`;
});

const useAltIfMatching = (color: string, lastColor: string) => {
  if (color === lastColor) {
    return darken(color, 0.25);
  }
  return color;
};

const colors = Object.values(songSectionColors);

const getHashValue = (seed: string) => {
  const x = Math.sin(
    seed.split('').reduce((acc, char) => {
      return acc + char.charCodeAt(0);
    }, 0) * 10000
  );
  return x - Math.floor(x);
};

export const pickRandomColor = (seed: string = uuidv4()) => {
  const hashValue = getHashValue(seed);
  return colors[Math.floor(hashValue * colors.length) % colors.length];
};

export const getBestEditColor = (name: string) => {
  if (name.toLowerCase().includes('verse')) return songSectionColors.verse;
  if (
    name.toLowerCase().includes('pre') &&
    name.toLowerCase().includes('chorus')
  )
    return songSectionColors.preChorus;
  if (name.toLowerCase().includes('chorus')) return songSectionColors.chorus;
  if (name.toLowerCase().includes('hook')) return songSectionColors.hook;
  if (name.toLowerCase().includes('instrumental'))
    return songSectionColors.instrumental;
  if (name.toLowerCase().includes('bridge')) return songSectionColors.bridge;
  if (name.toLowerCase().includes('outro')) return songSectionColors.outro;
  if (name.toLowerCase().includes('intro')) return songSectionColors.intro;
  return songSectionColors.song;
};

export const makeSectionsFromLyrics = (alignedLyrics: AlignedLyric[]) => {
  const sections: Record<number, SongSection> = {
    0: {
      name: 'Song',
      color: songSectionColors.song,
      lyrics: [] as AlignedLyric[],
    },
  };

  const groupedLyrics = alignedLyrics.reduce(
    (acc, lyric) => {
      if (lyric.timing?.type === 'point') {
        acc.push({
          name: lyric.text.replace(/[\[\]]/g, ''),
          lyrics: [],
          seconds: lyric.timing.seconds,
        });
      } else {
        const lastGroup = acc[acc.length - 1];
        if (lastGroup) {
          lastGroup.lyrics.push(lyric);
        }
      }
      return acc;
    },
    [
      {
        name: 'Song',
        lyrics: [],
        seconds: 0,
      },
    ] as { name: string; lyrics: AlignedLyric[]; seconds: number }[]
  );
  if (groupedLyrics.length) {
    sections[0].name = 'Intro';
  } else {
    sections[0].lyrics = alignedLyrics;
  }

  let lastSectionColor = songSectionColors.song;

  groupedLyrics.forEach((group) => {
    if (group.lyrics.length === 0) return;
    const color = useAltIfMatching(
      getBestEditColor(group.name),
      lastSectionColor
    );
    lastSectionColor = color;
    sections[group.seconds] = {
      name: group.name,
      color,
      lyrics: group.lyrics,
    };
  });

  return sections;
};

export default function getSections(alignedLyrics: AlignedLyric[]) {
  if (alignedLyrics.length) {
    return makeSectionsFromLyrics(alignedLyrics);
  } else {
    return {
      0: {
        name: 'Song',
        color: pickRandomColor(),
        lyrics: [] as AlignedLyric[],
      },
    };
  }
}
