import {Injectable, inject} from '@angular/core';
import {ShowService} from './show.service';
import {ShowTypePipe} from '../../../widget-modules/pipes/show-type-translater/show-type.pipe';
import {ShowSongService} from './show-song.service';
import {Song} from '../../songs/services/song';
import {ShowSong} from './show-song';
import {Show} from './show';
import {ChordMode} from '../../../widget-modules/components/song-text/song-text.component';
import {UserService} from '../../../services/user/user.service';
import {User} from '../../../services/user/user';
import {ConfigService} from '../../../services/config.service';
import {Config} from '../../../services/config';
import {TextRenderingService} from '../../songs/services/text-rendering.service';
import {Section} from '../../songs/services/section';
import {LineType} from '../../songs/services/line-type';
import {Line} from '../../songs/services/line';
import {firstValueFrom} from 'rxjs';

type DocxModule = typeof import('docx');
type DocxDocument = import('docx').Document;
type DocxParagraph = import('docx').Paragraph;
type DocxSectionOptions = import('docx').ISectionOptions;

export interface DownloadOptions {
  copyright?: boolean;
  chordMode?: ChordMode;
}

@Injectable({
  providedIn: 'root',
})
export class DocxService {
  private showService = inject(ShowService);
  private showSongService = inject(ShowSongService);
  private textRenderingService = inject(TextRenderingService);
  private userService = inject(UserService);
  private configService = inject(ConfigService);

  public async create(showId: string, options: DownloadOptions = {}): Promise<void> {
    const data = await this.prepareData(showId);
    if (!data) return;
    const docx = await this.loadDocx();
    const {show, songs, user, config} = data;
    const type = new ShowTypePipe().transform(show.showType);
    const title = `${type} ${show.date.toDate().toLocaleDateString()}`;

    const paragraphs = [...this.renderTitle(docx, title), ...this.renderSongs(docx, songs, options, config)];

    const sections: DocxSectionOptions[] = [
      {
        properties: {
          page: {
            margin: {top: 400, bottom: 400, left: 400, right: 400},
          },
        },
        children: paragraphs,
      },
    ];
    const document = this.prepareNewDocument(docx, type, user.name, options, sections);

    const blob = await docx.Packer.toBlob(document);

    // saveAs from FileSaver will download the file
    this.saveAs(blob, `${title}.docx`);
  }

  private prepareNewDocument(docx: DocxModule, type: string, name: string, options: DownloadOptions, sections: DocxSectionOptions[]): DocxDocument {
    return new docx.Document({
      creator: name,
      title: type,
      description: '... mit Beschreibung',
      sections,
      styles: {
        paragraphStyles: [
          {
            id: 'songtext',
            name: 'Song Text',
            basedOn: 'Normal',
            next: 'Normal',
            quickFormat: true,
            run: options?.chordMode === 'hide' ? {} : {font: 'courier new'},
            paragraph: {indent: {left: 0}},
          },
          {
            id: 'licence',
            name: 'Lizenz',
            basedOn: 'Normal',
            next: 'Normal',
            quickFormat: true,
            run: {size: 15, color: '#666666'},
            paragraph: {indent: {left: 0}},
          },
        ],
      },
    });
  }

  private renderSongs(
    docx: DocxModule,
    songs: {
      showSong: ShowSong;
      sections: Section[];
    }[],
    options: DownloadOptions,
    config: Config
  ): DocxParagraph[] {
    return songs.reduce((p: DocxParagraph[], song) => [...p, ...this.renderSong(docx, song.showSong, song.showSong, song.sections, options, config)], []);
  }

  private renderSong(docx: DocxModule, showSong: ShowSong, song: Song, sections: Section[], options: DownloadOptions, config: Config): DocxParagraph[] {
    const songTitle = this.renderSongTitle(docx, song);
    const copyright = this.renderCopyright(docx, song, options, config);
    const songText = this.renderSongText(docx, sections, options?.chordMode ?? showSong.chordMode);

    return copyright ? [songTitle, copyright, ...songText] : [songTitle, ...songText];
  }

  private renderSongText(docx: DocxModule, sections: Section[], chordMode: ChordMode): DocxParagraph[] {
    return sections.reduce((p: DocxParagraph[], section) => [...p, ...this.renderSection(docx, section, chordMode)], []);
  }

  private renderSongTitle(docx: DocxModule, song: Song): DocxParagraph {
    return new docx.Paragraph({
      text: song.title,
      heading: docx.HeadingLevel.HEADING_2,
      thematicBreak: true,
      spacing: {
        before: 200,
      },
    });
  }

  private renderCopyright(docx: DocxModule, song: Song, options: DownloadOptions, config: Config): DocxParagraph | null {
    if (!options?.copyright) {
      return null;
    }

    const label = song.label ? song.label + ', ' : '';
    const artist = song.artist ? song.artist + ', ' : '';
    const termsOfUse = song.termsOfUse ? song.termsOfUse + ', ' : '';
    const origin = song.origin ? song.origin + ', ' : '';
    const licence = song.legalOwner === 'CCLI' ? 'CCLI-Liednummer: ' + song.legalOwnerId + ', CCLI-Lizenz: ' + config.ccliLicenseId : 'CCLI-Liednummer: ' + song.legalOwnerId;

    return new docx.Paragraph({
      text: artist + label + termsOfUse + origin + licence,
      style: 'licence',
    });
  }

  private renderSection(docx: DocxModule, section: Section, chordMode: ChordMode): DocxParagraph[] {
    return section.lines
      .filter(line => {
        if (line.type === LineType.text) {
          return true;
        }
        switch (chordMode) {
          case 'show':
            return true;
          case 'hide':
            return false;
          case 'onlyFirst':
            return section.number === 0;
        }
      })
      .map((line, i) => this.renderLine(docx, line, i === 0));
  }

  private renderLine(docx: DocxModule, line: Line, isFirstLine: boolean): DocxParagraph {
    const spacing = isFirstLine ? {before: 200} : {};
    return new docx.Paragraph({
      text: line.text,
      style: 'songtext',
      spacing,
    });
  }

  private renderTitle(docx: DocxModule, type: string): DocxParagraph[] {
    const songTitle = new docx.Paragraph({
      text: type,
      heading: docx.HeadingLevel.HEADING_1,
      thematicBreak: true,
    });

    return [songTitle];
  }

  private async prepareData(showId: string): Promise<{
    songs: {showSong: ShowSong; sections: Section[]}[];
    show: Show;
    user: User;
    config: Config;
  } | null> {
    const show = await firstValueFrom(this.showService.read$(showId));
    if (!show) return null;
    const user = await this.userService.getUserbyId(show.owner);
    if (!user) return null;
    const config = await this.configService.get();
    if (!config) return null;

    const showSongs = await this.showSongService.list(showId);
    const songsLoaded = showSongs
      .map(showSong => {
        const sections = this.textRenderingService.parse(showSong.text, {
          baseKey: showSong.keyOriginal,
          targetKey: showSong.key,
        });
        return {
          showSong,
          sections,
        };
      })
      .filter(_ => !!_)
      .map(
        _ =>
          _ as {
            showSong: ShowSong;
            sections: Section[];
          }
      );
    const songs = show.order.map(_ => songsLoaded.find(f => f.showSong.id === _)).filter(_ => !!_);
    return {songs, show, user, config};
  }

  private saveAs(blob: Blob, fileName: string) {
    const a = document.createElement('a');

    document.body.appendChild(a);
    a.setAttribute('target', '_self');
    a.style.display = 'none';

    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = fileName;
    a.click();

    setTimeout(() => {
      window.URL.revokeObjectURL(url);
      document.body.removeChild(a);
    }, 1000);
  }

  private loadDocx(): Promise<DocxModule> {
    return import('docx');
  }
}
